传统 Web 服务的延迟是一个数:请求进去,响应出来,中间的时间。LLM 服务不是——它流式地产出结果,用户在第一个字出现时就已经开始体验了。把整个过程压成一个「响应时间」,会同时掩盖两类完全不同的问题,并且指向完全相反的优化手段。
所以必须拆成三个指标,它们对应推理的两个阶段(见 1.1):
| 指标 | 含义 | 由什么决定 | 用户感受 |
|---|---|---|---|
| TTFT time to first token | 从发出请求到收到第一个 token | prefill 阶段:输入长度(计算量约 O(n²))、排队时间、缓存命中 | 「它反应快不快」 ——等待感的全部来源 |
| TPOT time per output token | 后续每个 token 的平均间隔 (也称 ITL,inter-token latency) | decode 阶段:模型大小、显存带宽、batch 大小 | 「它打字快不快」 ——流畅度 |
| E2E latency | 整个请求的总时长 | ≈ TTFT + TPOT × (输出 token 数 − 1) | 只有非流式场景才直接可感 |
这个分解的实用价值在于:它把「慢」这个模糊抱怨翻译成了可定位的工程问题。用户说「慢」,如果是 TTFT 高,你该去看输入长度、排队和缓存;如果是 TPOT 高,你该去看模型大小、batch 配置和硬件带宽——两条路毫无交集,走错方向就是几周的浪费。
不同产品形态对三个指标的敏感度差异极大,用同一套阈值管理所有服务是常见的错误:
流式对话:TTFT 决定「有没有反应」,是第一优先级;TPOT 只需快过用户的阅读速度即可,再快用户也感知不到——这是一条重要的止损线,说明 TPOT 的优化存在收益归零点。举例:若目标是每秒吐 20 个 token,TPOT 只需 ≤ 50ms,做到 20ms 并不会让体验更好,但可能让你的 batch 配置付出吞吐代价。
语音对话:TTFT 的要求严苛得多,因为人类对话中的沉默极难忍受;这类场景常常值得为压低 TTFT 牺牲相当多的吞吐。
离线批处理:TTFT 和 TPOT 都无所谓,唯一指标是吞吐(以及由此决定的成本)。此时应该把 batch 开到显存允许的上限。
Agent 内部调用:单步的 TTFT 会被乘以循环轮数放大(见 4.2)。一个 8 轮的 agent,每轮多 300ms,用户就多等 2.4 秒——所以 agent 场景对 TTFT 的敏感度远高于它看起来的样子。
这是 Part 2 全部内容的地基。增大 batch 会摊薄权重读取成本,吞吐上升;但批内每一步要处理的数据更多,且每条请求都要等整批一起走完这一步,单请求延迟上升。反过来,batch=1 延迟最优,但 GPU 大部分算力在空转,成本高到不可接受。
还有一个更隐蔽的取舍在 batch 内部:新请求的 prefill 与在途请求的 decode 争抢同一次迭代(1.3 已提到)。优先 prefill,新用户的 TTFT 好看、老用户的 TPOT 卡顿;优先 decode 则相反。TTFT 与 TPOT 在调度层面直接互为代价——这正是下面案例要解决的问题。
用 goodput 而不是 throughput 做决策。「每秒 5000 token」听起来很好,但如果其中一半请求的 TTFT 已超出 SLA,这些吞吐是无效的。goodput(有效吞吐)= 单位时间内满足 SLA 的请求数。用它做容量决策,可以避免「指标很好但用户在骂」的经典困境。
Georgia Tech 与 Microsoft Research India 的团队直指上文那个调度冲突:要提高吞吐就得增大 batch,但新请求的 prefill 一旦入批就会停住(stall)正在进行的 decode,导致 TPOT 抖动。
他们的方案是 chunked-prefill + stall-free scheduling:把一个 prefill 请求切成大小接近的若干块,每次迭代只处理一块,并与在途的 decode 拼在同一批里执行——新请求可以加入批次而不暂停任何正在进行的解码。这样既拿到了大 batch 的吞吐,又不让 TPOT 出现尖刺。
条件:三组数字分别对应不同模型与硬件配置,基线均为 vLLM,衡量的是在满足延迟约束前提下的服务容量——这正是上文 goodput 的思路,而不是裸吞吐。
这个案例的方法论价值大于数字本身:当你面对一个「A 和 B 不可兼得」的取舍时,先别急着在曲线上选点,去检查这条曲线是不是可以被整体推动。此处的关键洞察是——prefill 之所以阻塞 decode,是因为它被当成一个不可分割的原子操作;一旦允许它被切分,冲突的前提就消失了。
压测工具:vLLM 自带 vllm bench serve 命令,可指定请求到达率与数据集,直接输出 TTFT / TPOT / E2E 的分位数;第三方工具可选 NVIDIA 的 AIPerf(genai-perf 的后继,后者已宣布弃用)等,提供类似能力;早期常用的 llmperf 仓库已于 2025-12 归档。关键是选一个能报分位数而不只报均值的工具。若后续轮次命中前缀缓存,TTFT 可能偏低:重复压测前清理缓存,或记录并解释命中率。
务必用真实长度分布:用等长请求压测会系统性地高估性能——连续批处理与分页的收益、prefill 阻塞的代价,全都取决于长度方差。把线上真实的输入/输出长度分布采样出来做压测输入。
务必测排队:单并发下测出的 TTFT 只包含计算时间。把并发压到目标负载的 1×、1.5×、2× 分别测,观察 TTFT 的 P99 在哪个点开始非线性上升——那就是你的容量拐点(2.4 会用到这个数)。
线上监控口径:TTFT、TPOT 至少各留 P50/P95/P99,并按输入长度分桶(如 <1K、1–8K、>8K)。不分桶的话,长请求的正常慢会污染整体分位数,掩盖真正的异常。
商业 API 场景:你无法控制对方的 batch 配置,但同样要测——把 TTFT/TPOT 作为供应商的 SLA 监控项,它们的波动是 6.5 降级策略的触发信号。
当前:E2E ≈ 0.8 + 0.04 × 499 ≈ 0.8 + 20.0 ≈ 20.8 秒。
新方案:E2E ≈ 1.1 + 0.025 × 499 ≈ 1.1 + 12.5 ≈ 13.6 秒。总时长减少 35%。
但这道题的陷阱是「值得吗」不能只看 E2E。需要追问三件事:
① 用户真的会读完 500 个 token 吗?如果多数用户扫两眼就走,那么 TTFT 恶化 300ms 是所有人都要付的代价,而 TPOT 的改善只有读完的人才享受得到。这种情况下新方案可能是净负面。
② TPOT 40ms 是否已经快过阅读速度?40ms/token 约合每秒 25 个 token,通常已快于人的阅读速度——那么把它降到 25ms 属于用户感知不到的优化,收益接近于零。
③ 模型小一半,质量退化了多少?这才是真正的成本,必须用 eval 集量化(6.4)。
结论:多半不值得。这道题想训练的判断是——E2E 是一个会误导人的聚合指标,优化它之前先问用户实际感知的是哪一段。
因为吞吐只数「产出了多少 token」,而 goodput 只数「满足 SLA 的请求」。增大 batch,每一步处理的数据变多,单请求的延迟随之上升;当延迟越过 SLA 阈值,这些请求产出的 token 在业务上等于零——吞吐曲线还在涨,goodput 已经开始掉头向下。
实践含义:容量规划应该以 goodput 峰值对应的 batch 配置为准,而不是吞吐峰值。两者往往不在同一点上,且吞吐峰值总是在更大的 batch 处——只盯吞吐会系统性地把系统配置到 SLA 违约区。
TPOT 平稳说明解码本身没问题——模型、硬件、batch 配置都健康。问题被锁定在 TTFT 独有的那几个成分上:排队或超长输入。
排队(更常见):负载已越过容量拐点,请求在队列里等待。特征是 P99 的恶化与 QPS 高峰在时间上重合,且和请求内容无关。
超长输入:少数请求输入极长,prefill 计算量按 O(n²) 增长。特征是慢请求与长输入一一对应,与 QPS 无关。
怎么区分:把 TTFT 按输入长度分桶。若各桶内的 P99 都高 → 排队问题,需扩容或限流;若只有长输入桶高 → 计算问题,需 chunked prefill、输入长度上限或长短请求分流。
反例警示:如果此时有人提议「换个更小的模型」,那是在治 TPOT 的病——方向完全错了,而且会白白损失质量。
直接报一个数字是不合格的。合格的回答要包含四个要素:
① 拆指标:报 TTFT 和 TPOT,而不是笼统的「延迟」,并说明是否包含排队。
② 报分位数:P50 和 P99 至少各一个。均值在长尾分布上几乎没有信息量。
③ 给条件:什么模型、什么输入长度分布、什么并发水平下测的。同一套系统在 10 QPS 和 100 QPS 下是两个完全不同的数字。
④ 关联 SLA:这个数字对应的 goodput 是多少,即在这个延迟目标下系统能承载多少负载。
一个好的回答样例:「TTFT P50 约 400ms、P99 约 1.2s,TPOT P99 约 45ms;在输入 P95 为 4K token、并发 60 的负载下测得;SLA 是 TTFT P99 < 2s,当前 goodput 余量约 40%。」——这个回答同时展示了指标体系、测量素养和容量意识。