状态:草稿(2026-10-05),已反映实测结果。 「实测示例」一节中的数值是在一台 GPU 服务器上通过只读命令直接确认的,设备 ID 等识别信息已替换为占位符。原理说明基于内核与 KubeVirt 文档的内容,但在撰写本文时并未重新打开文档进行核对(未确认)。诸如内存锁定与崩溃循环之间的关系等未能验证的推测,均已明确标注。编写规则:以实测数据为基础,未能测得的内容标记为「未实测」。
目录
- 1. 为什么使用直通
- 2. 原理:四个组件
- 3. 实测示例:将 6GB 级 GPU 传递给一台服务器
- 4. 同一设备上 nvidia-smi 失败的原因
- 5. KubeVirt 将设备连接到虚拟机的途径
- 6. 常见陷阱:内存锁定与宿主机 OOM
- 7. 恢复为容器用 GPU(示例步骤)
- 8. 面试可能问到的问题
- 9. 常见误解
- 外部依据:直通的性能开销
- 未能确认的内容
- 参考资料
- 已确认的版本和日期
1. 为什么使用直通
为工作负载分配 GPU 的方法大致有四种。
| 方式 | 单位 | 隔离 | 与本文的关系 |
|---|---|---|---|
| 容器(device plugin) | 整张 GPU 或分时复用 | 进程级别,与宿主机内核和驱动共享 | 第 3 篇 |
| MIG | 在硬件层面分割 GPU(仅限支持的模型) | 硬件级别 | 第 3 篇 |
| vGPU | 由驱动创建的虚拟 GPU | 由虚拟机监控程序管理,需要许可证 | 范围外 |
| 直通 | 整张物理 GPU | 虚拟机独占设备 | 本文 |
需要使用直通的情况可以归纳为三种:当客机需要使用与宿主机不同的内核或驱动版本时,当客机操作系统不是 Linux 时,以及当需要容器无法满足的隔离性时。相反, 该 GPU 无法同时被宿主机和容器使用。 在本文后半部分,我们将实际看到同一设备在一端 nvidia-smi 运行失败的现象。
2. 原理:四个组件
直通是将设备的所属权从宿主机内核的通用驱动转移到用户空间进程(QEMU)的过程。为此,四个组件协同工作。
2.1 驱动绑定
PCI 设备拥有厂商 ID 和设备 ID(示例中的 GPU 为 10de:XXXX),内核会为其绑定一个匹配的驱动。通常情况下会绑定 nvidia 驱动,此时宿主机上的 nvidia-smi 即可正常工作。而在直通中,则会改用绑定 vfio-pci 。 vfio-pci 不是使用设备的驱动,而是将设备交予用户空间的中间驱动。绑定了该驱动的设备将不再由宿主机内核使用。
绑定方法有两种。一种是在运行时通过 driver_override 与 bind/unbind 进行切换,另一种是在启动时让 vfio-pci 优先抢占该 ID。示例服务器采用的是后者(第 3 节)。
2.2 IOMMU:将设备的内存访问范围限制在内的装置
GPU 通过 DMA 直接对系统内存进行读写。当把设备交给客机时,客机使用的内存地址与宿主机的实际地址不同,因此设备可能会访问到错误宿主机内存。 IOMMU 通过转换设备发出的 DMA 地址并限制其允许范围来解决这个问题。这是一种仅向设备开放客机内存区域的方式。
隔离的最小单位是 IOMMU 组。由于同一组中的设备无法将其 DMA 互相隔离开,因此组内的设备 必须全部移交给同一个所有者 。因此,GPU 的音频功能(示例中的 01:00.1)也需要一起绑定到 vfio-pci 中。
2.3 VFIO 接口
VFIO 是一项将设备安全暴露给用户空间的内核功能。用户空间进程通过打开 /dev/vfio/vfio(容器)和 /dev/vfio/<그룹 번호> 来执行以下操作:将设备的 BAR(内存区域)映射到自身的地址空间,将客机内存注册到 IOMMU,并通过事件文件描述符接收设备的中断并注入客机。QEMU 负责处理这些工作。
由于客机内存是 DMA 的目标,因此必须将其固定(pin),以防宿主机将其回收或交换出去。 这意味着拥有 12GiB 内存的虚拟机在启动时会固定 12GiB 的宿主机内存(根据文档所述,未进行直接测量)。
2.4 重置
虚拟机结束运行后,必须将设备恢复到干净的状态,以便下一个使用者安全使用。最好的方式是设备支持的 FLR(Function Level Reset),如果不支持则使用总线重置。无法正常重置的 GPU 会导致虚拟机使用一次后必须重启宿主机的问题。
3. 实测示例:将 6GB 级 GPU 传递给一台服务器
所有数值均通过只读命令确认,每台设备的 示例值各不相同。宿主机内核为 7.0.0-34-generic。设备 ID 已用 XXXX(GPU)和 YYYY(音频)隐去。
| 项目 | 值 | 含义 |
|---|---|---|
lspci -nnk -d 10de: |
01:00.0 GPU(10de:XXXX)与 01:00.1 音频(10de:YYYY)均由 Kernel driver in use: vfio-pci |
两个功能均由 vfio-pci 拥有 |
| IOMMU 组 | 组 16,成员仅有 01:00.0 和 01:00.1 两个 |
由于组很干净,可以单独将此 GPU 传递出去 |
| 内核命令行 | iommu 无相关参数,但 IOMMU 组有 20 个 |
在此内核中,IOMMU 默认处于开启状态 |
| 已加载模块 | vfio_pci, vfio_pci_core, vfio_iommu_type1, vfio, kvm_intel |
VFIO 栈已加载 |
/dev/vfio/ |
16(组设备)与 vfio(容器) |
准备好交予用户空间的状态 |
reset_method |
flr bus |
支持 FLR,否则使用总线重置 |
| BAR 区域 | 区域 0 为 16M,区域 1 为 8G(可预取),区域 3 为 32M | 会将 GPU 的内存窗口映射到客机 |
| PCIe 链路 | 能力 16GT/s x16,当前 16GT/s x8 (downgraded) |
链路宽度已降低至一半 |
链路宽度降低至 x8 的原因尚未确认。可能是由于主板布线或电源状态导致的,因此不作定论。
3.1 配置文件
在本示例中,用于实现直通的配置文件有两个。
# /etc/modprobe.d/vfio-gpu.conf
options vfio-pci ids=10de:XXXX,10de:YYYY
softdep nvidia pre: vfio-pci
softdep nouveau pre: vfio-pci
softdep snd_hda_intel pre: vfio-pci
# /etc/modules-load.d/vfio-pci.conf
vfio-pci
options vfio-pci ids=…使vfio-pci优先抢占这两个 ID。softdep … pre: vfio-pci使nvidia,nouveau,snd_hda_intel在vfio-pci加载之前启动,防止这些驱动率先夺取设备。modules-load.d在启动时加载vfio-pci模块本身。
示例服务器的启动镜像(initramfs)中没有 vfio 项。因此,此配置仅靠文件即可生效而无需重新生成镜像,还原时也只需出于同样的原因清理文件即可(不过尚未测试重启后是否确实能恢复原状)。
4. 同一设备上 nvidia-smi 失败的原因
在此宿主机上执行 nvidia-smi 时,会出现「无法与 NVIDIA 驱动通信」的提示。这并不意味着未安装驱动。宿主机上已安装 nvidia-driver-595-open(595.91.07),并且也有当前内核对应的模块软件包。
内核日志直接揭示了原因。
NVRM: GPU 0000:01:00.0 is already bound to vfio-pci.
nvidia 每当 vfio-pci 模块试图接管设备时,都会反复打印已经被 nvidia-smi 拥有的消息。由于绑定到设备的驱动只有一个,因此当所有权归属 VFIO 时,宿主机的 没有可通信的对象。这不是故障,而是符合配置的正常行为
。
| Kubernetes 侧的状态也说明了这一点。 | 位置 |
|---|---|
值 nvidia.com/gpu |
0 节点 |
(没有可供容器使用的 GPU) nvidia.com/<모델별 자원 이름> |
1 节点 |
(有可供虚拟机使用的设备) nvidia.com/gpu.deploy.operands |
false 节点标签 |
(GPU Operator 组件已关闭) driver.enabled |
false GPU Operator |
(使用宿主机安装的驱动的配置)
- 5. KubeVirt 将设备连接到虚拟机的途径
permittedHostDevices在 KubeVirt 配置的pciVendorSelector: 10DE:XXXX中注册了resourceName: nvidia.com/<모델별 자원 이름>和HostDevices,并且开启了功能门槛(feature gate) - 。节点的
virt-handler查找该 ID 的设备并将其作为 Kubernetes 扩展资源进行通告。其结果就是上表中的nvidia.com/<모델별 자원 이름>: 1。 - 在虚拟机定义的
devices.gpus中写入deviceName: nvidia.com/<모델별 자원 이름>。调度器仅将虚拟机放置在拥有此资源的节点上。 - 虚拟机启动时,QEMU 通过 VFIO 打开相应的 IOMMU 组并将设备连接到客机。
这里重要的一点是, 资源名称使用的不是 nvidia.com/gpu 而是单独的名称。容器用 GPU 和虚拟机用 GPU 是不同的资源,一张 GPU 只能以其中一种形式进行通告。
6. 常见陷阱:内存锁定与宿主机 OOM
如 2.3 节所述,直通了 GPU 的虚拟机将其客机内存固定在宿主机中。由于被固定的内存无法通过交换(swap)释放,因此在虚拟机启动的瞬间,就需要一次性消耗相当于客机大小的宿主机内存。如果同一宿主机上还在运行其他工作负载和节点守护进程,总内存很容易超出物理内存。
当宿主机整体内存不足时,内核的 OOM killer 就会启动。此时内核日志中记录的不是容器超限(Memory cgroup out of memory),而是宿主机整体内存不足(Out of memory: Killed process)。牺牲者不仅可能是虚拟机进程(qemu-kvm),还可能是节点的系统守护进程。如果虚拟机在崩溃后重新启动,就会陷入不断重复相同情况的崩溃循环(crash loop),而守护进程在此期间也会持续不断地一同死去。
笔者在实验环境中观察到的宿主机 OOM 日志与此流程一致。不过,由于未实际测量虚拟机启动时实际固定的内存大小,该解释仅为 推测(未实测)。教训有两点:在宿主机内存不富裕的服务器上最好不要部署多台 GPU 虚拟机;在启动虚拟机之前,应先将客机内存总和与宿主机已使用的内存相加进行评估。
7. 常态化恢复为容器用 GPU(示例步骤)
解除直通并将设备恢复为容器使用,最简单的方法是排空节点(cordon、drain)、清理配置文件然后重启。但如果该节点上还运行着其他服务,重启将是一项会中断所有服务的重大操作。以下是 不重启而仅传递设备 的示例。判定其可行的依据均已通过只读方式预先确认。
| 事前确认 | 结果 |
|---|---|
/dev/vfio/<그룹 번호> 占用 |
的进程 |
| 无 | 使用 GPU 的虚拟机 |
| 无 | 设备状态 flr bus 空闲,支持重置方式 |
nvidia 模块 |
595.91.07,当前内核可用,已签名 |
首先备份并清理两个配置文件,然后执行以下两个步骤。
# 1) 이 장치는 nvidia 드라이버로만 붙도록 지정한다.
echo nvidia | sudo tee /sys/bus/pci/devices/0000:01:00.0/driver_override
# 2) vfio-pci 에서 푼다. 풀리는 즉시 nvidia 가 장치를 가져간다.
echo 0000:01:00.0 | sudo tee /sys/bus/pci/drivers/vfio-pci/unbind
结果。 unbind 之后不久,设备的驱动变更为 nvidia 且 nvidia-smi 恢复正常运作。
| 项目 | 值 |
|---|---|
| GPU | 6GB 级笔记本 GPU,6,144MiB |
| 驱动 | 595.91.07 |
lspci |
01:00.0 的 Kernel driver in use: nvidia |
| 宿主机 | 运行时间保持不变,负载无变化,对其他服务无影响 |
需要同时注意的一点。
- 音频功能(
01:00.1)仍然保留在vfio-pci中。 我们认为对容器使用 GPU 没有影响,但未经确认。 - 内核日志中打印了两行
NVRM: … NV_ERR_INVALID_DATA(PlatformRequest) 警告,power.draw读数约为 752W。 这个数值对于该 GPU 的功率来说是不可能的,因此读取传感器或平台信息似乎存在问题,原因尚未查明。转换刚完成后的功率值请勿轻信。 - 重启后
nvidia是否会自动附加尚未测试。 由于已清理了配置文件,预计会附加,但这仍是推测。 - Kubernetes 这边保持不变。 节点的
nvidia.com/gpu仍然是0,KubeVirt 的nvidia.com/<모델별 자원 이름>也仍然是1(这似乎是virt-handler重新读取之前的旧值)。若要让容器使用 GPU,必须将节点标签nvidia.com/gpu.deploy.operands改为true以启用 GPU Operator 的组件。
若要还原,只需将 driver_override 改为 vfio-pci 并解除 nvidia 即可。在此转换之后,GPU VM 将无法使用此 GPU。 若要让容器和虚拟机交替使用单张 GPU,每次都需要进行此转换,因此在数量较少的环境中,必须明确指定其用途。
8. 面试可能出现的问题
- 直通、vGPU 和 MIG 有何区别? 通过隔离单元(整个设备、软件分区、硬件分区)、许可证以及支持的硬件进行区分。
- 为什么 IOMMU 组很重要? 同一组中的设备无法彼此隔离 DMA,因此必须一起传递。示例服务器的该组中只有 GPU 和音频,因此非常干净。
- 为什么 VM 启动时主机内存会被固定? 因为设备会对客户机内存执行 DMA,所以该区域不能移动或交换。
- 如果在主机上
nvidia-smi失败但已安装驱动程序,该怎么办? 首先查看附加到设备的驱动程序(如果附加了lspci -nnk).vfio-pci,则是按配置运行)。 - 容器和 VM 可以同时使用这张 GPU 吗? 直通方式下无法做到。需要考虑分时复用、MIG 或 vGPU。
9. 常见误解
- “驱动程序损坏”:所有权可能在其他驱动程序手中。
- “直通没有性能损失”:设备本身的性能几乎完全保留,但会带来链路宽度、重置、内存固定等运营成本。在示例服务器中未测量性能损失(未测量,由于这是运行服务的服务器,因此没有将 GPU 转回直通进行测试)。外部测量总结在下方的“外部依据”中。
- “IOMMU 必须单独启用”:在示例内核中,无需参数即可启用。根据设备和内核配置的不同而有所差异,因此请先确认是否能看到该组。
外部依据:直通的性能开销
由于无法直接测量直通 VM 的性能,我们查阅了针对相同问题的外部资料。能够打开的范围包括摘要、厂商文章和部分原文。
| 资料 | 确认的内容与条件 | 可靠性与局限性 |
|---|---|---|
| Walters 等人,“GPU Passthrough Performance: A Comparison of KVM, Xen, VMWare ESXi, and LXC for CUDA and OpenCL Applications”,IEEE CLOUD 2014 | 摘要:KVM 直通达到裸金属的 98~100%,Xen 和 VMware 达到 96~99%(两代 GPU,两种处理器微架构) | 同行评议论文或正文无法打开,因此 IOMMU、大页面、NUMA、链路宽度条件未知。2014 年 GPU |
| Steininger 等人,“GPU Passthrough Across Virtualization Platforms for LLM Inference”,Computers (MDPI) 第 15 卷,2026 | 摘要:将一张工作站级 GPU 直通到 Proxmox VM·LXC、KVM、OpenStack、Podman,vLLM 小型模型的吞吐量和词元间延迟在所有平台上均在裸金属的 2% 以内(统计学上等效) | 作者说明这并非普遍结论。设备未饱和的区域。由于无法访问正文,仅确认了摘要 |
| OpenNebula 厂商博客 (2026-03-18) | GB200 NVL4 直通 cuBLAS GEMM 71,891 对比 71,738 GFLOP/s(相差 0.21%),vLLM 吞吐量在 ±4% 以内 | 厂商文章,未明确说明 NUMA 绑定、大页面、IOMMU 配置 |
| VMware 性能研究 (2023-10) | 在 vGPU(完整配置文件 H100)下,GPT-J 6B 达到裸金属的 97.9~99.8%(MLPerf 推理 v3.1) | 并非直通,而是 vGPU。未经 MLCommons 验证的结果 |
| Samani, Amini Salehi, arXiv 2112.09780, 2021 | 比较了裸金属、VM(直通)和容器。没有一致的单一开销,端到端推理时间受 CPU 预处理和存储 I/O 的支配,GPU 部分的影响很小。如果主机上没有空闲核心,VM 开销会增加,而 CPU 绑定可减少该开销 | Tesla K40,2 插槽 80 核,HDD。较旧的 GPU |
| NVIDIA CUDA Programming Guide 3.4.2.5, NCCL GPU troubleshooting | 说明在裸金属中应关闭 IOMMU,而在 VM 直通中应开启 IOMMU 并使用 VFIO。ACS 会将 P2P 绕道至 CPU 根联合体,从而大大降低带宽,而 VM 无法关闭 ACS,若要在 VM 内部获得最高性能,则需要网卡的 ATS | 官方文档。在单 GPU 服务器中,P2P 不是关键问题 |
- 判定:有条件支持。 “设备本身的性能几乎完全保留”与外部测量报告的 0~5% 范围一致。但确认的数值来自于摘要、厂商文章以及设备未饱和的区域,因此 未获得与我们 GPU 相同条件下的数值。 未找到将链路宽度、大页面、IOMMU 配置作为变量进行测量的一手资料。
- “IOMMU 必须单独启用”结合 KubeVirt 文档指导启用内核参数(
intel_iommu=on,amd_iommu=on)与发行版默认值的不同来看,本文中的“请先确认是否能看到该组”是安全的表述。
未确认的事项
- GPU VM 启动时实际固定的内存大小(第 6 节的解释是与日志一致的推测)。
- PCIe 链路降至 x8 的原因。
- 直通 VM 内部的 GPU 性能(未测量与容器相比的吞吐量。由于环境在同一台服务器上运行生产服务,因此未进行将 GPU 传回 VM 的测试,而是由外部测量代替)。
- 重启后
nvidia是否会自动附加(在不重启的情况下进行了转换,未测试重启)。 - 本次未重新打开并对照本文原理说明中所引用的内核 VFIO 文档和 KubeVirt 主机设备文档。
参考资料
- 内核 VFIO 文档:https://www.kernel.org/doc/html/latest/driver-api/vfio.html
- KubeVirt 主机设备用户指南:https://kubevirt.io/user-guide/compute/host-devices/
确认的版本和日期
截至 2026-10-05。主机内核 7.0.0-34-generic,NVIDIA 驱动程序包 595.91.07(安装在主机上),使用 KubeVirt HostDevices 功能门。