vLLM 核心:PagedAttention 与 Continuous Batching
1. vLLM 概述
vLLM 是一个高性能 LLM 推理与服务引擎,由 UC Berkeley 团队开发。其核心创新是 PagedAttention,借鉴操作系统虚拟内存分页机制管理 KV Cache。
核心特性
- PagedAttention:高效 KV Cache 内存管理
- Continuous Batching:迭代级调度,最大化 GPU 利用率
- 高效吞吐:相比 naive 实现可达 24× 吞吐提升
- 兼容性:支持 HuggingFace 模型,OpenAI 兼容 API
QUESTION 面试题:vLLM 的核心创新是什么?解决了什么问题? vLLM 的核心创新是 PagedAttention,它解决了传统推理服务中 KV Cache 显存利用率低的问题(传统方案仅 20-40%)。PagedAttention 借鉴操作系统虚拟内存分页机制,将 KV Cache 划分为固定大小的 Block,按需分配,消除内部碎片和外部碎片,使 KV Cache 利用率提升到 >95%。配合 Continuous Batching 迭代级调度,vLLM 相比传统方案可实现 2-4× 甚至更高的吞吐提升。
2. 传统推理服务的内存问题
静态内存分配
传统方案为每个请求预分配最大长度的连续 KV Cache 内存:
请求 A (实际生成 100 Token,预分配 2048): [■■■■■□□□□□□□□□□□□□□□] → 浪费 95%
请求 B (实际生成 800 Token,预分配 2048): [■■■■■■■■□□□□□□□□□□□□] → 浪费 61%
请求 C (实际生成 2000 Token,预分配 2048): [■■■■■■■■■■■■■■■■■■■■] → 浪费 2%
三大问题
- 内部碎片:预分配空间大于实际使用
- 外部碎片:请求结束后释放的内存块无法被新请求使用(不连续)
- 预留浪费:为最大长度预留大量内存,限制了并发数
结果:传统方案中 KV Cache 显存利用率仅 20-40%
3. PagedAttention 详解
核心思想
借鉴操作系统虚拟内存分页机制:
- 将 KV Cache 划分为固定大小的 Block(页)
- 每个 Block 存储固定数量 Token 的 KV 数据
- 使用 Block Table(页表)映射逻辑 Block 到物理 Block
- 按需分配 Block,不需要预分配最大长度
架构图
逻辑视图(序列看到的):
Block 0: [K0,V0] [K1,V1] [K2,V2] [K3,V3]
Block 1: [K4,V4] [K5,V5] [K6,V6] [K7,V7]
Block 2: [K8,V8] [K9,V9] [empty] [empty]
物理内存(实际存储):
物理 Block 5: [K0,V0] [K1,V1] [K2,V2] [K3,V3] ← 序列 A
物理 Block 12: [K4,V4] [K5,V5] [K6,V6] [K7,V7] ← 序列 A
物理 Block 3: [K8,V8] [K9,V9] [empty] [empty] ← 序列 A
物理 Block 7: [K0',V0'] [K1',V1'] [K2',V2'] [K3',V3'] ← 序列 B
Block Table(序列 A):
逻辑 Block 0 → 物理 Block 5
逻辑 Block 1 → 物理 Block 12
逻辑 Block 2 → 物理 Block 3
Block 大小选择
- 通常为 16 或 256 个 Token
- 太小:Block Table 管理开销大
- 太大:内部碎片增加
- vLLM 默认:
block_size=16
内存管理优势
- 消除外部碎片:Block 可以分散在物理内存任意位置
- 减少内部碎片:仅最后一个 Block 可能有浪费(最多 block_size-1 个 Token)
- 按需分配:新 Token 生成时才分配新 Block
- 即时释放:序列结束后立即释放所有 Block
- KV Cache 利用率接近 100%(仅最后一个 Block 有少量浪费)
QUESTION 面试题:PagedAttention 与操作系统分页机制有什么对应关系?
操作系统分页 PagedAttention 虚拟地址空间 逻辑 KV 序列(Token 0, 1, 2, ...) 物理内存页 物理 KV Block(GPU 显存中的实际存储) 页表 (Page Table) Block Table(逻辑→物理映射) 按需分配页面 按 Token 生成进度分配 Block 页面换出 (Swap) KV Block 换出到 CPU 内存 共享内存 (Shared Memory) 前缀共享 (Prefix Caching) PagedAttention 借鉴了虚拟内存的核心思想:逻辑连续但物理不连续,按需分配,支持共享和换出。
Prefix Caching(前缀共享)
多个请求共享相同前缀时(如相同 System Prompt),可共享 KV Cache Block:
请求 A: [System Prompt] + [User Query A]
请求 B: [System Prompt] + [User Query B]
↑ 共享 Block(Copy-on-Write)
这意味着:
- System Prompt 只需 Prefill 一次,后续请求直接复用
- 共享的 Block 是只读的,新请求追加新 Block 时不影响共享部分
- 在 RAG 场景中,相同检索文档的 KV 也可共享
4. Continuous Batching(连续批处理)
传统 Static Batching
时间 →
请求 A: [████████████████████] 完成
请求 B: [████████] 完成 ... 空闲等待 ...
请求 C: [████████████████] 完成 .. 空闲
整个批次必须等最慢的请求完成后才能开始新批次。
Static Batching 的问题:Batch 内所有请求必须等到最长的那个完成后才能开始新 Batch,造成大量 GPU 空闲时间。
Continuous Batching(迭代级调度)
时间 →
Slot 1: [请求 A...A...A...A...A完成][请求 D...D...D完成][请求 F...]
Slot 2: [请求 B...B...B完成][请求 E...E...E...E完成][请求 G...]
Slot 3: [请求 C...C...C...C...C...C...C完成][请求 H...]
每个 Decode Step(迭代):
- 检查哪些请求已完成(输出 EOS 或达到最大长度)
- 从运行队列中移除已完成的请求,释放其 KV Cache Block
- 从等待队列中取出新请求,分配 KV Cache Block
- 如果显存不足,可抢占(preempt)低优先级请求(换出到 CPU 或重新计算)
调度策略
# 伪代码:迭代级调度
def schedule_step(running, waiting, block_manager):
# 1. 移除已完成的请求
completed = [r for r in running if r.is_finished()]
for r in completed:
block_manager.free(r)
running.remove(r)
# 2. 尝试从等待队列中接纳新请求
while waiting:
r = waiting[0]
if block_manager.can_allocate(r):
block_manager.allocate(r)
running.append(r)
waiting.pop(0)
else:
break # 显存不足
# 3. 如果没有空间且没有运行的请求,抢占
if not running and waiting:
preempt(running, block_manager)
QUESTION 面试题:Continuous Batching 相比 Static Batching 有什么优势? Static Batching:整批请求一起开始,等最慢的完成后才接受新请求。GPU 空闲时间多,吞吐低。 Continuous Batching:每个 Decode Step 检查完成情况,完成的请求立即释放资源,新请求立即填充进来。
- 消除了等待最慢请求的空闲时间
- 每个 GPU Slot 始终保持忙碌
- 延迟更稳定(新请求无需等整批完成)
- 吞吐量可提升 2-4×
5. 抢占与换出机制
当 GPU 显存不足以接纳新请求时,vLLM 使用两种策略:
策略 1:Swapping(换出到 CPU)
- 将某些请求的 KV Cache Block 从 GPU 复制到 CPU 内存
- 需要时再换回 GPU
- 适合 CPU 内存充裕的场景
策略 2:Recomputation(重新计算)
- 直接丢弃被抢占请求的 KV Cache
- 需要恢复时,使用原始 Prompt 重新计算 Prefill
- 适合 Prefill 较快的场景
| 策略 | 换出成本 | 恢复成本 | 适用场景 |
|---|---|---|---|
| Swapping | GPU→CPU 内存拷贝 | CPU→GPU 内存拷贝 | CPU 内存充足 |
| Recomputation | 无(直接丢弃) | 重新 Prefill | Prefill 短、Prompt 不长 |
QUESTION 面试题:vLLM 如何处理显存不足的情况? 当 GPU 显存不足以接纳新请求时,vLLM 采用**抢占(Preemption)**策略:
- Swapping:将某些请求的 KV Cache 换出到 CPU 内存,需要时换回。适合 CPU 内存充裕的场景。
- Recomputation:直接丢弃被抢占请求的 KV Cache,恢复时用原始 Prompt 重新 Prefill。适合 Prompt 较短的场景。
默认使用 Recomputation 策略(
--swap-space 0),因为 Prefill 通常比 PCIe 传输更快。
6. 部署框架对比
主流推理框架对比
| 特性 | vLLM | TGI | TensorRT-LLM | SGLang | llama.cpp |
|---|---|---|---|---|---|
| 开发者 | UC Berkeley | HuggingFace | NVIDIA | LMSYS | 社区 |
| PagedAttention | 原生 | 类似机制 | 有 | RadixAttention | 无 |
| Continuous Batching | 支持 | 支持 | 支持 | 支持 | 有限 |
| Prefix Caching | 支持 | 支持 | 支持 | 支持(更高效) | 无 |
| 投机解码 | 支持 | 支持 | 支持 | 支持 | 有限 |
| 量化支持 | GPTQ/AWQ/FP8 | GPTQ/AWQ | 全量化格式 | GPTQ/AWQ/FP8 | GGUF K-Quant |
| 硬件支持 | NVIDIA/AMD | NVIDIA/AMD | NVIDIA | NVIDIA/AMD | CPU/GPU/Apple |
| 易用性 | 高 | 高 | 中 | 高 | 高 |
| 适用场景 | 通用在线服务 | HF 生态集成 | 追求极致性能 | 高吞吐研究 | 消费级/本地 |
选型建议
| 场景 | 推荐框架 | 理由 |
|---|---|---|
| 在线推理服务(生产) | vLLM / TGI | 成熟稳定,吞吐高 |
| 追求极致性能 | TensorRT-LLM | NVIDIA 深度优化 |
| 研究实验 | SGLang | 灵活,RadixAttention 更高效 |
| CPU/Mac 本地部署 | llama.cpp | GGUF 格式,CPU 优化好 |
| 快速原型 | vLLM | OpenAI 兼容 API,上手快 |
QUESTION 面试题:vLLM、TGI、TensorRT-LLM 有什么区别?如何选型?
- vLLM:开源生态活跃,PagedAttention + Continuous Batching,通用性强,适合大多数在线服务场景
- TGI (Text Generation Inference):HuggingFace 开发,与 HF 模型生态无缝集成,适合 HF 用户
- TensorRT-LLM:NVIDIA 开发,针对 NVIDIA GPU 深度优化(Kernel Fusion、FP8 等),性能最高但使用门槛也最高
- SGLang:RadixAttention 前缀缓存更高效,适合研究场景
选型原则:通用选 vLLM,HF 生态选 TGI,极致性能选 TensorRT-LLM,研究选 SGLang。
7. 性能数据
vLLM 论文基准(vs Orca, TGI)
| 指标 | vLLM | Orca | HF TGI |
|---|---|---|---|
| 吞吐量 (tokens/s) | 2-4× Orca | 基准 | 0.5-1× Orca |
| KV Cache 利用率 | >95% | 20-40% | 20-40% |
| 延迟 (P99) | 更低 | 基准 | 更高 |
与同类引擎对比(2025)
| 引擎 | PagedAttention | Continuous Batching | Prefix Caching | Speculative Decoding |
|---|---|---|---|---|
| vLLM | 原生 | 支持 | 支持 | 支持 |
| TGI | 类似机制 | 支持 | 支持 | 支持 |
| TensorRT-LLM | 有 | 支持 | 支持 | 支持 |
| SGLang | RadixAttention | 支持 | 支持(更高效) | 支持 |
8. vLLM 使用示例
启动推理服务
python -m vllm.entrypoints.openai.api_server \
--model meta-llama/Llama-3-8B \
--tensor-parallel-size 2 \
--gpu-memory-utilization 0.9 \
--max-model-len 8192 \
--enable-prefix-caching
离线批量推理
from vllm import LLM, SamplingParams
llm = LLM(model="meta-llama/Llama-3-8B",
tensor_parallel_size=2,
gpu_memory_utilization=0.9)
params = SamplingParams(
temperature=0.7,
top_p=0.9,
max_tokens=512
)
outputs = llm.generate(prompts, params)
常用启动参数
| 参数 | 含义 | 推荐值 |
|---|---|---|
--tensor-parallel-size |
张量并行 GPU 数 | 根据模型大小选择 |
--gpu-memory-utilization |
GPU 显存使用比例 | 0.85-0.95 |
--max-model-len |
最大序列长度 | 根据业务需求 |
--enable-prefix-caching |
启用前缀缓存 | 推荐(System Prompt 场景) |
--quantization |
量化方式 | awq / gptq / fp8 |
--swap-space |
CPU 换出空间大小(GB) | 0(默认 Recomputation) |
9. 核心设计总结
| 组件 | 功能 | 优化效果 |
|---|---|---|
| PagedAttention | KV Cache 分页管理 | 消除碎片,利用率 >95% |
| Continuous Batching | 迭代级调度 | 最大化 GPU 利用率 |
| Block Manager | 跟踪 Block 分配/释放 | 精确的内存管理 |
| Scheduler | 请求级别的抢占/恢复 | 保证服务质量 |
| Prefix Caching | 共享前缀 KV Block | 减少重复计算 |