Kubernetes 上的 GPU 自动扩缩 —— Karpenter、KAI Scheduler、Gang Scheduling
是三层,不是一层。Karpenter 动态供给节点(不到一分钟,比 Cluster Autoscaler 快 40%)。KAI Scheduler 处理 gang scheduling、拓扑感知和分层队列 —— 它防住"八缺一"的部分分配陷阱:七个节点干等着、烧着钱,就因为缺一块 GPU。应用层自动扩缩器(NVIDIA Dynamo Planner、llm-d Workload Variant Autoscaler)按推理特有的信号扩缩 —— 队列深度、KV cache 利用率 —— 而不是按 CPU/DCGM 占空比。经典的 HPA 陷阱在于
DCGM_FI_DEV_GPU_UTIL是一个占空比测量:100% 可能是 10 个请求,也可能是 100 个。vLLM 预分配 KV cache 内存,所以内存永远不会触发缩容。这一课教你把三层组合起来,并避开 Karpenter 默认的WhenEmptyOrUnderutilized策略 —— 它会在推理进行到一半时干掉正在跑的 GPU 任务。
类型: Learn 语言: Python(标准库,一个玩具级的队列深度自动扩缩模拟器) 前置要求: 阶段 17 · 02(推理平台经济学)、阶段 17 · 04(推理服务引擎内部机制) 预计时间: ~75 分钟
学习目标
- 画出三个自动扩缩层(节点供给、gang scheduling、应用层),并说出每一层用的工具。
- 解释为什么
DCGM_FI_DEV_GPU_UTIL对 vLLM 是错误的 HPA 信号,并说出两个替代信号(队列深度、KV cache 利用率)。 - 描述 gang scheduling,以及 KAI Scheduler 防住的那种部分分配故障模式(8 块 GPU 里 7 块闲着)。
- 说出会干掉正在跑的 GPU 任务的那条 Karpenter consolidation 策略(
WhenEmptyOrUnderutilized),并给出 2026 年安全的替代方案。
问题背景
你的团队在 Kubernetes 上交付一个 LLM 服务。你用 DCGM_FI_DEV_GPU_UTIL 作为信号配了 HPA。服务在工作时间钉死在 100% 利用率。HPA 从不扩容 —— 它已经认为你满了。你手动加了一个副本;TTFT 降了。HPA 仍然不扩。信号在骗你。
另一边,你的节点用的是 Cluster Autoscaler。凌晨 2 点来了一个 1M token 的 prompt;集群花 3 分钟供给一个节点,请求超时了。
再另一边,你部署一个 70B 模型,要跨 2 个节点用 8 块 GPU。集群空着 7 块 GPU,第 8 块散在 3 个节点上。Cluster Autoscaler 为那缺的 1 块 GPU 供给一个节点。七个节点干等 4 分钟、烧着钱,等 Kubernetes 把最后一块 GPU 拉起来。
三层,三种不同的故障模式。2026 年的 GPU 感知自动扩缩不是"打开 HPA"。它是把节点供给、gang scheduling 和应用信号扩缩组合起来。
核心概念
第 1 层 —— 节点供给(Karpenter)
Karpenter 盯着 pending 的 pod,在约 45-60 秒内供给节点(GPU 节点上 Cluster Autoscaler 通常要 90-120 秒)。它按 NodePool 约束动态挑实例类型 —— 如果你的 pod 要 8 块 H100、集群里又没有匹配的节点,Karpenter 会直接供给一个,而不是扩一个已有的组。
consolidation 陷阱:Karpenter 默认的 consolidationPolicy: WhenEmptyOrUnderutilized 对 GPU 池很危险。它会干掉一个正在跑的 GPU 节点,把 pod 迁到更便宜、尺寸更合适的实例上。对推理工作负载,这意味着驱逐正在跑的请求、在新节点上重新加载一个 70B 模型。损失是几分钟的算力外加请求失败。
GPU 池的安全设置:
disruption:
consolidationPolicy: WhenEmpty
consolidateAfter: 1h让 Karpenter 在一小时后整理真正空着的节点,但永不驱逐正在跑的任务。
第 2 层 —— gang scheduling(KAI Scheduler)
KAI Scheduler(项目原名"Karp"后改名)处理默认 kube-scheduler 做不到的事:
Gang scheduling —— 全有或全无地调度。一个要 8 块 GPU 的分布式推理 pod,要么 8 块一起启动,要么一块都不启。没有它,你就掉进部分分配陷阱:8 个 pod 起了 7 个,无限期等待,烧钱。
拓扑感知 —— 知道哪些 GPU 共享 NVLink、哪些在同一个机架、哪些之间有 InfiniBand。据此放置 pod。一个 DeepSeek-V3 67B 的张量并行工作负载必须留在一个 NVLink 域里;KAI Scheduler 会尊重这一点。
分层队列 —— 多个团队带着优先级和配额抢同一个 GPU 池。只有优先级规则允许时,团队 A 的生产高峰才会被团队 B 的训练任务抢占。
KAI 作为次级调度器和 kube-scheduler 一起部署;你给工作负载打 annotation 让它走 KAI。Ray 和 vLLM production-stack 都集成了它。
第 3 层 —— 应用层信号
HPA 陷阱:DCGM_FI_DEV_GPU_UTIL 是一个占空比指标 —— 它测的是 GPU 在每个采样间隔里有没有在干活。100% 利用率可能是 10 个并发请求,也可能是 100 个;反正 GPU 都在忙。按占空比扩缩就是瞎扩缩。
更糟的是,vLLM 和类似引擎预分配 KV cache 内存(最高到 --gpu-memory-utilization)。哪怕只有一个请求,内存使用率也维持在 90% 上下。基于内存的 HPA 永远不会缩容。
2026 年的替代信号:
- 队列深度(等待 prefill 的请求数)。
- KV cache 利用率(多少比例的 block 分配给了活跃序列)。
- 每副本 P99 TTFT(你的 SLA 信号)。
- Goodput(每秒满足所有 SLO 的请求数)。
NVIDIA Dynamo Planner 和 llm-d Workload Variant Autoscaler 消费这些信号来扩缩副本。对 LLM 服务,它们完全取代 HPA。
什么时候用什么
| 扩缩决策 | 工具 |
|---|---|
| 增/删节点 | Karpenter |
| 调度多 GPU 任务 | KAI Scheduler |
| 增/删副本 | Dynamo Planner / llm-d WVA(或基于队列深度的自定义 HPA) |
| 选 GPU 类型 | Karpenter NodePool |
| 抢占低优先级 | KAI Scheduler 队列 |
分离式 prefill/decode 让一切更复杂
如果你跑分离式 prefill/decode(阶段 17 · 17),你就有两类 pod,扩缩触发器不同:prefill pod 按队列深度扩缩,decode pod 按 KV cache 压力扩缩。llm-d 把它们暴露成各自独立的 Services,按角色分别配 HPA。别想着在两者前面摆一个 HPA。
冷启动在这里也很重要
冷启动缓解(阶段 17 · 10)正是节点供给时间变得对用户可见的地方。Karpenter 的 45-60 秒预热,加上 20GB 模型加载,加上引擎初始化,意味着一个从零开始的请求要花 2-5 分钟。给 SLO 关键路径留一个 warm pool(min_workers=1),或者在应用层用 Modal 式的 checkpoint。
你该记住的数字
- Karpenter 节点供给:约 45-60 秒 vs Cluster Autoscaler 约 90-120 秒(GPU 节点)。
- KAI Scheduler 防住部分分配的浪费 —— 八缺一陷阱。
DCGM_FI_DEV_GPU_UTIL作为 HPA 信号:坏的;用队列深度或 KV 利用率。- Karpenter 的
WhenEmptyOrUnderutilized:会干掉正在跑的 GPU 任务。推理用WhenEmpty + consolidateAfter: 1h。
autoscaling实际使用
code/main.py 在一个突发型 GPU 工作负载上模拟一个三层自动扩缩器。对比朴素 HPA(占空比)、队列深度 HPA 和 KAI gang 调度的扩缩。报告未满足的请求、GPU 空闲分钟数和一个综合得分。
拿去用
这一课产出 outputs/skill-gpu-autoscaler-plan.md。给定集群拓扑、工作负载形态和 SLO,它设计一套三层自动扩缩方案。
练习
- 跑
code/main.py。在突发型工作负载下,朴素的占空比 HPA 丢掉了多少个队列深度 HPA 能接住的请求?差异从哪来? - 为一个在 H100 SXM5 上服务 Llama 3.3 70B FP8 的集群设计一个 Karpenter NodePool。指定
capacity-type、disruption.consolidationPolicy、consolidateAfter,以及一个把非 GPU 工作负载挡在这些节点之外的 taint。 - 你的团队报告说部署卡在 Pending,理由是"有空闲 GPU 但 pod 不调度"。诊断 —— 这是 Karpenter、kube-scheduler 还是 KAI Scheduler 的问题?哪些指标能证实?
- 给分离式 prefill pod 挑一个扩缩信号,给 decode pod 挑另一个。两个都给出理由。
- 算一算
WhenEmptyOrUnderutilizedconsolidation 陷阱在一个 7×24 生产服务上的代价 —— 该服务平均每天发生 60 次"丢请求"事件,P99 TTFT > 10 秒。
关键术语
| 术语 | 大家嘴上怎么说 | 它实际是什么 |
|---|---|---|
| Karpenter | "供给节点的那个" | Kubernetes 节点自动扩缩器;亚分钟级供给 |
| Cluster Autoscaler | "老的扩缩器" | Kubernetes 节点自动扩缩器的前身;更慢、基于组 |
| KAI Scheduler | "GPU 调度器" | 处理 gang + 拓扑 + 队列的次级调度器 |
| Gang scheduling | "全有或全无" | 原子地调度 N 个 pod,否则全部推迟 |
| 拓扑感知 | "机架感知" | 按 NVLink/IB/机架位置放置 pod |
DCGM_FI_DEV_GPU_UTIL | "GPU 利用率" | 占空比指标;对 LLM 不是扩缩信号 |
| 队列深度 | "等待中的请求" | prefill 受限扩缩的正确 HPA 信号 |
| KV cache 利用率 | "内存压力" | decode 受限扩缩的正确 HPA 信号 |
| Consolidation | "Karpenter 整理" | 把节点干掉换更便宜的实例类型 |
WhenEmpty + 1h | "安全整理" | 不驱逐正在跑的 GPU 任务的策略 |
延伸阅读
- KAI Scheduler GitHub —— 设计文档和配置示例。
- Karpenter Disruption Controls —— consolidation 策略语义与 GPU 安全默认值。
- NVIDIA — Disaggregated LLM Inference on Kubernetes —— Dynamo Planner 扩缩信号。
- Ray docs — KAI Scheduler for RayClusters —— Ray 集成模式。
- AWS EKS Compute and Autoscaling Best Practices —— 托管 Kubernetes 专属指南。
- llm-d GitHub —— Workload Variant Autoscaler 设计。