主题
一个 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/O | 4 × 512² = 1,048,576 | 约 33% |
| FFN(W₁、W₂) | 2 × 512 × 2048 = 2,097,152 | 约 67% |
| 两个 LayerNorm | 4 × 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