Appearance
前馈网络与残差连接
注意力负责在位置之间搬运、混合信息;而 前馈网络(FFN) 负责在每个位置内部做非线性加工;残差连接 + 层归一化 则像钢筋和水泥,让几十层能稳稳叠起来不塌。这三件套,构成了 Transformer 每一层。
1. 逐位置前馈网络(Position-wise FFN)
Attention 的输出里,每个位置还是一个 d_model 维向量。FFN 对每个位置独立、相同地套一个两层全连接网络:
[ \text{FFN}(x) = \max(0,; xW_1 + b_1),W_2 + b_2 ]
d_model=512 d_ff=2048 d_model=512
向量 ──Linear(升维)──► 中间向量 ──ReLU──► 中间向量 ──Linear(降维)──► 向量
(先"展开") (非线性) (再"压回")为什么先升维再降维?
- 升到一个更宽的空间(
d_ff通常是d_model的 4 倍,如 512→2048),能存更多「知识」、做更复杂的变换。 - 再压回
d_model,保证维度不变,能顺畅地和残差相加、传给下一层。 - 中间的 ReLU/GELU 提供非线性——没有它,两层线性等于一层线性,白搭。
有研究者把 FFN 看作模型的「键值记忆(key-value memory)」:注意力负责「从哪里取信息」,FFN 负责「对取到的信息做知识加工」。经验上,模型参数的大头都在 FFN,它是模型「记住事实」的重要场所。
「逐位置」是什么意思
句子 [w1, w2, w3] 各自过一个"相同"的 FFN:
FFN(w1) FFN(w2) FFN(w3) ← 三份用同一套权重,互不串门FFN 不 mixing 位置(那是注意力的活),因此天然可并行。
python
class FeedForward(nn.Module):
def __init__(self, d_model, d_ff):
super().__init__()
self.net = nn.Sequential(
nn.Linear(d_model, d_ff),
nn.GELU(), # 现代模型多用 GELU,原始论文用 ReLU
nn.Linear(d_ff, d_model),
)
def forward(self, x): # x: [B, T, d_model]
return self.net(x) # 输出同形状 [B, T, d_model]2. 残差连接(Residual / Skip Connection)
每个子层(注意力、FFN)的输出,都会加上它的输入:
输入 x ───────────────┐ ← 一条"捷径"直接连到后面
│ │
[子层: Attention/FFN] │ 子层只需学"增量" f(x)
│ │
└────► f(x) ──► ( + ) ──► x + f(x)为什么不可或缺
| 没有残差 | 有残差 |
|---|---|
| 梯度要穿过每一层,深网络易消失/爆炸 | 梯度可走「捷径」直达底层,深层可训 |
| 每层必须彻底改写表示,信息易丢失 | 每层只需学「在原有基础上补充什么」 |
| 堆到十几层就难收敛 | 轻松堆到几十上百层 |
残差让每个子层从「重写全文」变成「在原稿上批注」,训练稳定得多,这也是 Transformer 能加深的前提。
3. 层归一化(LayerNorm)
把每个位置的向量做标准化,稳定数值分布、加速收敛:
[ \text{LayerNorm}(x) = \gamma \cdot \frac{x - \mu}{\sqrt{\sigma^2 + \epsilon}} + \beta ]
- (\mu, \sigma^2):该位置向量内部各维度的均值与方差
- (\gamma, \beta):可学习的缩放与偏移
LayerNorm vs BatchNorm
BatchNorm: 跨"样本"维统计 → 依赖 batch,对变长序列/推理不友好
LayerNorm: 只在"特征"维内部统计 → 与 batch 大小无关,天然适合 NLP 序列所以 Transformer 全线使用 LayerNorm。
4. Pre-Norm vs Post-Norm
规范化和残差相加的先后顺序,有两种摆法:
Post-Norm(原始论文): Pre-Norm(现代主流):
x = LayerNorm(x + Sublayer(x)) x = x + Sublayer(LayerNorm(x))
│ │
深层训练不太稳定 训练更稳,可堆更深,大模型几乎都用它| Post-Norm | Pre-Norm | |
|---|---|---|
| 出处 | 原始《Attention Is All You Need》 | GPT-2 及之后几乎所有大模型 |
| 训练稳定性 | 层数深时需小心(配合 warmup) | 更稳,易收敛 |
| 极限质量 | 调得好略高 | 基本持平,工程上更省心 |
本教程的 NanoGPT 实战会采用 Pre-Norm。
5. 把三件套拼成一个「Block」
现在一个标准的 Transformer 层就长这样:
输入 x
│
┌──────┴───────┐
│ 多头注意力 │
└──────┬───────┘
Dropout
│
( + ) ◄── 残差: 加回输入 x
│
LayerNorm
│
┌──────┴───────┐
│ FFN │
└──────┬───────┘
Dropout
│
( + ) ◄── 残差: 再加回
│
LayerNorm
│
输出 → 送往下一个 Blockpython
class TransformerBlock(nn.Module):
def __init__(self, d_model, num_heads, d_ff, dropout=0.1):
super().__init__()
self.attn = MultiHeadAttention(d_model, num_heads) # 见上一页
self.ffn = FeedForward(d_model, d_ff)
self.norm1 = nn.LayerNorm(d_model)
self.norm2 = nn.LayerNorm(d_model)
self.drop = nn.Dropout(dropout)
def forward(self, x, mask=None):
# Pre-Norm 风格: 先 norm 再进子层, 子层输出走残差
x = x + self.drop(self.attn(self.norm1(x), self.norm1(x), self.norm1(x), mask))
x = x + self.drop(self.ffn(self.norm2(x)))
return x- Dropout:训练时随机丢弃部分神经元,防过拟合;推理时关闭。
- 把这样的 Block 堆 N 层(原始论文 N=6),就得到 Encoder 或 Decoder 的骨干。
6. 分工总览
┌───────────┬──────────────────┬───────────────────┐
│ 组件 │ 干什么 │ 一句话记忆 │
├───────────┼──────────────────┼───────────────────┤
│ 注意力 │ 跨位置混合信息 │ "谁该看谁" │
│ FFN │ 位置内非线性加工 │ "把看到的想明白" │
│ 残差连接 │ 稳住深层训练 │ "在原稿上批注" │
│ LayerNorm │ 稳定数值分布 │ "每层先归零再出发" │
│ Dropout │ 防过拟合 │ "偶尔随机偷懒" │
└───────────┴──────────────────┴───────────────────┘小结
- FFN:逐位置的两层网络(升维→激活→降维),提供非线性与「知识加工」。
- 残差:
x + f(x),让梯度直达、深层可训,是加深的基石。 - LayerNorm:在特征维归一化,比 BatchNorm 更适合变长序列。
- Pre-Norm 更稳,是现代大模型默认选择。
- 三件套组成一个 Block,堆 N 层即成骨干。
下一步
所有零件都认全了。下一页把它们组装成完整的 Transformer,看看整台机器如何协同 → 完整架构。