3、静态批处理与动态批处理:静态批处理的局限、动态批处理的核心思想与优势
在大模型推理服务化部署的演进过程中,批处理(Batching)策略是决定GPU利用率与请求延迟的关键机制。早期的推理框架普遍采用静态批处理(Static Batching),即预先固定一个batch大小,将多个请求凑满一个批次后统一交给GPU执行。这种策略在CV模型或传统DNN模型中表现尚可,但在以Transformer架构为主的大语言模型(LLM)推理场景中,静态批处理暴露出严重的算力浪费问题。vLLM之所以能在业界获得广泛认可,核心突破之一便是引入了基于连续批处理(Continuous Batching)思想的动态批处理机制。本章将深入剖析静态批处理的局限性,阐明动态批处理的核心思想、实现优势,并通过一个具体的工程案例帮助读者建立从理论到实践的完整认知。
3.1 静态批处理的局限:从请求粒度到GPU空转
3.1.1 核心原理:请求级同步等待
静态批处理的工作模式可以概括为“凑批—执行—同步释放”。推理服务维护一个请求队列,当队列中的请求数量达到预设的batch size(例如4、8或16)时,框架将这些请求打包成一个批次,一次性送入GPU执行。在Transformer模型的推理过程中,一个批次内的所有序列必须共享相同的序列长度(通常padding到该批次的最大长度),并且前向传播的每一步(Prefill和Decode)都是同步进行的。这意味着,如果批次中有3个请求已经生成了结束符(EOS),而第4个请求还在继续生成token,那么前3个请求所占用的GPU显存和计算资源必须继续被占用,直到整个批次全部完成。
3.1.2 三大核心局限
| 局限维度 | 具体表现 | 对延迟/吞吐的影响 |
|---|---|---|
| GPU利用率低下 | 已完成的序列无法释放显存,新请求无法插入,GPU计算单元在Decode阶段大量空闲 | 吞吐量(Tokens/s)显著下降,GPU算力浪费可达40%-60% |
| 尾延迟(Tail Latency)恶化 | 一个长序列请求会拖慢整个批次,其他短请求必须等待其全部生成完毕 | P99延迟急剧升高,用户体验不稳定 |
| 调度僵化 | 无法应对请求到达速率的不均匀性;请求少时batch凑不满,请求多时排队严重 | 服务吞吐波动大,资源规划困难 |
3.1.3 案例拆解:一个典型的静态批处理困境
假设某团队部署了一个基于静态批处理的LLM服务,batch size设置为4。某一时刻,GPU正在处理4个请求:A(需要生成200个token)、B(需要生成50个token)、C(需要生成30个token)、D(需要生成10个token)。由于静态批处理的同步特性,当B、C、D分别在生成完50、30、10个token后已经满足用户需求,但它们无法退出批次。GPU必须继续为这4个请求同时执行前向计算,直到A生成完200个token。在这个过程中,B、C、D的显存占用无法释放,新的请求E、F只能排队等待。最终,整个批次的完成时间由最慢的A决定,而GPU在最后100个token的生成阶段,实际上只有A在产生有效输出,其余3个请求的计算完全属于空转。
3.2 动态批处理的核心思想:迭代级调度
3.2.1 核心原理:从“请求级”到“迭代级”
动态批处理(Continuous Batching)的核心思想是将调度粒度从“整个请求”细化为“单个Decode迭代步”。在vLLM的实现中,调度器在每个Transformer解码步骤(Step)开始时,都会重新评估当前所有活跃序列的状态。如果一个序列已经生成了EOS token,它会在当前Step结束后立即从批次中移除,其占用的GPU显存(包括KV Cache)被立即释放。与此同时,调度器可以从等待队列中选取新的请求,将其Prefill计算插入到下一个Step中。这种机制使得GPU在每个Step上都能处理尽可能多的有效序列,实现了真正的“流水线式”计算。
3.2.2 操作流程:动态批处理的调度循环
- 状态检查:在每个Step开始时,遍历当前批次中的所有序列,标记已完成(EOS)或达到最大长度的序列。
- 显存回收:将已完成序列的KV Cache从物理显存中释放,更新显存分配器(vLLM使用PagedAttention管理KV Cache,支持按需分配和回收)。
- 新请求准入:检查等待队列,根据当前可用显存和GPU算力,决定是否将新请求的Prefill计算加入本Step。
- 混合执行:在一个Step内,同时执行新请求的Prefill计算和已有序列的Decode计算(vLLM支持Prefill与Decode的混合批处理)。
- 迭代循环:重复上述步骤,直到所有请求处理完毕。
3.2.3 动态批处理的核心优势
- 吞吐量大幅提升:由于GPU始终在处理有效请求,整体吞吐量(Tokens/s)可提升2-3倍。vLLM官方测试显示,在相同硬件条件下,动态批处理比静态批处理吞吐量提升最高可达23倍(在长序列场景下)。
- 尾延迟显著降低:短请求不再被长请求阻塞,P99延迟可降低一个数量级。每个请求的完成时间只取决于其自身的生成长度和GPU当前的负载。
- 显存利用率优化:结合PagedAttention技术,KV Cache不再需要连续的大块显存,而是按需分页分配,显存碎片化问题得到有效缓解。
- 服务稳定性增强:动态批处理天然适应请求到达速率的变化,无需人工调整batch size参数。
3.3 实战案例:从静态批处理迁移到vLLM动态批处理
3.3.1 背景与初始现象
某互联网公司内部有一个基于自研静态批处理框架的代码生成服务,使用2张NVIDIA A100(80GB)GPU。业务方反馈:在高峰期(并发请求约50个),服务的平均响应时间达到8秒,P99响应时间超过20秒,且GPU利用率仅为35%左右。通过监控发现,大量请求在排队等待凑批,而GPU在Decode阶段存在明显的计算空转。
3.3.2 关键数据与问题分析
| 指标 | 静态批处理(优化前) | vLLM动态批处理(优化后) | 变化 |
|---|---|---|---|
| GPU利用率 | 35% | 78% | ↑ 122% |
| 平均响应时间 | 8.2s | 2.4s | ↓ 70.7% |
| P99响应时间 | 21.5s | 4.8s | ↓ 77.7% |
| 吞吐量(Tokens/s) | 1,200 | 3,850 | ↑ 220% |
3.3.3 迁移操作步骤
- 环境准备:使用vLLM 0.4.2版本,通过pip安装,并下载对应的模型权重(该团队使用的是CodeLlama-13B)。
- 启动服务:使用vLLM的OpenAI兼容API启动服务,关键参数配置如下:
python -m vllm.entrypoints.openai.api_server \
--model /data/models/codellama-13b-hf \
--tensor-parallel-size 2 \
--gpu-memory-utilization 0.90 \
--max-num-seqs 32 \
--max-model-len 4096 \
--swap-space 16
- 参数调优:将
max-num-seqs从默认值调至32,以允许更多序列同时参与动态批处理;gpu-memory-utilization设为0.90,确保KV Cache有足够的显存空间。 - 客户端改造:业务代码只需将API endpoint从原来的自研接口切换为vLLM的
/v1/completions接口,请求格式兼容OpenAI规范。 - 灰度验证:先切10%流量,观察P99延迟和GPU利用率稳定后,逐步放量至100%。
3.3.4 结果对比与复盘
迁移后,GPU利用率从35%提升至78%,平均响应时间从8.2秒降至2.4秒。更重要的是,在高峰期并发请求达到80个时,vLLM的动态批处理依然能保持稳定的P99延迟(4.8秒),而旧框架在并发超过60个时P99延迟已飙升至30秒以上。复盘发现,旧框架的瓶颈在于静态批处理导致的“长尾阻塞”,而vLLM的迭代级调度让短请求能够快速完成并释放资源,从而大幅提升了整体吞吐。
max-num-seqs并增加swap-space,以允许更多序列在显存和CPU内存之间进行换入换出。如果追求极致的低延迟,可以设置--enforce-eager模式减少CUDA graph的捕获开销,但会牺牲部分吞吐。
3.4 常见误区与总结复盘
3.4.1 误区一:动态批处理就是“来一个处理一个”
这是最常见的误解。动态批处理仍然会积累一定数量的请求后才开始执行,只是调度粒度从“整个请求”变成了“单个Decode步骤”。vLLM的调度器会综合考虑当前显存余量、等待队列长度、序列的生成进度等因素,决定每个Step执行哪些序列。盲目追求“零等待”反而会导致GPU在每个Step上处理的序列过少,降低吞吐。
3.4.2 误区二:动态批处理不需要配置batch size
虽然不需要手动设置固定的batch size,但max-num-seqs参数实际上就是动态批处理的上限。如果该值设置过小,GPU无法充分利用;设置过大,则可能导致显存溢出(OOM)。建议根据模型大小和GPU显存进行估算:max-num-seqs ≈ 可用显存 / (平均序列长度 × KV Cache每token占用)。
3.4.3 误区三:动态批处理只对长序列场景有效
实际上,动态批处理在短序列场景下同样有效。因为短序列的生成速度快,静态批处理中频繁的“凑批—执行—释放”循环会导致大量调度开销;而动态批处理通过迭代级调度,让短序列能够快速插入和退出,减少了调度等待时间。
3.4.4 总结复盘
静态批处理与动态批处理的本质区别在于:前者以“请求”为调度单元,后者以“迭代步”为调度单元。动态批处理通过精细化的调度,让GPU始终处于高利用率状态,同时显著降低了尾延迟。在实际部署中,vLLM的Continuous Batching结合PagedAttention,已经成为LLM推理服务的事实标准。对于正在使用静态批处理框架的团队,建议尽早迁移到vLLM或同类支持动态批处理的推理引擎,以获得数量级的性能提升。