voyy.

理解 LLM 推理性能:Latency、Throughput 与 Goodput

作者 voyy

讨论 LLM Serving 性能时,“快”至少有三层含义:用户多久看到第一个 token,后续 token 生成是否流畅;系统单位时间能够完成多少工作;以及在延迟目标下,有多少请求真正达标。本文从一次请求的 Prefill 和 Decode 出发,依次介绍 TTFT、TPOT、ITL、E2E Latency、Throughput、SLO 与 Goodput,说明为什么峰值吞吐并不等于良好的在线服务。

本文只讨论 LLM Serving 的延迟、吞吐与服务水平,不涉及回答质量、安全性和推理成本。

1. 从一次 LLM 请求开始:Prefill 和 Decode

用户发起一次 LLM 请求后,模型并不是直接“连续写出”完整回答,而是大致经历两个阶段:先理解输入,再逐步生成输出。

1.1 Prefill:处理输入

在 Prefill 阶段,模型会处理用户输入的 Prompt,并为其中的 token 计算和保存中间结果。这些结果会被存储在 KV Cache 中,供后续生成使用。

Prompt 越长,模型需要处理的输入内容越多,准备阶段通常也越耗时。这个阶段可以理解为模型先“读完题目并建立上下文”。

1.2 Decode:逐步生成

完成输入处理后,模型进入 Decode 阶段。模型每次根据已有上下文生成一个新的 token,再把这个 token 加入上下文,继续生成下一个 token:

y1y2y3y_1 \rightarrow y_2 \rightarrow y_3 \rightarrow \cdots

由于模型需要根据前面的内容逐步生成,Decode 通常不能像处理输入那样大规模并行。KV Cache 可以避免模型反复计算已经处理过的上下文,但模型仍然需要不断读取缓存并生成新的 token。

这个阶段可以理解为模型“一个字一个字地写答案”。输出越长,Decode 持续的时间通常也越长。

因此,一次请求可以简单表示为:

请求到达Prefill:处理输入Decode:生成输出请求完成\text{请求到达} \rightarrow \text{Prefill:处理输入} \rightarrow \text{Decode:生成输出} \rightarrow \text{请求完成}

Prefill 和 Decode 的计算特征不同:前者更像是一次性处理大量输入,后者更像是反复进行小步计算。正是这种差异,使 LLM Serving 需要同时考虑用户等待时间和系统整体吞吐能力。后文将分别介绍用于衡量这些表现的具体指标。

2. Latency 与 Throughput

理解 Prefill 和 Decode 之后,我们就可以开始讨论一个 LLM 推理系统到底“快不快”。这里存在两个不同的观察视角:

  1. 站在用户的角度,我们关心一次请求需要等待多久,这对应 Latency
  2. 站在系统的角度,我们关心单位时间内能够完成多少工作,这对应 Throughput

Latency 和 Throughput 并不是同一个问题:一个系统可以让少量用户获得很低的延迟,也可以通过批处理同时服务大量用户、获得很高的聚合吞吐,但很难在所有负载下同时把两者做到最好。

2.1 Latency:一个请求需要等多久?

Latency 描述单个请求的响应速度,直接影响聊天、代码补全等交互式应用的用户体验。对于流式生成请求,常见的延迟指标包括:

  • TTFT(Time To First Token):从计时起点到收到第一个输出 token 的时间。它主要受到请求排队、调度、Prefill 计算和首 token 采样等因素影响。TTFT 越低,用户越快看到模型开始回复。
  • ITL(Inter-Token Latency):相邻两个输出 token 之间的时间间隔。ITL 描述流式生成过程中每一次停顿的长短;ITL 越低、波动越小,输出通常越流畅。
  • TPOT(Time Per Output Token):第一个 token 返回后,平均生成一个后续 token 所需的时间。它可以理解为一次请求在 Decode 阶段的平均 ITL。
  • E2E Latency(End-to-End Latency):从计时起点到完整响应生成结束所经历的总时间。

这里的“计时起点”需要在 benchmark 中明确说明。客户端口径通常从发出请求开始计时,包含网络和协议开销;服务端口径则可能从服务收到请求开始计时,更侧重 Serving 系统本身。只有测量边界一致,延迟数字才适合直接比较。

下图展示了一次流式生成请求中各项延迟指标的测量范围:

LLM 请求中 TTFT、ITL、TPOT 和 E2E Latency 的时间线

图:TTFT 描述回答何时开始,ITL 和 TPOT 描述回答过程中的生成速度,E2E Latency 描述完整请求最终耗时。

假设一次请求共生成了 NN 个输出 token,那么在忽略少量测量误差的情况下,可以近似写成:

E2E LatencyTTFT+(N1)×TPOT\text{E2E Latency} \approx \text{TTFT} + (N-1)\times\text{TPOT}

这个关系说明:对于短回答,TTFT 在总体体验中占比更高;对于长回答,持续生成阶段的 TPOT 会逐渐成为 E2E Latency 的主要组成部分。

2.2 Throughput:整个系统能处理多少工作?

如果说 Latency 关注的是一个请求有多快,那么 Throughput 衡量的是推理系统在单位时间内能够完成多少工作,反映系统整体的服务能力。

Request Throughput

Request Throughput 衡量系统单位时间内完成的请求数量,通常以 requests/srequests/s 表示,也常写作 RPS(Requests per Second)

Request Throughput=完成的请求数统计时长\text{Request Throughput} = \frac{\text{完成的请求数}}{\text{统计时长}}

RPS 可以反映系统处理并发请求的能力,但不能体现每个请求包含的实际工作量。例如,生成一句简短问候与生成一篇长文,对模型造成的计算负载显然不同。因此,当输入长度、输出长度或流量模式不一致时,直接比较 RPS 很容易产生误导。

影响 RPS 的因素包括:

  • Prompt Length 和 Generation Length
  • 模型规模、精度与硬件配置
  • Batch Size、并发度和请求到达模式
  • Prefix Cache、Speculative Decoding 等优化方式
  • 推理引擎的调度与内存管理能力

Token Throughput

Token Throughput 衡量系统单位时间内处理或生成的 token 数量,通常以 tokens/stokens/s 表示,也常写作 TPS(Tokens per Second)。根据统计对象不同,可以进一步分为:

  • Input Token Throughput:系统单位时间内实际处理的输入 token 数。
  • Output Token Throughput:系统单位时间内生成的输出 token 数。
  • Total Token Throughput:输入与输出 token 的合计吞吐量。

在 LLM 性能讨论中,还需要特别区分单用户生成速度系统聚合吞吐量

  • Per-user Output Token Rate 描述单个请求每秒收到多少输出 token,在稳定生成阶段近似为 1/TPOT1/\text{TPOT}
  • Aggregate Output Token Throughput 描述所有并发请求每秒生成的输出 token 总数。

提高并发度可能让 Aggregate TPS 上升,却同时让单个用户的 TPOT 变差。因此,“系统每秒生成多少 token”和“一个用户看到文字出现得多快”是两个不同的问题。

Input TPS 与 Output TPS 对不同 workload 的意义也不同:

  • 对于长文档摘要等长输入、相对短输出的任务,Prefill 占比更大,因此 Input TPS 更值得关注。
  • 对于短 Prompt、长输出的聊天或内容生成任务,Decode 占比更大,因此 Output TPS 和单用户 TPOT 更关键。

改变输入和输出长度会改变 Prefill 与 Decode 的工作占比,所以不同长度分布下测得的 TPS 不能直接比较。阅读 benchmark 时,至少应确认 TPS 指的是 Input、Output 还是 Total TPS,以及它是单请求速度还是系统聚合吞吐。

随着 Batch Size 和并发请求数增加,系统聚合 TPS 通常会先上升,直到计算能力、显存容量或内存带宽达到饱和。超过饱和点后,继续增加请求不一定能提高实际吞吐量,反而可能导致队列增长、延迟恶化,甚至出现超时或请求失败。

3. 从 Throughput 到 Goodput:高并发下怎样才算“好”?

实际的 LLM Serving 系统通常需要在 Throughput 和 Latency 之间做权衡。更大的 Batch Size 和更高的并发度能够提高 GPU 利用率与系统聚合吞吐,但也可能增加排队时间和资源竞争,使 TTFT、TPOT 或尾部延迟变差。

因此,评价在线服务时不能只问“峰值 Throughput 有多高”,还要问:在满足业务延迟要求的前提下,系统能够持续完成多少请求? 要回答这个问题,我们需要进一步引入延迟分布、SLO 和 Goodput。

3.1 为什么提高 Throughput 可能让用户变慢?

单个请求执行 Decode 时,每一步只生成一个新 token,工作量较小,通常难以充分利用 GPU。Serving 系统因此会把多个请求组织到同一个 batch 中执行,让一次计算同时服务多个序列。

Batch Size 从 1 增加到 8 后提高 GPU 利用率和系统吞吐量

图:Batching 让多个请求共享一次模型执行,有助于提高 GPU 利用率和聚合吞吐量。

不过,Batching 并不是免费的。随着系统负载增加,新请求可能需要在队列中等待;新的 Prefill 还可能与正在进行的 Decode 竞争执行时间和硬件资源。因此,更高的并发度与更大的 batch 可能同时带来:

  • 更高的 Request Throughput 和 Aggregate TPS
  • 更长的排队时间与 TTFT
  • 更差的 TPOT 或更明显的 token 间隔波动
  • 更严重的 Tail Latency

Sarathi-Serve 研究的核心正是这种 throughput–latency trade-off:Decode Batching 有利于系统吞吐,但 Prefill 与 Decode 在同一个 Serving Engine 中交错执行时,也会造成延迟干扰。论文通过 Chunked Prefill 和 Stall-free Batching 缓解这一问题。(arXiv)

下图用一条简化曲线展示负载增加时延迟的变化:

请求负载增加时延迟在饱和点附近快速上升

图:负载较低时,提高请求到达速率可以增加系统吞吐,而 Latency 变化较小;接近饱和点后,排队开始快速增长,Latency 随之显著恶化。

这说明,把 GPU 跑得很满并不等于用户体验就很好。在高并发环境中,系统吞吐和单请求延迟必须结合起来观察。

3.2 单一平均值看不见尾部延迟

即使我们开始关注 Latency,只看平均值仍然不够。平均延迟告诉我们所有请求平均需要等待多久,却无法展示这些延迟在请求之间是如何分布的。两个平均延迟相同的系统,实际体验可能完全不同。

假设两个系统分别处理了 100 个请求:

系统 请求延迟分布 平均延迟
A 100 个请求都是 200 ms 200 ms
B 98 个请求为 100 ms,2 个请求为 5.1 s 200 ms

系统 A 的所有请求都在 200 ms 完成;系统 B 的大多数请求虽然更快,却有 2% 的请求需要等待 5.1 秒。两者得到的平均延迟完全相同,只看 Mean,我们无法区分这两种截然不同的分布。

因此,在线服务通常还会观察 Median、P90、P95 和 P99 等延迟分位数。下面是一种常见的右偏延迟分布:大多数请求集中在左侧的低延迟区域,少量请求散落在右侧,形成一条长尾。

右偏延迟分布中的 P50、P99 和 Tail Latency

图:P50 描述延迟分布的中心位置,Mean 会被右侧慢请求拉高,而 P95、P99 等高分位数用于观察分布尾部。

这些统计量分别回答不同的问题:

  • Mean(平均值):所有观测值之和除以请求数,能够反映整体平均水平,但容易受到极端值影响。
  • Median / P50(中位数):一半请求的延迟不超过该值,常用来描述典型请求所处的位置。
  • P95 / P99:分别表示至少约 95% / 99% 的请求延迟不超过该值,常用来量化高延迟尾部。

以 TTFT 为例:

P99(TTFT)=2sP99(\mathrm{TTFT}) = 2\text{s}

可以理解为:在当前样本和统计方法下,约 99% 的请求 TTFT 不超过 2 秒;剩余约 1% 的请求可能更慢,甚至慢得多。

分布右侧的高延迟请求所体现的问题,通常称为 尾部延迟(Tail Latency)。生产环境之所以关注它,是因为即使绝大多数请求都很快,少量严重变慢的请求仍然会影响服务的稳定性。P95、P99 等高分位数正是量化尾部延迟的常用方法。

不过,分位数仍然只是在描述系统“慢到了什么程度”,它不会自动告诉我们这样的延迟是否符合业务要求。要判断服务是否足够快,还需要为这些指标设定明确目标——这就引出了 SLO。

3.3 SLO:多快才算“合格”?

同样是 2 秒的 TTFT,对于离线文档总结可能完全可以接受;对于 IDE 中的实时代码补全,却可能已经明显影响使用体验。因此,生产系统不能只把目标写成:

Latency越低越好\text{Latency} \rightarrow \text{越低越好}

还需要根据业务场景,为关键指标设定可以测量和验证的目标。这就是 SLO(Service Level Objective,服务水平目标)

这里可以顺便区分两个相关概念:

  • SLI(Service Level Indicator) 是实际测量到的服务指标,例如 P99 TTFT 为 1.7 秒。
  • SLO 是希望 SLI 达到的目标,例如 P99 TTFT 不超过 2 秒。

例如,一个交互式应用可以规定:

P99(TTFT)2sP99(TPOT)50msP99(\mathrm{TTFT}) \le 2\text{s} \qquad P99(\mathrm{TPOT}) \le 50\text{ms}

这是一种总体百分位目标:它要求在指定统计窗口和工作负载下,约 99% 的请求满足相应的延迟边界。SLO 把抽象的“响应要快、生成要流畅”转换成了可以验证的服务目标。

现实中的标准化 benchmark 也会采用类似设计。例如,MLPerf Inference v6.0 为 GPT-OSS 120B Interactive 场景设置了 P99 TTFT ≤ 2.0 s、TPOT ≤ 15 ms 的 latency constraints;DeepSeek-R1 Interactive 的约束则是 P99 TTFT ≤ 1.5 s、TPOT ≤ 15 ms。这些阈值只适用于对应的模型、数据集与场景,不应该直接照搬到所有 LLM 应用中,但它们很好地展示了 SLO 的作用。(MLCommons)

除了总体百分位目标,Goodput Benchmark 还经常使用单请求合格阈值。例如,规定一个请求必须同时满足:

TTFT2sTPOT50ms\mathrm{TTFT} \le 2\text{s} \qquad \mathrm{TPOT} \le 50\text{ms}

只有同时满足两个条件,这个请求才被视为合格请求。

有了 SLO,问题就从“哪个系统的 Latency 更低”进一步变成:在满足业务延迟要求的前提下,系统每秒能够完成多少个合格请求? 这正是 Goodput 要回答的问题。

3.4 Goodput:满足 SLO 的有效吞吐量

Throughput 统计系统每秒完成了多少请求,而 Goodput 只统计其中满足全部单请求 SLO 的请求。按照 AIPerf 的定义:

Goodput=满足全部 SLO 的请求数Benchmark Duration\text{Goodput} = \frac{\text{满足全部 SLO 的请求数}} {\text{Benchmark Duration}}

如果一个请求的 TTFT 达标,但 TPOT 超过阈值,那么它仍然不能被计入 Goodput。因此:

0GoodputRequest Throughput0 \le \text{Goodput} \le \text{Request Throughput}

换句话说,Throughput 衡量的是系统完成工作的速度,Goodput 衡量的是系统完成符合既定服务目标的工作的速度。(NVIDIA Docs)

假设在相同的工作负载、测试时长和 SLO 下测试两个 Serving 系统:

系统 Request Throughput Goodput SLO 合格率
Server A 100 req/s 60 req/s 60%
Server B 85 req/s 80 req/s 94.1%

Server A 的原始吞吐更高,但高负载使大量请求违反了延迟 SLO,最终每秒只有 60 个合格请求。Server B 的原始吞吐稍低,却能让每秒 80 个请求满足全部 SLO。因此,如果目标是在既定延迟约束下提供稳定服务,Server B 反而具有更高的有效服务能力。

DistServe 进一步把这一思想作为 LLM Serving 的系统优化目标:给定应用的 TTFT 和 TPOT 要求后,系统应该优化在这些延迟约束下能够持续服务的最大请求速率,而不是只追求没有延迟约束的峰值 Throughput。DistServe 通过分离 Prefill 与 Decode,减少两阶段之间的资源干扰,并分别优化资源配置。(arXiv)

vLLM 的 Benchmark CLI 也提供了 --goodput 参数,可以为 ttfttpote2el 设置以毫秒为单位的单请求阈值。例如,在完整的 vllm bench serve 命令中加入:

--goodput ttft:2000 tpot:50

表示只有 TTFT 不超过 2000 ms、且 TPOT 不超过 50 ms 的请求才会被计入 Goodput;任何一个条件不满足,该请求就不算合格。这里省略了模型、后端、数据集和请求速率等其他 Benchmark 参数。(vLLM)

3.5 Goodput 如何随系统负载变化?

Goodput 不是一个脱离测试条件的固定数字,它会随着请求到达速率、并发度、输入输出长度和 SLO 阈值发生变化。

  • 负载较低时:请求几乎不需要排队,大部分请求都能满足 SLO,因此 Goodput 通常接近 Request Throughput。
  • 负载接近饱和点时:Request Throughput 可能仍在上升,但排队和资源竞争开始推高 TTFT、TPOT 与尾部延迟,两条曲线逐渐分离。
  • 系统过载后:Request Throughput 往往趋于平台,Goodput 则可能停止增长甚至下降,因为越来越多的请求虽然最终完成,却已经违反了 SLO。

下图展示了 Offered Load 增加时 Throughput 和 Goodput 的典型变化:

负载增加时 Throughput 与 Goodput 的变化曲线

图:低负载时 Throughput 与 Goodput 接近;负载升高后,违反 SLO 的请求越来越多,两条曲线开始分离。

绿色曲线的峰值是当前测试条件下测得的 Maximum Goodput;达到这个峰值时对应的 Offered Load,可以理解为系统接近可用容量边界时的负载水平。

这也说明,报告 Goodput 时必须同时说明测试条件,至少包括:

  • 模型、精度、推理引擎和硬件配置
  • 输入与输出长度分布
  • 请求到达模式、请求速率和最大并发度
  • TTFT、TPOT 或 E2E 等 SLO 阈值
  • Benchmark 持续时间、预热方式和请求数量
  • 超时、失败请求与取消请求的处理方式

只有这些条件基本一致,两个 Goodput 数字才具有可比性。最终,我们真正关心的不是系统在不考虑延迟时能够“塞进”多少工作,而是:

在满足 Latency SLO 的前提下,系统能够维持多大的有效吞吐?\boxed{ \text{在满足 Latency SLO 的前提下,系统能够维持多大的有效吞吐?} }

4. 小结

评价一个 LLM Serving 系统,需要从单个请求一路看到整个系统:

  • TTFT、TPOT、ITL 和 E2E Latency 描述单个请求各阶段需要等待多久。
  • Mean、P50、P95 和 P99 描述大量请求中的延迟分布,帮助我们区分平均体验、典型体验和尾部延迟。
  • Request Throughput 和 Token Throughput 描述系统单位时间内完成了多少请求或处理了多少 token。
  • SLO 为这些指标设定业务上可以接受的边界。
  • Goodput 描述单位时间内有多少请求真正满足了全部单请求 SLO。

这些概念之间的关系可以概括为:

Latency MetricsLatency DistributionSLO合格请求单位时间内计数Goodput\text{Latency Metrics} \rightarrow \text{Latency Distribution} \xrightarrow{\text{SLO}} \text{合格请求} \xrightarrow{\text{单位时间内计数}} \text{Goodput}

LLM 推理性能指标中 Latency、Throughput、SLO 与 Goodput 的关系

图:Latency 描述单请求体验,Throughput 描述系统处理能力,SLO 划定合格边界,Goodput 则衡量满足边界的有效请求吞吐。

参考资料

  • Key metrics for LLM inference (LLM Inference Handbook)
  • vLLM — Metrics (vLLM)
  • vLLM — vllm bench serve (vLLM)
  • NVIDIA AIPerf Metrics Reference (NVIDIA Docs)
  • MLPerf Inference v6.0 — GPT-OSS 与 DeepSeek-R1 Interactive 场景 (MLCommons)
  • DistServe: Disaggregating Prefill and Decoding for Goodput-optimized Large Language Model Serving (arXiv)
  • Taming Throughput-Latency Tradeoff in LLM Inference with Sarathi-Serve (arXiv)