如果你翻过 KV Cache 驱逐方向的论文,会有一种错觉:这个领域已经相当成熟了。StreamingLLM、H2O、SnapKV、CAKE,一条清晰的技术演进线,从暴力滑动窗口到层感知自适应分配,benchmark 上的数字都在涨。但一个做过真实推理服务的人翻开推理引擎的开关列表,会发现一件很尴尬的事——在主流框架里,能直接开的生产级 KV 驱逐方案,基本只有一个「滑动窗口 + attention sink」。
这个落差值得认真对待,它不是纯粹的工程滞后。
驱逐到底在解决什么
先把背景摆正。KV Cache 是自回归生成的基本优化:已经算过的 K、V 不再重算,复杂度从 O(n²) 降到 O(n)。代价是显存,而且这部分显存随序列长度、层数、隐藏维度、并发数线性增长。LLaMA-2 70B 在 4096 token 下单请求的 KV Cache 就能超过 4GB,这直接决定了你能同时接多少个请求。
压缩手段大致三条路:量化(KIVI 这种免训练的非对称 2bit 方案,ERNIE 4.5 的技术报告里也提到了 KV 量化)、稀疏化(MInference,Qwen3 技术报告提到用了这类方法)、以及驱逐。驱逐的优势是免训练、即插即用,代价也在这——它是在推理时「丢东西」,丢错了没人能立刻发现。
算法一路演进,问题也一路转移
StreamingLLM 的思路最简单:只保留最近 N 个 token 的 KV。实验里发现纯裁剪会让注意力失去稳定的对齐目标,于是额外保留最初几个 token 作为 attention sink,效果能明显回升。这个 trick 后来被大量 KV Drop 方法沿用。有意思的是,attention sink 在量化方向上是需要被消除的干扰,在驱逐方向反而成了救命稻草。
H2O 往前走了一步,持续累计每个 token 的历史注意力得分,缓存满了就删得分最低的「非 heavy hitter」。这引入了真实开销:得分要一直记、一直更新,而且它依据的仍然是过去,对未来 decode 阶段的关注点没有预判能力。
SnapKV 的切入点挺讨巧。它观察到 prompt 末尾的 observation window 上的注意力分布,和后续生成时的关注点高度重叠,不同层的命中率大多数时候高于 90%。既然如此,prefill 结尾一次性筛一遍就够了,之后不再变。
CAKE 解决的是「多层多 head 怎么分预算」这块硬骨头。它给每层算两个量:空间离散度(注意力行向量的熵)和时间变化度(注意力列向量的方差),再组合成偏好分,按比例分配每层的缓存预算。直觉上说得通——注意力铺得广的层要留更多历史,波动大的层也要留余量。这个「切蛋糕」的框架,相当于把资源分配从平均切变成特征感知。
三道过不去的坎
第一条是真难。prefill 时看不到未来的 query,「注意力权重低」和「对输出影响为零」不是一回事,attention 本身又是非线性的。SnapKV 用一个末尾窗口去近似整个解码期的注意力分布,这是一种经验性假设,在长链条推理、代码生成这类上下文依赖重的任务上尤其脆弱。删掉的那一对 KV 只要碰到关键信息,输出直接漂移,而漂移往往不是报错,是一句看起来通顺但错的话。
第二条是结构耦合。不同层的冗余率差很多,不同 head 关注的位置也不同,在单 head 维度上做 drop 容易把信息割裂开。CAKE 这类方案就是在补这个洞,但补的方式是引入新的可调参数——两个温度系数 τ1、τ2,再加观测窗口长度、各层预算的总量。每一个都能在离线 benchmark 上调出好数字,也都能在换了模型或换了业务分布之后失效。
第三条最致命,也是「基本没人用」的核心原因:和动态批处理 + PagedAttention 冲突。
现在的推理引擎里,KV Cache 被切成固定大小的 block,通过块表寻址,多个请求可以共享前缀块,配合写时复制。驱逐方案大多以 token 为单位做选择,这意味着要在 prefill 之后插一段额外的处理时间——重排、重分配、复制块。这段处理时间有时比它省下来的解码时间还长,端到端延迟反而变差。一个稳定性收益不确定、延迟还可能倒退的优化,在任何上过线的团队里都很难推得动。目前公开可见的适配,主要是 vLLM 对滑动窗口策略的支持。
一个先摆正坐标系的说法
很多人把 KV 驱逐和前綴缓存混在一起谈,这会让排查方向出错。它们其实处在两个不同层级。KV Cache 解决单次请求内部的重复计算;Prompt Cache 解决跨请求相同前缀的复用。后者有明确、可验证的正确性:「前缀 token 序列逐位一致就命中,差一个 token 后面全部失效」。这个规则没有任何近似,可以被工程严密地遵守。
也正因为它可验证,工程上的收益反而更大。把 System Prompt 冻住不塞时间戳和请求 ID;工具定义顺序固定;长文档紧贴在 System Prompt 之后、用户问题之前;对话历史只追加、要截断就从最旧的删;换行符统一,别让 \n 和 \n\n 切成不同 token。这些都不需要改模型,也无法在 benchmark 上拿分,但能把 TTFT 和成本直接压下去。
相对地,分页本身提供了另一个实战层面的选择。当显存真的不足,驱逐粒度和 LRU、LFU、按优先级这三类替换策略,是可以按服务形态调的:延迟敏感型给每个请求留大 KV Cache、接受并发变低;吞吐优先型压每请求最大序列长度、换更多并发。这是粗粒度但确定性强的赌局,出错的后果可以被压测提前看到。
线上能接受的驱逐长什么样
我的判断是,要在生产里用上更聪明的驱逐,需要同时满足几条:驱逐的单位跟随 block 而不是 token,避免额外的重排拷贝;输出侧有可观测的漂移检测,而不是等到用户投诉;容量收益得在真实会话分布上量出来,而不是合成 benchmark。参数不能多到只有原论文作者知道怎么调。
在 Reach 那条线被补上之前,我倾向于继续做可验证的前缀稳定性和分页策略,把「不丢任何 KV」这件事当默认。论文里的稀疏性和热力分布都是真的,但这跟「删了对这家公司的业务没有影响」是两件事。
转载请注明出处: 皮哈皮阿
本文的链接地址: https://blog.phpat.com/ai-engineering/630.html
微信或手机浏览器扫一扫,随时随地随心阅读与分享
-
推理成本下降之后:模型路由、KV Cache 与端侧推理
上个月一个团队拿着账单来找我:模型单价降了,费用却涨了三倍。看完调用链就明白了,问题不在单价,在用法。单位 token 便宜,不等于每次任务便宜。
2026/10/10
-
2026 年 AI 应用评测:从单轮打分到多轮轨迹评估
一个 Agent 的评测分数从 72% 涨到 81%,团队很高兴,直到发现它学会了在失败时直接结束任务,把“没做完”算成了“无需回答”。评测设计错了,指标越好看越危险。
2026/10/19
-
长度惩罚参数调优:常见参数名字容易混淆
长度惩罚在主流 API 里并不存在。真正管长度的是 max_tokens 和 stop sequences,frequency、presence、repetition 惩罚管的是用词重复和新词探索。把这些旋钮分开,能避免输出被截断、术语被替换成同义词两类故障。
5天前
-
推理模型的流式输出:thinking 块怎么流
推理模型的流式输出不只是逐字渲染:思考块、正文、工具调用参数混杂在 SSE 流里,客户端要按字段分流、独立缓冲、支持中途中断。记录接通义千问与智谱过程中的判断和踩坑。
4天前
-
动态拼批不是开个开关:线上拼批的取舍与门禁
在线服务里的动态拼批和离线批量推理是两码事。窗口时长、KV Cache 水位、批内参数对齐共同决定拼接能否成立。上线前该盯的是 p99 和显存峰值,而不只是吞吐。
2天前
-
推理服务排队延迟的分层观测:从请求数到Token账本
盯 QPS 和 P99 看不到排队延迟长在哪。把这件事拆成入口→上游准入队列→后端等待→batch 解码四层,用 vLLM 暴露的排队与 KV 缓存指标做层级归因,讨论分层之后仍然看不见的部分与取舍。
1天前
京公网安备11011402053654 
@muffin:链接已更新,分类图像功能...
AI网址导航模版图标库更换指南
作者大大,图标库邀请链接失效了,麻烦更新...
AI网址导航模版图标库更换指南
太好看了,快点更新!
友情链接
@戴墨镜的刘备:哈喽,路过点赞。
vue.js组件之手写签名板vue-signature-simple
太好看了,快点更新!太棒了!👍真的很赞!
vue.js组件之手写签名板vue-signature-simple
你写得非常清晰明了,让我很容易理解你的观...
Emlog插件RSS订阅器发布