推理服务 SLA 与弹性扩缩容:从 P99 延迟说起
很多团队上线推理服务后都遇到同一个困惑:平均延迟 200ms,用户却总抱怨"卡"。答案是尾部延迟——决定体验的是最慢的那 1% 请求。本文从 P99 说起,聊推理服务 SLA 与弹性扩缩容的生产实践。
先把指标定对
对话式 LLM 服务的延迟要拆成两段看:TTFT(首 token 时间)决定"反应快不快",decode 吞吐决定"打得快不快"。对交互式应用,业界常见的 SLO 是 P99 TTFT 控制在数百毫秒以内;对异步批处理任务,可以放宽到秒级换取更高吞吐。这里有个经典陷阱:平均延迟掩盖问题。一个把平均响应时间改善四成、却把 P99 恶化两成的版本,对最差的那批用户就是明确的回归,上线前必须对比优化前后的分位数。
| 核心指标 | 含义 | 实践建议 |
|---|---|---|
| P99 TTFT | 首 token 时间的 99 分位 | 主 SLO 指标,超标持续 60 秒即告警 |
| 每池队列深度 | 等待 prefill 或 decode 槽位的请求数 | 容量压力最早的信号,作为扩容主信号 |
| 每卡 tokens 每秒 | 吞吐效率 | 请求量稳定但该值下滑,警惕 KV cache 抖动 |
| KV cache 命中率 | 前缀缓存复用比例 | 据公开经验,低于三成需检查缓存配置 |
| 每千 token 成本 | 财务 SLO | 按模型版本跟踪,捕捉上线后的成本回归 |
为什么不能按 GPU 利用率扩容
传统 HTTP 服务按 CPU 利用率扩容的经验,在 GPU 推理上会系统性失效,原因有三:第一,GPU 利用率是滞后指标,等利用率冲高时队列早已堆积,尤其 decode 阶段是访存瓶颈,利用率与压力并不同步;第二,冷启动太慢,GPU 推理副本从调度到可服务要 30 至 90 秒,加载 70B 级模型权重可能要十几分钟,而流量尖峰 60 秒就冲过去了——扩容动作永远追不上尖峰;第三,张量并行组必须整体伸缩,2 卡 TP 组加 1 卡毫无意义,扩缩容要以完整模型实例为单位。
正确的主信号是队列深度:在请求开始排队之前扩容,而不是之后。再叠加预测性伸缩——推理流量有极强的日周期与周周期规律,提前 15 分钟预热远比事后追赶便宜。
分池调度与冷启动治理
生产级方案通常把 prefill 与 decode 拆成独立资源池分别伸缩:流量突发时先扩 prefill 池(TTFT 恶化的元凶),decode 吞吐下滑时再扩 decode 池,混在一个池里互相挤兑是最常见的过度供给原因。入口层还要做准入控制:设置最大队列深度,超限返回 429 加 Retry-After 头,而不是让队列无限增长拖垮所有请求;再按租户做令牌桶限流,防止单个大客户的突发流量劣化全平台的 P99。
冷启动治理三板斧:保持至少 1 个热副本避免缩容到零;对必须缩到零的场景,用每 30 至 60 秒一次的保活探测或启动后的预热接口提前加载权重与 KV cache;LoRA 适配器在启动时预加载而非首次请求时加载。热池的代价据公开经验约为峰值算力的两到三成,这通常比尖峰时刻的 SLA 违约便宜得多。引擎侧选用基于 PagedAttention 与连续批处理的方案(如 vLLM),据公开评测可带来 2 倍以上的 P99 改善,是杠杆率最高的单项优化。
写在最后
三条落地建议:第一,SLA 按 TTFT 与 decode 分段定义,全部用分位数告警,并把每千 token 成本与产品指标放在同一张大盘上;第二,扩缩容以队列深度为主信号、预测性伸缩为辅,按 prefill 与 decode 分池管理;第三,每次变更前后跑同一并发水位下的负载测试,验证 P99 而非平均值。推理服务的竞争力,最终体现在最慢的那 1% 请求上。