Virtio-nvgpu:在KVM虚拟机内部实现接近原生的NVIDIA GPU访问

Hacker News Top 工具

摘要

Virtio-nvgpu实现了在KVM虚拟机内部接近原生的NVIDIA GPU访问,在帧渲染方面实现了与裸机相比仅2%以内的性能差距,并支持多个虚拟机之间的高效GPU共享。

暂无内容
查看原文
查看缓存全文

缓存时间: 2026/09/24 04:02

nestrilabs/virtio-nvgpu

来源:https://github.com/nestrilabs/virtio-nvgpu

virtio-nvgpu

在 KVM 客户机内实现接近原生的 NVIDIA GPU 访问。客户机渲染性能与宿主机裸机运行相比在 2% 误差范围内,且 CPU 开销相同。

virtio-nvgpu驱动程序 ABI 级别 转发 NVIDIA 内核驱动程序的 ioctl 调用,在 Linux 客户机和宿主机之间直接传递,完全绕过了 API 层级的翻译。客户机运行的是未经修改的 NVIDIA 原生用户模式驱动程序——使用相同的库、相同的 Vulkan 和 NVENC,与同一块显卡通信。

其目标是 无头流媒体:客户机虚拟机内的合成器负责在 GPU 上渲染、合成和编码帧,然后输出压缩后的视频。客户机无需显示器,宿主机保留对显卡的控制。

当前状态

它已可用,并经过实际测量。 一个 Wayland 客户端在客户机内运行,捕获层在游戏自身的设备上进行编码,H.264 视频流从另一端输出——618 帧数据被 ffmpeg 成功解码,未发生错误。

在 RTX 3060(驱动程序 595.99.02)上测量,客户机与 同一台宿主机的裸机环境 对比,运行相同的无头 Vulkan 负载:

宿主机处理单帧耗时客户机帧耗时
39 ms-0.4%比裸机更快,在噪声误差范围内
9.9 ms-0.7%
2.0 ms+1.7%
0.5 ms+7.1%一次唤醒成本约 0.02 ms,而帧时间仅为一半
0.05 ms+40.8%

当每帧处理时间高于约 2 毫秒时——这涵盖大多数游戏绘制的每一帧——客户机性能与裸机相比在 2% 误差范围内。 低于此阈值时,等待 GPU 的开销开始显现,相对于几乎不存在的帧成本变得显著。

CPU 开销是另一半考量,因为只有当客户机开销足够低时,共享 GPU 才有价值。以约 100 fps 运行 12 秒,一个客户机的情况:

CPU 占用时间
宿主机裸机0.40 s
客户机0.37 s

客户机的成本与宿主机相同。 在渲染循环中没有任何转发开销,因为根本无需转发:NVIDIA 用户模式驱动程序通过其已映射的内存提交命令,而该内存就是宿主机的内存。在超过 813,691 帧中,后端仅处理了 13,792 条消息——平均每 59 帧一次跨越,且几乎全部是设备初始化设置。

完整方法、原始运行数据以及这些数字 未能 证明的内容,请参阅:BENCHMARKS.md

单卡多客户机

四台客户机运行在同一张 RTX 3060 上,每台负载相同:25.84、26.49、25.57、25.79 fps ——总计 103.7 fps,与单客户机时的 102.9 fps 相近——p50 帧时间分别为 39.165、39.164、39.168 和 39.165 ms。随着客户机数量增加,总帧率几乎不变,分配均匀度精确到小数点后四位。

四台客户机同时正确渲染,并且四台 同时进行 H.264 编码,每台精确控制在 60 Hz,未触发 NVENC 会话数限制。

四台是已测试的数量,而非发现的上限。

驱动程序版本

已在 595.99.02 上测量;A2000 显卡使用 615.71.09 驱动程序可以渲染,但未进行基准测试。内置的 ABI 配置文件版本:535.129.03、580.178.04、595.71.05,支持范围按区间匹配,拒绝任何早于第一个版本的驱动程序。详情见下文。

已知可用功能

  • 客户机枚举显卡 —— nvidia-smi 报告真实的功耗和显存,deviceUUID 是宿主机的
  • Vulkan 渲染:vulkaninfo 正常退出,离屏绘制像素正确
  • Wayland 客户端通过客户机内的合成器呈现
  • 通过 Vulkan Video 使用 NVENC,在客户端自身设备上编码
  • 导入的缓冲区是宿主机的内存,通过共享窗口映射

尚未实现的功能

  • 超过四台客户机,或运行比 720p vkcube 更高负载的场景。四台可以均匀共享显卡;八台尚未尝试。
  • 两张显卡、两个驱动程序版本。 RTX 3060 / 595.99.02 是数据来源;RTX A2000 / 615.71.09 可以渲染但未进行基准测试。
  • CUDA 已转发但仅限枚举,未经测试;沙箱、按版本驱动的共享以及多租户封装尚未构建。

仓库布局

四个组件,三个许可区域。这种划分是有意为之:客户机半边必须是 GPL 协议才能接触内核符号,宿主机半边应采用宽松许可以便他人基于其构建,而两边共享的定义必须能被双方包含。

目录许可证内容说明
driver/GPL-2.0客户机内核模块。注册 /dev/nvidia*,通过 virtqueue 转发 ioctl 和 mmap。刻意不包含 ABI 感知逻辑。
device/Apache-2.0virtio 设备,作为一个 Rust crate,依赖列表中不含 VMM。所有 VMM 相关部分都是 trait。
isolate/Apache-2.0设计文档,尚无代码。 将作为沙箱化的每客户机辅助进程,持有真正的设备文件描述符。目前后端自身在 VMM 进程中持有它们。
gen/生成的 ABI 表。已提交且可重现。
protocol/BSD-3-Clause OR GPL-2.0+供双方共享的线路格式和 ABI 定义。采用双重许可,使 GPL 驱动程序和 Apache crate 可以包含相同的头文件。

布局遵循 chromeos/virtio-media (https://chromium.googlesource.com/chromiumos/platform/virtio-media/),该项目解决了相同的问题——一个仓库包含 GPL 客户机驱动程序和宽松许可、VMM 无关的设备 crate。

如何从 VMM 使用

device/ 不依赖任何虚拟机监控程序。VMM 通过实现一组小型 trait 来采用该设备——将描述符链实现为 Read/Write、事件队列、客户机内存映射、宿主机内存映射——无需修改 crate 即可获得完整的设备功能。可选功能会在不支持时优雅降级,而非构建失败,因此 VMM 可以在支持所有功能之前就采用它。

缓冲区和窗口簿记逻辑位于 device/ 中。VMM 仅提供原始的映射和取消映射操作,别无其他。

有一项 不会 是 trait:隔离机制。预期设计是为每个客户机进程运行一个沙箱化的辅助进程,因此最终采用它意味着需要继承一个 进程模型,而不仅仅是库依赖。该辅助进程尚未编写——目前后端自身持有设备描述符——isolate/ 是设计文档所在地,在完成编写前仅此而已。


设计动机

我们想要的流媒体管线

客户机 VM(无头,无物理显示器)
──────────────────────────────────────────

  游戏 / 应用程序
    │ Vulkan 或 OpenGL
    ▼
  Wayland 合成器(客户机侧)
    │ 合成所有窗口
    │ CUDA 零拷贝导入合成帧
    ▼
  NVENC 硬件编码器(客户机侧)
    │ H.264 / H.265 比特流(每帧约 100 KB)
    ▼
  流传输到远程客户端

整个 渲染 → 合成 → 编码 管线 在 GPU 上、在客户机内 运行。只有压缩后的比特流离开。这要求客户机对 GPU 资源拥有 真正的、驱动程序级别的访问权限:缓冲区句柄、栅栏、CUDA 设备指针、NVENC 会话。

为何现有方案不足

virtio-gpu + Venus(API 级别翻译)。 Venus 在客户机中序列化每个 Vulkan 或 OpenGL 调用,通过 virtio 传输,然后在宿主机端重放。对于此用例有三个问题:

  1. 延迟在绘制调用密集型负载上累积。 游戏每帧发出 1,000–5,000 个绘制调用,外加绑定、描述符更新和渲染通道转换,每个都需要单独序列化和重放。在 60 fps 下,帧预算为 16.6 毫秒;1–3 毫秒的序列化意味着在任何 GPU 工作之前就损失了 6–18% 的时间。
  2. CPU 开销显著。 序列化、传输和重放消耗宿主机 CPU,而这些 CPU 资源本是应用程序所需。在按计算资源计费且资源有限的环境中,这种浪费直接影响产出。
  3. 客户机侧编码不可行。 GPU 缓冲区由 宿主机 拥有。客户机合成器无法看到或导入它们,因此无法在客户机内将 CUdeviceptr 指向一个由 Venus 管理的缓冲区——这意味着必须进行完整的 CPU 回读和复制才能实现 NVENC。

DRM 原生上下文(Intel / AMD)。 客户机运行真实的 Mesa 驱动程序,在本地构建命令缓冲区,仅提交操作跨越边界。客户机侧的缓冲区所有权和编码工作正常。NVIDIA 平台不存在此方案。

VFIO 直通。 在客户机中提供原生性能和完整的驱动程序栈,但它将整个 GPU 专用于一个虚拟机。在多租户环境中,这通常不是可行选项。

virtio-nvgpu 的不同之处

翻译发生在 内核驱动程序 级别(ioctl 到 /dev/nvidia*),而非 图形 API 级别。客户机运行 NVIDIA 真实的用户模式库,这些库在 客户机本地 构建 GPU 命令缓冲区——单个绘制调用永远不会被序列化:

                    Venus                  virtio-nvgpu
                    ──────────────         ──────────────────────

每个绘制调用:      序列化 +               本地函数调用
                    传输 +                 (无 VM 退出)
                    反序列化 +
                    重放

每帧边界           ~2,000 条消息           ~5–20 条消息
  跨越次数         (每个 API 调用一条)    (队列提交 + 分配)

GPU 命令           重放后在宿主机端生成     在客户机内由 NVIDIA
  缓冲区                                   自身的编译器生成

CPU 开销           序列化 +                渲染开销几乎为零
                    反序列化                (仅 ioctl 转发)

客户机缓冲区       宿主机拥有缓冲区        客户机拥有缓冲区
  所有权           合成器无法跟踪          合成器完全可见并可控制

客户机 NVENC       不可行                  可用(真正的 CUDA 互操作)

工作原理

客户机内核驱动程序。 注册 /dev/nvidiactl/dev/nvidia0...N/dev/nvidia-uvm。执行 ioctl() 时,它将请求序列化到一个控制 virtqueue 上。执行 mmap() 时,它使用正确的缓存属性将相应的共享内存区域映射到调用进程中。它复制原始字节,不做任何 ABI 决策。

设备 crate。 接收请求,将客户机句柄映射到宿主机设备文件描述符,对 ioctl 参数进行 ABI 感知的翻译——重写嵌入的指针和文件描述符——并针对宿主机的设备发出调用。缓冲区和窗口簿记逻辑在此处。

事件。 第二个 virtqueue 反向运行。宿主机监视其打开的每个描述符,并在其可读时发出通知,这是唤醒等待 GPU 的客户机的方式。否则客户机根本无法等待——它轮询一个内核报告为永久就绪的描述符,并空转。

隔离机制——尚未构建。 计划是为每个客户机进程运行一个沙箱化的辅助进程,持有真正的设备文件描述符并以非特权方式执行 ioctl(2) 调用。目前后端自身在 VMM 进程中执行此操作。 isolate/ 包含设计文档,暂无代码。

┌─ 客户机 ────────────────────────────────────────────────┐
│  应用程序 → NVIDIA Vulkan / GL / CUDA                   │
│                     │ ioctl(/dev/nvidia*)              │
│  driver/ (GPL)      ▼                                  │
│    序列化 → virtqueue                                   │
│    mmap → 共享区域                                      │
└───────────────────────────┬─────────────────────────────┘
                            │ VM 退出
┌───────────────────────────▼─────────────────────────────┐
│  VMM(实现设备 trait)                                   │
│                                                         │
│  device/ (Apache-2.0)                                   │
│    ├─ 客户机句柄 → 宿主机 FD                             │
│    ├─ 翻译嵌入的 FD 和指针                               │
│    └─ 缓冲区 + 窗口簿记                                  │
│                     │                                   │
│    └─ ioctl(宿主机 /dev/nvidia*) · mmap → 共享窗口       │
│       (计划包含每客户机非特权隔离进程,                    │
│        当前运行的并非此方案)                              │
│                                                         │
│  宿主机 NVIDIA 驱动程序 → GPU                            │
└─────────────────────────────────────────────────────────┘

功能范围

支持范围

  • Vulkan 渲染,包括向客户机内的合成器呈现——这需要 /dev/nvidia-drm/dev/nvidia-modeset,两者均已实现且都不是显示器:它们是使缓冲区可共享的机制
  • OpenGL 渲染(无头 EGL)
  • CUDA 设备内存分配
  • CUDA ↔ Vulkan/GL 互操作,零拷贝,GPU 侧指针
  • 从 CUDA 设备指针进行 NVENC 编码;NVDEC 解码

不支持范围

  • cudaMallocManaged() / 完整统一虚拟内存
  • 扫描输出。 无物理显示输出:流媒体盒上没有显示器,帧以视频而非线路上的像素形式离开
  • MIG、SR-IOV
  • 任意 NVIDIA 驱动程序版本——每个支持范围都是明确的,如同 nvproxy

性能

已在一张显卡上,通过一个合成负载进行测量——详见 BENCHMARKS.md 了解方法和原始运行数据,以及此测量 未涵盖 的内容(它不支持与其他任何虚拟机监控程序的比较,因为没有运行其他程序)。

virtio-nvgpu,实测数据Venus,设计预期
GPU 受限(每帧 ≥2 ms)达到裸机的 98–100%90–97%
非常轻的帧(≤0.5 ms)达到裸机的 93–71%
渲染客户机的 CPU 成本与裸机相同高(序列化和重放)
每帧宿主机跨越次数~0.02数千次
客户机侧 NVENC可用,零拷贝不可行

差异是结构性的:Venus 每次 API 调用 都跨越 VM 边界,每帧数千次。virtio-nvgpu 每次 ioctl 跨越一次——而渲染循环不发出 ioctl,因为提交操作就是对映射内存的写入。在非常轻的帧下剩余的开销不是转发而是 等待:客户机为 GPU 休眠,唤醒成本约 0.02 毫秒,无论帧多小。

Venus 列是该项目的设计范围,而非此处测量的结果。


驱动程序版本

NVIDIA 的内核驱动程序 ABI 不稳定;ioctl 结构体布局在不同版本间会变化。支持范围是明确的,以下是完整列表。

内置 ABI 配置文件:

配置文件覆盖范围
535.129.03从 535.129.03 到下一个配置文件
580.178.04从 580.178.04 到下一个配置文件
595.71.05595.71.05 及更新版本

配置文件按键值范围而非单点匹配:两个配置文件之间的发行版使用较低版本的配置文件,任何比最后一个配置文件更新的驱动程序使用最后一个配置文件。任何 早于 535.129.03 的版本都会被拒绝,而不是猜测——转发一个从未见过的布局的 ioctl 会导致得到看似合理但错误的结果,而非报错。

因此,一个比最新配置文件新得多的驱动程序会被 接受,前提是假设其所需的一切未发生变化。这个假设正是新配置文件存在以替代的,也是当新驱动程序行为异常时首先怀疑的对象。

实际运行的驱动程序版本:

版本显卡运行情况
595.99.02RTX 3060全部功能——渲染、呈现、编码,以及 BENCHMARKS.md 中的所有数据
615.71.09RTX A2000可枚举和渲染;未进行基准测试,且自首次测试后未再次测试

两张显卡,两个版本,其中一个彻底测试。其他任何组合均未测试。

如何构建配置文件

成本是有界的,原因有三。配置文件按键值范围而非单点匹配,因此两个已知版本之间的发行版会选择较低的配置文件。结构体部分是从 NVIDIA 发布的 open-gpu-kernel-modules 在每个标签处 机械地推导 出来——为每个字段编译一个探测器,读回 sizeofoffsetof——而非手工转录。判断部分(哪些命令存在以及哪些是安全的)则跟踪 nvproxy 上游。

参见 gen/,以及其中的 supported_versions() 获取代码中的列表——该函数,而非此表格,才是决策依据。


先前工作

gVisor nvproxy——直接的灵感来源。它转发 N

相似文章

@QuixiAI: https://x.com/QuixiAI/status/2092804334417236135

X AI KOLs Following

这是 NVIDIA 开放 GPU 内核模块的一个分支,它支持消费级 GeForce GPU(3090、4090、5090)之间的 PCIe 点对点通信,允许直接通过 PCIe 进行数据传输,从而显著提升多 GPU 性能。

vgpu

Product Hunt

vgpu 是一个针对 WebGPU 的 TypeScript 库,具有类型化着色器导入、一个轻量级的 GPU 优先 API,并兼容浏览器、无头 Node 和测试环境。

Linux补丁引入"KNOD",用于将内核网络卸载直接交给AMD GPU

Reddit r/LocalLLaMA

Linux内核补丁引入了KNOD,这是一种用于将内核网络数据包卸载直接交给AMD GPU的机制,能够在无需用户空间依赖(如ROCm)的情况下加速数据包处理。该代码管理GPU队列,对每个数据包程序进行JIT编译,并完全从内核调度工作。

ZeroGPU

Product Hunt

ZeroGPU是一个为AI推理设计的计算高效层,旨在优化GPU使用并降低成本。