大模型推理服务在生产环境中面临的核心挑战是吞吐量与延迟的平衡。传统静态批处理(Static Batching)在请求长度差异较大时产生严重的计算浪费,而连续批处理(Continuous Batching)通过迭代级调度解决了这一问题。vLLM框架的PagedAttention与Continuous Batching组合方案已成为大模型部署的事实标准,能够将GPU利用率提升3-8倍。
静态批处理的性能瓶颈分析
传统批处理将多个请求组装成一个batch,等到batch内所有请求生成完毕才释放资源。问题在于,生成任务的长尾效应极为明显——一个batch中如果有1个请求需要生成500 token,其余请求即使只需50 token,也必须等待最长的请求完成。GPU在此期间大量空转。
实测数据显示,当batch内请求长度方差较大时,静态批处理的GPU利用率通常不超过30%。对于变长输出场景(如代码生成、长文摘要),浪费更为严重。
Continuous Batching的迭代级调度原理
Continuous Batching的核心思想是将批处理粒度从请求级降到迭代级(iteration-level)。每个decode步骤都重新评估batch的组成:
1. 新请求在当前迭代中插入batch,无需等待现有请求完成
2. 已完成生成的请求立即从batch中移除,释放KV Cache空间
3. 每次迭代只处理当前active的请求,实现动态拼装
这种机制要求KV Cache的管理必须是细粒度的。vLLM通过PagedAttention将KV Cache按固定大小的block(通常16个token)分配,类似操作系统的虚拟内存分页机制。每个序列的KV Cache由一组block指针组成,无需连续物理内存。
vLLM中Continuous Batching的实现细节
vLLM的调度器(Scheduler)在每次step前执行以下逻辑:
class Scheduler:
def schedule(self):
# 1. 释放已完成序列的block
for seq in self.running:
if seq.is_finished():
self.block_manager.free(seq)
self.running.remove(seq)
# 2. 尝试将waiting队列中的请求加入running
while self.waiting:
seq = self.waiting[0]
# 检查是否有足够block容纳prefill
if self.block_manager.can_allocate(seq):
self.block_manager.allocate(seq)
self.running.append(seq)
self.waiting.pop(0)
else:
break # 显存不足,等待下一轮
# 3. 组装当前step的batch
batch = self.running[:self.max_batch_size]
return batch
关键在于第2步:prefill阶段需要一次性分配整个prompt的KV Cache,是显存消耗最大的环节。调度器通过token预算控制prefill的并发数,避免OOM。decode阶段每个序列只增加1个token的block,显存增量极小。
动态批处理与Prefill-Decode分离调度
在实际部署中,prefill和decode的计算特征差异显著:
– Prefill:计算密集型,GPU算力是瓶颈
– Decode:访存密集型,显存带宽是瓶颈
vLLM默认采用mixed策略,在一个batch中混合prefill和decode请求。但更优的方案是Disaggregated Serving,将prefill和decode分配到不同的GPU实例:
# prefill节点配置
python -m vllm.entrypoints.api_server --model meta-llama/Llama-3-70B --served-model-name llama70b-prefill --max-num-batched-tokens 8192 --gpu-memory-utilization 0.95
# decode节点配置
python -m vllm.entrypoints.api_server --model meta-llama/Llama-3-70B --served-model-name llama70b-decode --max-num-seqs 256 --gpu-memory-utilization 0.90
prefill完成后,通过NCCL将KV Cache传输到decode节点,decode节点以高并发处理大量序列。这种分离架构在DeepSeek的部署实践中将TTFT降低60%,吞吐提升2.3倍。
显存碎片治理与Block分配策略
Continuous Batching高频创建销毁block会导致显存碎片。vLLM的BlockManager维护一个空闲block池(free block list),采用以下策略:
1. 分配时从free list头部取出block,避免碎片
2. 序列完成时block归还free list,供新请求复用
3. 当free list耗尽,触发抢占(preemption):将最低优先级序列swap到CPU内存
抢占策略有Recalculate和Swap两种。Recalculate丢弃KV Cache并在重新调度时重新计算prefill,适用于显存紧张但CPU资源充足的场景。Swap将KV Cache暂存CPU内存,空间释放后换回,延迟更低但实现复杂。
生产环境性能调优参数
基于A100 80GB部署Llama-3-70B的推荐配置:
{
"max_num_seqs": 256,
"max_num_batched_tokens": 16384,
"gpu_memory_utilization": 0.92,
"swap_space": 16,
"block_size": 16,
"enable_chunked_prefill": true,
"max_paddings": 256
}
其中max_num_seqs控制decode阶段最大并发序列数,max_num_batched_tokens限制单次prefill的token总量。enable_chunked_prefill允许将长prompt分块处理,避免单个大请求阻塞整个batch。
监控指标方面,关注vLLM暴露的以下Prometheus指标:
– vllm:num_requests_running:当前运行序列数
– vllm:num_requests_waiting:等待队列长度
– vllm:gpu_cache_usage_perc:KV Cache占用率
– vllm:time_to_first_token_seconds:首token延迟
– vllm:time_per_output_token_seconds:单token生成延迟
当waiting队列持续增长且gpu_cache_usage超过95%时,说明显存不足,应增加GPU或降低max_num_seqs。TTFT突然增大通常是prefill阻塞导致,需调低max_num_batched_tokens。
Continuous Batching的适用场景与限制
Continuous Batching并非万能。以下场景效果有限:
1. 流式输出且客户端读取速度慢:序列虽已生成但未被消费,仍占用batch资源
2. 所有请求长度高度一致:静态批处理的浪费本就不大,动态调度的开销反而是负担
3. 单请求显存占用极大(如100K+上下文):一个请求可能耗尽全部KV Cache,批处理失去意义
对于大多数在线推理场景——对话系统、代码补全、文档问答,Continuous Batching配合PagedAttention能将单卡吞吐从20-30 req/s提升至200+ req/s,是大模型服务化的关键技术基石。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/da-mo-xing-tui-li-lian-xu-pi-chu-li-continuousbatching-yuan/