注意力的本质是可微的软寻址:用一个提问去匹配所有候选的索引标签,按匹配度加权取回内容。它最初只是机器翻译里给 RNN 打的一个补丁,三年后成了整个架构的主体。本篇讲清 QKV 三者的关系、手算一遍完整流程,并给出变体谱系的导航。
软寻址:QKV 在做什么
想象一次字典查询。你手上有个待查的词,字典里每个条目有词条和释义。传统查表是硬匹配:词条完全相等才返回释义,否则什么也没有。注意力做的是软匹配——把待查词和所有词条算一遍相似度,然后把所有释义按相似度混合起来返回。
三个角色一一对应:
| 角色 | 字典类比 | 在模型里 |
|---|---|---|
| Query | 待查的词 | 当前 token 发出的”提问”,表达它需要什么信息 |
| Key | 每个条目的词条 | 每个候选 token 的”索引标签”,宣告自己能提供什么 |
| Value | 每个条目的释义 | 每个候选 token 真正携带的内容 |
关键在于 Key 与 Value 的分离:用什么被检索到,和被检索到之后交出什么,是两件事。一个 token 可以用语法角色作索引,交出的却是语义内容。硬要把两者合并(令 )模型仍能训练,但表达力显著受损。
一个具体例子
句子”猫追球,它累了”。模型读到”它”时必须解决指代:它指猫还是球?
- Query 来自”它”:编码出一个”我是代词,我需要找到一个能被形容为累的实体”的提问向量
- Key 来自前文每个 token:“猫”的 Key 大致是”有生命的名词、主语位置”,“球”的 Key 是”无生命的物体、宾语位置”
- Query 与”猫”的 Key 点积大,与”球”的 Key 点积小
- softmax 把两个分数归一成权重,比如 0.74 与 0.16
- Value 按权重混合:输出向量里绝大部分是”猫”的语义内容
这里没有任何离散的”选择”操作,全程只有点积、softmax、加权求和——所以梯度能一路回传,对齐关系是学出来的,不是规则写的。这一点是注意力相较于早期硬对齐(hard alignment)方案的根本优势,思想上与同期 Neural Turing Machine 的可微内存寻址同源。
完整公式
拆成四步:打分( 算相似度矩阵)→ 缩放(除 )→ 归一(softmax 沿 key 维)→ 混合(乘 加权求和)。,,,输出 。注意 与 可以不等——这正是 cross-attention 能连接不同长度序列的原因。
手算一遍
设 ,序列三个 token:猫、球、它。只算”它”这一个 Query。
三个 Key 与对应点积:
| token | Key | 除 | 权重 | ||
|---|---|---|---|---|---|
| 猫 | 4 | 2.0 | 7.389 | 0.736 | |
| 球 | 1 | 0.5 | 1.649 | 0.164 | |
| 它 | 0 | 0.0 | 1.000 | 0.100 |
指数和 ,逐项相除即得权重。取三个 one-hot 的 Value:、、,则输出恰好是权重本身:
“它”的新表示中 73.6% 的信息来自”猫”——指代消解在一次矩阵乘法里完成了。
import torch
import torch.nn.functional as F
q = torch.tensor([[1., 1., 0., 0.]]) # (1, d_k)
k = torch.tensor([[2., 2., 0., 0.], # 猫
[1., 0., 1., 0.], # 球
[0., 0., 0., 2.]]) # 它
v = torch.eye(3) # Value 取 one-hot 便于读出权重
scores = q @ k.T / k.size(-1) ** 0.5 # 缩放点积打分
weights = F.softmax(scores, dim=-1)
out = weights @ v
print(scores) # tensor([[2.0000, 0.5000, 0.0000]])
print(weights) # tensor([[0.7361, 0.1643, 0.0996]])
print(out) # 与 weights 相同为什么必要
若 各分量独立、均值 0 方差 1,则 的方差为 。维度越高分数分布越宽,softmax 被推向饱和区——输出接近 one-hot,梯度趋于零。除以 把方差拉回 1。
用上面的例子直接看差别:不缩放时原始分数 ,权重变成 ;缩放后是 。前者几乎退化成硬选择,一旦选错就没有梯度信号纠正。真实模型 常为 64 或 128, 达 8~11.3,不缩放根本训不动。
发明史
固定向量瓶颈。2014 年的 seq2seq 把整个源句压进 encoder 最后一个隐状态,decoder 全程只能读这一个向量。句子越长压缩损失越大,翻译质量随长度单调下降——20 词以上明显崩坏。问题很清楚:一个定长向量装不下变长信息。
Bahdanau attention(2014)。既然装不下,就不要压缩。让 decoder 每生成一个词时回头看 encoder 的全部隐状态,用一个小 MLP 打分:
softmax 归一后加权求和得到 context 向量。这是注意力的原型,也叫 additive attention。论文标题”Jointly Learning to Align and Translate”点明了动机:统计机器翻译时代的对齐(alignment)本是独立模块,现在把它变成可微的、与翻译联合训练的一部分。副产品是可视化的对齐热图——注意力自动学出了语序调整。
Luong attention(2015)。把打分函数从 MLP 简化为点积 ,即 multiplicative attention。少了参数、快了很多,效果不降。今天的 scaled dot-product 直接继承这一支。
self-attention(2016–2017)。此前注意力总是”decoder 查 encoder”,需要两个序列。intra-attention 的思路是让序列查自己:Q、K、V 全部来自同一份输入。这样注意力不再依附于任何外部结构,成为一个独立的序列变换算子。
Attention Is All You Need(2017)。既然 self-attention 已经能建模任意距离的依赖,RNN 的递归还有必要吗?Transformer 的回答是没有——完全删掉循环,只留注意力和 FFN 堆叠。收益不只是效果:递归被移除后,序列所有位置可以并行计算,训练吞吐提升一个量级。这才是注意力真正统治的原因。
代价是失去了 RNN 的归纳偏置,此后十年都在补:顺序信息靠位置编码,可见范围靠掩码, 复杂度靠稀疏化与算子优化。
变体总览
| 变体 | 一句话 | 代表场景 |
|---|---|---|
| self-attention | Q、K、V 同源,序列自己看自己 | Transformer 编码器与解码器主体 |
| cross-attention | Q 来自解码器,K、V 来自编码器 | 翻译、语音识别、扩散模型注入文本条件 |
| causal / 双向 | 掩码约束可见范围,见 注意力掩码 | causal 用于自回归生成,双向用于 BERT 式理解 |
| multi-head | 拆成多组子空间并行计算再拼接 | 标准配置,演进见 从 MHA 到 MLA |
| multi-query / GQA | 多个 Q 头共享一组 K、V 头 | 推理阶段压 KV Cache 显存 |
| 稀疏注意力 | 只算部分 token 对,破 | 长上下文,见 稀疏注意力 |
| 通道 / 空间注意力 | 对特征图的通道与空间维加权 | CNN 视觉骨干,见 CBAM(Convolutional Block Attention Module)注意力机制 |
两条正交的划分轴:Q 与 K 的来源(同源即 self,异源即 cross)和可见性约束(全可见、因果、稀疏)。任何注意力实现都能在这两个轴上定位。
两条必然的后续主线
顺序信息。 里没有任何位置项,对输入置换是等变的——“猫追球”与”球追猫”在注意力眼中完全相同。顺序必须由 位置编码 从外部注入,这条支线本身发展出了从正弦编码到 RoPE 的完整谱系。
计算效率。序列长度 下注意力矩阵是 ,显存与计算都随 平方增长。两个方向的优化:算子侧不再显式落盘中间矩阵,见 FlashAttention;推理侧缓存历史 K、V 避免重算,见 KV Cache。
体系全图见 注意力机制 MOC。
延伸阅读
- Neural Machine Translation by Jointly Learning to Align and Translate — Bahdanau attention 原始论文
- Effective Approaches to Attention-based NMT — Luong 的乘性打分与 global/local 对比
- Attention Is All You Need — Transformer 与 scaled dot-product
- The Illustrated Transformer — 图解版本,适合建立直觉