16. Chunked Prefill 是什么?为什么能同时改善吞吐量和延迟?
整理 Chunked Prefill 的原理、收益与工程取舍。
简单回答
Chunked Prefill(分块预填充)是把长 Prompt 的 Prefill 阶段拆成若干小块(比如每块 512 token),不一次性算完,而是每个 batching 步只算一块。这样做有两个收益:一是新请求的 Prefill 不再阻塞已有请求的 Decode——以前一个长 Prompt 进来要 Prefill 几秒,这期间所有 Decode 中的请求全部停摆,TTFT 和 TBT 都被拉长;二是把"计算密集型的 Prefill"和"访存密集型的 Decode"在同一个 batch 里混合执行,硬件利用率反而更高。是 vLLM、SGLang 等现代推理框架的默认调度策略。
详细解释
没有 Chunked Prefill 之前的痛点
本专题第 01 篇讲过 Prefill 和 Decode 的性质差异——Prefill 是 compute-bound 一次性处理整个 Prompt,Decode 是 memory-bound 逐 token 生成。在第 03 篇讲的 Continuous Batching 框架下两者在同一个推理引擎里调度。
但传统 Continuous Batching 有个调度难题:什么时候让新请求进来做 Prefill。常见的两种朴素策略都有问题:
Prefill-first:新请求一来就先把它的整个 Prompt Prefill 完,这期间已有请求的 Decode 全部停下来。后果是已有请求的 TBT(Time Between Tokens,token 间延迟)会出现明显尖刺——Decode 中的用户突然几秒看不到新 token 蹦出来。如果 Prompt 是 16K,这个停顿可能长达数秒。
Decode-first:让正在 Decode 的请求优先,新请求排队等所有 Decode 请求完成后再 Prefill。后果是新请求的 TTFT 极差——可能要等几十秒。
无论哪种策略,长 Prompt 的进入都会破坏服务质量。这个矛盾在 vLLM 早期版本里非常明显。
Chunked Prefill 的做法
Chunked Prefill 把长 Prompt 拆成固定大小的 chunk(比如 512 或 1024 token),每个调度步只 Prefill 一个 chunk,剩下的 chunk 留到下一步。
具体流程:
- 一个 batching step 里,调度器看 GPU 还有多少 token 预算(典型 2048-4096 token)
- 用一部分预算给 Decode 中的请求各算一个新 token(每个请求 1 token)
- 用剩下的预算给一个或多个 Prefill 中的请求各算一个 chunk(可能 512 token)
- 把这些 token 拼成一个大 forward 一次性算完
这样 Prefill 和 Decode 在同一个 forward 里并存了——512 个 Prefill token 加上几十个 Decode token 一起进 GPU。
为什么同时改善吞吐和延迟
延迟侧(TTFT 和 TBT):
新请求来了不需要等几秒做 Prefill 了——第一个 chunk 算完就能给用户回第一个 token。TTFT 大幅降低。同时已有请求的 Decode 也不会被长 Prefill 阻塞——chunked 后每步只占用一小段 Prefill 预算,Decode 仍然在每步推进。TBT 不再出现尖刺。
整体效果是延迟分布的尾部(P99)收得很紧。这对生产系统的 SLA 很重要——平均延迟好看不算什么,TBT 偶尔卡几秒会让用户觉得"卡顿"。
吞吐侧:
这个是反直觉的——拆成更小的 chunk 直觉上应该让吞吐变差,但实际反而变好。原因是 Prefill 和 Decode 的硬件特性互补。
Prefill 是 compute-bound,把 GPU 的算力打满,但显存带宽用得不充分(一次读完模型参数算 N 个 token)。Decode 是 memory-bound,显存带宽是瓶颈但算力闲置(读完参数只算 1 个 token)。
把两者拼在同一个 forward 里,Prefill 部分把算力用满,同时这次访存也能服务于 Decode token——本来 Decode 自己也要把模型参数读一遍,现在和 Prefill 共用一次访存。硬件上算力和带宽同时被打满。
实测数据上,开 Chunked Prefill 的 vLLM 比关掉的版本,吞吐量能提升 20%-40%,同时 TTFT 和 TBT 的 P99 都改善 50% 以上。
Chunk 大小怎么选
Chunk 大小是核心参数。太大失去拆分意义,太小调度开销盖过收益。
太大(比如 4096)—— 一个 step 算 4096 个 Prefill token 加几十 Decode token,相当于又退回到 Prefill-first 的体验,已有 Decode 的 TBT 又会有尖刺。
太小(比如 64)—— 每步只 Prefill 64 token,调度开销比例上升,且 Prefill 总耗时变长(总 token 数不变但每次开销增加)。
实际经验上 512 或 1024 是甜区。具体值还要看 GPU 算力和模型大小——A100 跑 70B 模型可能 1024 合适,H100 跑 7B 模型 512 已经够。一些框架用 token budget 而不是固定 chunk 大小——每步总共能处理 N token(比如 2048),按需分配给 Prefill 和 Decode,更灵活。
工程实现细节
Attention kernel 要支持混合长度。一个 forward 里有的请求是 Prefill chunk(多个 token),有的是 Decode(1 token),attention 计算对每个请求的实际长度不一样。Flash Attention 的 varlen 接口(处理变长序列)就是为这种场景设计的。
KV Cache 的写入要分阶段。Prefill chunk 写入 KV Cache 时只算这一块的 KV,下一个 chunk 来时要拿到上一块已经写入的部分做 attention。这要求 KV Cache 管理(本专题第 07 篇讲的 vLLM PagedAttention)支持"部分写入"。
调度器要做 token budget 平衡。每步先满足正在 Decode 的请求(每个 1 token),剩下的预算分配给 Prefill。如果有多个 Prefill 请求要排序——通常按 FIFO 或优先级。
何时不开 Chunked Prefill
Chunked Prefill 不是免费的——固定开销略高(每步多一次调度逻辑,attention kernel 复杂度上升)。在以下场景反而可能拖慢:
- 短 Prompt 场景:Prompt 都在几百 token 以内,Prefill 本身就快,拆分意义不大。
- 批吞吐优先:如果系统的目标是离线批处理,单次大批量 Prefill 不需要考虑 TBT,传统 Prefill-first 反而效率更高。
- 超大 batch decode 时:Decode 请求已经把 token budget 占满了,Prefill 进不来,Chunked 的优势体现不出来。
但在线服务场景下 Chunked Prefill 几乎是默认选择,主流推理框架(vLLM、SGLang、TensorRT-LLM)都内置了。
面试时可以这样答
Chunked Prefill 解决的核心问题是 Prefill 和 Decode 在同一个推理引擎里抢资源的矛盾。新请求来了,Prefill 一整个长 Prompt 要几秒,这期间已有 Decode 全部停摆 TBT 出现尖刺;如果反过来让 Decode 优先,新请求 TTFT 又会很差。
做法是把长 Prompt 的 Prefill 拆成固定大小的 chunk,比如每块 512 token。每个调度步先满足正在 Decode 的请求各 1 个 token,剩下的 token 预算分给 Prefill 一个 chunk,混合在一个 forward 里算完。
收益有两块。延迟上 TTFT 和 TBT 同时改善——新请求第一个 chunk 算完就能蹦字,已有 Decode 不再被长 Prefill 阻塞。吞吐上反而变好——Prefill 是 compute-bound、Decode 是 memory-bound,混在一起算同一份模型参数,算力和带宽同时被打满,硬件利用率比单做 Prefill 或单做 Decode 都高。实测吞吐量能涨 20-40%,TTFT/TBT 的 P99 改善 50%+。
Chunk 大小是关键参数。太大就退回 Prefill-first 的尖刺问题,太小调度开销盖过收益。经验上 512 或 1024 是甜区,更先进的做法是用 token budget 动态分配。
实现上 attention kernel 要支持变长(Flash Attention varlen)、KV Cache 要支持分阶段写入。这些都是 vLLM、SGLang 等现代推理框架的默认能力。在线服务场景几乎是必开的优化,离线批处理的话传统 Prefill-first 可能更高效。
常见追问
- Chunked Prefill 和 Speculative Decoding 能不能同时开?两者是互补还是冲突?
- Token budget 怎么动态调整?根据什么信号变?
- 多机多卡推理下 Chunked Prefill 还有同样的收益吗?通信开销会不会抵消?