Wat is virtio-nvgpu?
virtio-nvgpu is een experimenteel virtio-apparaat dat NVIDIA GPU's toegankelijk maakt voor KVM-virtuele machines (guests) met minimaal prestatieverlies (maximaal 2%). In tegenstelling tot API-level translation, stuurt dit project NVIDIA kernel driver ioctl's direct door op ABI-niveau, waardoor de guest gebruik kan maken van ongewijzigde NVIDIA user-mode drivers.
Doel en Functionaliteit
Het hoofddoel is headless streaming. Hierbij rendert een compositor in de VM frames en codeert deze via NVENC op de GPU, om vervolgens alleen de gecomprimeerde videostream te verzenden. De host behoudt de fysieke kaart, terwijl de guest bijna native prestaties ervaart.
Belangrijkste kenmerken en prestaties
- Prestaties: Bij typische game-frames (boven de 2 ms) is het verschil met bare metal verwaarloosbaar.
- CPU-belasting: De CPU-overhead is minimaal omdat er geen zware serialisatie van individuele draw-calls plaatsvindt.
- Multi-tenancy: Het is mogelijk om meerdere guests (tot vier getest) gelijktijdig op één GPU te laten draaien met een gelijkmatige verdeling van resources.
- Scope: Ondersteunt Vulkan, OpenGL (headless EGL), CUDA-geheugenallocatie en NVENC/NVDEC. Fysieke display-output (scanout) valt buiten de scope.
Architectuur en Vergelijking
Het project is modulair opgebouwd met een splitsing in licenties (GPL voor de guest-driver, Apache-2.0 voor de device crate) om adoptie door verschillende VMM's te vergemakkelijken.
Vergelijking met andere methoden:
- Tegenover Venus: Waar Venus elke API-aanroep serialiseert (hoge latentie en CPU-last), kruist
virtio-nvgpu de VM-grens alleen bij ioctls. Dit maakt GPU-side encoding (NVENC) mogelijk, wat bij Venus niet levensvatbaar is.
- Tegenover VFIO passthrough: In tegenstelling tot VFIO, waarbij de GPU volledig aan één VM wordt toegewezen, maakt
virtio-nvgpu het delen van de GPU tussen meerdere guests mogelijk.
virtio-nvgpu: Bijna native toegang tot NVIDIA GPU's in KVM-virtuele machines
virtio-nvgpu stuurt NVIDIA kernel driver ioctl's door tussen een Linux-guest en de host op het niveau van de driver ABI, waardoor vertaling op API-niveau volledig wordt omzeild. De guest maakt gebruik van de eigen, ongewijzigde user-mode drivers van NVIDIA — dezelfde bibliotheken, dezelfde Vulkan en NVENC, die communiceren met dezelfde kaart.
Het doel is headless streaming: een compositor binnen de VM rendert, stelt frames samen en codeert deze op de GPU, om vervolgens gecomprimeerde video te verzenden. De VM heeft geen monitor en de host behoudt de kaart.
Huidige status
Het project is functioneel en is gemeten. Een Wayland-client presenteert binnen een guest, de capture-laag codeert op het apparaat van het spel, en de H.264-output komt aan de andere kant uit — 618 frames die door ffmpeg zonder fouten worden gedecodeerd.
Prestaties
Gemeten op een RTX 3060 (driver 595.99.02), waarbij de guest is vergeleken met de host op bare metal met een identieke headless Vulkan-belasting:
| Wat de host nodig heeft voor één frame | Guest frame-tijd | Verschil t.o.v. bare metal |
| 39 ms | 9,9 ms | −0,4% (binnen de ruismarge) |
| 2,0 ms | 0,5 ms | −0,7% |
| 0,5 ms | 0,05 ms | +1,7% |
| (een wake kost ~0,02 ms, en het frame is de helft daarvan) | — | +7,1% |
| — | — | +40,8% |
Boven een frame-tijd van ongeveer 2 ms — wat geldt voor elk frame dat een game tekent — zit een guest binnen 2% van bare metal. Daaronder begint de kostprijs van het wachten op de GPU zichtbaar te worden bij frames die nauwelijks bestaan.
CPU-belasting
CPU-verbruik is de andere helft van de vergelijking, omdat een gedeelde GPU alleen zinvol is als de guests weinig resources verbruiken. Gemeten bij een onbeperkte snelheid van ~100 fps gedurende 12 seconden voor één guest:
- Host, bare metal: 0,40 s CPU gebruikt
- Guest: 0,37 s CPU gebruikt
Een guest kost hetzelfde als de host. Er wordt niets verbruikt aan forwarding in een render-loop, omdat er niets wordt geforward: NVIDIA's user-mode driver dient via geheugen dat is gemapt, en dat geheugen is dat van de host. Over 813.691 frames bediende de backend 13.792 berichten — gemiddeld één crossing per 59 frames, waarvan bijna alles apparaat-setup was.
Voor de volledige methode en ruwe resultaten zie BENCHMARKS.md.
Meerdere guests op één kaart
Bij vier guests op één RTX 3060 met dezelfde belasting in elke guest, waren de resultaten: 25,84, 26,49, 25,57 en 25,79 fps. Samen is dit 103,7 fps, tegenover 102,9 fps voor een enkele guest, met p50 frame-tijden van respectievelijk 39,165, 39,164, 39,168 en 39,165 ms. Het totaal verandert niet wanneer er guests worden toegevoegd en de verdeling is gelijk tot op vier decimalen.
Alle vier de guests renderen correct tegelijkertijd en coderen tegelijkertijd H.264, elk precies op 60 Hz, zonder dat de NVENC-sessielimiet is bereikt. Er is vier guests getest; dit is geen vastgestelde limiet.
Driver-versies
Gemeten op versie 595.99.02. Een A2000 op 615.71.09 rendert wel, maar is niet gebenchmarkt. De geleverde ABI-profielen zijn: 535.129.03, 580.178.04 en 595.71.05, gematcht op bereik; alles ouder dan de eerste wordt geweigerd.
Wat werkt (bekend)
- Een guest enumereert de kaart —
nvidia-smi rapporteert werkelijk vermogen en geheugen, en de deviceUUID is die van de host.
- Vulkan rendering:
vulkaninfo sluit af met 0, offscreen draws zijn pixel-correct.
- Een Wayland-client presenteert via een compositor in de guest.
- NVENC via Vulkan Video, waarbij encoding plaatsvindt op het apparaat van de client.
- Geïmporteerde buffers zijn het geheugen van de host, gemapt via een shared window.
Wat nog niet is gerealiseerd
- Meer dan vier guests, of guests die zwaardere taken uitvoeren dan
vkcube op 720p. (Vier delen de kaart gelijkmatig; acht is niet geprobeerd).
- Twee kaarten of twee driver-versies tegelijk. De cijfers komen van een RTX 3060 / 595.99.02; een RTX A2000 / 615.71.09 heeft gerenderd maar is niet gebenchmarkt.
- CUDA is geforward maar niet getest verder dan enumeratie; de jailer, per-versie driver shares en de multi-tenant envelope zijn nog niet gebouwd.
Projectstructuur
Het project is verdeeld over vier componenten en drie licentiezones. Deze splitsing is bewust: het guest-gedeelte moet GPL zijn om kernel-symbolen aan te kunnen raken, het host-gedeelte moet permissief zijn zodat anderen erop kunnen voortbouwen, en de definities die beide delen delen moeten vanuit beide kanten kunnen worden opgenomen.
| Directory | Licentie | Beschrijving |
driver/ | GPL-2.0 | Guest kernel module. Registreert /dev/nvidia*, forwardt ioctl en mmap over de virtqueue. Bewust niet ABI-aware. |
device/ | Apache-2.0 | Het virtio-apparaat, als een Rust crate zonder VMM in de dependency-lijst. Elk VMM-aspect is een trait. |
isolate/ | Apache-2.0 | Een design-notitie, nog geen code. De gesandboxte per-guest helper die de echte device FD's zal vasthouden. Momenteel houdt de backend deze zelf vast in het proces van de VMM. |
gen/ | — | Gegenereerde ABI-tabellen. Ingecheckt en reproduceerbaar. |
protocol/ | BSD-3-Clause OF GPL-2.0+ | Wire-formaat en ABI-definities gedeeld door beide helften. Dual-licensed zodat zowel de GPL-driver als de Apache-crate dezelfde headers kunnen includeren. |
De lay-out volgt chromeos/virtio-media, wat hetzelfde probleem oplost: één repository die een GPL guest-driver bevat naast een permissief gelicentieerde, VMM-agnostische device crate.
Gebruik vanuit een VMM
device/ is afhankelijk van geen enkele virtual machine monitor. Een VMM adopteert het apparaat door een kleine set traits te implementeren — descriptor chains als Read/Write, een event queue, guest memory mapping, host memory mapping — en krijgt zo het volledige apparaat zonder de crate te hoeven patchen. Optionele functionaliteiten degraderen in plaats van dat de build faalt, zodat een VMM het kan adopteren voordat elke feature wordt ondersteund.
Buffer- en window-boekhouding bevindt zich in device/. De VMM levert enkel ruwe map- en unmap-functies.
Eén onderdeel zal geen trait zijn: de isolate. Het beoogde ontwerp draait één gesandboxte helper-proces per guest-proces. Het adopteren hiervan betekent uiteindelijk het overnemen van een procesmodel, niet slechts een library-dependency. Deze helper is nog niet geschreven; momenteel houdt de backend de device descriptors zelf vast.
Motivatie en Ontwerp
De gewenste streaming-pipeline
- Guest VM (headless, geen fysiek display)
- Game / applicatie → Vulkan of OpenGL
- Wayland compositor (guest-zijde) → stelt alle vensters samen → CUDA zero-copy import van het samengestelde frame
- NVENC hardware encoder (guest-zijde) → H.264 / H.265 bitstream (~100 KB per frame)
- Stream naar remote client
De volledige render → composite → encode pipeline draait op de GPU, binnen de guest. Alleen de gecomprimeerde bitstream verlaat de VM. Dit vereist dat de guest echte, driver-level toegang heeft tot GPU-resources: buffer handles, fences, CUDA device pointers en NVENC-sessies.
Waarom bestaande benaderingen tekortschieten
- virtio-gpu + Venus (API-level translation): Venus serialiseert elke Vulkan- of OpenGL-aanroep in de guest, transporteert deze over virtio en speelt deze aan de host-zijde af.
- Latentie: Bij workloads met veel draw-calls stapelt de latentie zich op. Games voeren 1.000–5.000 draw-calls per frame uit. Bij 60 fps is het budget 16,6 ms; 1–3 ms aan serialisatie verbruikt direct 6–18% van dit budget.
- CPU-overhead: Serialisatie, transport en replay verbruiken host-CPU die de applicatie nodig heeft.
- Guest-side encoding: GPU-buffers zijn eigendom van de host. De guest-compositor kan ze niet zien of importeren, waardoor er geen praktisch pad is naar een
CUdeviceptr in de guest die naar een door Venus beheerde buffer wijst. Dit maakt NVENC onmogelijk zonder een volledige CPU readback en kopie.
- DRM native context (Intel / AMD): De guest draait de echte Mesa-driver, bouwt command-buffers lokaal op, en alleen de submissions passeren de grens. Dit werkt correct voor buffer-ownership en encoding, maar bestaat niet voor NVIDIA.
- VFIO passthrough: Native prestaties en een complete driver-stack in de guest, maar het wijst de gehele GPU toe aan één VM. In multi-tenant omgevingen is dit vaak geen optie.
Wat virtio-nvgpu anders doet
Vertaling vindt plaats op het niveau van de kernel driver (ioctls naar /dev/nvidia*), niet op het niveau van de graphics API. De guest draait de echte user-mode bibliotheken van NVIDIA, die GPU command-buffers lokaal in de guest bouwen. Individuele draw-calls worden nooit geserialiseerd.
| Aspect | Venus | virtio-nvgpu |
| Per draw call | serialize + transport + deserialize + replay | lokale functie-aanroep (geen VM exit) |
| Boundary crossings per frame | ~2.000 berichten (één per API-aanroep) | ~5–20 berichten (queue submits + allocs) |
| GPU command buffers | gegenereerd op HOST na replay | gegenereerd in GUEST door NVIDIA-compiler |
| CPU overhead | hoog (serialisatie + deserialisatie) | bijna nul voor rendering (alleen ioctl forwarding) |
| Guest buffer ownership | HOST bezit buffers; compositor heeft geen zicht | GUEST bezit buffers; compositor heeft volledige controle |
| Guest NVENC | niet levensvatbaar | werkt (echte CUDA interop) |
Werking
- Guest kernel driver: Registreert
/dev/nvidiactl, /dev/nvidia0…N en /dev/nvidia-uvm. Bij een ioctl() wordt het verzoek geserialiseerd naar een control virtqueue. Bij een mmap() wordt de juiste shared-memory regio in het aanroepende proces gemapt met de juiste caching-attributen. Het kopieert ruwe bytes en maakt geen ABI-beslissingen.
- Device crate: Ontvangt verzoeken, mapt guest-handles naar host-device file descriptors, voert ABI-aware vertaling uit van ioctl-parameters (herschrijven van embedded pointers en file descriptors) en voert deze uit tegen de apparaten van de host. De boekhouding van buffers en windows bevindt zich hier.
- Events: Een tweede virtqueue draait in de tegenovergestelde richting. De host houdt elk geopend descriptor in de gaten en meldt wanneer deze leesbaar wordt. Dit is hoe een guest die wacht op de GPU wordt gewekt. Zonder dit zou de guest moeten pollen.
- Isolate (nog niet gebouwd): Het plan is een gesandboxte helper per guest-proces die de echte device FD's vasthoudt en de
ioctl(2) aanroepen onbevoorrecht uitvoert. Momenteel doet de backend dit zelf binnen het proces van de VMM.
Architectuur-overzicht
Guest Application → NVIDIA Vulkan / GL / CUDA → driver/ (GPL) → serialize → virtqueue / mmap → shared region → VM exit → VMM (device traits) → device/ (Apache-2.0) → translate embedded FDs and pointers → ioctl(host /dev/nvidia*) → Host NVIDIA driver → GPU.
Reikwijdte (Scope)
Ondersteund
- Vulkan rendering, inclusief presentatie aan een compositor in de guest (vereist
/dev/nvidia-drm en /dev/nvidia-modeset, beide geïmplementeerd; deze zijn niet voor display, maar om buffers deelbaar te maken).
- OpenGL rendering (headless EGL).
- CUDA device memory allocatie.
- CUDA $\leftrightarrow$ Vulkan/GL interop, zero-copy, GPU-side pointers.
- NVENC encoding vanuit CUDA device pointers; NVDEC decoding.
Buiten scope
cudaMallocManaged() / volledige unified virtual memory.
- Scanout: geen fysieke display-output (frames verlaten de box als video, niet als pixels op een kabel).
- MIG, SR-IOV.
- Willekeurige NVIDIA driver-versies: elk ondersteund bereik is expliciet.
Prestaties-vergelijking
Gemeten op één kaart met een synthetische load.
| Meting | virtio-nvgpu | Venus (by design) |
| GPU-bound (≥2 ms per frame) | 98–100% van bare metal | 90–97% |
| Zeer lichte frames (≤0.5 ms) | 93–71% van bare metal | — |
| CPU-kosten rendering guest | gelijk aan bare metal | hoog (serialize en replay) |
| Host crossings per frame | ~0,02 | duizenden |
| Guest-side NVENC | werkt, zero-copy | niet levensvatbaar |
Het verschil is structureel: Venus kruist de VM-grens per API-aanroep. virtio-nvgpu kruist deze per ioctl — en een render-loop voert geen ioctls uit, omdat submission een write is naar gemapt geheugen. Wat overblijft bij zeer lichte frames is niet de forwarding, maar het wachten: de guest slaapt voor de GPU, en de wake-up kost ~0,02 ms.
Driver-versies en ABI-profielen
Omdat NVIDIA's kernel driver ABI niet stabiel is, is ondersteuning expliciet.
Geleverde ABI-profielen:
- 535.129.03: Dekt versie 535.129.03 tot de volgende profielversie.
- 580.178.04: Dekt versie 580.178.04 tot de volgende profielversie.
- 595.71.05: Dekt versie 595.71.05 en nieuwer.
Profielen werken met bereiken: een release tussen twee profielen gebruikt het lagere profiel. Alles ouder dan 535.129.03 wordt geweigerd. Een driver die veel nieuwer is dan het laatste profiel wordt geaccepteerd in de aanname dat de benodigde zaken niet zijn veranderd.
Daadwerkelijk gedraaide versies:
- 595.99.02 (RTX 3060): Volledig getest (renders, presents, encodes, benchmarks).
- 615.71.09 (RTX A2000): Enumereert en rendert; niet gebenchmarkt.
Hoe een profiel wordt gebouwd
De kosten hiervan zijn beperkt omdat:
- Profielen werken met bereiken.
- De struct-definities mechanisch worden afgeleid van NVIDIA's
open-gpu-kernel-modules (via probes per veld voor sizeof en offsetof), in plaats van handmatige transcriptie.
- De logica over welke commando's bestaan en veilig zijn, de upstream
nvproxy volgt.
Eerdere werken (Prior Art)
- gVisor nvproxy: De directe inspiratie. Forwardt NVIDIA ioctls van gesandboxte containers naar de host driver. Ondersteunt Vulkan, OpenGL, CUDA en NVENC in productie. De ABI-definities en handler-logica zijn de primaire referentie.
- chromeos/virtio-media: Het sjabloon voor de projectstructuur (GPL guest driver naast een VMM-agnostische Rust device crate).
- WSL2 /dev/dxg: Een productie-driver-level GPU proxy over een echte virtualisatiegrens (Windows host).
- DRM native context (Intel / AMD): Hetzelfde doel, bereikt voor andere vendors.
virtio-nvgpu streeft naar een gelijkwaardige capaciteit voor NVIDIA.
virtio-nvgpu: Bijna native toegang tot NVIDIA GPU's in KVM-virtuele machines
virtio-nvgpu stuurt NVIDIA kernel driver ioctl's door tussen een Linux-guest en de host op het niveau van de driver ABI, waardoor vertaling op API-niveau volledig wordt omzeild. De guest maakt gebruik van de eigen, ongewijzigde user-mode drivers van NVIDIA — dezelfde bibliotheken, dezelfde Vulkan en NVENC, die communiceren met dezelfde kaart.
Het doel is headless streaming: een compositor binnen de VM rendert, stelt frames samen en codeert deze op de GPU, om vervolgens gecomprimeerde video te verzenden. De VM heeft geen monitor en de host behoudt de kaart.
Huidige status
Het project is functioneel en is gemeten. Een Wayland-client presenteert binnen een guest, de capture-laag codeert op het apparaat van het spel, en de H.264-output komt aan de andere kant uit — 618 frames die door ffmpeg zonder fouten worden gedecodeerd.
Prestaties
Gemeten op een RTX 3060 (driver 595.99.02), waarbij de guest is vergeleken met de host op bare metal met een identieke headless Vulkan-belasting:
| Wat de host nodig heeft voor één frame | Guest frame-tijd | Verschil t.o.v. bare metal |
| 39 ms | 9,9 ms | −0,4% (binnen de ruismarge) |
| 2,0 ms | 0,5 ms | −0,7% |
| 0,5 ms | 0,05 ms | +1,7% |
| (een wake kost ~0,02 ms, en het frame is de helft daarvan) | — | +7,1% |
| — | — | +40,8% |
Boven een frame-tijd van ongeveer 2 ms — wat geldt voor elk frame dat een game tekent — zit een guest binnen 2% van bare metal. Daaronder begint de kostprijs van het wachten op de GPU zichtbaar te worden bij frames die nauwelijks bestaan.
CPU-belasting
CPU-verbruik is de andere helft van de vergelijking, omdat een gedeelde GPU alleen zinvol is als de guests weinig resources verbruiken. Gemeten bij een onbeperkte snelheid van ~100 fps gedurende 12 seconden voor één guest:
- Host, bare metal: 0,40 s CPU gebruikt
- Guest: 0,37 s CPU gebruikt
Een guest kost hetzelfde als de host. Er wordt niets verbruikt aan forwarding in een render-loop, omdat er niets wordt geforward: NVIDIA's user-mode driver dient via geheugen dat is gemapt, en dat geheugen is dat van de host. Over 813.691 frames bediende de backend 13.792 berichten — gemiddeld één crossing per 59 frames, waarvan bijna alles apparaat-setup was.
Voor de volledige methode en ruwe resultaten zie BENCHMARKS.md.
Meerdere guests op één kaart
Bij vier guests op één RTX 3060 met dezelfde belasting in elke guest, waren de resultaten: 25,84, 26,49, 25,57 en 25,79 fps. Samen is dit 103,7 fps, tegenover 102,9 fps voor een enkele guest, met p50 frame-tijden van respectievelijk 39,165, 39,164, 39,168 en 39,165 ms. Het totaal verandert niet wanneer er guests worden toegevoegd en de verdeling is gelijk tot op vier decimalen.
Alle vier de guests renderen correct tegelijkertijd en coderen tegelijkertijd H.264, elk precies op 60 Hz, zonder dat de NVENC-sessielimiet is bereikt. Er is vier guests getest; dit is geen vastgestelde limiet.
Driver-versies
Gemeten op versie 595.99.02. Een A2000 op 615.71.09 rendert wel, maar is niet gebenchmarkt. De geleverde ABI-profielen zijn: 535.129.03, 580.178.04 en 595.71.05, gematcht op bereik; alles ouder dan de eerste wordt geweigerd.
Wat werkt (bekend)
- Een guest enumereert de kaart —
nvidia-smi rapporteert werkelijk vermogen en geheugen, en de deviceUUID is die van de host.
- Vulkan rendering:
vulkaninfo sluit af met 0, offscreen draws zijn pixel-correct.
- Een Wayland-client presenteert via een compositor in de guest.
- NVENC via Vulkan Video, waarbij encoding plaatsvindt op het apparaat van de client.
- Geïmporteerde buffers zijn het geheugen van de host, gemapt via een shared window.
Wat nog niet is gerealiseerd
- Meer dan vier guests, of guests die zwaardere taken uitvoeren dan
vkcube op 720p. (Vier delen de kaart gelijkmatig; acht is niet geprobeerd).
- Twee kaarten of twee driver-versies tegelijk. De cijfers komen van een RTX 3060 / 595.99.02; een RTX A2000 / 615.71.09 heeft gerenderd maar is niet gebenchmarkt.
- CUDA is geforward maar niet getest verder dan enumeratie; de jailer, per-versie driver shares en de multi-tenant envelope zijn nog niet gebouwd.
Projectstructuur
Het project is verdeeld over vier componenten en drie licentiezones. Deze splitsing is bewust: het guest-gedeelte moet GPL zijn om kernel-symbolen aan te kunnen raken, het host-gedeelte moet permissief zijn zodat anderen erop kunnen voortbouwen, en de definities die beide delen delen moeten vanuit beide kanten kunnen worden opgenomen.
| Directory | Licentie | Beschrijving |
driver/ | GPL-2.0 | Guest kernel module. Registreert /dev/nvidia*, forwardt ioctl en mmap over de virtqueue. Bewust niet ABI-aware. |
device/ | Apache-2.0 | Het virtio-apparaat, als een Rust crate zonder VMM in de dependency-lijst. Elk VMM-aspect is een trait. |
isolate/ | Apache-2.0 | Een design-notitie, nog geen code. De gesandboxte per-guest helper die de echte device FD's zal vasthouden. Momenteel houdt de backend deze zelf vast in het proces van de VMM. |
gen/ | — | Gegenereerde ABI-tabellen. Ingecheckt en reproduceerbaar. |
protocol/ | BSD-3-Clause OF GPL-2.0+ | Wire-formaat en ABI-definities gedeeld door beide helften. Dual-licensed zodat zowel de GPL-driver als de Apache-crate dezelfde headers kunnen includeren. |
De lay-out volgt chromeos/virtio-media, wat hetzelfde probleem oplost: één repository die een GPL guest-driver bevat naast een permissief gelicentieerde, VMM-agnostische device crate.
Gebruik vanuit een VMM
device/ is afhankelijk van geen enkele virtual machine monitor. Een VMM adopteert het apparaat door een kleine set traits te implementeren — descriptor chains als Read/Write, een event queue, guest memory mapping, host memory mapping — en krijgt zo het volledige apparaat zonder de crate te hoeven patchen. Optionele functionaliteiten degraderen in plaats van dat de build faalt, zodat een VMM het kan adopteren voordat elke feature wordt ondersteund.
Buffer- en window-boekhouding bevindt zich in device/. De VMM levert enkel ruwe map- en unmap-functies.
Eén onderdeel zal geen trait zijn: de isolate. Het beoogde ontwerp draait één gesandboxte helper-proces per guest-proces. Het adopteren hiervan betekent uiteindelijk het overnemen van een procesmodel, niet slechts een library-dependency. Deze helper is nog niet geschreven; momenteel houdt de backend de device descriptors zelf vast.
Motivatie en Ontwerp
De gewenste streaming-pipeline
- Guest VM (headless, geen fysiek display)
- Game / applicatie → Vulkan of OpenGL
- Wayland compositor (guest-zijde) → stelt alle vensters samen → CUDA zero-copy import van het samengestelde frame
- NVENC hardware encoder (guest-zijde) → H.264 / H.265 bitstream (~100 KB per frame)
- Stream naar remote client
De volledige render → composite → encode pipeline draait op de GPU, binnen de guest. Alleen de gecomprimeerde bitstream verlaat de VM. Dit vereist dat de guest echte, driver-level toegang heeft tot GPU-resources: buffer handles, fences, CUDA device pointers en NVENC-sessies.
Waarom bestaande benaderingen tekortschieten
- virtio-gpu + Venus (API-level translation): Venus serialiseert elke Vulkan- of OpenGL-aanroep in de guest, transporteert deze over virtio en speelt deze aan de host-zijde af.
- Latentie: Bij workloads met veel draw-calls stapelt de latentie zich op. Games voeren 1.000–5.000 draw-calls per frame uit. Bij 60 fps is het budget 16,6 ms; 1–3 ms aan serialisatie verbruikt direct 6–18% van dit budget.
- CPU-overhead: Serialisatie, transport en replay verbruiken host-CPU die de applicatie nodig heeft.
- Guest-side encoding: GPU-buffers zijn eigendom van de host. De guest-compositor kan ze niet zien of importeren, waardoor er geen praktisch pad is naar een
CUdeviceptr in de guest die naar een door Venus beheerde buffer wijst. Dit maakt NVENC onmogelijk zonder een volledige CPU readback en kopie.
- DRM native context (Intel / AMD): De guest draait de echte Mesa-driver, bouwt command-buffers lokaal op, en alleen de submissions passeren de grens. Dit werkt correct voor buffer-ownership en encoding, maar bestaat niet voor NVIDIA.
- VFIO passthrough: Native prestaties en een complete driver-stack in de guest, maar het wijst de gehele GPU toe aan één VM. In multi-tenant omgevingen is dit vaak geen optie.
Wat virtio-nvgpu anders doet
Vertaling vindt plaats op het niveau van de kernel driver (ioctls naar /dev/nvidia*), niet op het niveau van de graphics API. De guest draait de echte user-mode bibliotheken van NVIDIA, die GPU command-buffers lokaal in de guest bouwen. Individuele draw-calls worden nooit geserialiseerd.
| Aspect | Venus | virtio-nvgpu |
| Per draw call | serialize + transport + deserialize + replay | lokale functie-aanroep (geen VM exit) |
| Boundary crossings per frame | ~2.000 berichten (één per API-aanroep) | ~5–20 berichten (queue submits + allocs) |
| GPU command buffers | gegenereerd op HOST na replay | gegenereerd in GUEST door NVIDIA-compiler |
| CPU overhead | hoog (serialisatie + deserialisatie) | bijna nul voor rendering (alleen ioctl forwarding) |
| Guest buffer ownership | HOST bezit buffers; compositor heeft geen zicht | GUEST bezit buffers; compositor heeft volledige controle |
| Guest NVENC | niet levensvatbaar | werkt (echte CUDA interop) |
Werking
- Guest kernel driver: Registreert
/dev/nvidiactl, /dev/nvidia0…N en /dev/nvidia-uvm. Bij een ioctl() wordt het verzoek geserialiseerd naar een control virtqueue. Bij een mmap() wordt de juiste shared-memory regio in het aanroepende proces gemapt met de juiste caching-attributen. Het kopieert ruwe bytes en maakt geen ABI-beslissingen.
- Device crate: Ontvangt verzoeken, mapt guest-handles naar host-device file descriptors, voert ABI-aware vertaling uit van ioctl-parameters (herschrijven van embedded pointers en file descriptors) en voert deze uit tegen de apparaten van de host. De boekhouding van buffers en windows bevindt zich hier.
- Events: Een tweede virtqueue draait in de tegenovergestelde richting. De host houdt elk geopend descriptor in de gaten en meldt wanneer deze leesbaar wordt. Dit is hoe een guest die wacht op de GPU wordt gewekt. Zonder dit zou de guest moeten pollen.
- Isolate (nog niet gebouwd): Het plan is een gesandboxte helper per guest-proces die de echte device FD's vasthoudt en de
ioctl(2) aanroepen onbevoorrecht uitvoert. Momenteel doet de backend dit zelf binnen het proces van de VMM.
Architectuur-overzicht
Guest Application → NVIDIA Vulkan / GL / CUDA → driver/ (GPL) → serialize → virtqueue / mmap → shared region → VM exit → VMM (device traits) → device/ (Apache-2.0) → translate embedded FDs and pointers → ioctl(host /dev/nvidia*) → Host NVIDIA driver → GPU.
Reikwijdte (Scope)
Ondersteund
- Vulkan rendering, inclusief presentatie aan een compositor in de guest (vereist
/dev/nvidia-drm en /dev/nvidia-modeset, beide geïmplementeerd; deze zijn niet voor display, maar om buffers deelbaar te maken).
- OpenGL rendering (headless EGL).
- CUDA device memory allocatie.
- CUDA $\leftrightarrow$ Vulkan/GL interop, zero-copy, GPU-side pointers.
- NVENC encoding vanuit CUDA device pointers; NVDEC decoding.
Buiten scope
cudaMallocManaged() / volledige unified virtual memory.
- Scanout: geen fysieke display-output (frames verlaten de box als video, niet als pixels op een kabel).
- MIG, SR-IOV.
- Willekeurige NVIDIA driver-versies: elk ondersteund bereik is expliciet.
Prestaties-vergelijking
Gemeten op één kaart met een synthetische load.
| Meting | virtio-nvgpu | Venus (by design) |
| GPU-bound (≥2 ms per frame) | 98–100% van bare metal | 90–97% |
| Zeer lichte frames (≤0.5 ms) | 93–71% van bare metal | — |
| CPU-kosten rendering guest | gelijk aan bare metal | hoog (serialize en replay) |
| Host crossings per frame | ~0,02 | duizenden |
| Guest-side NVENC | werkt, zero-copy | niet levensvatbaar |
Het verschil is structureel: Venus kruist de VM-grens per API-aanroep. virtio-nvgpu kruist deze per ioctl — en een render-loop voert geen ioctls uit, omdat submission een write is naar gemapt geheugen. Wat overblijft bij zeer lichte frames is niet de forwarding, maar het wachten: de guest slaapt voor de GPU, en de wake-up kost ~0,02 ms.
Driver-versies en ABI-profielen
Omdat NVIDIA's kernel driver ABI niet stabiel is, is ondersteuning expliciet.
Geleverde ABI-profielen:
- 535.129.03: Dekt versie 535.129.03 tot de volgende profielversie.
- 580.178.04: Dekt versie 580.178.04 tot de volgende profielversie.
- 595.71.05: Dekt versie 595.71.05 en nieuwer.
Profielen werken met bereiken: een release tussen twee profielen gebruikt het lagere profiel. Alles ouder dan 535.129.03 wordt geweigerd. Een driver die veel nieuwer is dan het laatste profiel wordt geaccepteerd in de aanname dat de benodigde zaken niet zijn veranderd.
Daadwerkelijk gedraaide versies:
- 595.99.02 (RTX 3060): Volledig getest (renders, presents, encodes, benchmarks).
- 615.71.09 (RTX A2000): Enumereert en rendert; niet gebenchmarkt.
Hoe een profiel wordt gebouwd
De kosten hiervan zijn beperkt omdat:
- Profielen werken met bereiken.
- De struct-definities mechanisch worden afgeleid van NVIDIA's
open-gpu-kernel-modules (via probes per veld voor sizeof en offsetof), in plaats van handmatige transcriptie.
- De logica over welke commando's bestaan en veilig zijn, de upstream
nvproxy volgt.
Eerdere werken (Prior Art)
- gVisor nvproxy: De directe inspiratie. Forwardt NVIDIA ioctls van gesandboxte containers naar de host driver. Ondersteunt Vulkan, OpenGL, CUDA en NVENC in productie. De ABI-definities en handler-logica zijn de primaire referentie.
- chromeos/virtio-media: Het sjabloon voor de projectstructuur (GPL guest driver naast een VMM-agnostische Rust device crate).
- WSL2 /dev/dxg: Een productie-driver-level GPU proxy over een echte virtualisatiegrens (Windows host).
- DRM native context (Intel / AMD): Hetzelfde doel, bereikt voor andere vendors.
virtio-nvgpu streeft naar een gelijkwaardige capaciteit voor NVIDIA.