主题
HAMi vGPU 实现原理
一、整体架构
HAMi 采用 三大组件 协作实现 vGPU 切分:
用户 Pod 请求 GPU
│
▼
┌──────────────┐ ┌───────────────────┐ ┌──────────────────┐
│ hami-scheduler│────▶│ hami-device-plugin │────▶│ libvgpu.so (注入)│
│ (调度决策) │ │ (设备注册+分配) │ │ (运行时资源限制) │
└──────────────┘ └───────────────────┘ └──────────────────┘二、各节点的 GPU 硬件与 vGPU 配置
| 节点 | 公网 IP | GPU 型号 | 物理 GPU 数 | 显存/卡 | devicesplitcount | vGPU 数 | 每 vGPU 显存 |
|---|---|---|---|---|---|---|---|
| 10-60-18-8 | 117.50.213.129 | RTX 3090 | 1 | 24576 MB | 4 | 4 | 6 GB |
| 10-60-205-41 | 117.50.185.68 | RTX 3080 Ti | 1 | 12288 MB | 2 | 2 | 6 GB |
| 10-60-10-196 | 117.50.188.237 | RTX 2080 | 1 | 8192 MB | 2 | 2 | 4 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 |
migstrategy | MIG 策略,消费级显卡不支持 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注入到容器- 它拦截
cudaMalloc、cudaMemcpy等 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 的核心在于:
- ConfigMap 按节点差异化配置:
nodeconfig中按节点名分别设置devicesplitcount,大显存卡(3090 24G)切 4 份,小卡(2080 8G)切 2 份 - 软件 vGPU(hami-core):通过
libvgpu.soCUDA Hook 实现显存/算力隔离,无需 MIG 硬件支持,消费级显卡也能用 - 调度器感知:hami-scheduler 作为 extender 参与调度,理解 vGPU 粒度资源,保证不超卖
- 运行时注入:通过环境变量 + 挂载方式,在容器启动时动态注入资源限制
六、问题排查: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 03. 环境确认 — 这是 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 管理员,在宿主机上执行以下操作之一:
- 检查 IOMMU 分组:确保两张 GPU 在宿主机上位于不同的 IOMMU 组
- 检查 VFIO 绑定:确认两张 GPU 都正确绑定到
vfio-pci驱动 - 调整 PCI 拓扑:在虚拟机 XML 配置中,将两张 GPU 分配到不同的 PCI root port
- 重启虚拟机:完全关闭并重新启动 VM(冷启动,非热重启),让 QEMU 重新分配 PCI 资源
- 检查宿主机内核参数:确保宿主机启用了
intel_iommu=on iommu=pt