vLLM KV Cache 实现机制
·
vLLM KV Cache 实现机制
一、核心机制:PagedAttention 是如何实现的
1.1 背景:传统 KV Cache 的三大问题
在自回归解码过程中,每个输入 token 都会生成 Key(K)和 Value(V)张量,这些张量被缓存起来以避免重复计算,称为 KV Cache。
传统框架(如 HuggingFace Transformers)的 KV Cache 存在三个核心问题:
| 问题 | 描述 | 影响 |
|---|---|---|
| 内存占用过大 | 单序列 LLaMA-13B 的 KV Cache 约 1.7GB | 并发能力极低 |
| 内存碎片化 | 连续物理存储导致大量"内存空洞" | GPU 利用率 < 40% |
| 缺乏共享机制 | 不同请求间相同前缀的 KV Cache 无法复用 | 计算资源浪费 |
1.2 PagedAttention 核心原理
vLLM 的 PagedAttention 借鉴了操作系统虚拟内存的分页思想,将 KV Cache 分割为固定大小的块(通常为 16KB)进行管理:
逻辑视角(连续): [Block0] [Block1] [Block2] [Block3] ... [BlockN]
↓ ↓ ↓ ↓
物理视角(分散): GPU Block #3 | GPU Block #0 | GPU Block #7 | CPU RAM Block #2
核心创新点:
- 逻辑块与物理块解耦:逻辑上连续的空间可以映射到物理上非连续的存储区域
- 按需分配:只保留当前计算所需的 KV 块在 GPU 显存中
- 高效换入换出:当 GPU 显存不足时,可以以块为单位将 KV Cache 迁移到 CPU 内存
- 消除外部碎片:块大小固定,物理内存不会产生小碎片
- 减少内部碎片:最后一个块不满时,浪费空间最多为一个块大小(约 16KB)
1.3 PagedAttention 计算流程
二、存储架构:缓存存在哪里
2.1 存储层级
vLLM 的 KV Cache 存储分为三层:
┌──────────────────────────────────────────────────────┐
│ Layer 1: GPU VRAM(主要存储) │
│ ├── 存储所有活跃序列的 KV Cache │
│ ├── 块大小:16KB / 32KB(可配置) │
│ └── 容量受 gpu_memory_utilization 控制 │
├──────────────────────────────────────────────────────┤
│ Layer 2: CPU RAM(Offloading 可选) │
│ ├── 当 GPU 显存不足时,LRU 驱逐的块可 Offload 到此 │
│ ├── 需通过 --kv-offloading-size 参数启用 │
│ └── 恢复时需要重新拷贝回 GPU │
├──────────────────────────────────────────────────────┤
│ Layer 3: Disk(swap_space 可选,v0.x 旧机制) │
│ ├── 通过 --swap-space 参数配置 │
│ ├── 主要用于多级调度时的极端情况 │
│ └── v1.x 推荐使用 CPU Offloading 替代 │
└──────────────────────────────────────────────────────┘
2.2 存储容量估算
单个序列的 KV Cache 估算公式:
KV Cache(字节) = 2 × num_layers × num_heads × seq_len × head_dim × dtype_bytes
以 LLaMA-13B(BF16)为例,典型配置:
num_layers = 40,num_heads = 40,head_dim = 128- 单 token 的 KV Cache:
2 × 40 × 40 × 2 × 128 = 819,200 bytes ≈ 0.8 MB - 1024 token 上下文:
0.8 MB × 1024 ≈ 800 MB/序列 - 并发 16 序列:
800 MB × 16 = 12.8 GB仅 KV Cache
以 Qwen-72B 为例,单序列 KV Cache 可达 3-5 GB,并发能力严重受限于 GPU 显存。
2.3 关键存储参数
| 参数 | 含义 | 建议值 |
|---|---|---|
--gpu-memory-utilization |
vLLM 预分配 GPU 显存比例 | 0.85-0.92(根据模型大小调整) |
--kv-cache-dtype |
KV Cache 数值精度 | fp8(需 H100+)/ fp16 / half |
--swap-space |
CPU 磁盘交换空间(GB) | 50-200 GB |
--kv-offloading-size |
v1.x CPU Offloading 容量 | 根据 CPU RAM 配置 |
--block-size |
单个 KV Cache 块大小 | 16(默认)/ 32 |
三、BlockManager 内存管理体系
3.1 核心数据结构
# vLLM 内部块管理结构(简化示意)
class Block:
def __init__(self, block_size=16):
self.block_id: str # 全局唯一块ID
self.device: str # "gpu" / "cpu" / "disk"
self.content: Tensor # 实际 KV 数据
self.last_accessed: float # LRU 追踪时间戳
self.num_tokens: int # 块内有效 token 数
self.ref_count: int # 引用计数(支持多序列共享)
class BlockTable:
# 逻辑块ID → 物理块ID 的映射表
logical_block_ids: List[int]
physical_block_ids: List[int]
3.2 块分配算法(LRU + RefCount)
vLLM 采用 LRU(最近最少使用) 策略进行块驱逐:
- 正常分配:请求到来时,从空闲块池中分配物理块,建立逻辑→物理映射
- 内存不足时的抢占顺序:
- 优先驱逐已完成请求的 KV Cache
- 其次驱逐低优先级等待请求
- 最后抢占正在运行但优先级较低的请求(触发重计算)
- RefCount 引用计数:被多个序列共享的块不会单独被驱逐(如 Prefix Caching 场景)
3.3 两种处理模式的对比
| 机制 | 触发条件 | 处理方式 | 代价 |
|---|---|---|---|
| 重计算抢占(RECOMPUTE) | GPU 显存不足以分配新块(默认) | 直接释放 KV 块,请求从头重算 | 重复计算,但恢复快 |
| KV Offloading(v1.x) | 显式配置 --kv-offloading-size |
将 KV 块迁移到 CPU RAM | 迁移开销大,但避免重计算 |
四、缓存池与缓存命中率优化
4.1 是否需要"缓存池"?
vLLM 内置了缓存池机制,无需额外部署。但理解其机制有助于调优。
vLLM 缓存池的本质:
┌─────────────────────────────────────────────────────┐
│ GPU Memory Pool(预分配) │
│ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │
│ │ Block#0│ │ Block#1│ │ Block#2│ │ Block#3│ ... │
│ └────────┘ └────────┘ └────────┘ └────────┘ │
│ ↑ ↑ │
│ 已分配给 空闲可用 │
│ 活跃序列 块池 │
└─────────────────────────────────────────────────────┘
4.2 Prefix Caching(前缀缓存复用)
vLLM 支持零开销的前缀缓存,当多个请求共享相同的前缀 prompt 时,直接复用已计算的 KV Cache:
# 启用前缀缓存
llm = LLM(
model="meta-llama/Llama-3-8B",
enable_prefix_caching=True # 关键参数
)
复用逻辑(Hash 匹配):
请求 0: "你好,请帮我写一封道歉信给[用户],内容是..."
→ 计算完整 KV Cache,hash = H0
请求 1: "你好,请帮我写一封道歉信给[公司],内容是..."
→ 前缀 "你好,请帮我写一封道歉信给" 完全相同
→ 复用前 16 个 token 的 KV Cache,仅计算新增部分
请求 2: "帮我翻译成英文:"Hello, how are you?""
→ 前缀完全不同,全部从头计算
Hash 匹配三要素:
- 父块哈希值(父块的 hash)
- 块内词元(精确 token 序列,防止哈希碰撞)
- 多模态附加哈希(多模态场景的额外 embedding hash)
⚠️ 已知限制:FP8 量化的 KV Cache 与 Prefix Caching 存在兼容性问题(见 GitHub Issue #3880)
4.3 提升缓存命中率的实践策略
| 策略 | 操作方法 | 预期收益 |
|---|---|---|
| 启用 Prefix Caching | --enable-prefix-caching 或代码中设置 |
前缀相同请求减少 50-90% 计算量 |
| 请求合并(Batching) | 将前缀相同的请求合并提交 | 自动提升前缀复用概率 |
| Prompt 模板化 | 固化系统 Prompt 格式,减少变动 | 提高长期缓存命中率 |
| 调整块大小 | --block-size 16(默认小块) vs 32(大块) |
小块提升细粒度复用,大块减少碎片 |
| 足够的 GPU 显存 | 提高 gpu_memory_utilization 至 0.90 |
减少 LRU 驱逐,保障缓存池深度 |
4.4 缓存池深度与命中率的关系
缓存池块数 = gpu_memory × gpu_memory_utilization / block_size
示例(24GB GPU,LLaMA-13B):
→ 23000 MB × 0.90 / 16 KB ≈ 1290 个块
→ 每序列约需 800 MB / 16 KB = 50 个块
→ 最大并发 ≈ 1290 / 50 ≈ 25 个序列
缓存池过小的表现:
- 频繁的 LRU 驱逐,缓存反复重建
- 高并发时吞吐量骤降
preemption告警频繁出现
五、生产环境调优参数汇总
5.1 启动参数配置建议
python -m vllm.entrypoints.openai.api_server \
--model meta-llama/Llama-3-70B-instruct \
--tensor-parallel-size 4 \
--gpu-memory-utilization 0.90 \ # 高并发场景可调至 0.92-0.93
--max-num-seqs 256 \ # 最大并发序列数
--block-size 16 \ # 小块提升复用率
--enable-prefix-caching \ # 启用前缀缓存
--kv-cache-dtype fp8 \ # FP8 量化 KV Cache(H100+)
# --kv-offloading-size 64 # v1.x 可选:启用 CPU Offloading
# --swap-space 100 # 旧版 swap 空间配置
5.2 调优流程建议
第一步:保守配置启动
gpu_memory_utilization=0.70,观察日志中实际 KV Cache 使用率
第二步:逐步提升至稳定阈值
每次 +0.05,调到不再出现频繁 preemption 为止
第三步:优化并发数
max_num_seqs 从 32 开始,逐步提升直到 GPU 利用率饱和
第四步:启用高级特性
enable_prefix_caching → fp8 量化 → kv_offloading(按需)
5.3 监控关键指标
| 指标 | 含义 | 健康范围 |
|---|---|---|
num_preemptions |
被抢占重计算的请求数 | 越少越好(<5%) |
gpu_memory_utilization |
实际 GPU 显存使用率 | 接近配置值 |
prefix_cache_hit_rate |
前缀缓存命中率 | 高并发时 >30% 优秀 |
cache_access_total |
总缓存访问次数 | 越高越好 |
num_blocks |
当前活跃块数 / 总块数 | 接近满负荷时注意驱逐 |
六、回答摘要
| 问题 | 答案 |
|---|---|
| vLLM 缓存 KV 是如何实现的? | 通过 PagedAttention,将 KV Cache 按 16KB 固定块管理,逻辑块映射到物理块,消除连续存储限制 |
| 需要准备存储吗? | 不需要额外准备。vLLM 启动时自动预分配 gpu_memory_utilization 比例的 GPU 显存作为 KV Cache 池 |
| 缓存存储在哪里? | 主要在 GPU VRAM;通过 --kv-offloading-size 配置后可 Offload 到 CPU RAM;极端情况可通过 --swap-space 使用磁盘 |
| 需要维护缓存池提升命中率吗? | vLLM 已内置缓存池管理。提升命中率的关键是:启用 Prefix Caching + 合并相同前缀请求 + 配置足够的 GPU 显存 |
参考来源
- vLLM 官方文档:https://docs.vllm.ai/
- vLLM Prefix Caching 设计文档:https://github.com/vllm-project/vllm/blob/main/docs/design/prefix_caching.md
- Block Manager 深度解析:https://cloud.tencent.com/developer/article/2623508
- vLLM 核心模块 kv_cache.py 源码解析:https://www.cnblogs.com/security-hyacinth/p/19771675
- vLLM PagedAttention 原理:https://blog.csdn.net/v_july_v/article/details/144218958
- KV Cache 三大问题与解决方案:https://blog.csdn.net/weixin_43667338/article/details/145038093
- GPU Memory 管理与 OOM 解析:https://blog.csdn.net/hadoop5ranger/article/details/148728475
- vLLM 吞吐量优化 10 个调优方法:https://new.qq.com/rain/a/20251009A07RV400
- FP8 KV Cache 实践:https://blog.csdn.net/weixin_28939623/article/details/156141283
- KV Offloading 机制解析:https://blog.csdn.net/qq_38662930/article/details/158843637
更多推荐


所有评论(0)