从云原生走向 AI 原生:一套面向未来的架构方法论 → 阅读《AI 原生基础设施》

GPU 推理自动伸缩:从并发指标到缩容到零

草稿

推理的弹性不是“加副本”三个字:指标选错会抖动,缩容到零要重新算一遍冷启动的账。

vLLM 生产化部署立起了多副本,但副本数是 values.yaml 里写死的。真实的推理流量有明显的潮汐(白天高峰、夜间几乎为零),GPU 又是按小时计价的最贵资源。本章解决两个问题:用什么指标驱动伸缩,以及缩容到零时冷启动怎么算账

内容来源
本章的机制对比基于 KServe 自动伸缩样例(Apache 2.0)与社区项目 gpu-autoscale-inference(MIT 许可,KEDA + Redis + vLLM + GKE 的端到端缩容到零实现)。命令与配置以两个仓库的当前版本为准。

三种伸缩机制,三种指标语义

机制驱动指标缩容到零典型用法
Knative KPA(KServe 默认)每副本目标并发请求数原生支持KServe InferenceService 开箱即用
HPACPU/内存/自定义指标不支持(最少 1 副本)传统无状态服务
KEDA事件源:队列长度、Kafka 积压、Prometheus 指标原生支持(0 到 N)队列缓冲型推理、批处理
表 1: KPA / HPA / KEDA 对比

KServe 的 GPU 样例展示了最短路径:一个带 nvidia.com/gpu 资源请求的 InferenceService,加一行注解就能获得并发驱动的伸缩:

apiVersion: serving.kserve.io/v1beta1
kind: InferenceService
metadata:
  name: flowers-sample-gpu
  annotations:
    autoscaling.knative.dev/target: "1"   # 每副本目标并发数
spec:
  predictor:
    # ...模型与运行时配置...
    resources:
      limits:
        nvidia.com/gpu: 1

为什么 GPU 推理的伸缩指标要用“排队”而不是“利用率”

HPA 的默认直觉是 CPU 利用率,但这对 GPU 推理几乎必然失真,原因在本书前文已经埋好:解码型负载受带宽屋顶线约束,算力占用低是常态(见Roofline 模型);GPU 利用率只表示“有 kernel 在跑”,与请求排队无关(见GPU 指标语义)。按它伸缩,要么纹丝不动,要么追着噪声抖。

推理负载的真实压力信号是排队:请求在引擎的等待队列里积压,SLO(TTFT/尾延迟)开始恶化。所以正确的指标链是“队列深度或并发数驱动伸缩,尾延迟做验收”,这与vLLM一章“交付 SLO 下的 Goodput 而不是峰值吞吐”的结论一脉相承。KEDA 的价值在这里:它把“队列长度”这类事件源做成一等公民,并且支持从零副本起步。

缩容到零:四层冷启动账

缩容到零省的是真金白银(空闲时 GPU 节点可以整个还给云),代价是把冷启动从“镜像拉取”放大成一整条链路。以社区项目 gpu-autoscale-inference 的实测分解为例(Qwen2.5-1.5B,T4 节点,GKE):

机制触发条件典型耗时
PodKEDA ScaledObject 驱动 HPARedis 队列深度超过阈值约 30 秒(轮询周期)
节点Cluster Autoscaler出现带 nvidia.com/gpu 请求的 Pending Pod约 2 分钟(GPU 虚机启动)
镜像GKE Secondary Boot Disk 等节点启动即挂载预缓存盘接近 0
模型权重常驻 PVCPod 启动加载权重入显存约 128 秒(1.5B 模型)
表 2: 缩容到零的四层冷启动(社区项目实测分解)

四层加起来,一次完全冷的请求要等约三四分钟。这个项目给出的工程答案值得原样学习:

  1. 队列缓冲:请求先进 Redis 队列再由 worker 消费,冷启动期间队列吸收突发,客户端无感、无重试;
  2. 权重常驻:模型权重放 PVC,Pod 反复重建不重复下载;
  3. 镜像预热:容器层预缓存到节点启动盘;
  4. 全链路遥测:DCGM(GPU 利用率/功耗/显存)、vLLM 指标(TTFT、KV cache)、kube-state-metrics(副本与节点生命周期)进同一块 Grafana 面板,伸缩行为本身可验收。

一句话总结:缩容到零不是省掉待机费的黑魔法,而是用队列缓冲吸收冷启动的延迟,前提是你算清了每一层的账。

动手实验:零成本跑通 KEDA 的 0 → 1 → 0

实验目标

在一台无 GPU 的 minikube 上验证 KEDA 的核心机制:队列深度驱动 Deployment 在 0 与 1 副本之间自动伸缩。机制验证不需要 GPU,GPU 场景只是把同样的 ScaledObject 换个目标。

前置条件与成本预估

  • minikube 集群(上手篇的集群删掉 GPU 参数重建即可,或任何 2C/4G 集群)。
  • 成本:零(本地集群)。

操作步骤

# 步骤 1:安装 KEDA
helm repo add kedacore https://kedacore.github.io/charts
helm install keda kedacore/keda --namespace keda --create-namespace

# 步骤 2:部署一个 Redis 作为队列,一个待伸缩的 worker(睡眠占位即可)
kubectl create deployment queue --image=redis:7 --port=6379
kubectl expose deployment queue --port=6379
kubectl create deployment worker --image=redis:7 -- sleep infinity

# 步骤 3:声明伸缩规则:队列长度超过 5 则扩到 1,空闲则缩回 0
cat <<'EOF' | kubectl apply -f -
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: worker-scaler
spec:
  scaleTargetRef:
    name: worker
  minReplicaCount: 0
  maxReplicaCount: 1
  cooldownPeriod: 60
  triggers:
  - type: redis
    metadata:
      address: queue:6379
      listName: inference_queue
      listLength: "5"
EOF

# 步骤 4:观察周期一:空闲时副本被缩到 0
kubectl get deployment worker -w   # 一分钟后 REPLICAS 变为 0

# 步骤 5:制造负载:向队列压入 6 个任务,观察 0 → 1
kubectl exec deployment/queue -- redis-cli LPUSH inference_queue a b c d e f
kubectl get deployment worker -w   # 副本变为 1

# 步骤 6:清空队列,观察冷却后 1 → 0
kubectl exec deployment/queue -- redis-cli DEL inference_queue

验证清单

  • 步骤 4:空闲状态下 worker 副本数归零(缩容到零生效)
  • 步骤 5:队列超过阈值后约 30 秒内副本从 0 变 1(事件驱动激活)
  • 步骤 6:清空队列后经过 cooldownPeriod 副本回到 0
  • kubectl get hpa 能看到 KEDA 托管的 HPA 对象及其当前指标
  • 把“约 30 秒的激活延迟”记下来:这就是四层冷启动账的第一层,GPU 场景还要再叠加节点与模型加载

原理速览

KEDA 的架构是“激活器 + 标准组件”:它周期性轮询事件源(这里是 Redis 的 LLEN),队列为空时把 Deployment 缩到 0 并停止 HPA 评估;一旦超过阈值,先把副本拉到 1,再把控制权交给底层 HPA 按指标继续扩。所以你既得到了缩容到零,又保留了 HPA 生态的全部语义。GPU 推理场景里,把 worker 换成带 nvidia.com/gpu 请求的 vLLM Deployment、把队列换成真实的请求缓冲,就是社区项目的完整形态。

清理与止损

kubectl delete scaledobject worker-scaler
kubectl delete deployment worker queue
helm uninstall keda -n keda && kubectl delete ns keda
minikube delete

节点层伸缩与治理的衔接

副本伸缩解决 Pod 层,节点层由 Cluster Autoscaler 接管:Pending 的 GPU Pod 会触发 GPU 节点池扩容,空闲后节点回收,这就是“空闲时零成本”的完整闭环。两件事需要与治理体系对齐:

  • 冷启动预算要进 SLO:缩容到零的服务,P99 延迟必须包含激活时间,或者用队列把同步等待变成异步。
  • 在线弹性与离线队列互补:KEDA 管在线推理的潮汐,Kueue管离线训练的排队,两者共享同一个 GPU 池时,配额与优先级的划分要在控制面统一设计,避免弹性推理把训练饿死(或反之)。

总结

GPU 推理的自动伸缩,选指标先于选工具:并发与队列深度才是推理负载的真实压力信号,CPU 利用率会把人带偏;缩容到零的价值与代价都来自冷启动,四层账(激活、节点、镜像、模型)算清了才能设计缓冲与 SLO。至此推理侧从物理层、引擎、生产栈到弹性全部闭环,下一章PyTorch转回训练侧:另一套完全不同的调度语义。

参考资料

创建于 2026/09/15 更新于 2026/09/15 2492 字 阅读约 5 分钟