一个被忽略的误差集中点
设想一个在线客服模型,系统提示词约 2000 token,用户每轮追加几百字,会话持续几十轮。为了把显存留给更大的 batch,服务端把 KV Cache 压到 2 bit。上线后长会话的回复开始出现一种特征:前几句还正常,越到后面越容易跑题,甚至重复上一轮的句式。把量化关掉,问题消失;把量化精度提到 4 bit,问题减轻但不消失。
直觉上,量化误差应该均匀分布在所有 token 上,长上下文里每个 token 分摊的误差很小。但实测往往不是这样:误差高度集中在序列最前面的少数 token 上,尤其是第 0 个 token。要理解这个现象,需要先理解 Attention Sink 这个机制,以及它如何让首 Token 的 Key 向量变成一个数值上的极端离群点。
Attention Sink:注意力为什么流向语义无关的首 Token
自注意力在每一步解码时,会为当前 Query 对所有历史 Key 计算相似度,再经过 softmax 得到一组权重,最后用这些权重对 Value 加权求和。softmax 有一个硬性约束:所有权重之和必须等于 1。这意味着模型不能“什么都不看”,它必须把注意力预算分配出去。
StreamingLLM 这篇工作(Xiao 等人,ICLR 2024)观察到一个稳定现象:即使首 Token 在语义上并不重要,模型仍会给它分配异常高的注意力权重,作者把这种现象称为 attention sink。论文给出的解释是,首 Token 充当了一个“汇”——当当前 Query 与近期上下文都不太匹配时,把权重倾倒给一个固定位置,比强行分配给语义无关的 token 更划算。论文进一步发现,在窗口注意力中只要保留最初若干 token 的 KV,性能就能大幅恢复,这说明首 Token 承担的是结构性角色,而非内容角色。
这里有一个容易混淆的点:Attention Sink 说的是注意力权重集中,而 Massive Activations(Sun 等人,COLM 2024)说的是激活值本身出现极端大的数值。两者是相关但不同的现象。Massive Activations 论文报告,LLM 中极少数激活值会比其余激活值大出几个数量级,这些值在不同输入下基本保持恒定,起到类似偏置项的作用,并导致注意力概率向对应 token 集中。KVSink 这篇工作(Su 与 Yuan,2025)把两条线索接了起来:它观察到极端激活离群出现在 attention sink token 上,并且这些离群值会跨层演化。
对量化而言,关键结论是:首 Token 的 Key 向量不是“普通 token 之一”,它在某些通道上带有远大于其他 token 的数值。
逐 Token 均匀量化在首 Token 上为何放大误差
低比特量化的基本做法是:为一段数值选一个缩放因子,把浮点数映射到有限的整数格点上。以对称均匀量化为例,若某个向量分量的绝对值上界为 (a),量化位宽为 (b),则缩放因子 (s = a / (2^{b-1}-1)),量化结果 (q = \mathrm{round}(x/s)),反量化得到 (\hat{x} = q \cdot s)。
这里 (a) 的工程含义是“这段数值里最大的那个绝对值”,(s) 是相邻两个量化格点之间的间距。位宽固定时,(a) 越大,(s) 越大,格点越稀疏,所有分量的舍入误差上限都随之变大。
“逐 Token 量化”指的是:对每个 token 的 Key 向量单独统计 (a),独立选 (s)。这种做法对普通 token 是合理的,因为每个 token 的数值范围彼此独立。问题出在首 Token:如果它的某个通道存在极端离群值,这个离群值会把该 token 的 (a) 抬得很高,于是同一 token 内所有正常通道都被迫使用一个很粗的格点。误差不是被离群通道自己承担,而是被它“传染”给了整个向量。
KIVI 这篇工作(Liu 等人,ICML 2024)对 KV Cache 的元素分布做了系统研究,结论是 Key cache 应当按通道分组量化,而 Value cache 应当按 token 量化。这个不对称结论正好呼应上面的机制:Key 的难点在通道维度的离群,Value 的难点在 token 维度的分布差异。KIVI 报告,在 Llama、Falcon、Mistral 等模型上,2 bit 量化可以维持接近原精度的质量,同时把峰值显存降到约 2.6 分之一,并支持更大的 batch。
需要注意证据边界:KIVI 的结论是“Key 适合按通道量化”,它并没有声称首 Token 是唯一需要特殊处理的 token。把首 Token 单独拎出来讨论,是后续围绕 attention sink 的一系列工作的重点。
三种处理离群值的工程路线
面对首 Token 上的极端离群,工程上有三条常见路线,它们的取舍并不相同。
离群通道保留:把数值最大的若干通道(或若干 token)继续用 16 bit 存放,其余通道走低比特。KVSink 讨论的 Preserve-First-N(PFN)策略就是保留最前面 N 个 token 的原始精度。KVSink 指出这种静态策略有两个局限:一是缺乏对 attention sink 的系统分析,二是无法覆盖出现在初始位置之外的 sink token。KVSink 提出的替代做法是在推理过程中预测 sink token,以较小开销实现更完整的保留。
分组量化:不按整个向量选一个缩放因子,而是把通道切成若干组,每组独立选 (s)。这样离群通道只抬高自己所在组的格点间距,不再污染其他组。组越小,精度越好,但每组都要存一份缩放因子,元数据开销上升,且硬件上需要更细粒度的反量化支持。
混合精度:按 token 或按层分配不同位宽。例如前若干 token 用 8 bit,其余用 2 bit。它实现简单,但位宽切换点是一个静态超参,遇到 sink token 出现在中间位置时会失效。
| 方案 | 对首 Token 离群的处理 | 显存开销 | 实现复杂度 | 主要失效场景 |
|---|---|---|---|---|
| 逐 Token 均匀量化 | 无,离群污染整个向量 | 最低 | 最低 | 首 Token 误差放大,长上下文退化 |
| 离群通道保留 | 直接保留高精度通道或 token | 中等,取决于保留比例 | 中等,需识别离群位置 | 静态保留漏掉非首位 sink |
| 分组量化 | 把离群限制在组内 | 低,但需存组缩放因子 | 较高,依赖细粒度反量化 | 组划分不当仍会跨组污染 |
| 混合精度 | 按位置或层分配位宽 | 中等 | 低 | 位宽切换点静态,分布偏移后失效 |
这张表的读法是:显存开销和实现复杂度大致同向,真正拉开差距的是“能否跟上 sink token 的实际位置”。静态方案便宜,但把假设写死在了部署配置里。
一次请求里数值是怎么流动的
把上面的机制放回一次具体请求。假设服务端收到一个长会话请求,系统提示词 2000 token,历史对话 6000 token,当前问题 200 token。
flowchart TD
A[输入 token 序列] --> B[逐层计算 Q K V]
B --> C[首 Token 的 Key 出现离群通道]
C --> D[按 token 统计缩放因子]
D --> E[离群值抬高整向量格点间距]
E --> F[正常通道舍入误差放大]
F --> G[反量化后的 Key 偏离原值]
G --> H[注意力权重分布偏移]
H --> I[生成结果漂移或重复]
C --> J[识别 sink 位置]
J --> K[离群通道或 sink token 走高位宽]
K --> G
流程里有两个分支值得展开。
第一个分支是误差如何进入注意力。Key 的误差不会直接改变输出,它先改变 Query 与 Key 的点积,再经过 softmax 改变权重分布。softmax 对输入的差异是敏感的:如果首 Token 的 Key 被量化后偏离原值,它与当前 Query 的相似度就会变化,原本应该流向首 Token 的那部分权重可能被重新分配到其他 token 上。由于首 Token 承担的是结构性角色,这种重分配会改变整个注意力输出的基线。
第二个分支是保留策略如何介入。识别出 sink 位置后,把这些位置的 KV 以更高精度存放,反量化时它们回到接近原值的状态,注意力分布也就更接近未量化版本。KVSink 的核心主张正是:保留要覆盖真实的 sink token,而不只是最前面几个。
生产环境该看哪些信号
量化带来的退化往往是渐进的,不会立刻报错。可观测性上,以下几类信号有实际定位价值。
逐位置的量化误差:对每个 token 位置统计反量化 Key 与原始 Key 的相对误差,按位置画出来。如果误差曲线在开头出现尖峰,说明离群通道正在污染整向量;如果尖峰出现在中间某处,说明存在非首位的 sink token,静态保留策略会漏掉它。
注意力权重分布:记录每一层每个 head 在首 Token 上的平均注意力权重。量化前后这个值如果明显下降,说明 sink 结构被破坏,后续生成更容易漂移。
长上下文任务指标:短 prompt 上的困惑度可能看不出问题,因为误差集中在首 Token,而短序列里首 Token 占比高、影响反而被稀释。更有区分度的是长上下文检索类任务和长会话一致性检查。
显存与吞吐:量化收益要同时看峰值显存和实际吞吐。KIVI 报告其方法在真实推理负载上带来约 2.35~3.47 倍吞吐提升,但这是特定配置下的结果,不能直接外推到其他模型和 batch 规模。
定位顺序上,先确认误差是否集中在特定位置,再确认这些位置是否与注意力 sink 重合,最后才决定用哪种保留策略。跳过前两步直接调位宽,往往只是把问题推到另一个阈值上。
什么时候这些方案会失效
第一类失效来自 sink 位置的漂移。如果模型或输入分布使得 sink 出现在初始 token 之外,任何写死“保留前 N 个”的配置都会漏掉真正的离群点。KVSink 明确把这一点列为现有实践的局限。
第二类失效来自离群通道的变化。Massive Activations 论文报告这些极端值在不同输入下基本恒定,但这是针对它所研究的模型和层的观察,不能推断所有模型、所有层都满足。如果离群通道随输入变化,静态的通道保留列表就会失配。
第三类失效来自分组粒度的权衡。组太小,缩放因子元数据占比上升,低比特带来的显存收益被吃掉;组太大,离群又会在组内扩散。这个平衡点依赖具体模型的通道分布,没有通用最优值。
第四类失效来自评估本身的偏差。只用短序列困惑度评估,会低估长上下文中的退化;只用单一任务评估,可能恰好避开了受 sink 影响最大的场景。
与替代路线的比较
KV Cache 量化不是唯一选择。窗口注意力只保留最近若干 token 的 KV,显存收益直接且实现简单,但 StreamingLLM 的结论是:当文本长度超过缓存大小时,窗口注意力会失效,而保留初始 token 的 KV 能大幅恢复性能。这说明窗口方案丢掉的恰好是 sink 所在的位置。
KV 剪枝按重要性丢弃 token,收益与量化可以叠加,但它同样需要判断哪些 token 重要,而 sink token 的重要性并不来自语义内容。如果剪枝的重要性打分只看语义相关性,就可能把 sink token 判为低价值。
低秩分解压缩的是 KV 的整体结构,对单个通道的极端离群不敏感,因此它和离群保留策略解决的是不同层面的问题。
综合来看,量化负责降低每个元素的位宽,离群保留负责不让少数元素拖累其余元素,两者是互补关系。真正决定长上下文生成稳定性的,是能否在推理过程中持续、准确地识别出承担 sink 角色的位置,而不是在部署配置里一次性写死。这一点目前仍没有完全统一的答案,也是后续工作要继续验证的方向。