3、vLLM核心概念:PagedAttention、Continuous Batching、KV Cache等原理
3.1 为什么传统推理框架难以高效服务大模型
在深入 vLLM 的三大核心机制之前,我们需要先理解传统推理框架(如简单的 Hugging Face Transformers + 原生 PyTorch 循环)在服务大语言模型时面临的三个根本性瓶颈。这些瓶颈直接决定了 LLM 推理服务的吞吐量、延迟和显存利用率。
- 显存碎片化与浪费:每个请求的 KV Cache(键值缓存)长度随生成 token 数动态增长。传统框架按最大序列长度预分配连续显存,导致大量内部碎片和预留浪费。例如,一个实际只生成 50 个 token 的请求,却占用了足以容纳 2048 个 token 的连续显存块。
- 静态批处理(Static Batching):传统框架通常等待一批请求全部完成生成后才释放显存并接收新请求。由于不同请求的生成速度不同,先完成的请求必须等待后完成的请求,造成 GPU 计算资源空转,显著降低吞吐量。
- KV Cache 重复计算:对于共享前缀(如 System Prompt、Few-shot 示例)的请求,传统框架为每个请求独立计算并存储相同的 KV Cache,造成大量重复计算和显存占用。
vLLM 正是针对上述三大痛点,分别提出了 PagedAttention、Continuous Batching 和 自动前缀缓存(Automatic Prefix Caching) 三大核心技术。其中,PagedAttention 是 vLLM 的基石,Continuous Batching 是调度策略,而前缀缓存则是对 KV Cache 的进一步优化。
3.2 核心原理一:PagedAttention——操作系统式的显存管理
PagedAttention 借鉴了操作系统中虚拟内存的分页机制。它将每个请求的 KV Cache 划分为固定大小的块(Block),每个块可以存储固定数量 token 的键值向量。这些块在物理显存中不必连续,通过一张块表(Block Table)来记录逻辑块到物理块的映射关系。
核心思想:将连续的 KV Cache 逻辑空间映射到非连续的物理显存空间,按需分配,彻底消除内部碎片和预留浪费。
下图展示了 PagedAttention 的核心数据结构与映射关系。左侧是请求的逻辑 KV Cache 视图(连续),右侧是物理显存中的实际存储块(非连续),中间通过块表建立映射。
PagedAttention 带来的直接收益非常显著:
- 显存利用率接近 100%:按需分配块,仅在生成新 token 时申请新块,彻底消除内部碎片。
- 支持超长序列:逻辑上可以表示任意长度的 KV Cache,只要物理显存中有足够的空闲块。
- 高效共享:多个请求可以共享相同的物理块(如共享前缀),通过引用计数管理块的生命周期,实现内存级去重。
3.3 核心原理二:Continuous Batching——动态调度与流水线并行
Continuous Batching(连续批处理)打破了传统静态批处理的“木桶效应”。其核心思想是:在迭代级别(Iteration Level)进行调度,而不是在请求级别。每个 Decode 步骤结束后,调度器立即将已完成生成的请求移出批次,并插入新的等待请求。
下图对比了静态批处理与连续批处理的调度差异。左侧静态批处理中,请求 B 完成后必须等待 A 和 C 全部完成才能释放;右侧连续批处理中,B 完成后立即释放显存并插入新请求 D。
Continuous Batching 的实现依赖 vLLM 的 调度器(Scheduler) 和 执行器(Executor) 的紧密配合。调度器维护四个队列:
| 队列名称 | 状态 | 调度策略 |
|---|---|---|
| Waiting Queue | 等待被调度 | 按到达时间排序,优先处理 Preempted 请求 |
| Running Queue | 正在执行 Decode | 每次迭代检查是否完成,完成则移出 |
| Swapped Queue | 被抢占,KV Cache 已换出到 CPU | 当 GPU 显存不足时,按优先级换回 |
| Preempted Queue | 被抢占,等待重新调度 | 优先于新请求被调度 |
3.4 核心原理三:KV Cache 与自动前缀缓存
KV Cache 是 Transformer 解码过程中存储历史 token 的 Key 和 Value 向量的缓存。在自回归生成中,每生成一个新 token,都需要计算该 token 与所有历史 token 的注意力分数。如果不缓存,每次生成都要重新计算全部历史 token 的 K/V 向量,计算复杂度为 O(n²),显存占用为 O(n)。KV Cache 将复杂度降为 O(n),但代价是显存占用随序列长度线性增长。
vLLM 在 PagedAttention 的基础上,进一步实现了 自动前缀缓存(Automatic Prefix Caching,APC)。其原理是:如果多个请求的 Prompt 具有相同的前缀(例如相同的 System Prompt、Few-shot 示例或文档上下文),这些前缀对应的 KV Cache 块可以在物理显存中共享。vLLM 使用哈希表对每个 KV 块的内容进行哈希,当新请求的某个前缀块哈希与已有块匹配时,直接复用该物理块,无需重新计算。
实践建议:在部署聊天机器人或 RAG 应用时,将固定不变的 System Prompt 放在 Prompt 的最前面,并保持其完全一致(包括标点符号和空格)。vLLM 会自动识别并缓存这部分 KV Cache,可减少 30%~70% 的首 token 延迟(TTFT),并显著提升吞吐量。
3.5 案例:多请求并发下的显存与调度对比
假设我们使用 A100 40GB GPU 部署 Llama-2-7B 模型(权重约 14GB),服务 4 个并发请求,每个请求最大生成长度为 1024 token。传统框架按最大长度预分配,每个请求预留 1024 token 的 KV Cache 空间。假设每个 token 的 KV Cache 占用 0.5MB(7B 模型,GQA 配置下约 0.1MB~0.5MB),则每个请求预留 512MB,4 个请求共预留 2GB。但实际上,4 个请求的平均生成长度可能只有 256 token,实际使用 512MB,浪费了 1.5GB。
使用 vLLM 后,PagedAttention 按需分配,4 个请求实际只占用 512MB。释放出的 1.5GB 显存可以用于:
- 增大批处理大小(Batch Size),提升吞吐量。
- 支持更长的上下文窗口。
- 缓存更多共享前缀,加速后续请求。
同时,Continuous Batching 确保 GPU 始终在处理有效计算。假设请求 A 生成了 50 token 后结束,传统框架需要等待 B、C、D 全部完成(可能还需 200 token 的时间)才能释放 A 的显存。vLLM 在 A 结束的瞬间就将其移出批次,并立即插入新的请求 E,GPU 利用率从约 60% 提升至 95% 以上。
3.6 常见误区与调优复盘
误区一:认为 PagedAttention 只影响显存,不影响计算速度。
实际上,PagedAttention 通过减少显存碎片和内存拷贝,间接提升了计算效率。更重要的是,它使得更大的 Batch Size 成为可能,而更大的 Batch Size 通常意味着更高的 GPU 利用率(在显存允许范围内)。
误区二:盲目调大 --max-num-seqs 参数。
增大最大序列数虽然可以提升吞吐量,但也会增加调度延迟和显存压力。如果设置过大,可能导致频繁的抢占(Preemption),反而降低整体性能。建议根据实际并发需求,从默认值开始逐步调优,并监控 GPU 利用率指标。
复盘建议:在生产环境中,使用 vllm.engine.arg_utils.AsyncEngineArgs 配置参数时,重点关注以下三个参数:
# 配置示例
--gpu-memory-utilization 0.90 # 控制 GPU 显存利用率上限
--max-num-seqs 256 # 最大并发序列数
--enable-prefix-caching # 启用自动前缀缓存(vLLM 0.4.0+)
通过监控 /metrics 端点中的 vllm:num_requests_running 和 vllm:cache_usage 指标,可以直观地观察 Continuous Batching 和 PagedAttention 的实际运行效果。
总结而言,PagedAttention 解决了显存管理问题,Continuous Batching 解决了调度效率问题,而自动前缀缓存则进一步挖掘了 KV Cache 的复用潜力。三者协同工作,使得 vLLM 在保持低延迟的同时,实现了数倍于传统框架的吞吐量提升。理解这三个核心概念,是后续进行性能调优和架构设计的基础。