2. 连续批处理基础:概念、与传统批处理的区别及在 vLLM 中的实现原理
2.1 连续批处理的核心概念
在深入了解 vLLM 的优化手段之前,我们必须先厘清“连续批处理”(Continuous Batching)这一核心概念。传统意义上的“静态批处理”(Static Batching)是指:当多个请求到达后,系统必须等待所有请求都完成预处理(Prefill)阶段,或者等待一个固定大小的批次凑齐后,才统一交给 GPU 执行。在这个批次中,所有请求的生命周期被强行绑定在一起——GPU 必须等待该批次中最慢的那个请求完全生成完毕(包括所有后续的 Decode 步骤),才能释放整个批次的计算资源,转而处理下一批请求。
连续批处理则彻底打破了这种“批次同步”的枷锁。它的核心思想是:在 token 级别动态调度请求。当一个请求完成了它的生成(即遇到 EOS 或达到最大长度)时,它所占用的 GPU 计算槽位会立即被一个等待队列中的新请求所接管。同样,当一个请求完成了 Prefill 阶段,它会立即进入 Decode 阶段,而无需等待同批次的其它请求完成 Prefill。这种机制使得 GPU 的计算单元始终处于饱和状态,极大地消除了因请求长短不一而导致的“气泡”(Bubble)等待时间。
2.2 与传统批处理的本质区别
为了更清晰地理解两者的差异,我们可以从以下几个维度进行对比:
| 对比维度 | 传统静态批处理 | 连续批处理 |
|---|---|---|
| 调度粒度 | 请求级(整个请求完成才释放资源) | Token 级(每生成一个 token 都可能触发调度) |
| 资源释放时机 | 批次中所有请求全部完成 | 单个请求完成即释放其占用的 KV Cache 槽位 |
| GPU 利用率 | 存在大量“气泡”(等待慢请求) | 接近饱和,气泡被新请求填充 |
| 请求延迟 | 短请求被长请求拖累,延迟高 | 短请求可快速完成,延迟低且稳定 |
| 吞吐量 | 受限于最慢请求,吞吐波动大 | 整体吞吐显著提升,波动小 |
| 实现复杂度 | 简单,但性能受限 | 需要精细的调度器和内存管理 |
2.3 在 vLLM 中的实现原理:PagedAttention 与调度器
vLLM 之所以能高效实现连续批处理,其基石是 PagedAttention 机制。PagedAttention 将每个请求的 KV Cache(键值缓存)划分为固定大小的“块”(Block),这些块在物理上不必连续,而是通过一张“块表”(Block Table)进行映射。这带来了两个关键优势:
- 内存碎片化消除: 传统方式为请求预分配连续内存,容易产生碎片。PagedAttention 按需分配块,内存利用率接近 100%。
- 块级共享: 多个请求(如并行采样)可以共享相同的 Prompt 块,进一步节省内存。
基于 PagedAttention,vLLM 的调度器(Scheduler)实现了连续批处理的核心逻辑。其工作流程如下:
调度循环(简化伪代码):
while True:
# 1. 检查是否有请求完成生成
finished = [req for req in running if req.is_finished()]
for req in finished:
free_blocks(req.kv_cache_blocks) # 释放 KV Cache 块
running.remove(req)
# 2. 从等待队列中取出新请求,填充空闲槽位
while waiting_queue and len(running) < max_num_seqs:
new_req = waiting_queue.pop(0)
allocate_blocks(new_req) # 为新请求分配 KV Cache 块
running.append(new_req)
# 3. 执行一次前向传播(包含 Prefill 和 Decode)
execute_batch(running)
# 4. 将新生成的 token 写入对应的 KV Cache 块
for req in running:
write_kv_cache(req)
这个循环的关键在于:每次迭代都会检查并释放已完成请求的资源,并立即从等待队列中拉入新请求。因此,GPU 在每一轮迭代中处理的都是“当前活跃”的请求集合,而不是固定的批次。vLLM 还引入了 抢占(Preemption) 机制:当所有 KV Cache 块被占满时,调度器会抢占某些请求(将其 KV Cache 换出到 CPU),以保证新请求的公平性。
2.4 案例与效果对比
假设我们有一个 GPU,最大可同时处理 4 个请求。现在有 6 个请求到达,它们的预计生成长度如下:
- 请求 1:短文本(10 tokens)
- 请求 2:长文本(100 tokens)
- 请求 3:中文本(50 tokens)
- 请求 4:短文本(15 tokens)
- 请求 5:中文本(60 tokens)
- 请求 6:短文本(20 tokens)
传统静态批处理: 第一批次只能容纳 4 个请求(假设 batch size = 4)。系统必须等待请求 2(最长的 100 tokens)全部生成完毕,才能释放资源处理请求 5 和 6。总耗时约为 100 tokens 的生成时间,且 GPU 在等待请求 1、3、4 完成后仍有大量空闲。
vLLM 连续批处理: 第一批次同样处理 4 个请求。当请求 1(10 tokens)完成后,其槽位立即被请求 5 接管。请求 4 完成后,请求 6 立即进入。最终,总耗时可能仅为 100 tokens 左右,但 GPU 在整个过程中始终在处理 4 个请求,吞吐量提升显著。实际测试中,vLLM 在共享 GPU 场景下可实现 2~4 倍的吞吐量提升,且请求延迟的尾部延迟(P99)大幅降低。
2.5 常见误区与复盘
动态批大小只是调整批次中请求的数量,但批次内的请求仍然同步执行。连续批处理则允许同一批次中的请求处于不同的生成阶段(有的在 Prefill,有的在 Decode),这是本质区别。
恰恰相反,由于 PagedAttention 消除了内存碎片,并支持块级共享,vLLM 的显存利用率通常高于传统方案。它只是将显存管理从“预分配”改为了“按需分配”。
实际上,短文本请求受益更大。在传统批处理中,短请求被长请求阻塞;在连续批处理中,短请求可以快速完成并释放资源,从而降低整体延迟。
复盘总结: 连续批处理是 vLLM 高性能的基石。它通过 token 级调度和 PagedAttention 内存管理,实现了 GPU 计算资源的高效复用。理解这一机制,是后续学习 vLLM 的 Prefix Caching、Chunked Prefill 等高级优化技术的前提。在实际部署中,我们应关注 max_num_seqs、gpu_memory_utilization 等参数,它们直接影响连续批处理的调度效果。