第 15 章 Transformer 架构

本章把「注意力」从一个辅助对齐技巧,推导成唯一的序列建模算子:先解释 RNN seq2seq 的瓶颈,再逐步建立 Q/K/V、缩放、多头、位置编码、残差与归一化、掩码,最后给出复杂度分析与稀疏注意力家族。

学习目标

读完本章,你应当能够:

  1. 说明固定长度上下文向量为什么构成 RNN seq2seq 的瓶颈,注意力如何解除该瓶颈;
  2. 用「软寻址」解释 Q/K/V,并完整推导缩放点积注意力中 √d_k 的来源(内积方差随维度的增长)与不缩放的后果;
  3. 写出多头注意力的维度账本与参数量,说明每头维度 d_k = d_model/H 的约束;
  4. 说明自注意力为什么是置换等变的,比较正弦位置编码、可学习位置编码、RoPE、ALiBi 的性质与长度外推能力;
  5. 辨析 Post-LN 与 Pre-LN 的梯度差异,说明大模型为何普遍采用 Pre-LN + RMSNorm;
  6. 构造因果掩码,解释掩码与 softmax 中 −∞ 的配合为什么等价于「只在可见位置归一化」;
  7. 推导 O(N²d) 时间与 O(N²) 显存的来源,比较滑窗、Longformer、BigBird、Linformer、Performer 的近似思路与代价,并实现滑动窗口注意力。

前置知识检查

你需要知道在哪一节补齐
矩阵乘法与批量矩阵乘的维度规则本章 15.3.1 节的维度账本与 15.3.2 节的 Q@Kᵀ 形状
Softmax 的数值稳定性(减最大值)本章 15.6 节结合 −∞ 掩码复述(完整版见第 5 章)
方差的可加性 Var(Σxᵢ) = ΣVar(xᵢ)(独立时)本章 15.2.2 节推导 √d_k 时显式列出
残差连接与梯度流本章 15.5 节从 ∂y/∂x = ∂F/∂x + I 讲起(见第 13、14 章)
层归一化(LayerNorm)的统计维度本章 15.5.3 节说明它对最后一维归一化
序列建模、隐藏状态、RNN 展开本章 15.1 节重新叙述,不需要事先掌握

15.1 注意力的起源:从 RNN 瓶颈到对齐

15.1.1 固定长度上下文向量的瓶颈

机器翻译的经典结构是 seq2seq:编码器(encoder)逐词读入源句,把全部信息压进最后一个隐藏状态 h_N;解码器(decoder)从 h_N 出发逐词生成目标句。

1
2
3
4
5
源句  x₁ x₂ x₃ … x_N
       │  │  │      │
       ▼  ▼  ▼      ▼
编码器 h₁ h₂ h₃ … h_N ──▶ 上下文向量 c = h_N ──▶ 解码器 ──▶ y₁ y₂ …
                                                        (每步都只看这一个 c)

问题在于 c 是固定长度的:不管源句是 5 个词还是 50 个词,编码器都必须把它塞进同一个 d 维向量。这带来两个后果:

  1. 信息瓶颈:长句的信息量远大于 d 个实数所能承载,解码器必然丢失细节;
  2. 无差别压缩:生成第 j 个目标词时真正需要的往往只是源句的一两个位置(例如翻译 “the cat” 时对应「猫」),但 c 里所有位置的信息被等权混合,解码器无法「按需取用」。

15.1.2 对齐与注意力权重

注意力的想法是:不压缩。保留编码器每一个时间步的输出 h₁ … h_N,让解码器在每一步动态挑选当前最需要的部分:

1
2
3
4
5
解码第 j 步:
  ① 打分:    e_{j,i} = score(解码器状态 s_{j−1}, 编码器状态 h_i)
  ② 归一化:  α_{j,i} = exp(e_{j,i}) / Σ_{i'} exp(e_{j,i'})     (α 就是「注意力权重」)
  ③ 加权求和:c_j = Σ_i α_{j,i} · h_i                            (软对齐)
  ④ 用 c_j 与 s_{j−1} 一起预测 y_j

α_{j,i} 读作「生成第 j 个目标词时,源句第 i 个位置该占多大分量」。它有两个好处:长度不再受限(c_j 随源句长度自适应地加权),可解释(把 α 画成矩阵就是一张对齐热图)。

重要

注意力的本质是可微的软检索:它不做出「选第 3 个词」这样的硬决策,而是输出一个权重分布,再对全部候选做加权平均。权重来自 softmax,所以整条链可导、可端到端训练。这个「软」字是理解后续全部推导的钥匙。


15.2 缩放点积注意力:Q/K/V 与 √d_k

15.2.1 Q/K/V 的直觉:软寻址

把注意力想象成一次数据库检索(或哈希查表),但有两点不同:查询不是精确匹配而是相似度匹配,返回的不是单条记录而是全部记录的加权和。

数据库概念注意力记号含义
查询条件 queryQ(N × d_k)当前 token 想「问」什么
索引键 keyK(N × d_k)每个 token 能「答」什么特征
存储值 valueV(N × d_v)每个 token 实际提供的内容
匹配分数QKᵀ查询与键的相似度(内积)
软寻址权重softmax(QKᵀ/√d_k)每行是一个概率分布,和为 1
检索结果softmax(...)V值的加权汇总

三个矩阵都由同一个输入 X(N × d_model)经线性投影得到:

1
2
Q = X W_Q ,  K = X W_K ,  V = X W_V
W_Q, W_K: d_model × d_k ;  W_V: d_model × d_v

为什么要三套不同的投影,而不是直接拿 X 三两配对? 因为「我想找什么」「我是什么」和「我提供什么」是三种不同的角色。同一个词在作 query 时关心的是主谓关系,在作 key 时提供的是词性特征,在作 value 时贡献的是语义内容。让 W_Q、W_K、W_V 各自学习,模型才获得这种角色分离的表达力。

15.2.2 完整公式与 √d_k 的方差推导

标准写法(对应 【09-02 编程题 1】 的题面公式):

1
2
3
4
5
Attention(Q, K, V) = softmax( Q Kᵀ / √d_k ) V

形状:Q(N × d_k) · Kᵀ(d_k × N) = S(N × N)
      softmax 按行做(每一行是一个 query 对所有 key 的分布)
      A(N × N) · V(N × d_v) = O(N × d_v)

为什么必须除以 √d_k? 设 q、k 是 d_k 维向量,各分量独立、均值 0、方差 1(初始化与 LayerNorm 之后这是合理近似)。内积是 d_k 项之和:

1
2
3
4
5
qᵀk = Σ_{i=1}^{d_k} q_i k_i
E[q_i k_i] = E[q_i]·E[k_i] = 0
Var(q_i k_i) = E[q_i²]·E[k_i²] − 0 = 1 · 1 = 1
Var(qᵀk) = Σ_{i=1}^{d_k} Var(q_i k_i) = d_k          (各项独立,方差可加)
标准差   = √d_k

结论:内积的方差随 d_k 线性增长,量级是 √(d_k)。 于是 d_k = 64 时分数典型波动范围约 ±8,d_k = 512 时约 ±22。

不缩放会怎样? 分数的一大一小的差距被放大后送进 softmax:

1
softmax(z)_i = exp(z_i) / Σ_j exp(z_j)

设两个分数差 Δ。softmax 的雅可比是 ∂softmax_i/∂z_j = softmax_i(δ_ij − softmax_j),当某个分量接近 1 时,其余分量的导数趋于 0 —— 这就是饱和区。

1
2
Δ = 2  → 权重比 exp(2) ≈ 7.4      → 分布仍有梯度
Δ = 22 → 权重比 exp(22) ≈ 3.6×10⁹  → 分布塌缩成 one-hot,梯度 ≈ 0

后果是梯度消失:softmax 输出的梯度趋近于零,W_Q、W_K 几乎收不到更新,训练在早期就停滞。除以 √d_k 把分数方差重新拉回 1 量级,使 softmax 工作在梯度最灵敏的区域。

警告

√d_k 里是每个头的维度 d_k,不是模型维度 d_model。多头注意力中 d_k = d_model/H,例如 d_model = 512、H = 8 时 d_k = 64,缩放因子是 √64 = 8 而不是 √512 ≈ 22.6。把两者搞混是高频错误。


15.3 多头注意力与维度账本

15.3.1 为什么要多头

单头注意力只能输出一个加权平均。如果句子中同时存在「指代关系」「时态一致」「局部搭配」等多种依赖,一个平均分布无法同时表达它们——softmax 每行只有一个分布,注意力只能「聚焦在一处」。

多头注意力把 d_model 切成 H 份并行计算,每份在自己的子空间里学一种关系:

1
2
3
第 h 个头: head_h = Attention( X W_Q^(h), X W_K^(h), X W_V^(h) )
          = softmax( Q_h K_hᵀ / √d_k ) V_h
拼接:     MultiHead = Concat(head_1, …, head_H) W_O

「子空间假设」:不同头会自发分化——有的头盯着相邻词(局部语法),有的头盯着句首(全局主题),有的头负责指代消解。这是一种可观测的经验事实(把每个头的注意力矩阵画出来即可看到分工),而不是理论保证:训练不保证每个头都学到互不相同的模式,实际中常有冗余头可以被剪掉而不明显掉点。

15.3.2 维度账本与参数量

这是编程题(【08-26 编程题 2】)最常考的部分,务必能默写:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
d_model = 512, H = 8

每个头:  d_k = d_v = d_model / H = 64
W_Q^(h): d_model × d_k = 512 × 64
W_K^(h): 512 × 64        W_V^(h): 512 × 64        (每个头各一套)

单头内:  Q_h(N×64) @ K_hᵀ(64×N)  →  S_h(N×N)     ← 分数矩阵与头无关,只与 N 有关
         softmax 按行 → A_h(N×N)
         A_h(N×N) @ V_h(N×64)     →  O_h(N×64)

拼接:   Concat(O_1 … O_8) → (N × 512)
输出投影:W_O: 512 × 512  →  最终 (N × 512)

对应 【09-09 选择 Q7】:Q ∈ R^(B×L×d) 与 Kᵀ ∈ R^(B×d×M) 相乘,批量矩阵乘只在后两维做矩阵乘,第一维 B 作为批维度对齐保留;后两维 (L×d)(d×M) 内维相消得 L×M,所以结果是 B×L×M。这个 L×M 矩阵就是注意力分数矩阵,第 (i,j) 项表示第 i 个 query 对第 j 个 key 的相关度,行数必须等于 query 个数。

参数量统计(单个注意力模块,含偏置省略):

1
2
3
W_Q, W_K, W_V:  3 × (d_model × d_model) = 3 d_model²
W_O:               d_model × d_model    =   d_model²
合计:             4 d_model²

注意参数量与头数 H 无关:8 个头各用 512 × 64,总和与单头 512 × 512 完全相同。多头不增加参数,只改变计算的组织方式(把它看成「分块 + 并行 + 拼接」,等价于给每个子空间独立归一化与独立 softmax 温度)。

提示

记忆账本的口诀:d_k 是所有形状计算的分母,N 是所有注意力矩阵的边长。任何维度对不上时,先问「这一维是 d_model、d_k 还是 N」。


15.4 位置编码:把顺序注入无先验的结构

15.4.1 自注意力是置换等变的

自注意力对输入序列做的是 softmax(QKᵀ/√d_k)V。若把输入的行按置换矩阵 P 重排(X → PX),则 Q → PQ、K → PK、V → PV:

1
2
(PQ)(PK)ᵀ = P Q Kᵀ Pᵀ        ⟹  softmax 逐行作用后仍是 P·softmax(QKᵀ)·Pᵀ
(P·A·Pᵀ)(PV) = P (A V)        ⟹  输出同样只是被重排

也就是说:打乱输入顺序,输出只是被同样打乱,模型本身完全察觉不到顺序变化。「猫追狗」与「狗追猫」在它眼里是同一个多重集合。这与 RNN 的时序递推、CNN 的局部窗口形成鲜明对比——那两者天然含顺序先验。

所以顺序必须显式注入。通用做法是给每个位置 i 构造一个向量 p_i,与词嵌入相加(或拼接):

1
z_i = Embed(token_i) + p_i           (相加要求 dim(p_i) = d_model)

15.4.2 正弦位置编码与相对位置性质

原始 Transformer 用固定公式(不学习):

1
2
PE(i, 2k)   = sin( i / 10000^(2k/d_model) )
PE(i, 2k+1) = cos( i / 10000^(2k/d_model) )

其中 i 是位置,k 是维度索引(偶数维用 sin,奇数维用 cos)。波长从 2π 到 10000·2π 几何级数分布,于是低维捕捉细粒度位置、高维捕捉粗粒度位置。

为什么它能表达相对位置? 用和角公式看位置 i+Δ 与位置 i:

1
2
PE(i+Δ, 2k)   = sin(ω_k i + ω_k Δ) = sin(ω_k i)cos(ω_k Δ) + cos(ω_k i)sin(ω_k Δ)
PE(i+Δ, 2k+1) = cos(ω_k i + ω_k Δ) = cos(ω_k i)cos(ω_k Δ) − sin(ω_k i)sin(ω_k Δ)

PE(i+Δ) 是 PE(i) 的线性变换,而这个变换矩阵只依赖 Δ、不依赖 i。既然它是线性变换,Q/K 的线性投影就有机会把它「吸收」成只与相对距离有关的项——这就是正弦编码能支持相对位置推理的原因。

15.4.3 可学习位置编码、RoPE 与 ALiBi

方案形式是否可学习相对位置长度外推代表
正弦编码z_i = x_i + PE(i)否通过线性关系间接获得公式对任意 i 有定义,但训练分布外的位置效果差原始 Transformer
可学习编码z_i = x_i + E[i],E 为 L_max × d 参数量是无显式结构,靠数据学差:超出 L_max 无参数可用BERT、GPT-2
RoPE对 q、k 按二维子空间旋转角度 θ = i·ω否(角度固定)内积只依赖 i − j较好,可配合插值/NTK 缩放外推LLaMA、Qwen、GPT-NeoX
ALiBi不编码位置,直接在分数上加线性偏置 −m·(i−j)斜率 m 固定显式只依赖距离好,训练短序列即可外推BLOOM、MosaicML

RoPE(rotary position embedding)的核心思想:不用加法,而是把 q、k 按位置相关的角度做旋转。对每一对维度 (2t, 2t+1) 施加旋转矩阵 R(i, θ_t):

1
2
q_i' = R(i, θ) q_i ,  k_j' = R(j, θ) k_j
q_i'ᵀ k_j' = q_iᵀ R(i,θ)ᵀ R(j,θ) k_j = q_iᵀ R((j−i), θ) k_j

最后一步用了旋转矩阵的正交性 R(a)ᵀ = R(−a) 与 R(−i)R(j) = R(j−i)。于是内积只依赖相对距离 j − i,且不改变向量模长。这是 RoPE 被广泛采用的原因:相对位置性质是结构性保证的,不是学出来的。

ALiBi(attention with linear biases)的思路更直接:完全不动 q、k,只在分数矩阵上加一个与距离成正比的惩罚:

1
score(i,j) ← score(i,j) − m_h · (i − j)          (j ≤ i,只看因果位置)

m_h 是每个头固定的斜率(几何级数,如 1/2, 1/2², …)。距离越远惩罚越大,等价于给模型一个「近处优先」的强先验。它的外推优势来自形式简单:加性偏置对任意长度都成立,不依赖训练时见过的最大长度。

长度外推问题:模型在 L = 4096 上训练,推理时喂 8192 个 token,性能通常明显下降。原因有二:位置编码从未见过 i > 4096(可学习编码直接失效,正弦与 RoPE 虽可计算但分布外);注意力分布本身也没在更长序列上校准过。常见补救是位置插值(把推理位置 i 按 i·L_train/L_infer 压缩回训练范围)与 NTK 缩放(改变 RoPE 各维频率的基)。


15.5 残差、归一化与前馈网络

15.5.1 残差:每个子层都包一层

Transformer 的每个子层都是 y = Sublayer(x) + x。理由与第 14.5.3 节完全相同:

1
∂y/∂x = ∂Sublayer/∂x + I

恒等项 I 保证梯度有一条无损旁路,即使子层内部的雅可比很小,梯度也能回流到浅层。由于 x 与 Sublayer(x) 要相加,两者的最后一维都必须等于 d_model——这也是 W_O 输出 d_model 维的原因。

15.5.2 Post-LN 与 Pre-LN

归一化放在残差里的位置有两种排布:

1
2
3
4
Post-LN(原始 Transformer)              Pre-LN(现代大模型)
  x ──▶ Sublayer ──▶ ⊕ ──▶ LayerNorm       x ──▶ LayerNorm ──▶ Sublayer ──▶ ⊕
  x ────────────────┘                       x ────────────────────────────┘
  y = LN(x + Sublayer(x))                   y = x + Sublayer(LN(x))

差异在梯度路径上:

1
2
Post-LN: ∂y/∂x 要穿过 LayerNorm 的雅可比,且主路径是 Sublayer 而非恒等
Pre-LN:  残差主路径是纯恒等 x → x,LayerNorm 位于旁路内部

后果是 Post-LN 的深层梯度容易被 LayerNorm 缩放系数压制,需要学习率 warmup 才能稳定训练(原始论文明确要求 warmup);而 Pre-LN 的恒等主路径使梯度尺度与层数基本无关,训练更稳、对学习率与 warmup 更宽容。代价是 Pre-LN 的最终输出范数会随层数增长,因此通常在最后一层后再补一次 LayerNorm(即 final_norm)。

重要

这是「为什么大模型普遍用 Pre-LN」的标准答案:Pre-LN 把恒等路径放在了残差主干上,使梯度不经过归一化的雅可比。它的代价(输出范数漂移)只需一个末端 LayerNorm 就补偿掉,而 Post-LN 的代价(必须精细调 warmup)无法用结构消除。

15.5.3 RMSNorm 的位置

RMSNorm(root mean square norm)是 LayerNorm 的简化:不减均值,只除以均方根,且去掉仿射偏置。

1
2
LayerNorm(x) = γ · (x − μ) / √(σ² + ε) + β ,   μ、σ² 沿最后一维统计
RMSNorm(x)   = γ · x / √( (1/d)Σ_i x_i² + ε )

减去均值的那一步在 Transformer 里贡献有限,而它带来的均值计算与同步开销不小,因此 RMSNorm 用更少的运算换到几乎相同的效果,在 LLaMA 系模型里成为标配。它出现的位置仍然是 Pre-LN 的那个位置(子层输入之前),即 y = x + Sublayer(RMSNorm(x))。

15.5.4 前馈网络与激活函数

每个 Transformer 层里,注意力后面还有一个位置独立的前馈网络(FFN, feed-forward network):

1
2
FFN(x) = W₂ · act(W₁ x + b₁) + b₂
W₁: d_model × d_ff ,  W₂: d_ff × d_model ,  通常 d_ff = 4 d_model

它的作用:注意力负责跨位置混合信息(token 之间交换内容),FFN 负责逐位置的非线性变换(对每个 token 的表示做加工)。两者分工明确——注意力是唯一的跨位置算子,FFN 是唯一的非线性算子(若去掉 FFN,整个模型退化为若干线性投影与 softmax 的叠加,表达力大幅下降)。

现代变体把两层线性改成门控形式(GLU 家族):

1
2
3
ReLU FFN:    act = ReLU(z)
GELU FFN:    act = GELU(z) = z · Φ(z)          (Φ 是标准正态 CDF,平滑版 ReLU)
SwiGLU FFN:  FFN(x) = (Swish(x W₁) ⊙ (x W₃)) W₂ ,  Swish(z) = z · σ(z)

SwiGLU 多引入一个投影 W₃,为了保持参数量不变,实践上把 d_ff 从 4 d_model 下调到约 (8/3) d_model。

参数量占比是一个常考的估算题:

1
2
3
注意力:4 d_model²
FFN:   2 · d_model · d_ff = 2 · d_model · 4 d_model = 8 d_model²
单层合计:12 d_model² ,其中 FFN 占 8/12 ≈ 67%

所以「Transformer 的多数参数在 FFN 里」这句话是对的——这也是 MoE(专家混合)把 FFN 换成多个专家的原因:改一个模块就能把参数量放大若干倍,而注意力保持共享。


15.6 编码器、解码器与掩码

15.6.1 三种用法

结构每个位置能看到训练目标典型用途代表
encoder-only全序列(双向)掩码语言模型(完形填空)理解类:分类、抽取、句向量BERT、RoBERTa
decoder-only只有自己及左侧下一 token 预测生成类:对话、续写、代码GPT 系列、LLaMA
encoder-decoder编码器双向,解码器带因果掩码并交叉注意编码器输出序列到序列翻译、摘要、语音识别T5、BART、原始 Transformer

三者的差别只在可见性与训练目标,模块本身完全一样。所以「Q@Kᵀ 的维度」这类问题与结构无关,永远是 B×L×M。

15.6.2 因果掩码的构造

自回归生成要求第 i 个位置不能「看到」未来的 j > i,否则训练时就能直接抄答案。做法是构造一个下三角掩码矩阵:

1
2
3
4
5
6
7
8
M[i][j] = 允许(True)   当 j ≤ i        (含对角线:自己永远可见)
        = 屏蔽(False) 当 j > i

        j=0   j=1   j=2   j=3
i=0  [  ✓     ✗     ✗     ✗  ]
i=1  [  ✓     ✓     ✗     ✗  ]
i=2  [  ✓     ✓     ✓     ✗  ]
i=3  [  ✓     ✓     ✓     ✓  ]

实现上只需 torch.tril(torch.ones(N, N))。它对应 【09-02 编程题 1】 里滑动窗口掩码的「同一族」思路:窗口掩码 |i − j| ≤ ω 是把上下三角都截断,因果掩码是只保留一侧。

15.6.3 掩码与 −∞ 的配合

掩码不是「算完再删」,而是在 softmax 之前把被屏蔽的分数改成 −∞:

1
2
3
4
S = Q Kᵀ / √d_k
S̃[i][j] = S[i][j]   若 M[i][j] = True
        = −∞        否则
A = softmax(S̃ 按行) ,  然后 O = A V

为什么这样等价于「只在可见位置归一化」? 因为 exp(−∞) = 0:

1
A[i][j] = exp(S̃[i][j] − max_t S̃[i][t]) / Σ_t exp(S̃[i][t] − max_t S̃[i][t])

被屏蔽位置的 exp(−∞) = 0,于是它们既不进分子也不进分母——分母 Σ_t 虽然形式上写的是对所有 t 求和,实际有效项只有可见位置。把分子分母同时限制在可见集合上,正是「只在窗口内归一化」的定义。这就是滑动窗口注意力不需要为每行单独切片的根本原因。

数值稳定的前提:每行至少要有一个有限分数,否则减最大值会得到 −∞ − (−∞) = NaN。因果掩码下 j = i 自己一定可见;滑动窗口下 |i − i| = 0 ≤ ω 同样保证自己可见。这个细节在编程题里是必须说明的正确性依据。

警告

掩码必须在 softmax 之前生效。若先算 softmax 再乘 0/1 掩码,被屏蔽位置的概率质量已经被分走,结果是「不可见位置的权重被抹掉但可见位置的权重没有重新归一化」,总和不等于 1,输出尺度整体偏小。


15.7 复杂度分析与稀疏注意力家族

15.7.1 O(N²d) 与 O(N²) 的来源

逐个算子数一遍(单层、单头、忽略常数):

1
2
3
4
QKᵀ :  (N × d_k) × (d_k × N)  →  N²d_k  次乘加
AV  :  (N × N)   × (N × d_v)  →  N²d_v  次乘加
投影:  X W_Q/W_K/W_V/W_O      →  4 N d_model²
FFN :  2 N d_model d_ff        →  8 N d_model²

于是(把 d_k、d_v 统一记作 d):

1
2
时间:O(N² d + N d²)        当 N > d 时,N²d 项主导 ⟹ 常写作 O(N² d)
中间张量的显存:分数矩阵与注意力矩阵各占 N² ⟹ O(N²)

关键认识:O(N²) 的显存不是「模型的固有需求」,而是 softmax(QKᵀ) 这个物化中间矩阵的开销。这为两条优化路线留了口子:

  1. 不物化整个 N×N 矩阵 → FlashAttention(分块 + online softmax,把中间结果留在片上内存);
  2. 不计算全部 N² 个分数 → 稀疏注意力家族(只算被选中的位置对)。

注意

FlashAttention 只是常数因子与访存层面的优化,它不改变 O(N²) 的渐进复杂度,因为它算的仍然是精确的稠密注意力。第 19 章会给出 online softmax 的完整推导与 IO 复杂度分析,本章只建立「为什么要分块」的动机。

15.7.2 稀疏注意力家族

方法近似思路复杂度代价与限制
滑动窗口(sliding window)每个 query 只看 |i − j| ≤ ω 的邻居时间 O(Nωd),显存 O(Nω)感受野只有 ω,需多层堆叠才能传播全局信息;ω 需与任务匹配
Longformer滑窗 + 少量全局 token(如 [CLS])对所有位置双向注意O(Nω),ω ≪ N全局 token 数量是超参,过多则退回 O(N²)
BigBird滑窗 + 全局 + 随机稀疏连接(三者混合)O(N)(ω、全局数、随机数均为常数)有理论上的图灵完备/通用近似保证,实现较复杂
Linformer假设注意力矩阵低秩,把 K、V 从 N 投影到固定长度 k时间与显存均 O(N)低秩假设在长序列上不总成立;k 太小会掉点
Performer用核方法近似 softmax:exp(qᵀk) ≈ φ(q)ᵀφ(k),从而交换乘法顺序线性 O(N)近似有误差,核函数选择影响大,长序列精度通常弱于精确注意力

共同的取舍是:稀疏或低秩近似把 N² 降到 N·ω 或 N,代价是放弃了「任意两个位置都能直接交互」的完备性。它们都依赖同一条实证规律——大多数任务的依赖具有局部性,远距离 token 通过多层堆叠间接交互即可(这正是 【09-02 编程题 1】 题面给出的动机)。是否掉点取决于任务对长程依赖的真实需求。

15.7.3 滑动窗口注意力的实现

对应 【09-02 编程题 1】:窗口掩码(|i − j| ≤ ω,两端自动截断)→ 缩放打分 → 窗口内 softmax(减窗口内最大值)→ 对 V 加权求和 → 非法参数返回 0。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import numpy as np


def build_window_mask(seq_len, window):
    """mask[i][j] 为真 ⟺ 位置 i 的 query 要和位置 j 的 key 打分。"""
    idx = np.arange(seq_len)
    # 广播出全部下标差;两端超界的位置天然不在矩阵里,截断自动完成
    return np.abs(idx[:, None] - idx[None, :]) <= window


def sliding_window_attention(q, k, v, window):
    """q、k、v 均为 (seq_len, d_k);返回 (seq_len, d_k)。"""
    seq_len, d_k = q.shape
    mask = build_window_mask(seq_len, window)
    score = q @ k.T / np.sqrt(d_k)             # (N,d)@(d,N) -> (N,N)
    # 窗口外置 -inf:exp(-inf) = 0,既不进分子也不进分母,
    # 因此与「只在窗口内归一化」完全等价,无需逐行切片
    score = np.where(mask, score, -np.inf)
    # 数值稳定:每行减去窗口内最大值;位置 i 自己必在窗口内,最大值有限
    score = score - score.max(axis=1, keepdims=True)
    weight = np.exp(score)
    weight = weight / weight.sum(axis=1, keepdims=True)   # 按行归一化
    return weight @ v                                     # (N,N)@(N,d) -> (N,d)


def is_valid(batch_size, seq_len, d_k, window):
    """非法参数按题意返回 0,判断必须放在读矩阵之前。"""
    return batch_size >= 1 and seq_len >= 1 and d_k >= 1 and 1 <= window <= seq_len

三个易错点:① 归一化权重只是展示精度,不能先量化成 2 位小数再乘 V(否则末位会差 0.01);② 输出要处理负零,-0.00 应归成 0.00;③ 非法输入的分支要在读矩阵之前判断,否则会先读到脏数据再报错。

15.7.4 与 RNN/CNN 的对比

维度RNNCNNTransformer
每层计算复杂度O(N d²)O(k N d²)(k 为核宽)O(N² d + N d²)
最长路径(依赖距离)O(N),随距离线性增长O(N/k),需堆叠O(1),任意两位置一步直达
并行性差(时间步串行,无法在 N 上并行)好好(整个序列一次性矩阵乘)
归纳偏置强:顺序、递推中:局部性、平移等变(见第 14.5 节)无顺序先验,必须靠位置编码注入
长序列瓶颈梯度消失 + 串行感受野增长慢N² 时间与显存
显存峰值位置激活随层数特征图注意力矩阵 N×N

为什么 Transformer 取代了 RNN? 关键不是「更准」,而是可并行:RNN 的第 t 步依赖第 t−1 步,无法把序列长度摊到 GPU 的并行度上;Transformer 把 N 个位置一次性变成矩阵乘,同时最长依赖路径从 O(N) 降到 O(1),长程依赖更容易学。代价是 N²,而这正是 15.7.2 节全部方法要解决的问题。


本章小结

关键结论一句话表述常见错误
注意力的起源固定长度上下文向量是瓶颈,注意力用变长加权替代压缩以为注意力只是为了可解释性
Q/K/V 直觉软寻址:query 找 key,按相似度对 value 加权求和把三者当成同一个投影
缩放因子Var(qᵀk) = d_k,故除以 √d_k;不缩放则 softmax 饱和、梯度消失用 √d_model 而不是 √d_k
多头参数量与头数无关,只把 d_model 切成 H 个子空间并行以为多头增加参数量
Q@Kᵀ 维度批量矩阵乘只动后两维,(B,L,d)(B,d,M) → B×L×M,行数 = query 数写成 B×M×L 或 B×d×d
位置编码自注意力置换等变,顺序必须显式注入以为 ViT 或 Transformer 自带顺序感
RoPE旋转 q、k,使内积只依赖 i − j,且保模长以为 RoPE 是加法位置编码
Pre-LN vs Post-LNPre-LN 把恒等路径放在残差主干,梯度不穿过归一化以为 Post-LN 更稳
RMSNorm不减均值、无偏置,位置仍在子层输入前以为它替换了残差
因果掩码下三角,j ≤ i 可见;掩码须在 softmax 之前生效先 softmax 再乘掩码
exp(−∞) = 0窗口外概率恒为零,与「只在窗口内归一化」等价以为要逐行切片重算
FFN逐位置非线性,占单层约 2/3 参数;SwiGLU 需按 8/3 调 d_ff以为注意力是参数大头
复杂度时间 O(N²d)、显存 O(N²),N² 来自被物化的分数矩阵以为 FlashAttention 降低渐进复杂度
稀疏注意力滑窗/Longformer/BigBird/Linformer/Performer 用局部性或低秩换线性复杂度以为近似永不掉点
RNN/CNN/Transformer差异在并行性、最长依赖路径、归纳偏置与复杂度只说「Transformer 更准」

自测题

Q1(推导)设 q、k 是 d_k 维独立同分布向量,各分量均值 0、方差 1。推导 qᵀk 的方差,并说明不除以 √d_k 会发生什么。

答案与解析

qᵀk = Σ_{i=1}^{d_k} q_i k_i。每项期望 E[q_i k_i] = E[q_i]E[k_i] = 0,方差

1
2
3
Var(q_i k_i) = E[q_i² k_i²] − 0 = E[q_i²]·E[k_i²] = 1 × 1 = 1
Var(qᵀk) = Σ_{i=1}^{d_k} 1 = d_k      (各对独立,方差可加)
标准差   = √d_k

即内积的波动量级随 d_k 线性增长。不缩放时分数差被放大 √d_k 倍再送进 softmax;softmax 的雅可比是 ∂A_i/∂z_j = A_i(δ_ij − A_j),当某项 A ≈ 1 时其余分量的导数趋于 0,即进入饱和区。d_k = 512 时典型分数差可达 22 量级,exp(22) ≈ 3.6 × 10⁹,分布塌缩成近似 one-hot,梯度几乎为零,W_Q、W_K 得不到有效更新。除以 √d_k 把方差拉回 1,使 softmax 工作在梯度灵敏区。

常见错误:答「防止数值溢出」。溢出只是表象,核心是分布饱和导致梯度消失;√d_k 也远不到浮点溢出的量级。

Q2(计算)d_model = 512、H = 8、d_ff = 2048。写出单层注意力模块与 FFN 的参数量,并说明多头是否增加了参数。

答案与解析
1
2
3
注意力:4 × d_model² = 4 × 512² = 1 048 576      (W_Q、W_K、W_V、W_O 各 512²)
FFN:   2 × d_model × d_ff = 2 × 512 × 2048 = 2 097 152
单层合计 ≈ 3 145 728 ,其中 FFN 占 2 097 152 / 3 145 728 ≈ 66.7%

多头不增加参数:每头的 W_Q^(h) 是 512 × 64,8 头拼起来等价于一个 512 × 512(8 × 512 × 64 = 512 × 512)。多头改变的是计算组织方式——在 H 个子空间里各做一次独立 softmax,而不是在 d_model 维上做一次。

常见错误:把每头投影按 d_model × d_model 算,把参数量放大 H 倍;或漏掉 W_O。另注意 SwiGLU 要把 d_ff 从 4 d_model 下调到约 8/3 d_model 才能保持参数量可比。

Q3(辨析)为什么 Transformer 必须加位置编码?正弦编码为什么具备相对位置性质?

答案与解析

必须加的原因:设输入被置换矩阵 P 重排,则 Q → PQ、K → PK、V → PV,而 (PQ)(PK)ᵀ = P(QKᵀ)Pᵀ,因此输出只是被同样重排——自注意力是置换等变的,模型根本无法区分「猫追狗」与「狗追猫」。RNN 的递推与 CNN 的局部窗口天然带顺序先验,Transformer 没有,顺序只能由位置编码显式注入。

正弦编码的相对位置性质:由和角公式,位置 i+Δ 的编码是位置 i 编码的线性变换,且该变换只依赖 Δ:

1
2
PE(i+Δ, 2k)   = sin(ω_k i)cos(ω_k Δ) + cos(ω_k i)sin(ω_k Δ)
PE(i+Δ, 2k+1) = cos(ω_k i)cos(ω_k Δ) − sin(ω_k i)sin(ω_k Δ)

因 Q、K 是线性投影,这个只依赖 Δ 的变换有机会被吸收进内积,使分数表达相对距离。注意这是**「可表达」而非「保证学会」**:能力由结构给出,是否用起来取决于训练。

常见错误:把「置换等变」说成「置换不变」。置换等变是「输出随输入一起置换」,恰恰说明模型没有任何位置概念。

Q4(辨析/推导)滑动窗口注意力把窗口外分数置为 −∞ 后直接对整行做 softmax。为什么这与「只在窗口内归一化」等价?这个操作安全的前提是什么?

答案与解析

等价性:设第 i 行可见集合 W_i = {j : |i − j| ≤ ω}。因为 exp(−∞) = 0,对 j ∉ W_i 分子为 0,而分母 Σ_t exp(S̃[i][t]) 中 t ∉ W_i 的项也全为 0,于是分母等于 Σ_{t∈W_i} exp(S̃[i][t])。分子分母同时被限制在 W_i 上,正是「只在窗口内归一化」的定义——这就是编程题无需逐行切片、可以保持向量化的原因。

安全前提:每行至少存在一个有限分数,否则减最大值时会得到 −∞ − (−∞) = NaN。因果掩码下 j = i 必可见;滑动窗口下 |i − i| = 0 ≤ ω(要求 ω ≥ 1)同样保证自己可见。

顺序前提:−∞ 必须在 softmax 之前写入。若先 softmax 再乘 0/1 掩码,被屏蔽位置原本占有的概率质量被直接丢弃,可见位置没有重新归一化,该行权重之和小于 1,输出被整体缩小。

常见错误:只答「exp(−∞) = 0」而漏掉「每行至少一个有限值」——当某行全被屏蔽时(如 ω = 0 或掩码构造错误),实现会产生 NaN。

Q5(辨析)比较 Post-LN 与 Pre-LN 的梯度路径,说明为什么大模型普遍选择 Pre-LN,以及 RMSNorm 在其中处于什么位置。

答案与解析

两种排布是 y = LN(x + Sublayer(x)) 与 y = x + Sublayer(LN(x))。梯度路径的差别是:

1
2
3
4
Post-LN: 主梯度路径要穿过 LayerNorm 的雅可比(含 1/σ 缩放与减均值项),
         残差的「短路」效应被 LN 削弱,深层梯度尺度随层数衰减
Pre-LN:  残差主路径是纯恒等 x → x,∂y/∂x = I + ∂Sublayer(LN(x))/∂x,
         恒等项与层数无关,梯度尺度稳定

所以 Post-LN 需要学习率 warmup 才能稳定训练(原始论文明确要求),Pre-LN 对学习率与 warmup 宽容得多;代价是 Pre-LN 的输出范数随层数累积增长,通常在最后一层后补一次 LayerNorm(final_norm)收口。

RMSNorm 的位置:它是 LayerNorm 的简化——不减均值、不带偏置,RMSNorm(x) = γ · x / √((1/d)Σ x_i² + ε)。它替换的是归一化函数本身,位置仍是 Pre-LN 的位置:y = x + Sublayer(RMSNorm(x))。所以「Pre-LN + RMSNorm」是两个独立选择叠加,不是一回事。

常见错误:把 RMSNorm 说成「另一种残差排布」;或认为 Pre-LN 一定精度更高——取舍主要在训练稳定性与调参成本。

Q6(计算)某模型 d_model = 4096、N = 8192、FP16 存储。估算单个头物化分数矩阵 QKᵀ 所需的显存,并说明 FlashAttention 与稀疏注意力各自从哪一侧解决这个问题。

答案与解析

分数矩阵有 N × N = 8192² = 6.71 × 10⁷ 个元素,FP16 每元素 2 字节:

1
2
单头单层: 6.71 × 10⁷ × 2 B ≈ 1.34 × 10⁸ B ≈ 128 MiB
softmax 输出 A 再占一份 ⟹ 约 256 MiB(若不做算子融合)

关键是 N² 与 d_model 无关,只由序列长度决定——这就是长上下文的显存瓶颈。

FlashAttention 从「访存」一侧解决:它不改变「算哪些位置对」(仍是精确稠密注意力),而是不把 N×N 矩阵写回 HBM,用分块与 online softmax 把中间结果留在片上 SRAM 逐块累积。它优化的是显存峰值与 IO,渐进复杂度仍是 O(N²)。

稀疏注意力从「算多少」一侧解决:只计算被选中的位置对(滑窗 |i − j| ≤ ω、Longformer 的全局 token、BigBird 的随机连接、Linformer 的低秩投影、Performer 的核近似),把时间与显存降到 N·ω 或 N;代价是放弃「任意两位置一步直达」的完备性,可能掉点。

常见错误:声称 FlashAttention 把复杂度降到 O(N)。它降低的是常数因子与显存峰值,不是渐进复杂度。

与真题的关联

真题考查角度对应本章小节
【09-09 选择 Q7】 Q@Kᵀ 的维度与分数矩阵含义批量矩阵乘只动后两维,(B,L,d)(B,d,M) → B×L×M;行数 = query 数15.3.2 节(维度账本)
【09-02 编程题 1】 滑动窗口注意力窗口掩码 |i − j| ≤ ω、窗口内 softmax(减最大值)、非法参数返回 015.6.2、15.6.3、15.7.3 节(含可运行实现)
【08-26 编程题 2】 未归一化多头点积注意力的维度账本每头 d_k = d_model/H,β = q kᵀ,c = β V,拼接后经 W_p 回 2R 维并加残差15.3.1、15.3.2、15.5.1 节
【09-02 选择 Q11】(延伸)prefill/decode 瓶颈注意力与 KV Cache 的显存来源15.7.1 节(第 19 章展开)
【09-02 选择 Q4】(延伸)算子融合把 RMSNorm 与残差融进同一 kernel15.5.3 节(第 19 章展开)

延伸阅读

  • Vaswani 等,Attention Is All You Need(2017):缩放点积注意力、多头、正弦位置编码、Post-LN 的原始定义。
  • Bahdanau 等,Neural Machine Translation by Jointly Learning to Align and Translate(2015):注意力机制的起源与对齐可视化。
  • Su 等,RoFormer: Enhanced Transformer with Rotary Position Embedding(2021):RoPE 的旋转推导与相对位置性质。
  • Press 等,Train Short, Test Long: Attention with Linear Biases Enables Input Length Extrapolation(2022):ALiBi 与外推性实验。
  • Xiong 等,On Layer Normalization in the Transformer Architecture(2020):Pre-LN 的梯度分析与 warmup 必要性论证。
  • Beltagy 等,Longformer(2020);Zaheer 等,BigBird(2020);Wang 等,Linformer(2020);Choromanski 等,Performer(2021):稀疏与低秩近似四条路线。
  • Dao 等,FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness(2022):分块与 online softmax(第 19 章详细推导)。
  • 本套书相关章节:第 5 章(Softmax 数值稳定性)、第 12 章(反向传播)、第 13 章(归一化与残差)、第 14 章(ViT 与「无先验 + 位置编码」范式)、第 19 章(FlashAttention、KV Cache、prefill/decode)。