本节摘要:「吞吐」「首 token 延迟」「每 token 间隔」这些词在讨论里经常被混用,导致优化目标错位。本节给出四个核心指标的严格定义与相互制约关系,并引出"显存天花板"——并发上限由 KV Cache 显存决定。这是全册的度量衡:第 8 章的压测与监控全部建立在本节的概念上。
「KV Cache 把显存吃光了」——你会在 vLLM 的日志里反复见到这个词组。它同时压着本节的两个主题:延迟和并发上限都与显存中的这部分状态直接挂钩。上一节我们把成本拆到了 prefill 与 decode 两段,本节把"慢"和"扛不住"翻译成可以度量、可以写进验收标准的指标语言。没有这套语言,后续所有优化都无从谈起——你甚至说不清一个改动到底是让服务变好了还是变差了。
吞吐(Throughput):整个服务每秒产出的 token 总数。注意它是"总量"——八条请求同时生成时,吞吐是八条加在一起。服务方的成本优化看它,因为它直接决定一张卡单位时间能赚多少 token。
单请求延迟(Latency):一条请求从发出到收完全部输出的时间。用户对"我这一个请求等多久"的体感看它。吞吐和延迟经常背道而驰:把批凑得越大吞吐越高,但批里每条请求的单步耗时被拉长,延迟变差。
TTFT(Time To First Token,首 token 延迟):从请求到达到第一个 token 吐出的间隔。交互式产品的体感关键——用户可以接受打字机式的流式输出,但对"点了发送之后屏幕干瞪着"零容忍。TTFT 主要由排队时间与 prefill 耗时构成。
TBT / TPOT(Time Between Tokens,每 token 间隔):流式输出阶段相邻 token 的平均间隔,决定"打字机"的滚动速度。它主要由 decode 单步耗时决定,批越大它越差。
| 指标 | 谁在关心 | 由什么决定 | 被什么伤害 |
|---|---|---|---|
| 吞吐 | 服务方/成本 | 批大小 × decode 效率 | 显存不足限制并发、批凑不起来 |
| 单请求延迟 | 用户(总体感) | TTFT + 输出长度 × TBT | 排队、大批稀释 |
| TTFT | 交互产品 | 排队时间 + prefill 耗时 | 请求堆积、长输入 |
| TBT | 交互产品 | decode 单步耗时 | 批过大、序列过长 |
这张表里最重要的一行关系是:批大小同时正向影响吞吐、负向影响 TBT。推理服务调优的核心矛盾,就是把批开到多大而不把体验指标打穿——第 8 章的调优闭环本质上是在这个跷跷板上找平衡点。
服务能同时运行多少条请求,不取决于 CPU 有多快,而取决于显存里装得下多少条请求的 KV Cache。模型权重是固定开销(8B 模型 FP16 约 16 GB,70B 模型约 140 GB),剩下的显存才是给 KV Cache 的活动空间。粗略地说:
最大并发数 ≈(总显存 − 权重 − 激活与缓冲)÷(单请求 KV Cache 字节数)
单请求 KV Cache 有一个全册反复使用的公式,请记住它:
KV Cache 显存 = 2 × 层数 × KV 头数 × head_dim × 精度字节数 × 序列长度 × 批内请求数 其中"2"对应 Key 与 Value 两份张量。 以 Llama-3-8B(32 层、32 头、head_dim 128、FP16 即 2 字节)为例, 单条请求每 1024 个 token 的 KV Cache: 2 × 32 × 32 × 128 × 2 × 1024 ≈ 0.5 GB 一张 80 GB 的卡,权重占约 16 GB,若给 KV Cache 留 50 GB, 理论并发 ≈ 50 ÷ 0.5 ≈ 100 条(按每条 1024 token 上下文计)。
用一小段脚本把这笔账算清楚,后续调参时可以直接改系数复用:
def kv_cache_gb(layers, kv_heads, head_dim, seq_len, batch, bytes_per=2): """估算 KV Cache 显存占用(GB)。bytes_per: FP16=2, FP8/BF8 类=1""" total_bytes = 2 * layers * kv_heads * head_dim * bytes_per * seq_len * batch return total_bytes / 1024 ** 3 # Llama-3-8B:32 层、32 头、head_dim 128,上下文 4096,并发 32 条 print(kv_cache_gb(32, 32, 128, seq_len=4096, batch=32)) # ≈ 64.0 GB # 同模型换 GQA 后 KV 头数为 8: print(kv_cache_gb(32, 8, 128, seq_len=4096, batch=32)) # ≈ 16.0 GB
留意第二组数字:同一个模型若用了分组查询注意力(GQA,KV 头数远少于注意力头数),KV Cache 直接缩到原先的几倍之一。这就是为什么新模型纷纷采用 GQA/MQA——架构设计本身就在为显存天花板松绑。
但这里有一个刺眼的事实:按上面的算法,传统推理框架的真实并发往往远够不到理论值。原因不是公式错了,而是分母里的显存没有被充分利用——传统框架按"最长可能序列"给每条请求整块预留空间,大量显存被从未用到的预留量锁死。这笔糊涂账,正是第 2 章的主角。
讨论优化效果时要先声明口径,否则数字没有意义:
单请求口径:固定输入输出长度,测单发的 TTFT 与 TBT。它反映模型与硬件的物理下限,几乎不随推理框架变化——换 vLLM 不会让单条请求的 decode 突然快一倍,因为权重搬运量没变。
服务口径:按真实流量的输入输出长度分布发压,测整体吞吐与分位延迟(P50/P99)。框架优化的收益几乎全部体现在这个口径上:并发能力上去了,同等硬件单位时间产出的 token 翻数倍,是"把闲置资源用起来"的结果。
关键直觉:vLLM 类系统的优化,本质不是把单步算得更快,而是让更多请求共享同一段搬运时间、把同一块显存伺候更多条请求。理解了这一点,你就能预判哪些优化有效:任何"提高共享度"的手段(批处理、前缀共享、多 LoRA 共享权重)都有大收益;任何"只加快单步"的手段收益则受带宽物理上限约束。
曾有个团队用"单发 TBT 20 毫秒"推算"每卡每秒 50 token、一百张卡扛五千万日请求",上线即被打穿。问题出在两处:一是真实流量长度分布远比测试用例恶劣,长尾请求占掉数倍显存;二是没算排队——并发上限受显存天花板约束,流量一超,TTFT 不是线性劣化而是雪崩式劣化。容量规划必须用服务口径压测到拐点,第 8.3 节会给出完整方法。
下一节我们把问题清单收拢,对照看看 vLLM 给每个问题准备的哪把钥匙——先把全景图立起来,再逐章深入。