22. 什么是 Attention Sink?StreamingLLM 是怎么用它处理长流式输入的?
整理 Attention Sink 现象的成因与 StreamingLLM 的处理思路。
简单回答
Attention Sink 是一个反直觉的实证现象:在 Decoder-only 模型里,序列开头的几个 token(通常是前 1-4 个)会获得不成比例的高注意力权重,无论它们的语义内容是什么。这是模型为了让 softmax 归一化稳定而"自学"出来的——softmax 必须把权重和分配到某个地方,模型选择把"多余"的注意力倾倒到开头几个 token 上,相当于一个"attention 垃圾桶"。StreamingLLM 利用这个发现实现了长流式输入:保留开头的 attention sink token + 滑动最近窗口,丢掉中间过期的 KV,使得模型能稳定处理无限长输入流。
详细解释
发现 Attention Sink 的过程
Attention Sink 的实证发现来自 Xiao 等人 2023 年的 StreamingLLM 论文。他们想做的事是简单的:在长流式对话场景下让模型只保留最近 N 个 token 的 KV Cache(即 sliding window 风格),丢掉过老的内容,这样 KV Cache 就不会无限膨胀。
但这个朴素方案立刻翻车了——只要丢掉了序列开头的几个 token 的 KV,模型生成质量瞬间崩溃。哪怕开头那几个 token 看起来语义上不重要(可能就是 BOS 或几个无关词),扔掉就出问题。
进一步分析注意力权重分布,研究者发现一个稳定的现象:在浅层之后的几乎每一层、每个 head 上,前 1-4 个 token 都获得了远高于其他位置的注意力权重,加起来经常占总权重的 40%+。这些 token 的语义内容没什么特别,但模型就是"看着它们"。这就是 Attention Sink。
为什么会出现 Attention Sink
这个现象的成因和 softmax 的归一化性质直接相关。
Softmax 强制注意力权重之和等于 1。但很多时候模型在生成某个 token 时,其实"不需要"看任何之前的 token——比如生成一个标点符号、一个虚词。但 softmax 不允许"什么都不看",权重必须分配出去。
模型在训练中学到的解决方案是:把这些"多余"的权重倾倒到一个稳定的位置。开头的 token 是最自然的选择——它们对所有位置都可见、位置稳定、模型容易找到。久而久之这个位置就被训练成了"attention 垃圾桶"。
后续研究(如 Off-by-One Attention)也证实了这个直觉。如果在 softmax 里加一个虚拟的"无对象"维度(让权重可以分配给"什么都不看"),attention sink 现象会显著减弱。但主流模型至今没改 softmax,所以 attention sink 仍然普遍存在。
StreamingLLM 的核心想法
理解了 attention sink,StreamingLLM 的方案就很自然:
不要扔掉开头的 token——保留前几个(论文里推荐 4 个)作为 attention sink。这部分 KV 永远在缓存里。
中间老的 token 扔掉。只保留最近的 N 个 token(滑动窗口)的 KV。
新生成的 token 加入窗口,超出窗口最旧的(除了 sink 部分)丢掉。
整个 KV Cache 由两部分组成:固定的 sink token 段 + 滑动的最近窗口段。中间任意长度的内容被"遗忘",但模型生成质量保持稳定。
通过这个方案,StreamingLLM 在几百万 token 长度的输入流上仍然能稳定生成,而朴素的滑动窗口方案在几千 token 后就会崩溃。
不能解决"长程理解"
这里要把 StreamingLLM 的能力边界说清楚——它解决的是"长输入流下生成不崩溃",不是"长上下文理解"。
中间被丢掉的 token 是真的丢了,模型完全无法访问。这意味着模型不能基于早期内容做精确推理(比如对话开始时用户提到的名字到几小时后还能记住)。它适用的场景是:对话或任务的上下文相关性主要集中在"开头的指令" + "最近的几轮对话"上,中间内容可以忘。这个假设在客服、流式日志分析、实时翻译等场景下成立,但在长文档理解、长程推理上不成立。
要做真正的长上下文理解,还是要用 RoPE 扩展、长上下文训练、或者外接 RAG 这些路线。StreamingLLM 是对"流式无限输入但短期相关"场景的特化方案,不是通用长上下文解。
Attention Sink 的其他工程含义
理解 attention sink 之后,几个原本看不懂的工程现象就解释通了。
KV Cache 量化时不能均匀对待所有位置。开头几个 token 的 KV 在注意力中的权重很大,量化误差会被放大。一些 KV Cache 量化方案(如 KIVI)会把开头几个 token 保持高精度,其他位置用低精度。
长上下文训练时丢首部 token 会损害模型质量。一些早期的"长上下文继续训练"方案直接把开头的 BOS 等 token 截掉,结果模型训练效果变差。原因就是丢失了 attention sink 位置。
System Prompt 处于 sink 位置带来的副作用。System Prompt 通常在序列开头,就处于 attention sink 的位置上。这意味着它在注意力上的权重天然就高——既是好事(指令被持续关注),也是坏事(System Prompt 中的偏置容易被放大)。
后续的发展
StreamingLLM 之后,社区出了一系列后续工作。H2O 把"重要 token 保留 + 不重要 token 丢弃"做得更精细,根据实际注意力权重动态选择保留哪些 KV。SnapKV 在 prefill 阶段就做 KV 选择性丢弃。这些方案都建立在 attention sink 这个基础观察上——既然有些 token 注意力权重特别高,那 KV Cache 压缩就应该围绕这个分布做。
面试时可以这样答
Attention Sink 是一个实证现象——在 Decoder-only 模型里,序列开头的前 1-4 个 token 会获得不成比例的高注意力权重,加起来经常占总权重的 40% 以上,无论这些 token 的语义内容是什么。
成因和 softmax 的归一化性质有关。softmax 强制权重和等于 1,但模型有时候并不需要看任何之前的 token,多余的权重必须倒在某个地方。模型自学到的方案是把权重稳定倒在开头的几个 token 上,相当于一个 attention 垃圾桶。
StreamingLLM 利用这个发现做长流式输入。它发现朴素的滑动窗口方案——只保留最近 N 个 token——在序列变长后会崩溃,原因正是丢掉了开头的 attention sink token。改进方案是保留开头几个 sink token + 滑动最近窗口,中间过期内容丢弃。这样 KV Cache 大小固定,模型能在几百万 token 的流式输入下稳定生成。
但要注意 StreamingLLM 解决的不是长上下文理解——中间被丢掉的内容是真的丢了,模型无法访问。它适合"开头指令 + 最近对话相关"的场景,比如客服流、实时翻译、流式日志分析。真正要长程理解还是要靠 RoPE 扩展、长上下文训练或 RAG。
顺带提一下,理解 attention sink 还能解释几个工程现象——KV Cache 量化不能均匀对待所有位置;System Prompt 处在 sink 位置上权重天然就大,是好事也可能是坏事。
常见追问
- 为什么 sink 现象只在序列开头几个位置出现,而不是某些中间位置?
- Attention Sink 在不同模型架构(GQA、MoE)上表现一致吗?
- 既然 attention sink 是 softmax 强制归一化导致的,为什么不直接改 softmax 让模型可以"什么都不看"?