K8s 调度 CPU 负载很简单,但一上 GPU 就变成另一回事:GPU 是"不可拆分"的大资源、驱动要装、显存会打爆、训练要"整组进程一起调度"、推理要低延迟高吞吐。本指南把 AI/ML 工作负载上 K8s 的完整链路讲清楚:怎么声明和调度 GPU、怎么用 NVIDIA Device Plugin 与 MIG 把大卡切小、训练作业(TFJob/PyTorchJob)怎么写、推理服务(KServe/vLLM)怎么部署,以及节点池、成本与可观测性的一揽子方案。
目录
- 1. GPU 资源声明与调度基础
- 2. NVIDIA Device Plugin 与 GPU 拓扑
- 3. 共享 GPU:MIG 与 Time-Slicing
- 4. 训练作业:TFJob 与 PyTorchJob
- 5. 推理服务:KServe 与 vLLM
- 6. 节点池与 GPU 成本
- 7. AI 工作负载的调度与排队
- 8. GPU 可观测性与故障排查
- 9. 生产最佳实践
1. GPU 资源声明与调度基础
1.1 为什么 GPU 不能像 CPU 一样调度
CPU/内存:可超卖、可分片、量级小
GPU:一块卡 16~80GB 显存,昂贵、不可分,被整块占用
→ 调度器把 GPU 当"整数资源",Pod 申请 nvidia.com/gpu: 1
代价:一块卡只能被一个 Pod 独占(除非 MIG 切片);显存打爆会崩掉 Pod。
1.2 声明 GPU 的 Pod
apiVersion: v1
kind: Pod
metadata:
name: train-job
spec:
containers:
- name: trainer
image: nvcr.io/nvidia/pytorch:24.01-py3
resources:
requests: { nvidia.com/gpu: 1 }
limits: { nvidia.com/gpu: 1 }
nvidia.com/gpu 是扩展资源,只能整数申请;requests 与 limits 必须相等(不能超卖)。没有 NVIDIA Device Plugin 时节点"不知道"自己有 GPU,Pod 永远调度不上去。
2. NVIDIA Device Plugin 与 GPU 拓扑
2.1 插件上报与拓扑感知
nvidia-device-plugin(DaemonSet)调用 Kubelet 的 Device Plugin API:
ListAndWatch 上报卡数 → Pod 申请 nvidia.com/gpu 时把 /dev/nvidia*
挂载进容器,并用 nvidia-container-toolkit 注入 CUDA 运行时。
验证:kubectl describe node gpu-node-1 | grep -A3 Allocatable → nvidia.com/gpu: 8
多卡训练时卡间走 NVLink/PCIe:同组带宽 600GB/s,跨 NUMA 骤降。
生产方案:NVIDIA Topology Manager + CPUManager/NUMAResources,
或 Volcano 的拓扑感知调度。
原则:优先把整机 8 卡给一个大 Job,别把 8 卡拆给 8 个小 Job。
3. 共享 GPU:MIG 与 Time-Slicing
3.1 MIG:硬件级切片
A100/H100 支持把一块卡硬件级切成实例(如 7×10GB + 1×20GB):
每实例独立显存/算力/故障隔离 → 真正"一片卡多租户"。
约束:仅 Ampere 及以后架构;与 Time-Slicing 互斥(节点级全局配置)。
3.2 启用 MIG 与时间片共享
nvidia-smi -mig 1
nvidia-smi mig -cgi 1g.10gb,3g.40gb -C # 切成 1×10GB + 3×40GB
# Device Plugin ConfigMap 里设 migStrategy: single
Time-Slicing:不切硬件,多 Pod 轮流用同一块卡的 GPU 时间。
提高利用率,但无故障隔离,一个 OOM 影响全部。
适合推理等轻量多实例场景,不适合生产训练。
选型:要隔离+稳定用 MIG;要把空闲卡用起来、能接受相互影响用 Time-Slicing;两者同节点不能混用。
4. 训练作业:TFJob 与 PyTorchJob
4.1 为什么用训练 Operator
直接跑 Pod 无法管理多卡分布式训练的主从协调、失败重启与角色发现。
Kubeflow Training Operator 一个 Operator 管三种 CRD:
TFJob(Chief/PS/Worker)、PyTorchJob(Master/Worker)、MPIJob。
4.2 PyTorchJob 示例
apiVersion: kubeflow.org/v1
kind: PyTorchJob
metadata:
name: resnet50-train
namespace: ml
spec:
runPolicy:
cleanPodPolicy: Running
pytorchReplicaSpecs:
Master:
replicas: 1
restartPolicy: OnFailure
template: &tr
spec:
containers:
- name: pytorch
image: nvcr.io/nvidia/pytorch:24.01-py3
command: ["python", "-m", "torch.distributed.run", "--nproc_per_node=8", "train.py"]
resources:
limits: { nvidia.com/gpu: 8 }
Worker:
replicas: 3
restartPolicy: OnFailure
template: *tr
用 kubectl apply -f pytorchjob.yaml -n ml 提交、kubectl get pytorchjob resnet50-train -n ml 观察。restartPolicy: OnFailure 只重启该角色组;Master 挂了通常整个 Job 失败,配合 runPolicy 的 backoffLimit 控整体重试,避免无限重启烧钱。
5. 推理服务:KServe 与 vLLM
5.1 InferenceService 与 ServingRuntime
KServe(原 KFServing):InferenceService 一个 CRD 描述模型服务,
自动做模型加载、版本管理与自动伸缩(HPA/KEDA)。
通过 ServingRuntime 支持 vLLM、Triton、TFServing 等。
5.2 用 vLLM 部署 LLM 推理
apiVersion: serving.kserve.io/v1beta1
kind: InferenceService
metadata:
name: llama2-7b
namespace: ml
spec:
predictor:
model:
modelFormat: { name: vllm }
runtime: vllm-0.6.0
storageUri: s3://models/llama2-7b/
resources:
requests: { cpu: "4", memory: 24Gi, nvidia.com/gpu: 1 }
limits: { nvidia.com/gpu: 1 }
访问:curl -X POST http://<service>/v2/models/llama2-7b/generate -H "Content-Type: application/json" -d '{"prompt": "你好"}'。
推理比训练更讲究延迟与吞吐:vLLM 用 PagedAttention + Continuous Batching
提高显存利用率;显存估算:7B FP16 ≈ 14GB 权重 + KV cache;
用 KEDA 按 in-flight requests 伸缩副本。
6. 节点池与 GPU 成本
6.1 节点池分层
训练池:A100/H100 整机 8 卡,Spot 通常不适合(中断成本高)
推理池:L4/T4 或 MIG 切片,按需水平伸缩,可用 Spot
开发池:共享卡 / MIG 切片 / 远程开发容器
成本要点:GPU 节点空转成本高 → 必须弹性伸缩(Karpenter/CA);
监控 GPU 利用率 < 30% 的作业并回收。
6.2 按作业分摊
按 Job 记账:Kubecost/OpenCost 按 request 分配 GPU 成本(单价×卡数×时长)。
给作业打 labels 按团队汇总:kubectl label -n ml pytorchjob resnet50-train \
team=alg-2 project=vision --overwrite。
用 ResourceQuota + LimitRange 防无节制申请。
7. AI 工作负载的调度与排队
7.1 默认调度器不够,用 Volcano 与 Kueue
训练/推理特有的调度需求:整组调度(Gang,Master+Workers 要么全起
要么都不起)、按配额排队与优先级、多卡通信拓扑感知、低优让位高优。
默认调度器逐 Pod 调度,以上都不满足。
Volcano:Scheduler + Queue + PodGroup,支持 Gang/公平/优先级抢占,
常见于 Kubeflow 生态,直接对接 TFJob/PyTorchJob。
Kueue:作业级准入排队,LocalQueue/ClusterQueue 按配额排队,
与 Cluster Autoscaler 联动扩缩容,更轻量。
选择:底层卡竞争激烈 → Volcano;多团队配额治理 → Kueue。
8. GPU 可观测性与故障排查
8.1 DCGM 指标
kubectl apply -f https://raw.githubusercontent.com/NVIDIA/dcgm-exporter/main/deploy/dcgm-exporter.yaml
# DCGM_FI_DEV_GPU_UTIL 计算利用率 / DCGM_FI_DEV_FB_USED 显存
# DCGM_FI_DEV_POWER_USAGE 功率 / DCGM_FI_DEV_TEMP 温度
8.2 常见故障排查
Pod Pending → describe 看 Events(无 GPU 可分配/污点/配额)
启动报 CUDA OOM → 显存申请不够,看 nvidia-smi 余量调 requests
训练慢/利用率低 → 先查数据管道(num_workers/DataLoader/存储 IO),
用 nvidia-smi dmon 判断是"等数据"还是"真在算"
Xid/驱动错误 → kubectl logs + dmesg,升级驱动与 container-toolkit
9. 生产最佳实践
9.1 Checklist
□ 节点装 Driver + container-toolkit + device-plugin,三件套版本对齐
□ 声明 GPU 用 requests=limits,并设 CPU/内存 兜底
□ 显存按模型权重 + KV cache 精确估算
□ 训练用 Operator(TFJob/PyTorchJob),配合 Volcano/Kueue 排队
□ 推理用 KServe + vLLM,按 in-flight 请求伸缩
□ 节点池分层:训练独占卡、推理弹性、开发用 MIG
□ 开启 DCGM 指标与告警,作业打成本标签按团队分摊
9.2 常见坑与对策
| 坑 | 现象 | 对策 |
|---|---|---|
| 驱动/插件版本不匹配 | 卡不显示、容器起不来 | 三件套统一版本,升级前演练 |
| 显存估错 | 推理 OOM | 按权重+KV cache 预留并压测 |
| 默认调度器乱跑 | 多卡跨节点、通信慢 | 整组调度 + 拓扑感知(Volcano) |
| 空跑 GPU | 卡占用但利用率 0 | DCGM 监控 + 闲置回收 |
| 无限重启 | 失败作业反复烧钱 | 设 runPolicy.backoffLimit |
| 抢占式实例被打断 | 训练中断丢进度 | 定期 checkpoint 续训 |
小结
K8s 上的 AI/ML 工作负载,核心是把"GPU 这张大而贵的卡"调度好、用满、算清账:设备层用 NVIDIA Device Plugin 让节点"认识"卡并支持 MIG 切片;训练层用 TFJob/PyTorchJob 管理分布式作业,用 Volcano/Kueue 做整组调度与排队;推理层用 KServe+vLLM 拿到低延迟高吞吐;成本层按节点池分层、按作业打标签分摊,并用 DCGM 盯住利用率。记住最贵的三件事:闲置 GPU 是烧钱、无限重启是烧钱、显存估错是事故。先把"卡能不能被正确声明、调度、监控"这条链路跑通,再谈扩到多团队多项目。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。