第 12 章 稀疏注意力:局部、块稀疏与可学习检索¶
序列足够长时,全连接注意力的 Prefill 按 \(n^2\) 涨。这一章走一条路——不改注意力的数学,只少让一部分位置参与计算。softmax 还在,QKV 还在;变的是「谁和谁配对」。
稠密时算了也接近零的位置,干脆别算;前提是别把真正有用的那几处漏掉。
12.1 前提:训练好的注意力本来就很「尖」¶
把训练收敛后的注意力权重画成图,常见形状是:绝大多数位置接近零,真正亮起来的只有少数几类——
- 最近的一段(局部语法、指代、代码缩进);
- 序列开头附近(系统提示、角色设定、Attention Sink,见 12.2);
- 少量语义相关的远处位置(攻略里那句「周一闭馆」、代码里的函数定义)。
既然大量配对对输出几乎没贡献,稀疏化的动机就很直接。若每个 query 只访问约 \(k\) 个 key(或等价地,只访问总计约 \(k\) 个 token 的块),注意力计算从
只有 \(k\ll n\),并且「选出这些 key」的开销也明显小于稠密注意力时,理论稀疏才兑现成延迟下降。短序列上投影与 FFN 仍占大头时,稀疏往往好看不好用。
12.2 固定模式:窗口、全局 token 与 Attention Sink¶
最早能大规模用起来的,是人写死的连接模式。
滑动窗口(SWA)。 每个位置只看最近 \(w\) 个 token。训练时整段注意力从 \(O(n^2)\) 降为 \(O(nw)\);推理时若同步丢掉窗外 KV,单层缓存从 \(O(n)\) 变成 \(O(w)\)。代价清楚:窗口外的信息不能直接看见,只能靠层层残差间接传——无膨胀时,\(L\) 层理论感受野大约 \(L\times w\)。Mistral 一类把窗口开到 4096,短对话几乎等于全注意力;超长文档则必须接受「远处细节可能传不过来」。
全局 token(Longformer 思路)。 再加少数特殊位置:它们能看所有人,也被所有人看,当信息枢纽。适合文档里天生有标题、[CLS] 这类锚点的任务;纯解码对话里要额外设计「哪些位置配当全局」,不如窗口常见。
Attention Sink / StreamingLLM。 流式场景想只保留最近窗口时,有人发现:把最开头几个 token 的 KV 也丢掉,质量会明显崩。 经验做法是永久保留少量开头 KV,再配滑动窗口。一种有用直觉是:softmax 要归一化,模型常把一部分「无处安放」的质量压在早期位置上;但完整因果还包括训练分布、位置编码与数据写法。工程上更稳的结论是——做滑窗流式时,留一点 sink,通常比「只留最近 w 个」稳。
固定模式的共同上限:模式写死了,相关信息若不在窗口或全局集合里,就彻底看不到。 行程助手若只开窗口,用户第一轮贴的闭馆规则滚出窗口后,后面重排行程时可能直接丢。
12.3 为什么实用方案几乎都按「块」稀疏¶
理想稀疏是「每个 query 各自挑最相关的几个 key」。数学上漂亮,GPU 上常跑不满:访问完全随机,矩阵乘单元吃不到连续块,理论 FLOPs 降了,墙钟不一定降。
所以能进主线的方案,多半按块组织:
- 把序列切成 64 / 128 一类长度的块;
- 先在块级做粗选择(或使用固定的块模式);
- 选中的块内部再跑完整(或近似完整)的注意力。
块内访存连续,能映射到高效 kernel。这里再次碰到第 4 章的带宽故事:算法的存活,常常取决于能不能变成硬件喜欢的计算形状,而不是纸上复杂度最优。牺牲一点「精确到 token 的选择自由度」,换来实测加速,是稀疏路线的主旋律。
12.4 可学习的检索式稀疏¶
固定窗口解决不了「答案在远处某一段」时,下一代做法是让模型自己决定看哪些块。一条常见的三分支结构是:
- 压缩分支:每个块压成代表向量,先做块级粗相关——相当于翻目录。
- 选择分支:按粗分选 top-\(n\) 个块,块内再做精细注意力——精读选中的章节。
- 滑窗分支:无论如何保留最近一段,稳住局部流畅与短程依赖。
三路输出再经门控合并。Native Sparse Attention(NSA)是这一设计的代表说法;Qwen 等报告里的 QSA / 同类模块是同一思想的产品实现。关键不在名字,而在:选择机制要和模型一起训练,而不是推理时突然套一个与训练分布不符的硬掩码。
和固定窗口比,检索式稀疏多出「可能看见远处」的能力;和全注意力比,它把精细计算限制在被选中的块上。大海捞针在这里变成硬考题:答案所在块若没被选中,后面块内注意力再准也没用。
12.5 和 FlashAttention、GQA 各管什么¶
三件事经常被揉成「长序列优化」一锅粥,最好分开记:
| 改什么 | 主要收益 | 不改什么 | |
|---|---|---|---|
| FlashAttention | 实现路径(分块、在线 softmax) | 显存流量、峰值、常数因子 | 仍是全连接,渐近 \(O(n^2)\) |
| GQA / MLA(第 7 章) | 每 token KV 的份数或维度 | 缓存体积 | Prefill 仍按连接模式算配对 |
| 稀疏注意力(本章) | 哪些位置参与配对 | 长序列计算(及部分模式下的缓存) | 不自动等于更瘦的每 token KV |
可以叠用:GQA 模型上再跑滑窗或检索式稀疏,很常见。读论文时看清作者宣称的加速来自哪一列。
12.6 稀疏的代价与适用边界¶
训练和推理最好一致。 稠密训到底、推理突然改成强稀疏,通常掉点。滑窗 + sink 在流式场景有时可作后处理近似;可学习检索式稀疏几乎都要训练期就在场。
漏了就是漏了。 块选择是硬门:没选中含答案的块,不存在「再仔细一点就能看见」。因此稀疏模型更需要多针、RULER、真实长文档,而不是只报稠密任务的平均分。
省不省 KV,取决于模式。
- 滑动窗口 + rolling cache:可以安全丢掉窗外历史 → 省缓存。
- 普通 top-k 块检索:未来 query 仍可能点到很早的块 → 往往 仍存全量 KV,省的是计算不是缓存。
- 再叠跨层 KV 复用等,会让账本更细,需要逐项核对。
口诀「稀疏只省计算、不省缓存」适用于常见检索式稀疏,不是所有稀疏变体的定律。
质量只缓解一部分。 稀疏减少干扰项,有时反而让注意力更「尖」;若模式或选择器系统性地丢掉中间段落,召回会更差。稀疏解不了长上下文的全部质量问题。
12.7 行程助手里怎么想象这条路¶
用户持续粘贴攻略、点评、旧行程时:
- 纯滑窗:最近对话流畅,早期约束容易丢——适合「闲聊补充」,不适合「全程遵守第一页的硬规则」。
- 滑窗 + sink:系统提示和最初的硬约束更稳一点,仍然不是全文检索。
- 检索式稀疏:有机会在重排时重新点到「周一闭馆」所在块;若选择器没点中,表现会断崖式掉——产品上要靠评测盯住这种失败,而不是只看平均 BLEU 或主观流畅度。
动手¶
实现一个极简块稀疏注意力:分块 → 块级打分 → 选 top-\(n\) → 块内完整注意力。在长序列上对比稠密实现的耗时与输出差异;再刻意构造「答案落在未选中块」的例子,看清楚失败模式。若有余力,给同一模型加一个仅推理期的滑动窗口,观察短任务与长检索任务各掉多少。
核心问答¶
Q1:稀疏注意力和第 7 章的 GQA 是一回事吗? 不是。GQA 减的是 KV 的份数(缓存);稀疏减的是参与计算的 token / 块数(计算)。正交,可同时用。
Q2:Attention Sink 为什么重要? 经验上,训练后的模型常把开头位置当成稳定的注意力落点;流式推理若突然拿掉,等于改变模型见惯的分布。softmax 泄压是有用直觉,不是唯一解释。做滑窗流式时,保留少量开头 KV,通常比只保留最近窗口稳。
Q3:为什么稀疏要按块,不能每个 token 自由选? 自由选访存太随机,矩阵乘单元吃不饱,纸面 FLOPs 换不成墙钟。按块牺牲一点选择粒度,换连续访存和真实加速。
Q4:训好的稠密模型能不能直接改成稀疏推理? 可以尝试,但会掉点,幅度看任务。滑窗加 sink 是少数常被用作免训练近似的方案;检索式稀疏基本需要训练配合。
Q5:稀疏注意力省显存吗? 看哪一种。滑窗配 rolling cache 会省;普通检索式稀疏通常不省全量历史 KV;跨层复用等还要另算。读论文时把「少读 KV」和「少存 KV」分开核对。
Q6:稀疏能解决「中间找不到」吗? 只部分相关。它可能减少干扰,也可能因漏块制造新的盲区。质量仍要靠数据、位置编码,以及第 14 章里是否保留足够的精确回看层。