在大语言模型推理服务中,预填充阶段(Prefill)和解码阶段(Decode)的资源分配策略直接影响系统的吞吐量与延迟表现。传统的连续批处理(Continuous Batching)机制将预填充请求与解码请求混合调度,但当长序列预填充任务进入队列时,往往需要一次性分配大量 KV 缓存内存,这不仅容易触发显存溢出(OOM),还会阻塞其他小规模请求的执行,形成显着的调度热点。分块预填充(Chunked Prefill)策略正是为解决这一痛点而设计的内存管理技术,其核心思路是将长序列的前缀计算拆解为固定大小的分块,使显存分配更加平滑可控。

分块预填充的工作原理可以从两个层面理解。在请求维度上,传统的预填充操作会将整个输入序列的注意力计算一次性完成,这要求为完整的 KV 缓存预留内存空间。以一个长度为 8192 的输入为例,假设使用 70B 参数模型且采用 FP8 量化,单次预填充可能需要占用数十 GB 的显存用于存储注意力矩阵的键值向量。当多个长序列请求同时到达时,显存竞争急剧加剧,系统不得不选择延迟某些请求或直接抛出 OOM 错误。分块预填充则将这个过程重新组织:首先处理前 N 个 tokens(如 4096 个),完成计算后释放中间状态并与解码请求交叉执行,待解码阶段消耗一定 token 后再继续处理后续的分块。这种交错执行模式使得显存压力始终维持在可控范围内。

在调度维度上,分块预填充改变了传统的 “先来先服务” 批处理逻辑。vLLM 在实现该功能时引入了 chunk_size 参数用于控制每个分块的大小,并将其与 max_num_batched_tokens 参数协同工作。当一个长序列请求进入调度器时,系统会计算该请求需要分割为多少个分块,每个分块的 token 数由 chunk_size 决定,而整个批次的总 token 数则受限于 max_num_batched_tokens。通过这种双重约束,系统能够在保证显存利用率的同时维持较高的批次吞吐量。值得注意的是,分块预填充并非完全消除预填充的延迟,而是通过将长尾延迟分摊到多个调度周期来降低 P99 延迟的抖动。

工程实践中,分块预填充的效果受多个参数影响。chunk_size 是最直接的调优点,默认值通常设置为 4096 或 8192,需要根据模型规模、显存容量以及请求分布来确定。对于显存相对紧张的部署环境(如单卡 A100 80GB 运行 70B 模型),适当减小 chunk_size 至 2048 可以进一步降低峰值显存使用,但会增加调度开销;对于显存充裕的场景,增大 chunk_size 至 16384 则能减少分块数量,提升计算效率。另一个关键参数是 max_num_batched_tokens,它决定了单个批次中所有请求的 token 总数上限,合理设置可以避免因分块过多导致批次过于碎片化。实际部署时建议通过负载测试观察 OOM 发生频率与批次吞吐量的变化曲线,逐步迭代找到最优配置。

从效果评估来看,分块预填充在两类场景中收益最为显著。第一类是长上下文应用场景,例如文档摘要、代码仓库分析或多轮对话系统,这类场景的输入序列长度经常达到数千甚至上万 tokens,分块预填充可以有效避免因单一请求占用过多显存而引发的排队等待。第二类是混合负载场景,即同时存在长序列预填充请求和短序列解码请求的环境,分块预填充确保了短请求不会被长请求 “饿死”,从整体上提升了请求级别的公平性和响应延迟的稳定性。根据 vLLM 社区的基准测试数据,开启分块预填充后,在典型的混合负载测试中 P50 延迟可降低约 15% 至 25%,P99 延迟的波动范围收窄近一半。

需要指出的是,分块预填充并非万能解药。分块操作本身会引入额外的调度开销,在某些极端短序列场景下可能反而略微增加延迟。此外,分块策略对前缀缓存(Prefix Caching)的效率有一定影响,因为分块后相同前缀可能被分散到不同的计算批次中,缓存命中率的下降需要通过其他优化手段补偿。因此,在实际部署时应结合具体业务场景进行权衡,将分块预填充作为整体推理优化方案的一环而非孤立的银弹。

资料来源:vLLM 项目 GitHub 仓库(https://github.com/vllm-project/vllm)