一条完整主线
01
业务需求
独占、强 SLA、开发测试、细粒度显存或算力
02
隔离目标
故障、显存、SM、带宽、时延分别要隔离到什么程度
03
选择机制
整卡 / MIG / MPS / Time-Slicing / HAMi
04
暴露资源
Device Plugin、GPU Operator 或 HAMi 向 Kubelet 注册资源
05
调度放置
kube-scheduler 或 HAMi Scheduler 选择节点和物理 GPU
06
运行时执行
MIG 硬件实例、MPS server、CUDA 时间片或 HAMi-Core 执行限制
07
监控兜底
DCGM、进程显存、P99、Xid、OOM 与共享密度
先分清五个层次
| 层次 | 机制 | 真正被切分的对象 | 隔离强度 |
| 设备直通 | 整卡 / PCIe Passthrough | 整张物理 GPU | 最强,但粒度最粗 |
| 硬件分区 | MIG | GPU Instance / Compute Instance,对应 SM、显存切片和缓存等硬件资源 | 强 |
| 驱动级并发 | MPS | 多个 CUDA client 通过 MPS server 共享执行资源 | 中等,仍共享故障域和部分硬件 |
| 时间复用 | Time-Slicing | 多个进程交错获得 GPU 执行时间 | 弱,不提供显存和性能硬隔离 |
| K8S 软件虚拟化 | HAMi | 用调度账本分配显存/算力,再在容器内执行软限制 | 取决于设备后端,NVIDIA 常见路径是软件隔离 |
核心机制对比
| 机制 | K8S 如何表达 | 能保证什么 | 不能保证什么 | 典型场景 |
| MIG | 每个 MIG profile 作为独立扩展资源,例如 nvidia.com/mig-1g.10gb | 固定规格的硬件资源与故障隔离,性能更稳定 | 不能动态借用相邻实例的空闲资源;profile 组合会产生碎片 | 强 SLA 推理、多租户生产、稳定小训练 |
| MPS | 官方 Device Plugin 按 replicas 暴露共享访问,MPS daemon 管理 client | 多进程 kernel 并发;可限制 client 的执行资源和显存 | 不等于 MIG 级隔离;共享故障域,干扰仍需监控 | 可信 workload、小 kernel、多路推理 |
| Time-Slicing | Device Plugin 为每张卡创建多个逻辑引用 | 让多个 Pod 获得同一张 GPU 的共享访问权 | replicas=N 不代表固定 1/N 算力,也不隔离显存 | Notebook、开发测试、低优离线任务 |
| HAMi | nvidia.com/gpu + nvidia.com/gpumem + nvidia.com/gpucores | 细粒度显存/算力请求、设备感知调度、异构加速器统一管理 | 软件限制不自动获得 MIG 的硬件故障隔离;能力依赖设备后端 | 私有云、多团队研发、细粒度 vGPU、国产异构设备 |
| CUDA VMM | 不是 K8S 资源类型,由应用或运行时调用 CUDA Driver API | 虚拟地址与物理显存页分离,支持按需映射和弹性显存池 | 不负责多租户算力隔离,也不是 GPU 调度器 | KV Cache、模型权重缓存、弹性显存 |
为什么默认 Kubernetes 不够
默认 NVIDIA Device Plugin 把 GPU 注册为整数扩展资源,例如 nvidia.com/gpu: 8。kube-scheduler 主要知道“还有几份资源”,不知道:
- 两个逻辑资源是否来自同一张物理 GPU。
- 当前还剩多少真实显存、SM 和 HBM 带宽。
- 两个 workload 放在一起会产生多大干扰。
- 一个共享进程崩溃是否会影响同卡其他进程。
因此,GPU 虚拟化至少需要两部分:
- 资源表达和放置:Device Plugin、GPU Operator、HAMi Scheduler 等告诉 Kubernetes 能分配什么。
- 执行和隔离:MIG、MPS、CUDA driver、HAMi-Core 等真正执行硬件或软件限制。
只改 scheduler 的资源数量,不会自动得到显存、算力和故障隔离。
选型决策树
01
是否要求强故障隔离和稳定 SLA
是 -> 整卡或 MIG
02
是否需要细粒度显存/算力任意配比
是 -> HAMi,或厂商 vGPU 方案
03
是否是可信 workload 且希望 kernel 并发
是 -> MPS
04
是否只想让低利用率任务共享访问
是 -> Time-Slicing
05
是否主要解决 KV Cache / 权重显存弹性
是 -> CUDA VMM,不要把它当 GPU 算力虚拟化
Q: MIG、MPS、Time-Slicing 和 HAMi 最本质的区别是什么?
核心含义
MIG 在硬件层切设备,MPS 在驱动层并发执行,Time-Slicing 在时间上共享访问,HAMi 在 Kubernetes 调度和容器运行时层做细粒度软件配额。
判断标准
不要只背“隔离强弱”,还要追问隔离对象:MIG 能提供固定硬件实例;MPS 可以限制 client 的执行资源和显存但仍共享故障域;Time-Slicing 只提供共享访问;HAMi 的显存/算力限制依赖软件拦截或设备后端能力。
Q: GPU 虚拟化是不是就是把一张卡显示成很多张卡?
不是。“显示成多份”只是资源表达。真正要看底层有没有硬件实例、执行资源限制、显存限制和故障隔离。Time-Slicing 可以把一张卡上报成多个逻辑 slot,但这些 slot 仍然共享整张卡;MIG 的每一份则对应真实硬件实例。
常见误区
资料来源
先把四个“Runtime”概念分开
| 名词 | 典型实现 | 在 GPU 接入中的职责 |
| CRI 容器运行时 | containerd、CRI-O | 接收 kubelet 创建 Pod/Container 的请求,管理镜像、sandbox 和容器生命周期 |
| 低层 OCI Runtime | runc、crun | 按照 OCI spec 真正创建 Linux namespace、cgroup 和进程 |
| NVIDIA Container Toolkit / Runtime | nvidia-ctk、nvidia-container-runtime、CDI hook/spec | 把 GPU device node、驱动库和必要环境注入容器;它不负责 Kubernetes 调度 |
| Kubernetes RuntimeClass | runtimeClassName: nvidia | 让 Pod 选择 containerd/CRI-O 中某个 runtime handler;它只是 Kubernetes 选择入口,不是另一套容器引擎 |
GPU Operator v25.10.0 及以后默认启用 CDI(Container Device Interface),用于标准 GPU workload 的设备注入。通过 Device Plugin 正常申请 nvidia.com/gpu 的 Pod 通常不需要显式写 runtimeClassName;旧版或手工 legacy runtime 路径如果没有把 NVIDIA runtime 设为默认,则需要配置并选择 RuntimeClass。不要把不同版本的教程拼成一份配置。
一张图看懂“系统识别 GPU”
01
主机 PCIe 枚举 GPU
lspci 能看到 NVIDIA 设备
02
NVIDIA Kernel Driver 绑定设备
nvidia-smi、/dev/nvidia* 正常
03
Container Toolkit 配置 containerd/CDI
GPU 能被正确注入容器
04
Device Plugin 向 kubelet 注册
/var/lib/kubelet/device-plugins/ 下建立 gRPC socket
05
kubelet 上报健康设备
Node Capacity/Allocatable 出现 nvidia.com/gpu
06
kube-scheduler 选择节点
Pod limits 请求整数 GPU
07
Device Plugin Allocate
返回 device、mount、env 或 CDI device
08
容器运行 CUDA canary
vectorAdd/Test PASSED
09
DCGM/告警接管
持续观测 Xid、ECC、温度、功耗与利用率
这里最重要的边界是:
- Driver 让宿主机内核和 CUDA 用户态能够访问 GPU。
- Container Toolkit/CDI 让容器能获得设备节点和匹配的宿主机驱动库。
- Device Plugin 让 Kubernetes 看见、分配和健康管理 GPU。
- GPU Operator 自动部署和维护前面这些组件,本身不是 CUDA 数据面。
- NFD/GFD 负责硬件与 GPU 属性标签,帮助 Operator 和 scheduler 识别节点类型。
- DCGM Exporter 负责指标,不参与设备分配。
接入前检查:先隔离,再部署
1. 节点基础面
新节点首先要完成普通 Kubernetes worker 的基线:
- 固件、BIOS、BMC、GPU 型号和 PCIe 拓扑符合机器验收单。
- OS、kernel、containerd、kubelet 版本符合当前集群支持矩阵。
- hostname、DNS、NTP、镜像仓库、软件源和控制面网络可达。
- kubelet 的 cgroup driver 与 containerd 配置一致。
- 如果需要 RDMA/GPUDirect,再单独核对 NIC、OFED、IOMMU、NUMA 和拓扑;不要把“GPU 能跑”当成“多机训练网络已就绪”。
NODE=gpu-node-01
kubectl get node "$NODE" -o wide
kubectl describe node "$NODE"
# 先阻止普通业务进入,canary Pod 会单独容忍这个 taint
kubectl taint node "$NODE" gpu-bootstrap=true:NoSchedule --overwrite
如果节点还没有加入集群,应先走平台既有的 kubeadm、Cluster API 或云厂商节点池流程。不要把包含 token、CA hash 的一次性 kubeadm join 命令写死到公共 Runbook。
2. 宿主机硬件与驱动基线
lspci -nn | grep -i nvidia
uname -r
lsmod | grep '^nvidia'
nvidia-smi -L
nvidia-smi --query-gpu=index,uuid,name,pci.bus_id,driver_version,memory.total \
--format=csv
nvidia-smi topo -m
验收标准:
lspci 数量和机型 BOM 一致。nvidia-smi -L 列出的 UUID 数量一致且没有掉卡。- Driver 版本属于平台锁定的 GPU Operator/Container Toolkit/CUDA 兼容矩阵。
- 拓扑与预期 PCIe/NVLink/NVSwitch 连接一致。
- Secure Boot、kernel module 签名、nouveau 冲突、Xid/ECC 错误已经处理。
若 lspci 都看不到 GPU,应先检查硬件、虚拟机 passthrough 或 BIOS;此时安装 Device Plugin 没有意义。
路径 A:已有 GPU Operator,推荐生产使用
1. 先判断 Operator 是否已经是集群能力
helm list -A | grep gpu-operator
kubectl get clusterpolicy
kubectl get pods -n gpu-operator
GPU Operator 是集群级控制器,不需要为每个新节点再 helm install 一次。默认情况下,NFD 发现 NVIDIA PCI vendor ID 后会产生:
feature.node.kubernetes.io/pci-10de.present=true
Operator 根据这个标签把 driver、toolkit、device-plugin、GFD、DCGM 和 validator 等 operand 部署到 GPU worker。应等待真实 NFD 探测结果,不要为了“让 Pod 跑起来”手工伪造硬件标签。
kubectl get node "$NODE" \
-o jsonpath='{.metadata.labels.feature\.node\.kubernetes\.io/pci-10de\.present}{"\n"}'
kubectl get pods -n gpu-operator -o wide \
--field-selector spec.nodeName="$NODE"
kubectl get events -n gpu-operator --sort-by='.lastTimestamp'
如果 GPU 节点有业务 taint,需要确认 Operator 管理的 DaemonSet 具有对应 toleration,否则控制器识别到了节点,节点侧 operand 仍然落不下来。
2. 明确 Driver 与 Toolkit 由谁管理
| 节点镜像现状 | Operator 配置原则 | 注意事项 |
| Driver、Toolkit 都未预装 | Operator 默认管理 Driver、Toolkit 和 Device Plugin | GPU worker 的 OS/kernel 需要符合 Driver Container 支持矩阵 |
| 已预装 Driver | 集群安装时使用 driver.enabled=false | 驱动版本由镜像/节点运维体系负责升级 |
| 已预装 Driver + Toolkit | 使用 driver.enabled=false、toolkit.enabled=false | 必须提前验证 containerd/CDI 或 NVIDIA runtime 配置 |
这些 Helm value 通常影响整个 ClusterPolicy,不是“临时只给一台节点改一下”的开关。混合 OS、混合 Driver 版本或不同责任边界应通过节点池、Driver CRD/nodeSelector 等受控方案管理,不能让同一节点同时被主机包管理器和 Driver Container 争抢内核模块。
如果集群还没有 Operator,安装时必须先从官方 Platform Support/Component Matrix 锁定版本,再写入 GitOps values:
GPU_OPERATOR_VERSION=<经过平台验证并锁定的版本>
helm repo add nvidia https://helm.ngc.nvidia.com/nvidia
helm repo update
helm upgrade --install gpu-operator nvidia/gpu-operator \
--namespace gpu-operator \
--create-namespace \
--version "$GPU_OPERATOR_VERSION" \
--wait
生产环境还需要把 registry mirror、imagePullSecret、proxy、toleration、priorityClass、驱动类型和版本写入 values 文件,而不是长期依赖命令行 --set。
3. 验证 Operator 状态
kubectl get clusterpolicy cluster-policy \
-o jsonpath='{.status.state}{"\n"}'
kubectl get pods -n gpu-operator -o wide \
--field-selector spec.nodeName="$NODE"
kubectl logs -n gpu-operator \
-l app=nvidia-operator-validator \
--all-containers --tail=100
目标节点至少要看到对应的 Driver(若由 Operator 管理)、Container Toolkit、Device Plugin、GFD、DCGM Exporter 和 Validator 状态正常。Validator 中 CUDA/Device Plugin 校验失败时不能直接移除接入 taint。
路径 B:手工管理 Driver、Runtime 和 Device Plugin
这条路径适合不可变 GPU 节点镜像、离线环境或平台已经有成熟的 OS 配置管理体系。它的优点是版本可控,缺点是升级、回滚和节点漂移都要自己负责。
1. 安装并锁定 Host Driver
优先使用发行版/企业镜像的软件包管理方式,不建议在自动化节点上长期混用 .run installer。安装后至少验证:
nvidia-smi
nvidia-smi -L
ls -l /dev/nvidia* /dev/nvidia-caps/* 2>/dev/null
nvidia-smi 顶部显示的 “CUDA Version” 是 该 Driver 可支持的最高 CUDA API 版本信息,不等于节点已经安装了同版本 CUDA Toolkit。
2. 安装 NVIDIA Container Toolkit 并配置 containerd
containerd --version
nvidia-ctk --version
sudo nvidia-ctk runtime configure --runtime=containerd
sudo systemctl restart containerd
sudo systemctl is-active containerd
nvidia-ctk 会为 containerd 写入 NVIDIA runtime/drop-in 配置。修改后必须重启 containerd,并确认 kubelet 没有因为 CRI socket 或配置格式错误进入 NotReady。
legacy 模式下有两种选择:
- 使用
--set-as-default 把 nvidia handler 设为默认低层 runtime。 - 保留普通
runc 为默认,为 GPU Pod 配置 RuntimeClass:
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
name: nvidia
handler: nvidia
当前 CDI 路径应按所锁定的 Container Toolkit、containerd 和 Device Plugin 版本统一配置。标准 Device Plugin workload 的 CDI 注入通常对业务 YAML 透明,不应为了照抄旧教程同时开启多套 hook。
3. 部署 NVIDIA Device Plugin
Device Plugin 一般以 DaemonSet 运行在 GPU 节点。生产上使用 Helm/GitOps 锁定 chart 和镜像版本,并配置 GPU nodeSelector、toleration、MIG strategy 与共享策略;官方 static DaemonSet 更适合快速实验。
helm repo add nvdp https://nvidia.github.io/k8s-device-plugin
helm repo update
DEVICE_PLUGIN_VERSION=<经过验证并锁定的版本>
helm upgrade --install nvidia-device-plugin nvdp/nvidia-device-plugin \
--namespace nvidia-device-plugin \
--create-namespace \
--version "$DEVICE_PLUGIN_VERSION"
Device Plugin 启动后会在 /var/lib/kubelet/device-plugins/ 下通过 gRPC 向 kubelet 注册资源名和健康设备。kubelet 再把设备数量写入 Node Status;scheduler 只根据 Capacity/Allocatable 做放置,不会直接执行 nvidia-smi。
kubectl get pods -n nvidia-device-plugin -o wide \
--field-selector spec.nodeName="$NODE"
kubectl logs -n nvidia-device-plugin \
-l app.kubernetes.io/name=nvidia-device-plugin \
--tail=200
kubectl get node "$NODE" \
-o jsonpath='{.status.capacity.nvidia\.com/gpu}{" / "}{.status.allocatable.nvidia\.com/gpu}{"\n"}'
全链路验收:不要只看 nvidia-smi
第 1 关:Node 正常加入集群
kubectl get node "$NODE"
kubectl get node "$NODE" \
-o jsonpath='{.status.conditions[?(@.type=="Ready")].status}{"\n"}'
目标:Ready=True,CNI、containerd、kubelet 正常。
第 2 关:Kubernetes 已发布 GPU 资源
kubectl get node "$NODE" \
-o jsonpath='{.status.capacity.nvidia\.com/gpu}{" / "}{.status.allocatable.nvidia\.com/gpu}{"\n"}'
kubectl describe node "$NODE" | grep -A8 -E 'Capacity:|Allocatable:'
整卡模式下,空闲节点的 Capacity/Allocatable 数量应与健康物理 GPU 数量一致。若使用 MIG、Time-Slicing、MPS 或 HAMi,资源名和数量要按对应策略解释,不能继续用整卡数量验收。
第 3 关:运行绑定到新节点的 CUDA canary
apiVersion: v1
kind: Pod
metadata:
name: gpu-node-canary
spec:
restartPolicy: Never
nodeSelector:
kubernetes.io/hostname: gpu-node-01
tolerations:
- key: gpu-bootstrap
operator: Equal
value: "true"
effect: NoSchedule
containers:
- name: vectoradd
image: nvcr.io/nvidia/k8s/cuda-sample:vectoradd-cuda12.5.0
resources:
limits:
nvidia.com/gpu: 1
应把 nodeSelector、镜像 tag 和 runtimeClass(如果走 legacy 非默认 NVIDIA runtime)替换为集群实际值。
kubectl apply -f gpu-node-canary.yaml
kubectl get pod gpu-node-canary -o wide
kubectl describe pod gpu-node-canary
kubectl logs gpu-node-canary
验收标准:
- Pod 明确调度到目标新节点。
- Events 没有
FailedScheduling、FailedCreatePodSandBox 或 Device Plugin Allocate 错误。 - 日志输出
Test PASSED。 - Pod 运行期间,
kubectl describe node 的 Allocated resources 能看到 GPU request;status.allocatable 表示节点可分配容量上限,不会因为 Pod 占用而动态减一。
第 4 关:监控与稳定性
kubectl get pods -n gpu-operator -o wide \
--field-selector spec.nodeName="$NODE" | grep -E 'dcgm|validator'
nvidia-smi -q -d ECC,POWER,TEMPERATURE
journalctl -k --since '-30 min' | grep -E 'NVRM|Xid'
还要确认 Prometheus 已抓到该节点的 DCGM 指标,至少覆盖 GPU 利用率、显存、温度、功耗、ECC/Xid 与 PCIe/NVLink 相关指标。一次 vectorAdd 成功只能证明基本功能,不能证明长时间训练或通信稳定。
第 5 关:解除隔离并清理 canary
kubectl delete pod gpu-node-canary
kubectl taint node "$NODE" gpu-bootstrap:NoSchedule-
kubectl get node "$NODE" --show-labels
只有前四关都通过才能移除 gpu-bootstrap taint。如果平台还使用 workload=gpu:NoSchedule 等永久 taint,应保留并让正式 GPU workload 显式 toleration。
接入共享方案前必须先过整卡基线
01
整卡 nvidia.com/gpu canary 通过
03
-> MIG:启用 MIG mode,配置 GI/CI 和 mig.strategy
04
-> Time-Slicing:加载 Device Plugin replicas 配置
05
-> MPS:加载 MPS sharing 配置和隔离参数
06
-> HAMi:切换到 HAMi scheduler/device-plugin/core 链路
07
-> 再按新资源名和隔离语义跑一轮 canary
- MIG:验收
nvidia.com/mig-* profile、MIG Manager 状态和实例拓扑。 - Time-Slicing:验收 shared resource 数量,但不能把
replicas 当成固定算力份额。 - MPS:验收并发、显存/active thread 限制以及 MPS server 故障边界。
- HAMi:验收 Webhook、HAMi Scheduler、Device Plugin、HAMi-Core 和显存/算力配额。
同一张物理 GPU 不应同时被 NVIDIA 官方 Device Plugin 与 HAMi/其他 vGPU Device Plugin 重复注册。生产上应通过独立节点池、nodeSelector 和 DaemonSet 调度范围明确所有权。
分层故障定位
| 现象 | 最可能故障层 | 首先检查 |
lspci 看不到卡 | 硬件/BIOS/虚拟化 passthrough | BMC、PCIe slot、VM 配置、固件 |
lspci 有,nvidia-smi 失败 | Host Driver | kernel module、Secure Boot、nouveau、版本、Xid |
| 宿主机正常,GPU 容器创建失败 | Toolkit/CDI/runtime | containerd config、CDI spec、runtime handler、kubelet/containerd 日志 |
容器路径正常,但 Node 没有 nvidia.com/gpu | Device Plugin/kubelet | DaemonSet、plugin 日志、gRPC socket、设备健康状态 |
| Allocatable 正常,Pod 一直 Pending | scheduler/策略 | limits、taint/toleration、affinity、quota、已有占用 |
| Pod 已运行,但 CUDA 报错 | 镜像/Driver 兼容或设备注入 | Driver-CUDA 兼容、容器内库、可见 UUID、Allocate 结果 |
| 运行中 GPU 从 Allocatable 消失 | 设备健康/Xid | Device Plugin ListAndWatch、kernel log、DCGM/Xid 告警 |
生产变更与回滚原则
- 新节点保持 bootstrap taint,失败不会影响业务。
- Driver、Toolkit、Device Plugin、GPU Operator 版本写入 GitOps/镜像清单,不使用
latest。 - 改 Driver 或 runtime 前先
cordon/drain,预留可能重启节点的窗口。 - 保存 containerd、Toolkit、Operator values 和节点标签基线,确保可回退。
- 先在单节点 canary 池验证,再扩到同型号节点池。
- 除基本 CUDA canary 外,根据业务补充 NCCL、RDMA、MIG/共享和长稳测试。
Q: 面试官问“一个新 GPU 节点来了,怎么让 Kubernetes 用起来”,应该怎么回答?
我会先给节点加 bootstrap taint,完成普通 worker 的 OS、containerd、kubelet、CNI 和网络基线;然后从下往上打通 GPU 链路:确认 PCIe 枚举,安装并验证 NVIDIA Driver;配置 NVIDIA Container Toolkit,使 containerd 能通过 CDI 或 NVIDIA runtime 注入设备;部署 Device Plugin,让它通过 gRPC 向 kubelet 注册健康 GPU,最终在 Node Status 中出现 nvidia.com/gpu。生产上我优先用 GPU Operator统一管理 Driver、Toolkit、Device Plugin、GFD、DCGM 和 Validator。最后必须在目标节点跑一个真实申请 nvidia.com/gpu 的 CUDA canary,检查调度、Allocate、容器内 kernel 和 DCGM 指标,全部通过后才移除 taint。若还要上 MIG、MPS、Time-Slicing 或 HAMi,会在整卡基线通过后再切换,并重新按对应资源语义验收。
Q: 为什么宿主机 nvidia-smi 正常,Kubernetes 仍可能看不到 GPU?
nvidia-smi 只证明 Host Driver 能访问设备。Kubernetes 还依赖 Container Toolkit/CDI 打通容器设备注入,依赖 Device Plugin 向 kubelet 注册并持续上报健康设备。Runtime 配错时 Pod 可能创建失败;Device Plugin 未运行或设备被标记 unhealthy 时,Node Status 不会出现可用的 nvidia.com/gpu。
资料来源
MIG 到底切了什么
MIG 把一张支持该能力的 GPU 划分为若干 GPU Instance(GI),每个 GI 再包含可运行 CUDA workload 的 Compute Instance(CI)。profile 名称中的两部分分别表达计算切片和显存规格,例如 1g.10gb;具体 profile、数量和名称取决于 GPU 型号。
03
-> 按 profile 创建 GPU Instance
04
-> 为 GPU Instance 创建 Compute Instance
05
-> NVIDIA driver 枚举 MIG device UUID
06
-> Device Plugin 上报 MIG 扩展资源
07
-> Pod 申请一个 MIG profile
MIG 的价值是性能和故障隔离更稳定,但资源形状固定。一个 1g workload 空闲时,旁边的 3g workload 不能自动借走它的硬件切片。
裸机怎么用
下面以单张 GPU 为例。命令必须在没有业务进程占用该 GPU 时执行;profile 仅作为示例,要先查询本机实际支持项。
# 1. 确认 GPU 与 MIG 状态
nvidia-smi -L
nvidia-smi -i 0 --query-gpu=name,mig.mode.current,mig.mode.pending --format=csv
# 2. 启用 MIG mode
sudo nvidia-smi -i 0 -mig 1
# 3. 查询可用 GPU Instance profile 与 placement
nvidia-smi mig -lgip
nvidia-smi mig -lgipp
# 4. 示例:创建两个 3g.20gb GI,并同时创建对应 CI
sudo nvidia-smi mig -cgi 3g.20gb,3g.20gb -C
# 5. 验证实例与 UUID
nvidia-smi mig -lgi
nvidia-smi mig -lci
nvidia-smi -L
仅执行 -mig 1 不够:没有创建 GI/CI 时,CUDA workload 还没有可使用的 MIG device。创建后的 MIG device 可以通过 MIG-<UUID> 选择,例如容器运行时可把该 UUID 放进 NVIDIA_VISIBLE_DEVICES。
Kubernetes + GPU Operator 怎么用
1. 选择 MIG strategy
GPU Operator / NVIDIA Device Plugin 常见两种策略:
| 策略 | 资源表达 | 适用场景 |
single | 节点上的 MIG 设备采用一致几何,使用方式更统一 | 同一节点全部做同规格推理池 |
mixed | 不同 profile 以独立资源名上报,例如 nvidia.com/mig-1g.10gb | 同节点需要多种规格 |
安装时启用 MIG Manager 的示意命令:
helm repo add nvidia https://helm.ngc.nvidia.com/nvidia
helm repo update
helm install --wait gpu-operator nvidia/gpu-operator \
-n gpu-operator --create-namespace \
--set mig.strategy=mixed
实际部署时应锁定经过验证的 GPU Operator 版本,不要在生产集群直接跟随 latest。
2. 给节点应用 MIG geometry
MIG Manager 监听节点的 nvidia.com/mig.config label。变更前先清空业务 workload:
NODE=gpu-node-1
kubectl cordon "$NODE"
kubectl drain "$NODE" --ignore-daemonsets --delete-emptydir-data
# profile 名称以该节点的 MIG Manager ConfigMap 为准
kubectl label node "$NODE" \
nvidia.com/mig.config=all-1g.10gb --overwrite
kubectl get node "$NODE" \
-L nvidia.com/mig.config,nvidia.com/mig.config.state
只有状态进入 success,并且 Device Plugin 重新上报资源后,节点才重新开放:
kubectl describe node "$NODE" | grep -E 'nvidia.com/(gpu|mig-)'
kubectl uncordon "$NODE"
3. Pod 申请 MIG 实例
mixed strategy 下,Pod 申请具体 profile;资源名必须以 kubectl describe node 的实际结果为准。
apiVersion: v1
kind: Pod
metadata:
name: mig-demo
spec:
restartPolicy: Never
containers:
- name: cuda
image: nvcr.io/nvidia/cuda:12.8.0-base-ubuntu22.04
command: ["bash", "-lc", "nvidia-smi && sleep 3600"]
resources:
limits:
nvidia.com/mig-1g.10gb: 1
验证:
kubectl apply -f mig-demo.yaml
kubectl get pod mig-demo -o wide
kubectl exec mig-demo -- nvidia-smi -L
kubectl exec mig-demo -- nvidia-smi
容器应该只看到被分配的 MIG device 及其显存规格,而不是整张物理卡。
重配与回收
裸机删除顺序通常是先删 CI,再删 GI,最后按需关闭 MIG mode:
sudo nvidia-smi mig -dci
sudo nvidia-smi mig -dgi
sudo nvidia-smi -i 0 -mig 0
GPU Operator 环境优先让 MIG Manager 管理,不要一边手工执行 nvidia-smi mig,一边让 controller 按 label 重建几何。需要恢复整卡时,先 drain 节点,再应用 all-disabled 等实际配置中存在的 profile。
生产风险
Q: 为什么强 SLA 推理优先考虑 MIG?
因为 MIG 的资源边界来自硬件实例,性能和故障隔离比 MPS/Time-Slicing 更可预测。代价是规格固定、资源不能自动借用,并且重配 geometry 会影响节点上的 GPU workload。
资料来源
MPS 执行链路
01
MPS control daemon
接收管理命令并创建 MPS server
02
MPS server
持有 GPU 调度资源,接收多个 client 的 CUDA work
03
CUDA clients
通过 pipe 连接 server,提交 kernel 和内存申请
04
GPU
允许来自不同 client 的 kernel 并发执行
05
监控
nvidia-smi / DCGM / MPS ps 观察 client、显存和吞吐
MPS 的目标是降低多 CUDA context 独立运行的切换成本,并增加小 kernel 的并发机会。它不是 MIG:不同 client 仍然共享物理 GPU、部分缓存、带宽和故障域。
裸机怎么用
1. 启动 MPS daemon
下面是单用户、单 GPU 的最小实验流程。生产环境需要独立服务账号、目录权限和进程生命周期管理。
export CUDA_VISIBLE_DEVICES=0
export CUDA_MPS_PIPE_DIRECTORY=/tmp/nvidia-mps
export CUDA_MPS_LOG_DIRECTORY=/tmp/nvidia-log
mkdir -p "$CUDA_MPS_PIPE_DIRECTORY" "$CUDA_MPS_LOG_DIRECTORY"
nvidia-cuda-mps-control -d
# 查看 server 和 client
echo ps | nvidia-cuda-mps-control
随后在同一组 MPS pipe 配置下启动两个 CUDA 进程,它们会连接到 MPS server。应用必须在创建 CUDA context 之前设置资源限制。
2. 限制执行资源
# 该 client 新建 CUDA context 时最多使用约 30% 的可用执行资源
export CUDA_MPS_ACTIVE_THREAD_PERCENTAGE=30
python inference_worker.py
CUDA_MPS_ACTIVE_THREAD_PERCENTAGE 限制的是 client 可用的执行线程比例,最终值会按硬件支持粒度取整。它不是“把每个时刻的 GPU-Util 精确锁死在 30%”,也不直接保证业务吞吐等于整卡的 30%。
MPS 还支持通过控制命令设置未来 client 的默认执行比例:
echo 'set_default_active_thread_percentage 30' \
| nvidia-cuda-mps-control
3. 限制显存
# 限制未来 MPS client 在 GPU 0 上可分配的 device memory
echo 'set_default_device_pinned_mem_limit 0 8G' \
| nvidia-cuda-mps-control
# client 侧还可以进一步收紧,不能突破 server 的更低限制
export CUDA_MPS_PINNED_DEVICE_MEM_LIMIT='0=6G'
python inference_worker.py
超出限制时,CUDA 内存分配会返回 OOM。显存限制要预留 CUDA context、库 workspace 和通信 buffer,不要只按模型权重大小计算。
4. 停止 MPS
echo ps | nvidia-cuda-mps-control
echo quit | nvidia-cuda-mps-control
退出前先停止 client workload。异常退出后还要检查残留 server、pipe 目录和 GPU 上下文。
Kubernetes 怎么用
NVIDIA Device Plugin 的 MPS sharing 用 replicas 定义等份共享资源。官方文档仍将该能力标为实验性;MPS 与 Time-Slicing 互斥,并且当前不能用于已启用 MIG 的设备。
1. 创建共享配置
apiVersion: v1
kind: ConfigMap
metadata:
name: nvidia-device-plugin-config
namespace: gpu-operator
data:
mps-4: |-
version: v1
flags:
migStrategy: none
sharing:
mps:
renameByDefault: true
resources:
- name: nvidia.com/gpu
replicas: 4
这表示每张 GPU 暴露 4 个 nvidia.com/gpu.shared,MPS control daemon 为每个共享资源限制为大约总显存和执行资源的四分之一。它与裸机手工设置任意百分比不同:这里是按 replicas 等份管理。
2. 让 Device Plugin 加载配置
GPU Operator 环境可以让 ClusterPolicy 引用 ConfigMap,并通过节点 label 选择配置:
kubectl apply -f nvidia-device-plugin-config.yaml
kubectl patch clusterpolicy/cluster-policy \
--type merge \
-p '{"spec":{"devicePlugin":{"config":{"name":"nvidia-device-plugin-config","default":"mps-4"}}}}'
kubectl label node gpu-node-1 \
nvidia.com/device-plugin.config=mps-4 --overwrite
不同 GPU Operator / Device Plugin 版本的 chart 字段可能变化,上线前要以所锁定版本的配置 schema 为准。
3. Pod 申请与验证
apiVersion: v1
kind: Pod
metadata:
name: mps-demo
spec:
restartPolicy: Never
containers:
- name: cuda
image: nvcr.io/nvidia/cuda:12.8.0-base-ubuntu22.04
command: ["bash", "-lc", "nvidia-smi && sleep 3600"]
resources:
limits:
nvidia.com/gpu.shared: 1
kubectl describe node gpu-node-1 \
| grep -E 'nvidia.com/(gpu.shared|gpu.sharing-strategy|mps.capable)'
kubectl apply -f mps-demo.yaml
kubectl exec mps-demo -- nvidia-smi
验证不能只看 Pod Running,还要同时运行多份实际 workload,比较:
- 单独运行与共置运行的吞吐、P50/P99 延迟。
- 每个 client 的显存峰值和 OOM 行为。
- SM Active、DRAM throughput、L2、PCIe 等 DCGM 指标。
- 任一 client 异常时,其他 client 是否受到影响。
什么时候适合 MPS
| 适合 | 不适合 |
| 同一团队的多个小模型推理 | 互不信任租户、需要强故障隔离 |
| 单任务 kernel 很小,整卡利用率低 | 单任务已经接近打满 SM 或 HBM |
| 计算型与 I/O 型 workload 互补 | 严格 P99 SLO 且无法容忍共享抖动 |
| 能持续采集干扰指标并自动降级 | 只有“多放几个 Pod”而没有运行时保护 |
Q: MPS 设置 30%,是不是这个进程永远只能获得 30% GPU 性能?
不是。active thread percentage 约束 client 能使用的执行资源上限,不直接等价于业务性能比例。实际吞吐还受 kernel 并行度、显存带宽、cache、CPU/IO、其他 client 负载和硬件取整影响,必须用真实 workload 基准测试。
常见误区
资料来源
系统链路
01
Device Plugin 读取 timeSlicing 配置
02
-> 为每张物理 GPU 创建 N 个逻辑引用
03
-> Kubelet 把逻辑数量写入 Node Capacity / Allocatable
04
-> kube-scheduler 按普通扩展资源分配 Pod
05
-> 多个 Pod 获得同一 GPU UUID 的访问权
06
-> CUDA driver 在进程之间交错执行
kube-scheduler 不知道这些 slot 来自同一张物理卡,也不会为每个 slot 保留固定 SM 或显存。Time-Slicing 解决的是“能不能共享访问”,不是“怎么保证共享性能”。
Kubernetes 怎么配置
1. 创建 ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: nvidia-device-plugin-config
namespace: gpu-operator
data:
time-slicing-4: |-
version: v1
flags:
migStrategy: none
sharing:
timeSlicing:
renameByDefault: true
failRequestsGreaterThanOne: true
resources:
- name: nvidia.com/gpu
replicas: 4
关键参数:
| 参数 | 含义 | 为什么重要 |
replicas: 4 | 每张物理 GPU 创建 4 个共享访问名额 | 控制最大共享密度,不是算力比例 |
renameByDefault: true | 资源名改为 nvidia.com/gpu.shared | 防止用户把共享 slot 当独占 GPU |
failRequestsGreaterThanOne: true | 拒绝单容器一次申请多个共享 slot | 强调申请的是访问权,不是成比例算力 |
2. 让 Device Plugin 加载配置
GPU Operator 环境示例:
kubectl apply -f nvidia-device-plugin-config.yaml
kubectl patch clusterpolicy/cluster-policy \
--type merge \
-p '{"spec":{"devicePlugin":{"config":{"name":"nvidia-device-plugin-config","default":"time-slicing-4"}}}}'
kubectl label node gpu-node-1 \
nvidia.com/device-plugin.config=time-slicing-4 --overwrite
官方 Device Plugin 的共享方式按节点配置:同一节点上的 GPU 使用相同 sharing method,不能让 GPU0 用 Time-Slicing、GPU1 用 MPS。需要不同策略时,应拆成不同节点池或至少使用不同节点配置。
3. 确认资源已经上报
假设节点有 8 张 GPU、每张设置 4 个 replicas,Node 应显示 32 个共享资源:
kubectl describe node gpu-node-1 \
| grep -E 'nvidia.com/(gpu.shared|gpu.replicas|gpu.sharing-strategy)'
kubectl get node gpu-node-1 \
-o jsonpath='{.status.capacity.nvidia\.com/gpu\.shared}{"\n"}'
如果仍然只看到 8 张独占 GPU,检查 ConfigMap namespace、ClusterPolicy 引用、节点 label 和 Device Plugin Pod 日志。
Pod 怎么申请
apiVersion: v1
kind: Pod
metadata:
name: timeslice-demo
spec:
restartPolicy: Never
containers:
- name: cuda
image: nvcr.io/nvidia/cuda:12.8.0-base-ubuntu22.04
command: ["bash", "-lc", "nvidia-smi -L && sleep 3600"]
resources:
limits:
nvidia.com/gpu.shared: 1
kubectl apply -f timeslice-demo.yaml
kubectl get pod timeslice-demo -o wide
kubectl exec timeslice-demo -- nvidia-smi -L
kubectl exec timeslice-demo -- nvidia-smi
Pod 通常会看到整张物理 GPU 的 UUID 和显存容量。多个 Pod 可能看到同一 UUID,这正是它们共享同一张卡的证据。不能因为 nvidia-smi 显示整卡显存,就让每个进程都按整卡容量分配。
如何验证真实边界
不要只创建 4 个 sleep Pod。至少进行下面四组实验:
- 单任务基线:记录单独运行的吞吐、P50/P99 和显存峰值。
- 并发压力:同时运行 2/4 个计算密集 workload,观察性能是否近似均分以及上下文切换开销。
- 显存冲突:让两个 workload 的显存总需求超过物理显存,确认其中一个可能 OOM,证明 replicas 不隔离显存。
- 异常影响:记录 Xid、Pod 重启和其他同卡 workload 的影响,验证共享故障域风险。
kubectl get pods -o wide
kubectl logs -n gpu-operator -l app=nvidia-device-plugin-daemonset --tail=200
nvidia-smi pmon -s um -c 10
适用与不适用
| 适合 | 不适合 |
| Notebook、交互式开发、低负载实验 | 延迟敏感在线推理 |
| 低优离线推理和评测 | 互不信任租户 |
| 可以接受性能抖动的教学/研发集群 | 显存峰值大、容易 OOM 的训练 |
| 希望低成本提高 Pod 密度 | 要求固定 GPU 百分比计费或 SLA |
Q: replicas=4 时,一个 Pod 实际拿到多少 GPU?
它拿到的是“访问一张共享 GPU”的权利,而不是静态 1/4 卡。如果只有一个 CUDA 进程,它可能使用大部分 GPU;多个满负载进程同时运行时,驱动会在它们之间交错执行,但不提供固定算力、显存或 P99 保证。
常见误区
资料来源
HAMi 解决什么问题
官方 NVIDIA Device Plugin 的 Time-Slicing/MPS 主要按 replicas 做等份共享。平台如果希望用户声明“我要一张物理 GPU 上的 3GiB 显存和 30% 算力”,默认 kube-scheduler 无法理解这种二维设备容量。
HAMi 为 Pod 增加细粒度资源:
resources:
limits:
nvidia.com/gpu: 1
nvidia.com/gpumem: 3000
nvidia.com/gpucores: 30
nvidia.com/gpu:需要分配的 GPU 设备数量。nvidia.com/gpumem:每个设备的显存需求,常用单位为 MiB。nvidia.com/gpucores:设备计算资源百分比,1 表示 1%。
HAMi 还支持多种 GPU/NPU/MLU/DCU 等后端,但每种设备的共享和隔离能力不同,不能只看“支持列表”就假设所有硬件都有相同能力。
核心架构
02
-> Mutating Webhook 识别 HAMi resource,并设置 hami-scheduler
03
-> Scheduler Extender 维护全局设备视图,执行 Filter / Score / Bind
04
-> 选定物理 GPU UUID,把分配结果写入 Pod annotation
05
-> HAMi Device Plugin 在 Allocate 阶段挂载设备和运行时配置
06
-> HAMi-Core 注入容器,执行显存检查与算力节流
07
-> Monitor / Metrics 持续观测分配和使用情况
| 组件 | 职责 | 关键状态 |
| Mutating Webhook | 识别 HAMi 资源并选择调度入口 | spec.schedulerName |
| Scheduler Extender | 维护剩余显存/算力,选择节点和物理设备 | Node/Pod annotation、设备全局视图 |
| HAMi Device Plugin | 注册设备、读取调度结果、向容器注入设备与环境 | GPU UUID、Allocate 结果 |
| HAMi-Core | 在容器内拦截 CUDA/NVML 调用,执行显存上限和算力节流 | 显存上限、SM limit、运行时计数器 |
这条链路解释了为什么 HAMi 不能只靠 Device Plugin:标准 scheduler 在调度阶段看不到每张 GPU 剩余多少显存和算力,而 Device Plugin 的 Allocate 又发生在节点已经选定之后。HAMi 需要 Scheduler Extender 先做设备级放置,再通过 annotation 把结果交给节点侧执行。
Helm 部署
1. 前置检查
GPU 节点应先满足:
- NVIDIA driver 和 NVIDIA Container Toolkit 可用。
- 容器运行时已正确配置 NVIDIA runtime。
- Kubernetes、Helm、CUDA/driver 版本符合当前 HAMi release 的兼容矩阵。
- 同一批节点没有另一个 Device Plugin 同时注册和管理相同 GPU 资源。
HAMi、Volcano vGPU Device Plugin 和 NVIDIA 官方 Device Plugin 不应同时管理同一节点上的同一类 GPU。生产上应通过节点池和 label 划分职责。
2. 安装
# 标记由 HAMi 管理的 GPU 节点
kubectl label node gpu-node-1 gpu=on --overwrite
helm repo add hami-charts https://project-hami.github.io/HAMi/
helm repo update
helm install hami hami-charts/hami \
-n kube-system
部分 HAMi 版本要求 scheduler 镜像 tag 与 Kubernetes 版本对应。安装前应阅读所锁定 chart 版本的 values 和兼容说明,不要直接复制其他集群的版本参数。
3. 验证控制面与节点组件
kubectl get pods -n kube-system \
| grep -E 'hami-(scheduler|device-plugin)'
kubectl logs -n kube-system deploy/hami-scheduler --tail=200
kubectl get daemonset -n kube-system | grep hami-device-plugin
至少确认:
hami-scheduler 正常运行并可访问 API Server。- 每个目标 GPU 节点都有
hami-device-plugin Pod。 - Node 上已经注册 HAMi 管理的 GPU 数量与设备信息。
- 原 NVIDIA Device Plugin 没有同时争抢相同资源名。
Pod 怎么申请细粒度 GPU
apiVersion: v1
kind: Pod
metadata:
name: hami-vgpu-demo
annotations:
hami.io/node-scheduler-policy: "binpack"
spec:
restartPolicy: Never
containers:
- name: cuda
image: nvcr.io/nvidia/cuda:12.8.0-base-ubuntu22.04
command: ["bash", "-lc", "nvidia-smi && sleep 3600"]
resources:
limits:
nvidia.com/gpu: 1
nvidia.com/gpumem: 3000
nvidia.com/gpucores: 30
kubectl apply -f hami-vgpu-demo.yaml
kubectl get pod hami-vgpu-demo -o wide
kubectl get pod hami-vgpu-demo \
-o jsonpath='{.spec.schedulerName}{"\n"}'
kubectl describe pod hami-vgpu-demo
kubectl exec hami-vgpu-demo -- nvidia-smi
预期现象:
- Webhook 将任务路由到 HAMi scheduler。
- Pod annotation 中记录选中的 GPU UUID、显存和算力分配结果。
- 容器内
nvidia-smi 看到的显存信息受到 HAMi-Core 的 NVML 拦截影响。 - 超出显存配额的 CUDA 分配返回 OOM,而不是无限挤占同卡其他 Pod。
- 算力限制依靠运行时节流,
nvidia-smi 的瞬时 GPU-Util 仍可能波动。
注意:Node Capacity/Allocatable 可能主要显示 nvidia.com/gpu,而 gpumem/gpucores 的剩余容量由 HAMi scheduler 的设备视图和 annotation 管理,不一定像普通 Extended Resource 一样直接出现在 Capacity 中。
调度策略
| 策略 | 含义 | 适合场景 |
binpack | 优先把任务放到已经使用的节点/GPU,保留完整设备 | 提高整卡可用性、便于缩容、减少碎片 |
spread | 把任务分散到不同节点/GPU | 降低热点和共享干扰,提高故障分散 |
选择不能只看利用率:小模型推理可以 binpack,延迟敏感或带宽密集 workload 更适合 spread。多 GPU 训练还要考虑 NVLink/PCIe 拓扑,不能只按显存余量拼卡。
HAMi 与其他方案怎么选
| 维度 | NVIDIA Time-Slicing | NVIDIA MPS Sharing | HAMi | MIG |
| 分配粒度 | 共享访问名额 | 按 replicas 等份 | 显存 MiB + 算力百分比 + 设备数 | 固定硬件 profile |
| 调度感知 | 主要看逻辑数量 | 主要看逻辑数量 | 感知单卡剩余显存/算力和设备策略 | profile 作为独立资源 |
| 隔离方式 | 时间复用,弱 | MPS server 限制 | 软件/设备后端限制 | 硬件隔离 |
| 异构设备 | NVIDIA | NVIDIA | 面向多厂商扩展 | 支持 MIG 的 NVIDIA GPU |
| 系统复杂度 | 低 | 中 | 较高,增加 Webhook/Scheduler/Core | 中,需要 profile 生命周期管理 |
HAMi 还提供 dynamic MIG 等扩展能力,但它们依赖支持的 GPU、驱动和 HAMi 版本。面试时应把“HAMi 软件 vGPU”和“HAMi 调用硬件 MIG 动态创建实例”区分开。
排障路径
01
Pod 未被调度
看 schedulerName、Webhook、HAMi scheduler 日志
02
资源不足
看 Pod annotation、设备注册信息、显存/算力剩余视图
03
Pod 卡在 ContainerCreating
看 Device Plugin Allocate、runtime、libvgpu 注入
04
显存限制没生效
看 HAMi-Core、LD_PRELOAD、CUDA/NVML 兼容性
05
性能抖动
看 gpucores、binpack 密度、HBM/SM 指标和同卡 workload
06
设备重复或数量异常
排查是否与 NVIDIA/Volcano Device Plugin 冲突
Q: HAMi 是怎么做到“申请 3GiB GPU 显存”的?
调度阶段,HAMi Scheduler 先从全局设备视图中选择剩余显存足够的物理 GPU,并把 UUID 和 3GiB 配额写入 Pod annotation;Allocate 阶段,Device Plugin 注入设备、环境变量和 HAMi-Core;容器内 HAMi-Core 拦截 CUDA/NVML 的显存查询和分配调用,超过配额时返回 OOM。它是调度账本和容器内执行限制的组合。
Q: HAMi 和 MIG 最大的区别是什么?
MIG 的边界来自硬件实例,规格固定、故障和性能隔离更强;HAMi 的优势是显存/算力比例更灵活、能做设备感知调度和异构统一管理,但 NVIDIA 常见 vGPU 路径主要依赖软件拦截与节流,不能直接宣称获得 MIG 同等级硬件隔离。
资料来源
一条生产决策链
01
给 workload 分类
在线推理、离线推理、训练、Notebook、系统任务
02
定义不可妥协约束
P99、故障隔离、显存上限、吞吐、可抢占性
03
建立单任务画像
显存峰值、SM Active、HBM、PCIe、CPU/IO、运行时间
04
选择虚拟化机制
整卡 / MIG / MPS / Time-Slicing / HAMi
05
选择调度策略
独占、binpack、spread、干扰感知、拓扑感知
06
设置运行时保护
显存/算力上限、共享密度、在线降级与抢占
07
灰度与复测
单任务基线 -> 两任务共置 -> 压力/异常 -> 扩大范围
场景选型表
| 场景 | 首选 | 原因 | 不建议 |
| 核心在线推理,严格 P99 | 整卡或 MIG | 性能和故障边界最可预测 | 直接 Time-Slicing;无监控的 MPS |
| 同团队多个小模型推理 | MPS | kernel 可并发,能限制显存和执行资源 | 把 MPS 当硬隔离卖给不可信租户 |
| Notebook / 开发实验 | Time-Slicing | 配置简单,提高共享访问密度 | 承诺固定 1/N 性能 |
| 多团队细粒度显存/算力 | HAMi | 支持 MiB/百分比声明和设备感知放置 | 忽略 HAMi-Core 兼容性与插件冲突 |
| 大训练或通信密集训练 | 整卡、拓扑感知分配 | 需要稳定 SM/HBM/NVLink,减少同卡干扰 | 为了表面利用率强行切碎 |
| KV Cache / 权重弹性 | CUDA VMM + 运行时准入 | 解决虚拟地址和物理显存页弹性 | 把 VMM 当算力虚拟化 |
生产架构怎么分层
| 层次 | 组件 | 需要维护的状态 |
| 设备发现 | GPU Feature Discovery、DCGM、HAMi Device Plugin | 型号、显存、MIG 状态、健康、拓扑 |
| 资源表达 | NVIDIA Device Plugin、MIG profile、HAMi resources、DRA | 资源名、容量、共享关系、设备属性 |
| 准入与调度 | kube-scheduler、Scheduler Plugin、HAMi Scheduler | quota、剩余显存/算力、优先级、干扰画像 |
| 运行时执行 | MIG、MPS server、CUDA driver、HAMi-Core、CUDA VMM | 硬件实例、client 限制、内存映射 |
| 监控与回退 | DCGM Exporter、业务指标、告警、抢占/迁移 | P99、吞吐、SM/HBM、OOM、Xid、降级状态 |
面试时可以说:Device Plugin 解决“怎么把设备交给 Pod”,不等于解决“共享后性能是否安全”;虚拟化方案必须和调度、监控、回退形成闭环。
上线前验证矩阵
论文项目怎么映射
论文部分只回答“为什么需要在基础机制上再加系统策略”。详细背景、算法和实验仍放在<a href="../../../content/ai-infra/papers/index.html">论文工作</a>页面。
| 项目 | 使用的基础机制 | 机制本身缺什么 | 论文补了什么 |
| DeepShare | NVIDIA MPS + DCGM | MPS 能并发,但不知道哪两个作业适合共置,也不知道何时该保护欠保障租户 | QAD、Random Forest 干扰预测、动态准入阈值、连续窗口在线降级 |
| ElastiCo | MPS 执行资源限制 + CUDA VMM | 固定 SM/显存比例无法追随训练和推理阶段变化 | 资源形态变换、影子定价、训推阶段感知的动态调整 |
| Maestro | CUDA VMM | VMM 只提供映射能力,不知道每个 Agent stage 应预留多少 KV Cache | 输出长度预测、准入控制、按需物理页映射和降级策略 |
这里不要混淆:
- DeepShare 的 GPU 共置依赖 MPS,但贡献不是“发明 MPS”,而是让共置服从租户保障和干扰闭环。
- ElastiCo 组合了算力和显存弹性,但 MPS/VMM 只是执行原语,策略来自资源形态与价格信号。
- Maestro 使用 CUDA VMM 管 KV Cache,不是把一张 GPU 虚拟成多个 Kubernetes GPU。
- HAMi 是可落地的开源平台方案,和论文里的自研策略可以互补,但当前项目没有宣称论文原型直接基于 HAMi。
面试设计题答法
Q: 让 10 个团队共享 100 张 GPU,你会怎么设计虚拟化方案?
先分池
核心在线推理和大训练保留整卡/MIG 池;Notebook、离线推理进入共享池;不要让所有 workload 共用一种策略。
再选机制
强 SLA 用 MIG;可信小 workload 用 MPS;低优研发用 Time-Slicing;需要任意显存/算力配比和异构统一管理时评估 HAMi。
最后闭环
调度器维护 quota、拓扑和共享密度,DCGM 与业务指标检测干扰,超过阈值自动迁移、降级到独占或抢占低优任务。
Q: 为什么不全部使用 HAMi 或全部使用 MIG?
因为它们优化的目标不同。MIG 用固定硬件边界换稳定性,但会产生 profile 碎片;HAMi 用软件调度和运行时控制换细粒度与灵活性,但系统复杂度和隔离边界不同。生产平台通常按 workload 分池组合使用,而不是追求单一方案统一全部场景。
常见误区
系统链路
01
Linux CFS
OS 决定哪个进程/线程拿到 CPU 时间片
02
CUDA Stream/Event
程序员组织 H2D、kernel、D2H 等 GPU 任务的顺序和依赖
03
CUDA Block 调度
GPU 把 grid 里的 block 分配到 SM
04
Warp Scheduler
SM 内部选择 ready warp 发射指令
为什么这个问题容易混淆?
因为“线程”这个词在 CPU 和 GPU 里含义不同。
所以面试回答时要先分层:
- CPU OS 调度层:进程/线程如何共享 CPU。
- CUDA 任务提交层:stream/event 如何组织 GPU work。
- GPU kernel 执行层:block/warp 如何映射到 SM。
- 集群资源调度层:K8s/Volcano/YARN 等如何分配 GPU 设备给任务。
不要把这四层混成一个“GPU 调度器”。
Linux CFS:公平地分 CPU 时间
CFS 全称是 Completely Fair Scheduler,目标不是让某个任务跑到最快,而是在多个 runnable task 之间尽量公平地分配 CPU 时间,同时兼顾交互响应。
CFS 调度对象
CFS 调度的是 Linux 内核里的 task_struct。对用户来说,它可以表现为:
- 一个进程;
- 一个线程;
- 一个容器里的某个线程;
- 一个 cgroup 下的一组任务。
在 Linux 里,线程和进程最终都可以作为调度实体参与 CPU 调度。CFS 不关心这个任务是不是 AI 训练、数据加载、推理服务、日志线程,它只看到“这个 runnable task 需要 CPU”。
vruntime:谁“欠 CPU 时间”最多
CFS 的核心指标是 vruntime,可以理解成“加权后的虚拟运行时间”。
- 任务真实运行越久,
vruntime 越大。 - nice 值越低、优先级越高,权重越大,同样运行一段真实时间,
vruntime 增长越慢。 - 调度器倾向于选择
vruntime 最小的任务运行,因为它看起来“拿到的公平份额最少”。
直觉上:
谁的 vruntime 小,说明谁相对更“饿”,应该优先给 CPU。
谁的 vruntime 大,说明谁已经跑得比较多,可以先等一等。
这和传统时间片轮转不一样。Round Robin 更像“每个人轮流拿固定时间片”;CFS 更像“持续维护每个人已经拿到的公平份额”。
nice、weight 和公平份额
Linux 的 nice 值会影响任务权重。
CFS 不是简单地“高优先级永远先跑”。它通过权重影响 vruntime 增长速度,让高权重任务在长期上获得更多 CPU 时间,但仍然允许其他任务运行。
runqueue 和红黑树
每个 CPU 通常有自己的 runqueue。CFS 会把 runnable task 按 vruntime 组织起来,经典实现使用红黑树。
01
任务变为 runnable
进入当前 CPU 的 CFS runqueue
02
按 vruntime 排序
vruntime 小的任务排在更靠左的位置
03
选择最左任务
调度器选择 vruntime 最小的 task
04
运行一段时间
task 的 vruntime 增加
05
重新入队或继续运行
根据抢占、阻塞、唤醒和时间粒度决定
这个结构的意义是:调度器能快速找到“最应该运行”的任务。
抢占和上下文切换
CFS 是抢占式调度。当前任务正在 CPU 上运行时,如果出现一个更应该运行的任务,调度器可以触发抢占。
常见触发点包括:
- 周期性 tick 或调度时钟更新;
- 当前任务主动阻塞,例如等待 IO、锁、网络;
- 新任务唤醒,例如交互请求到来;
- 当前任务运行超过合理粒度;
- 更高优先级或更小
vruntime 的任务需要运行。
CPU 上下文切换通常涉及:
- 保存当前任务寄存器状态;
- 切换内核栈;
- 切换或更新地址空间相关状态;
- 更新调度统计;
- 恢复下一个任务的执行上下文。
上下文切换不是免费的。线程数过多、锁竞争严重、频繁唤醒阻塞,都可能导致 CPU 时间花在调度和切换上,而不是有效计算上。
CUDA Stream/Event:任务级异步调度
对 CUDA 程序员来说,Stream/Event 是控制 GPU work 提交顺序和依赖的工具。
Stream 是 GPU 任务队列:
- 同一个 stream 内的操作按提交顺序执行;
- 不同 stream 的操作可以并发执行,但前提是硬件资源允许;
- 常见操作包括 H2D 拷贝、kernel launch、D2H 拷贝、event record/wait。
Event 是依赖和计时工具:
- 可以记录某个 stream 上的进度点;
- 可以让另一个 stream 等待这个 event;
- 可以用于测量 GPU 端耗时;
- 可以避免 CPU 端粗暴
cudaDeviceSynchronize()。
01
CPU 提交任务
把 H2D、kernel、D2H 放进一个或多个 stream
02
Stream 保证顺序
同一 stream 内先提交的先执行
03
Event 表达依赖
一个 stream 可以等待另一个 stream 的完成点
04
GPU runtime/driver 派发
把 ready 的 work 交给 GPU 执行
05
硬件执行 kernel
进入 block/SM/warp 层级
这里要强调:stream 不等于 SM 调度器。Stream 决定的是 kernel、memcpy 等任务之间的顺序和并发机会;block 和 warp 怎么在 SM 里执行,是更底层的硬件执行机制。
CUDA Block / Warp 调度:吞吐优先
一次 kernel launch 会产生一个 grid,grid 里包含很多 block。GPU 的工作是把这些 block 分配到 SM 上执行。
block 是调度到 SM 的基本单位
block 也常被称为 CTA(Cooperative Thread Array)。一个 block 里的 thread 可以:
- 共享 shared memory;
- 使用
__syncthreads() 做 block 内同步; - 通过 thread/block index 处理不同数据。
GPU 硬件会把 block 调度到某个 SM。一个 block 一旦驻留到 SM 上,通常不会像 CPU task 那样被 CFS 时间片频繁抢占并迁移到另一个 SM,而是运行到完成。
这带来两个重要结论:
- 不同 block 之间默认没有执行顺序保证。
- block 数量要足够多,否则 SM 可能吃不满。
block residency:为什么不是想放多少就放多少
一个 SM 能同时驻留多少个 block,受多种资源限制:
所以 CUDA 调优不是“block 越大越好”。block 太小,单个 block 并行度不足;block 太大,可能占用太多 register/shared memory,导致 SM 上可同时驻留的 warp 变少。
warp scheduler:隐藏内存延迟
block 被放到 SM 后,block 内 thread 会被组织成 warp。NVIDIA GPU 上一个 warp 通常是 32 个 thread。SM 内部的 warp scheduler 会在多个 ready warp 之间选择并发射指令。
它的关键目标不是公平,而是吞吐:
- 某个 warp 等 HBM 访存时,切换到另一个 ready warp;
- 某个 warp 遇到长延迟指令时,让其他 warp 填补流水线;
- 通过足够多 active warp 隐藏内存和执行延迟。
01
Block 驻留 SM
block 占用 register、shared memory、thread slots
02
Thread 组成 warp
通常 32 个 thread 一组
03
Warp 等待访存
当前 warp 可能 stalled
04
选择 ready warp
warp scheduler 发射另一个可执行 warp
05
提高吞吐
用并发 warp 隐藏延迟,而不是追求单线程低延迟
这就是 GPU 和 CPU 的核心差异之一:CPU 用复杂控制逻辑优化单线程延迟;GPU 用大量 warp 并发隐藏延迟,追求整体吞吐。
CFS vs CUDA 调度:核心对比
面试中最容易犯的错是说“GPU 也像 CPU 一样靠 OS 调度每个 thread”。这不对。CUDA thread 是 GPU kernel 内的逻辑执行单元,不是 Linux CFS 直接调度的 OS thread。
Stream/Event vs Thread Block/Warp:不同层次
对 CUDA 程序员来说,最需要区分的是这两层:
例子:
stream1: H2D batch0 → kernel batch0 → D2H result0
stream2: H2D batch1 → kernel batch1 → D2H result1
这是 stream 层的并发组织。每个 kernel batchX 内部又会被拆成 grid/block/thread,并由 GPU 把 block 分配到 SM、把 thread 组织成 warp。
换句话说:
- Stream/Event 解决“多个 GPU 任务之间怎么排队、等待、重叠”。
- Block/Warp 调度解决“一个 kernel 内部怎么并行执行”。
和 AI Infra 的关系
这个对比不是纯 CUDA 八股。它能解释很多系统现象。
为什么 GPU 训练任务不适合像 CPU 一样频繁抢占?
CPU 线程抢占和上下文切换相对常见,CFS 正是为共享 CPU 时间设计的。但 GPU 训练任务通常有:
- 大量显存状态:模型参数、梯度、optimizer state、activation;
- NCCL communicator 和多 rank 同步;
- kernel 执行和通信 overlap;
- CUDA context、缓存分配器、通信 buffer;
- checkpoint 恢复成本。
如果频繁像 CPU 时间片一样抢占 GPU 训练任务,可能会导致上下文切换、显存迁移、通信重建和 checkpoint 回滚成本远大于收益。所以集群调度里 GPU 抢占往往要 checkpoint-aware、gang-aware,而不是简单时间片轮转。
为什么 Time Slicing 和 MPS 不等于 CUDA block 调度?
Kubernetes / NVIDIA device plugin 里的 GPU time-slicing、MPS 属于多进程/多 Pod 共享 GPU 的资源管理机制。
- Time Slicing:多个进程或容器按时间片共享 GPU。
- MPS:多个 CUDA 进程通过 MPS server 更高效共享 GPU 执行资源。
- CUDA block/warp 调度:一个 kernel 内部 block 和 warp 如何映射到 SM。
它们层次不同。不能说“开了 time-slicing 后,K8s 会调度 CUDA thread block”。K8s 只调度 Pod;NVIDIA device plugin/driver 负责 GPU 资源共享;kernel 内部 block/warp 仍由 GPU 硬件调度。
为什么 CPU 线程很多会拖慢 GPU 任务?
GPU kernel 虽然在 GPU 上执行,但 GPU 程序仍依赖 CPU:
- CPU 负责 dataloader、预处理、tokenization;
- CPU 发起 kernel launch;
- CPU 提交 H2D/D2H;
- CPU 管理 CUDA runtime、NCCL、RPC;
- CPU 处理服务端请求、排队和调度。
如果 CPU 侧线程过多、上下文切换严重、NUMA 绑定不合理或 dataloader 卡住,GPU 可能出现空洞:SM 没活干、GPU Util 周期性下降。此时问题不是 CUDA block 调度,而是 CPU 供给链路和 OS 调度压力。
高频面试问答
Q: Linux CFS 和 CUDA thread block 调度有什么本质区别?
Linux CFS 是 CPU 上的操作系统调度器,调度对象是进程或线程,目标是公平性、响应性和 CPU 时间共享。它用 vruntime、nice 权重、抢占和上下文切换决定哪个 task 在 CPU core 上运行。CUDA thread block 调度是 GPU kernel 内部的执行机制,调度对象是 grid 中的 block/CTA,GPU 把 block 分配到 SM 上;block 内 thread 再组成 warp,由 SM 的 warp scheduler 选择 ready warp 发射指令。CPU 调度强调公平和低延迟,GPU 调度强调吞吐和隐藏内存延迟。
面试口径:CFS 调 OS task,CUDA block 调 kernel 内工作单元;前者公平,后者吞吐。
Q: CUDA Stream/Event 和 Thread Block/Warp 调度是什么关系?
它们处在不同层次。Stream/Event 是任务级异步调度和依赖管理工具,用来组织 H2D、kernel、D2H 等 GPU work 的顺序、等待和重叠。同一个 stream 内顺序执行,不同 stream 可以并发;event 可以表达跨 stream 依赖。Thread Block/Warp 调度是 kernel 内部的硬件并行执行机制:一个 kernel launch 产生 grid,grid 中的 block 被调度到 SM,block 内 thread 被组织成 warp,SM 的 warp scheduler 在 ready warp 之间切换。
Stream 管 kernel 之间,block/warp 管 kernel 里面。
Q: GPU block 一旦被调度到 SM 后,会像 CPU 线程一样被频繁抢占吗?
通常不会。CPU 线程是 OS 调度实体,CFS 可以通过时间片和抢占频繁切换 runnable task。CUDA block 是 kernel 内部的工作单元,一旦驻留到某个 SM 上,通常运行到完成;SM 内部通过切换 ready warp 来隐藏延迟,而不是像 OS 一样把 block 按时间片迁移来迁移去。现代 GPU 支持某些粒度的抢占能力,但相对 CPU task 抢占更粗、更贵,AI 训练平台也不会把它当作常规公平时间片机制使用。
Q: 为什么 GPU 调度更强调吞吐而不是公平?
GPU 的设计目标是把大量相似计算并行执行,尽量提高 SM、Tensor Core、HBM 带宽等资源的利用率。kernel 内部的 thread/warp 往往属于同一个计算任务,不需要像多用户 CPU 进程那样做强公平分配。SM 的 warp scheduler 更关心哪个 warp ready、能不能填满流水线、能不能隐藏访存延迟。公平性通常出现在更上层,例如多 Pod 共享 GPU、集群队列 quota、租户配额,而不是单个 kernel 内每个 CUDA thread 的公平时间片。
Q: 如果 GPU 利用率周期性掉低,这和 CFS 有关系吗?
可能有间接关系。GPU 利用率掉低可能是 GPU kernel 本身太小、block 不足、访存低效,也可能是 CPU 侧没有及时喂数据。CPU 侧 dataloader、tokenizer、RPC worker、kernel launch 线程都受 Linux 调度影响。如果 CPU 线程过多、上下文切换严重、NUMA 远端访问、锁竞争或 IO 阻塞,GPU 就会等输入或等 launch,表现为周期性空洞。因此排查时要同时看 GPU timeline 和 CPU 侧 perf/top/线程状态,而不是只看 CUDA kernel。
Q: CFS 的 vruntime 可以类比 GPU 的什么指标?
严格来说没有一一对应。vruntime 是 CFS 为了公平分配 CPU 时间定义的虚拟运行时间,用来决定哪个 OS task 更应该运行。GPU kernel 内部没有为每个 CUDA thread 维护类似 vruntime 的公平指标。GPU 更常看的指标是 active warps、occupancy、warp stall、SM Active、memory coalescing、HBM bandwidth 等,它们衡量的是硬件吞吐和延迟隐藏效果,而不是公平份额。