2026-09-03

Kubernetes GPU 超配:Time-Slicing、MIG 与队列回填

从 Kubernetes GPU 资源语义出发,对比队列回填、NVIDIA Time-Slicing、MIG 和运行时共享,并给出可执行配置、验证指标与回滚边界。

GPU 利用率低,不等于可以直接把一张卡声明成四张。先确认浪费发生在什么位置:任务排队方式不合理、显存长期占用但计算空闲,还是业务为了峰值申请了过多副本。原因不同,处理手段也不同。

Kubernetes 原生 GPU 通常以 nvidia.com/gpu 这类扩展资源出现。扩展资源按整数调度,不能像 CPU 那样原生超卖。节点有 8 张卡,默认最多只能同时满足 8 个 nvidia.com/gpu: 1 请求。要让更多 Pod 共享物理 GPU,必须由设备插件、运行时或硬件分区重新暴露资源;调度器本身不会自动完成显存隔离或算力限额。

先把四种方案分开

方案 是否同卡并发 隔离能力 适合场景 主要风险
队列回填、优先级、抢占 否 保持整卡隔离 训练、批处理、可中断任务 抢占成本、检查点恢复
NVIDIA Time-Slicing 是 无显存与故障隔离 开发环境、低风险推理、小任务 OOM 相互影响、延迟抖动
MIG 是 硬件级计算与显存分区 多租户推理、稳定小规格任务 固定规格、资源碎片、重配成本
MPS、vGPU 或框架级共享 是 取决于实现 需要更细粒度控制的固定负载 运维复杂、版本兼容、可观测性差异

如果问题只是“大任务挡住了后面的小任务”,先做队列治理,不要急着同卡共享。Kueue 的 Cohort 可以让队列借用空闲配额,配合优先级和抢占回收资源;这里借的是集群配额,不是把一张 GPU 的显存切成多份。

先建立共享前基线

至少采集一周数据,并覆盖发布、流量高峰和训练检查点阶段。只看 nvidia-smi 某一秒的利用率没有决策价值。

nvidia-smi \
  --query-gpu=index,uuid,utilization.gpu,memory.used,memory.total,power.draw \
  --format=csv

kubectl get pods -A --field-selector=status.phase=Pending
kubectl get events -A --sort-by='.lastTimestamp' | tail -n 100
kubectl describe node <gpu-node> | sed -n '/Capacity:/,/System Info:/p'

接入 DCGM Exporter 后,分别看持续利用率和峰值,而不是只看平均数:

avg_over_time(DCGM_FI_DEV_GPU_UTIL[1h])
max_over_time(DCGM_FI_DEV_FB_USED[24h])
max_over_time(DCGM_FI_DEV_MEM_COPY_UTIL[1h])

共享候选通常同时满足这些条件:

  • GPU 计算存在稳定空档,不是采样误差。
  • 显存峰值仍有余量,模型加载和突发 batch 不会把卡顶满。
  • 任务可重试,单次失败不会造成数据损坏。
  • 业务能接受一定尾延迟抖动。
  • 已能关联 Pod、GPU、OOM、请求延迟和重试记录。

核心在线推理、长时间训练、无检查点任务,不应成为第一批试验对象。

用 Time-Slicing 做最小实验

下面使用独立资源名 nvidia.com/gpu.shared,避免共享 Pod 误落到整卡池。replicas: 4 表示每张物理 GPU 对外暴露四个可调度份额,不代表每个 Pod 获得四分之一显存或固定四分之一算力。

保存为 time-slicing-config.yaml:

apiVersion: v1
kind: ConfigMap
metadata:
  name: time-slicing-config
  namespace: gpu-operator
data:
  shared: |-
    version: v1
    flags:
      migStrategy: none
    sharing:
      timeSlicing:
        renameByDefault: true
        failRequestsGreaterThanOne: true
        resources:
          - name: nvidia.com/gpu
            replicas: 4    

应用配置,并让 GPU Operator 的 device plugin 使用它:

kubectl apply -f time-slicing-config.yaml

kubectl patch clusterpolicies.nvidia.com/cluster-policy \
  -n gpu-operator --type merge \
  -p '{"spec":{"devicePlugin":{"config":{"name":"time-slicing-config","default":"shared"}}}}'

kubectl get pods -n gpu-operator -w

如果只修改 ConfigMap,GPU Operator 不会自动监听并重启 device plugin。维护窗口内显式重启:

kubectl rollout restart -n gpu-operator daemonset/nvidia-device-plugin-daemonset

确认节点同时显示物理卡数量与共享资源容量:

kubectl get node <gpu-node> \
  -o jsonpath='{.metadata.labels.nvidia\.com/gpu\.count}{" physical\n"}{.metadata.labels.nvidia\.com/gpu\.replicas}{" replicas\n"}{.status.allocatable.nvidia\.com/gpu\.shared}{" shared allocatable\n"}'

用四个 Pod 验证调度结果

先把试验限制在一台节点,不要直接改整个 GPU 池:

kubectl label node <gpu-node> gpu-mode=shared
apiVersion: apps/v1
kind: Deployment
metadata:
  name: gpu-share-check
spec:
  replicas: 4
  selector:
    matchLabels:
      app: gpu-share-check
  template:
    metadata:
      labels:
        app: gpu-share-check
    spec:
      nodeSelector:
        gpu-mode: shared
      containers:
        - name: check
          image: nvcr.io/nvidia/cuda:12.8.1-base-ubuntu22.04
          command: ["bash", "-lc"]
          args: ["nvidia-smi -L && sleep infinity"]
          resources:
            limits:
              nvidia.com/gpu.shared: 1
kubectl apply -f gpu-share-check.yaml
kubectl get pods -l app=gpu-share-check -o wide
kubectl exec deploy/gpu-share-check -- nvidia-smi -L
kubectl describe node <gpu-node> | sed -n '/Allocated resources:/,$p'

四个 Pod 能落到单卡节点,只能证明调度容量被放大。它不能证明显存有隔离,也不能证明业务吞吐一定提高。下一步必须运行真实模型并比较共享前后的每卡吞吐、P95/P99 延迟、OOM、Pod 重启和 GPU Xid 错误。

Time-Slicing 的边界

  • 多个进程共享同一物理 GPU 和显存故障域,一个进程 OOM 可能影响同卡其他任务。
  • 调度器只看到逻辑份额,看不到每个进程的实时显存峰值。
  • nvidia.com/gpu.shared: 2 不代表获得两倍算力。示例中的 failRequestsGreaterThanOne 会直接拒绝这种请求。
  • DCGM 指标主要按物理 GPU 汇总。做 Pod 级归因时,需要结合进程、容器和业务指标。
  • 高密度并发可能增加上下文切换,吞吐没有增加,尾延迟却先恶化。

因此,共享池要和整卡池分开。最简单的办法是使用独立节点标签、taint/toleration 和独立资源名;不要让延迟敏感服务靠“运气”避开共享节点。

什么时候改用 MIG

MIG 把支持的 NVIDIA GPU 切成固定硬件实例,每个实例拥有独立显存、缓存和计算资源。它比 Time-Slicing 更适合需要稳定延迟和租户隔离的推理服务,但会引入规格碎片:剩余资源不一定能拼成业务需要的 profile,重配也可能影响节点上现有任务。

选择 MIG 前先回答:

  1. 模型在目标 MIG profile 内能否加载,并保留足够 KV Cache 或训练峰值余量?
  2. 实例规格是否能覆盖常见模型,而不是为单个模型做一次性切分?
  3. 是否接受节点重配、驱逐和重新调度窗口?
  4. 监控、配额和容量报表能否识别 MIG 资源名?

如果这些问题还没有答案,先维持整卡或小规模 Time-Slicing,不要把硬件分区当成自动优化器。

发布与回滚门槛

从 replicas: 2 开始,只放可重试、低优先级任务。下面是一组试验门槛示例,数值应替换成业务自己的 SLO:

指标 继续扩大 立即回滚
每物理 GPU 有效吞吐 明显高于整卡基线 没提升或下降
P99 延迟 仍在业务 SLO 内 连续超出 SLO
OOM、Pod 重启 不高于基线 同卡出现关联失败
GPU Xid 错误 0 任意新增错误
排队时间 持续下降 只是把等待转成运行时抖动

回滚时恢复之前保存的 ClusterPolicy 配置,再重启 device plugin。不要在仍有共享任务运行时直接删除资源名;先停止新调度,排空共享节点,确认 Pod 已迁移,再恢复整卡配置。

最终判断

GPU 超配不是一个开关。优先级通常是:先修正 requests 和副本数,再做队列回填;仍有稳定空档时,给低风险任务试用 Time-Slicing;需要硬隔离时再评估 MIG 或 vGPU。

验收要看每张物理卡是否完成了更多有效工作,同时延迟、OOM、故障范围仍在可接受边界内。

参考链接