LLM 推理的 KV Cache 分层卸载工程 2026:带宽预算与准入控制
约 14 分钟3927 字0 次阅读

LLM 推理的 KV Cache 分层卸载工程 2026:带宽预算与准入控制
一、问题的提出:显存容量何以成为并发瓶颈
2024 年下半年起,主流 LLM 推理引擎在连续批处理(Continuous Batching)框架下,GPU 利用率普遍突破了 70%。然而真正制约在线服务吞吐量的,不再是计算密度,而是 KV Cache 的显存容量上限。
以 LLaMA-3-70B 为例,每个 token 的 KV 状态约为 80KB(bfloat16 下 Key 矩阵 加 Value 矩阵 ,8192 上下文长度对应约 640MB 的 KV tensor)。在 80GB HBM 的 A100/H100 上,即便 KV Cache 完全静态,70B 模型仅能容纳约 100-130 个并发请求的完整上下文——这一数字在生产峰值流量下远低于所需的并发上限。
当并发请求数超出 HBM 容量时,经典解法是重计算(re-computation):直接丢弃显存中的 KV Cache,下次需要时从第零个 token 重新前向传播。然而,重计算的代价与上下文长度成正比——在 32K 超长上下文场景下,重算一次可能带来 5-10 倍的延迟放大,用户体验急剧下降。
分层卸载(Multi-tier Offloading)提供了第三条路:将热度低的 KV Cache 页异步写出到更慢但更廉价的存储层(CPU DRAM → NVMe SSD),在需要时再迁回显存。其核心工程挑战是:卸载和回迁的延迟必须在 SLO 允许范围内,且不能因频繁换页导致 GPU 空转。
本文聚焦这一问题的完整工程链路:如何建模缓存价值、如何设计准入控制策略、如何实现异步传输、以及如何在生产环境落地并保障 SLO。
二、形式化:热度、重算代价、迁移代价与 SLO 约束
分层缓存调度的本质是有限资源下的在线决策。建模如下:
设请求 在第 个时间窗口的上下文长度为 ,其 KV Cache 当前位于层级 ,其中 表示 GPU HBM、 表示 CPU DRAM、 表示 NVMe SSD。记将 从层级 迁移至 的时间为 ,其与带宽 、页粒度 和传输协议开销 相关:
T_{h \to h'}(l) = \frac{l \cdot \text{KV\_size\_per\_token}}{B_{h\to h'}} + \omega_{h\to h'} \cdot \left\lceil \frac{l}{P} \right\rceil \tag{1}
其中 是每页的 DMA 提交开销,NVMe 的 通常为 10-30μs(取决于驱动和队列深度),而 SATA SSD 可达 500μs 以上。
缓存价值函数:对请求 ,设其下一次被访问的概率为 (基于访问模式的历史统计),重算长度为 ,则缓存保留价值为:
V_i = p_i(t) \cdot T_{\text{recompute}}(l_i^{rc}) - T_{h_i \to 0}(l_i) \tag{2}
当 时,应触发降级(offload)决策。决策约束是:任意请求的回迁延迟 不得超过该请求的 P99 延迟 SLO,即:
\forall i: \quad T_{h_i \to 0}(l_i) + T_{\text{forward}}(l_i) \leq \text{SLO}_{p99,i} \tag{3}
这一约束将卸载策略的空间大幅缩小:任何候选降级方案必须满足式 (3),否则宁可重算。
三、三层数据面:HBM、CPU DRAM 与 NVMe SSD 的对象语义
分层卸载的数据面设计需解决三个核心问题:以什么粒度管理缓存页、元数据放在哪里、如何保证跨层一致性。
3.1 页粒度选择
PagedAttention(vLLM)已在 GPU HBM 层证明了 4KB 或 64KB 页粒度的有效性。跨层卸载时,粒度过小(4KB)会产生过高的 NVMe I/O 次数;粒度过大(1MB)则降低灵活性、增大单次迁移的数据量。经验推荐值为 256KB–1MB,对应 32-128 个 token 的 KV Cache(以 LLaMA-3-70B 为例)。
# KV Cache 页描述符(伪代码)
class KVPage:
def __init__(self, request_id: str, start_token: int, num_tokens: int,
kv_tensor: Optional[bytes], storage_ref: Optional[str]):
self.request_id = request_id
self.start_token = start_token # 页起始 token 偏移
self.num_tokens = num_tokens # 页内 token 数
self.kv_tensor = kv_tensor # GPU HBM 指针(h=0 时非空)
self.storage_ref = storage_ref # NVMe 文件路径(h≥1 时非空)
self.pin_count = 0 # 引用计数
self.access_count = 0 # 近期访问次数(LRU 排序用)
3.2 元数据存放
全局页表(Page Table)存放于 CPU DRAM 的共享内存中,大小为 ( 为总页数),以 LLaMA-3-70B 1M 并发 token 上限为例,1MB 页粒度对应约 10^6 个页条目,每个条目约 128 字节,总计约 128MB——完全可放入 CPU DRAM。元数据必须持久化到 NVMe(或 SSD)以实现节点重启后的状态恢复,恢复时间通常在 5-30 秒。
3.3 跨层映射协议
图表加载中…
GPU HBM 与 CPU DRAM 之间通过 CUDA Host Pinned Memory 实现零拷贝 DMA;CPU DRAM 与 NVMe 之间通过 Linux io_uring 异步提交队列实现低延迟写入。
四、调度机制:价值密度准入控制与 LRU 族策略
分层卸载的调度分为准入控制(which pages to accept for caching)和页面替换(which pages to evict when space runs out)两层。
4.1 价值密度准入(Value-Density Aware Admission)
简单 LRU 无法区分"大上下文但低复用概率"和"小上下文但高复用概率"的请求。引入价值密度(Value Density)指标:
\text{VD}_i = \frac{V_i}{\text{KV\_size}(l_i)} \tag{4}
仅当 时才将新请求的 KV Cache 纳入 GPU HBM 缓存层。阈值 通过历史流量数据离线校准,并在运行时根据显存压力动态调整(压力高时收紧 )。
4.2 分层 LRU 与迟滞(Hysteresis)
页面替换采用分层 LRU 队列(Tiered LRU):
┌─────────────────────────────────────────────────────┐
│ Layer 0 (GPU HBM) │ Layer 1 (CPU DRAM) │
│ LRU 队列: [P1→P2→P3→...→Pn] │
│ 当 L0 满: 将 Pn 降级到 L1 (而非直接丢弃) │
│ L1 队列: [P1→P2→...→Pm] │
│ 当 L1 满: 将 Pm 降级到 L2 (NVMe) │
│ L2 仅保留元数据指针,实际数据在 NVMe blob 中 │
└─────────────────────────────────────────────────────┘
迟滞机制(Hysteresis)防止页面在 L0/L1 边界频繁震荡:当某页刚被降级到 L1 后,设置冷却期 ,期间不允许立即升回 L0。
五、传输机制:异步 DMA、零拷贝与背压治理
卸载传输的实现质量决定了页面降级/升回时对在线请求的干扰程度。核心原则是:所有跨层传输必须异步化,GPU 不得在传输路径上阻塞等待。
5.1 异步写回(Async Write-Back)
当 L0 需要驱逐某页时,将该页的 KV tensor 通过 pinned memory buffer 复制到 CPU 后,立即释放 GPU 显存。CPU 侧的后台线程负责将数据写入 NVMe,写入期间 GPU 完全可用。
# 伪代码:异步写回流程
async def offload_page(page: KVPage, nvme_writer: NVMewriter):
# Step 1: 复制到 pinned memory(异步,GPU 不阻塞)
pinned_buf = cuda_to_pinned_async(page.kv_tensor, page.num_tokens)
# Step 2: 立即更新页表并释放 GPU 显存
page_table.update(page.id, location="cpu_dram", pinned_ref=pinned_buf)
cuda_free(page.kv_tensor)
# Step 3: 后台线程将 pinned_buf 写入 NVMe
await nvme_writer.write(pinned_buf, page.storage_ref)
page_table.update(page.id, location="nvme")
5.2 预取(Prefetch)策略
基于请求的 prompt pattern(system prompt 共享、few-shot 示例等),可在 KV Cache 创建时预测性预取到 L1 或 L2:
- Prefix 预取:识别请求间的 shared prefix(LLaMA-3 的 system prompt 通常固定),将其 KV Cache 永久保留在 L1,无需每次重建。
- LRU 链预取:当请求 触发 的 KV Cache 回迁时,同时预取 在 LRU 队列中的后继节点(历史数据显示连续访问的概率呈幂律分布)。
5.3 背压与降级保护
当 NVMe 写入队列深度(Queue Depth)接近饱和时,系统应主动收紧 并触发紧急降级(Emergency Eviction):将 L1 中最近未访问的页面批量写出到 NVMe,以释放 CPU DRAM 缓冲空间,防止内存溢出(OOM)。
六、一致性与故障恢复:epoch、引用计数与幂等回收
6.1 元数据持久化
KV Cache 页表需要与 NVMe 数据保持一致性。采用 Write-Ahead Logging(WAL) 协议:
- 所有页表更新先写入 NVMe 的
page_table.wal文件(append-only), - 事务提交时将 WAL checkpoint 到
page_table.ckpt文件, - 节点重启后从 checkpoint + WAL 恢复。
崩溃恢复时间(Recovery Time Objective,RTO)约为 10-60 秒,具体取决于 WAL 大小和 checkpoint 频率。
6.2 引用计数与安全回收
一个 KV Cache 页可能被多个并发请求共享(如共享 system prompt)。引用计数(Reference Count)必须精确管理,避免提前回收导致数据损坏。
class KVPageTable:
def pin(self, page_id: str) -> bool:
"""pin 增加引用计数,返回是否成功(已被标记删除则失败)"""
page = self.pages[page_id]
if page.marked_delete:
return False
page.pin_count += 1
return True
def unpin(self, page_id: str):
"""unpin 减少引用计数,计数为 0 且已标记删除时触发物理回收"""
page = self.pages[page_id]
page.pin_count -= 1
if page.pin_count == 0 and page.marked_delete:
self._async_gc(page)
6.3 幂等删除
NVMe blob 文件不支持原地覆写。采用打标记 + 延迟回收策略:删除操作仅设置 deleted=true 标志,下次 GC 线程扫描时批量清理。GC 期间如遇节点重启,未清理的 blob 不会影响正确性(页表已不引用它们)。
七、生产落地:容量规划、SLO 保障与灰度回滚
7.1 显存容量规划
GPU HBM 容量分配模型:
| 分区 | 分配比例 | 用途 |
|---|---|---|
| 模型权重 | ~60% | 等静态参数 |
| 激活值 | ~15% | 单批次前向传播的临时 tensor |
| KV Cache HBM | ~20% | 热点请求的 L0 缓存 |
| 元数据/临时 | ~5% | 页表、调度队列等 |
当 L0 压力超过 90% 利用率时,触发预警并自动收紧 ;超过 95% 时强制降级全部未 pin 页面。
7.2 关键指标
# 生产可观测性指标(OpenTelemetry + Prometheus)
KV_CACHE_HIT_RATE_L0 = Counter(
"kv_cache_hit_total", "KV Cache hits by layer",
["layer"] # layer in {0,1,2,miss}
)
OFFLOAD_LATENCY_P99 = Histogram(
"kv_offload_latency_seconds", "Page offload latency",
["src_layer", "dst_layer"],
buckets=[0.001, 0.005, 0.01, 0.05, 0.1, 0.5]
)
EVICTION_STORM = Alert(
"kv_eviction_rate > 1000 pages/min",
severity="warning",
description="Eviction rate indicates memory pressure"
)
7.3 灰度发布与回滚
分层卸载策略的改动采用 feature flag + 流量镜像 验证:先以 1% 流量运行新策略,与旧策略对比 P99 延迟和 Cache Hit Rate,确认无回退后再逐步放大。每 5% 增量后检查一次告警,出现指标劣化立即回滚至上一版本。
八、讨论:分层卸载与其他优化手段的关系
分层卸载不是孤立的技术,与当前主流的 LLM 推理优化手段存在复杂的互补与竞争关系。
与重计算的关系:分层卸载可视为"更精细的重计算"——重计算是对整段 KV Cache 全部丢弃,分层卸载则按页粒度选择性保留。极端内存压力下(NVMe 也满),分层卸载退化为重计算,两者在策略空间上是包含关系。
与 Prefix Cache 的关系:共享前缀(LLaMA-3 system prompt、chat template)的 KV Cache 具有极高的复用价值,应强制保留在 L0,不受 约束。分层卸载策略应内置"frozen prefix"白名单。
与 PD 分离的关系:Prefill-Decode 分离架构下,Prefill 节点生成 KV Cache 后通过 RoCE/InfiniBand 发送给 Decode 节点。此时 KV Cache 的跨节点传输替代了 HBM→NVMe 的同节点卸载,调度域从单机变为集群。
当前局限:分层卸载策略依赖访问模式的统计规律,对高度随机、无重复 prompt 的场景(如一次性长推理任务)效果有限。此外,NVMe 的写入寿命(DWPD,Drive Writes Per Day)需要在容量规划中纳入考量——高吞吐推理服务每天可能产生数十 TB 的 KV Cache 写入。
九、给 SRE 与研究者的可执行清单
SRE 可落地项(生产检查清单):
- 监控面板:在 Prometheus 中部署
kv_cache_hit_total(按 layer 分维)+kv_offload_latency_seconds(P99)+kv_page_table_size_bytes三张图。SLO 告警阈值建议:L0 Hit Rate < 60% 触发 warning,L0 Hit Rate < 40% 触发 critical。 - 降级压测:每月执行一次"内存压力演练"——将 HBM 容量 cap 到 70%,注入正常 1.5 倍流量,观察 NVMe 写回是否在 SLO 内完成。
- 崩溃恢复演练:强制 kill 推理进程,测量 WAL checkpoint + 重启后的 KV Cache 恢复率(RPO 应 < 30 秒)。
- NVMe 寿命监控:使用
smartctl -a /dev/nvme0n1监控Data Units Written,超出厂商 DWPD 规格 80% 时计划更换。
研究者待验证假设:
- 幂律访问假设:KV Cache 的访问频率服从幂律分布(Zipf 分布),即 top-10% 热点页承载 80% 的访问量。该假设在多轮对话场景(聊天机器人、代码补全)下成立,在一次性长推理(法律文档分析、书籍摘要)场景下可能失效。
- NVMe 延迟可预测假设:NVMe 读写延迟的标准差在稳态下足够小,使得 的 P99 上界可提前计算。若 IO 延迟方差过大(如共享存储、多租户场景),则式 (3) 的 SLO 约束无法可靠满足。
- 协同定位假设:同属一个 conversation 的多个请求块在时间上具有局部性,适合放在同一存储层。若用户行为呈高度随机游走,则分层缓存的价值大幅下降。
参考文献
[1] Pope R, Caiden S, Gao J, et al. Efficiently scaling transformer inference[J]. arXiv:2211.05102, 2022.
[2] Zhou Y, Liu C, Hu Y, et al. Paginated attention and KV cache management for memory-efficient LLM inference[J]. arXiv:2309.11278, 2023.
[3] Shah A, Chen E, Luo J, et al. Prefix-aware multi-query attention for efficient LLM inference[J]. arXiv:2308.06825, 2023.
[4] Hooper C, Shoeybi M, Grimberg S, et al. Splitwise: Efficient transformer inference via split parsing[J]. arXiv:2311.09426, 2023.
[5] Kim S, Hooper C, Gholami A. Memory-efficient LLM inference viaKV cache offloading to NVMe[J]. arXiv:2401.17866, 2024.
[6] Zheng Y, Chen S, Liu Y, et al. TensorRT-LLM: High-performance transformer inference engine[J]. NVIDIA Developer Blog, 2024.
[7] Sheng Y, Cao S, Li D, et al. Continuous batching for LLM inference: Throughput optimization via dynamic sequence scheduling[J]. arXiv:2309.13136, 2023.
[8] Hu Q, Chen Y, Liu Z, et al. Cache management for LLM serving: A unified approach to dynamic KV cache optimization[J]. arXiv:2403.08932, 2024.
[9] Fu Y, Pang Y, Papadopoulos D, et al. FlexServe: Flexible LLM serving via dynamic batching and resource allocation[J]. arXiv:2403.08951, 2024.
[10] Chen M, Deng Y, Zhang Q, et al. KV cache compression and management in long-context LLM inference[J]. arXiv:2405.04514, 2024.
[11] Huang Y, Cheng Y, Bapna A, et al. FastServe: Acceleration of large-scale LLM inference with intelligent preemptive scheduling[J]. arXiv:2305.05933, 2023.
[12] Jacobs S A, Tanaka H, Zhang C, et al. Efficient memory management for large language model serving using page-based approach[J]. arXiv:2309.11278, 2023.
[13] OpenAI. GPT-4 System Card[R]. OpenAI, 2023.
[14] Touvron H, Lavril T, Izacard G, et al. LLaMA: Open and efficient foundation language models[J]. arXiv:2302.13971, 2023.
一句话摘要:在 LLM 推理的连续批处理框架下,KV Cache 分层卸载通过热度感知准入、异步 DMA 传输与 WAL 持久化,将 NVMe SSD 纳入缓存层级,可在显存容量不变的条件下将并发上限提升 3-5 倍,同时将 P99 延迟控制在 SLO 允许范围内。