KV 缓存淘汰 KV Cache Eviction

KV Cache Eviction / KV Cache Management Policy

KV 缓存淘汰是在 GPU 显存不足、推理请求并发多的时候,以什么策略丢弃哪些 KV Cache 页来腾空间给新请求的调度策略,常见有 LRU、优先级分级、滑动窗口、分层分区,直接决定了单 GPU 的并发上限和 OOM 故障率。

详细解释

**KV Cache Eviction(KV 缓存淘汰 / KV Cache 管理策略)**一句话:一张 A100-80G 总共就 80GB HBM,模型权重占了 70B BF16 的 140G(需要多卡)或者 7B FP8 的 7GB,剩下的空间用来放所有并发请求的 KV Cache;如果 100 个并发请求的 KV Cache 加起来超过了剩余显存,你作为调度器必须扔掉一些「相对没用的」KV Cache 页来给新请求腾空间,扔什么、什么时候扔、扔多少——这套规则就是 KV Cache 淘汰策略。 没有淘汰策略的原生推理引擎,KV Cache 超了就直接 OOM Crash,用户看到 500;好的淘汰策略配合 vLLM PagedAttention 能让单 GPU 的并发数(TPS 上限)提升 2 至 4 倍,OOM 故障率从 1% 降到 0.01%。

KV Cache 淘汰的底层前提是 PagedAttention(vLLM 2023 年提出的技术)——它把 KV Cache 切成固定大小的物理页(类似 OS 的内存页),用页表把「逻辑上连续的 Token 序列」和「物理上零散的 KV 页」映射起来;只有在分页机制的前提下,才能像 OS 做虚拟内存一样去做「LRU 页淘汰、页换入换出、共享物理页」这些事情,没有分页的话 KV 是一块连续大内存,根本没法做细粒度淘汰,只能整请求 kill。

唯元智创 自己的推理集群采用「四分层三级淘汰策略」:20% HBM 固定放钉住的前缀(Pinned Prefix,永远不淘汰)、40% 放活跃请求的 KV Cache、30% 放滑动窗口式的历史上下文窗口(超过 32K 就从最老的页滚着扔)、剩下 10% 做缓冲;任何新请求来时按优先级找可释放的页,OOM 概率百万分之一以下,单 4090 跑 7B FP8 模型稳定 120 并发不崩。

六大主流 KV Cache 淘汰策略对比(2025 年选型)

策略做了什么适合场景实现复杂度并发上限提升倍率典型引擎内置支持
① 全 Kill(没有淘汰,超了直接 OOM)任何请求进来有空间就放,没空间新请求直接返回 OOM 500极低并发的原型 / 个人部署(一张 4090 跑 5 个并发以内)极低,没有代码×1(基线)HuggingFace generate 默认就是这个
② 整请求级 FIFO/LRU(整请求整体扔掉)按请求整个为粒度,先进先出 / 最少最近使用,整个请求的所有 KV 页一起扔低并发 + 请求长度都差不多的简单场景×1.2–1.5vLLM 早期版本
③ 页级 LRU(单页粒度最近最少用)PagedAttention 基础上,每个 KV 页独立 LRU,最久没被访问的单页先扔大多数通用推理场景,长短请求混合×2–3vLLM 新版、SGLang 默认
④ 分层优先级(Pinned / LRU / Sliding-Window 三区)三区独立管理:Pinned 永久不扔、LRU 普通热页、SW 滚着扔超过窗口的老页高并发生产服务,有固定常用前缀场景×3–4.5SGLang 2025 / 唯元内部推理引擎
⑤ 重要性打分 + 启发式淘汰每个 KV 页按「最近 Attention Score + 所在层深度 + 是前缀还是生成 Token」算一个重要性分,先扔分最低的超长上下文 100K+、推理质量对 KV 丢页敏感的场景极高×3.5–5学术前沿 + 超大上下文模型(Kimi 1M 上下文)
⑥ 分层内存(HBM + CPU DDR + NVMe)三级换页GPU HBM 存热页、CPU 内存存温页、本地 NVMe 存冷页,类似 OS 的虚拟内存 swap超长上下文 1M Token 场景,单卡放不下整份 KV极高(驱动/通信复杂)×5–10SGLang / TensorRT-LLM 企业版

落地 KV 淘汰的 6 条工程纪律

  1. 永远不要让「正在流式输出中的活跃请求」的 KV 页被淘汰——用户正打字的中途把它的 KV 扔了,流式输出立刻中断卡死或胡言乱语;活跃请求的 KV 页优先级最高,只有当整个 GPU 内存 100% 满并且没有可扔的非活跃页时,才允许先 kill 掉一个优先级最低的活跃请求再腾空间,而且要返回标准 429 错误通知用户重试。
  2. Sliding Window Attention 模型的淘汰要跟注意力窗口对齐,不要瞎扔窗口中间的 KV 页——比如模型只支持最近 32K 滑动窗口,你就应该把 32K 之前最老的页按顺序滚着扔;如果扔中间的某一段(比如窗口里 16K 处的页),Attention 计算会直接对不齐产生大量幻觉,输出完全不可用。
  3. Pinned 的前缀页一定要有硬上限,不能让钉住的内容吃掉 80% HBM——实际设置最多不超过 20-25% 的总 KV 可用空间;如果你的热门前缀有几百条,不要全钉住,挑最近 7 天 Top 20 钉住就行,剩下的走 LRU 温区就够了,别为了 1% 的命中率提升牺牲 50% 的并发上限。
  4. 做指标监控:命中率、淘汰频率、每类页的空间占比、被 Kill 的活跃请求数量——理想的健康值:(a)页级命中率 > 99%(大部分访问的页已经在内存里);(b)每秒淘汰页次数 < 总页数的 1%(淘汰不抖动);(c)活跃请求占用率 30-70%(有余量应对突发);(d)因为内存满被 Kill 的活跃请求每小时 < 1 次 / GPU。任何一个指标突破健康区立刻报警。
  5. 预热 + 冷启动策略:重启后的前 5 分钟,LRU 是空的,这时候不要接高并发——先用一批预定义的 dummy 请求把 Pinned 前缀页加载好、把常用工具 Schema 的 KV 页热好,再接真实流量;否则冷启动的几百毫秒-几秒高延迟窗口会让用户直接投诉。
  6. 多租户场景一定要做 KV Cache 按租户隔离 + 配额限制——不然一个大客户的 1 万并发长上下文请求,把 KV Cache 全占了,其他 100 个小客户全部 OOM 报错,这是 SaaS 级事故;每个租户设置最大 KV Cache 配额(比如大客户最多占 30%、中小客户各 0.5%),超过配额的请求走 429,禁止跨租户抢资源。Rate Limit 里的多租户配额策略可以直接平移到这里。

常见问题

KV Cache 淘汰策略和推理服务的 Batch 调度(Continuous Batching)是什么关系?两者应该先优化哪个?
两者是推理引擎中「两个相互依赖的核心子系统」:Continuous Batching(动态批处理 / 迭代级调度)决定「下一个 Decode step 把哪些请求拼在一起组成一个 Batch 跑一次 Forward」;KV Cache 淘汰策略决定「Batch 内每个请求的 KV 页在显存里怎么放、满了扔谁」。前者决定整体 Throughput 上限(每秒能处理多少 Token),后者决定在 Throughput 目标下能同时挂多少并发请求(挂起但没被 Batch 选中的请求的 KV 页也要占空间)。优化顺序:先做 Continuous Batching(这个是 0 到 1,不开 Continuous Batching 单 GPU TPS 只有它的 1/10),然后再做 PagedAttention + 页级 KV 淘汰(把并发上限从 Continuous Batching 基线 ×3),最后做分层分区 + Pinned Prefix(再 ×1.5)。三层叠加:Continuous Batching(×10) → PagedAttention + LRU Eviction(×3)→ 分层三区 + Pinned Prefix(×1.5),最终比原生 HF generate 并发上限 ×45,这就是为什么 2023 年之前一张 A100 跑 7B 只能挂 5 个并发,现在 2025 年同样一张卡稳定挂 200 个并发的核心原因。两者不是二选一,是连续三层的组合优化,少一层都会让最终 TPS 掉一大截。TPS(每秒 Token 数) 的优化体系文章里详细列了这三层的贡献比例,可以对照着看。
把 KV Cache 的一部分 offload 到 CPU 内存/SSD 上,真的有用吗?会不会比直接重新 Prefill 还慢?
看场景——短上下文(< 8K)的情况下,确实重新 Prefill 比从 CPU DDR 把 KV 页 PCIe 搬运回 GPU 更快;但长上下文(32K / 128K / 1M)的情况下,从 CPU 内存 / NVMe 分层加载 KV 比重新 Prefill 快 2 至 10 倍,因为 Prefill 计算密集(Attention + FFN 要算),而 KV 搬运只是纯内存拷贝(IO 密集,计算上不耗时)。量化决策公式:「重新 Prefill 耗时 T_prefill = 前缀长度 L × Prefill 每 Token 延迟;从 CPU 加载 KV 耗时 T_offload = 每页大小 × 页数 / PCIe 带宽」;如果 L 很大(≥ 32K)且 T_offload < T_prefill,offload 就划算」。 实际数据:Llama 3-70B 上 32K 前缀的 Prefill 需要 3 至 5 秒;同样 32K 的 KV Cache 页(约 32K × 2(K+V)× 80(层)× 128(头维度)/ 2(FP8)≈ 320MB)从 CPU DDR 通过 PCIe 4.0 x16(带宽 32GB/s)搬到 GPU 只需要 10 毫秒,比重新 Prefill 快 300 倍;但 2K 短前缀 Prefill 只需要 30 毫秒,offload 反而还慢几毫秒。所以 SGLang 的分层 offload 策略是:< 4K 前缀不做 offload(超了直接 Prefill)、4K-64K 走 CPU DDR offload、> 64K 走 NVMe + 预取;这个阈值设置覆盖了 99% 场景的最优选择。
FP8 / INT4 量化 KV Cache 对淘汰策略有什么影响?要不要专门调整?
本质影响就是「每个 KV 页变小了 → 同一张 GPU 能放下的页数变多 → 并发上限直接 ×2 至 ×4」;策略本身不需要大改,但三个阈值一定要重新算(还是按原来 BF16 设的阈值会浪费一半的 HBM 并发空间)。三个核心参数重算:(1)KV Cache 总可用空间不变,但每页字节数从 BF16 变 FP8 直接减半、变 INT4 直接减到 1/4 → 总页数 ×2 或 ×4 → 并发上限按比例提升,Pinned 区绝对值从原来的 20% 空间变成 40% 空间 → 需要把 Pinned 区从「20% 空间」改回「最多 Top 20 前缀」而不是按百分比,不然钉住太多;(2)淘汰触发水位线要上调——原来 90% 开始扔,FP8 可以调到 95%、INT4 调到 97%,因为每页更小、页更多,碎片率比 BF16 低;(3)分层 offload 的大小阈值也变——原来 32K 触发 CPU offload,FP8 可以调到 64K、INT4 调到 128K 才触发,因为同 HBM 能塞下的窗口更大了。最后一个经验:KV Cache 量化的质量损失远远小于权重量化——KV 用 FP8 对输出质量的影响几乎可以忽略不计(< 0.2% 下降),INT4 KV 也只有 1% 左右影响,所以 2025 年生产推理基本上默认都开 FP8 KV Cache(配合 FP8 量化权重一起用效果最佳),完全不用纠结质量问题,先开了拿 2 倍并发再说。