Skip to content

Chunked Prefill ​

Chunked Prefill(分块预填充)将长 Prompt 拆成多个较小的 Token 块,分多轮完成 Prefill,并逐块写入 KV Cache。它不会截断上下文,也不是滑动窗口注意力;其目标是限制一次 Prefill 的执行时间,避免长 Prompt 长时间阻塞正在 Decode 的请求。

完整 Prefill 与 Chunked Prefill 的调度时间线对比

为什么需要分块 ​

Prefill 会并行处理整个 Prompt,计算量随输入长度快速增长;Decode 每轮通常只处理一个新 Token。两者共享 GPU 时,一次超长 Prefill 可能占用一个很长的调度迭代,使所有 Decode 请求的下一个 Token 延迟突然升高,这种现象常被称为 head-of-line blocking。

Chunked Prefill 把一次较大的延迟尖峰拆成多次较短的计算,让调度器可以在块与块之间穿插其他请求。

核心原理 ​

设 Prompt 长度为 L,分块上限为 C,需要处理的块数为:

N=⌈LC⌉

第 j 个块的 Token 区间记为 [sj,ej)。计算该块时:

  • Query 来自当前块 [sj,ej);
  • Key 和 Value 包含 KV Cache 中的前缀 [0,sj),以及当前块 [sj,ej);
  • 当前块仍使用因果掩码,计算完成后再把新产生的 KV 追加到缓存。

因此每个 Token 仍能看到它之前的完整上下文。忽略浮点舍入和不同 Kernel 的数值差异,逐块 Prefill 与一次性 Prefill 得到的模型语义和最终 KV Cache 等价。

这里的 Decode 穿插方式取决于推理引擎:可以让 Prefill 块和 Decode 分别执行,也可以在同一次模型前向中组成 mixed batch。Chunked Prefill 本身只规定“Prefill 分块”,并不天然要求混合执行。

Token 预算 ​

调度器通常为每轮设置 Token 预算。采用混合批次时,可以近似写成:

∑r∈Ptcr+|Dt|≤Tbudget

其中 Pt 是本轮 Prefill 请求集合,cr 是请求 r 本轮处理的块长度,Dt 是 Decode 请求集合。每个普通 Decode 请求通常贡献一个输入 Token。

这个预算同时约束长 Prompt 对本轮耗时的影响,并给运行中的 Decode 请求留出空间。实际实现还要检查 KV Cache、请求槽位、页对齐和最大 Prefill 请求数。

优势与代价 ​

方面影响
Decode 延迟减少长 Prefill 导致的 TPOT 尖峰
调度公平性调度器可以在块之间服务其他请求
峰值工作区限制单次 Prefill 的激活值和临时工作区规模
TTFT一个长 Prompt 要经过多轮才能开始生成,可能上升
计算效率块过小会增加 Kernel 启动和调度开销,也可能降低 GEMM 效率

Chunked Prefill 不会减少最终 KV Cache 的大小,也不会从根本上减少模型需要完成的总计算量。它主要改变计算在时间轴上的分布,用少量额外调度开销换取更平滑的在线延迟。

分块大小 C 是关键权衡:

  • C 较大:Prefill 吞吐和 TTFT 通常更好,但更容易阻塞 Decode。
  • C 较小:TPOT 更平滑,但迭代次数和小算子开销增加。

实际调优应以业务的 TTFT、TPOT 和 Goodput 目标为准,而不是孤立追求最大的 Prefill 吞吐量。

SGLang 中的实现 ​

SGLang 使用 chunked_prefill_size 控制单块上限,并由 max_prefill_tokens 限制一次调度中的 Prefill Token 总量。PrefillAdder 在请求超出剩余块预算时设置本轮区间,保存未完成请求,后续迭代继续追加 KV;启用 enable_mixed_chunk 后,调度器还可把 Prefill 块与运行中的 Decode 请求合并。

相关入口:

参考 ​

  1. Taming Throughput-Latency Tradeoff in LLM Inference with Sarathi-Serve