做 KV cache 淘汰策略时会撞上一个诡异现象:按注意力分数丢掉历史 token,只要被丢的集合里包含序列最开头那几个 token,模型输出立刻崩坏——困惑度爆炸,甚至吐不出完整句子。而这些 token 往往只是 BOS 和开头的几个碎片,语义贡献约等于零。Xiao et al. 2023(StreamingLLM 论文)系统展示了这个现象,并给了它名字:attention sink。他们的可视化里,各层各头的注意力图第一列常年是深色的。
从 softmax 的约束推必然性
注意力的归一化是硬约束:$\sum_j a_{ij} = 1$,且 $a_{ij} = e^{s_{ij}} / \sum_k e^{s_{ik}} > 0$——每个 key 都分到严格正的质量,全部分完,一分不许少。这个约束平时无害,但当一行 query 对所有内容 key 都不感兴趣时(当前 token 的语义和上文谁都不搭,或者模型此刻只需要"不检索任何东西"),权重无处安放:必须有个 key 来承接这些无着落的概率质量。
承接者是谁?训练中最早稳定下来、位置最固定的 token——序列最开头的 BOS 和几个初始 token。它们出现在每个样本的同一位置,承担的是注意力"空操作"(no-op)受体的职能:模型学到的做法是把无关紧要的默认质量倾倒到它们头上,输出端再用 value 的残差把这个"检索结果"中和掉。对 attention 输出 $o_i = \sum_j a_{ij} v_j$ 求个偏导就能看清 sink 的经济学:注意力质量每往 sink 挪一分,权重就要从其它位置扣一分,sink 的价值向量 $v_{\text{sink}}$ 在训练中被调成一个"无害基准",让所有层都有一个零成本的卸载口。ViT 侧的独立证据(Darcet et al. 2023 的 "Vision Transformers Need Registers")给出了对偶诊断: ViT 的高范数伪影 token 恰好也是大量注意力的汇聚点,给 patch 序列补几个可学习的"寄存器"token 承接这部分质量后,伪影消失、下游涨点——说明 sink 现象不是语言建模的特产,是 softmax 归一化的普适产物。
"Softmax-off-by-one"(2024 年 9 月一篇流传很广的短文)把同一个观察推到极致:既然问题是归一化强迫权重和为 1,那就引入一个永远在场的"无注意力"键,softmax 分母加 1 但分子不变,模型可以整体把概率质量交给这个"什么都不看"的选项。注意力的输出端加一个可学习常数项吸收这部分质量即可。它没有经过大规模训练验证,但和 sink 现象互为表里:softmax 需要一个卸载口,模型自己长出来了 sink;显式给卸载口,sink 或许就解职了。
StreamingLLM:把 bug 变成架构
StreamingLLM 的贡献是把观察变成可用的系统方案。流式对话里 KV cache 要恒定大小,朴素的滑窗淘汰(保留最近 $w$ 个 token)过不了几轮就崩——原因正是开头 token 被淘汰。方案简单到只有一句话:缓存 = 开头的 attention sink token + 最近的滑动窗口,中间的 token 全部丢弃。注意力分数按这个位置集合重算归一化,模型在多轮流式对话上困惑度稳定、几乎可以无限延长,而 cache 大小恒定在 sink 数 + $w$。
这在理论上严格成立的前提是 RoPE:sink 和窗口内的相对位置关系保持训练时见过的形态(sink 永远在位置 0–3,窗口 token 的相对距离 ≤ w)。代价同样明确:中间被丢弃的 token 永久不可检索——StreamingLLM 解决的是"对话不崩、cache 恒定",不是"长程记忆",大海捞针测试里它该丢还是丢。后者要靠检索或外推,不在此方案射程内。
对 KV 淘汰策略(H2O 等按分数淘汰的路线)这是重要提醒:分数高不代表可以丢——sink token 的"高分"是归一化的必需结构,不是信息含量。淘汰算法如果把 sink 当普通高分数 token 处理,在权重重归一化后就会拆掉模型的空操作通道。实践里的稳妥做法是位置规则先于分数规则:固定的 sink 槽位永远保留,分数淘汰只在剩余槽位里做。
小结
attention sink 是 softmax 归一化约束在自由生长中的解:权重必须分配,模型于是造了一个专职接收无意义质量的 token。理解这一点有三重实用价值:做 KV cache 淘汰时给 sink 留永久豁免位;做流式部署时可以直接采用 sink + 滑窗的 StreamingLLM 配方;做架构研究时它是个提醒——softmax 的"和为一"不是中性设计,它持续地向模型索取一个卸载口,无论是自发长出的 sink 还是显式设计的 off-by-one 项。位置编码那篇会再碰一次这个主题:sink 能和滑窗共存的前提,是 RoPE 下"位置 0 永远在最开头"这一几何事实。