Kubernetes AI/ML 工作负载:GPU 调度与训练作业

面向 AI/ML 的 Kubernetes 实战:GPU 资源声明与调度、NVIDIA Device Plugin 与 MIG 共享、TFJob/PyTorchJob 训练作业、KServe 与 vLLM 推理服务、GPU 节点池与成本、调度排队与可观测性。

K8s 调度 CPU 负载很简单,但一上 GPU 就变成另一回事:GPU 是"不可拆分"的大资源、驱动要装、显存会打爆、训练要"整组进程一起调度"、推理要低延迟高吞吐。本指南把 AI/ML 工作负载上 K8s 的完整链路讲清楚:怎么声明和调度 GPU、怎么用 NVIDIA Device Plugin 与 MIG 把大卡切小、训练作业(TFJob/PyTorchJob)怎么写、推理服务(KServe/vLLM)怎么部署,以及节点池、成本与可观测性的一揽子方案。


目录


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卡占用但利用率 0DCGM 监控 + 闲置回收
无限重启失败作业反复烧钱设 runPolicy.backoffLimit
抢占式实例被打断训练中断丢进度定期 checkpoint 续训

小结

K8s 上的 AI/ML 工作负载,核心是把"GPU 这张大而贵的卡"调度好、用满、算清账:设备层用 NVIDIA Device Plugin 让节点"认识"卡并支持 MIG 切片;训练层用 TFJob/PyTorchJob 管理分布式作业,用 Volcano/Kueue 做整组调度与排队;推理层用 KServe+vLLM 拿到低延迟高吞吐;成本层按节点池分层、按作业打标签分摊,并用 DCGM 盯住利用率。记住最贵的三件事:闲置 GPU 是烧钱、无限重启是烧钱、显存估错是事故。先把"卡能不能被正确声明、调度、监控"这条链路跑通,再谈扩到多团队多项目。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「云原生」更多文章

  1. Kubernetes 成本优化:FinOps、资源画像与降本实践
  2. 边缘与轻量 Kubernetes:K3s、KubeEdge 与资源受限环境
  3. 策略即代码:OPA Gatekeeper、Kyverno 与合规治理