一块 A100 80GB,只跑了一个占用 2GB 显存的推理服务,<span leaf="">nvidia-smi</span> 里 GPU 利用率长期 5%。

这不是个别现象。很多 AI 集群真正浪费的不是 CPU,也不是内存,而是被“整卡分配”锁死的 GPU。

Kubernetes 原生 GPU 管理只认识 <span leaf="">nvidia.com/gpu: 1</span> 这种整卡资源。但推理服务、Notebook、开发调试、轻量训练,往往只需要几 GB 显存和一部分算力。

要把 GPU 用起来,就得把链路打通:驱动、运行时、device-plugin、调度器、容器内限制,一个都不能少。


先看原生 K8s 卡在哪里

Kubernetes 管 GPU 的入口是 Device Plugin Framework

NVIDIA 的 <span leaf="">k8s-device-plugin</span> 会在节点上发现 GPU,然后通过 gRPC 向 kubelet 注册扩展资源,比如:

resources:
  limits:
    nvidia.com/gpu: 1 # 只能按整卡申请

Pod 调度时,scheduler 只知道某个节点还有几张 GPU。kubelet 在创建容器时,调用 device-plugin 的 <span leaf="">Allocate</span>,拿到设备 ID,再把 GPU 暴露给容器。

这个模型很稳,但粒度太粗。

比如一台机器有 8 张 A100。K8s 看到的是 <span leaf="">nvidia.com/gpu=8</span>,不是 <span leaf="">gpu-memory=640GB</span>,也不是 <span leaf="">gpu-cores=800%</span>

所以只要一个 Pod 申请了 1 张卡,即使它只用了 2GB 显存,剩下 78GB 对 K8s 来说也已经“不可用”。

这就是 GPU 共享方案要解决的核心问题:让调度器理解显存和算力,并在运行时真的限制住它们。


GPU Operator 负责铺路,不负责切卡

生产环境里,不建议手工在 GPU 节点上装驱动、装 container runtime、装 device-plugin。

节点一多,驱动版本、CUDA 版本、container-toolkit 版本,很容易变成运维事故现场。

NVIDIA GPU Operator 的价值在这里。

它以 Operator 方式托管 GPU 节点的软件栈,包括:

组件作用
NVIDIA Driver安装或管理 GPU 驱动
nvidia-container-toolkit让容器能访问 GPU
device-plugin向 kubelet 注册 GPU 资源
DCGM Exporter采集 GPU 监控指标
MIG Manager管理 MIG 实例
Node Feature Discovery标记 GPU 节点能力

但要注意一点:GPU Operator 本身不是细粒度共享方案。

它解决的是 GPU 节点 Day-0 / Day-1 运维问题。真正的显存切分、算力配额、虚拟资源调度,需要 MIG、MPS、HAMi 这类方案接上来。

可以把整体链路理解成这样:

图片

如果你用 HAMi,通常会让 HAMi 的 device-plugin 接管 GPU 资源注册和分配。GPU Operator 仍然可以负责驱动、runtime、DCGM 等底层能力。


MIG、MPS、HAMi,到底选哪个?

GPU 虚拟化不是一个方案打天下。

不同场景,对隔离性、利用率、硬件兼容、运维复杂度的要求不同。

方案切分方式隔离性支持硬件典型场景
MIG硬件级切分A100/A30/H100 等在线推理、多租户
MPS多进程共享上下文多数 NVIDIA GPU单租户高并发任务
HAMi软件虚拟化 + 调度扩展NVIDIA/AMD/寒武纪等开发环境、推理、混部
商业 qGPU/cGPU厂商实现中到强云厂商平台公有云托管场景

MIG 的优势是隔离性强。每个实例有独立的显存、计算单元、缓存切分,故障隔离也更好。

问题是粒度固定。比如 A100 的 MIG profile 是预定义的,你不能随意切一个 3GB 显存、20% 算力的实例。

MPS 更像是性能优化手段。它让多个 CUDA 进程共享 GPU 上下文,减少上下文切换,但不擅长做租户隔离和配额约束。

HAMi 的定位更偏 K8s 云原生。它让用户可以按 <span leaf="">显存 + 算力比例</span> 申请 GPU,并通过调度器和容器内拦截机制做限制。

如果是强隔离在线业务,优先看 MIG。如果是研发环境、推理小模型、高密度混部,HAMi 更灵活。


HAMi 是怎么把一张卡拆开的?

HAMi,原名 <span leaf="">k8s-vgpu-scheduler</span> / <span leaf="">vGPU scheduler</span>,现在叫 Heterogeneous AI computing Management Interface。它在 2024 年进入 CNCF Sandbox,社区活跃度和生态兼容性都在提升。

它的关键点不是“真的把 GPU 物理切开”,而是在三个阶段协同完成虚拟化。

1. 调度阶段:让 scheduler 看懂虚拟资源

用户提交 Pod 时,除了申请 GPU 数量,还可以申请显存和算力比例。

一个典型 Pod 可能长这样:

resources:
  limits:
    nvidia.com/gpu: 1       # 申请 1 个虚拟 GPU
    nvidia.com/gpumem: 4096 # 申请 4GB 显存
    nvidia.com/gpucores: 30 # 申请 30% 算力

HAMi scheduler extender 会参与调度决策。

它会检查每个节点、每张物理 GPU 上还剩多少显存、多少算力。然后根据 binpack 或 spread 策略,选择合适的节点和 GPU。

调度完成后,分配结果会写入 Pod annotation。后面的 device-plugin 和运行时限制都依赖这份分配信息。

2. 分配阶段:device-plugin 注入环境

HAMi device-plugin 会替换或接管原生 NVIDIA device-plugin 的能力。

当 kubelet 调用 <span leaf="">Allocate</span> 时,它会读取 Pod annotation,然后向容器注入必要的环境变量和挂载。

常见信息包括:

CUDA_VISIBLE_DEVICES=0
GPU_MEMORY_LIMIT=4096
GPU_CORE_LIMIT=30
LD_PRELOAD=/usr/local/vgpu/libvgpu.so

这些变量不是给 K8s 看的,而是给容器里的 CUDA 进程看的。

也就是说,K8s 调度层只负责“分给谁”。真正限制显存和算力,要进入容器运行时阶段。

3. 运行时阶段:拦截 CUDA API

HAMi 的显存限制通常依赖 LD_PRELOAD

容器启动后,libvgpu.so 会优先加载。它会劫持 CUDA Driver API,比如 cuMemAlloccuMemFree,维护每个进程的显存计数。

当进程申请显存时,流程大概是:

图片

算力限制更复杂。

常见做法是监控任务的 SM 使用情况,再通过时间片、sleep 注入等方式控制实际占用。这类限制不是硬件级隔离,所以会有一定抖动。

这也是为什么 HAMi 很适合提升利用率,但不能替代强隔离场景下的 MIG。


快速落一套 HAMi:先跑通,再调优

下面给一个最小化实践路径。

前提是 GPU 节点驱动和容器运行时已经正常。可以通过 GPU Operator 部署,也可以你自己维护,但生产里更推荐 Operator。

先确认节点能看到 GPU:

kubectl get nodes -l nvidia.com/gpu.present=true
kubectl describe node <gpu-node> | grep -A5 Capacity

安装 HAMi 通常可以用 Helm。实际版本请以社区 release 为准,生产环境不要直接追 latest。

helm repo add hami https://project-hami.github.io/HAMi
helm repo update

helm install hami hami/hami \
  -n kube-system \
  --set scheduler.defaultSchedulerPolicy=binpack

安装后重点看三个组件:

kubectl get pods -n kube-system | grep hami
kubectl get ds -n kube-system | grep hami
kubectl logs -n kube-system deploy/hami-scheduler -f

然后提交一个申请 4GB 显存、30% 算力的测试 Pod。

apiVersion: v1
kind: Pod
metadata:
  name: vgpu-test
spec:
  containers:
  - name: cuda
    image: nvidia/cuda:12.2.0-base-ubuntu22.04
    command: ["sleep", "3600"]
    resources:
      limits:
        nvidia.com/gpu: 1
        nvidia.com/gpumem: 4096
        nvidia.com/gpucores: 30

Pod Running 后,不要只看 <span leaf="">nvidia-smi</span>

你还要检查 Pod annotation、环境变量、容器内 CUDA 可见设备。

kubectl get pod vgpu-test -o yaml | grep -A8 annotations
kubectl exec -it vgpu-test -- env | grep -E "CUDA|GPU|VGPU"
kubectl exec -it vgpu-test -- nvidia-smi

如果 annotation 没有分配信息,先查 scheduler。如果环境变量没注入,查 device-plugin。如果限制不生效,再查 LD_PRELOAD 和 CUDA 版本兼容。

这条排查链路很关键:调度成功,不代表运行时限制成功。


生产里最容易踩的坑

GPU 虚拟化上生产,难点不在安装,而在边界条件。

CUDA 版本兼容

LD\_PRELOAD 方案依赖 CUDA API 拦截。CUDA 版本变化后,API 行为、符号、调用路径可能变化。

如果你集群里同时跑 CUDA 11、CUDA 12,甚至不同基础镜像混用,一定要做矩阵测试。

建议至少覆盖:

  • PyTorch / TensorFlow 主版本
  • CUDA runtime 版本
  • Driver 版本
  • 常用推理框架,比如 Triton、vLLM、TensorRT-LLM

尤其是 vLLM 这类对显存管理比较激进的框架,最好单独压测。

监控不能只看物理卡

DCGM 和 nvidia-smi 看到的是物理 GPU 视角。虚拟 GPU 的配额、使用量、OOM 次数、限流次数,需要从 HAMi 或自定义 exporter 补齐。

生产里至少要有这些指标:

指标用途
物理 GPU 显存使用率判断整体水位
虚拟 GPU 显存配额判断是否超分
虚拟 GPU 实际显存判断单租户异常
GPU SM 利用率判断算力瓶颈
CUDA OOM 次数发现配额不合理
调度失败次数发现碎片化问题

只看物理卡会出现一个问题:明明物理显存还有空闲,Pod 却因为虚拟配额不足调度失败。

这不是 bug,而是调度层的资源账本和物理层视角不一致。

软件隔离不是故障隔离

HAMi 可以限制显存申请,可以压算力占用。但它不能阻止所有 GPU 级别故障传播。

比如驱动异常、GPU reset、ECC 错误、某些非法 kernel 调用,仍可能影响同卡其他任务。

所以多租户强隔离场景要谨慎。

一个常见组合是:

  • 在线核心推理:MIG
  • 离线训练和实验:HAMi
  • 单租户高吞吐:MPS
  • 云厂商托管:商业 GPU 共享方案

不要为了利用率,把所有业务都塞进同一种虚拟化模型。


动态算力分配,下一步会怎么走?

现在很多集群的 GPU 配额还是静态的。

用户申请 4GB 显存、30% 算力,系统就按这个额度分配。但真实负载经常有潮汐:推理有峰谷,Notebook 有空闲,训练也有数据加载间隙。

更理想的方式是动态调整。

比如某个推理 Pod 当前 QPS 很低,只保留 10% 算力。另一个批处理任务进入高峰,就临时提升到 60%。

这要求系统具备三类能力:

  1. 实时采集虚拟 GPU 使用数据
  2. 根据策略调整算力份额
  3. 在不重启 Pod 的情况下让限制生效

显存动态调整会更难。

因为很多深度学习框架会预分配显存池。即使业务暂时不用,也不一定会主动释放给其他进程。

所以短期更现实的是:动态调算力,显存仍偏静态配额。

长期看,GPU Pool 化、远程 GPU、RDMA、跨节点调度会继续演进。但延迟、带宽、故障域都会变复杂,不是简单把 GPU 变成“远程磁盘”就完事。


写在最后

Kubernetes 原生 GPU 管理解决了“能不能用 GPU”,但没有解决“能不能把 GPU 用满”。

真正落地 GPU 虚拟化,需要从 device-plugin、GPU Operator、调度扩展、运行时拦截、监控告警一起看。HAMi 给了开源体系一条比较完整的路径,但生产里仍要结合 MIG、MPS 和业务隔离要求做取舍。

正文到此结束
  • 本文作者:xinyu.he
  • 文章标题:Kubernetes 集群中 GPU-动态算力分配
  • 本文地址:https://www.hxy.bj.cn/archives/824/
  • 版权说明:若无注明,本文皆Xinyu.he blog原创,转载请保留文章出处。
最后修改:2026 年 06 月 10 日
如果觉得我的文章对你有用,请随意赞赏