Transformer 架构二:多头注意力、位置编码与前馈网络
本章导学
上一章我们完成 Transformer 整体架构的宏观拆解,了解编码器、解码器的层级结构。本章将深入三大核心组件:多头注意力的内部子空间特性、各类位置编码技术的演进对比、前馈网络 FFN 的作用与现代变体改进。多头注意力、位置编码、前馈网络是 Transformer 的三大基础构件,共同决定模型的语义建模能力。很多学习者只记住公式,却不理解:为什么要切分成多头?正弦位置编码有什么短板?RoPE 旋转位置编码解决了什么问题?前馈网络究竟在做什么,SwiGLU 激活相比 ReLU 带来哪些收益。本章结合数学推导、实验现象、工程实践,把每一个组件的设计动机、优缺点、适用场景完整展开,同时搭配可运行代码片段,打通理论与实现,为下一章层归一化、残差连接、训练技巧做铺垫。
30.1 多头注意力再深度剖析:多子空间语义建模
在第 28 章我们已经给出多头注意力基础公式,但是很多深层细节没有展开。单头自注意力,使用唯一一组\(W_Q,W_K,W_V\)完成投影,全部语义关系被压缩到同一个特征子空间。而多头注意力将 QKV 切分为 h 个独立的头,每个头拥有自己的低维投影子空间,每个头独立执行缩放点积注意力运算。原版论文设定\(d_{model}=512\),头数\(h=8\),每个头维度\(d_k=64\)。
\(MultiHead(Q,K,V)=Concat(head_1,...,head_h)W^O\)
\(head_i=Attention(Q_i,K_i,V_i)\)
这里存在一个极易被误解的点:多头并没有增加总的参数量与计算量 。如果直接使用单头 512 维注意力,QKV 投影矩阵维度为\(512\times512\);切分为 8 头,每头 64 维,8 组投影拼接等价于完整 512 维投影,参数量和单头保持一致。多头的收益不是算力提升,而是多子空间解耦能力。
不同注意力头会自发学习不同类型的语义依赖关系,这一点可以通过注意力权重可视化得到验证。在对预训练模型的注意力头分析当中,可以观察到几类典型行为:
- 部分头学习句法依赖:关注主谓、定宾的语法绑定,例如代词指向对应的名词;
- 部分头学习局部临近依赖:倾向于关注相邻的 1‑2 个 token,捕捉局部短语信息;
- 部分头学习长距离依赖:跨越几十个 token 建立关联,处理长句指代、上下文呼应;
- 还有部分头学习特殊符号关系:重点关注标点、分隔符、特殊标记 token。
如果只用单头注意力,所有这些类型的关联模式被迫挤压在同一个特征空间,不同语义模式之间会互相干扰。多头提供多个独立子空间,让不同类型的语言规律在各自子空间内学习,互不冲突。
但头的数量并不是越多越好。当总模型维度固定,头数量 h 增大,每个头维度\(d_k\)就会变小。每个头向量表征能力下降,头过多会出现每个头学到的模式趋同,出现头冗余现象。大模型工程实践中,遵循通用经验:每个头维度一般维持在 64‑128 区间。例如 LLaMA‑1 7B,模型维度 4096,头数 32,单头维度 128。
同时区分多头注意力的两种使用场景:
- 自注意力场景:Q、K、V 来自同一序列,编码器双向自注意力、解码器因果自注意力;
- 交叉注意力场景:Q 来自解码器,K、V 来自编码器输出记忆,用于编码‑解码架构,例如机器翻译。
多头注意力内部的缩放因子依旧沿用\(\frac{1}{\sqrt{d_k}}\),注意这里缩放使用的是单头维度\(d_k\),不是整体模型维度\(d_{model}\),这是代码实现高频出错点。很多初学者错误使用整体维度做缩放,会造成分数分布偏移,训练效果变差。
30.1.1 多头注意力常见工程坑点
第一,分头与拼接时张量维度变换。输入[batch,seq_len,d_model],投影之后 reshape 为[batch,seq_len,h,d_k],再 transpose 交换 seq_len 与 h 维度,得到[batch,h,seq_len,d_k]。很多人 transpose 顺序写错,后续矩阵乘法全部出错。 第二,掩码广播。mask 的维度需要适配多头维度,不需要给每一个头单独构造 mask,掩码可以广播到全部 head 维度,减少显存占用。 第三,多头输出之后的输出投影矩阵\(W^O\)必不可少。拼接各个头的输出之后,必须经过统一线性层,用来融合各个头的子空间信息,如果省略该层,多头的信息融合能力会大幅衰减。
30.2 位置编码全谱系:从正弦余弦、可学习位置编码到 RoPE
自注意力本身没有时序概念,token 打乱顺序,输出几乎不变。位置编码的核心使命:向输入嵌入注入序列位置信息,告诉模型 token 出现的先后次序。位置编码是 Transformer 不可或缺的模块,学术界先后诞生多套方案,各自有不同的优缺点。
30.2.1 原版正弦‑余弦位置编码(Sinusoidal Positional Encoding)
原始《Attention Is All You Need》论文使用正弦余弦位置编码。
\(\begin{cases} PE_{(pos,2i)} = sin(pos / 10000^{2i/d_{model}}) \\ PE_{(pos,2i+1)} = cos(pos / 10000^{2i/d_{model}}) \end{cases}\)
pos代表 token 在序列的位置下标;i代表向量维度索引。偶数维度使用 sin,奇数维度使用 cos。 关键特性:
- 无训练参数,位置编码是固定公式计算得到,不需要参与模型训练;
- 具备外推能力:训练时最多处理长度为 N 的序列,推理阶段可以处理超过 N 的更长序列,可以直接计算没有见过的 pos 位置的 PE 向量;
- 相对位置编码性质:\(PE_{pos+k}\)可以由\(PE_{pos}\)经过线性变换得到,模型可以学习 token 之间相对距离。
但是正弦余弦位置编码有明显短板:
- 属于绝对位置编码,直接把位置向量和词嵌入向量相加,位置信息与语义嵌入直接耦合;
- 外推能力有限,当推理序列远远长于训练最大长度,性能会明显衰减;
- 没有针对注意力分数计算做优化,位置信息只在输入层注入,注意力计算阶段本身感知不到相对位置。
使用方式:位置编码向量与词嵌入向量逐元素相加,不是拼接。如果是拼接,会改变特征维度,后续全部网络层维度都要适配。
30.2.2 可学习位置编码(Learnable Positional Embedding)
BERT 模型采用可学习位置编码。构造一个 Embedding 表格,表格大小为max_seq_len × d_model,每一行对应一个位置 pos 的向量,这些向量作为参数参与反向传播更新。
优点:位置向量由数据驱动学习,在训练最大序列长度之内,拟合效果优于正弦编码。 致命缺点:无法外推。表格只预分配到训练设定的最大长度,推理遇到更长序列,没有对应的位置参数,完全不能处理超出训练长度的输入。这也是 BERT 原生不能直接处理超长文本的原因。
30.2.3 RoPE 旋转位置编码(Rotary Position Embedding)
现在 LLaMA、Qwen、通义千问几乎全部开源大模型使用 RoPE 旋转位置编码,属于相对位置编码方案。它不再把位置编码加到输入嵌入,而是在计算 Q、K 注意力分数的时候,对 Q、K 向量执行旋转矩阵变换,将位置信息融入点积计算。
核心思想:对 Query、Key 向量,根据各自的位置 pos,执行二维旋转变换。点积的结果仅仅依赖两个 token 之间的相对位置。
\((QRoPE(pos_i))^\top\cdot(KRoPE(pos_j))=f(pos_i-pos_j)\) 两个向量点积,只由二者位置差\(pos_i-pos_j\)决定。
RoPE 带来两大核心优势:
- 优秀的外推能力,推理阶段可以处理比训练时更长的上下文;
- 把相对位置直接编码进注意力打分逻辑,相比绝对位置相加,位置和语义解耦更好。
同时 RoPE 也有工程细节:当外推长度极大时,会出现位置衰减,因此衍生出 RoPE 缩放(Linear Scaling、NTK‑RoPE)进一步拓展上下文窗口。
对比总结三类位置编码:
- 正弦余弦 PE:无参数,可有限外推,绝对位置,现代大模型很少直接使用;
- 可学习 PE:训练区间效果好,不能外推,BERT 使用;
- RoPE 旋转 PE:相对位置,强大外推能力,当前开源解码器大模型主流选择。
30.2.4 位置编码 PyTorch 实现片段
import torch
import torch.nn as nn
import math
#原版正弦余弦位置编码
class SinPositionalEncoding(nn.Module):
def __init__(self,d_model,max_len=5000,dropout=0.1):
super().__init__()
self.dropout = nn.Dropout(p=dropout)
pe = torch.zeros(max_len, d_model)
position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)
div_term = torch.exp(torch.arange(0, d_model, 2).float()* (-math.log(10000.0)/d_model))
pe[:,0::2] = torch.sin(position*div_term)
pe[:,1::2] = torch.cos(position*div_term)
pe = pe.unsqueeze(0)
self.register_buffer('pe', pe)
def forward(self,x):
# x:[batch,seq_len,d_model]
x = x + self.pe[:,:x.size(1)]
return self.dropout(x)
register_buffer代表 pe 是常量缓冲区,不参与梯度更新。输入词嵌入与 pe 相加,再经过 dropout,送入后续网络。
30.3 前馈网络 FFN:逐位置非线性变换与现代激活变体
多头注意力负责序列 token 之间的信息交互,完成跨位置信息聚合;而前馈网络 FFN 负责对每一个 token 特征,独立做非线性变换,FFN 不会跨 token 传递信息,每个位置的计算完全相互独立。
原始论文 FFN 公式:
\(FFN(x)=max(0,xW_1+b_1)W_2+b_2\) 两层全连接网络,中间维度放大,原版设定\(d_{model}=512\),中间隐藏维度\(d_{ff}=2048\),放大倍数 4 倍。先升维,ReLU 激活,再降维回原始模型维度。
很多学习者会疑惑:已经有注意力模块,为什么还需要 FFN? 我们可以做一个假设实验:如果删掉 FFN,Transformer 只剩下多头注意力、残差、归一化。多头注意力全部是线性加权运算,即便多头拼接输出投影,整套网络等价于巨大线性变换。线性模型的表达能力存在上限,无论堆叠多少层,整体等价于单层矩阵乘法。FFN 引入非线性激活函数,给模型带来非线性拟合能力,是模型理解复杂语义的关键。
注意力完成信息收集,FFN 对收集之后的特征做加工、变换、抽象,二者各司其职。注意力是 "信息聚合",FFN 是 "特征加工"。每一层编码器、解码器内部,二者成对出现。
30.3.1 激活函数演进:ReLU → GELU → SwiGLU
原始 Transformer 使用 ReLU 激活函数\(ReLU(x)=max(0,x)\)。ReLU 简单高效,但是存在死亡 ReLU 问题:部分神经元输出持续为 0,梯度不再更新,神经元永久失效。
GPT 系列模型改用 GELU 高斯误差线性单元:
\(GELU(x)=x\Phi(x)\) \(\Phi(x)\)是高斯分布累积分布函数。GELU 输入有概率性质的平滑激活,缓解死亡 ReLU,现在广泛应用。
而 LLaMA 等现代大模型使用 SwiGLU,对 FFN 结构本身做改动,不再是简单两层网络。SwiGLU 把输入做两路线性变换,一路经过 swish 激活,两路逐元素相乘之后再投影输出:
\(SwiGLU(x)=(xW_1\otimes\sigma(xW_2))W_3\) \(\otimes\)代表逐元素相乘,\(\sigma\)为 swish 激活。SwiGLU 提升模型收敛效果,但是参数量增加,因此工程实践当中会把放大倍数从 4 倍调整到 8/3 倍,平衡参数量。
重要工程事实:现在主流开源大模型已经基本不使用原版 ReLU‑FFN,全部替换为 SwiGLU 变体 FFN。
30.3.2 FFN 的现实行为
FFN 是逐位置运算,不受序列长度影响,时间复杂度\(O(n\cdot d_{model}\cdot d_{ff})\)。对比注意力\(O(n^2d)\),当序列很长的时候,FFN 的算力开销远小于注意力模块。
在预训练大模型的可解释性研究中发现,FFN 会学习大量知识事实,很多实体知识、常识记忆存储在 FFN 的权重当中,而不是全部保存在注意力。这打破了很多人的固有认知:注意力只是信息通路,大量知识存储在 FFN 前馈网络。
30.4 三大组件之间的协同工作流
我们把多头注意力、位置编码、FFN 串起来,看单层编码器完整数据流:
- token id 映射为词嵌入向量;
- 叠加位置编码(RoPE 则不在此处相加,在 QK 计算阶段施加旋转);
- 多头自注意力模块:序列各个 token 互相访问,完成跨位置信息聚合;
- 残差连接、层归一化;
- FFN 前馈网络,每个位置独立非线性特征变换;
- 残差连接、层归一化,输出单层结果,送入下一层。
三者分工总结:
- 位置编码:解决 "顺序" 问题,告诉模型 token 先后关系;
- 多头自注意力:解决 "信息交互" 问题,token 之间互相读取信息;
- FFN 前馈网络:解决 "非线性特征变换" 问题,对聚合后的信息做加工,存储知识。
三者缺一不可,去掉任意一个,模型语言能力会出现断崖式下跌。
30.5 本章小结与实践任务
本章深入拆解 Transformer 三大核心构件。多头注意力依靠多子空间,让模型并行学习多种不同语义关联模式,同时厘清分头、缩放因子、维度变换的工程易错点;位置编码完整梳理正弦 PE、可学习 PE、RoPE 旋转编码的原理与优缺点,RoPE 是当下解码器大模型主流方案;前馈网络 FFN 承担非线性特征加工,对比 ReLU、GELU、SwiGLU 激活演进,FFN 也是模型存储大量事实知识的载体。
很多初学者把 Transformer 全部能力归功注意力,必须纠正认知:注意力、位置编码、FFN 三者协同,才构成完整的建模能力。
实践任务:
- 手写正弦位置编码,输入不同 pos,观察输出向量变化;
- 修改 FFN 代码,把 ReLU 替换为 SwiGLU 实现;
- 思考,如果完全移除位置编码,模型训练会出现什么现象?RoPE 和正弦 PE 的外推差异体现在哪里。