Chunked Prefill
Chunked Prefill(分块预填充)将长 Prompt 拆成多个较小的 Token 块,分多轮完成 Prefill,并逐块写入 KV Cache。它不会截断上下文,也不是滑动窗口注意力;其目标是限制一次 Prefill 的执行时间,避免长 Prompt 长时间阻塞正在 Decode 的请求。
为什么需要分块
Prefill 会并行处理整个 Prompt,计算量随输入长度快速增长;Decode 每轮通常只处理一个新 Token。两者共享 GPU 时,一次超长 Prefill 可能占用一个很长的调度迭代,使所有 Decode 请求的下一个 Token 延迟突然升高,这种现象常被称为 head-of-line blocking。
Chunked Prefill 把一次较大的延迟尖峰拆成多次较短的计算,让调度器可以在块与块之间穿插其他请求。
核心原理
设 Prompt 长度为
第
- Query 来自当前块
; - Key 和 Value 包含 KV Cache 中的前缀
,以及当前块 ; - 当前块仍使用因果掩码,计算完成后再把新产生的 KV 追加到缓存。
因此每个 Token 仍能看到它之前的完整上下文。忽略浮点舍入和不同 Kernel 的数值差异,逐块 Prefill 与一次性 Prefill 得到的模型语义和最终 KV Cache 等价。
这里的 Decode 穿插方式取决于推理引擎:可以让 Prefill 块和 Decode 分别执行,也可以在同一次模型前向中组成 mixed batch。Chunked Prefill 本身只规定“Prefill 分块”,并不天然要求混合执行。
Token 预算
调度器通常为每轮设置 Token 预算。采用混合批次时,可以近似写成:
其中
这个预算同时约束长 Prompt 对本轮耗时的影响,并给运行中的 Decode 请求留出空间。实际实现还要检查 KV Cache、请求槽位、页对齐和最大 Prefill 请求数。
优势与代价
| 方面 | 影响 |
|---|---|
| Decode 延迟 | 减少长 Prefill 导致的 TPOT 尖峰 |
| 调度公平性 | 调度器可以在块之间服务其他请求 |
| 峰值工作区 | 限制单次 Prefill 的激活值和临时工作区规模 |
| TTFT | 一个长 Prompt 要经过多轮才能开始生成,可能上升 |
| 计算效率 | 块过小会增加 Kernel 启动和调度开销,也可能降低 GEMM 效率 |
Chunked Prefill 不会减少最终 KV Cache 的大小,也不会从根本上减少模型需要完成的总计算量。它主要改变计算在时间轴上的分布,用少量额外调度开销换取更平滑的在线延迟。
分块大小
较大:Prefill 吞吐和 TTFT 通常更好,但更容易阻塞 Decode。 较小:TPOT 更平滑,但迭代次数和小算子开销增加。
实际调优应以业务的 TTFT、TPOT 和 Goodput 目标为准,而不是孤立追求最大的 Prefill 吞吐量。
SGLang 中的实现
SGLang 使用 chunked_prefill_size 控制单块上限,并由 max_prefill_tokens 限制一次调度中的 Prefill Token 总量。PrefillAdder 在请求超出剩余块预算时设置本轮区间,保存未完成请求,后续迭代继续追加 KV;启用 enable_mixed_chunk 后,调度器还可把 Prefill 块与运行中的 Decode 请求合并。
相关入口: