QEvict 阅读笔记:把 KV Cache 淘汰从「一锤子买卖」变成可逆的三层调度

笔记日期: 2026-08-08 作者: Zhongzhu Zhou 笔记对象论文: QEvict: Recoverable Quantized KV Eviction for Attention-Drift-Robust Long-Context Decoding 论文作者: Ayushman Garg, Akshita Gupta, Shaswata Bhattacharya, Abhishek Gupta, Sandeep Kumar, Manoj Kumar(印度理工学院罗尔基分校 / 德里分校) arXiv: 2608.05326 发表状态: 预印本(cs.LG),2026 年 8 月

1. 问题背景:淘汰是单向门,但重要性并不是单向变化的

长上下文 LLM 推理里,KV cache 压缩方法几乎都要回答同一个问题:哪些 token 值得留下? 过去两年主流答案是「淘汰」——用某种基于注意力的分数给每个缓存 token 或窗口打分,保留分数最高的一部分,永久丢弃剩下的。StreamingLLM 只保留 sink token 加最近窗口;H2O 用累积注意力质量保留「重击手」(heavy hitter);SnapKV、AdaKV、CriticalKV、DefensiveKV 则不断改进打分和预算分配的具体方式——分别用观察窗口、按注意力头自适应分配预算、引入 value 相关信号、以及对未来不确定性做鲁棒化处理。

这些方法背后有一个共同假设,也正是这篇论文直接挑战的地方:一旦某个 token 或窗口被淘汰,它就永久消失了,隐含的赌注是「过去不重要就能很好地预测未来也不重要」。论文的核心实证发现是,这个赌注比看起来更容易输。注意力不仅是稀疏的,还会漂移。在解码第 50 步看起来无关的一段 token(比如模型已经翻篇的某个实体或从句),到第 150 步可能突然变得至关重要——模型需要解析指代关系、检索之前陈述过的事实,或者把推理拉回到长文档前面提到过的某个点。二元淘汰无法表达「我现在不确定还需不需要它,但先便宜地留个痕迹以防万一」这种中间状态。它只能说是或否,一旦说了否,无论模型之后的需求如何变化,那部分信息都无法恢复。

另一大类方法——KV cache 量化(KIVI、KVQuant、ZipCache)——走的是相反策略:什么都不丢,但把大部分内容用降低的数值精度表示(比如 2-4 bit 而不是 16 bit)。这保留了广泛的历史覆盖(不丢东西),但代价是量化误差被统一或按简单启发式规则均摊,没有区分「我有充分证据这块区域重要」和「我完全不知道这块区域是否重要」。在极紧的内存预算下,这会把宝贵的精度浪费在真正不重要的区域上,同时让真正重要的区域得不到全精度保真度。

QEvict 的思路是不再把「淘汰」和「量化」当成必须二选一的两种压缩策略,而是把它们看作同一个表示保真度连续谱上的两个点,一个窗口可以在其间动态双向移动,依据的正是模型此刻真实的注意力模式揭示出哪些区域现在真正重要。一个窗口起初处于全精度,当其累积注意力分数下降时可以被降级为紧凑的 INT2 表示,如果之后分数再次上升,还可以被晋升回全精度,只有当它同时超出全精度和量化两个层级的容量预算时,才会被永久淘汰。这把缓存管理从一次性分类问题变成了持续的路由问题——精神上更接近一个带按需换页机制的内存层级(L1/L2/磁盘),而不是一个过滤器。

前置知识

KV cache 及其为何主导长上下文内存开销。 在自回归解码中,transformer 会在每一层为每个已生成的 token 缓存 key(KK)和 value(VV)投影,以避免在每个新解码步都重新计算。对于有 LL 层、HkvH_{kv} 个 KV 头、head 维度 dkvd_{kv}、序列长度 nn 的模型,缓存大小按每个 batch 元素 O(LHkvdkvn)O(L \cdot H_{kv} \cdot d_{kv} \cdot n) 增长——随序列长度线性增长,对于大 batch、长上下文的模型,这可能与模型参数本身的内存占用相当甚至超过它。这正是「如何在不损害模型效果的前提下压缩 KV cache」成为高效 LLM 服务领域最活跃方向之一的原因。

基于注意力的重要性打分。 大多数淘汰方法用注意力权重本身作为信号来判断哪些缓存状态重要。如果 at,h(i)a_t^{\ell,h}(i) 表示第 tt 个查询 token,在第 \ell 层、第 hh 个头,分配给缓存的 key ii 的 softmax 注意力概率,那么把这些概率在多个解码步上累加,就能得到 token/窗口 ii 的经验重要性分数:持续获得高注意力的 token 是「重要」的,从未被关注的 token 是移除的候选。这篇论文关注的关键微妙之处在于,这个分数是一个移动目标——它是「到目前为止发出过哪些查询」的函数,而未来的查询可能表现得跟过去截然不同。

分组查询注意力(GQA)与 FlashAttention-2 简介。 GQA 让多个查询头共享数量更少的 KV 头(即 Hkv<HqH_{kv} < H_q)以减小 KV cache 大小,按比例缩减缓存体积。FlashAttention-2 是一种融合、IO 感知的注意力核函数,避免物化完整的 n×nn \times n 注意力矩阵,使训练和推理在长上下文下都大幅加速并更省内存。QEvict 设计上明确要与两者兼容,但——正如后文实验部分会看到的——它与 FlashAttention-2 融合核的交互恰恰是论文里最有意思的细节之一。

低比特 KV 量化简介。 与把 key 和 value 存成 16 bit 浮点数不同,可以把它们存成比方说 2 bit 或 4 bit 整数,外加少量按 block 的 scale/zero-point 参数,在注意力计算前立刻反量化回浮点数。KIVI 推广了 KV cache 的非对称量化方案——注意到 key 和 value 有不同的统计分布(key 往往有更结构化的、按通道的模式;value 更接近按 token 的分布),因此应沿不同的轴量化。QEvict 沿用了这套非对称的「按通道量化 key / 按 token 量化 value」方案,但加入了一个关键改动:量化只在窗口第一次被降级时发生一次,得到的低比特表示会被缓存下来,在之后每一次晋升/降级循环中反复复用,而不是每次都从头重新计算。

2. 架构与流水线总览

QEvict 把解码期间的缓存管理组织成三个阶段,在整个生成过程中反复循环。

flowchart TB
    subgraph S1["阶段一:输入与窗口划分"]
        A["输入 token 序列"] --> B["划分为连续语义窗口<br/>ω1, ω2, ω3, ..."]
        B --> C["所有窗口初始存储<br/>为全精度 (FP16/BF16)"]
    end
    subgraph S2["阶段二:累积窗口打分"]
        D["解码步 t 的查询"] -->|attends to| E["KV Cache"]
        E --> F["逐窗口注意力分数<br/>在路由周期 Ω 内累积"]
        F --> G["按累积分数 S̄(w) 排序窗口"]
    end
    subgraph S3["阶段三:动态层级路由"]
        H["Tier 1: 全精度缓存<br/>(top Kf 个窗口)"]
        I["Tier 2: 量化可恢复缓存<br/>INT2(次 Kq 个窗口)"]
        J["Tier 3: 永久淘汰<br/>(剩余窗口)"]
        H <-->|晋升/降级| I
        I -->|仅当超出 Kf+Kq 才淘汰| J
    end
    S1 --> S2
    S2 --> S3
    S3 -->|每生成 Ω 个 token 重新打分并路由| S2

Figure 1 (对应论文 Fig.2): QEvict 整体工作流程

Figure 1(对应论文 Fig.2):QEvict 框架整体工作流程。阶段一把输入序列划分为连续的 KV 窗口,同时保留 sink 与最近 token 为全精度。阶段二在每个窗口上累积注意力分数,得到稳定的重要性排序。阶段三在固定字节预算下把窗口路由到全精度层、保持可被注意力访问且可后续晋升的可恢复 INT2 层,或永久淘汰层,随重要性演变动态晋升与降级。

这里有三个细节比高层流程更值得注意。第一,路由的单位是窗口,而不是单个 token。这是一个下文会详细讨论的设计选择——它能抵抗单 token 层面瞬时注意力噪声,并保留自然语言的局部连贯性(跨越若干相邻 token 的实体、从句、话语关系)。第二,路由每生成 Ω\Omega 个 token 才发生一次,而不是每一步都发生——这把重新打分的成本分摊到多个解码步上。第三,也是最重要的一点,Tier 1 和 Tier 2 之间的箭头是双向的:这正是整篇论文的核心。被降级到 INT2 的窗口并没有消失;它仍以降低精度参与注意力计算,并继续累积注意力分数,所以如果模型的查询之后重新关注它,它在下一次路由事件时会被晋升回全精度。

3. 在提出方案之前先诊断问题

在给出机制之前,作者在 Llama-3.1-8B-Instruct 上跑了一组诊断实验(512-token prefill、生成 256 token、20% KV 预算、5 个受保护 sink token),为第 4 节的每一个设计选择打下依据。这一点值得仔细梳理,因为系统类论文能这么明确地在提出方案之前先测量失效模式,而不是直接提方案再跟 baseline 比,是比较少见的。

作者定义了两个新的诊断指标:

未来遗漏质量(Future Missed Mass, FMM)。 在解码步 tt,先跑未压缩的 FullKV 模型得到整个缓存上的真实注意力分布。FMM 衡量的是,在未来每个解码步,这份真实注意力质量中有多大比例落在某个压缩策略在第 tt 步已经永久丢弃的 token 上。直观理解:如果一个策略的 FMM 很高,说明压缩后的模型正在「盲飞」——完全丢失了未压缩模型实际会用到的信息。

全局长期重要性复苏率(Global LIR) 衡量的是一个已经沉寂(不在全精度集合中)一段时间的窗口,之后重新进入全精度集合的频率。如果复苏率很低,就验证了传统淘汰假设(一旦不重要就一直不重要);如果复苏率不可忽略,就证伪了这个假设。

作者还定义了选择抖动(Selection Churn),即连续两次路由事件之间保留窗口/token 集合的 Jaccard 距离——用来衡量压缩缓存对「什么重要」的判断在相邻两次路由事件之间有多不稳定。

观察一:窗口能稳定缓存决策

比较逐 token 淘汰(窗口大小 Ωw=1\Omega_w=1,称为 R1)与基于窗口的路由(R3,Ωw=8\Omega_w=8;R4,Ωw=32\Omega_w=32),图 1(a)显示,基于窗口的策略在整个解码过程中的 FMM 明显低于逐 token 淘汰。定量来看,选择抖动从 R1(逐 token)的 0.017 降到 R3(窗口大小 8)的 0.0012——稳定性提高了一个量级以上——而在 R4(窗口大小 32,过粗)时又回升到 0.018。这为「用窗口而不是单个 token 作为路由单位」提供了实证依据:单 token 的注意力分数在相邻路由事件之间噪声很大、波动明显,但约 8 个 token 连续跨度上的平均分数是一个稳定得多的信号,同时又不像窗口大小 32 那么粗——粗到会丢失对真正重要和不重要区域的细粒度区分能力。

Figure 2 (对应论文 Fig.1): FMM 与注意力质量诊断

Figure 2(对应论文 Fig.1):促成窗口级路由与可恢复低比特保留的诊断实验。(a)连续窗口路由降低了未来遗漏质量,实线与虚线 R3 曲线之间的差距展示了量化层的未来效用。(b)在相同 KV cache 预算下,三层分配比二元的全精度保留加淘汰保留了明显更多的真实注意力质量。

观察二:二元淘汰丢弃了一段有用的中间区域

在相同字节预算和窗口大小下,比较二层策略(R2:全精度+淘汰)与提出的三层策略(R3:全精度+INT2+淘汰),图 1(b)显示:R2 在全精度中保留 33.3% 的真实注意力质量,淘汰 64.8%(剩下的是很小的局部尾部)。R3 只在全精度中保留 19.7%,但额外在 INT2 层保留了 42.6%,把真正淘汰的比例缩减到 35.7%。关键的是,从量化层计算出的 INT2 分数与对应的 FullKV 注意力分数保持了 0.9824 的余弦一致度——说明量化后的表示足够准确,能产生可用的重要性信号,而不只是可用的内容。而且,如果禁用对 R3 量化层的访问(即把它当作已被淘汰,图 1(a)中的虚线曲线)会让 FMM 重新跳升到接近 R1/R2 的水平——这直接证明,停留在 INT2 层的窗口确实被后续解码步有效地参考了,而不是白白放在那里。

观察三:重要性有持续性,但不是静态的

这是最直接支撑晋升/降级机制的观察。全局 LIR 用两种方式衡量:对于一个oracle(用完美事后视角重新计算)排名最高的全精度集合,复苏率只有 0.98%——说明一旦知道真实情况,「真正重要」的核心窗口集合大体上是稳定的。但对于实际部署的全精度策略(在线做决策,没有事后视角),复苏率是 6.18%——高出六倍以上。换句话说:在线的缓存管理决策比底层真实情况噪声更大,而且确实有一部分窗口会真正离开又重新进入重要集合。论文还指出降级明显比晋升更频繁——重要性衰减的频率高于复苏的频率,这正是符合直觉的不对称性,也正是为什么一个可恢复的中间层(而不是对称的淘汰)才是正确的结构性应对:你需要一个便宜的地方来暂存窗口,度过它们(通常更长的)低表观重要性时期,同时能在(更少见但确实存在的)重新活跃期以低成本把它们取回来。

4. QEvict 机制的形式化描述

4.1 问题设定

考虑 transformer 第 \ell 层,有 HqH_q 个查询头和 HkvH_{kv} 个 KV 头(GQA 风格,HkvHqH_{kv} \le H_q),head 维度 dkvd_{kv}。在解码步 tt,查询 qt,hq_t^{\ell,h} 关注当前可访问的 key 和 value:

at,h=softmax(qt,h(Kacc,t)dkv),ot,h=at,hVacc,t.(注意力)a_t^{\ell,h} = \mathrm{softmax}\left(\frac{q_t^{\ell,h}\left(K_{\mathrm{acc},t}^\ell\right)^\top}{\sqrt{d_{kv}}}\right), \qquad o_t^{\ell,h} = a_t^{\ell,h} V_{\mathrm{acc},t}^\ell. \tag{注意力}

这是标准的缩放点积注意力,唯一的细微差别是,Kacc,tK_{\mathrm{acc},t}^\ellVacc,tV_{\mathrm{acc},t}^\ell——「可访问」的 key/value——是在每一次路由/注意力步骤中,由此刻全精度窗口与反量化后的 INT2 窗口混合重建出来的。已淘汰的窗口不做任何贡献;它们干脆不出现在这个重建过程中。

在保留一段受保护的 sink 前缀 SS(少量始终保持全精度的锚定 token,沿用 StreamingLLM 关于早期 token 起「注意力汇聚点」作用的观察)和一段受保护的近期区域 LtL_t(最近生成的 token,尚未被打分)之后,剩下的历史缓存被划分成连续窗口 Wt\mathcal{W}_t。每个窗口 ww 被赋予一个状态:

zt(w){Full,Quantized,Evicted}.(状态)z_t^\ell(w) \in \{\mathrm{Full}, \mathrm{Quantized}, \mathrm{Evicted}\}. \tag{状态}

Mf(w)M_f(w)Mq(w)M_q(w) 分别表示窗口 ww 在全精度和量化表示下的存储成本(字节数)。第 \ell 层所有窗口的整体分配必须满足一个字节预算约束:

Bsink+Blocal+wWt[1 ⁣[zt(w)=Full]Mf(w)+1 ⁣[zt(w)=Quantized]Mq(w)]Btotal.(预算)B_{\mathrm{sink}} + B_{\mathrm{local}} + \sum_{w \in \mathcal{W}_t} \Big[ \mathbb{1}\!\left[z_t^\ell(w) = \mathrm{Full}\right] M_f(w) + \mathbb{1}\!\left[z_t^\ell(w) = \mathrm{Quantized}\right] M_q(w) \Big] \le B_{\mathrm{total}}. \tag{预算}

设计上的直觉是:BsinkB_{\mathrm{sink}}BlocalB_{\mathrm{local}} 是固定开销(假设 sink 和最近窗口始终保持全精度,因为它们体量小且持续重要)。剩下的一切都必须挤入 BtotalB_{\mathrm{total}} 的余量之中。注意这正好形式化了引言中提到的那一点:QEvict 是在同时决定驻留(这个窗口是否被保留?)和表示精度(如果保留,以什么位宽?)——它不像纯淘汰方法那样只选择一个单一的保留子集;它是在联合选择「子集与精度分配」。

4.2 累积窗口打分——推导与直觉

aτ,h(i)a_\tau^{\ell,h}(i) 为解码步 τ\tau 时,查询头 hh 分配给缓存 token ii 的注意力概率。在路由事件 tt(每生成 Ω\Omega 个 token 发生一次,即 t0(modΩ)t \equiv 0 \pmod{\Omega}),窗口 ww 的累积分数以增量方式更新:

St(w)=StΩ(w)+1Hqh=1Hqτ=tΩ+1tiwaτ,h(i),t0(modΩ).(1)\overline{S}_t^\ell(w) = \overline{S}_{t-\Omega}^\ell(w) + \frac{1}{H_q}\sum_{h=1}^{H_q}\sum_{\tau=t-\Omega+1}^{t}\sum_{i \in w} a_\tau^{\ell,h}(i), \qquad t \equiv 0 \pmod{\Omega}. \tag{1}

逐层拆解一下,因为这个结构同时编码了几个设计决策:

  • 最内层求和 iwaτ,h(i)\sum_{i \in w} a_\tau^{\ell,h}(i),把注意力概率在窗口 ww 内的所有 token 上汇聚,针对单个查询步 τ\tau 和单个头 hh。这就是分数的「窗口化」——不再追踪逐 token 的注意力(观察一表明这样噪声很大),而是每个窗口每步每个头得到一个数。
  • 中间求和,τ\tautΩ+1t-\Omega+1tt,把这个窗口化分数在自上一次路由事件以来的所有解码步上累加。这正是让打分「累积」而非瞬时的关键——某个窗口在某一步短暂获得高注意力但其余时间被忽略,不会主导排名;而某个窗口在整个区间内持续获得中等注意力,会被认可为重要。
  • 最外层对 hh 求和后除以 HqH_q,是在查询头之间做平均。这是一个简化设计选择:与维护逐头重要性(会让簿记开销乘以 HqH_q)不同,QEvict 把它折叠成每层每个窗口一个分数。显而易见的替代方案——像 AdaKV 那样按头路由——理论上能实现更细粒度的分配(某个窗口可能对第 3 个头重要但对第 7 个头不重要),但代价是路由决策和元数据要多追踪 HqH_q 倍;QEvict 的消融实验(附录 G,正文引用但未复现)发现这个平均分数在实践中已经足够。
  • 递推关系 St=StΩ+(新区间的贡献)\overline{S}_t = \overline{S}_{t-\Omega} + (\text{新区间的贡献}) 意味着这个分数是整段生成历史上的加性累加器,而不是一个带窗口或指数衰减的滑动平均。这里有一个值得指出的具体含义:一个在生成早期获得大量注意力但之后再也没被关注过的窗口,即使很久之后,累积分数依然会很高——这个分数从不遗忘。这与观察三的发现相一致(重要窗口大体上是持续存在的),但确实带来了一个自然的设计问题(下文讨论),即极长的生成过程是否最终需要某种衰减机制,让新近性能够压过久远的历史。

4.3 字节约束的层级分配——推导

给定保留 sink 与最近区域后的剩余历史预算 Bhist=BtotalBsinkBlocalB_{\mathrm{hist}} = B_{\mathrm{total}} - B_{\mathrm{sink}} - B_{\mathrm{local}},以及选定的量化层比例 q[0,1]q \in [0,1](超参数——历史预算中有多大比例分给 INT2 层而不是全精度层),两个层级的容量为:

Kf=(1q)BhistMf,Kq=qBhistMq.(3)K_f = \left\lfloor \frac{(1-q)B_{\mathrm{hist}}}{M_f} \right\rfloor, \qquad K_q = \left\lfloor \frac{qB_{\mathrm{hist}}}{M_q} \right\rfloor. \tag{3}

这里的推导就是简单的「除法+取整」:如果决定量化层拿到字节预算的 qq 份,而每个量化窗口花费 MqM_q 字节,那么最多能塞进 qBhist/Mq\lfloor qB_{\mathrm{hist}} / M_q \rfloor 个量化窗口;全精度层用剩下的 (1q)(1-q) 份对称地计算。论文的消融实验(第 5 节,正文简要总结)发现在多个 benchmark 上 q=0.70q=0.70 表现较好——即大约 70% 的历史预算投入到更便宜的 INT2 层,让它覆盖远大的一段序列,剩下 30% 用来为分数最高的窗口购买全精度保真度。这种不对称性符合观察二的发现:量化层是让你在紧预算下保留覆盖面的关键,而全精度层是留给你此刻最有信心「一定重要」的那部分窗口。

4.4 动态路由与恢复——晋升/降级算法

在每一次路由事件,全精度和量化层的候选窗口池为:

Wtcand=FtΩQtΩLtaged.(4)\mathcal{W}_t^{\mathrm{cand}} = \mathcal{F}_{t-\Omega} \cup \mathcal{Q}_{t-\Omega} \cup \mathcal{L}_t^{\mathrm{aged}}. \tag{4}

这句话的意思是:候选池由上一次路由事件时在全精度层的窗口,加上上一次路由事件时在量化层的窗口,再加上刚刚「老化」脱离受保护近期区域 Lt\mathcal{L}_t、现在第一次有资格被打分和路由的窗口组成。候选之后按累积分数 St(w)\overline{S}_t^\ell(w)(公式 1)排序,并从最高分开始贪心分配:

Ft=TopKKf ⁣(Wtcand;St),\mathcal{F}_t = \mathrm{TopK}_{K_f}\!\left(\mathcal{W}_t^{\mathrm{cand}}; \overline{S}_t^\ell\right), Qt=TopKKq ⁣(WtcandFt;St),(5)\mathcal{Q}_t = \mathrm{TopK}_{K_q}\!\left(\mathcal{W}_t^{\mathrm{cand}} \setminus \mathcal{F}_t; \overline{S}_t^\ell\right), \tag{5} Et=Wtcand(FtQt).\mathcal{E}_t = \mathcal{W}_t^{\mathrm{cand}} \setminus (\mathcal{F}_t \cup \mathcal{Q}_t).

用文字说:取分数最高的 KfK_f 个候选进入全精度层;在剩下的候选中,取次高分的 KqK_q 个进入量化层;剩下的全部淘汰。因为量化层窗口仍能被注意力访问并持续累积分数(公式 1 并不区分窗口目前存放在哪一层——它只是累加接收到的注意力,而量化窗口仍会参与注意力计算,只是以降低的精度),窗口的命运在跨路由事件之间是真正可逆的:

FullQuantizedEvicted.(6)\mathrm{Full} \rightleftarrows \mathrm{Quantized} \longrightarrow \mathrm{Evicted}. \tag{6}

只有那些落在组合容量 Kf+KqK_f + K_q 之外的窗口——即连便宜的量化层都排不上的窗口——才会被永久移除。这正是整个机制的核心:淘汰现在只是应用于排名最低的候选的最后手段,而不是应用于单一阈值以下所有窗口的二元命运。

算法一(路由事件 tt 处的动态层级路由)

输入: 候选窗口 w ∈ W_cand 的累积分数 S̄_t(w),
      容量 K_f (全精度) 和 K_q (量化),
      上一次的层级分配 (F_{t-Ω}, Q_{t-Ω})
输出: 新的层级分配 (F_t, Q_t, E_t)

1.  W_cand ← F_{t-Ω} ∪ Q_{t-Ω} ∪ L_aged           // 构建候选池 (公式 4)
2.  for each window w in W_cand:
3.      通过公式 (1) 计算 S̄_t(w)                   // 累积自上次路由事件以来的注意力
4.  按 S̄_t(w) 降序排列 W_cand
5.  F_t ← 排序后的 W_cand 中 top K_f 个窗口          // 晋升/保留为全精度
6.  remaining ← W_cand \ F_t
7.  Q_t ← 排序后的 remaining 中 top K_q 个窗口        // 降级/保留为量化层
8.  E_t ← remaining \ Q_t                            // 永久淘汰剩余部分
9.  for each window w in Q_t:
10.     if w 之前不在量化层 (w ∉ Q_{t-Ω}):
11.         用非对称的「按通道量化 key / 按 token 量化 value」方案量化 w
12.         把量化编码 + scale/zero-point 存入持久化台账 (ledger)
13.     else:
14.         复用台账中已有的量化编码                  // 迁移稳定,无需重新量化
15. for each window w in F_t:
16.     if w 之前在 Q_{t-Ω} 中 (即正在被晋升):
17.         用台账中存储的编码反量化 w
18.         在 w 原始的绝对位置上重新应用 RoPE
19. return (F_t, Q_t, E_t)

这段伪代码里有两个实现细节值得从推导层面细讲,因为它们正是让可恢复层不随时间退化的关键。

迁移稳定的量化(第 9-14 行)。 朴素的晋升/降级实现方式会是:每次窗口被降级时,从当前的全精度值重新量化;每次晋升时,反量化;如果之后再次降级,又从(已经有损的)反量化后的值重新量化。这会让量化误差在反复迁移中累积——每一次经过有损通道的往返都会增加更多误差,就像反复保存 JPEG 会让画质每次都进一步下降一样。QEvict 的解决方法是:一个窗口只在它第一次被降级时量化一次,把得到的低比特编码和量化参数(scale、zero-point)存入一个持久化台账,之后每次再降级时,直接复用那些完全相同、已存储的编码,而不是从一个可能已经退化的重建结果重新量化。这意味着量化误差每个窗口只会产生一次,无论它之后在层级之间震荡多少次。

晋升 key 的位置处理(第 15-19 行)。 Key 在RoPE 之前的域中被量化——即在应用旋转位置编码之前——同时连同其原始绝对 token 位置一起存储。当一个窗口被晋升回全精度时,会用存储的原始位置重新应用 RoPE。为什么选在 RoPE 之前?因为 RoPE 会依据绝对位置将 key 向量旋转一个角度;如果你在应用 RoPE 之后量化 key,旋转角度就会被烘焙进量化后的数值,之后反量化就需要从有损、低比特精度的数据中「撤销」一次旋转——以一种更隐蔽的方式累积误差。在旋转之前量化、在反量化之后重新应用(数值上精确的)旋转,能让位置信息与有损压缩步骤保持解耦。

4.5 一个具体数值例子

有了真实数字过一遍公式,会更容易建立信任。以 Llama-3.1-8B-Instruct 的 KV cache 几何结构为例:Hkv=8H_{kv}=8 个 KV 头(从 32 个查询头经 GQA 缩减而来)、head 维度 dkv=128d_{kv}=128、共 L=32L=32 层。在 BF16(每元素 2 字节)下,每个 token、每层的 KV 存储量为 2×Hkv×dkv×2字节=2×8×128×2=40962 \times H_{kv} \times d_{kv} \times 2\,\text{字节} = 2 \times 8 \times 128 \times 2 = 4096 字节(开头的 2 是因为要同时存 KKVV)。跨全部 32 层,就是 4096×32=1310724096 \times 32 = 131072 字节 128KB\approx 128\,\text{KB},每个 token 在全精度下的开销。

假设某次路由事件考虑一个 ω=8\omega=8 个 token 的窗口。那么 Mf(w)=8×128KB=1024KB=1MBM_f(w) = 8 \times 128\,\text{KB} = 1024\,\text{KB} = 1\,\text{MB} 每个窗口(跨所有层求和,因为公式(预算)里的字节预算是整个缓存层面的约束)。量化到 INT2(加上少量 per-block scale/zero-point 元数据,大约 5%)把这个数字降到大约 Mq(w)1MB×216×1.05131KBM_q(w) \approx 1\,\text{MB} \times \tfrac{2}{16} \times 1.05 \approx 131\,\text{KB}——每个窗口的压缩比接近 8 倍,与论文所宣称 INT2 量化比全精度 BF16 便宜约 8 倍的说法一致。

现在代入公式(3)。假设保留 sink 和最近窗口后的历史预算是 Bhist=100MBB_{\mathrm{hist}} = 100\,\text{MB}(这是为了说明比例关系而设的示意数字,不是论文实际的 RULER 32K 配置),并按论文推荐取 q=0.70q=0.70。那么:

Kf=0.30×100MB1MB=30 个窗口=240 个 token(全精度),K_f = \left\lfloor \frac{0.30 \times 100\,\text{MB}}{1\,\text{MB}} \right\rfloor = 30 \text{ 个窗口} = 240 \text{ 个 token(全精度)}, Kq=0.70×100MB0.131MB534 个窗口4272 个 token(INT2).K_q = \left\lfloor \frac{0.70 \times 100\,\text{MB}}{0.131\,\text{MB}} \right\rfloor \approx 534 \text{ 个窗口} \approx 4272 \text{ 个 token(INT2)}.

也就是说,同样 100 MB 的预算,如果全部用全精度,只能买到 240 个 token 的覆盖;而 QEvict 的三层拆分能买到 240 个 token 的全精度覆盖,外加 4272 个 token 的 INT2 可恢复覆盖——在同样的字节成本下,总覆盖 token 数大约多了 18.8 倍,代价是要接受更大那部分层级上的量化噪声。这具体地说明了为什么观察二的发现(三层结构合计保留 62.3% 的注意力质量,而二层结构只有 33.3%)并不令人意外——一旦看到这个原始算术,量化层每个 token 的成本如此之低,即便只分配适度的字节预算,也能买到巨大的额外覆盖面。

5. 设计选择:为什么这样做、显而易见的替代方案是什么、在哪里会失效

为什么用窗口而不是 token? 为什么有效: 观察一表明窗口化把选择抖动降低了 10 倍以上(在 Ωw=8\Omega_w=8 时从 0.017 降到 0.0012),同时降低了 FMM,因为自然语言的重要性确实在空间上是相关的(一整个从句或实体提及是重要的还是不重要的,而不是各个子词 token 独立地重要或不重要)。显而易见的替代方案: 逐 token 路由(如 R1 基线,或概念上类似 SnapKV/AdaKV 更细粒度的 token 选择),能给予保留恰好重要 token 的最大灵活性。窗口化在哪里会失效: 如果窗口边界恰好把真正重要的 token 和真正不重要的邻居分到一起,整个窗口就会基于噪声更大的邻居被一起晋升/降级——观察一在 Ωw=32\Omega_w=32(R4)时已经展示了这种失效模式,FMM 曲线平均更低(丢弃的东西更少),但代价是分配更粗糙,把预算浪费在恰好与重要 token 共享窗口的不重要 token 上。论文根据消融实验选择了 Ωw=8\Omega_w=8,但这显然是一个依赖数据集/任务的最优点,而不是通用常数,论文也坦诚地承认这是未来工作(「自适应窗口边界……留待未来研究」)。

为什么用持久化量化台账,而不是每次降级都重新量化? 为什么有效: 它把每个窗口的总量化误差限定在一次往返之内,无论发生多少次震荡——报告中 INT2 与 FullKV 分数之间 0.9824 的余弦一致度,即便在报告的抖动水平下依然保持,间接验证了这一点。显而易见的替代方案: 无状态的按需量化,实现更简单,不需要在 KV cache 之外额外维护一个数据结构(台账)。台账方案的代价所在: 内存。台账本身需要存储在某处,对于抖动很高的负载(许多窗口频繁震荡),台账自身的体量可能变得不可忽略——论文没有明确报告台账的内存开销,这是一个值得指出的空缺(见第 8 节)。

为什么 q=0.70q=0.70(历史预算的 70% 分给量化层)? 为什么有效: 论文自己的诊断(观察二)表明量化层正是「可恢复覆盖」收益的主要来源——在 R2→R3 转变中,42.6 个百分点的注意力质量从「已淘汰」转移到「已量化」,而全精度覆盖只放弃了 13.6 个百分点(33.3% → 19.7%)。较为宽裕的量化层分配正好针对这种不对称性。显而易见的替代方案: 更保守的分配(比如 q=0.3q=0.3q=0.5q=0.5),保留更多窗口在全精度,牺牲覆盖面来换取更小集合上更高的逐窗口保真度。q=0.70q=0.70 可能不是最优的场景: 对于对少量关键事实的精度极度敏感(而不是需要广泛覆盖)的任务——比如单针而非 RULER 中多 key/多 value 变体的大海捞针任务——一个能保证序列中更大比例保持全精度的更小 qq 值实际上可能表现更好,而表 2 确实显示,QEvict 在最难的单/多 key 检索子任务上(MK-3:QEvict 77.80 对 DefensiveKV 97.00)相对基线的优势要小于聚合/跟踪类任务——这说明固定的 q=0.70q=0.70 并非在所有任务类型上都是最优的。

为什么用累积(永不衰减)分数,而不是指数滑动平均? 为什么有效: 观察三表明oracle版本的重要窗口集合高度持续存在(复苏率 0.98%)——说明早期重要的东西往往会持续重要,所以一个从不遗忘的加性累加器是对真实情况的合理建模。显而易见的替代方案: 一个带指数衰减的滑动分数,对近期注意力赋予更高权重,能让缓存对单次长生成内部的话题转变更敏感。累积打分可能失效的场景: 极长的生成过程中如果真的发生话题转变(比如一份 5 万 token 的文档,模型前 1 万 token 关注某个子话题,后 4 万 token 关注一个无关的话题),早期、已不再相关的窗口可能纯粹因为历史累积而保留一个人为偏高的分数,挤占真正当前相关、但还没来得及积累可比分数的窗口。论文自己的评估设置(诊断实验用 512-token prefill + 生成 256 token;RULER 最多用到 32K 上下文)所处的范围内,这种「衰减与否」的问题并不明显发作,但对于相邻量化论文(KVQuant)所瞄准的「上千万上下文」体制,这确实是一个悬而未决的真实问题。

6. 实验结果:复现与解读

6.1 长距离检索:32K 上下文下的 RULER

表 2(对应论文 Table 2)报告了 Llama-3.1-8B-Instruct 在 32K 上下文下的 RULER 性能,把 QEvict 与匹配 20% 缓存预算的淘汰基线,以及在可比内存开销下的量化基线进行了比较。

Figure 3 (对应论文 Table 2): 32K 上下文下的 RULER 性能

Figure 3(对应论文 Table 2,复现为图片):Llama-3.1-8B-Instruct 在 32K 上下文长度下的 RULER 性能。QEvict 在淘汰和量化两大基线家族中都取得了匹配预算下的最佳宏平均。

仔细读这张表:QEvict 在 13 个 RULER 子任务(聚合、大海捞针的多个变体、问答、跟踪)上取得 87.6 的宏平均,比最强的匹配预算淘汰基线 Layer-DefensiveKV 高出 1.2 分,比最强的可比内存量化基线(KVQuant-3b)高出 8.6 分——同时与未压缩的 Full-KV 参考(使用 5 倍内存)只差 2.4 分。最引人注目的单项数字是 CWE(常用词提取,一种聚合任务):QEvict 得分 43.98,对比最好的淘汰基线得分 26.80(CriticalKV)——差距超过 17 分。这在机制上是合理的:聚合任务需要综合上下文中非局部分散的信息,这正是「我 1 万个 token 之前以为不重要的某个窗口,结果对这个特定聚合查询来说很重要」最容易出现的场景,永久淘汰在这种场景下受损最严重。反过来,在 MK-3(一种更难的多 key 检索变体)上,QEvict 的 77.80 落后于 DefensiveKV 的 97.00——这是一个 INT2 层的量化噪声显然确实在需要精确 key 匹配的任务上付出了一些精度代价的案例,与上文关于 qq 对单针精度敏感型任务可能不是最优的设计讨论相一致。

6.2 长上下文理解:三个预算下的 LongBench

表 3(对应论文 Table 3,复现为图片)报告了 Llama-3.1-8B-Instruct 和 Mistral-7B-Instruct-v0.2 在 12 个任务上的 LongBench 性能,涵盖单文档问答、多文档问答、摘要和小样本学习,在 20%、10%、5% 三个 KV 内存预算下。

Figure 4 (对应论文 Table 3): 三种预算下的 LongBench 性能

Figure 4(对应论文 Table 3,复现为图片):跨三种 KV cache 预算和两个模型的 LongBench 性能。QEvict 相对匹配预算基线的优势随着预算收紧而扩大。

值得强调的模式是,差距随着预算收紧如何变化。在 20% 内存下,QEvict 的宏平均得分为 46.4(Llama)/ 37.8(Mistral),对比最强淘汰基线的 44.0 / 37.4——只有约 2 分的适度优势。但在 5% 内存下,差距扩大到 Llama 上 9.7 分、Mistral 上 4.7 分。这正是可恢复层假设所预测的:当预算足够让每个方法都保留一个相对较大的全精度集合时,永久淘汰犯的错误影响较小(有更多余量去吸收一次错误决策)。而在极紧的预算下,每一次淘汰决策都是高风险的,这正是拥有一个便宜的「也许」层——而不是强迫每个窗口都做出硬性是/否判断——最能带来收益的地方。在我看来,这种随预算收紧而扩大的差距,是论文中对核心论点最有说服力的一条证据。

6.3 压缩下的推理能力:GSM8K

Figure 5 (对应论文 Fig.3): KV cache 压缩下的 GSM8K 准确率

Figure 5(对应论文 Fig.3):KV cache 压缩下的 GSM8K 准确率。QEvict 与代表性淘汰、量化基线在三个指令微调语言模型(Llama-3.1-8B-Instruct、Mistral-7B-Instruct-v0.2、Qwen2.5-7B-Instruct)上的准确率-内存权衡曲线。

GSM8K 是 LongBench/RULER 之外一个有用的补充 benchmark,因为它测试的是多步自回归生成质量,而不是单次检索/理解式回答——缓存需要在模型自己生成的多个推理步骤中始终保持有用,而不仅仅是为了提取一个最终答案。三个模型各自面板中的准确率-内存曲线显示,QEvict(每个面板中高亮的线)在低到中等内存预算下始终贴近 Pareto 前沿的顶部,在 10%-20% 内存预算区间优势尤为明显——这与机制的核心主张一致:多步推理正是模型可能需要「回头看」自己在很多步之前生成的中间推理(核对一致性、避免矛盾、检索早期子结果)的场景,而这恰恰是永久淘汰处理得最差的场景。

6.4 端到端服务效率:与后端相关的权衡

Figure 6 (对应论文 Table 4): 端到端推理效率

Figure 6(对应论文 Table 4,复现为图片):Llama-3.1-8B-Instruct 上的端到端推理效率,256-token prefill,生成 1024 个 token,batch size 32。

对任何真正考虑部署这套方案的人来说,这是最重要的一张表,也是论文对一个真实局限性最坦诚的地方。两种注意力后端给出了截然相反的结论:

eager/SDPA 注意力下,QEvict 仅带来 0.5% 的首字延迟(TTFT)开销(689.3ms → 692.4ms),把每 token 延迟(TPOT)降低了 9.3%(84.05ms → 76.26ms),并把解码吞吐量提升了 9.8%(379.26 → 416.44 tok/s)——一个干净、纯粹的胜利。这是合理的:由于实际参与计算的有效缓存变小了(大部分是 INT2,一部分被淘汰),注意力操作本身的原始计算与内存带宽成本会下降,而在 eager/SDPA 路径下,这份节省超过了反量化和路由的簿记开销。

FlashAttention-2 下,情况反转:TTFT 开销增至 14.4%(675.0ms → 772.0ms),TPOT 变 145%(59.74ms → 146.35ms),解码吞吐量下降 59%(535.11 → 218.05 tok/s)——尽管峰值 GPU 内存确实下降了实实在在的 29.7%(29.54GB → 20.78GB)。原因,正如论文在局限性一节中坦诚指出的,是架构层面的:FlashAttention-2 的速度恰恰来自从不物化完整的注意力概率矩阵——但 QEvict 的路由机制需要那些注意力概率来计算公式(1)中的累积窗口分数。所以在每一次路由事件,QEvict 都必须从 FlashAttention-2 回退到较慢的 SDPA 路径,仅仅为了暴露打分所需的注意力权重,还要在每次注意力调用前重建(反量化、重新应用 RoPE)活跃的低比特窗口。在非融合的 eager 路径中,这份额外工作相对节省而言相当便宜;而在融合的 FlashAttention-2 路径中,这份额外的非融合工作变成了主导成本,足以抵消并反超内存节省带来的收益。

这是一个如果只看吞吐量headline数字很容易忽略的重要微妙之处:QEvict 当前的实现用内存换取了一个与后端相关的延迟成本,同时实现内存节省FlashAttention-2 的速度优势,需要目前还不存在的、针对这种方法的融合低比特注意力与路由核函数——论文正确地把这标记为未来工作,而不是宣称已经解决。

6.5 消除实验的提示:路由周期和量化层比例的敏感性

虽然论文将详细的消除实验放在附录里,但正文中仍报告了一个有用的定性结果:在不同 benchmark 上扫描路由周期 Ω\Omega、量化层比例 qq、以及可恢复层的量化精度之后,结果普遍倾向于 Ω=8\Omega=8q=0.70q=0.70。还有一个值得单列出来的发现:INT2 在固定字节预算下提供更广的历史覆盖面,而 INT4 用更少的覆盖面换取更低的量化误差——这本质上是另一个层面的“覆盖面 vs 保真度”权衡,与 qq 本身控制的“全精度 vs 量化”权衡处于同一个设计轴上。对從事部署调优的人来说,这意味着实际上有两个可以独立调节的旋钮:“量化层占引入预算的比例”和“量化层本身的位宽”,两个参数都影响最终的精度-内存权衡点,但影响的机制不完全相同。

7. 局限性(论文陈述的,以及我自己的观察)

论文自己陈述的局限性(第 5.1 节):当前实现在注意力之前重建活跃的低比特窗口(而不是直接在量化表示上计算注意力),并在路由事件时选择性回退到 SDPA,这两者都引入了开销,而一个融合核函数的实现本可以消除这些开销。评估也只集中在 decoder-only 模型和固定大小的连续窗口上,把自适应窗口边界和其他架构(比如 encoder-decoder,或标准 GQA transformer 之外的架构)留给未来工作。

8. 批判性分析

这篇论文特有的弱点与缺陷。 首先,FlashAttention-2 吞吐量回归(表 4 中解码吞吐量下降 59%)是一个严重的实际注意事项,如果读者只看 LongBench/RULER 的准确率表,很容易错过——一个把你的吞吐量减半以节省 30% 内存的系统,对延迟敏感型服务而言绝不是免费午餐,论文应该把这个权衡更早、更突出地摆在前面,而不是把它作为文末简短提及的「局限性」。第二,持久化量化台账自身的内存开销从未被量化。每个曾经被降级过的窗口都会一直保留其量化编码和量化参数(以便廉价地重新晋升),但论文从未报告台账本身占用总内存预算的比例,尤其是在高抖动负载下(许多窗口反复循环)——这正是那种可能在对抗性或长期运行条件下侵蚀 headline 内存节省的「隐藏成本」。第三,关于路由周期 Ω\Omega 和量化层比例 qq 的消融实验被放在正文没有复现的附录里,主文只报告了跨 benchmark 的单一「最佳」配置(Ω=8\Omega=8,q=0.70q=0.70)——但第 6 节自己的结果(MK-3 检索表现不佳,LongBench 中随预算收紧而扩大的差距)表明这些超参数在不同任务类型上并非一致最优,读者若不深入附录很难评估其敏感性。

作者低估或省略的局限性。 评估仅限于 7-8B 参数量级的模型。晋升/降级动态、Ω=8\Omega=8 窗口划分的选择,以及 q=0.70q=0.70 的分配比例,是否能推广到更大规模模型(70B+,注意力头数不同)或专家混合(MoE)架构(其中每个专家路由决策的注意力模式可能表现得相当不同),完全没有测试。另外,路由周期 Ω\Omega 引入了一个真实的「延迟 vs 新鲜度」权衡,论文没有明确分析:每 8 个 token 才路由一次,意味着重要性信号在任意时刻可能滞后多达 8 个 token,对于生成过程中极其快速的话题转变(无论是对抗性的还是自然发生的),这种滞后窗口可能比论文的消融实验(仅在相对稳态的长文档任务上评估)揭示的影响更大。最后,论文从未讨论多请求/多租户服务场景(表 4 中的 batch size 32 是单一负载的复制,而不是并发的异构请求)——真实生产环境中的服务涉及不同上下文长度、不同生成阶段的请求共享 GPU 资源,QEvict 按请求的路由开销在真正异构的 batch 中如何叠加,目前尚不清楚。

具体的、可操作的改进建议。 (1)显式报告台账内存开销与抖动率的函数关系,理想情况下用一个专门设计用来最大化层级间震荡的最坏情况对抗性负载,让实践者能够提前预算,而不是在生产环境中凭经验才发现。(2)研究一种混合注意力后端策略——例如,大部分解码步默认使用 FlashAttention-2,只在(不频繁的、每 Ω\Omega 个 token 一次)路由事件本身回退到 SDPA,而不是让人以为回退影响整个区间——论文的描述暗示 SDPA「在路由事件时」被使用,但表 4 的吞吐量数字表明开销远大于仅仅每 Ω\Omega 步一次的简短回退所能预测的量,澄清 FlashAttention-2 的减速究竟集中在哪(只在路由事件步,还是因为反量化而发生在每个解码步),会让这个权衡分析更具可操作性。(3)把目前的消融网格(仅 Ω{1,8,32}\Omega \in \{1,8,32\} 和少量 qq 取值)扩展成一个针对 (Ω,q)(\Omega, q) 联合的正式敏感性热力图,并按任务类型(检索密集型 vs 聚合密集型 vs 推理密集型)拆分报告,而不仅仅是宏平均,因为第 6 节自己已经显示该方法的优势随任务类型有明显不同。(4)至少测试一个 30-70B 规模的模型,如果可行的话再测试一个 MoE 架构,以确定核心发现(基于窗口的打分稳定性、三层可恢复性)是否与模型规模无关,还是只对当前研究的 7-8B 级别成立。

8b. 为什么累加器设计不只关乎性能,还关乎正确性

值得再多花一点时间讨论公式(1)中一个容易被读过去的细微之处:递推关系 St(w)=StΩ(w)+(新贡献)\overline{S}_t^\ell(w) = \overline{S}_{t-\Omega}^\ell(w) + (\text{新贡献}) 只有在窗口的身份在跨路由事件间被保持一致时才有意义——即系统需要知道路由事件 tt 时的「窗口 47」和 tΩt-\Omega 时的「窗口 47」是同一段逻辑 token 跨度,即便它的物理存储位置和表示(全精度还是 INT2)在两次之间可能已经变化。这意味着 QEvict 的实现必然维护一个独立于层级分配的、稳定的「窗口到位置」映射——这是数学公式里隐含、但有实际工程重量的簿记要求:每一次晋升或降级都必须更新层级归属,同时不打乱用于累积分数的身份标识。对比一个假想的无状态重新设计:每次路由事件只用当前 Ω\Omega 个 token 区间内该窗口的注意力重新计算分数(即完全去掉 StΩ(w)\overline{S}_{t-\Omega}^\ell(w) 这一项)——这种方案实现起来更简单(不需要为每个窗口维护持久化的分数状态),但会丢掉观察三所表明的正是重要的历史信号:一个 200 个 token 之前很重要、但最近 8 个 token 里很安静的窗口,打分会显得它从未重要过,这就违背了引入一个持久性、累积性重要性概念的整个动机。论文没有用这种方式明确表述,但累加器的设计与第 3 节的诊断发现是不可分割的——它正是「重要性有持续性但不是静态的」这一发现在数学上的直接编码。

10b. 把 QEvict 的设计维度与近亲方法对比

论文本身的 Table 1 做了一个定性比较(选择性淘汰/低比特保留/窗口路由/动态恢复),但值得逐一走一遍每个竞争方法为了保持设计简单实际放弃了什么,因为这才是 QEvict 组合这些特性并非几个已有想法的显而易见拼接、而是真正有非平凡贡献的关键。

StreamingLLM 刻意保持简单:少量 sink token 加一个滑动的近期窗口,完全没有打分机制。这几乎是零计算成本,但它没有任何机制来保留任何历史上重要但不在近期的内容,这正是为什么它在表 2-3 几乎所有任务上的 RULER 和 LongBench 数字都是最弱的——它甚至没有尝试解决 QEvict 所针对的问题,而是在解决一个更便宜、更受限的问题(有界内存的流式处理),恰好在同一批 benchmark 上被评测。

SnapKV / AdaKV / CriticalKV 都在改进重要性如何被打分(分别基于观察窗口、按头自适应预算、value 感知),但淘汰决策仍是二元且永久的——它们都在让这个一次性决策变得更聪明,而不是让决策变得可逆。QEvict 的贡献与它们是正交的:理论上你可以把 QEvict 的累积注意力打分器换成 AdaKV 的自适应按头预算分配,放进 QEvict 的三层层级结构里,因为路由机制(算法一)只需要每个窗口某种标量重要性信号,而不特指 QEvict 使用的那一种。论文没有探索这种混合方案,在我看来这是一个相当自然的下一步实验。

DefensiveKV / Layer-DefensiveKV 显式建模对未来注意力的不确定性(对之后关注对象分布性漂移的鲁棒性)——从动机上看是与 QEvict 最接近的前人工作,因为两者都在回应同一个底层问题(重要性随时间变化)。区别在于机制:DefensiveKV 试图让单次淘汰决策对未来漂移更鲁棒(在决策时就对冲风险),而 QEvict 完全绕开了对鲁棒一次性决策的需求,允许决策被重新审视。表 2 的 RULER 结果显示两者在不同子任务上各有优势(Layer-DefensiveKV 在一些大海捞针变体上胜出,QEvict 在聚合和跟踪任务上胜出),说明这两种哲学——鲁棒预测 vs 可逆决策——并非彼此严格主导,甚至有可能组合起来。

KIVI / KVQuant / ZipCache 很好地解决了另一个问题:廉价地保留广泛覆盖面。从 QEvict 的视角看,它们的弱点是精度分配统一或粗粒度——它们无法区分「我很确信这重要」和「我完全没有信号」,所以在极紧的预算下无法把精度集中到最需要的地方。QEvict 本质上是引入了它们的量化机制(非对称的按通道 key/按 token value 方案直接继承自 KIVI),但把它包裹在一个感知重要性的分配策略里,这正是为什么表 2 的量化比较组中 QEvict 用更少的内存(20% vs 约 22%)击败 KVQuant-3b 达 8.6 分——这个胜利不是来自更好的量化编码,而是来自把同样的量化预算花得更有选择性。

这样看下来,QEvict 并没有提出一个新的打分函数或新的量化编码——两者都是从前人工作借来的。它真正的新贡献是位于两者之间的路由策略:让重新审视决策变得廉价的可恢复三层层级结构与迁移稳定台账。这是一个值得明确指出的公正描述,因为只看结果表的读者可能会以为收益来自更好的打分器或更好的量化器,但实际上主要来自对现有技术在架构层面的编排。

8c. 对一些常见疑问的回应

量化层里的窗口还能参与注意力计算吗? 能,这正是 QEvict 与纯淘汰方法的根本区别。在标准的淘汰方法中,被标记为“不重要”的 token 会从物理内存中删除,后续时刻它根本不存在,无法参与任何计算。在 QEvict 中,被降级到 INT2 层的窗口仍然在每一步解码时参与完整的注意力计算(只是先要在每个识别该层的时刻反量化回浮点),这意味着它们对最终输出仍有真实贡献,只是保真度低一点。

如果一个窗口一直得到高分,它会永久保持在全精度层吗? 是的,除了每次路由事件重新排名时它碰巧被其他得分更高的窗口挤出前 KfK_f 个位置。因为公式(1)的分数从不衰减,一个持续获得高注意力的窗口实际上会随时间积罯越来越高的分数,相当于在排名中获得一个越来越安全的位置——这正是观察三中 oracle 集合 0.98% 低复苏率的数学后果。

为什么不对每个注意力头单独路由? 完全可以这样做(AdaKV 就是这个思路),但代价是簇记开销依 HqH_q 倍增长——每个窗口需要为每个头单独记录层级归属和量化状态,而不是共享一个。QEvict 选择在所有头上平均,探眸 W_cand 的处理开销保持平坦。

9. 可复现性说明

论文报告了具体、可核对的超参数:路由周期 Ω=8\Omega=8,5 个受保护 sink token,32 个 token 受保护近期区域,INT2 量化层,量化层比例 q=0.70q=0.70,在 LongBench、RULER(32K 上下文)和 GSM8K 上,针对三个广泛可用的指令微调开源模型(Llama-3.1-8B-Instruct、Qwen2.5-7B-Instruct、Mistral-7B-Instruct-v0.2)进行评估。这三个 benchmark 都是公开的、在 KV cache 压缩文献中标准使用的,这在原理上让独立复现和比较变得可行。论文声明完整的基线配置、解码协议和字节级内存核算在附录中(正文引用为 Appendix B、G、H、I、J)——试图精确复现结果的读者需要这些附录才能获得确切的基线超参数,以及用于在结构上如此不同的方法(窗口+量化 vs 纯 token 淘汰 vs 纯量化)之间计算「匹配内存预算」的精确字节核算公式。摘录的正文中没有提到代码或工件发布;独立复现需要根据给出的公式重新实现路由算法、迁移稳定的量化台账,以及诊断指标(FMM、Global LIR、Selection Churn)。

10. QEvict 在更广阔的 KV cache 压缩版图中的位置

QEvict 最好被理解为纯淘汰(StreamingLLM、H2O、SnapKV、AdaKV、CriticalKV、DefensiveKV)与纯量化(KIVI、KVQuant、ZipCache)之外真正意义上的第三类方法:一种混合、动态的层级结构,借用了淘汰「保留重要的东西」的直觉,以及量化「什么都保留,只是更便宜」的直觉,用一个真正可逆的中间层来化解两者之间的张力,而不是选择一种哲学放弃另一种。它与经典的内存层级设计有更多共同的血统(晋升/降级/淘汰结构直接类比于带有中间层的 CPU 缓存层级结构,或者带有「最近使用但不驻留」列表的操作系统级按需换页),而不是与之前 KV cache 专用工作有更多共同点——这是一个思考这个想法接下来能走向何处的有用视角。比如,能否用第四层(磁盘支持,像 Tutti 的 SSD 支持 KV cache 那样)进一步扩展这个层级结构,把 QEvict 的 INT2 层变成介于全精度「L1」和 SSD 支持的「L3」之间的「L2」,来支撑更长的上下文?论文没有探讨这个方向,但结构上的契合看起来是自然的。

10c. 对部署方的实际建议

如果你正在考虑是否在自己的服务栈中采用 QEvict,这里有一些比约分提醒。首先看你当前的注意力后端:如果你的服务栈已经重度依赖 FlashAttention-2(大多数现代高吸造量 LLM 服务框架都是这样),那么表 4 的数字意味着直接接入当前开源实现可能会带来决定性的吞吐量回退,除非你评估后发现自己可以投资实现一套融合的低比特注意力核函数。反之,如果你的服务堆栈仍在使用 eager/SDPA 路径(比如较旧的部署,或一些小规模实验环境),那么表 4 的数字直接告诉你这是一个干净、无代价的胜利——TTFT 几乎不受影响,而吐吐量、TPOT 都得到改善。

第二,你的主要任务类型很重要。如果你的业务以单次、准确的关键事实检缢为主(比如合同审查中需要定位到确切一句话),那么 MK-3 类任务上 QEvict 相对 DefensiveKV 的 19 分差距应该让你保持警惕,可以考虑为这一类任务专门降低 qq 值或干蓨回退到全精度。而如果你的业务更接近长文档摘要/聚合式问答(信息分散在整个上下文里,需要综合才能答对),那么 CWE 任务上超过 17 分的优势说明 QEvict 正是你想要的那种方案。

第三,要认真对待台账内存开销的未知数。在你自己的负载下先进行一次小规模压测,人工构造一个高频振荡的极端场景(比方说一个总是在重要性阈值附近波动的合成工作负载),监控实际内存占用是否明显超出 Table 4 中报告的峰值——因为台账本身的开销没有被报告,你很可能会遇到一些导迁论文基准时未见过的隐藏开销。

10d. QEvict 与之前发布过的 KV Cache 系统类工作对比

对照本开系列之前已发布的若干篇 KV cache 类论文笔记(例如 AnchorKV、DynaCalKV、LOCKS、MosaicKV、CounterCausalKV),一个有意思的共同主题是:这些汇合需要的共同背景知识(注意力片段、频带限制、量化基本知识)可以在十次篇拆解中重复利用,但每篇论文在“统一重要性信号”到“资源分配决策”的进一步机制上采取了完全不同的片面。CounterCausalKV 与 QEvict 在动机上最接近——两者都在处理“当前看似不重要、但未来可能重要”的情况,但 CounterCausalKV 的重点是一个反因果驚讶信号,而 QEvict 的重点是一个可逆、分层的路由结构,二者实际上并不互斥——一个可能的未来组合是用反因果驚讶作为 QEvict 的打分信号,而不是单纯的累积注意力。

10e. 对中文读者的额外补充:为什么这篇工作对国内 LLM 服务团队有现实意义

对于国内很多在自建推理服务引擎(基于 vLLM、SGLang 等开源框架二次开发)的团队来说,QEvict 这篇工作最值得关注的并不是它的绝对指标领先于基线多少,而是它提供了一套可以直接移植到既有淘汰引擎上面的增量式改进方法。如果你的推理引擎已经实现了类似 SnapKV 或 AdaKV 的打分与预算分配逻辑,理论上只需要在现有打分基础上加一个量化中间层、一套迁移稳定台账机制,而不需要重写打分部分。这对已经在生产环境中跨过一道工程雨量相当大。

但也要看到这套机制的发行成本不小。FlashAttention-2 下的吐吐量回退意味着,如果你的推理服务层已经对延迟/吐吐量非常敏感(比如面对 C 端客户的 SLA 承诺),引入 QEvict 前必须先在自己的就绪监控中确认内存节省能否转化为更大的 batch size 或更长的可处理上下文——否则单纯的内存节省本身不会自动转化为业务价值,反而可能因吐吐量下降而靠后。对于仍在使用未融合 SDPA 路径、对延迟宽容度更大的场景(比如离线批处理、高吞吐非实时任务)来说,这则是一个几乎无代价的内存节省方案,值得优先试点。

10f. 小结:一张决策流程图

把前面所有讨论抓在一起,对于任何想判断自己的场景是否适合 QEvict 的人,可以用下面这张判断图快速自档:首先问自己的推理引擎用的是 eager/SDPA 还是 FlashAttention-2。如果是前者,QEvict 几乎是无代价的选择;如果是后者,需要认真评估吐吐量回退是否可接受,或是否有精力自行开发融合核函数。其次问自己的主要任务类型属于聚合/跨段推理还是单针精确检索:前者 QEvict 优势明显,后者优势较小。最后问自己的预算区间:预算越紧,QEvict 相对二元淘汰的优势越大,因为可恢复层恰恰在预算最紧时发挥最大价值。这三个问题的答案基本决定了 QEvict 对你的具体部署场景是否合适。

10g. 一个完整的典型解码周期演示

为了让机制更具体,让我们追踪一个单个窗口在一次完整解码过程中可能经历的完整生命周期。假设模型正在处理一份长文档问答任务,文档中有一段提到了某个关键人名的背景信息,假设它在窗口临时编号 217。在提礠阶段的前几个路由事件,模型的查询注意力正集中在问题本身,窗口 217 的累积分数很低,它在第三次路由事件时被降级到 INT2 层——根据式(6),它从 Full\mathrm{Full} 过渡到 Quantized\mathrm{Quantized},并根据伪代码第 9-12 行的逻辑第一次被量化,量化编码存入台账。接下来好几个路由事件,该窗口保持在 INT2 层,但它仍在参与每一次解码步的注意力计算,它的累积分数仍在根据式(1)持续更新。当推理进入推理阶段后,模型需要回溯早先提到的那个关键人名以保持一致性,查询开始大量关注窗口 217。累积分数快速上升,到下一次路由事件时,它重新排入前 KfK_f 个候选,根据式(6)从 Quantized\mathrm{Quantized} 晋升回 Full\mathrm{Full}。此时伪代码第 15-19 行的逻辑生效:从台账中取出之前存下的量化编码,反量化回浮点,并在其原始位置上重新应用 RoPE。整个循回中,该窗口只在第一次降级时发生了一次量化误差,之后所有的降级/晋升循回都在复用同一份编码,这正是迁移稳定量化机制在具体场景下如何防止误差绵延的完整展示。

10h. 若要自己实现一个类似机制,需要先想清楚的几个预算团震

如果你想在自己的推理引擎里实现一个类似 QEvict 的可恢复层级结构,先把下面几个预算团想清楚会少走很多弯路:第一,你需要一个能对窗口行进行统一编号的机制,并且这个编号在全精度层、量化层、被淘汰之间切换时保持稳定——这含得在你现有的内存分配器上新引入一层抽象,而不能直接让窗口地址等同于 GPU 显存中的真实指针。第二,你需要在自己的注意力内核里预留一个能够掴取 SDPA 风格完整注意力概率矩阵的回路,即便你平时一直用 FlashAttention-2——这意味着你的核函数库至少要同时维护两套实现。第三,你需要一个独立于主 KV 存储的台账数据结构,用来追踪每个窗口的量化编码和 scale/zero-point,并确保它的生命周期与对应窗口的实际驻留状态保持一致。把这三个部分先拆开设计清楚,再开始实现路由算法本身,会比一开始就把三者绠在一起实现要容易许多。

10i. 与长上下文 SSD 分层方法的拼接可能性

本系列笔记之前写过的 Tutti 提出用 SSD 存储提升上下文容量的思路:当 GPU 显存不足以容下整个 KV cache 时,把一部分历史片段以盘阵换方式卸到 SSD 上,需要时再启回。把这个思路与 QEvict 的三层结构拼在一起,一个自然的延伸是四层层级:全精度(L1)→INT2 可恢复(L2)→SSD 备份(L3)→真正的永久淘汰(L4)。QEvict 现有的公式(3)-(5)应该能很自然地推广到四层:只需把 Et\mathcal{E}_t(第三层逆向集合)拆成两个子集合,一个写入 SSD 保留可启回能力,一个彻底丢弃。这意味着在极端长上下文(百万 token 以上)场景下,一个“QEvict + SSD 备份”的组合方案理论上可以同时取得比单一方案更广的覆盖面和更低的延迟回退,但本论文并未探讨这一方向,值得后续工作跟进。

10j. 小注:关于“可恢复”一词的翻译选择

论文标题里的 “Recoverable” 在中文里很容易被译成“可恢复的”或“可恢归的”,但这里本诺笔记选择一直用“可恢复”,因为它更准确地对应论文的语义:不是在某个固定时机把历史“归因”为某种状态,而是任何时刻都能把一个已降级的窗口“恢复”回到它应得的全精度形态,而无需付出与全新创建等价的计算/重新量化代价。这个词义上的小差异其实对应了机制上的一个关键点:迁移稳定台账正是为了保证这个“恢复”操作能够便宜、可靠、不碰到重复量化误差的时间冒。

10k. 再说说选定这篇论文的理由

在星期六/星期日的 catch-up 周期里,我们往往优先选择那些在方法上与近期已发布的工作形成明确对话、但又带有真正新维度的论文。QEvict 满足这个标准:我们之前已经写过 KV cache 领域里很多量化类(KVQuant、OScaR、SparDA)、驱逐类(AnchorKV、DynaCalKV)、基于预测鲁棒性的方法(CounterCausalKV),但还没有一篇将“量化”与“驱逐”真正融合成一个可两方向切换、可回溯性保证的统一层级结构。QEvict 恰好补上了这一块空白,且它中部的两个新诊断指标(FMM 和 Global LIR)本身就是可以独立于具体算法、被后续工作候复用的分析工具,这也是一个值得在周末 catch-up 时间细窗的地方。

10l. 对应用方的一个参考决策树

最后,把前面每一节的建议抓在一张概念上的“决策树”里:若你的服务层目前绑定 FlashAttention-2 且对吐吐量敏感 → 暂时观望,等待融合核函数成熟或自行投入开发;若你的服务层仍在用 eager/SDPA 或对延迟不敏感 → 可以很快推进集成;若你的主要任务是单针精确检索 → 优先考虑降低 qq 或直接继续用 DefensiveKV 类方案;若你的主要任务是聚合/写作/多步推理 → QEvict 的优势最明显,很值得一试;若你处在高并发、异构请求的商业服务环境 → 目前论文未提供足够证据,需要自己补做小规模压测。

10m. 本文未回答的一个开放问题

论文中没有明确回答的一个问题是:当 QEvict 与其他内存优化手段(比如 GQA、权重量化)堆叠时,量化后的权重误差与 QEvict 自己的 INT2 KV 量化误差是否会产生非线性叠加效应。在实际生产部署中,模型权重本身往往已经被量化到 FP8 或 INT4,在这种基础上再叠加 KV cache 的 INT2 层,两种误差来源是否会相互放大(比如权重量化引入的激活分布偏移,反过来影响 KV 量化的 scale/zero-point 校准准度),还是会相互殓销?这需要一个专门的联合量化敏感度实验才能回答,而论文本身完全没有提及。对于想将 QEvict 与已经上线的量化服务栈(比如 AWQ 或 GPTQ 量化过的模型)结合的团队,这是一个在上线前值得先小规模验证的具体风险点。

10n. 对未来评估基准的具体建议

如果你正在设计下一代 KV cache 压缩方法的评估方案,这篇论文提到的三个诊断指标值得直接写入你自己的开发流程:(1)FMM——只需一份未压缩的 FullKV 参考运行日志,就能对任何新方法计算出它到底丢失了多少未来真实会用到的信息;(2)Global LIR——只需跟踪一个窗口/token 集合的集合成员变化历史,就能定量新方法假设的“重要性稳定性”成立到什么程度;(3)Selection Churn——一个实现成本极低的 Jaccard 距离指标,就能暴露方案在前后两次决策之间有多不稳定。将这三个指标作为标准报告项,对整个 KV cache 压缩领域的可比性会有很大提升——目前大部分前人工作只报告终端任务得分,不报告这类机制级别的中间量。

10o. 再补一段:关于累积分数无限制增长的实屌考量

前面在设计选择一节提到累积分数永不衰减可能在极长生成中带来问题,这里值得再具体化一下。公式(1)中的 St(w)\overline{S}_t^\ell(w) 是一个无上限的浮点数累加。在实际实现中,虽然 BF16/FP32 的数值范围足以容下非常大的累加值而不会溢出,但随着累加项数量增大(每 Ω=8\Omega=8 个 token 累加一次,一个 100 万 token 的超长上下文会产生超过 12.5 万次累加),浮点累加误差(每次加法操作都存在存在容量不足而四舍五入的小残差)在理论上也会缓慢积罯。对于上百万 token 的上下文,这个误差量级在实践中应该远远小于量化本身引入的误差,但这也意味着若未来有人要把 QEvict 推向 KVQuant 所目标的千万 token 上下文体制,就需要对累加器本身的数值稳定性做一个专门的分析,而不能直接假设它在任何尺度下都同样适用。

10p. 最后的提醒:不要设备遇读

本篇所有地方性建议都建立在一个共同前提上:你已经读过这个领域里大量的前人工作,对“重要性不稳定”、“预算受限”、“量化误差传播”这一套基本词汇已经很熟悉。如果你是刚开始接触这个领域,建议先完整看一遍第 3 节的三个观察,把它们当作基础直觉,再回来看第 4 节的公式推导——那样会发现每一个公式项都能回溯到一个具体的实验观察,而不是平白无故出现的数学符号。

10q. 关于 sink token 选择的一个小细节

论文默认使用 5 个受保护的 sink token,这个数字来自 StreamingLLM 的经验结果,而不是针对 QEvict 自己的专门调优。值得注意的是,sink token 在 QEvict 的体系中的角色与全精度层/量化层不同:它们不参与任何打分或路由决策,无条件地永远处于全精度。这意味着如果你的应用场景中 sink token 的作用不如 Llama 类模型那么明显(比如某些经过特殊预训练的模型),这部分预算可能花得不够划算,值得在自己的模型上重新验证。

11. 结论

QEvict 把一个未被充分审视但很简单的观点变得严谨:KV cache 淘汰背后的标准假设——一旦某个 token 看起来不重要,它就会一直不重要——在足够多的场合下被实证证明是错误的,尤其是对于聚合类长上下文任务和多步推理。它给出的解决方案,一个带迁移稳定量化的三层可恢复层级结构,在淘汰和量化两类基线上都带来了一致的「每字节精度」提升,在预算最紧、每一次淘汰决策风险最高的场景下收益最明显。这套机制本身很精巧,而诊断工作(FMM、Global LIR、Selection Churn)独立于具体算法本身就是一份有用的贡献——未来的 KV cache 压缩论文如果能照例报告这些相同的诊断指标,会很有价值。论文最令人耳目一新的坦诚之处在于,它明确指出了主要的悬而未决的问题:当前非融合实现的、与后端相关的权衡(在 eager/SDPA 下是干净的胜利,在 FlashAttention-2 下是真实的吞吐量回归)能否用融合核函数来弥合——在那之前,QEvict 是否适合某个特定部署场景,很大程度上取决于该部署已经绑定了哪种注意力后端。