Transformer 原理(二):Self-Attention 深度拆解 —— Q K V 的前世今生
Transformer 系列第二篇。从"it 指代什么"的动机出发,逐步拆解 Self-Attention 的完整计算过程,深入理解 Q、K、V 的设计哲学与缩放因子的数学证明。
这是 Transformer 原理系列的第二篇。上一篇我们建立了 Transformer 的全局直觉——Encoder-Decoder 架构、数据流、Embedding 和 BPE。
本篇深入 Transformer 最核心的创新:Self-Attention(自注意力机制)。
为什么需要 Self-Attention?
在上一篇中,我们用”圆桌会议”来比喻 Self-Attention——让每个词同时看到所有其他词。但为什么需要”看到其他词”?看一个经典例子:
"The animal didn't cross the street because it was too tired."
这句话中的 "it" 指代什么?
作为人类,你一眼就能判断 “it” 指的是 “animal”——因为”tired(疲倦)“是动物的属性,而不是街道的属性。
但对模型来说,“it” 只是一个孤立的 Token。要理解 “it” 的含义,模型必须回头看句子中的其他词,找到 “it” 和 “animal” 之间的关联。
这就是 Self-Attention 要做的事:让每个词都能”看到”句子中的所有其他词,并根据相关程度来更新自己的理解。
经过 Self-Attention 处理后,“it” 的向量会融入 “animal” 的语义信息,模型就能”理解”这个指代关系了。
Q、K、V 的直觉理解
Self-Attention 的核心公式是:
先不急看公式,我们从直觉开始。
搜索引擎类比
想象你在用搜索引擎:
- Query(查询):你输入的搜索词,比如”北京好玩的地方”
- Key(键/索引):数据库中每篇文章的标题,比如”北京故宫旅游攻略”、“上海东方明珠介绍”
- Value(值/内容):文章的实际内容
搜索过程是:
- 拿你的 Query 和每篇文章的 Key 做匹配,算出相关度分数
- 按分数高低排序——“北京故宫旅游攻略”分数最高
- 返回分数高的文章的 Value(实际内容)
Self-Attention 做的事完全一样,只不过”搜索者”和”被搜索的数据库”都是同一个句子里的词:
句子:"猫 坐在 垫子 上"
当处理"坐"这个词时:
Query = "坐"发出的提问:"谁在执行这个动作?动作发生在哪里?"
Key = 每个词的"身份标签":"猫"→"我是名词/主语","垫子"→"我是名词/地点"
Value = 每个词的"实际内容":"猫"→动物属性,"垫子"→物品属性
匹配结果:"坐"的 Query 和"猫"的 Key 高度匹配(动词找主语)
→ 高权重提取"猫"的 Value → "坐"的表示融入了"猫"的信息
关键洞察:Q、K、V 来自同一个输入
在 Self-Attention 中,Q、K、V 都是从同一个输入向量 X 变换而来的:
其中 、、 是三个不同的权重矩阵(上一篇我们提到过,它们就是每层 Encoder/Decoder 中可训练的参数)。
为什么要做这个变换? 如果直接用原始输入 X 同时当 Q、K、V(即不做投影),注意力公式就退化成 ——模型只能计算原始特征之间的相似度,无法学到更复杂的语义关系。三个不同的权重矩阵让模型可以从不同角度提取信息。详细分析见后面的深度小节。
逐步计算:一个完整的例子
我们用一个 2 个词、每个词 4 维的简化例子,走一遍 Self-Attention 的完整计算过程。
输入
假设句子 “I love” 经过 Embedding 后得到两个 4 维向量:
第一行是 “I” 的向量 ,第二行是 “love” 的向量 。
第一步:生成 Q、K、V
用三个权重矩阵把 X 变换成 Q、K、V。为了简化,这里用 (实际模型中通常是 64 或 128):
同样的方式计算 K 和 V(用不同的 、 矩阵,这里省略具体数字)。假设最终得到:
第二步:计算注意力分数(Q · K^T)
用 Q 和 K 的点积来衡量”每个词对其他词的关注程度”:
点积(Dot Product):两个向量对应位置的数字相乘后求和。比如 。
为什么点积能衡量”相关度”? 这不是随意选择,而是有几何意义的。
点积度量的是两个向量的方向一致性。用一个简单的例子:
向量 A = [1, 0] (指向右边 →)
向量 B = [1, 0] (也指向右边 →) → 点积 = 1×1 + 0×0 = 1 ← 最大(方向相同)
向量 A = [1, 0] (指向右边 →)
向量 C = [0, 1] (指向上面 ↑) → 点积 = 1×0 + 0×1 = 0 ← 零(方向垂直,无关)
向量 A = [1, 0] (指向右边 →)
向量 D = [-1, 0] (指向左边 ←) → 点积 = 1×(-1) + 0×0 = -1 ← 最小(方向相反)
方向相同 → 点积大;方向垂直 → 点积为零;方向相反 → 点积为负。
在 Self-Attention 中,Q 和 K 经过各自的权重矩阵变换后,语义相关的词会被投影到相近的方向。比如”坐”的 Query 在问”谁是我的主语?”,“猫”的 Key 在说”我是名词/主语”——这两个向量在”寻找主语”这个维度上方向一致,点积就大。而”坐”的 Query 和”的”的 Key 在任何维度上都没有对齐,点积就小。
所以点积天然就是”这两个词有多相关”的度量——不是人为定义的,而是向量几何的内在性质。
结果矩阵的含义:
Key→ "I" "love"
Query↓
"I" 2 1 ← "I"对"I"的关注=2,对"love"的关注=1
"love" 2 3 ← "love"对"I"的关注=2,对"love"的关注=3
第三步:缩放(除以 )
为什么要除以 ? 因为当维度 很大时,点积的数值也会很大,导致 Softmax 的输出变成接近 one-hot 的极端分布(几乎所有权重集中在一个词上),梯度消失,模型训练困难。除以 是为了把数值控制在合理范围。详细的数学证明见后面的深度小节。
第四步:Softmax 归一化
对每一行做 Softmax,把原始分数变成概率分布(每行加起来 = 1)。
Softmax 的公式是什么? 对于一组分数 ,Softmax 把每个分数变成:
其中 (自然常数)。核心思想:先对每个分数取指数(,让所有值变成正数),然后除以总和(归一化到 0~1)。
用第一行 走一遍:
e^1.15 ≈ 3.16
e^0.58 ≈ 1.79
总和 = 3.16 + 1.79 = 4.95
softmax(1.15) = 3.16 / 4.95 ≈ 0.64
softmax(0.58) = 1.79 / 4.95 ≈ 0.36
所以整个矩阵经过 Softmax 后:
为什么用 而不是直接除以总和? 指数函数会放大差异——分数高的词会获得更多权重,分数低的词权重被压缩得更小。这让模型能更”果断”地把注意力集中在关键词上,而不是平均分配。
含义:
- “I” 把 64% 的注意力放在自己,36% 放在 “love”
- “love” 把 36% 的注意力放在 “I”,64% 放在自己
第五步:加权求和(乘以 V)
用注意力权重对 V 做加权求和,得到最终输出:
最终结果:每个词的新向量都融合了其他词的信息。
- “I” 的新向量 = 64% 的自己 + 36% 的 “love”
- “love” 的新向量 = 36% 的 “I” + 64% 的自己
这就是 Self-Attention 的完整计算——五步走完。
总结:Self-Attention 的五步计算流程
输入 X
│
├──→ × W_Q ──→ Q (查询)
├──→ × W_K ──→ K (键)
└──→ × W_V ──→ V (值)
│
▼
Q × K^T ← 第一步:计算注意力分数(谁和谁相关)
│
▼
÷ √(d_k) ← 第二步:缩放(防止数值过大)
│
▼
Softmax ← 第三步:归一化为概率分布
│
▼
× V ← 第四步:按权重提取信息
│
▼
输出 ← 每个词的新向量,融合了上下文信息
📌 深入理解:为什么要分三个矩阵 Q、K、V?
这是 Self-Attention 中最常被追问的问题。从三个维度来回答。
维度一:数据库检索类比(功能分工)
Q、K、V 的分工就像搜索引擎:
- Q 和 K 共同决定 谁和谁相关(建立路由/寻址)
- V 决定 相关之后传递什么信息(特征提取)
这种分工让”判断相关性”和”提取信息”可以独立优化,互不干扰。
维度二:语义不对称性(中文例子)
以 “猫 坐在 垫子上” 为例,当模型处理 “坐” 时:
同一个词 "坐" 通过三个矩阵变换后:
Q_"坐" :发出提问 → "谁在做这个动作?发生在哪里?"
K_"坐" :暴露身份 → "我是一个动词,表示一种姿态"
V_"坐" :携带内容 → "坐下、静态、臀部着地的具体语义"
Q 和 K 承担的角色完全不同:Q 是主动提问,K 是被动应答。
如果强制 Q = K(即 ),“坐” 就只能去找和自己特征最相似的词(比如同义词”趴”、“躺”),而无法建立 动词→名词 的语法关联(“坐”→“猫”)。
维度三:子空间投影(信息论视角)
三个不同的权重矩阵把原始输入投影到三个不同的子空间:
- 和 的子空间专注于学习 寻址机制 ——决定谁和谁有关联
- 的子空间专注于学习 特征提取 ——决定关联之后传递什么信息
如果不做投影会怎样? 令 ,注意力退化为 。这时模型只能计算原始特征的物理相似度(比如两个向量的数值接近程度),完全失去了对复杂语义关系的抽象映射能力。
📌 深入理解:缩放因子为什么是 ?
这不是经验调参调出来的超参数,而是通过严格的数学推导得出的。
问题设定
设 和 是两个独立的 维向量,假设每一维的分量都满足独立同分布(i.i.d),且均值为 0,方差为 1:
它们的点积为:
推导点积的方差
期望:
由于 和 独立,且均值都是 0:
方差:
由于各维度独立:
结论
点积 的均值为 0,方差为 ,标准差为 。
三种缩放策略的对比
| 策略 | 点积方差 | Softmax 行为 | 后果 |
|---|---|---|---|
| 不缩放 | (很大) | 输出接近 one-hot,某个词权重接近 1,其余接近 0 | 梯度消失,模型失去学习能力 |
| 除以 | (很小) | 输出接近均匀分布,所有词权重几乎相等 | 模型失去”选择性关注”的能力 |
| 除以 ✅ | 1(刚好) | 输出在梯度最敏感的区域 | 模型既能区分重点,又不会梯度消失 |
一句话总结:除以 就是把点积的方差标准化回 1,让 Softmax 工作在梯度最健康的区域。
📌 深入理解:Attention 权重矩阵是对称的吗?
不是。
直觉理解很简单:在自然语言中,“猫”关注”坐”的强度 ≠ “坐”关注”猫”的强度。
- “坐”在找主语 → 强烈关注”猫”(权重高)
- “猫”在找什么? → 可能稍微关注”坐”,但更关注其他修饰词(权重低)
关注关系是单向的,就像有向图——A→B 的边权 ≠ B→A 的边权。
从公式上看,注意力矩阵 中第 个词对第 个词的权重为:
逐符号解读:
- :第 个词对第 个词的注意力权重
- :“正比于”,意思是经过 Softmax 归一化后,权重的相对大小由后面的值决定
- :指数函数,就是 Softmax 中的那个”取指数”操作
- :第 个词的 Query 向量与第 个词的 Key 向量的点积
反过来,第 个词对第 个词的权重为:
关键在于: 和 不相等。因为 ,,而 ——同一个词作为 Query(主动提问)和作为 Key(被动应答)时,向量完全不同。所以 ,注意力矩阵天然就是不对称的。
📌 深入理解:Self-Attention vs Cross-Attention
上一篇提到 Decoder 中有 Cross-Attention,它和 Self-Attention 到底有什么不同?用翻译的例子来说。
翻译场景:英→中
假设我们在翻译 “I love cats” → “我 爱 猫”。
Self-Attention(发生在 Encoder 内部):
"I love cats" 内部互相关注
"love" 的 Q 去查 "I"、"love"、"cats" 的 K → 发现"I"和"cats"最相关
Q 来自 "love",K 也来自 "I love cats" → 同一个序列
Cross-Attention(发生在 Decoder 中):
Decoder 正在生成 "猫" 这个词时:
"猫" 的 Q 去查 "I"、"love"、"cats" 的 K → 发现 "cats" 最相关
Q 来自 Decoder 的 "猫"
K 和 V 来自 Encoder 对 "I love cats" 的理解结果 → 不同的序列!
区别一目了然:
- Self-Attention:Q、K、V 都来自 同一个序列(自己看自己)
- Cross-Attention:Q 来自 Decoder(“我正在生成什么”),K 和 V 来自 Encoder(“原文说了什么”)
对比表
| 维度 | Self-Attention | Cross-Attention |
|---|---|---|
| Q 来源 | 自身序列 | Decoder(目标语言) |
| K/V 来源 | 自身序列 | Encoder 输出(源语言) |
| 作用 | 序列 内部 建模依赖关系 | 跨序列 对齐源语言和目标语言 |
| 类比 | 自己读自己写的笔记 | 翻译时回头看原文 |
为什么需要 Cross-Attention?
因为 Decoder 在生成 “猫” 时,需要知道原文中哪个词对应——它必须 回头看 Encoder 的输出。Cross-Attention 就是 Decoder “回头看原文” 的机制:用自己当前的状态(Q)去原文的理解结果(K/V)中搜索最相关的信息。
在 Decoder-only 架构(GPT、LLaMA 等现代大模型)中,没有 Cross-Attention,只有带因果掩码(Causal Mask)的 Self-Attention。关于 Decoder-only 为什么成为主流,会在第四篇展开。
📌 深入理解:Attention 的复杂度与瓶颈
Self-Attention 的计算复杂度是多少?为什么长上下文是挑战?
复杂度分析
回顾前面的五步计算,逐步算出每一步的开销:
设:n = 序列长度(有多少个词),d = 向量维度
第一步:生成 Q、K、V
X(n×d) × W_Q(d×d) = Q(n×d) → 乘法次数:n × d × d = O(n·d²)
K 和 V 同理,共 3 次 → O(n·d²)
第二步:计算注意力分数 Q × K^T
Q(n×d) × K^T(d×n) = 注意力矩阵(n×n) → 乘法次数:n × d × n = O(n²·d) ← 瓶颈!
第三步:缩放 + 第四步:Softmax
对 n×n 矩阵逐元素操作 → O(n²)
第五步:加权求和 注意力矩阵 × V
(n×n) × V(n×d) = 输出(n×d) → 乘法次数:n × n × d = O(n²·d)
总时间复杂度:(第二步和第五步主导)
空间复杂度:需要存储 的注意力矩阵 + 的 Q、K、V →
核心瓶颈是 ——注意力矩阵的大小和序列长度的平方成正比:
| 序列长度 n | 注意力矩阵元素数 | 以 FP16 计算的显存 |
|---|---|---|
| 512 | 262K | 0.5 MB |
| 4,096 | 16.7M | 32 MB |
| 32,768 | 1.07B | 2 GB |
| 128,000 | 16.4B | 31 GB |
当上下文长度从 512 增长到 128K,注意力矩阵的显存从 0.5 MB 暴增到 31 GB——这就是长上下文的核心挑战。
优化方案预览
| 方案 | 核心思想 | 复杂度 | 详见 |
|---|---|---|---|
| Flash Attention | 不改数学,优化显存 IO(分块计算) | 不变 | 第五篇 |
| MQA / GQA | 减少 K/V 头数,降低 KV Cache | 不变 | 第五篇 |
| Sparse Attention | 稀疏化注意力模式 | — | |
| Linear Attention | 用核函数近似 | — |
小结
本篇完整拆解了 Self-Attention 的工作原理:
-
动机:“it” 指代什么?——模型需要看到句子中的其他词来理解每个词的含义。
-
Q、K、V 直觉:搜索引擎类比——Q 是搜索词,K 是标题,V 是内容。三者都从同一个输入 X 通过不同的权重矩阵变换而来。
-
五步计算:X → Q、K、V → 点积 → 缩放 → Softmax → 加权求和 → 输出。
-
为什么分三个矩阵:功能分工(寻址 vs 特征提取)+ 语义不对称性 + 子空间投影。
-
缩放因子 :数学证明点积方差 = ,除以 标准化回 1。
-
注意力矩阵不对称:因为 ,自然语言关系是有向图。
-
复杂度 :序列长度平方级增长是长上下文的核心瓶颈。
下一篇,我们将看到 Transformer 如何用 Multi-Head Attention(多头注意力) 从多个角度同时关注不同的语言特征,以及如何用 位置编码 让模型感知词的顺序——从最初的 Sinusoidal 到现代的 RoPE。
本系列参考:The Illustrated Transformer by Jay Alammar(CC BY-NC-SA 4.0)
💬 评论
评论加载中...