大模型推理加速(一):先分清 Prefill、Decode 和你真正要优化的指标

Jellow 编辑发布,AI 辅助生成并完成技术核对。 计算示例和实测结果会在文中分别说明。

同一个模型,有时要等几秒才开始回答,有时第一句话出现得很快,后面却一个字一个字地往外蹦。这两种“慢”,往往需要不同的处理方法。

讨论推理加速之前,先把一次请求拆开:服务器什么时候收到请求,什么时候开始计算,什么时候产生第一个 token,又在什么时候完成回答。只有知道时间花在哪里,才能判断某个优化是否有效。

本文以使用 KV Cache 的自回归、decoder-only Transformer 为例。后面的数字是人为设置的计算示例,不是某张显卡的性能实测。

1. 一次生成包含两个主要阶段

假设输入经过分词后有 1,024 个 token,模型需要继续生成 128 个 token。

Prefill 阶段处理已经给定的输入,计算各层表示并建立 KV Cache。输入 token 已经全部已知,因此同一层中多个位置的计算可以组织成较大的矩阵运算。因果注意力仍然保留:后面的位置可以看前面,前面的位置不能偷看后面。

Prefill 的最后一个位置通常就能给出第一个输出 token 的概率分布。

Decode 阶段继续生成后面的 token。标准自回归生成需要先确定前一个 token,再计算下一个 token。即使显卡还有空闲算力,同一条序列也不能随意把这些依赖关系拆开。Prefill 和 Decode 的计算特点,以及由此带来的优化差异,可参考 NVIDIA 的推理优化说明

这里容易产生一个误解:使用 KV Cache 后,模型是不是只看最后一个 token?实际情况是,新的 query 仍然需要访问历史 key 和 value;缓存节省的是历史状态的重复计算。Hugging Face 的缓存说明给出了这一过程。

2. “吞吐更高”不一定代表“聊天更快”

先明确几个指标:

指标 测量内容 更接近哪种体验
TTFT 从发出请求到收到第一个输出的时间 要等多久才开始回答
TPOT 除第一个输出 token 外,平均每个输出 token 花费的时间 开始回答后生成得快不快
E2E latency 一次请求从开始到完成的总时间 多久能拿到完整答案
Output throughput 测量窗口内成功产生的输出 token 总数除以窗口时间 服务整体能处理多少输出

测量位置必须写清楚。客户端 TTFT 会包含网络、排队和服务端处理,不能直接等同于 GPU 的 Prefill 时间。输入和输出 token 混合统计得到的 total throughput,也不能直接和 output throughput 比较。

对产生至少两个输出 token 的请求,一种常见的计算口径是:

TPOT = (E2E latency - TTFT) / (输出 token 数 - 1)

vLLM 的基准工具采用上述 TPOT 定义。它还区分 TPOT 和流式输出之间的间隔 ITL:一次流式输出可能包含多个 token,尤其是在推测解码中,二者并不总相等。指标定义

下面是假设数据:

配置 单请求输出长度 TTFT TPOT 推导出的 E2E
A 101 token 0.30 秒 0.020 秒 2.30 秒
B 101 token 0.80 秒 0.015 秒 2.30 秒

两种配置的完整回答耗时相同,但 A 开始得早,B 后半段更流畅。如果只比较总耗时,就会丢掉这部分体验差异。

3. 为什么单请求 Decode 容易受显存带宽限制

把一个稠密线性层简化成矩阵乘法。处理很多 token 时,一次读入的权重可以参与更多运算;只处理一个 token 时,权重读取成本更难被摊薄。

可以用一个简化的下界模型建立直觉:

单步耗时 >= max(运算量 / 可达到的计算速率,
               数据搬运量 / 可达到的内存带宽)

这个模型假设计算和搬运能较好重叠,没有计入所有调度、同步和通信开销。

再假设一个 70 亿参数的稠密模型,权重全部使用 2 字节存储,每步都需要从显存读取约 14 GB 权重。如果持续可达到的带宽是 1 TB/s,仅权重搬运就需要约 14 毫秒。这是人为设定条件下的估算,不是对具体模型输出速度的承诺。

长上下文还会带来 KV Cache 访问,大 batch 会提高权重复用,多卡部署会引入通信。这些因素都可能改变瓶颈。因此,“Decode 经常受带宽限制”是一种工作负载特征,不是所有模型、所有 batch 下都成立的定律。

4. 批处理解决的是复用与空转问题

多个请求合成一个 batch,有机会共同摊薄权重读取和 kernel 启动成本。但请求的输出长度不同,固定 batch 如果一直等最长的请求结束,短请求留下的位置就会空着。

Continuous batching 可以在迭代边界移出已完成请求,再加入新请求。它改变的是调度单位。Orca 的 iteration-level scheduling 是理解这一方向的重要工作。Orca 论文

另一个问题是长输入对正在 Decode 的请求造成干扰。Chunked prefill 把较长的输入处理拆成若干块,允许调度器更细地安排输入处理与输出生成。块大小会影响首 token 等待时间、输出间隔和吞吐,需要结合负载调节。vLLM 调优文档

这也解释了为什么把 batch 调大后,吞吐可能提升,P95 延迟却恶化:服务更忙,并不等于每个用户等待更短。

5. 做一次有解释力的对照实验

建议先准备三个固定的负载组,再逐项调整配置:

负载组 输入长度 目标输出长度 想观察的问题
短问短答 256 token 128 token 基础服务开销与输出延迟
长文理解 8,192 token 128 token 输入处理与长上下文影响
长答案生成 1,024 token 1,024 token 持续 Decode 表现

这些长度是实验设计示例,可以按真实业务替换。每组从低并发开始,逐步提高到出现明显排队的位置,同时记录 TTFT、TPOT、吞吐、失败率和显存占用。

比较结果时,固定模型与 tokenizer 版本、精度、采样参数、硬件和软件版本;明确是固定并发还是固定到达率。预热也要一致。重复使用相同输入可能命中前缀缓存,热缓存结果应与冷缓存结果分开记录。vLLM 基准注意事项

测试最终要回答一个具体问题,例如“在 P95 TTFT 不超过 1 秒的条件下,最大可接受请求率提高了多少”。要得到这个结论,还需要避免负载模型把排队问题藏起来。

6. 闭环压测可能把过载藏起来

负载生成方式也会改变结论。

闭环压测通常维持固定并发:一个请求完成后才发下一个。当服务变慢时,客户端也会跟着降低发送速度。它适合模拟“固定数量的用户各自等待回答”,却可能把系统的过载点藏起来。

开放环压测按照预定到达率发请求,不等待前一个请求完成,更接近有独立用户持续到达的场景。vLLM 的在线基准区分请求率、并发上限和不同的到达模式,也支持按照延迟 SLO 统计 goodput。vLLM Benchmark CLI

假设服务在某个输入输出分布下,稳定处理能力约为每秒 5 个请求:

  • 闭环客户端看到延迟增加后自然降到每秒 4.5 个请求,队列看起来一直不长;
  • 开放环仍以每秒 6 个请求到达,未完成请求会持续积累,P95 和 P99 很快恶化。

这里的 5、4.5、6 都是假设数字。关键判断是:当长期到达率高于完成率时,不存在稳定的排队状态。短时间压测可能在队列填满前结束,于是给出过于乐观的结果。

在稳定区间内,还可以用 Little 定律做一次一致性检查:

平均在途请求数 L = 到达率 λ × 平均系统时间 W

例如平均每秒完成 5 个请求,平均端到端时间 2 秒,则系统内平均约有 10 个请求。这个数量包括排队和执行中的请求,不等于 GPU 上某一时刻的 batch size。如果监控值相差很大,先检查统计窗口、重试、流式连接关闭和请求是否丢失。

7. 用 SLO goodput 取代孤立的峰值吞吐

吞吐只计算“完成了多少”,没有说明这些请求是否来得及。可以先给业务定义一组服务目标,例如:

TTFT P95 <= 1.0 s
TPOT P95 <= 40 ms
请求失败率 <= 0.1%

然后把 goodput 定义为单位时间内同时满足这些条件的请求数。某个配置可能把输出吞吐从每秒 1,000 token 提高到 1,200 token,但大量短请求的 TTFT 超标;它的峰值吞吐更高,goodput 反而可能更低。

SLO 必须按请求类型制定。交互式聊天通常更在意 TTFT 和输出节奏,离线摘要更关心完成时间与总成本。把两类流量混在一个 P95 中,会让指标既不能代表聊天体验,也不能代表批任务效率。

一次更完整的容量实验可以这样进行:

  1. 从真实流量中只保留 token 长度、到达间隔和采样参数等非敏感统计,构造可重复数据集;
  2. 选择多个到达率,每档持续到队列、吞吐和延迟进入稳定区间;
  3. 每档同时报告 TTFT、TPOT、E2E 的 P50/P95/P99、完成率、goodput 和显存峰值;
  4. 画出“到达率—goodput”和“到达率—P95 延迟”曲线,找到延迟开始陡增的位置;
  5. 只在同一 SLO 和同一输入输出分布下比较优化前后容量。

这样测试得到的不是“这台机器最快能跑多少 token”,而是“在用户还能接受的延迟下,它能稳定接住多少请求”。后一个问题才更接近部署决策。

更新说明(2026-09-05): 补充了推导、失效条件和可执行的部署判断方法。


本站的内容生成、审核与纠错说明

发表评论