Transformer 原理(二):Self-Attention 深度拆解 —— Q K V 的前世今生

Transformer 系列第二篇。从"it 指代什么"的动机出发,逐步拆解 Self-Attention 的完整计算过程,深入理解 Q、K、V 的设计哲学与缩放因子的数学证明。

Transformer 原理(二):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 的核心公式是:

Attention(Q,K,V)=softmax(QKTdk)V\text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V

先不急看公式,我们从直觉开始。

搜索引擎类比

想象你在用搜索引擎:

  1. Query(查询):你输入的搜索词,比如”北京好玩的地方”
  2. Key(键/索引):数据库中每篇文章的标题,比如”北京故宫旅游攻略”、“上海东方明珠介绍”
  3. Value(值/内容):文章的实际内容

搜索过程是:

  1. 拿你的 Query 和每篇文章的 Key 做匹配,算出相关度分数
  2. 按分数高低排序——“北京故宫旅游攻略”分数最高
  3. 返回分数高的文章的 Value(实际内容)

Self-Attention 做的事完全一样,只不过”搜索者”和”被搜索的数据库”都是同一个句子里的词:

句子:"猫 坐在 垫子 上"

当处理"坐"这个词时:
  Query = "坐"发出的提问:"谁在执行这个动作?动作发生在哪里?"
  Key   = 每个词的"身份标签":"猫"→"我是名词/主语","垫子"→"我是名词/地点"
  Value = 每个词的"实际内容":"猫"→动物属性,"垫子"→物品属性

  匹配结果:"坐"的 Query 和"猫"的 Key 高度匹配(动词找主语)
  → 高权重提取"猫"的 Value → "坐"的表示融入了"猫"的信息

关键洞察:Q、K、V 来自同一个输入

在 Self-Attention 中,Q、K、V 都是从同一个输入向量 X 变换而来的:

Q=XWQ,K=XWK,V=XWVQ = X \cdot W_Q, \quad K = X \cdot W_K, \quad V = X \cdot W_V

其中 WQW_QWKW_KWVW_V 是三个不同的权重矩阵(上一篇我们提到过,它们就是每层 Encoder/Decoder 中可训练的参数)。

为什么要做这个变换? 如果直接用原始输入 X 同时当 Q、K、V(即不做投影),注意力公式就退化成 softmax(XXT)X\text{softmax}(XX^T)X——模型只能计算原始特征之间的相似度,无法学到更复杂的语义关系。三个不同的权重矩阵让模型可以从不同角度提取信息。详细分析见后面的深度小节。


逐步计算:一个完整的例子

我们用一个 2 个词、每个词 4 维的简化例子,走一遍 Self-Attention 的完整计算过程。

输入

假设句子 “I love” 经过 Embedding 后得到两个 4 维向量:

X=[10100101]X = \begin{bmatrix} 1 & 0 & 1 & 0 \\ 0 & 1 & 0 & 1 \end{bmatrix}

第一行是 “I” 的向量 [1,0,1,0][1, 0, 1, 0],第二行是 “love” 的向量 [0,1,0,1][0, 1, 0, 1]

第一步:生成 Q、K、V

用三个权重矩阵把 X 变换成 Q、K、V。为了简化,这里用 dk=3d_k = 3(实际模型中通常是 64 或 128):

WQ=[101010100011]W_Q = \begin{bmatrix} 1 & 0 & 1 \\ 0 & 1 & 0 \\ 1 & 0 & 0 \\ 0 & 1 & 1 \end{bmatrix} Q=XWQ=[10100101][101010100011]=[201021]Q = X \cdot W_Q = \begin{bmatrix} 1 & 0 & 1 & 0 \\ 0 & 1 & 0 & 1 \end{bmatrix} \cdot \begin{bmatrix} 1 & 0 & 1 \\ 0 & 1 & 0 \\ 1 & 0 & 0 \\ 0 & 1 & 1 \end{bmatrix} = \begin{bmatrix} 2 & 0 & 1 \\ 0 & 2 & 1 \end{bmatrix}

同样的方式计算 K 和 V(用不同的 WKW_KWVW_V 矩阵,这里省略具体数字)。假设最终得到:

K=[110011],V=[101010]K = \begin{bmatrix} 1 & 1 & 0 \\ 0 & 1 & 1 \end{bmatrix}, \quad V = \begin{bmatrix} 1 & 0 & 1 \\ 0 & 1 & 0 \end{bmatrix}

第二步:计算注意力分数(Q · K^T)

用 Q 和 K 的点积来衡量”每个词对其他词的关注程度”:

QKT=[201021][101101]=[2123]QK^T = \begin{bmatrix} 2 & 0 & 1 \\ 0 & 2 & 1 \end{bmatrix} \cdot \begin{bmatrix} 1 & 0 \\ 1 & 1 \\ 0 & 1 \end{bmatrix} = \begin{bmatrix} 2 & 1 \\ 2 & 3 \end{bmatrix}

点积(Dot Product):两个向量对应位置的数字相乘后求和。比如 [2,0,1][1,1,0]=2×1+0×1+1×0=2[2,0,1] \cdot [1,1,0] = 2 \times 1 + 0 \times 1 + 1 \times 0 = 2

为什么点积能衡量”相关度”? 这不是随意选择,而是有几何意义的。

点积度量的是两个向量的方向一致性。用一个简单的例子:

向量 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

第三步:缩放(除以 dk\sqrt{d_k}

QKTdk=13[2123][1.150.581.151.73]\frac{QK^T}{\sqrt{d_k}} = \frac{1}{\sqrt{3}} \begin{bmatrix} 2 & 1 \\ 2 & 3 \end{bmatrix} \approx \begin{bmatrix} 1.15 & 0.58 \\ 1.15 & 1.73 \end{bmatrix}

为什么要除以 dk\sqrt{d_k} 因为当维度 dkd_k 很大时,点积的数值也会很大,导致 Softmax 的输出变成接近 one-hot 的极端分布(几乎所有权重集中在一个词上),梯度消失,模型训练困难。除以 dk\sqrt{d_k} 是为了把数值控制在合理范围。详细的数学证明见后面的深度小节。

第四步:Softmax 归一化

对每一行做 Softmax,把原始分数变成概率分布(每行加起来 = 1)。

Softmax 的公式是什么? 对于一组分数 [z1,z2,,zn][z_1, z_2, \ldots, z_n],Softmax 把每个分数变成:

softmax(zi)=ezij=1nezj\text{softmax}(z_i) = \frac{e^{z_i}}{\sum_{j=1}^{n} e^{z_j}}

其中 e2.718e \approx 2.718(自然常数)。核心思想:先对每个分数取指数(eze^z,让所有值变成正数),然后除以总和(归一化到 0~1)。

用第一行 [1.15,0.58][1.15, 0.58] 走一遍:

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 后:

softmax[1.150.581.151.73][0.640.360.360.64]\text{softmax}\begin{bmatrix} 1.15 & 0.58 \\ 1.15 & 1.73 \end{bmatrix} \approx \begin{bmatrix} 0.64 & 0.36 \\ 0.36 & 0.64 \end{bmatrix}

为什么用 eze^z 而不是直接除以总和? 指数函数会放大差异——分数高的词会获得更多权重,分数低的词权重被压缩得更小。这让模型能更”果断”地把注意力集中在关键词上,而不是平均分配。

含义:

  • “I” 把 64% 的注意力放在自己,36% 放在 “love”
  • “love” 把 36% 的注意力放在 “I”,64% 放在自己

第五步:加权求和(乘以 V)

用注意力权重对 V 做加权求和,得到最终输出:

Output=[0.640.360.360.64][101010]=[0.640.360.640.360.640.36]\text{Output} = \begin{bmatrix} 0.64 & 0.36 \\ 0.36 & 0.64 \end{bmatrix} \cdot \begin{bmatrix} 1 & 0 & 1 \\ 0 & 1 & 0 \end{bmatrix} = \begin{bmatrix} 0.64 & 0.36 & 0.64 \\ 0.36 & 0.64 & 0.36 \end{bmatrix}

最终结果:每个词的新向量都融合了其他词的信息。

  • “I” 的新向量 [0.64,0.36,0.64][0.64, 0.36, 0.64] = 64% 的自己 + 36% 的 “love”
  • “love” 的新向量 [0.36,0.64,0.36][0.36, 0.64, 0.36] = 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(即 WQ=WKW_Q = W_K),“坐” 就只能去找和自己特征最相似的词(比如同义词”趴”、“躺”),而无法建立 动词→名词 的语法关联(“坐”→“猫”)。

维度三:子空间投影(信息论视角)

三个不同的权重矩阵把原始输入投影到三个不同的子空间:

  • WQW_QWKW_K 的子空间专注于学习 寻址机制 ——决定谁和谁有关联
  • WVW_V 的子空间专注于学习 特征提取 ——决定关联之后传递什么信息

如果不做投影会怎样?Q=K=V=XQ = K = V = X,注意力退化为 softmax(XXT)X\text{softmax}(XX^T)X。这时模型只能计算原始特征的物理相似度(比如两个向量的数值接近程度),完全失去了对复杂语义关系的抽象映射能力。


📌 深入理解:缩放因子为什么是 dk\sqrt{d_k}

这不是经验调参调出来的超参数,而是通过严格的数学推导得出的。

问题设定

qqkk 是两个独立的 dkd_k 维向量,假设每一维的分量都满足独立同分布(i.i.d),且均值为 0,方差为 1:

qi,ki(0,1),i=1,2,,dkq_i, k_i \sim (0, 1), \quad i = 1, 2, \ldots, d_k

它们的点积为:

qk=i=1dkqikiq \cdot k = \sum_{i=1}^{d_k} q_i k_i

推导点积的方差

期望:

由于 qiq_ikik_i 独立,且均值都是 0:

E[qiki]=E[qi]E[ki]=0×0=0\mathbb{E}[q_i k_i] = \mathbb{E}[q_i] \cdot \mathbb{E}[k_i] = 0 \times 0 = 0 E[qk]=i=1dkE[qiki]=0\mathbb{E}[q \cdot k] = \sum_{i=1}^{d_k} \mathbb{E}[q_i k_i] = 0

方差:

Var[qiki]=E[qi2ki2](E[qiki])2=E[qi2]E[ki2]0=1×1=1\text{Var}[q_i k_i] = \mathbb{E}[q_i^2 k_i^2] - (\mathbb{E}[q_i k_i])^2 = \mathbb{E}[q_i^2] \cdot \mathbb{E}[k_i^2] - 0 = 1 \times 1 = 1

由于各维度独立:

Var[qk]=i=1dkVar[qiki]=dk\text{Var}[q \cdot k] = \sum_{i=1}^{d_k} \text{Var}[q_i k_i] = d_k

结论

点积 qkq \cdot k 的均值为 0,方差为 dkd_k,标准差为 dk\sqrt{d_k}

三种缩放策略的对比

策略点积方差Softmax 行为后果
不缩放dkd_k(很大)输出接近 one-hot,某个词权重接近 1,其余接近 0梯度消失,模型失去学习能力
除以 dkd_k1/dk1/d_k(很小)输出接近均匀分布,所有词权重几乎相等模型失去”选择性关注”的能力
除以 dk\sqrt{d_k}1(刚好)输出在梯度最敏感的区域模型既能区分重点,又不会梯度消失

一句话总结:除以 dk\sqrt{d_k} 就是把点积的方差标准化回 1,让 Softmax 工作在梯度最健康的区域。


📌 深入理解:Attention 权重矩阵是对称的吗?

不是。

直觉理解很简单:在自然语言中,“猫”关注”坐”的强度 ≠ “坐”关注”猫”的强度。

  • “坐”在找主语 → 强烈关注”猫”(权重高)
  • “猫”在找什么? → 可能稍微关注”坐”,但更关注其他修饰词(权重低)

关注关系是单向的,就像有向图——A→B 的边权 ≠ B→A 的边权。

从公式上看,注意力矩阵 AA 中第 ii 个词对第 jj 个词的权重为:

Ai,jexp(qikjdk)A_{i,j} \propto \exp\left(\frac{q_i \cdot k_j}{\sqrt{d_k}}\right)

逐符号解读

  • Ai,jA_{i,j}:第 ii 个词对第 jj 个词的注意力权重
  • \propto:“正比于”,意思是经过 Softmax 归一化后,权重的相对大小由后面的值决定
  • exp(x)=ex\exp(x) = e^x:指数函数,就是 Softmax 中的那个”取指数”操作
  • qikjq_i \cdot k_j:第 ii 个词的 Query 向量与第 jj 个词的 Key 向量的点积

反过来,第 jj 个词对第 ii 个词的权重为:

Aj,iexp(qjkidk)A_{j,i} \propto \exp\left(\frac{q_j \cdot k_i}{\sqrt{d_k}}\right)

关键在于:qikjq_i \cdot k_jqjkiq_j \cdot k_i 不相等。因为 qi=xiWQq_i = x_i \cdot W_Qkj=xjWKk_j = x_j \cdot W_K,而 WQWKW_Q \neq W_K——同一个词作为 Query(主动提问)和作为 Key(被动应答)时,向量完全不同。所以 Ai,jAj,iA_{i,j} \neq A_{j,i},注意力矩阵天然就是不对称的。


📌 深入理解: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-AttentionCross-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)

总时间复杂度:O(n2d)O(n^2 \cdot d)(第二步和第五步主导)

空间复杂度:需要存储 n×nn \times n 的注意力矩阵 + n×dn \times d 的 Q、K、V → O(n2+nd)O(n^2 + n \cdot d)

核心瓶颈是 n2n^2——注意力矩阵的大小和序列长度的平方成正比:

序列长度 n注意力矩阵元素数以 FP16 计算的显存
512262K0.5 MB
4,09616.7M32 MB
32,7681.07B2 GB
128,00016.4B31 GB

当上下文长度从 512 增长到 128K,注意力矩阵的显存从 0.5 MB 暴增到 31 GB——这就是长上下文的核心挑战

优化方案预览

方案核心思想复杂度详见
Flash Attention不改数学,优化显存 IO(分块计算)O(n2d)O(n^2d) 不变第五篇
MQA / GQA减少 K/V 头数,降低 KV Cache不变第五篇
Sparse Attention稀疏化注意力模式O(nn)O(n\sqrt{n})
Linear Attention用核函数近似O(nd2)O(nd^2)

小结

本篇完整拆解了 Self-Attention 的工作原理:

  1. 动机:“it” 指代什么?——模型需要看到句子中的其他词来理解每个词的含义。

  2. Q、K、V 直觉:搜索引擎类比——Q 是搜索词,K 是标题,V 是内容。三者都从同一个输入 X 通过不同的权重矩阵变换而来。

  3. 五步计算:X → Q、K、V → 点积 → 缩放 → Softmax → 加权求和 → 输出。

  4. 为什么分三个矩阵:功能分工(寻址 vs 特征提取)+ 语义不对称性 + 子空间投影。

  5. 缩放因子 dk\sqrt{d_k}:数学证明点积方差 = dkd_k,除以 dk\sqrt{d_k} 标准化回 1。

  6. 注意力矩阵不对称:因为 WQWKW_Q \neq W_K,自然语言关系是有向图。

  7. 复杂度 O(n2d)O(n^2 \cdot d):序列长度平方级增长是长上下文的核心瓶颈。

下一篇,我们将看到 Transformer 如何用 Multi-Head Attention(多头注意力) 从多个角度同时关注不同的语言特征,以及如何用 位置编码 让模型感知词的顺序——从最初的 Sinusoidal 到现代的 RoPE。


本系列参考:The Illustrated Transformer by Jay Alammar(CC BY-NC-SA 4.0)

💬 评论

评论加载中...