Skip to content

HAMi vGPU 实现原理

一、整体架构

HAMi 采用 三大组件 协作实现 vGPU 切分:

用户 Pod 请求 GPU


┌──────────────┐     ┌───────────────────┐     ┌──────────────────┐
│ hami-scheduler│────▶│ hami-device-plugin │────▶│  libvgpu.so (注入)│
│ (调度决策)     │     │ (设备注册+分配)     │     │  (运行时资源限制)  │
└──────────────┘     └───────────────────┘     └──────────────────┘

二、各节点的 GPU 硬件与 vGPU 配置

节点公网 IPGPU 型号物理 GPU 数显存/卡devicesplitcountvGPU 数每 vGPU 显存
10-60-18-8117.50.213.129RTX 3090124576 MB446 GB
10-60-205-41117.50.185.68RTX 3080 Ti112288 MB226 GB
10-60-10-196117.50.188.237RTX 208018192 MB224 GB

说明: 每个 vGPU 部署一个 vLLM 模型实例。RTX 2080 (4GB vGPU) 只能跑 Qwen3-0.6B,Qwen2.5-0.5B 在 4GB 下 OOM。

三、配置不同显卡 vGPU 的关键机制

1. hami-device-plugin ConfigMap — 按节点配置切分策略

json
{
  "nodeconfig": [
    {
      "name": "10-60-18-8",        // RTX 3090 节点
      "operatingmode": "hami-core",
      "devicesplitcount": 4,        // 每卡切 4 份
      "devicememoryscaling": 1,
      "preconfigureddevicememory": 0
    },
    {
      "name": "10-60-205-41",      // RTX 3080 Ti 节点
      "operatingmode": "hami-core",
      "devicesplitcount": 2,        // 每卡切 2 份
      "devicememoryscaling": 1
    },
    {
      "name": "10-60-10-196",      // RTX 2080 节点
      "operatingmode": "hami-core",
      "devicesplitcount": 2,        // 每卡切 2 份
      "devicememoryscaling": 1
    }
  ]
}

核心参数解释:

参数作用
devicesplitcount每张物理 GPU 切分为多少份 vGPU,这是不同显卡差异化配置的核心
devicememoryscaling显存缩放因子,默认为 1
preconfigureddevicememory预配置的显存量,0 表示按 splitcount 均分
operatingmode运行模式:hami-core(软件 vGPU)或 mig(硬件 MIG)
filterdevices可按 UUID 或索引过滤特定 GPU
migstrategyMIG 策略,消费级显卡不支持 MIG,设为 none

2. hami-scheduler-device ConfigMap — 定义全局设备规格

yaml
nvidia:
  resourceCountName: nvidia.com/gpu              # GPU 数量资源名
  resourceMemoryName: nvidia.com/gpumem           # 显存资源名(绝对值 MB)
  resourceMemoryPercentageName: nvidia.com/gpumem-percentage  # 显存百分比
  resourceCoreName: nvidia.com/gpucores            # GPU 算力核心
  deviceSplitCount: 4                              # 默认切分数
  deviceMemoryScaling: 1
  runtimeClassName: "nvidia"

3. libvgpu.so — 运行时资源限制(核心黑科技)

这是 HAMi 最核心的部分。从日志可以看到分配流程:

Allocate Response:
  env: CUDA_DEVICE_MEMORY_LIMIT_0 = 4000m     ← 限制显存 4000MB
  env: CUDA_DEVICE_SM_LIMIT = 0               ← SM 算力限制
  mount: /usr/local/vgpu/libvgpu.so           ← 注入 vGPU 动态库
  mount: /etc/ld.so.preload                   ← 预加载 libvgpu.so

工作原理:

  • libvgpu.so 是一个 CUDA Hook 库,通过 LD_PRELOAD 注入到容器
  • 它拦截 cudaMalloccudaMemcpy 等 CUDA API 调用
  • 在运行时实现显存隔离算力限制
  • 无需修改 NVIDIA 驱动,纯软件层面实现 vGPU

4. 设备注册与调度流程

① device-plugin 启动 → 探测物理 GPU → 写入 node annotation
   hami.io/node-nvidia-register: 
   [{"id":"GPU-xxx","count":2,"devmem":8192,
     "type":"NVIDIA GeForce RTX 2080","mode":"hami-core"}]

② 用户提交 Pod,声明资源请求:
   nvidia.com/gpu: 1            ← 要 1 个 vGPU
   nvidia.com/gpumem: 4000      ← 要 4000MB 显存

③ hami-scheduler 收到调度请求 → filter 阶段评估节点剩余资源
   → 找到满足条件的节点和 GPU → bind Pod 到节点

④ device-plugin Allocate 阶段:
   → 根据 annotation 确定分配哪张物理 GPU
   → 注入 libvgpu.so + 设置 CUDA_DEVICE_MEMORY_LIMIT
   → 容器启动后只能看到/使用被分配的显存量

四、用户如何使用不同规格的 vGPU

Pod 中通过不同的 resource 声明来请求不同大小的 vGPU:

yaml
# 小 vGPU - 适合推理任务
resources:
  limits:
    nvidia.com/gpu: 1
    nvidia.com/gpumem: 4000       # 4GB 显存

# 大 vGPU - 适合训练任务  
resources:
  limits:
    nvidia.com/gpu: 1
    nvidia.com/gpumem: 16000      # 16GB 显存

# 使用百分比方式
resources:
  limits:
    nvidia.com/gpu: 1
    nvidia.com/gpumem-percentage: 50  # 50% 显存

# 指定算力核心
resources:
  limits:
    nvidia.com/gpu: 1
    nvidia.com/gpucores: 30         # 30% 算力

五、总结

HAMi 为不同显卡配置不同 vGPU 的核心在于:

  1. ConfigMap 按节点差异化配置nodeconfig 中按节点名分别设置 devicesplitcount,大显存卡(3090 24G)切 4 份,小卡(2080 8G)切 2 份
  2. 软件 vGPU(hami-core):通过 libvgpu.so CUDA Hook 实现显存/算力隔离,无需 MIG 硬件支持,消费级显卡也能用
  3. 调度器感知:hami-scheduler 作为 extender 参与调度,理解 vGPU 粒度资源,保证不超卖
  4. 运行时注入:通过环境变量 + 挂载方式,在容器启动时动态注入资源限制

六、问题排查:10-60-18-8 节点第二张 GPU 无法虚拟化

现象

节点 10-60-18-8 物理上有 2 张 RTX 3090(lspci 可见),但:

  • nvidia-smi 只显示 1 张卡
  • HAMi device-plugin 日志:Discovered 1 device(s) for registration
  • 第二张卡无法被虚拟化使用

排查过程

1. 硬件层确认

bash
# lspci 显示 2 张 GPU
00:03.0 VGA compatible controller: NVIDIA Corporation GA102 [GeForce RTX 3090] (rev a1)
00:04.0 VGA compatible controller: NVIDIA Corporation GA102 [GeForce RTX 3090] (rev a1)

# /dev/ 下两个设备节点都存在
/dev/nvidia0  /dev/nvidia1

# 但 /proc 中 GPU 0 信息异常
# GPU 0 (00:03.0): UUID = ????????, VBIOS = ??, Firmware = N/A  ← 驱动初始化失败
# GPU 1 (00:04.0): UUID = GPU-42a6d7d5-..., VBIOS = 94.02.42.00.C7, Firmware = 570.153.02  ← 正常

2. 内核日志报错

NVRM: gpuHandleSanityCheckRegReadError_GM107: Possible bad register read:
      addr: 0x110100, regvalue: 0xbadf5620, error code: Unknown SYS_PRI_ERROR_CODE
NVRM: kflcnWaitForHalt_TU102: Timeout waiting for Falcon to halt
NVRM: gpuWaitForGfwBootComplete_TU102: GSP failed to halt with GFW_BOOT: (progress 0x0)
NVRM: RmInitAdapter: Cannot initialize GSP firmware RM
NVRM: GPU 0000:00:03.0: RmInitAdapter failed! (0x62:0x65:1859)
NVRM: GPU 0000:00:03.0: rm_init_adapter failed, device minor number 0

3. 环境确认 — 这是 KVM 虚拟机

bash
$ systemd-detect-virt
kvm

$ cat /sys/class/dmi/id/product_name
KVM

# CPU: Intel Xeon Platinum 8358P
# 两张 GPU 都在同一个 PCI root bus (0000:00) 上
# PCI 拓扑:
# -[0000:00]-+-03.0  (GPU 0 ❌)
#            +-04.0  (GPU 1 ✅)

根因分析

层级状态
物理硬件✅ 两张 GPU 都存在,lspci 可见
PCI BAR 分配✅ 内存地址不冲突 (8000000000 vs 8010000000)
KVM 直通❌ GPU 0 的 MMIO 地址空间未正确映射
NVIDIA 驱动❌ 读取 GPU 0 寄存器返回 0xbadf5620(硬件级错误)

核心原因:这是 KVM hypervisor 层的 IOMMU/PCI passthrough 问题,不是驱动层问题。

GPU 0 (PCI 00:03.0) 的 MMIO 寄存器在 hypervisor 侧没有正确映射到虚拟机。驱动可以探测到设备(PCI 配置空间可访问),但访问 GPU 的 BAR 寄存器空间时返回错误值 0xbadf5620,导致 GSP 固件无法启动,整个 GPU 初始化失败。

已尝试但无效的修复

方法结果原因
禁用 GSP (NVreg_EnableGpuFirmware=0)❌ 无效问题在 GSP 之前,MMIO 访问就已经失败
重载 nvidia 驱动 (rmmod + modprobe)❌ 无效驱动重载不改变 PCI BAR 映射
PCI bus rescan❌ 无效BAR 映射由 hypervisor 决定
PCI device reset❌ 无效无法修复 hypervisor 层的映射问题

修复建议

需要联系云服务商 / hypervisor 管理员,在宿主机上执行以下操作之一:

  1. 检查 IOMMU 分组:确保两张 GPU 在宿主机上位于不同的 IOMMU 组
  2. 检查 VFIO 绑定:确认两张 GPU 都正确绑定到 vfio-pci 驱动
  3. 调整 PCI 拓扑:在虚拟机 XML 配置中,将两张 GPU 分配到不同的 PCI root port
  4. 重启虚拟机:完全关闭并重新启动 VM(冷启动,非热重启),让 QEMU 重新分配 PCI 资源
  5. 检查宿主机内核参数:确保宿主机启用了 intel_iommu=on iommu=pt