HAMi vGPU 完整掌握 — Kubernetes GPU 共享与隔离
前言
上一篇文章 我们讲了 A100 的 MIG 硬件切分——性能最强、隔离最硬,但只有 A100/A30/H100 才支持。那普通的 Tesla T4、V100、A10 怎么办?K8s 原生限制一张 GPU 只能分给一个 Pod,跑个小推理就独占 16GB 显存,太浪费了。
HAMi(Heterogeneous AI Computing Virtualization Middleware)就是解决这个问题的开源 vGPU 方案。它原名「第四范式 k8s-vgpu-scheduler」,这次改名 HAMi 的同时也将核心的 vCUDA 库 libvgpu.so 开源了。它不挑 GPU 型号,能在驱动层拦截 CUDA API 调用,把一张物理 GPU 切分成多份虚拟 GPU,每份独立限制显存和算力,给多个 Pod 同时使用。
前置阅读:GPU 使用指南:如何在裸机、Docker、K8s 等环境中使用 GPU,了解基础 GPU 环境搭建。
一、为什么需要 vGPU?
1.1 问题根源
在裸机环境下,多个进程可以自然地共享同一张 GPU——驱动负责调度,各进程通过 CUDA Context 分时使用。但到了 K8s 里,情况就变了:
1 | 1. Device Plugin 上报物理 GPU 数量 → kube-apiserver |
K8s 把 GPU 当成不可拆分的整块资源,一张卡分给一个 Pod 就从可分配池中扣掉了,即使这个 Pod 只用了 2GB 显存跑个小推理。这就是 GPU 利用率低的根因——不是 GPU 不支持共享,是 K8s 的调度模型不让共享。
1.2 三种方案对比
1 | 隔离强度 |
| 方案 | 显存隔离 | 算力隔离 | 支持 GPU 型号 | 部署难度 |
|---|---|---|---|---|
| MIG | ✅ 硬件级 | ✅ 硬件级 | 仅 A100/A30/H100 | 低(原生支持) |
| HAMi vGPU | ✅ 驱动拦截 | ✅ 时间片比例 | 所有 NVIDIA GPU | 中(需安装 HAMi) |
| Time-Slicing | ❌ 无隔离 | ❌ 仅轮转 | 所有 NVIDIA GPU | 极低(一个配置参数) |
一句话选型:有 A100 用 MIG,没有就用 HAMi,临时测试用 Time-Slicing。HAMi 是普适性最强的方案。
二、HAMi 架构
2.1 核心组件
1 | ┌──────────────────────────────────────────────────┐ |
| 组件 | 作用 | 部署方式 |
|---|---|---|
| HAMi-Core | vCUDA 库,替换容器内原生 libcuda.so,拦截显存分配和算力调用 |
以 .so 文件挂载进容器 |
| Device Plugin | 上报 vGPU 资源(默认 1 张物理 GPU → 10 个 vGPU) | DaemonSet |
| Scheduler | 调度器扩展,感知 vGPU 资源,把 Pod 调度到有可用 vGPU 的节点 | Deployment |
| Webhook | 自动为 Pod 注入 vGPU 环境变量和 volume 挂载 | MutatingWebhook |
2.2 工作原理一张图讲清
1 | Pod 申请: nvidia.com/gpu=1, nvidia.com/gpumem=3000, nvidia.com/gpucores=30 |
核心机制:
LD_PRELOAD注入libvgpu.so,这个库在真正的libcuda.so之前拦截所有 CUDA API 调用。显存超了就拒绝,nvidia-smi的输出也帮你修成”看起来只有 3000MB”。
三、安装部署
3.1 前置检查
HAMi 依赖 NVIDIA 驱动栈,推荐先通过 GPU Operator 部署好底层组件(驱动 + container-toolkit + device-plugin)。
如果已经手动配好 NVIDIA 环境,确认以下 3 点:
1 | # 1. 驱动版本 ≥ 440 |
3.2 安装 HAMi
1 | # 给 GPU 节点打标签 |
查看自己的 K8s 版本:
1 | kubectl version --short |
3.3 验证安装
1 | # 看 HAMi 相关 Pod 状态 |
3.4 常用自定义配置
安装时通过 --set 可调整参数,完整列表见 官方配置文档:
1 | helm install hami hami-charts/hami \ |
| 参数 | 默认值 | 说明 |
|---|---|---|
devicePlugin.deviceSplitCount |
10 | 每张物理 GPU 最多同时跑的任务数 |
devicePlugin.deviceMemoryScaling |
1 | 显存放大比例,>1 启用虚拟显存(实验功能) |
devicePlugin.disablecorelimit |
false | true=关闭算力限制 |
devicePlugin.migStrategy |
none | MIG 策略,mixed 则专用资源名指定 MIG 设备 |
scheduler.defaultMem |
5000 | 不配显存时的默认值(MB) |
scheduler.defaultCores |
0 | 默认算力预留百分比,0=不限制 |
scheduler.defaultGPUNum |
1 | 默认 vGPU 数量,当 Pod 没设 nvidia.com/gpu 但有 gpumem/gpucores 时自动补 |
resourceName |
nvidia.com/gpu |
申请 vGPU 数量的资源名 |
resourceMem |
nvidia.com/gpumem |
申请 vGPU 显存大小的资源名 |
resourceMemPercentage |
nvidia.com/gpumem-percentage |
申请 vGPU 显存比例的资源名 |
resourceCores |
nvidia.com/gpucores |
申请 vGPU 算力比例的资源名 |
resourcePriority |
nvidia.com/priority |
任务优先级资源名 |
容器级别也有两个重要环境变量:
| 环境变量 | 可选值 | 说明 |
|---|---|---|
GPU_CORE_UTILIZATION_POLICY |
default / force / disable |
算力限制策略:default 空闲可突破,force 强制限制,disable 关闭 |
ACTIVE_OOM_KILLER |
true / false |
显存超用是否杀容器,true=超限直接 OOM Kill |
四、使用篇
4.1 最基本的 vGPU Pod
1 | apiVersion: v1 |
1 | kubectl apply -f gpu-pod.yaml |
输出中会看到 [HAMI-core Msg] 初始化日志,且显存显示为 0MiB / 3000MiB(而非整张卡的 15360MiB)。退出时 nvidia-smi 还会打印 HAMi-core 的清理日志:
1 | [HAMI-core Msg(16:139711087368000:libvgpu.c:836)]: Initializing..... |
最后一行 exit handler 日志就是 HAMi 的 vCUDA 库在容器退出时打印的,说明
libvgpu.so确实接管了 CUDA 调用。nvidia-smi显示的 3000MiB 上限也正是 Pod 中nvidia.com/gpumem: 3000所申请的。
4.2 资源字段速查
| 资源名 | 含义 | 示例 |
|---|---|---|
nvidia.com/gpu |
vGPU 数量 | 1 |
nvidia.com/gpumem |
显存限制(MB) | 3000 |
nvidia.com/gpumem-percentage |
显存百分比 | 20(表示 20%) |
nvidia.com/gpucores |
算力百分比(0-100) | 30 |
nvidia.com/gpumem-percentage |
显存百分比(如 50 = 50%) | 50 |
nvidia.com/priority |
优先级:0=高 / 1=低(默认 1) | 0 |
4.3 优先级策略
高优先级(priority=0) 和 低优先级(priority=1) 的行为差异:
| 场景 | 高优先级 Pod | 低优先级 Pod |
|---|---|---|
| GPU 上只有同优先级任务 | gpucores 限制不生效,可吃满 |
gpucores 限制不生效,可吃满 |
| GPU 上有高优先级任务 | 正常受 gpucores 限制 |
严格受 gpucores 限制,给高优任务让路 |
设计意图:高优任务独占 GPU 时别限制它,低优任务在 GPU 繁忙时自动被限速。这样既保证吞吐,又不浪费空闲算力。
五、进阶篇
5.1 查看 vGPU 资源分配详情
1 | # 列出所有使用 vGPU 的 Pod |
5.2 集成 Volcano 调度器
如果集群已有 Volcano,可以不用独立装 HAMi scheduler,直接用 Volcano vGPU 插件:
1 | # 1. 安装 Volcano |
5.3 多个 vGPU Pod 的显存隔离验证
同时跑两个 Pod,分别验证显存限制生效:
1 | # pod-a.yaml — 申请 2000MB |
两个 Pod 部署到同一节点后,分别 nvidia-smi 看到不同的显存上限——物理上是同一张卡,HAMi 让每个 Pod 以为自己独占一张”小卡”。
六、生产排障
6.1 vGPU Pod 一直 Pending:Insufficient nvidia.com/gpu
1 | # 1. 看节点还有多少 vGPU |
6.2 nvidia-smi 在容器内不显示 HAMi-core 信息
1 | # 确认 libvgpu.so 挂载成功 |
6.3 显存限制不生效(Pod 用了超过申请的显存)
- 确认
nvidia.com/gpumem在limits段而非requests段(HAMi 读的是 limits) - 确认
hami-device-plugin启动参数中--device-memory-scaling=1(关闭虚拟显存,默认值) - 部分旧版 CUDA 程序使用
cuMemAlloc而非cudaMalloc,HAMi 需确认是否拦截了该 API
6.4 算力限制感觉没效果
- 算力(
gpucores)限制是时间片比例,不是硬上限。如果 GPU 空闲,即使设了 30% 也能跑到 100% - 要想强制上限,需要设置
priority=1(低优先级),且 GPU 上有高优先级 Pod 在跑时才会严格限速
七、面试高频 5 题
1. HAMi 和 MIG 的本质区别?
MIG 是硬件级切分(A100 物理上切成 7 块),静态分区不灵活;HAMi 是软件级通过 LD_PRELOAD 注入拦截库,动态限制显存和算力,不限 GPU 型号。MIG 隔离性完胜,HAMi 灵活性完胜。
2. HAMi 怎么实现显存隔离?
通过 libvgpu.so 拦截 CUDA 的显存分配 API(cudaMalloc 等),记录每个进程已分配的显存总量,超过 nvidia.com/gpumem 限制时直接返回 cudaErrorMemoryAllocation,不给显存。同时 Hook nvidia-smi 的输出,让它”看起来”只有申请的大小。
3. HAMi Device Plugin 为什么把 1 张 GPU 上报成 10 个 vGPU?
deviceSplitCount 默认值 10,用于控制并发度——一张物理 GPU 上最多同时跑 10 个 vGPU Pod。这个值和性能无关,只影响调度并发。调大了可以多塞 Pod,但 GPU 竞争更激烈;调小了 Pod 排队等更久。
4. nvidia.com/gpucores: 30 是怎么限制算力的?
HAMi 在 CUDA kernel launch 时插入时间片控制:根据 gpucores 百分比计算每个时间窗口内该进程能占用的 SM(流处理器)时间比例。但它不是硬上限——如果 GPU 空闲,低利用率任务仍然可以跑到 100%。要强制限速,需要配合优先级策略。
5. HAMi 和原生 Time-Slicing 方案的核心差异?
Time-Slicing 只分调度时间,不隔离显存——Pod A 可能吃满 16GB 导致 Pod B OOM。HAMi 的核心价值就是显存硬隔离:每个 Pod 分到独立的显存配额,超了就报错,不会互相影响。这是”能用”和”能用好”的区别。
总结
1 | MIG 硬件切分 ──→ A100/A30/H100 专属(最强隔离) |
HAMi 填补了”没有 A100 但想共享 GPU”的缺口。核心就三个组件:Device Plugin 上报虚拟资源、Scheduler 感知调度、libvgpu.so 拦截隔离。安装 3 条命令,Pod 加 3 个 resource limits,搞定。