跳到正文

一个 Transformer Block:多头、残差、归一化与 FFN ​

一句话:一个 block 先用多头注意力在位置之间搬运信息,再用 FFN 在每个位置内部做非线性变换,残差和归一化让几十层能稳定训练。注意力负责“看哪里”,FFN 负责“记住什么”,后者通常占了三分之二的参数。

为什么需要 ​

只有一层注意力不够,原因有三个:

  • 一组 Q/K/V 只能表达一种加权关系。同一段话里,有的位置需要看相邻词,有的需要看指代对象,有的需要看句法父节点;一组权重会把它们平均掉。
  • 注意力是线性混合。O = A · V 对 V 是线性的,堆多少层注意力,整体仍然是线性组合,表达不了“先判断条件、再组合特征”这类非线性关系。
  • 层数一深就训不动。梯度经过几十次矩阵乘和 softmax,容易消失或爆炸;没有残差和归一化,深层网络很难收敛。

所以一个 block 要同时解决三件事:多种关系(多头)、非线性变换(FFN)、可训练深度(残差 + 归一化)。

核心机制 ​

一、多头:把表示空间切开 ​

设 d_model = 512,h = 8,每个头的维度是 d_k = 512 / 8 = 64。每个头有自己的一套 W_Q、W_K、W_V,独立计算注意力,得到 8 个 n × 64 的输出;把它们沿特征维拼成 n × 512,再乘一个输出矩阵 W_O 混合。

参数量是 4 · d_model²:Q、K、V、O 各一个 d_model × d_model 矩阵。如果不分头、直接用 512 维做一次注意力,参数量相同,计算量也接近;多头并不是“更贵的注意力”,而是把同一个表示空间切成多个子空间,让不同的头学习不同的关系。

二、FFN:每个位置各自变换 ​

注意力之后接一个两层前馈网络,对每个位置独立作用:

text
FFN(x) = W₂ · activation(W₁ · x + b₁) + b₂
W₁: d_model × d_ff      W₂: d_ff × d_model

原始论文取 d_ff = 4 · d_model,激活函数是 ReLU;现代模型多用 GELU 或 SwiGLU。先升维再降维,中间层提供非线性组合能力。可以把它理解成一组“键值记忆”:第一层判断输入匹配哪些模式,第二层把匹配到的内容写回表示。

参数量是 2 · d_model · d_ff。当 d_ff = 4d 时是 8d²,正好是注意力 4d² 的两倍;加上归一化,FFN 占一个 block 约三分之二的参数。模型的“知识”主要压在这部分参数里,注意力学的是怎么路由和组合。

三、残差:给梯度一条直路 ​

每个子层的输出都加回输入:

text
x = x + Attention(Norm(x))
x = x + FFN(Norm(x))

残差连接提供了一条恒等路径:反向传播时,梯度可以直接穿过加法回到前面的层,不必经过每一层的矩阵乘和 softmax。这让几十层、上百层的网络可以训练。代价是每一层的输出和输入必须在同一个维度,d_model 全程不变。

四、归一化:把数值拉回可控范围 ​

LayerNorm 对每个 token 的特征维做归一化:减均值、除标准差,再乘可学习的缩放 γ、加偏置 β。它和 BatchNorm 的关键区别是不跨 batch 统计,所以不受序列长度、padding 和 batch 组成影响,适合变长文本。

归一化的位置有两种:

结构计算顺序特点
Post-LN(原始论文)Norm(x + Sublayer(x))表达能力好,但深层需要 warmup,容易不稳定
Pre-LN(现代主流)x + Sublayer(Norm(x))梯度更稳,容易训深;输出前再加一个 Norm

Pre-LN 把归一化放在子层之前,残差路径上完全没有归一化,梯度可以沿恒等路径直接回传;代价是深层特征的尺度会累积,所以最后要加一个 final Norm。现在的开源大模型基本都用 Pre-LN 或其变体。

五、RMSNorm 与 SwiGLU ​

两个现代替换值得知道:

  • RMSNorm:只除以均方根,不减均值,也不加偏置。少一次均值计算和一组参数,效果在大模型上通常不差。
  • SwiGLU:把 FFN 改成门控结构 (W₁x) ⊙ sigmoid(W₃x) 再过 W₂,多一个矩阵。为了保持参数量不变,d_ff 通常从 4d 降到约 8d/3。

这些替换不改变 block 的骨架:归一化 → 注意力 → 残差 → 归一化 → FFN → 残差,只是把每个零件换成更省或更稳的版本。

图中两条从输入直接跨到加法的线就是残差路径:它们不经过注意力和 FFN,保证梯度和信息有一条短路径。d_model 在整个 block 里保持不变,所以残差可以直接相加;d_ff 只在 FFN 内部短暂放大,不改变输出维度。

六、参数都花在哪 ​

以一个 d_model = 512、h = 8、d_ff = 2048 的 block 为例:

部分参数量占比
Q/K/V/O4 × 512² = 1,048,576约 33%
FFN(W₁、W₂)2 × 512 × 2048 = 2,097,152约 67%
两个 LayerNorm4 × 512 = 2,048约 0.07%
合计约 315 万100%

原始 Transformer base 是 6 层编码器 + 6 层解码器:编码器层约 12d²,解码器层多一个交叉注意力、约 16d²,12 层合计约 4400 万;共享的词嵌入约 1900 万,总计约 6300 万,与论文报告的 65M 同一量级(差异来自词表与实现细节)。这个账可以帮你判断:微调时改哪里、量化时哪部分最占显存、模型变大主要大在哪。

最小上手 ​

用纯 Python 算一个 block 的参数分布,验证“FFN 占大头”:

python
d, d_ff = 512, 4 * 512
attn = 4 * d * d          # Wq, Wk, Wv, Wo
ffn = 2 * d * d_ff        # W1, W2
norm = 4 * d              # 两个 LayerNorm 的 gamma + beta
total = attn + ffn + norm
print(f"attention={attn:,}  ffn={ffn:,}  norm={norm:,}")
print(f"ffn_share={ffn / total:.1%}  per_layer={total:,}")
print(f"6_layers={6 * total:,}  embedding_32k={32_000 * d:,}")

输出:

text
attention=1,048,576  ffn=2,097,152  norm=2,048
ffn_share=66.6%  per_layer=3,147,776
6_layers=18,886,656  embedding_32k=16,384,000

换任何 d_model,比例都不变:FFN 是 8d²,注意力是 4d²,前者恒为后者的两倍。这也是为什么 LoRA 只调注意力投影时,参数效率高但知识改动有限;要让模型学新事实,FFN 往往更关键。

生产动作 ​

  • 微调先看目标模块。只调 Q/V 投影省参数,但改知识、改风格通常要覆盖 FFN;LoRA 的 rank、alpha 和目标层要一起选。
  • 量化优先看 FFN 和嵌入。它们占参数大头,量化收益最大;但激活离群值常出现在特定通道,要按模型实测,不能只看位宽。
  • 归一化精度不要随便降。LayerNorm/RMSNorm 的统计量对数值敏感,半精度训练时常用 FP32 累积。
  • 残差路径保持干净。不要在残差主干上随意插入缩放、dropout 或可学习门控;改结构要重新验证深层稳定性。
  • 不要用注意力头做解释。头的分工是涌现的,同一个头在不同输入上可能做不同的事;把“第 3 个头负责指代”写进文档,很快就会过时。

边界 ​

  • 残差和归一化不保证一定训得动。学习率、warmup、初始化、数据规模仍然决定成败;Pre-LN 更稳,但 Post-LN 在调好的情况下最终效果未必差。
  • 多头不是越多越好。头太多会让每个头的维度太小,表达能力下降;头数和 d_model 要一起定。
  • FFN 参数多不等于知识全在 FFN。路由、组合、上下文利用仍然依赖注意力;两者是分工,不是替代。
  • 现代组件不是普遍更优。RMSNorm、SwiGLU 的收益依赖模型规模和训练配方,小模型上差异可能被其他因素淹没。
  • block 结构因模型而异。并行注意力/FFN、MoE、不同归一化位置都存在;读配置时要看实际结构,不要按“标准 Transformer”想当然。

讲清楚 ​

一个 block 做两件事:多头注意力把不同位置的信息按相关性搬运过来,FFN 在每个位置内部做升维、非线性和降维,把模式写回表示。残差给梯度一条恒等路径,LayerNorm/RMSNorm 把数值拉回可控范围,两者共同让深层网络可训练。参数量上 FFN 约占三分之二,注意力约占三分之一,归一化几乎不占;所以“模型的知识在哪”和“微调改哪里”要分开判断。

现代模型多用 Pre-LN、RMSNorm 和 SwiGLU,但骨架仍是“归一化 → 子层 → 残差”的堆叠。

适用:Decoder-only 与 Encoder-Decoder 的通用 block;具体组件以模型配置为准 | 初稿 2026-10

把 AI 工程讲到既懂也会用