Prefill 与 Decode:推理为什么分成两个阶段?

先读完提示词,再一个一个写出回答

目录

  我们输入“法国的首都是”,模型回答“巴黎。”。这看起来是一件连续的事,计算机却会把它分成两种很不一样的工作:先处理整个提示词,再根据已经生成的内容逐个预测后续 Token。研究 LLM 推理系统时,通常把它们叫作 Prefill 和 Decode。[1]

Prefill 负责“读”;Decode 负责“接着写”。它们用的是同一模型,但每次送进模型的 Token 数量不同。

Prefill 处理提示词并预测首 Token,Decode 逐个续写

同一请求的时间线。点击查看原始 SVG

一次回答的时间线

  为便于画图,我们把提示词示意性地切成“法国 / 的 / 首都 / 是”四个位置;这不代表实际 Tokenizer 一定这样切分。模型先对这四个位置运行一次前向计算,并从最后一个位置的输出预测第一个回答 Token“巴黎”。接下来把“巴黎”作为新输入,再预测“。”。如果还要继续,才把“。”送回模型。

时刻 本次送入模型的内容 本轮产生的下一个 Token 阶段
首次前向 四个提示词位置 巴黎 Prefill
下一轮前向 刚生成的“巴黎” 。 Decode
若继续 刚生成的“。” 下一个 Token Decode

  第一个输出 Token 通常来自 Prefill 的最后位置,不是先把提示词 Prefill 一遍、再额外做一次完整 Decode 才得到它。不同实现的调度与统计口径可能略有差别,但这条计算依赖关系值得记住。

Prefill:先把提示词读完

  Prefill 一次处理提示词中的多个位置。因果掩码仍然有效:前面的 Token 看不到后面的 Token;但各位置的计算可以在一次前向中用较大的矩阵运算组织起来。同时,模型会建立各层的 KV Cache,供后续生成复用。

  提示词越长,这一步要处理的位置就越多。Prefill 通常有较大的矩阵乘法,GPU 的并行计算能力比较容易发挥出来,因此在常见配置下往往更偏计算密集。这只是性能倾向,不是对所有长度、模型和硬件的绝对判断。[1]

Decode:一次走一个新位置

  第一个回答 Token 出来后,同一请求还不能并行算出所有未知的未来 Token。下一步的输入要等上一步选出结果。因此 Decode 是一个接一个的依赖链。

  有 KV Cache 时,每轮只把刚生成的那个 Token作为新位置送进模型,计算它的 Q、K、V;新 Query 仍要读取历史 K/V 做 Attention,然后预测再下一个 Token。缓存省去旧位置的重复计算,不能省去当前 Query 读取历史的工作。

  “一个接一个”说的是同一请求内部。服务器可以把多个用户请求的 Decode 步骤放进一个批次,并行使用 GPU。不能把这两种并行混为一谈。

自己改变提示词和回答长度

教学示意:方块表示每次前向中新处理的位置数,不表示耗时或 FLOPs。假设首个输出 Token 由 Prefill 产生,后续每轮 Decode 产生一个。

为什么两个阶段的瓶颈不同

  Prefill 同时处理多个位置,矩阵乘法通常较大;Decode 单个请求每轮只处理一个新位置,却要读模型权重和不断增长的 KV Cache。常见 GPU 部署中,前者更容易受计算吞吐限制,后者往往更受显存带宽、缓存读取和小批次效率影响。多个请求一起 Decode 会改变批大小和瓶颈,因此不能把这句话当作固定定律。[2]

观察角度 Prefill Decode
同一请求每轮新处理的位置 多个提示词位置 通常 1 个新位置
主要产物 首个输出 Token、提示词 KV Cache 下一 Token、新增 KV Cache
常见性能倾向 较大的矩阵计算 权重与 KV 读取、逐步延迟
是否依赖未知的上一步输出 不依赖 依赖

  两个阶段的资源需求不同,还会互相干扰。比如一个很长的 Prefill 插入正在运行的 Decode 批次,可能让其他用户等更久。相关研究会采用分块 Prefill,或将 Prefill 与 Decode 放到不同资源上运行;这些是服务系统的调度选择,不是模型结构突然变成了两个模型。[1][2]

用户实际感受到的两个等待

  TTFT(Time to First Token) 是从请求发出到看到第一个输出 Token 的时间,包含排队、Prefill、首次选词和传输等环节。TPOT(Time per Output Token) 描述首 Token 之后,输出 Token 之间的平均间隔;有的系统也用相邻 Token 的 ITL(inter-token latency) 来看抖动。TTFT 主要受首次处理影响,TPOT/ITL 更能反映持续 Decode 的体验。[2]

  一个长提示词可能让首字迟迟不出现;首字很快出现,也不保证后面写得流畅。所以评价服务速度时,不能只看“每秒多少 Token”,还要看这两类等待。

当很多请求同时来到

  用户请求的提示词长度、到达时间和输出长度都不同。在线服务会动态调整批次:新请求可以进入,已结束的请求可以退出,正在生成的请求继续运行。这类连续批处理提高资源利用率;同时需要在吞吐、首 Token 延迟和后续输出间隔之间做取舍。KV Cache 还会随各请求动态增长,PagedAttention 等方法则重点改善它的内存管理。[3]

  把本课收束成一句话:Prefill 一次读入已知提示词并给出首 Token;Decode 依赖已生成的 Token,逐步续写。它们要优化的等待,不是同一种等待。

约 90 秒讲解视频

参考文献(References)

  1. Agrawal, A., Panwar, A., Mohan, J., et al. “SARATHI: Efficient LLM Inference by Piggybacking Decodes with Chunked Prefills.” arXiv preprint arXiv:2308.16369, 2023.↩
  2. Zhong, Y., Liu, S., Chen, J., et al. “DistServe: Disaggregating Prefill and Decoding for Goodput-optimized Large Language Model Serving.” OSDI, 2024. arXiv:2401.09670.↩
  3. Kwon, W., Li, Z., Zhuang, S., et al. “Efficient Memory Management for Large Language Model Serving with PagedAttention.” SOSP, 2023. arXiv:2309.06180.↩

第十九课复习总图

Prefill 与 Decode 第十九课复习总图

计算形态、性能瓶颈、体验指标和服务调度,一页回顾。点击查看原始 SVG

  下一课看量化:把权重从 16 位压到 INT8、INT4 时,究竟损失了什么?