4. vLLM核心机制:PagedAttention、Continuous Batching、动态批处理
4.1 为什么传统推理框架会“卡”在显存上
在深入 vLLM 之前,先看一个传统推理框架(如静态批处理的 PyTorch 原生服务)在并发请求下的显存分配方式。假设 KV Cache 按请求最大长度预留,每个请求预留 2048 token 的缓存空间,而实际平均只用到 512 token,那么 75% 的显存被白白占用。更糟的是,显存碎片化严重:当新请求到达时,即使总空闲显存足够,也可能因为找不到连续的大块空闲区域而触发显存不足(OOM)。
传统框架的批处理策略同样低效:要么等当前批次全部生成完毕再接收新请求(导致 GPU 空闲等待),要么频繁重启批次(导致重复计算)。这种“静态批处理”模式在请求到达率不均匀时,GPU 利用率波动极大。
4.2 PagedAttention:操作系统的“虚拟内存”思想
vLLM 的核心创新在于将操作系统中的分页机制引入 KV Cache 管理。传统方案为每个请求分配连续的显存块,而 PagedAttention 将 KV Cache 划分为固定大小的“页”(通常每页 16 或 32 个 token 的 KV 数据),通过页表实现逻辑连续、物理离散的映射。
PagedAttention 的注意力计算不再假设 KV 在内存中连续,而是通过页表逐页查找。虽然引入了额外的寻址开销,但 GPU 的高并行度完全掩盖了这一点。更重要的是,它带来了两个关键收益:显存碎片化几乎消失(任意空闲页都可分配),以及支持 KV Cache 共享(如并行采样时多个输出共享同一个前缀的 KV 页)。
4.3 Continuous Batching:迭代级调度
传统批处理以“完整序列”为单位:一个序列生成完 [EOS] 才退出批次,新请求必须等待整个批次结束。Continuous Batching 将调度粒度缩小到单次解码迭代(即一次 forward pass)。
具体流程如下:
- 迭代开始:调度器检查当前批次中哪些序列已生成
[EOS]或达到最大长度,将其移出批次。 - 插入新请求:从等待队列中取出新请求,为其分配初始 KV 页,加入当前批次。
- 执行前向:对批次内所有序列执行一次解码,得到新的 token 和更新的 KV Cache。
- 重复:进入下一次迭代,再次检查完成状态并插入新请求。
这种机制使得 GPU 在每一轮迭代都保持“满载”状态。即使某个请求已经生成完毕,它的显存页也会被立即回收,供新请求使用。在请求到达率较高的场景下,Continuous Batching 可以将吞吐量提升 10~20 倍(相比静态批处理)。
4.4 动态批处理:抢占与优先级
vLLM 的调度器还实现了动态的抢占(Preemption)机制。当显存不足时,调度器会选择部分序列将其 KV Cache 从 GPU 卸载到 CPU,腾出空间给新请求。被抢占的序列在后续迭代中会从 CPU 恢复,继续生成。这种机制保证了服务的高可用性,避免因单个大请求导致整个服务 OOM。
vLLM 支持两种抢占策略:按长度优先(优先抢占生成长度最短的序列,因为恢复成本低)和按到达时间优先(优先抢占最晚到达的请求)。实际部署中,通常配合 --max-num-seqs 和 --gpu-memory-utilization 参数来控制批大小和显存水位。
4.5 三种机制的协同工作流程
| 机制 | 解决的问题 | 核心数据结构 | 性能影响 |
|---|---|---|---|
| PagedAttention | 显存碎片化、预分配浪费 | 页表(逻辑页 → 物理页) | 显存利用率提升 4~5 倍 |
| Continuous Batching | GPU 空闲等待、批次切换开销 | 等待队列 + 运行批次 | 吞吐量提升 10~20 倍 |
| 动态抢占 | 长请求导致 OOM、服务不稳定 | CPU 侧 KV 缓存池 | P99 延迟更稳定 |
三者协同的典型流程:新请求到达 → 放入等待队列 → 调度器在下一轮迭代将其加入批次 → 分配物理页并建立页表映射 → 执行 PagedAttention 解码 → 若显存不足则抢占部分序列 → 被抢占序列的 KV 页标记为可回收 → 新请求获得显存继续执行。
4.6 案例:并发请求下的行为对比
假设 GPU 显存可容纳 100 个页(每页 32 token),同时到达 20 个请求,每个请求平均生成 200 token。
- 传统静态批处理:每个请求预留 2048 token 的连续 KV 空间,20 个请求共需 20 × 2048 × 2(K+V)× 2 bytes(FP16)≈ 160 MB。若实际平均只生成 200 token,则浪费约 90% 显存。GPU 利用率在批次切换时降为 0。
- vLLM:每个请求按需分配页,20 个请求平均共需 20 × 200 × 2 × 2 ≈ 16 MB。剩余显存可继续接收新请求。GPU 在每轮迭代都保持满负荷。
4.7 常见误区与调优要点
误区 2:盲目调大
--max-num-seqs 追求吞吐。当批大小超过一定阈值(通常 64~128),GPU 计算资源饱和,继续增大批大小只会增加排队延迟,降低单请求响应速度。
误区 3:忽略
--gpu-memory-utilization 的设置。默认值 0.9 意味着预留 10% 显存给激活值和临时张量。若模型较大或输入序列较长,应适当降低该值(如 0.85),避免运行时 OOM。
调优建议:先用 vllm serve --help 查看默认参数,再根据实际负载逐步调整。监控指标重点关注 gpu_cache_usage(KV 缓存使用率)和 num_requests_running(当前运行请求数)。当 gpu_cache_usage 长期高于 0.95 时,说明显存接近瓶颈,应考虑增加 --max-num-seqs 或使用更小的模型。