深入解析 K8s GPU 复用:从通用方案到 HAMi 异构架构全解
深入解析 K8s GPU 复用:从通用方案到 HAMi 异构架构全解
在云原生 AI 时代,GPU 作为算力核心资产,其高昂的成本与常常不到 40% 的实际利用率,让"显卡复用"成为企业降本增效的必答题。本文将全景解析当前主流的 GPU 复用方案,并深度剖析国产开源项目 HAMi 如何通过底层 Hook 技术与云原生调度,实现多厂商异构 GPU 的精细化隔离。
一、 GPU 复用全景:8 大主流方案对比
当前主流的显卡复用方案可按技术层次划分为五大类,不同路线在隔离强度、性能损耗、硬件门槛和部署复杂度上呈现明显的梯度差异:从硬到软,灵活性递增、隔离性递减。
| 方案 | 技术层次 | 隔离级别 | 性能损耗 | 硬件门槛 | 典型场景 |
|---|---|---|---|---|---|
| MIG | 硬件分区 | 物理硬隔离 | 几乎无 | 仅 A100/A30/H100 等 | 多租户推理、确定 QoS |
| NVIDIA vGPU | 驱动/Hypervisor 级 | 驱动级硬隔离 | 较低 | VGX 系列卡 + IOMMU | VDI、图形桌面云、虚机推理 |
| SR-IOV | PCIe 硬件虚拟化 | 硬件级 | 极低 | Ampere 及以后数据中心卡 | HPC/AI 多虚机直通 |
| CUDA MPS | API/运行时级 | 共享上下文 | 低 | 所有 NVIDIA GPU | 同机多进程推理、MPI 作业 |
| Time-Slicing | 容器/调度级 | 无隔离 | 中 | 所有 NVIDIA GPU | 开发/测试、轻量推理 |
| HAMi / libvgpu.so | CUDA API Hook | 软隔离 | 较低 | 不挑硬件,支持国产卡 | K8s 推理共享、显存配额超卖 |
| GPU 池化 | 远程调用 | 软隔离+网络隔离 | 较高 | CPU/GPU 节点分离 | 跨节点池化、CPU 节点远程用卡 |
| DRA (K8s 1.34+) | K8s 原生设备 API | 取决于策略 | 取决于实现 | 通用 | K8s 原生 GPU 分配统一入口 |
选型建议:
- 若硬件支持且需硬隔离 → MIG
- 需 VM 级隔离与热迁移 → NVIDIA vGPU
- 同一 K8s 集群内多 Pod 共享、需显存配额与超卖 → HAMi
- 快速验证或不支持 MIG 的卡 → Time-Slicing
- 面向未来 K8s 原生统一调度 → DRA
二、 HAMi 的黑魔法:零侵入 API Hooking 原理
在上述方案中,HAMi(Heterogeneous AI Computing Virtualization Framework) 因其不挑硬件、免费开源、支持显存超卖的特性,成为国内 K8s 推理集群提升利用率的主流开源方案。而 HAMi 实现零侵入隔离的核心,依赖于 Linux 系统的两大底层机制:LD_PRELOAD 和 dlsym。
整体拦截拓扑
在展开两个机制前,先看 HAMi 拦截一次显存申请的完整数据流:
1 | AI 程序(PyTorch/tf)进程 |
关键点:AI 程序以为自己调的是 NVIDIA 的 cuMemAlloc,实际先进了 HAMi 的替身;替身查配额、放行后才把请求转给真驱动。全程不改 AI 程序代码、不改驱动,这就是"零侵入"。
1. LD_PRELOAD:强行插队的"替身乘务员"
LD_PRELOAD 是 Linux 系统中的一个环境变量。程序运行时,动态链接器会按特定顺序加载共享库,而 LD_PRELOAD 拥有最高优先级。
通俗比喻:假设你要坐高铁(运行程序),正常会去找 3 号车厢的乘务员(标准库
libcuda.so)。但设置了LD_PRELOAD后,就像系统在检票口给你派了一个"替身乘务员"(你自定义的库libvgpu.so)。你一喊乘务员,响应你的是替身,而不是真身。
在 GPU 复用中的用途:HAMi 将自己编译的动态库 libvgpu.so 通过 LD_PRELOAD 注入容器。当 AI 程序调用 CUDA 库申请显存时,会最先跳入 libvgpu.so 的同名函数中,从而被 HAMi 拦截。
2. dlsym 与 RTLD_NEXT:寻找真身的"暗号"
如果替身乘务员只会拦车不会发车,系统就崩了。假函数在检查完配额后,必须把请求转发给真正的显卡驱动。
dlsym 是 Linux 动态加载库提供的 API,用于在运行时根据函数名查找真实内存地址。其中有一个特殊的伪句柄 RTLD_NEXT。
作用:当调用 dlsym(RTLD_NEXT, "cuMemAlloc") 时,它告诉动态链接器:
“在当前加载的库列表中,找名为
cuMemAlloc的函数,但跳过我自己,找排在我后面的第一个真正的cuMemAlloc。”
这巧妙地避免了假函数调用自己导致的死循环。
3. cuMemAlloc:向显卡申请显存的底层动作
PyTorch 中的 torch.cuda.malloc 底层最终调用的都是 NVIDIA 驱动层的 cuMemAlloc。拦截了这个函数,就掐住了所有 AI 框架申请显存的咽喉。
核心拦截逻辑伪代码:
1 |
|
三、 坚守当下:HAMi 的 Device Plugin 异构调度机制
由于 DRA 要求较高的 K8s 版本,当前生产环境主流仍是 Device Plugin 模式。HAMi 在此模式下通过「统一的 MutatingWebhook + 统一的 Scheduler-Extender + 每厂商一个 DevicePlugin + 每厂商一套资源名 + 每厂商一个 Hook 库」实现异构支持。
整体异构调度拓扑
1 | 用户提交 Pod(声明多厂商资源) |
三个统一 + 每厂商一份:Webhook 和 Scheduler-Extender 是全局唯一的;每个厂商有自己的 DevicePlugin、资源名、Hook 库(libvgpu.so/libvnpu.so/…)。这样加新厂商只需加一个 DP + 一套资源名 + 一个 Hook 库,不用动 Webhook 和调度器。
1. 架构与资源映射
不同厂商在 HAMi 中的适配方式:
| 厂商 | 扩展资源名 | 隔离实现 | 节点标签 |
|---|---|---|---|
| NVIDIA | nvidia.com/gpu / gpumem / gpucores |
libvgpu.so (LD_PRELOAD Hook) |
gpu=on |
| 华为昇腾 | huawei.com/Ascend910B3 / -memory / -core |
libvnpu.so + Limiter |
ascend=on |
| 寒武纪 MLU | cambricon.com/vmlu / vmemory / vcore |
厂商 SMLU 硬件级 + HAMi 调度 | mlu=on |
| 海光 DCU | hygon.com/dcunum / dcumem / dcucores |
libvgpu-control.so 变体 |
dcu=on |
2. 核心调度链路(五步走)
Step 1: 节点注册 — 每厂商 DevicePlugin 每 30 秒 Patch 节点注解,把设备规格写入 hami.io/node-{device-type}-register。异构节点示例(同时有 NVIDIA 和 MLU):
1 | hami.io/node-nvidia-register: GPU-00552014-...,10,32768,100,NVIDIA-V100,0,true |
Step 2: MutatingWebhook 拦截 — Webhook 遍历所有已注册设备类型,调用每个厂商的 MutateAdmission 判断 Pod 是否请求了该厂商资源。命中则把 schedulerName 改写为 hami-scheduler。
Step 3: Filter 阶段(全局记账) — Scheduler-Extender 持续解析所有节点注解构建全局设备表,并累加运行中 Pod 的已用量。在 Filter 阶段按厂商分别检查剩余显存/算力是否满足。
Step 4: Bind 阶段(结果传递) — 由于原生 Bind 无法传递配额,HAMi 在 Bind 阶段把完整分配决策 Patch 到 Pod 注解:
1 | hami.io/vgpu-devices-allocated: GPU-0fc3eda5-...,NVIDIA,3000,0:; |
Step 5: Allocate 阶段(环境注入) — Kubelet 调用 DevicePlugin 的 gRPC Allocate 接口,HAMi 从 Pod 注解读取配额,注入环境变量(如 HAMI_CONTAINER_MEM)并挂载 Hook 库(如 libvgpu.so 设置 LD_PRELOAD)。
3. 配置示例:异构集群 Helm 部署
通过 Helm values.yaml 启用多厂商:
1 | devicePlugin: |
提交异构任务时,只需声明不同厂商的扩展资源:
1 | # Ascend 910C 推理,软切分 28Gi 显存 + 40% 算力 |
四、 迈向未来:HAMi 的 DRA 模式与异构支持
Kubernetes 1.34 开始,DRA(Dynamic Resource Allocation)正式 GA,它突破了 Device Plugin 只能上报整数数量的限制,支持显存级申请。HAMi 也顺势推出了 HAMi-DRA 架构。
DRA 与 Device Plugin 模式对比
1 | Device Plugin 模式(当下): DRA 模式(未来): |
DRA 把 HAMi 原来靠 Webhook+Extender+注解协议"绕"出来的能力(显存级申请、异构统一调度)变成了 K8s 原生 API,链路更短、更标准。DRA 模式下每个厂商需一个独立 DRA Driver,目前 HAMi 支持:
- NVIDIA GPU:
hami-core-gpu.project-hami.io(GA) - 华为昇腾 910C:
ascend.project-hami.io(GA,HAMivNPUCore 模式)
异构集群部署示例(NVIDIA + Ascend)
在 DRA 模式下,异构集群采用"每个厂商一个独立 DRA Driver + 一个统一 Webhook"的架构:
1 | # 1. 前置要求:K8s >= 1.34,开启 FeatureGate DRAConsumableCapacity |
原生 DRA 异构任务 YAML——一个 Pod 可以同时申请 NVIDIA 和 Ascend 设备:
1 | apiVersion: resource.k8s.io/v1 |
五、 总结
显卡复用的本质是在"隔离性"与"弹性"之间寻找平衡点。
- 底层技术:
LD_PRELOAD与RTLD_NEXT构成了 HAMi 零侵入拦截的基石,让显存配额限制成为可能。 - 当下生产:HAMi 的 Device Plugin 模式通过 Webhook + 节点注解协议 + 厂商级 DevicePlugin,巧妙绕过了 K8s 原生 API 的限制,实现了对 12+ 厂商异构 AI 加速器的统一调度。
- 未来趋势:随着 K8s 1.34 DRA 的 GA,HAMi 正逐步将异构调度逻辑迁移至原生的
ResourceSlice与ResourceClaimAPI。DRA 模式将简化调度链路,但 Device Plugin 模式在短时间内仍是兼容多厂商与旧版本集群的中流砥柱。
一句话:HAMi 的精髓是用 LD_PRELOAD 这把"软刀子"在不改程序、不改驱动的前提下给显存配额套上枷锁,再用 Device Plugin + Webhook + Extender 把这套机制织进 K8s 调度——当下靠注解协议扛异构,未来靠 DRA 原生化。
