跳转至

GPU 工作负载与节点自动扩缩容

AI 平台的扩缩容不是一个 HPA。一次流量变化可能依次触发推理副本扩容、Pending Pod、GPU 节点创建、驱动和设备组件就绪、镜像拉取、模型下载以及缓存预热。任何一层缺少时间预算,自动扩容都可能在容量到达前超时。

1. 四层弹性

请求层
  并发、Token、优先级、排队和拒绝
Pod/Workload 层
  HPA、KEDA、WVA、训练弹性、队列准入
节点层
  Cluster Autoscaler、Karpenter、云厂商自动供给
容量层
  预留、Spot、配额、区域库存和裸金属交付

Kueue 不创建节点,HPA 也不理解 GPU 是否能从云厂商获得。每层要有独立 SLO 和失败原因。

2. 扩容时间线

T0  流量或排队增长
T1  Autoscaler 生成新副本
T2  Pod Pending
T3  Node Autoscaler 决定创建节点
T4  云实例获得容量并启动
T5  kubelet、CNI、CSI、GPU Driver/Plugin Ready
T6  镜像和模型下载
T7  GPU 权重加载、图编译和预热
T8  Pod Ready,Gateway 开始送流量

记录每个阶段的时间。只监控 T0 到 T1 会严重低估大型模型的冷启动。

3. 工作负载扩缩容信号

传统 HPA

CPU 对 LLM 推理通常不是主要瓶颈。HPA 更适合:

  • 网关、Tokenizer、Embedding 或 CPU 服务;
  • 已通过 Metrics Adapter 暴露的自定义指标;
  • 保持至少一个副本的稳定在线服务。

KEDA

KEDA 可以根据 Prometheus、消息队列等外部信号生成 HPA,并负责 0 到 1 的激活。适合:

  • 批推理队列;
  • 异步 Embedding;
  • 请求队列或 Token 指标;
  • 允许缩到零且能接受冷启动的模型。

推理专用信号

优先考虑:

  • Waiting Requests / Queue Depth;
  • Running Requests 与安全并发;
  • Prompt/Generation Token Rate;
  • TTFT、TPOT 和拒绝率;
  • KV Cache 使用率;
  • 模型加载状态;
  • 预测的请求持续时间。

只使用 GPU Utilization 容易误判:Decode 可能显存带宽受限但 SM 利用率不高,长 Prompt 又会让 Prefill 出现短时尖峰。

4. 一个 KEDA 思路示例

apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: llm-server
spec:
  scaleTargetRef:
    name: llm-server
  minReplicaCount: 1
  maxReplicaCount: 8
  cooldownPeriod: 300
  triggers:
    - type: prometheus
      metadata:
        serverAddress: http://prometheus.monitoring.svc:9090
        metricName: llm_waiting_requests
        query: sum(llm_waiting_requests{model="example-model"})
        threshold: "8"

指标名是平台契约示例,应根据实际引擎指标和聚合方式调整。扩容前需要处理缺失指标、Prometheus 不可用和高基数问题。

5. Cluster Autoscaler 与 Karpenter

能力 Cluster Autoscaler Karpenter
节点来源 预定义 Node Group 根据 NodePool 约束动态选择 Node
云厂商覆盖 广 取决于 Provider 实现
节点生命周期 主要关注扩缩容 同时覆盖供给、过期、漂移和整合
实例选择 从已配置组中选择 根据 Pending Pod 和约束选择实例
适合 稳定节点组、多云一致模式 云上动态实例和快速迭代节点池

Kubernetes 官方将二者都列为 SIG Autoscaling 相关的 Node Autoscaler。具体能力和支持云以目标 Provider 文档为准。

参考:Kubernetes Node Autoscaling

6. GPU NodePool 约束

一个 GPU NodePool 至少限制:

  • 允许的实例/GPU 型号;
  • 区域和可用区;
  • On-Demand、Spot 或预留容量;
  • 总 GPU/CPU/内存上限;
  • 节点 Taint 与平台标签;
  • OS/AMI/节点镜像;
  • 过期、整合和中断预算;
  • DaemonSet 资源开销;
  • 本地盘和网络能力。

Karpenter 概念示例:

apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: gpu-inference
spec:
  limits:
    nvidia.com/gpu: 32
  disruption:
    consolidationPolicy: WhenEmpty
    budgets:
      - nodes: "1"
  template:
    spec:
      taints:
        - key: accelerator
          value: gpu
          effect: NoSchedule
      requirements:
        - key: karpenter.sh/capacity-type
          operator: In
          values: ["on-demand"]

Provider 还需要 NodeClass 等云资源配置,本例不能直接作为完整安装清单。

7. Scale from Zero 的前提

Node Autoscaler 必须在节点不存在时仍能推断:

  • 节点将提供什么扩展资源;
  • 节点标签、Taint 和可用区;
  • DaemonSet 会消耗多少 CPU、内存和 Pod Slot;
  • DRA Driver/Device Plugin 与自动扩容是否兼容;
  • 多节点任务是否需要原子拓扑;
  • 云配额和实例库存是否允许供给。

如果自动扩容器看不到未来 GPU 资源,Pending Pod 不会触发节点创建。厂商托管 Kubernetes 常通过节点模板或 NodePool 元数据补充这些信息。

8. 队列与节点供给

Kueue 通常只在配额可用时准入 Workload。常见策略:

先准入再扩节点

优点:简单,Pending Pod 触发 Node Autoscaler。风险:配额有但云容量不足,已准入任务长期等待。

ProvisioningRequest

先由 Kueue/Autoscaler 协调容量供给,再完成准入。适合昂贵、稀缺或需要预留的整组容量,但组件集成和状态机更复杂。

保留最小容量

在线推理保留 Warm Pool,训练和批处理再弹性扩容。它牺牲少量空闲成本换取可预测延迟。

必须让用户看见“等待配额”“等待云容量”“等待节点启动”和“等待模型预热”的区别。

9. 多节点任务与原子扩容

一个 16 节点任务不能接受只创建 9 个节点后无限等待。需要考虑:

  • Gang/All-or-Nothing 准入;
  • 云供应商是否能原子获得整组容量;
  • 同一网络 Block/Placement Group/TPU Slice;
  • 部分容量失败后的释放;
  • 超时后是否换区域、型号或队列;
  • 创建后未及时启动任务的成本泄漏。

对于 TPU Slice、UltraServer 或高带宽网络域,节点数量和拓扑是同一个资源请求的一部分。

10. 缩容与中断

节点缩容不应只看“GPU 利用率低”。先判断工作负载是否可安全移动:

  • 在线推理是否有其他 Ready 副本;
  • 模型缓存重建需要多久;
  • 训练是否完成 Checkpoint;
  • 本地 NVMe 是否只有唯一数据;
  • PDB、Kueue 和终止宽限期是否一致;
  • DRA Claim、RDMA 连接和多机组如何释放;
  • Canary/发布期间是否允许扰动。

在线 GPU 节点建议使用保守整合策略,批处理节点可以在 Job 完成后更积极地缩容。

11. Spot 与抢占

适合 Spot:

  • 有可靠 Checkpoint 的训练;
  • 可重试的批推理和数据处理;
  • 低优先级实验;
  • 可以跨型号或区域迁移的任务。

不适合仅依赖 Spot:

  • 没有冗余的在线模型;
  • 长时间无法保存状态的训练;
  • 严格拓扑且很难重新获得整组容量的任务;
  • 冷启动远大于平均可用窗口的工作负载。

中断通知要同时触发停止准入、Checkpoint/Drain 和指标记录。

12. 防止弹性控制器打架

典型冲突:

  • HPA 扩副本,KEDA 同时管理同一个 Scale Target;
  • VPA 重建 Pod,影响推理可用性;
  • Karpenter 整合节点,Kueue 刚准入训练;
  • Canary 流量变化触发 HPA,破坏实验比例;
  • Prefill/Decode 两个 Autoscaler 失去平衡;
  • Model Cache DaemonSet 尚未完成,Pod 已被判定 Ready。

每个可扩对象只能有一个最终写入者。不同控制器之间通过明确指标和保护窗口协调。

13. 容量保护

建议分层:

硬下限:系统和生产推理最小容量
保护容量:单故障域、升级和突发预留
可借用容量:训练/实验可临时使用
弹性容量:云上按需或 Spot 扩展

当生产流量上升时,应先停止训练借用或新任务准入,而不是直接抢占已加载但低流量的推理副本。

14. 必须监控的指标

层级 指标
请求 到达率、排队、拒绝、TTFT/TPOT
Pod Desired/Ready、扩容决策、Pending 原因
队列 Admission、等待时间、配额、Flavor
节点 Provision 时间、失败、NotReady、整合
模型 下载、加载、预热和缓存命中时间
云容量 配额、库存不足、Spot 中断、价格
成本 空闲时间、冷启动浪费、单位 Token/Job 成本

端到端扩容 SLO 应从信号产生算到新增 Ready 容量,而不是只统计 Autoscaler Reconcile。

15. 故障模式

现象 常见原因
HPA 已扩容但没有新容量 GPU Pod Pending,节点未创建
节点创建后 Pod 仍 Pending DaemonSet 开销、标签/Taint、Device Plugin 未就绪
Pod Running 但未接流量 模型下载/加载、Readiness 或 Gateway 状态
扩缩容来回震荡 指标延迟、阈值过近、冷却不足
无法缩容 PDB、本地数据、长训练、do-not-disrupt
选择错误 GPU 型号 NodePool 约束太宽或平台标签不一致
成本异常增长 创建后未准入、模型加载失败、整合被永久阻止

16. 生产检查清单

  • 已记录从扩容信号到模型 Ready 的完整时间线。
  • 推理扩容基于队列、Token 或延迟,而不是只看 CPU。
  • Node Autoscaler 能识别未来 GPU 节点的资源和标签。
  • Kueue 准入与节点容量不足有不同状态和告警。
  • 多节点任务不会只供给部分容量后无限占用。
  • 在线推理有 Warm Pool 和故障冗余。
  • 缩容尊重 Checkpoint、PDB、本地缓存和 DRA Claim。
  • Spot 中断路径经过真实演练。
  • 每个 Scale Target 只有一个最终控制器。
  • 端到端扩缩容 SLO 和成本可以持续回归。

延伸阅读