大模型推理的 Prefill 和 Decode 阶段有什么区别?为什么优化方法不同?
简化版
大模型推理分两阶段:Prefill(预填充) 并行处理输入 token、建立每层 KV Cache,矩阵较大、并行度高,通常更偏算力受限并影响 TTFT;Decode(解码) 自回归逐个生成 token,每轮只算新位置但要读权重和历史 KV,小 batch 时通常更偏带宽受限并影响 TPOT。这是常见工作区间而非铁律:序列长度、batch、硬件和 Kernel 都可能改变瓶颈,因此优化方向不同但最终要由 Profile 判定。
详细版
Prefill:输入长度 S 时,各层并行计算 S 个位置的 Q/K/V,把 K、V 写入缓存,输出最后位置的 logits 产生第一个 token。因果注意力只允许看前文,但整段 prompt 的多个位置能并行算。
Decode:第 t 轮只算新位置的隐藏状态,用新 Query 读取此前所有 K/V,采样出下一个 token 再进入下一轮。轮次间有数据依赖,不能把未来 token 一次算完。
关键差异在算术强度(arithmetic intensity,每读一字节权重能做多少次计算):
- Prefill 的矩阵运算让一批权重数据服务多个位置,通常具有更高算术强度,更容易接近算力上限。
- 单序列 Decode 一次前向只产出 1 个 token,权重与 KV 搬运相对计算更多,算术强度较低,常卡在带宽。
所以 Prefill 常计算密集、Decode 常访存密集,但具体瓶颈随 batch、上下文、硬件变化(超长上下文注意力、大 Decode batch 都会改变结论)。
完整版教学
一、Prefill 做了什么
Tokenizer 先把 prompt 变成长度 S 的序列。Transformer 在每层并行计算所有 S 个输入位置,输出最后位置的 logits,并保存每个位置的 K、V 到缓存。第一个输出 token 产生前必须完成整段 prefill——所以prompt 越长,TTFT(首 token 延迟)越高。
举例:一个 4K token 的长 prompt,prefill 要一次算 4096 个位置的全部注意力和 FFN,计算量大但高度并行,能把 GPU 的算力单元喂满。
二、Decode 做了什么
采样出一个 token 后,模型只为这一个新位置做前向。KV Cache 让历史 token 的 K、V 不必重算,但注意力仍要读取全部历史缓存。上下文越长,每轮要访问的缓存越多。
生成 M 个 token 至少要 M 次「依赖前一步结果」的串行迭代——这是 Decode 慢的根本:时间步串行,无法像 prefill 那样并行。批内不同序列还可能在不同时刻结束,增加调度复杂度。
三、为什么瓶颈不同:算术强度这把尺子
理解两阶段差异的核心概念是算术强度 = 计算量 ÷ 访存量。
Prefill(S个位置一起算):
权重分块被 S 个位置复用 → 算术强度通常更高 → 更可能接近算力上限
单序列 Decode(一次1个token):
权重与 KV 搬运 → 只产出 1 个 token → 算术强度低 → 更可能受带宽限制
在现代 GPU 上,算力远快于显存带宽。Decode 每轮要把整个模型的权重从显存搬进计算单元,却只算出一个 token,大量时间在「等权重搬运」——这就是 Decode 访存受限的本质。
批处理能显著改善 Decode 吞吐:把 B 个序列放在同一轮,权重数据可服务更多 token,算术强度与设备利用率通常上升。但 batch 过大会拉长单轮时间、增加 KV 访问并恶化 TPOT,因此目标是在延迟 SLO 下选择合适的动态 batch,而不是无限做大。连续批处理帮助维持有效 batch,PagedAttention 则提高动态 KV 分配效率。
记忆钩子:Prefill 是”一次读题、并行建缓存”(算力受限,决定首字快慢);Decode 是”带着缓存逐字作答”(访存受限,决定后续流畅度)。 前者靠算得快,后者靠攒大 batch、省带宽。
四、两阶段的优化方向完全不同
| Prefill | Decode | |
|---|---|---|
| 常见瓶颈 | 较偏算力(长序列时也受注意力访存影响) | 小 Batch 时较偏权重/KV 带宽 |
| 决定指标 | TTFT(首 token 延迟) | TPOT(每 token 延迟) |
| 优化手段 | FlashAttention、算子融合、Prompt/Prefix 缓存、Chunked Prefill、高效长上下文注意力 | 连续批处理、PagedAttention、KV 量化、投机解码、GQA/MQA |
| 核心思路 | 减少大矩阵计算、复用重复前缀 | 攒大 batch、省 KV 带宽 |
两阶段共享同一份模型权重,却需要不同的 batch 策略,甚至可以拆开部署到不同实例(Prefill/Decode 分离),各自用最优配置。
五、如何定位到底卡在哪
不能拍脑袋,要分阶段 profiling,分别记录:
- Prefill 的 token 数与耗时、Decode 的 token 数与耗时;
- 每轮活跃序列数、KV Cache 用量、通信时间;
- GPU 算力利用率 vs 显存带宽利用率(判断 compute-bound 还是 memory-bound)。
只有把两阶段的指标拆开看,才能判断该优化「模型计算、内存管理还是调度」。比如 TTFT 高就查 prefill(长 prompt?排队?),TPOT 高就查 decode(batch 太小?上下文太长 KV 读太多?)。
六、易错点与边界
- 别把「Prefill 计算密集、Decode 访存密集」当铁律:超长上下文时 prefill 的注意力也会被 KV 访存拖累;Decode batch 很大时也可能变回 compute-bound。
- 流式输出不改变实际 Decode 速度:它只是把已生成的 token 尽早推给用户、改善体感,模型该算多久还是多久。
- TTFT 和 TPOT 要分开优化:交互式聊天对 TTFT 敏感,离线批量更看总吞吐——优化目标不能混。
七、常见误区与追问
- 误区:Prefill 和 Decode 只是 batch 大小不同。 Prefill 可并行处理整段提示并偏计算密集,Decode 每步只产生一个 token且反复读取权重与 KV,偏带宽和调度。
- 误区:降低 TTFT 一定同时降低 TPOT。 给长 Prefill 更高优先级可改善其 TTFT,却可能阻塞正在 Decode 的请求并恶化 TPOT。
- 误区:Decode 每步只算一个 token所以成本可忽略。 它串行重复很多步,长输出的累计时间和权重读取量很大。
- 追问:两个阶段分别适合什么优化? Prefill 侧重大批矩阵、chunking 和前缀复用;Decode 侧重连续批处理、KV 管理、低位宽和调度。
- 追问:怎样做阶段化监控? 分别记录排队、Prefill 时长、TTFT、每步 TPOT、输出长度与取消位置,避免端到端均值掩盖瓶颈。
八、加强记忆
Prefill vs Decode 记「高并行 vs 时间步串行、TTFT vs TPOT」:Prefill 并行处理输入并建立 KV,通常算术强度较高;Decode 逐 token 生成,小 batch 时通常更受权重与 KV 带宽限制。Prefill 可用 FlashAttention、前缀复用和算子融合,Decode 可用连续批处理、KV 管理和投机解码;但二者的瓶颈都会随长度、batch 与硬件变化。定位问题必须分阶段 Profile,并在延迟 SLO 下选 batch,不能把“算力/带宽”当成永远成立的标签。