大模型推理引擎 KV Cache 三级存储架构:从 GPU HBM 到 CPU DRAM 再到 NVMe SSD 的工程实践
引言:当 KV Cache 成为推理成本的核心矛盾
2026 年,大模型推理成本的结构正在发生深刻变化。随着 MoE 架构的普及(DeepSeek-V3、Qwen3 等仅激活部分参数),计算成本持续下降,但显存中 KV Cache 的占用却在剧增。以 DeepSeek-V3 671B 为例(激活 37B,128 个稀疏 KV Head),长上下文 2048K 推理时,单层 KV Cache 就可能超过 120MB,128 层累积可达 GB 级别。与此同时,H2O(纯 GPU HBM)方案的最大容量被物理硬件死死限制。
KV Cache 三级存储架构(GPU HBM → CPU DRAM → NVMe SSD)正是为打破这一瓶颈而生。它通过在不同存储层级之间动态调度 KV Cache 页面,实现了 "用容量换成本、用延迟换吞吐" 的工程折中。
本文将从底层原理到生产级实现,深度拆解 KV Cache 三级存储架构的完整设计:Packed Page Table、L3 预取决策、换页优先级、NIXL/Mooncake 远程卸载、以及与硬件卸载引擎的协同。
一、KV Cache 的内部结构:理解每一字节
要理解三级存储,先要理解 KV Cache 本身的物理含义。对于 Transformer 模型,每个 token 在每个 attention 层都会产生 K 和 V 向量:
K[t] = W_k · x[t] # shape: [num_kv_heads, head_dim]
V[t] = W_v · x[t] # shape: [num_kv_heads, head_dim]
以 DeepSeek-V3(MLA 架构)为例:
| 参数 | 值 |
|---|---|
| num_kv_heads (压缩) | 128 |
| head_dim (latent) | 512 |
| pagesize (vLLM 默认) | 16 tokens/page |
| 数据类型 | fp16/bf16 |
每页 KV Cache 大小 ≈ num_kv_heads × head_dim × pagesize × 2(K+V) × dtype_size,即 128 × 512 × 16 × 2 × 2 = 4,194,304 字节 ≈ 4 MB/层/page。
这意味着:一层一个 token 的 KV 仅约 256 KB,但 128 层 32K token 上下文的 KV Cache 就可能突破 50 GB。
二、基础支撑:PagedAttention 与虚拟页表
三级存储建立在一个前提之上——KV Cache 必须以页为单位管理和寻址。这就是 vLLM 提出 PagedAttention 的核心贡献。
2.1 PagedAttention 内存模型
传统方式为每个 request 预留连续最大长度显存(浪费 60%+)。PagedAttention 将 KV Cache 拆成固定大小的逻辑页,通过 Block Table(页表)映射逻辑块号到物理页框:
class PagedAttentionManager:
def __init__(self, page_size=16, gpu_page_count=1024, cpu_page_count=4096):
self.page_size = page_size # tokens per page
self.gpu_allocator = PageAllocator("GPU", gpu_page_count)
self.cpu_allocator = PageAllocator("CPU", cpu_page_count)
# Block Table: seq_group -> layer -> logical_block_id -> physical_page_id
self.block_table: Dict[str, Dict[int, List[int]]] = {}
def allocate_request(self, request_id: str, prompt_len: int) -> BlockTable:
pages_needed = ceil(prompt_len / self.page_size)
pages = self.gpu_allocator.allocate(pages_needed)
self.block_table[request_id] = pages
return pages
2.2 缺页异常(Page Fault)与换页触发
当 GPU KV 内存不足以支撑新的 prefill 或 decode 步骤时,触发 L2(CPU 内存)调度:
- L1_evict → L2:将 GPU 上的冷页驱逐到 CPU DRAM
- L2_fetch → L1:解命中 CPU 上的页需要被访问时提回 GPU
- L3(NVMe):当 CPU DRAM 也溢出时,进入 L3 NVMe 卸载
触发条件:
GPU allocated_pages / GPU total_pages > 80% → evict 冷页到 CPU
CPU allocated_pages / CPU total_pages > 70% → evict 至 NVMe (可选)
next_attention_page not in GPU page_table → page fault → fetch from L2/L3
三级存储调度策略
3.1 访问热度与空闲空闲 LRU-K
三级调度的核心在于准确区分 "热 KV 页" 和 "冷 KV 页"。最常用的启发式方法是 LRU(Least Recently Used) 及其变体。
在多请求并发场景下,LRU-K(跟踪每个页最近 K 次访问间隔)能更准确地识别 "即将被注意力扫描" 的页面:
class LRUKScheduler:
def __init__(self, k=2):
self.k = k
self.access_history: Dict[int, List[float]] = {} # page_id -> access timestamps
def record_access(self, page_id: int, ts: float):
if page_id not in self.access_history:
self.access_history[page_id] = []
self.access_history[page_id].append(ts)
# 仅保留最近 K 次
if len(self.access_history[page_id]) > self.k:
self.access_history[page_id] = self.access_history[page_id][-self.k:]
def predict_soon_use(self, page_id: int) -> bool:
"""基于历史间隔预测近期是否会再被访问"""
history = self.access_history.get(page_id, [])
if len(history) < self.k:
return False
# 访问间隔稳定且小于阈值 → 预测持续活跃
interval = history[-1] - history[-2]
return interval < 100 # ms 阈值
3.2 预加载与分批 CPU→GPU 拷贝
GPU → CPU 的拷贝延迟大约在 20-40 GB/s(PCIe Gen4 x16),而 NVMe → CPU 在 7-14 GB/s(PCIe Gen4 NVMe)。为了掩盖这些延迟,生产者线程通常会采用异步流水线:
# 简化的三级调度伪代码
async def triple_level_kv_manager():
gpu_free = gpu_allocator.free_pages
while running:
# 1. 预取:从 CPU 向 GPU 按需取页
pending_fetch = prefetch_queue.drain(limit=8)
for page, dest_gpu_addr in pending_fetch:
await dma_engine.copy_h2d(page.cpu_buffer, dest_gpu_addr)
gpu_allocator.register(page.id, dest_gpu_addr)
# 2. 内存压力检查
if gpu_allocator.utilization() > 0.85:
# 识别冷页(LRU 尾部)
cold_pages = lru_queue.get_tail_n(16)
for pg in cold_pages:
cpu_slot = cpu_allocator.allocate(1)
if cpu_slot:
await dma_engine.copy_d2h(pg.gpu_buffer, cpu_slot.buffer)
gpu_allocator.free(pg.id)
else:
# CPU 也满了 → 尝试 NIXL 远程卸载
nixl.offload(pg, remote_node="cpu-pool-1")
# 3. Swap Scheduler
await asyncio.sleep(0.001)
3.3 批处理调度与 KV Cache 重用的耦合
在实际调度器中,三级存储还需要与 Continuous Batching 和 Prefix Caching(Prompt Cache) 深度耦合:
- Prefix Cache:相同前缀的 prompt 可直接复用已计算好的 KV 页,仅在 GPU 命中时使用前缀。如果前缀 KV 已被驱逐到 CPU,则复用链路为 CPU→GPU→Attention,相比纯 GPU miss 仍快 10× 以上。
- Chunked Prefill:将长 prefill 拆为小 chunk,与 decode 三级交织调度,期间 KV 页可在不同层级间流动。
四、远程 CPU 卸载:Mooncake 与 NIXL 的工程实践
三级存储的极致形态是将 "CPU DRAM" 从本地延伸到远程 CPU 内存池(Disaggregated Memory)。这是 Mooncake(LMSYS 团队)与 NIXL(NVIDIA)的核心设计:
4.1 NIXL 的 KV Cache 卸载原理
┌──────────────────────────────────────────────────────┐
│ KV Cache 卸载目标: │
│ 当单机 GPU 内存不足时, │
│ 通过 RDMA/PCIe 将 KV 页远端放置到: │
│ - 本地 CPU DRAM(L2) │
│ - 远程 CPU 内存池(L2') │
│ - NVMe KV Store(L3) │
│ - 远程 GPU Pool(L1') │
└──────────────────────────────────────────────────────┘
NIXL 执行器的工作流:
- 预热(Prefetch):在请求到达前将对应 KV 预发到 GPU
- 解耦执行(Compute-Transfer Overlap):将 KV 上传与 GPU 计算在时间上重叠
- 属性感知调度(Affinity-Aware):KV 访问遵循 "热数据近 GPU、冷数据远 SSD" 的亲和力
- Prefill 节点生产 KV → 通过 Mooncake Connector 发送 → Decode 节点消费 KV
- KV Transfer 内核层做 RDMA 直接内存写入
- Prefill 节点的 KV 不再占用 GPU 内存,直接卸载到 CPU 节点
- 通过 Radix Tree 支持更丰富的 KV Cache 共享模式(共享前缀、beam search 副本)
- Mooncake Transfer Engine 后台预取 KV Cache 到 GPU
- SGLang/vLLM 后端共享同一 KV Cache 存储后端
- 支持 "分形 KV Cache"(Hierarchical KV Cache):GPU → CPU → Remote CPU → NVMe
- 4 tokens/page:细粒度,适合 max_seq_len < 8K 的场景;但 page table overhead 大
- 16 tokens/page(vLLM 默认):适应性好,但是批量拷贝时效率损失大
- 64 tokens/page:高页大小,适合长上下文场景(≥128K),CUDA transfer 效率高;但细粒度预测的页命中率下降
- 每个投机步骤前,记录当前 KV 页表的快照( Copy-on-Write page table)
- 验证投机 token 时合并正确的 KV,回滚错误分支(仅修改页表引用元数据)
- 这一步在三级存储版本中具有额外复杂度——被回滚的页可能在 CPU 上的缓存也需要同步版本标记
- PagedAttention 虚拟化:以固定页为调度单位,消除连续分配碎片
- LRU-K 热度预测:区分冷 KV 与热 KV,决定页驻留层
- RDMA 异步预取:用生产者-消费者模型掩盖传输延迟
- Multi-Version Rollback:与投机快照深度协同拒绝后的快速回滚
- NUMA 亲和 + Adaptive Page Size:最大化 DMA 带宽效率
Benchmark Attention:三星 HBM4 2026 年在推理推理性能表现如下(8×H200 SXM5)
| 场景 | 显存限制(HBM only) | 三级存储扩容 | TTFT 延迟增长 | 吞吐提升 |
|---|---|---|---|---|
| 70B 中文 32K 连续 32 请求 | 11.2 req/s (HBM 141GB) | 3× CPU DRAM | +4.2 ms | +31% |
| 70B 中文 128K 并发 128 请求 | 18.6 req/s (HBM only) | 8× CPU DRAM | +12.8 ms | +115% |
| 70B 混合任务 32+128K 持续 2h | 9.4 req/s | 动态 KV 池 | +2.1 ms | +53% |
| 100B+ MoE EP32 真实流量 | Out of Memory | NVMe 溢出 | +45 ms | 稳定运行 |
注:以上为理论峰值与实验室对比数据,实际性能受请求混合度、序列长度分布、Triton Kernel 优化等影响。
五、2026 年生产级方案对比
截至 2026 年底,这一领域的生产级方案呈现出四足鼎立:
5.1 vLLM v1 + Mooncake Disaggregated Prefill
vLLM 0.7+ 版本已支持通过 Connector 协议集成 Mooncake,实现 Prefill-Decode 分离部署(P/D Disaggregation):
5.2 SGLang + DeepEP 集成
SGLang 0.4 后引入的 RadixAttention(Radix Tree)进一步将前缀复用从单 node 扩展到多机:
5.3 Dynamo(NVIDIA 新方向)
NVIDIA Dynamo 框架更深入地使用了 NIXL 来执行自动 KV Cache 路由:
5.4 方案落地的关键指标
| 决策维度 | GPU HBM Only | DRAM Swapping | 三级存储推荐 |
|---|---|---|---|
| 延迟敏感度 | < 10ms TTFT | 20-50ms TTFT | 15-30ms TTFT |
| 吞吐撑大倍数 | 1× | 1.3-1.8× | 1.5-3.5× |
| 最大并发数 | 显存硬上限 | DRAM 上限 | CPU 内存池 |
| 成本/Token | 极高 | 中 | 低 |
| 实现复杂度 | 简单 | Kernel 编码 | 需要网卡/CS |
六、工程优化实战:从理论到稳定输出
6.1 页大小选择的艺术
在三级存储中,页大小直接影响 DMA 传输效率与淘汰精度:
生产建议:在线上部署中,通常采用自适应 page size:
| 模型上下文 | 推荐 page size |
|---|---|
| ≤ 8K | 8-16 tokens |
| 32K-128K | 16-32 tokens |
| ≥ 128K | 32-64 tokens |
6.2 Prefetch 预测模型
根据未来 4-8 个 token 生成的注意力访问模式,构建一个轻量级 Prefetch Strategist 模块,预测下一批将需要的 KV 页:
class PrefetchPredictor:
def predict_next_pages(self, active_seqs: List[Sequence]) -> List[int]:
"""基于当前 batch 的注意力访问模式,预测即将被访问的 KV 页"""
candidate_freq = defaultdict(int)
for seq in active_seqs:
# 本轮已经访问的最后一个 page
last_page = seq.current_page
# 后续页预测(按 chunked prefill 步长步进)
chunk_step = 256 if seq.is_prefill else 2 # decode 每次 1-2 页
for lookahead in range(1, 8):
next_page = last_page + lookahead * chunk_step
candidate_freq[next_page] += (8 - lookahead) # 距离越近、权重越高
# Top-K 预测页
topk = heapq.nlargest(4, candidate_freq.items(), key=lambda x: x[1])
return [p for p, _ in topk]
6.3 NUMA 拓扑感知的 CPU 页放置
在多路 CPU 系统中(如 2× Intel Xeon 4th Gen Sapphire Rapids 或 AMD EPYC 9004),NUMA 节点亲和性对 KV Cache 的 DMA 性能影响巨大:
错误配置:CPU 页在 Node 0,GPU 连接在 Node 1
→ QPI/UPI 跨越延迟 ~100ns,DMA 吞吐量下降 40%
正确配置:使用 nvidia-peermem 自动绑定 NUMA 拓扑
→ GPU 与最近 NUMA 节点对齐,DMA 带宽提升 3.1×
生产环境启用:
# 检查 GPU NUMA 节点
nvidia-smi topo -m
# 绑定 CPU 分配池到最近的 NUMA 节点
numactl --membind=0 --cpunodebind=0 python -m vllm.entrypoints.openai.api_server ...
七、深度 QKV Cache 投机缓存(Speculative KV Reuse)
三级存储与投机解码(Speculative Decoding)结合时,最棘手的问题是:被拒绝的投机 token 产生的 KV 也需要被清理或回滚。完整的解决方案是 Multi-Version Page Table(MVCC 思想):
class KVRollbackHandler:
def speculative_push(self, page_table: BlockTable, token_id: int) -> VersionId:
"""为投机 push 前的 KV 页打上快照版本"""
version = VersionId(self.snapshot_counter.increment())
page_table.mark_snapshot(version, token_id)
return version
def rollback(self, page_table: BlockTable, version: VersionId):
"""版本标记回滚 → 关联 L2/L3 缓存也回到上一版本"""
page_table.restore_snapshot(version)
# 通知 CPU 内存池清理对应快照
cpu_pool.evict_snapshot(version)
八、未来展望:从三级到 KV-Aware 架构系统化
KV Cache 三级存储正在推动推理基础设施迈向 KV-Aware 系统化架构:
| 时间线 | 技术方向 | 影响 |
|---|---|---|
| 2026 年末 | NIXL 开源成熟 | KV 异构卸载标准化 |
| 2027 年上半年 | HBM4 商用 + CXL 3.0 内存池 | 四级存储升级为 CXL 内存池(L2.5) |
| 2027 年下半年 | GPU 原生 KV Direct Access(NVIDIA Rubin) | GPU 可直接 DMA 访问 NVMe,省去 CPU 拷贝 |
| 2028 年 | KV Cache 联邦学习(Sandbox KV) | KV 加密跨节点传输,实现隐私计算推理 |
其中最具革命性的方向是 GPU 原生 KV Direct Attention(或 NVIDIA 所称的 "KV Cache Virtualisation" 支持),即 GPU 内核可直接对分布在不同层级存储中的 KV 执行 attention 计算而不影响带宽——这将从硬件层面实现真正的 "存储级注意力"。
总结
KV Cache 三级存储架构(GPU HBM → CPU DRAM → NVMe SSD)已经从 2025 年的前沿研究落地为 2026 年生产级推理基础设施的核心组件。其核心设计逻辑可归纳为:
在大模型推理降本的大背景下,三级 KV 存储架构不仅是工程优化,更是将 "HBM 稀缺性" 转化为 "可调度性" 的关键基础设施。在 HBM4 大规模部署与 CXL 3.0 普及的 2027 年,一个完整透明、跨节点、存储层次感知的 KV Cache 调度系统,终将成为推理平台的默认底座。

发表评论 取消回复