稀疏注意力 × KV Cache Offloading:跨层联动必须回答的八个问题

稀疏注意力每步 decode 算出”哪些 KV 重要”,然后丢弃这个信号。KV Cache offloading 用 LRU/FIFO 做卸载,不知道哪些 KV 重要。把两者连接起来是直觉上正确的方向——但注意力层的逐 token 重要性评分,必须跨越时间、粒度、请求边界和因果闭环四道鸿沟,才能映射为存储层的逐 block 生命周期管理。本文逐一枚举这四道鸿沟上的关键问题,不提供设计方案,只提供设计者绕不开的决策点。


一、两条并行的通路

KV Cache 是 decode 阶段的开销主体。一个 70B 模型在 32K 上下文下,单次 decode 的 KV 读取量约 10 GB——即使 H100 的 HBM 带宽达到 3.35 TB/s,仅 KV 读取就需要 ~3ms,已经与整个 decode forward 的耗时相当。更关键的是,长上下文下 KV Cache 的总量可能远超 GPU 显存,必须通过 offloading 把部分 KV 移到 CPU 或 NVMe。

与此同时,同一条流水线上的另一个模块——稀疏注意力——每步 decode 都在解决一个看似不相关的问题:怎么从全量 KV 中挑出最相关的子集,减少 attention 计算量。但它的副产品——”哪些 token 的 attention score 最高”——恰好是 offloading 最想要但得不到的信息。

1.1 注意力层每步都在选,但选完就扔

标准的 decode 流程:模型做 attention,稀疏注意力(StreamingLLM 保留 attention sink + 最近窗口,Vegas 按累积 attention score 动态选择,NSA/DSA 用轻量 Indexer 扫全序列筛出 top-k,CSA 先压缩再稀疏选择)在计算过程中自然筛选出最重要的 KV 位置。这些信息维度丰富:哪些 token 被反复读取、哪些 token 的 attention score 持续走低、哪些 token 刚刚从冷门突然变成焦点。

但推理引擎在 attention 完成后直接丢弃这些信息。下一轮 offloading 决策来临时,完全不知道上一轮 attention “看了哪些”。

1.2 offloading 层只看时间戳,不看重要性

当前 vLLM 的 Native KV Offloading 和 SGLang HiCache 的分层存储,卸载策略统一基于时间或访问计数:最久未访问的 block 优先移到 CPU,最常命中的 block(prefix cache)优先留在 GPU。

这个策略在 prefix-heavy 场景下表现良好——多个请求共享同一段 system prompt,前缀的访问频率直接反映其缓存价值。但进入 decode 阶段后,每个请求的 KV 序列是独一无二的,时间戳无法区分一个”很久没被访问但即将被需要”的 block 和一个”很久没被访问也确实不再需要”的 block。两者的处理应该完全不同。

1.3 两个模块之间缺一条信号线

一个模块在每步 decode 中自然产生”这个 token 重要吗”的信息。另一个模块每隔若干步做出”这个 block 还要留在 GPU 吗”的决策。两者之间没有信号传递——这就是问题空间的起点。

把这条线接上,直觉上是正确的方向。但接上之后,四层问题会依次浮现。


二、信号质量:稀疏注意力的评分能信吗?

引入稀疏注意力信号来指导 offloading 的前提,是回答第一层问题:这份信号本身是否具备足够的质量?三个维度:评分能跨时间积累吗?历史评分能预测未来需求吗?没有历史时怎么办?

2.1 时间错位:逐 step 信号如何积累为决策时刻的优先级?

稀疏注意力每步 decode 产出一组 top-k 索引。相邻 step 的 top-k 可能完全不同——一个 token 的 attention 焦点可以瞬间从上一句话的结尾跳到下一句话的开头。而 offloading 是周期性批量决策——两次卸载之间可能间隔数百 step。

这意味着:单步的 top-k 太脆弱,不能直接用;跨步的聚合才有意义。但聚合方式本身就是一个设计选择,每一种都有盲区:

  • 滑动窗口平均:一个 block 在 99 步中无人问津,第 100 步突然成为焦点。平均分仍然很低,block 可能在这 100 步中被卸载,而卸载正好发生在”它即将被需要”的前一刻。
  • 指数衰减:优先响应最近的信号,对突发的重要性变化反应快。但会过度放大末尾的噪声——如果一个 block 只是因为某个 token 在末尾几步中偶然被选中,得分会虚高。
  • 最大池化:保留峰值,不丢失任何一次”被关注过”的历史。但丢失频率信息——被关注一次和被关注一百次,在最大池化下得分相同。

没有唯一正确的聚合函数。不同的模型架构(attention 的集中度不同)和任务类型(代码补全的 attention 跳跃 vs 翻译的 attention 平滑)需要不同的选择。

2.2 单向性:过去不被关注的 KV,未来是否也不被需要?

这是整个方案中最难回答的问题。稀疏注意力给你的是回溯性信息:在已完成的 decode step 中,哪些 KV 被 attention 选中了。Offloading 需要的是前瞻性判断:在未来的 decode step 中,哪些 KV 会被需要。

两者的关系取决于时间跨度:

  • 短程(下一个 step):attention 焦点大概率与当前 step 相近。预测价值高。
  • 中程(50–200 step):相关性随距离递减,但语义段落内的 attention 模式具有连续性(同一段内的 token 反复互相关注)。
  • 长程(>200 step):几乎不可预测。长文档的跨段引用、代码中的远程函数调用——一个几十 step 前被忽略的 block 可能在未来某个时刻突然成为关键依赖。一旦它已被卸载,重新加载的 PCIe 延迟会造成显著的 decode 停顿。

这个问题没有完美解——它是稀疏注意力本身的信息边界决定的。但必须意识到它的存在:稀疏注意力的历史信号是未来需求的有偏估计。偏差的方向和量级取决于序列的注意力分散度:注意力高度集中的序列(如代码生成),历史信号对未来预测强;注意力均匀分散的序列(如创意写作的开放式发散),历史信号接近随机。

2.3 冷启动:没有历史时,用什么策略填补评分空白?

稀疏注意力需要”已经算过几轮 attention”才能产出有意义的评分。但新请求的 KV 从 prefill 结束的那一刻起就占用 GPU 显存——此时 offloading 决策必须立即开始,而稀疏注意力还来不及积累任何信号。

冷启动期间的策略决定了方案的下限。三个候选方向各有取舍:

  • 过渡期 LRU:在积累足够稀疏评分之前,退回到传统的时间戳策略。简单,但可能刚过过渡期就发现已经卸载了关键 block——过渡期的错误无法撤销。
  • 保守保留:新请求的前 N 步不触发任何卸载。最安全,但高并发下显存压力大——等于放弃了冷启动时段的所有显存调度。
  • prefill attention 作为种子评分:用 prompt 处理阶段的 attention pattern 作为初始信号。prefill 是一次性全量因果注意力(所有 Q 一起计算各自对前缀的注意力),decode 是逐 token 增量因果注意力(单个 Q 对全量 KV)——两种计算模式下的 attention 分布存在结构性差异,这个种子的预测价值取决于具体模型和任务。

三、信号翻译:attention 评分如何变成 offloading 指令?

即使稀疏注意力的评分质量过关,把评分翻译为 offloading 指令还需要跨越三道 gap:token→block 的粒度映射、多请求间的不可比评分、与已有 prefix caching 策略的优先级叠算。

3.1 粒度错位:token 级信号 → block 级决策

稀疏注意力选出一组 token 位置(最细粒度)。KV Cache offloading 搬的是 block(通常 16–256 token)。一个 block 内部可能同时包含 attention 焦点 token 和从未被选中的 token。

用什么聚合函数把 block 内所有 token 的 attention 评分压缩为一个 block 级的卸载优先级?

  • 取最大(max):只要 block 内有一个关键 token,整个 block 保留。对安全性最友好,但对显存最浪费——一个 256-token block 可能因为一个 token 而全部保留。
  • 取平均(mean):一个关键 token 被大量低分 token 稀释,block 可能被错误卸载。高频被关注的 block 和偶尔被关注的 block 在均值下不可区分。
  • 取 top-p%:在安全性和显存效率间折中。保留的 block 数取决于 p 的取值,但 p 没有先验——模型不同、任务不同,最优 p 也不同。

这个映射没有完美的数学解,因为 token 和 block 之间的语义归属关系是任意的——相邻 token 不一定语义相关(代码中一个换行符隔开了两个完全不相关的函数调用),语义相关的 token 不一定在同一个 block 内(跨段落的引用)。

3.2 多请求归一化:不同请求之间的评分怎么比较?

单个请求内部的 attention 评分只在其自己的 KV 序列中可比较。不同请求的 attention 分布各有不同尺度:

  • 请求 A:attention 高度集中——少数 token 占 90% 的 score,大部分 token 得分极低。
  • 请求 B:attention 均匀分散——每个 token 的得分相对平均。

请求 A 的”一般重要”token 可能得分高于请求 B 的”极其重要”token。但原始评分不可直接横向对比。而 offloading 决策是跨请求的全局资源调度——当 GPU 显存满了,必须选择卸载某个请求的某个 block,这个选择需要对所有请求的所有 block 做统一排序。

归一化策略直接影响结果:按每个请求内部百分位(自己在自己里面的 percentile)归一,保证了不同请求间的公平性但丢失了跨请求的绝对重要性差异(请求 A 整体就是比请求 B 的 attention 更发散,这本身是有意义的信息);按绝对阈值归一,保留了跨请求信息的可比性,但阈值的选择没有普适标准。

3.3 与前缀缓存的碰撞:两个保留理由如何叠加?

前缀缓存(prefix cache)中的 block 已经有了一条保留理由:被多个请求共享。稀疏注意力给它加上第二条:attention score 高。这两条理由叠加时,不能简单地取最大或求和——因为它们的语义不同。

实际上,prefix-cached block 的 attention 信号增量价值有限:如果它已被多个请求共享,共享本身就是最强的保留信号——attention 评分只是确认已知信息。真正的增量信息在于非共享 block:只属于单个请求、没有任何其他保留理由的 KV。对这些 block 而言,稀疏注意力评分可能是它们唯一的保留依据。

优先级叠算的方式决定了 offloading 策略的偏向:

  • 偏向前缀缓存的调度规则最大化共享收益,适合批量同类请求场景(如统一 prompt 的批量评估)。
  • 偏向稀疏评分的调度规则最小化关键数据丢失,适合长上下文混合请求场景(如 coding agent 的长时间多轮对话)。

四、系统副作用:联动之后会发生什么?

信号接入后,系统不再独立运作。两个新动态和一个潜在协同:注意力驱动的卸载可能制造自我实现的预言、与 speculative decoding 的 attention 模式冲突、以及卸载信号对 prefetching 的逆向应用。

4.1 自我实现的预言:卸载一个 block 等于永久杀死它

这是整个方案中最隐蔽的风险。一旦某个 block 因为”不在 top-k 中”被卸载到 CPU,它此后对 attention 计算完全不可见——无法进入后续任何 step 的 top-k,其稀疏注意力评分永久冻结在卸载前的数值。即使在 50 step 后的未来它本应成为 attention 焦点,系统也永远无法知道。

这个副作用的根源在于:卸载决策改变了评分机制本身的输入空间。被卸载的 block 失去了”证明自己的价值”的唯一机会——参与 attention 计算被选中。

解决这个问题的唯一办法是偶然回调(occasional recall):即使某个 block 的评分很低,仍以一定概率保留在 GPU,或定期从 CPU 重新加载一小批低分 block 参与 attention 计算,让它们有机会更新评分。但回调引入了新开销——PCIe 带宽用于”试探性加载”的比例。回调频率和带宽预算之间的权衡是这个问题的核心:太频浪费带宽,太疏错失关键 recall。

4.2 与投机解码的交互:draft model 的注意力也重要吗?

投机解码场景下,draft model 也做 attention——但它的 attention 模式与 target model 不同(模型结构、参数量、甚至 hidden state 来源都不同)。这引发一个额外问题:用谁的稀疏注意力评分来指导 offloading?

  • 只用 target 的评分:最权威——它决定最终的验证结果。但信号延迟增加一轮(验证在前,卸载在后,始终落后实际需求一步)。
  • 只用 draft 的评分:更实时——draft 在 target 之前运行,评分可提前用于下一轮 offloading。但权威性不足——draft model 的 attention 头数、hidden dim 都与 target 不同,其注意力焦点与 target 存在系统性偏差。
  • 两者融合:需要处理两种分布不可比的问题,增加了归一化的复杂度。

当前的 speculative decoding 实现中,这个问题完全未被考虑——draft model 产出的 hidden state 和 attention pattern 在验证后直接丢弃,不与 offloading 模块产生任何交互。

4.3 与 KV prefetching 的协同:卸载的逆过程

稀疏注意力的 top-k 信号不仅能指导”哪些该卸载”,也能指导”哪些该预取”——这是 offloading 的逆过程。已知 GPU 上保留的 top-k block 和已卸载 block 的 score 分布,可以预测:未来几步的 attention 展开后,哪些 CPU 上的 block 大概率需要被重新加载。

预取决策比卸载决策更需要前瞻性(问题 2.2 被放大到”预测未来”的尺度),但错误成本更低——一次成功的预取避免了一次同步 PCIe 等待(收益大),一次失败的预取只是浪费了带宽(损失小,block 下次真正需要时才重新加载)。这种”不对称收益”使 prefetching 比 offloading 更适合大胆使用稀疏注意力信号。

关于 KV Cache 预取的更多讨论,参见 KV Cache Prefetching:三层预取如何隐藏 KV 访问延迟

4.4 同一个词,两种含义:eviction 的术语混淆与信号门槛差异

前面三节讨论了一个共同的前提——用注意力信号决定”哪些 block 不应该留在 GPU”。但这个决定的执行方式有两种,它们对信号质量的要求完全不同。

“eviction” 这个术语在 KV Cache 的讨论中存在歧义

  • H2O/SnapKV 论文中的 eviction:永久删除。被淘汰的 KV 数据永远消失,即使未来某个时刻注意力焦点转向它,系统也永远无法恢复。错误代价是永久的信息损失和生成质量退化。
  • vLLM/LMCache 工程文档中的 eviction:层级迁移——GPU 显存 → CPU 内存 → NVMe 磁盘。被”逐出”的 block 只是搬到更慢的存储层,在需要时可以被重新加载。错误代价是一次 IO 延迟尖刺。

两者都叫 eviction,都做的是”从 GPU 移除一个 block”这个动作。但执行层的选择改变了决策层的容错空间:

执行方式 可逆性 错误代价 对注意力信号质量的要求
永久删除(H2O/SnapKV) 不可逆 永久信息丢失,生成质量退化 极高——不能误删
层级迁移(GPU → CPU) 可逆 一次 PCIe 延迟(~μs 级) 中——错了可以纠正
层级迁移(GPU → NVMe) 可逆 一次 NVMe IO 延迟(~ms 级) 中–高——纠正代价更大但仍可纠正

这个表揭示了一个隐藏的设计洞察:同一个注意力信号,用于指导”搬到 CPU”的质量门槛,远低于用于指导”永久删除”的质量门槛。 问题 2.1(时间错位)和 2.2(单向性)在层级迁移场景下严重程度降级——不那么准确的聚合函数、不那么可靠的历史预测,在可纠正的决策中都可以容忍。但对永久删除,这些问题仍然是硬的。

这也解释了为什么 H2O/SnapKV 论文中需要次模函数最优性保证(见 KV Cache 淘汰策略 §4.2)——它们的决策是不可逆的,所以需要在数学上证明信号质量够好。而本文讨论的 offloading 场景不需要这种保证——注意力信号可以给一个”不太好但比 LRU 好”的结果,依然有价值。

系统设计上的含义:同一套注意力评分基础设施可以服务两条决策线——低门槛的层级迁移(GPU → CPU)和高门槛的永久删除。元数据共享,tracking 共用,只有最终的阈值和执行动作不同。


五、经济性:做这件事值不值?

以上所有问题的解决都需要付出成本。最根本的权衡:稀疏注意力信号跟踪的 overhead,是否能被更好的 offloading 决策所覆盖?

5.1 元数据开销

为每个 KV block 维护 attention score 历史需要额外的元数据结构:至少需要 score 累加器(float32,4 bytes/block)和最近更新时间戳(int32,4 bytes/block)。在高并发场景下(256 并发 × 每请求平均 2000 block × 8 bytes = ~4 MB),元数据本身的存储开销不大。但更新开销更值得关注:每步 decode 遍历所有请求的 top-k block 做 score 累加——当并发请求数增加时,这个遍历的 CPU 开销也随之增长。

一笔粗略的账:如果 256 个并发请求 × 每步 top-k=512 × 4 bytes × 更新频率(每步),每步约需写入 512 KB 的 score 更新。在 x86 CPU 上一次 memcpy 即可完成。但如果元数据存储走 GPU global memory(与 attention kernel 在同一设备),每次更新需要一次 GPU kernel launch——这个 launch 开销是否被收益覆盖,取决于 offloading 决策的改进幅度。

5.2 收益边界:什么场景下联动才有意义?

不是所有场景都需要稀疏注意力驱动的 offloading。信号联动在不同场景下的边际收益差异显著:

场景 Offloading 触发频率 稀疏信号价值 结论
短上下文 + 高并发 极低(所有 KV 基本在 GPU 内) 引入信号跟踪的 overhead 不划算
长上下文 + 低并发 频繁(显存满 → 持续触发卸载) 极高 最有价值的场景——每次 offloading 决策都影响后续 decode 延迟
prompt-heavy(RAG) 中(前缀可复用,解码短) prefix caching 已覆盖大部分决策,稀疏信号增量有限
decode-heavy(coding agent) 高(长对话,KV 全量唯一) 极高 无复用前缀,每个 block 的唯一保留理由来自 attention 信号
批量评估(统一 prompt) 低(前缀共享率高) 共享信号压倒个性信号

核心判断:稀疏注意力驱动的 offloading 是为长上下文、低共享率的 decode-heavy 场景而生。在这些场景下,传统的 LRU/FIFO 完全无法区分一个 block 的语义价值——它们只能看到时间戳,看不到 attention。而在前缀共享率高的场景下,传统方法已经够好,稀疏信号的增量价值不足以覆盖跟踪开销。


六、问题层级:一张表总结

八个问题分布在四个层级上,每一层是下一层的前提:

层级 问题 本质 不解决会怎样
信号质量 时间错位、单向性、冷启动 稀疏注意力的评分能不能用 后续所有决策基于不可靠的信号——garbage in, garbage out
信号翻译 粒度错位、多请求归一化、前缀缓存冲突 怎么把评分变成 offloading 指令 有好的信号但不知道如何使用——信号被浪费
系统副作用 自我实现预言、投机解码交互、prefetching 协同、eviction 术语混淆下的信号门槛差异 联动本身引入的新动态 方案本身制造出新的 bug(静默丢弃关键 KV);或用永久删除的信号质量标准去要求层级迁移
经济性 元数据开销、场景收益边界 做这件事值不值 理论上正确但在生产环境中是负优化

没有一个问题是”不可解决的”,但每一个问题都要求设计者做出明确的取舍。它们的共性在于:不解决前三层的问题,就无法进入第四层的计算——你不能回答”做这件事值不值”,如果还不确定信号能不能用、怎么用、用了会有什么副作用。


七、一句话总结

稀疏注意力天然产出”哪些 KV 重要”的信号,KV Cache offloading 极度缺乏这条信号——把两者连接起来的直觉是正确的。但注意力层的逐 token 重要性评分,必须跨过时间、粒度、请求边界和因果闭环四道鸿沟,才能映射为存储层的逐 block 生命周期管理。这道映射本身的成本、时效性和可预测性,决定了方案的可行性边界——而长上下文 decode-heavy 场景是这个边界内最有价值的靶区。


相关阅读