Prefill 与 Decode:输入处理和逐步生成
解释 Prefill 和 Decode 如何使用 KV Cache、各自如何批处理,以及应该观察哪些延迟指标。
一个流式接口可能很久才返回第一个字,也可能很快开始输出、随后断断续续。两种现象都能表现为“响应慢”,但要检查的工作不同。
先看一条请求如何开始回答,再看它怎样接着往下生成,就能找到这两种等待分别发生在哪里。下面讨论标准因果注意力的自回归生成,假定每步生成一个新 token;排队和网络时间会在解释指标时一起考虑。
从输入到逐步生成
Prefill 处理输入 token,为各层建立 KV Cache,并产生可用于采样首个输出 token 的 logits。一个请求的输入位置可以并行计算,也可以按顺序分块调度,不要求一次把整段输入放进同一个计算批次。
Decode 把最新 token 送入模型,读取已有的 KV,追加当前 token 的 KV,再得到下一个 token 的 logits。同一请求的后续 token 依赖此前输出;服务端仍可以把多个请求当前的 Decode 步合成一个 batch。
两个阶段都能批处理
Prefill 的输入通常包含较多 token,矩阵计算更容易形成较高的计算强度。Decode 每个请求每步处理的 token 少,还要读取模型权重与历史 KV,较容易受到显存带宽和调度影响。瓶颈仍取决于模型、输入输出长度、batch 大小和硬件,不能仅凭阶段名称判断。
| 比较项 | Prefill | Decode |
|---|---|---|
| 单个请求的处理对象 | 输入 token,可能分块执行 | 最新 token,逐步生成后续输出 |
| 批处理方式 | 合并多个请求或输入块 | 合并多个请求各自的当前步骤 |
| 常见压力 | 计算吞吐、长输入占用调度时间 | 权重与 KV 读取、逐步调度、缓存容量 |
| 相关指标 | TTFT:到首 token 的延迟,还包括排队等开销 | ITL:相邻输出间隔;TPOT:首 token 后的平均每 token 耗时 |
例如,把更多正在生成的请求放进同一批次,系统每秒可能输出更多 token,但每轮计算也可能更久,用户看到的相邻输出就会隔得更远。因此,比较 batch 配置时,要一起记录 TTFT、ITL/TPOT 和吞吐,并注明输入长度、输出长度与并发数。
如何验证瓶颈
固定模型、硬件和请求数据,先逐项改变调度配置或并发度,观察 TTFT 与逐 token 延迟,再结合 GPU 利用率、显存带宽和队列指标解释变化。例如,吞吐提高但 ITL 变长,可能是批次承载了更多请求,不能直接解释为每个请求生成得更快。
下面的函数只计算给定时间窗口内的 token 吞吐。调用时需注明 total_tokens 统计的是输出 token 还是输入加输出,它不能代替 TTFT 或 TPOT:
def tokens_per_second(total_tokens: int, elapsed_seconds: float) -> float:
return total_tokens / elapsed_seconds
两个阶段共享调度资源时,下一步要看连续批处理如何安排每轮工作。如果已经准备压测,先明确延迟口径与负载模型,再比较配置。