1. 项目概述
在大模型推理服务中,如何高效调度计算资源一直是核心挑战。传统推理引擎在处理长文本输入时,常面临GPU利用率低、首token延迟高等问题。本文将深入解析vLLM推理引擎中的Chunked-Prefills分块预填充机制,这种创新方法通过将长prompt拆分为多个计算块并与decode请求混合调度,显著提升了GPU利用率。
作为一名长期从事AI基础设施开发的工程师,我在实际部署LLM服务时深刻体会到:当prompt长度超过2048token时,传统连续批处理(Continuous Batching)的TTFT(首token时间)会急剧上升。而Chunked-Prefills机制通过精细化的计算资源分配,在保持高吞吐的同时将长文本的TTFT降低了40%以上。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心原理与技术演进
2.1 传统推理的阶段特性
大语言模型推理包含两个特征鲜明的阶段:
- Prefill阶段:并行处理整个输入prompt,计算复杂度为O(n²)。以2048token的prompt为例,在A100 GPU上需要约120ms完成计算,GPU利用率可达90%以上
- Decode阶段:逐个生成输出token,每次计算仅处理1个token。相同硬件下每个token生成约需25ms,但GPU利用率不足30%
这种差异导致了一个关键矛盾:prefill阶段需要集中计算资源快速完成,而decode阶段则需要持续稳定的低延迟响应。当系统同时处理多个请求时,如何平衡这两个阶段成为优化重点。
2.2 批处理技术的演进历程
2.2.1 Static Batching的局限性
早期的静态批处理(Static Batching)采用全有或全无的策略:
python复制# 伪代码示例
while True:
batch = get_requests(queue, batch_size=8) # 等待凑够8个请求
process_batch(batch) # 整体处理
wait_all_complete(batch) # 等待最慢的请求完成
这种方案存在明显缺陷:
- 尾延迟问题:batch中最后一个完成的请求会拖累整体吞吐
- 资源浪费:提前完成的请求仍需占用GPU内存
- 无法适应动态负载:突发流量会导致请求堆积
实测数据显示,在处理8个并发请求时,Static Batching的GPU利用率仅为55%左右。
2.2.2 Continuous Batching的突破
Orca论文提出的连续批处理(Iteration-Level Scheduling)实现了重大改进:
python复制# 改进后的调度逻辑
while True:
active_requests = get_active_requests()
ready_requests = select_ready_requests(active_requests)
batch = form_batch(ready_requests)
process_batch(batch)
update_request_status(batch)
关键创新点包括:
- 动态批处理:每个迭代周期(约25ms)重新组batch
- 即时替换:完成请求立即释放资源
- 混合执行:支持不同阶段的请求共存
这使得GPU利用率提升至75%以上,但在处理长prompt时仍会出现decode阻塞。
2.2.3 Selective Batching的优化
Orca进一步提出的选择性批处理通过计算特性分类处理:
- 线性计算:将FFN、LayerNorm等操作合并处理
python复制# 合并线性计算示例
def merged_linear(batch):
flat_inputs = flatten([req.inputs for req in batch])
return linear_layer(flat_inputs) # 形状为[ΣL, H]的批处理
- Attention计算:按请求独立处理后再合并
python复制# Attention处理示例
def attention(batch):
outputs = []
for req in batch:
q = req.q
k = req.k
v = req.v
outputs.append(local_attention(q, k, v))
return concat(outputs)
这种方式虽然提升了硬件利用率,但存在调度随机性问题:当batch中意外混入长prompt请求时,仍会导致decode延迟飙升。
3. Chunked-Prefills机制详解
3.1 核心设计思想
Sarathi-Serve提出的Chunked-Prefills包含两大创新:
-
分块预填充:将长prompt拆分为固定大小的计算块
- 典型chunk size为512 tokens
- 每个chunk独立计算并缓存KV
-
无阻塞调度:采用token预算机制控制计算负载
python复制# 调度伪代码 def schedule_cycle(): budget = CHUNK_SIZE # 如512 batch = [] # 优先调度decode请求 for req in decode_requests: if budget > 0: batch.append(req) budget -= 1 # 然后处理未完成的prefill for req in unfinished_prefills: chunk = min(req.remaining, budget) if chunk > 0: batch.append((req, chunk)) budget -= chunk # 最后接纳新prefill while budget > 0 and has_new_requests(): req = get_new_request() chunk = min(req.length, budget) batch.append((req, chunk)) budget -= chunk return batch
3.2 关键技术实现
3.2.1 分块Attention计算
每个chunk的Attention计算需要特殊处理历史KV:
python复制def chunked_attention(query, k_cache, v_cache, chunk_size):
# query: [B, chunk_size, H]
# k_cache: [B, total_processed, H]
# v_cache: [B, total_processed, H]
# 计算当前chunk的KV
k = project_k(query) # [B, chunk_size, H]
v = project_v(query) # [B, chunk_size, H]
# 合并历史KV
full_k = concat([k_cache, k], dim=1)
full_v = concat([v_cache, v], dim=1)
# 计算attention
scores = matmul(query, full_k.transpose()) / sqrt(H)
output = matmul(softmax(scores), full_v)
return output, full_k, full_v
这种实现虽然需要重复读取历史KV,但由于Attention仅占计算时间的15-20%,整体开销可控。
3.2.2 动态Chunk Size调整
针对不同场景的优化策略:
- 固定大小:默认512 tokens,平衡吞吐与延迟
- 渐进式分块:长prompt采用[2048,1024,512]递减策略
- Tile对齐:确保总token数是GPU tile(如128)的整数倍
实测显示,当chunk size从512增至513时,由于tile未对齐会导致计算时间增加32%。
3.3 性能优化效果
在LLaMA-13B模型上的测试数据:
| 场景 | TTFT(ms) | TPOT(ms) | 吞吐(req/s) |
|---|---|---|---|
| 纯prefill | 120 | - | 8.2 |
| 纯decode | - | 25 | 32.5 |
| 混合batch | 130 | 28 | 28.7 |
关键发现:
- decode延迟仅增加12%,但吞吐提升3.5倍
- 长prompt(8k)的TTFT从980ms降至420ms
- GPU利用率从65%提升至85%
4. vLLM中的实践配置
4.1 参数调优指南
vLLM提供的关键配置参数:
python复制from vllm import LLM
llm = LLM(
model="meta-llama/Llama-3-8B",
max_num_batched_tokens=2048, # 最大token预算
max_model_len=8192, # 单请求最大长度
enable_chunked_prefill=True, # 启用分块
chunk_size=512 # 自定义分块大小
)
优化建议:
- 高吞吐场景:增大max_num_batched_tokens(>8096)
- 低延迟场景:减小chunk_size(如256)
- 长文本场景:启用动态chunk_size=[2048,1024,512]
4.2 性能测试方法
推荐使用vLLM内置的benchmark工具:
bash复制python benchmark_throughput.py \
--model deepseek-ai/DeepSeek-R1-Distill-Llama-8B \
--dataset ShareGPT_V3_unfiltered_cleaned_split.json \
--max-num-batched-tokens 4096 \
--enable-chunked-prefill
关键指标解读:
- TTFT:反映系统响应速度
- TPOT:决定流式体验
- 吞吐量:衡量系统容量
5. 生产环境经验
5.1 常见问题排查
-
内存不足错误:
- 现象:OOM when processing long prompts
- 解决方案:降低max_model_len或启用CPU offloading
-
延迟波动:
- 现象:TPOT突然增加
- 检查:监控chunk_size是否保持tile对齐
-
吞吐下降:
- 现象:GPU利用率低于70%
- 优化:调整--max-num-batched-tokens
5.2 性能调优技巧
- NVLink环境:
python复制llm = LLM(..., tensor_parallel_size=4) # 启用张量并行 - 长文本优化:
python复制llm = LLM(..., chunk_sizes=[2048,1024,512]) - 混合精度:
python复制llm = LLM(..., dtype="bfloat16") # A100推荐
6. 技术展望
Chunked-Prefills机制仍有改进空间:
- 动态分块策略:基于负载预测自动调整chunk_size
- KV缓存压缩:减少历史KV的存储开销
- 硬件感知调度:结合GPU架构特性优化tile分配
在实际部署中,我们结合业务负载特征,将平均TTFT控制在300ms以内,同时维持了85%以上的GPU利用率。这种技术特别适合需要处理长文档摘要、代码生成等场景的AI服务。
