Chunked Prefill
分块预填充会将长提示词拆分为多个 token 块,并与其他请求的解码过程交错执行,因此单个长提示词不会阻塞队列中的其他请求。
分块预填充是一种模型服务技术,它会将较长的输入提示拆分为较小的token组,并分多个步骤处理,同时穿插处理其他请求的token 生成。如果不使用这种技术,允许长提示进入的推理引擎就会在某一个步骤中只处理该提示;在其完成之前,其他用户已经开始接收的响应都会暂停。2023 年的 SARATHI 论文提出了分块预填充,后续的 Sarathi-Serve 则基于此构建了“无停顿”调度。DeepSpeed-FastGen 的 Dynamic SplitFuse 采用了相同思路。如今,vLLM 默认启用分块预填充,SGLang 和 TensorRT-LLM 也支持该功能;这些引擎为许多开放权重的大型语言模型(LLM)提供服务。
预填充、解码与生成停顿#
自回归 Transformer 推理包含两个阶段:
- 预填充:模型并行处理提示中的所有 token,计算它们的键和值,并将其存入 KV 缓存。预填充每读取一个字节的权重就会执行大量运算,因此通常受计算能力限制。
- 解码:模型为每个请求每一步生成一个 token,每次都读取整个缓存。解码每读取一个字节只执行少量运算,因此通常受内存带宽限制。
包含 20,000-token 预填充的一个 GPU 步骤耗时远超一个解码步骤,因此批次中的每个请求都必须等待。Sarathi-Serve 将这些暂停称为生成停顿;对于自身提示较短的用户,这些停顿会表现为 token 间延迟的峰值。
分块预填充的工作原理#
调度器会为每个步骤分配 token 预算,在 vLLM 中由 max_num_batched_tokens 设置。调度器首先为每个正在运行的解码请求添加一个 token,然后用剩余预算填入等待中的提示分块。放不进当前步骤的提示会延续到下一步骤,其 KV 缓存会逐块扩展,直到整个提示处理完毕并开始解码。在一个步骤中混合这两个阶段也能更充分地利用 GPU,因为解码会留下空闲的计算资源,预填充分块可以利用这些资源:
分块只改变处理提示 token 的时机。每个分块都会在因果掩码的约束下,对前面所有分块已缓存的键和值以及自身 token 进行注意力计算,因此其结果与一次性预填充相同,PyTorch 中的一个注意力层可以说明这一点:
import torch
import torch.nn.functional as F
torch.manual_seed(0)
prompt_len, chunk = 1000, 256
q, k, v = torch.randn(3, 1, 8, prompt_len, 64).unbind(0) # one layer: 8 heads, 64 dims each
# One-shot prefill: all prompt tokens attend causally in a single pass
full = F.scaled_dot_product_attention(q, k, v, is_causal=True)
# Chunked prefill: each chunk attends to the cached keys and values of earlier chunks plus itself
outputs = []
for start in range(0, prompt_len, chunk):
end = min(start + chunk, prompt_len)
mask = torch.ones(end - start, end, dtype=torch.bool).tril(diagonal=start) # causal over absolute positions
q_chunk, k_cached, v_cached = q[..., start:end, :], k[..., :end, :], v[..., :end, :]
outputs.append(F.scaled_dot_product_attention(q_chunk, k_cached, v_cached, attn_mask=mask))
chunked = torch.cat(outputs, dim=2)
print(f"{len(outputs)} chunks, same result as one pass: {torch.allclose(full, chunked, atol=1e-5)}")它会打印 4 chunks, same result as one pass: True:1,000-token 提示被分成 256、256、256 和 232 个 token 的分块进行处理,输出结果与一次性处理相同。
相关服务技术#
- 连续批处理:在每个步骤中添加和移除请求,而不是等待整个批次完成。分块预填充使长提示能够在这个循环中参与调度。
- PagedAttention:将 KV 缓存存储在固定大小的页面中,因此经过多个步骤预填充的提示可以逐步扩展缓存,而无需一开始就为完整长度预留内存。
- 提示缓存:复用重复提示前缀的键和值缓存,从而完全省去这部分预填充。
- 预填充与解码分离:在不同的 GPU 集群上运行这两个阶段,并在两者之间传输 KV 缓存,这样每个集群都能针对自身瓶颈进行扩缩和调优。
- 推测解码:先起草多个 token,再通过一次处理进行验证,从而加快解码阶段。
应用场景#
在长短提示共用服务器的场景中,分块预填充最有价值。检索增强生成(RAG)助手可以将一份 50 页的报告填入上下文窗口,并以分块方式进行预填充,同时继续为其他用户的聊天请求流式响应。视觉语言模型(VLM)会为每个提示添加数百或数千个图像 token,因此发送视频帧以生成描述的摄像头流水线恰好会产生这种请求混合。Ultralytics LLM 接口会将文本和图像发送到任何兼容 OpenAI 的端点,包括自托管的 vLLM 服务器;在该服务器上,YOLO26 流水线会请求描述检测到行人的视频帧,这些请求会与其他用户的流量一起进行分块预填充。
调整 token 预算#
该预算需要在两项延迟指标之间权衡。预算较小时,每个步骤可用于预填充的空间较少,这会降低流式用户的 token 间延迟,同时延长新提示的首 token 延迟;预算较大时则相反。长提示的后续分块还会重新读取不断增长的前序分块 KV 缓存,因此分块过小会增加内存流量。运维人员会根据真实提示长度,针对中位数和尾部推理延迟、首 token 延迟和吞吐量调整预算。vLLM 优化指南指出,2,048 个 token 的预算有利于降低 token 间延迟;对于大型 GPU 上的小型模型,指南建议使用超过 8,192 个 token 的预算以获得最高吞吐量。










