2600 字
约 8 分钟
18
S23-X09 · 推理服务的容量与压测

推理压测和 Web 压测不是一回事。QPS 相同,输入长度从 200 token 变成 8000 token,表现能差一个数量级;并发相同,输出长度翻倍,吞吐立刻腰斩。更要命的是很多人拿"平均值"汇报,而 SLA 从来只认 P99。

这一层要产出的不是一张漂亮的曲线,是三个数字。


1. 压测必须回答的三个问题

问题 产出 用途
单副本能扛多少并发 并发上限(在 SLA 内) 决定副本数
拐点在哪 排队开始恶化的负载点 定告警阈值
扩容粒度是多少 每副本多少 QPS/多少 token/s 定副本数与成本

只回答"能扛住 100 并发"是不够的——没有 SLA 约束的容量数字没有意义。定 SLA 至少要写三项:TTFT P95、端到端 P95、成功率。


2. 负载模型:闭合还是开放

模型 行为 问题 适用
固定并发(闭合) N 个虚拟用户不停发请求 协调遗漏:服务变慢时压不上去,指标偏乐观 快速摸底
固定到达率(开放) 每秒固定发 R 个请求 更接近真实流量 正式容量测试
真实分布回放 按线上输入/输出长度分布采样 构造复杂 定 SLA 前的最终验证

协调遗漏是压测最常见的坑:固定并发下,一旦响应变慢,客户端来不及发新请求,于是负载自动下降,P99 看着很漂亮。真实流量不会因为服务慢而变少,只会堆积。

// k6:固定到达率,避免协调遗漏
export const options = {
  scenarios: {
    steady: {
      executor: 'constant-arrival-rate',
      rate: 20, timeUnit: '1s',        // 每秒 20 个新请求,与服务快慢无关
      duration: '10m',
      preAllocatedVUs: 200, maxVUs: 800,
    },
  },
};

preAllocatedVUs 不足时 k6 会丢迭代并告警,看到 "insufficient VUs" 就说明压测本身成了瓶颈。


3. 四个指标,不能只看一个

指标 定义 受什么影响 报告方式
TTFT 首 token 到达时间 prefill 计算、排队 P50/P95/P99
TPOT 后续每 token 时间 decode 带宽、批大小 P50/P95
吞吐 总 token/s(输入+输出分开记) 批处理、并行度 单副本 token/s
成功率 非 5xx、非超时、非截断 超时设置、队列 百分比 + 错误分类

四个指标互相拉扯:加大批处理能提吞吐,但 TTFT 变差;缩小批处理 TTFT 好,吞吐掉。任何只报其中一项的容量结论都不可用,必须成组报。

# 从 SSE 流里算 TTFT / TPOT(示意)
t0 = time.perf_counter(); first = None; n = 0
for chunk in parse_sse(resp):
    if first is None: first = time.perf_counter() - t0
    n += 1
tpot = (time.perf_counter() - t0 - first) / max(n - 1, 1)
print(f"TTFT={first*1000:.0f}ms TPOT={tpot*1000:.1f}ms tokens={n}")

4. 输入输出长度分布决定一切

prefill 是算力密集,decode 是显存带宽密集,两者瓶颈不同,所以长度分布必须进实验矩阵。

场景 输入 输出 瓶颈
对话 1k~4k 200~800 decode 为主
长文摘要 8k~32k 300~1k prefill 为主
代码生成 2k 1k~3k 长 decode,KV cache 压力大
分类/抽取 500 10~50 排队与调度开销占比高

至少跑三档:短-短、长-短、短-长。只测短请求会系统性高估容量,因为长输入的 prefill 会挤占 decode 的显存与算力。


5. 脚本要点:别让压测工具成为瓶颈

# 先用 vLLM 自带基准摸上限,再用自研脚本验 SLA
vllm bench serve --model /models/qwen2.5-7b-instruct \
  --num-prompts 500 --request-rate 20 \
  --input-len 1024 --output-len 256 \
  --percentile-metrics ttft,tpot,e2el --metric-percentiles 50,95,99

自研脚本必须满足:异步并发(不是线程池串行)、复用连接、真实解析 SSE、客户端超时大于最坏排队时间、记录每个请求的四个指标原始值(不是只记平均)。

客户端超时设小了,会把"排队慢"误判成"失败",成功率一栏失真;设大了,压测结束后还要等很久才能收工。按目标 SLA 的 3 倍设超时是常见折中。


6. 拐点:阶梯加压 + 恢复间隔

一段式满载压测只能得到"扛不扛得住",得不到拐点。做法是阶梯:

for rate in 5 10 15 20 25 30; do
  run_load --rate "$rate/s" --duration 5m --warmup 60s
  sleep 120                      # 让队列排空再进入下一档
  echo "done rate=$rate"
done
现象 含义 处理
吞吐随负载线性上升 未饱和 继续加压
吞吐走平、TTFT 上升 到拐点 这是容量上限
吞吐下降、队列爆 已过载 该负载点即告警阈值
加档后先慢再恢复 冷启动/缓存效应 加 --warmup 重测

拐点取吞吐走平而 TTFT 开始陡升的那一档。告警阈值设在这一档的 70% 左右,留出反应时间。


7. 从指标到副本数

单副本并发上限 C = 实测在 SLA 内的最大并发
目标 QPS = Q
单请求平均占用时间 T(秒,含排队外的处理时间)
需要副本数 = ceil(Q × T / C × 冗余系数)

冗余系数:常驻负载 1.3~1.5;有明显峰值 1.5~2.0

但还有第二个约束必须核对:KV cache 显存。并发 = 同时在跑的序列数,长上下文时显存先于算力成为瓶颈:

可用 KV 显存 = 总显存 × gpu_memory_utilization - 权重 - 激活开销
可容纳序列数 ≈ 可用 KV 显存 / (2 × layers × kv_heads × head_dim × dtype × seq_len)

两个约束取更小的那个。很多容量事故不是算力不够,是 max_num_seqs 设得比显存能撑的还大,于是频繁 preemption、重算,延迟曲线出现诡异的台阶。


8. 冷启动与预热

阶段 耗时量级 处理
加载权重 秒级到分钟级 startupProbe 放宽容忍
CUDA graph / 编译 几十秒 启动时预热,别等第一个请求
缓存冷 前几十个请求明显慢 postStart 发若干预热请求
自动扩容 分钟级 提前扩容,别等崩了再扩
startupProbe:
  httpGet: { path: /health, port: 8000 }
  periodSeconds: 10
  failureThreshold: 60            # 约 10 分钟,覆盖权重加载
lifecycle:
  postStart:
    exec:
      command: ["/bin/sh","-c","for i in 1 2 3; do curl -s -X POST localhost:8000/v1/chat/completions -d @/etc/warmup.json >/dev/null; done"]

压测必须跳过预热窗口,否则前 60 秒的冷数据会把 P99 拉高,得出"容量不够"的错误结论。


9. 长稳测试:泄漏与碎片

短时间压测看不出两类问题:显存泄漏和 KV cache 碎片。

现象 原因 观察方式
显存缓慢上涨不回落 请求对象未释放、缓存无上限 24h 压测,看 memory.used 斜率
同样的负载越来越慢 KV 块碎片化,可用块变小 看 gpu_cache_usage 与 preemption 次数
只有长上下文才复现 块管理在长序列下退化 混合长短请求一起压

长稳至少跑 6~12 小时,混合长短请求,观察斜率而不是绝对值。没有长稳数据的容量结论只能算半成品。


10. 常见坑

坑 现象 改法
固定并发压测 P99 好得不像话 改固定到达率,避免协调遗漏
只报平均延迟 上线后大面积超时 报 P95/P99
只测短请求 容量高估一个量级 三档长度组合
客户端超时过短 成功率虚低 超时 = 3 × SLA
压测机成为瓶颈 加压后指标不动 看压测机 CPU/连接数
不跳过预热 P99 被冷启动污染 压测前预热 60 秒
只跑 5 分钟 漏掉泄漏 至少 6 小时长稳
忽略 KV cache 约束 高并发下频繁 preemption 按显存取 max_num_seqs 上限

11. 排错

现象 排查方向
加压到某档吞吐骤降 显存打满触发 preemption;查 gpu_cache_usage
两组人压测结果差一倍 负载模型不同(闭合 vs 开放)或长度分布不同
服务端指标正常但客户端超时 客户端连接池/超时配置;查客户端侧耗时
长输入请求挤掉短请求 调度策略;考虑分池或分优先级
扩容后 P99 没改善 瓶颈在网关或排队层,不在推理副本
成功率 99% 但仍被投诉 截断(finish_reason=length)未计入失败

12. 边界

场景 更适合的做法
离线批处理 只测吞吐,不测 TTFT
极低 QPS 内部工具 不做容量测试,靠监控兜底
流量稳定且远低于容量 定期复测即可,不必找拐点
模型/量化版本刚换 必须重测,历史容量数据作废
多租户共享同一副本 按租户加权压测,别用单一租户

13. 自测

  1. 固定并发压测为什么会系统性高估容量?
  2. TTFT 和 TPOT 分别由哪种计算阶段主导?
  3. 拐点用什么信号判定,而不是用"开始报错"判定?
  4. 容量公式算出的副本数为什么还要和 KV cache 约束取小?
  5. 压测客户端超时设得过短会污染哪个指标?
  6. 6 小时长稳能发现哪两类短测发现不了的问题?
S23-X09 · 推理服务的容量与压测
https://www.clxhxhhr.top/posts/5943/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。