第 10 章 推理请求生命周期与性能指标¶
开场问题:同一张卡、同一个模型,单请求只能到 40 token/s,并发 64 时总吞吐却能到 2000。多出来的性能,单请求时去哪了?
10.1 一次请求的完整路径¶
请求进来后依次经过:网关鉴权与限流 → 调度队列 → tokenize → Prefill(把整段 prompt 过一遍模型,写下全部 KV,产出第一个 token)→ Decode 循环(每次输入上一个 token,过一遍全部层,产出下一个)→ detokenize → 流式返回。
性能问题必须落在这条时间线的某一段上。"模型慢"不是诊断,"p99 的 TTFT 里有 300 ms 花在队列上"才是。
10.2 四个必须分开看的指标¶
- TTFT(首 token 延迟):排队时间 + Prefill 时间。用户感知为"点了发送之后多久开始有反应"。
- TPOT(每 token 时间):首 token 之后的平均吐字间隔,由 Decode 决定。
- ITL(token 间隔分布):比平均 TPOT 有用得多,因为卡顿是分布问题——平均 30 ms 但偶尔卡 800 ms,体验很差。
- 吞吐:单位时间处理的 token 数,要区分输入 token 和输出 token。
不同业务的目标函数完全不同:离线批处理只看吞吐;聊天要同时守住 TTFT 和 ITL;Agent 工具调用对 TTFT 极其敏感(一轮对话里可能有十几次模型调用)。
10.3 回到第一册的三个公式¶
开场那个反直觉现象,用第一册第 3 章的结论就能解释。Decode 阶段每生成一个 token,都要把全部模型权重从显存读一遍——8B 模型 bf16 就是 16 GB。一张带宽 2 TB/s 的卡,理论上每秒最多读 125 次权重,也就是 单请求最多 125 token/s,与算力无关。
但这次权重读取是可以被整个 batch 共享的:batch 里有 64 个请求,读一次权重能同时为 64 个请求各推进一个 token。所以 Decode 的吞吐几乎随 batch 线性增长,直到 batch 大到计算量追上访存量,工作点从带宽受限切换到算力受限。
这个切换点是推理系统一切设计的原点:Decode 天然在浪费算力,所以后面所有技术要么把浪费的算力换成别的东西(投机解码),要么想办法让 batch 更大(分页、调度)。
10.4 Prefill 和 Decode 是两种不同的负载¶
Prefill 一次处理几千个 token,是标准的大矩阵乘,算力受限,很容易吃满 GPU。Decode 每步只处理 batch 个 token,矩阵乘退化成瘦长的矩阵向量乘,带宽受限。
两者放在同一个 GPU 上会互相伤害:一个 4000 token 的 Prefill 要占用 GPU 几百毫秒,这期间所有正在 Decode 的请求全部卡住。第 12 章的分块 Prefill 和第 13 章的 PD 分离,都是在处理这个矛盾。
10.5 不只是 GPU 在花时间¶
小模型和短请求上,非 GPU 开销能占到三成:tokenize/detokenize、采样(尤其带 top-p 排序时)、Python 层的调度逻辑、请求序列化、以及 kernel launch 开销。
判断方法很简单:把 GPU 时间加总,和端到端时间比。差距大就说明瓶颈在 CPU 侧,这时优化 attention kernel 毫无意义。CUDA Graph 主要就是解决这类问题(第 17 章)。
10.6 怎么压测才不骗自己¶
四个常见的自欺:只测固定长度(真实负载长度分布极不均匀);闭环压测(发完一个再发下一个,永远压不出排队问题,应该用开环按到达率发);不做 warmup(首次运行包含编译和显存分配);缓存污染(重复用同一个 prompt 压测,前缀缓存全命中,数字好看但不真实)。
报告结果时必须同时给出:模型、精度、硬件、并行配置、输入输出长度分布、并发或到达率、以及 p50/p95/p99。缺一项,数字就无法比较。
核心问答¶
Q:为什么单请求的速度上限和 GPU 算力几乎无关? 因为 Decode 是带宽受限的。每个 token 都要把全部权重读一遍,速度上限约等于显存带宽除以模型大小。算力再强也没用。
Q:那为什么并发能提高总吞吐? 一次权重读取被 batch 内所有请求共享。这是推理系统最重要的经济学:batch 越大,每 token 的权重读取成本越低。
Q:用户抱怨"慢",先看哪个指标? 先问是"开始得慢"还是"吐字卡"。前者查 TTFT(排队还是 Prefill),后者查 ITL 分布(是不是被长 Prefill 打断)。
Q:为什么必须分开统计输入和输出 token? 它们走完全不同的路径、消耗完全不同的资源。输出 token 的单位成本通常比输入 token 高一个量级,计费和容量规划都要分开算。