07 · Prefill 与 Decode:两条不同的执行路径
同一个 Transformer,两种工作负载
Prefill 处理 prompt 中尚未缓存的多个 token,目的是一次性建立这些 token 的 KV Cache。它通常拥有较大的 token 维度,矩阵乘法并行度高,偏计算密集。
Decode 每个活跃 Sequence 每轮通常只输入最新一个 token,却要读取整个历史 KV Cache。它的矩阵尺寸较小,容易被内存带宽、kernel launch 和调度开销影响。
nano-vLLM 如何区分
Scheduler 返回 (seqs, is_prefill)。ModelRunner 据此进入不同输入准备路径:
- Prefill:拼接多条 Sequence 的 token,构建累计序列长度
cu_seqlens,支持 variable-length FlashAttention。 - Decode:每条 Sequence 提供最后一个 token、当前 context length、block table 和 slot mapping。
为什么不能把两者当成同一批
它们需要的元数据不同,Attention 调用不同,理想 batch 策略也不同。把 Prefill 与 Decode 混在一个简单 kernel 中会增加实现复杂度,并可能让两个阶段都得不到最佳效率。
常见性能指标
| 指标 | 更受哪个阶段影响 | 用户感受 |
|---|---|---|
| TTFT(首 token 时间) | 排队 + Prefill | 点发送后多久看到第一个字 |
| TPOT(每输出 token 时间) | Decode | 后续文字流动速度 |
| 总吞吐 tokens/s | 两者与 batch | 系统整体容量 |
只报告总 tokens/s 可能掩盖交互体验。一个吞吐很高的配置,仍可能因为等待聚批而拥有很差的 TTFT。
观察张量,不要只看函数名
调试时打印 input_ids.shape、positions.shape、slot_mapping.shape 和 block_tables.shape。当你能从形状判断当前是 Prefill 还是 Decode,就真正理解了两条路径。
HTML INTERACTIVE LAB07 · Prefill 与 Decode:两条不同的执行路径
单独打开 ↗课后习题等待完成
Decode 阶段为什么常比 Prefill 更容易受内存带宽影响?
动手任务
为 8 个并发请求、prompt 长度 64、输出长度 20,分别写出 Prefill 需要处理的初始 token 工作量和 Decode 需要循环的 step 数。
下一节:Attention