Transformer 解剖:从 Attention 到推理系统

第 5 章 Transformer Block:FFN、LayerNorm、Residual 的角色

作者 杨艺韬 · 6,400 字 · 发布于 · 更新于

第 2、3、4 章我们把 Multi-Head Self-Attention 拆得很透了。但单靠 Attention 没法搭起一个 Transformer——Llama 70B 的 80 层、GPT-3 的 96 层,每一层都不是纯 attention,而是一个完整的 Transformer Block:Attention + 前馈网络(FFN)+ 残差连接(Residual)+ 层归一化(LayerNorm)。

为什么是这四件套?为什么少了任何一个 Transformer 都训不动?这一章我们把 Block 的每个零件单独打开看,再合在一起看它们是怎么互锁的。读完这章你能:

  • 在白板上画出一个完整的 Transformer Block 的数据流(含张量形状);
  • 解释为什么 FFN 必须存在、为什么是 4×dmodeld_{\text{model}}、为什么 ReLU 后来被 SwiGLU 替代;
  • 解释 Pre-LN 和 Post-LN 的差别,以及为什么今天的大模型几乎一边倒选 Pre-LN;
  • 解释 RMSNorm 为什么取代了 LayerNorm;
  • 估算一个模型的参数量、计算量在 Attention 和 FFN 之间是怎么分配的。

5.1 为什么 Attention 不够:缺非线性

回想一下 Self-Attention 的输出是什么:

outputi=∑j=1TAijvj=∑j=1TAij(xjWV)\text{output}_i = \sum_{j=1}^{T} A_{ij} v_j = \sum_{j=1}^{T} A_{ij} (x_j W_V)

每个输出位置是 value 向量的加权和。其中 vj=xjWVv_j = x_j W_V 是输入的线性投影,权重 AijA_{ij} 来自 softmax,但加权求和本身仍然是输入的线性组合——把 AijA_{ij} 视作系数,整个 attention 输出就是 VV 的线性组合。

唯一的非线性来自 softmax。但 softmax 只作用在 attention 权重上,不作用在 value 本身的特征空间里。结果就是:经过若干层 Self-Attention 之后,输出仍然停留在「输入向量空间」附近的某种线性变换——这远远不够用来表达自然语言里的复杂结构。

这是一个深度学习里很基础的原则:纯线性的网络(无论多深)等价于一层线性变换。要让深度网络真正「深」起来——也就是每一层都能引入新的表达能力——必须每一层都加上一个非线性变换。

Transformer Block 里这件事由 Feed-Forward Network(前馈网络,FFN)来做。

5.2 FFN:每个 token 独立的非线性变换

FFN 的设计简单到出奇:

FFN(x)=Activation(xW1+b1)W2+b2\text{FFN}(x) = \text{Activation}(x W_1 + b_1) W_2 + b_2

也就是一个两层全连接 + 一个非线性激活函数。W1∈Rdmodel×dffnW_1 \in \mathbb{R}^{d_{\text{model}} \times d_{\text{ffn}}} 把维度从 dmodeld_{\text{model}} 升到 dffnd_{\text{ffn}}(典型 4dmodel4 d_{\text{model}}),激活后再用 W2∈Rdffn×dmodelW_2 \in \mathbb{R}^{d_{\text{ffn}} \times d_{\text{model}}} 降回 dmodeld_{\text{model}}。

关键点:FFN 是逐 token 独立的。每个位置的输入向量分别通过 FFN,不同位置之间没有交互。这个性质是 Self-Attention 的镜像——Self-Attention 让位置之间交互,FFN 让特征在每个位置上做非线性变换,两者互补。

flowchart TB
  X["输入 (T, d_model)"] --> SA[Self-Attention<br/>位置之间交互]
  SA --> FFN[FFN<br/>每个位置独立做<br/>非线性变换]
  FFN --> OUT["输出 (T, d_model)"]
  SUB1[所有位置 互相打通] -.对应.-> SA
  SUB2[每个位置 单独加工] -.对应.-> FFN

可以把它理解成「人开会」的隐喻:Self-Attention 是大家聚在一起讨论(位置间信息交换),FFN 是会后每个人回去自己消化加工(位置内非线性变换)。Transformer Block 是「讨论 → 消化」的循环——每一层都做一次。

FFN 的尺寸为什么是 4×

原始论文里 dffn=4dmodeld_{\text{ffn}} = 4 d_{\text{model}},这成了标准 FFN 的事实默认值。改用 SwiGLU 的现代模型则从 83dmodel\frac{8}{3} d_{\text{model}} 起步——那是为了让三个矩阵的总参数量和 4d4d 的两矩阵 FFN 打平,下文会讲。

为什么是 4?这没有一个严格证明,但有几条经验观察:

第一,FFN 是 Transformer 的「记忆容量」。研究表明,FFN 的权重很大程度上扮演着「key-value 字典」的角色——Geva et al.(2021)的论文 Transformer Feed-Forward Layers Are Key-Value Memories 论证了:FFN 第一层的权重充当一组 key(与输入做内积得到每条「记忆」的激活系数),第二层的权重充当对应的 value,按系数加权取回。dffnd_{\text{ffn}} 越大,能存的 key-value 对越多,模型记忆容量越大。

第二,4d4d 大致是表达力和参数量之间的甜点。更窄则 FFN 容量受限,更宽则参数量和计算量线性上涨、收益递减。原论文没有论证 4d4d 为何最优,它更像一个被后续模型一路沿用下来的经验默认值。

第三,FFN 占 Transformer 大约 2/3 的参数。一个 Transformer Block 的参数量(不含归一化和 bias):

  • Attention:4dmodel24 d_{\text{model}}^2(WQ,WK,WV,WOW_Q, W_K, W_V, W_O 各一个 d×dd \times d)
  • FFN:dmodel⋅4dmodel⋅2=8dmodel2d_{\text{model}} \cdot 4 d_{\text{model}} \cdot 2 = 8 d_{\text{model}}^2(W1,W2W_1, W_2 各一个 d×4dd \times 4d)

合起来 12d212 d^2,FFN 占 8/12=2/38/12 = 2/3。也就是说Transformer 大部分参数其实在 FFN 里——这个直觉对很多读者是反常识的,因为 attention 名声更大。

激活函数:从 ReLU 到 GELU 到 SwiGLU

原始 Transformer 用的激活函数是 ReLU:ReLU(x)=max⁡(0,x)\text{ReLU}(x) = \max(0, x)。简单粗暴有效。

GPT(初代)和 BERT 改用了 GELU(Gaussian Error Linear Unit):

GELU(x)=x⋅Φ(x)\text{GELU}(x) = x \cdot \Phi(x)

其中 Φ(x)\Phi(x) 是标准正态分布的累积分布函数。GELU 是 ReLU 的「平滑版」——在 0 附近不再有硬切,而是平滑地过渡。BERT、GPT-2/3 都用 GELU。

到了 PaLM、Llama 1 这一代,激活函数又一次升级到 SwiGLU(Swish-Gated Linear Unit),由 Shazeer 在论文 GLU Variants Improve Transformer(2020)提出。SwiGLU 是「门控版 FFN」:

SwiGLU(x,W,V,W2)=(Swish(xW)⊙xV)W2\text{SwiGLU}(x, W, V, W_2) = (\text{Swish}(xW) \odot xV) W_2

其中 Swish(x)=x⋅σ(x)\text{Swish}(x) = x \cdot \sigma(x)(sigmoid 加权),⊙\odot 是逐元素相乘。和原始 FFN 比,SwiGLU 多了一条并行的线性分支:Swish(xW)\text{Swish}(xW) 作为门,逐元素调制线性分支 xVxV 的输出——本质是「让模型学会哪个特征该开、哪个该关」。在 transformers 的 Llama 实现里,WW、VV、W2W_2 分别对应 gate_proj、up_proj、down_proj(transformers-4815a0a/src/transformers/models/llama/modeling_llama.py:175)。

flowchart LR
  X["输入 x"] --> W["× W (门控分支 gate_proj)"]
  X --> V["× V (线性分支 up_proj)"]
  W --> SW["Swish 激活"]
  V --> GATE
  SW --> GATE["逐元素相乘 ⊙"]
  GATE --> W2["× W_2 (降回 d_model)"]
  W2 --> OUT[输出]

SwiGLU 多了一个矩阵 VV,参数量大约是原始 FFN 的 1.5 倍。为了控制总参数量不爆炸,实际工程把 dffnd_{\text{ffn}} 从 4d4d 改成 83d\frac{8}{3} d——参数量大致与原 FFN 相当但表达力更强。这就是 Llama 系列配置里 intermediate_size 以 83⋅hidden_size\frac{8}{3} \cdot \text{hidden\_size} 起步的来历(Llama 1/2 的 7B/13B 再向上对齐到 256 的倍数;Llama 2 70B 和 Llama 3 还会再乘一个系数,见 5.7 节细节六)。

为什么 SwiGLU 比 GELU 好? 主要是经验:门控让 FFN 能「对每个特征独立打开/关闭」。Shazeer 那篇 GLU Variants Improve Transformer 在 T5 的预训练与下游任务上做了对照,相同参数量和计算量下,SwiGLU/GEGLU 这一族的预训练困惑度低于 ReLU / GELU,下游任务也多数更好(幅度不大);Llama 1 报告直接采用了这个结论。今天主流开源大模型大多切到了 SwiGLU(Gemma 用的是同族的 GeGLU)。

主流模型激活函数对照:

模型 激活 FFN 类型 dffnd_{\text{ffn}}
原始 Transformer ReLU 标准 FFN 4d4d
BERT / GPT-2/3 GELU 标准 FFN 4d4d
Llama 1/2 (7B/13B) SwiGLU GLU 变种 83d\frac{8}{3} d(4096 → 11008)
Llama 2/3 (70B) SwiGLU GLU 变种 3.5d3.5 d(8192 → 28672)
Mistral 7B / Qwen2 SwiGLU GLU 变种 3.5d3.5 d / 5.4d5.4 d(transformers-4815a0a/src/transformers/models/qwen2/configuration_qwen2.py:63 的缺省是 4096 → 22016)
PaLM SwiGLU GLU 变种 4d4d(未按 83\frac{8}{3} 折算,论文接受了三矩阵的额外开销)

直觉总结:FFN 是「每个 token 单独经过一个深度小、参数大的非线性层」,给整个模型提供主要的非线性表达力和大部分参数容量。激活函数从 ReLU 到 SwiGLU 是工程上「在不改变结构的前提下挤出更多表达力」的演化。

5.3 Residual:让深度可训

设想一下:堆叠 80 层 Transformer,每一层做 Attention + FFN。从第 80 层往前,loss 对第 1 层的梯度要穿过 80 次复合运算——每次都包含矩阵乘法和非线性。链式法则里这相当于 80 个雅可比矩阵相乘,方差累积、梯度消失或爆炸的风险极高。

这是深度神经网络从 2010 年代初一直被困扰的问题。直到 2015 年 ResNet(He et al.)提出残差连接(residual connection / skip connection):

y=x+F(x)y = x + F(x)

每一层的输入 xx 直接「跳过」函数 FF,加到输出上。这意味着梯度可以从 yy 直接传到 xx,完全绕过 FF:

∂L∂x=∂L∂y⋅(1+∂F∂x)\frac{\partial \mathcal{L}}{\partial x} = \frac{\partial \mathcal{L}}{\partial y} \cdot \left(1 + \frac{\partial F}{\partial x}\right)

那个「+1」就是关键——它保证梯度有一条系数恒为 1 的快速通道,无论 FF 的雅可比是多少,恒等那一路的梯度都能原样传到底层。

Transformer 把这个想法用在了每个子层(Attention 子层和 FFN 子层)上(这里先按今天主流的 Pre-LN 写法,原始论文的 LayerNorm 放法见 5.4 节):

y=x+SubLayer(LN(x))y = x + \text{SubLayer}(\text{LN}(x))

具体到 Block:

x_in
 ├─→ LN → MHA → out1
 │                |
 ├────────────────+→ x_mid
 │
 ├─→ LN → FFN → out2
 │                |
 ├────────────────+→ x_out

每个子层(Attention、FFN)都有一条 residual 旁路。这样从输入到输出至少存在一条「线性恒等」的路径——梯度可以无损地传到任何一层。

flowchart TB
  XIN[x_in] --> LN1[LayerNorm]
  XIN --> ADD1["+ residual"]
  LN1 --> MHA[Multi-Head<br/>Attention]
  MHA --> ADD1
  ADD1 --> XMID[x_mid]
  XMID --> LN2[LayerNorm]
  XMID --> ADD2["+ residual"]
  LN2 --> FFN[FFN]
  FFN --> ADD2
  ADD2 --> XOUT[x_out]

Residual 的几何理解

另一个非常有用的视角:把每一层的 F(x)F(x) 看成是「对 xx 的微调」。Residual 让模型默认「保留 xx 的大部分信息,在它上面做一点微调」。深度网络的每一层不需要重新发明轮子——它继承上一层的状态,只在必要的方向上加工。

这个性质和 Transformer 训练动力学非常契合。当 80 层堆叠时,每层只需要做「小改动」,最终累计成大变化。如果没有 residual,每层都要从零构建一个完整的输出——80 次完整构建几乎不可能稳定收敛。

实践中的一个观察:当你在训练一个 Transformer 时把 residual 砍掉(强制 y=F(LN(x))y = F(\text{LN}(x))),层数一多往往就训不起来。residual 不是锦上添花,是必要条件。

5.4 LayerNorm:稳定每个位置的输入分布

加入 residual 之后,新问题来了:每一层 residual 把 x+F(x)x + F(x) 输出,下一层的输入就是「原始输入 + 多次累加的修改量」。如果不约束,这个输出的方差会逐层放大(每加一次 F(x)F(x) 都引入新方差),到第 80 层会大到难以控制。

这就需要归一化(normalization)来约束每一层的输入分布。

LayerNorm 的定义

LayerNorm(Ba et al., 2016)是为 RNN/序列模型设计的归一化方法:对每个 token 的特征向量,沿着特征维做归一化:

LN(x)=γ⋅x−μσ2+ϵ+β\text{LN}(x) = \gamma \cdot \frac{x - \mu}{\sqrt{\sigma^2 + \epsilon}} + \beta

其中:

  • μ=1d∑i=1dxi\mu = \frac{1}{d} \sum_{i=1}^{d} x_i 是特征的均值
  • σ2=1d∑i=1d(xi−μ)2\sigma^2 = \frac{1}{d} \sum_{i=1}^{d} (x_i - \mu)^2 是特征的方差
  • γ,β∈Rd\gamma, \beta \in \mathbb{R}^d 是可学习的缩放和偏移参数
  • ϵ\epsilon 是数值稳定项(典型 10−510^{-5})

注意 LayerNorm 是对每个 token 单独做的——位置 ii 的归一化只用位置 ii 自己的特征,与其他位置无关。这是它和 BatchNorm 最大的区别:

特性 BatchNorm LayerNorm
归一化沿哪个维度 batch 维 特征维
是否跨样本 是 否
训练/推理是否一致 不一致(推理用滑动平均) 一致
序列长度变化 敏感 不敏感
对 batch_size 敏感 是(小 batch 不稳定) 否

LayerNorm 的「不跨样本」和「不依赖 batch_size」是它在序列模型里大获成功的关键。语言任务里 batch 之间序列长度可以差很多,batch_size 经常被显存约束(特别是大模型),BatchNorm 的统计量会非常不稳定。

Pre-LN vs Post-LN:一个分水岭

原始 Transformer 论文里 LayerNorm 放在 residual 之后——叫 Post-LN:

y=LN(x+SubLayer(x))y = \text{LN}(x + \text{SubLayer}(x))

但很快人们发现,Post-LN 训练 Transformer 很不稳定,特别是在层数多、模型大的情况下。GPT-2 把 LayerNorm 挪到了每个子层的输入端、residual 分支之内(论文只说仿照 pre-activation ResNet,没有展开原因),这就是 Pre-LN:

y=x+SubLayer(LN(x))y = x + \text{SubLayer}(\text{LN}(x))
flowchart TB
  subgraph postln ["Post-LN(原始论文,不稳定)"]
    P_X[x] --> P_SUB[SubLayer]
    P_X --> P_ADD["+"]
    P_SUB --> P_ADD
    P_ADD --> P_LN[LayerNorm]
    P_LN --> P_OUT[y]
  end
  subgraph preln ["Pre-LN(GPT-2 之后主流)"]
    PR_X[x] --> PR_LN[LayerNorm]
    PR_X --> PR_ADD["+"]
    PR_LN --> PR_SUB[SubLayer]
    PR_SUB --> PR_ADD
    PR_ADD --> PR_OUT[y]
  end

为什么 Pre-LN 更稳定?关键在 residual 的「恒等通道」。Pre-LN 的残差连接把 xx 直接加到 SubLayer(LN(x))\text{SubLayer}(\text{LN}(x)) 上,不经过任何归一化——梯度可以原路返回,链式法则上是 1+∂F∂x1 + \frac{\partial F}{\partial x}。

Post-LN 把 LayerNorm 放在 residual 之后,残差连接中间多了一次归一化。LayerNorm 的雅可比矩阵是非平凡的(涉及标准差的倒数等),把它接到 residual 路径上,梯度的「恒等通道」被破坏了——恒等那一路的梯度不再能原样回传,会随层数衰减。

Xiong et al.(2020)在论文 On Layer Normalization in the Transformer Architecture 里给了数学分析:在初始化时刻,Post-LN 靠近输出那几层的参数梯度尺度是 O(dln⁡d)O(d\sqrt{\ln d}),不随层数 LL 变小,只能靠 learning-rate warm-up 压住训练初期的更新;而 Pre-LN 的同一个上界带一个 1/L1/\sqrt{L} 因子,层数越深梯度反而越温和,所以 Pre-LN 可以去掉 warm-up、直接用较大的学习率。

实践中:

  • Pre-LN:今天的 GPT、Llama、Mistral、DeepSeek 几乎全部用 Pre-LN。优点是训练稳定,可以堆很深。
  • Post-LN:BERT 仍用 Post-LN,BERT-base(12 层)和 BERT-large(24 层)都要靠学习率 warmup(论文是前 10,000 步线性升温)才能稳定训练。
  • Sandwich-LN:少数模型在子层前后都加归一化(CogView 的 Sandwich-LN;Gemma 2 也在子层输出上再加一层 RMSNorm,见 transformers-4815a0a/src/transformers/models/gemma2/modeling_gemma2.py:326、:332)——多一次归一化开销,换更稳的训练。
  • DeepNorm:微软提出的 Post-LN 改进版,让 Post-LN 也能训上千层。技术上可行,但实际工业基本用 Pre-LN。

总结:你在写一个新 Transformer,无论模型多大,默认选 Pre-LN。

RMSNorm:LayerNorm 的简化版

2020 年之后,大模型逐渐把 LayerNorm 换成更简化的 RMSNorm(Root Mean Square Normalization,Zhang & Sennrich 2019 提出):

RMSNorm(x)=γ⋅x1d∑i=1dxi2+ϵ\text{RMSNorm}(x) = \gamma \cdot \frac{x}{\sqrt{\frac{1}{d}\sum_{i=1}^{d} x_i^2 + \epsilon}}

和 LayerNorm 比,RMSNorm 去掉了均值的减法——只用 RMS(root mean square,根均方)做缩放,没有偏移项 β\beta。

直觉:RMSNorm 论文的假设是,LayerNorm 起作用主要靠「重新缩放」,「减均值」(重新居中)可以省掉。少一次求均值和减法、少一个 β\beta 参数,每个归一化层都省一点开销。

实验:RMSNorm 的原论文 Root Mean Square Layer Normalization(Zhang & Sennrich 2019)在多种架构和任务上做了对照,结论是质量与 LayerNorm 相当,而运行时间减少 7%~64%——区间这么宽是因为收益取决于模型结构,Norm 占总计算比重越大、省得越多。注意这个数字来自 RMSNorm 论文本身,不是 Llama 论文;Llama 论文只是沿用了 RMSNorm,没有单独为它做消融。

主流大模型几乎全部已切到 RMSNorm:Llama 1/2/3、Mistral、Qwen、DeepSeek、Yi——RMSNorm 是新一代标配。

归一化变种 公式核心 使用模型
BatchNorm 沿 batch 维归一化 不用于序列模型
LayerNorm 沿特征维归一化(含均值减法) 原始 Transformer / BERT / GPT-2/3
RMSNorm 沿特征维归一化(只 RMS 缩放) Llama / Mistral / Qwen / DeepSeek
GroupNorm 沿特征分组归一化 CV 模型常用,NLP 主干里少见

5.5 完整 Block 的数据流

把 5.2、5.3、5.4 节合在一起,今天主流 Transformer Block 长这个样子(Pre-LN + RMSNorm + GQA + SwiGLU):

flowchart TB
  XIN["x_in (T, d)"] --> RES1["+ residual"]
  XIN --> RMS1[RMSNorm]
  RMS1 --> RMSout1[normalized x]
  RMSout1 --> ROPE[RoPE 旋转 Q,K]
  ROPE --> MHA[Multi-Head<br/>Self-Attention<br/>GQA]
  MHA --> RES1
  RES1 --> XMID["x_mid (T, d)"]
  XMID --> RES2["+ residual"]
  XMID --> RMS2[RMSNorm]
  RMS2 --> SWI[SwiGLU FFN]
  SWI --> RES2
  RES2 --> XOUT["x_out (T, d)"]

形式化的数据流:

1. x ← x_in                              shape (T, d)
2. h ← x + Attention(RMSNorm(x))         shape (T, d)
3. y ← h + FFN(RMSNorm(h))               shape (T, d)
4. x_out ← y                             shape (T, d)

这就是 Llama 一个 Block 的全部内容。整个模型:

embedding → [Block × N] → final RMSNorm → LM head

每个 Block 的输入输出形状完全一致 (T,dmodel)(T, d_{\text{model}})——这是 Transformer 能堆叠任意深度的关键性质。Llama-3 70B 的 80 层就是 80 个这样的 Block 串起来的。

5.6 参数量与计算量分解

来一笔账。设 d=dmodeld = d_{\text{model}}、头数为 hh、dffn=4dd_{\text{ffn}} = 4d(标准 FFN),单 Block 的参数:

子模块 参数量 占比
MHA: WQ,WK,WV,WOW_Q, W_K, W_V, W_O 4d24d^2 33%
FFN: W1,W2W_1, W_2(标准) 8d28d^2 67%
RMSNorm × 2(仅 γ\gamma) 2d2d <1%
合计 ≈12d2\approx 12d^2 100%

如果是 SwiGLU(dffn=83dd_{\text{ffn}} = \frac{8}{3}d),FFN 的参数:

SwiGLU FFN=d⋅83d⋅3=8d2\text{SwiGLU FFN} = d \cdot \frac{8}{3}d \cdot 3 = 8d^2

(三个矩阵:WW、VV、W2W_2)。所以总参数仍然是 ≈12d2\approx 12d^2——SwiGLU 在保持参数量不变的前提下提升了表达力,这就是 5.2 节我们说「Llama 的 intermediate_size 是 83\frac{8}{3} 倍」的工程合理性。

对应的整模型参数:LL 层 Block,每层 12d212d^2,再加 embedding V⋅dV \cdot d(VV 是词表大小)和 LM head(如果不共享权重,又是一个 V⋅dV \cdot d)。Llama-3 70B 的实际参数:

  • d=8192d = 8192, h=64h = 64(GQA: hkv=8h_{kv} = 8), L=80L = 80, V=128256V = 128256
  • L⋅12d2≈80⋅12⋅81922≈64.4L \cdot 12d^2 \approx 80 \cdot 12 \cdot 8192^2 \approx 64.4 G
  • embedding(不共享): 128256⋅8192≈1.05128256 \cdot 8192 \approx 1.05 G
  • LM head(不共享): 128256⋅8192≈1.05128256 \cdot 8192 \approx 1.05 G
  • 两处校正(方向相反,正好差不多抵消):
    • GQA 让 K/V 投影缩水:WK,WVW_K, W_V 不再是 d×dd \times d 而是 d×(hkvdk)=8192×1024d \times (h_{kv} d_k) = 8192 \times 1024,每层比 4d24d^2 省 0.117 G,80 层省约 9.4 G;
    • FFN 比 83d\frac{8}{3}d 更宽:Llama-3 70B 的 intermediate_size 是 28672(3.5d3.5d)而不是 83d≈21845\frac{8}{3}d \approx 21845——transformers-4815a0a/src/transformers/models/llama/convert_llama_weights_to_hf.py:170 的 compute_intermediate_size 按 70B params.json 里的 ffn_dim_multiplier=1.3、对齐到 4096 算出这个值,于是每层 FFN 从 8d28d^2 涨到 3×8192×286723 \times 8192 \times 28672,80 层多出约 13.4 G。
  • 合计 64.4−9.4+13.4+1.05+1.05≈70.564.4 - 9.4 + 13.4 + 1.05 + 1.05 \approx 70.5 G

按 Llama-3 70B 的配置逐项精确求和(含各层 RMSNorm 的 γ\gamma)是 70,553,706,496,约 70.6B——和我们的估算几乎一致。这里按「LM head 不共享」来算是对的:Llama 1 / 2 / 3 的 LM head 一直都是独立矩阵,并非「Llama 3 扩词表后才放弃共享」(见 §5.8 的说明)。这个估算量级正确,足以让你看到模型卡片就估出参数量。

计算量 / FLOPs 分解(前向,单个 token,标准 FFN):

子模块 FLOPs
QKV 投影 ∼6d2\sim 6 d^2
Attention(QKTQK^T 与 AVAV 各一次) ∼4Td\sim 4 T d
输出投影 ∼2d2\sim 2 d^2
FFN ∼16d2\sim 16 d^2
合计 ∼24d2+4Td\sim 24 d^2 + 4Td

注意 24d224 d^2 正好是「每层参数量 12d212d^2 的两倍」——这就是那条常用的经验公式「前向 FLOPs ≈ 2 × 参数量」。上下文短时(T≪dT \ll d)FFN 主导计算量;当 TT 越过 6d6d,attention 那一项开始反超(摊到整段序列上,它就是那个 T2dT^2 d 的平方项)。实际工程中 TT 在 4K~128K 之间变化,计算瓶颈会从 FFN 慢慢转向 attention——这是第 13 章「长上下文之战」的工程动因。

计算-访存比(重要,预示推理工程):

  • FFN:每一步都要把权重从 HBM 读出来做矩阵乘。prefill 或大 batch 时一份权重被很多 token 复用,是计算密集(compute-bound);小 batch 解码时每份权重只服务几个 token,瓶颈变成读权重的带宽
  • Attention:解码时每一步都要读整段 KV Cache、做 softmax——访存密集(memory-bound)

这两种瓶颈对应不同的优化策略。FFN 用低比特量化(INT4 / FP8)把权重压小,让 HBM 读得快——第 16 章。Attention 用 Flash Attention 把 KV 访问局部化,让 SRAM 替 HBM 干活——第 18 章。这两条优化在第六部分会展开。

5.7 一些容易踩的细节

细节一:LayerNorm/RMSNorm 在哪里?

确认两遍:Pre-LN 下,LayerNorm 在子层(Attention 或 FFN)之前,residual 连接绕过 LayerNorm:

# 正确(Pre-LN)
h = x + attention(rmsnorm(x))     # 注意 residual 加的是 x,不是 rmsnorm(x)
y = h + ffn(rmsnorm(h))           # 同上

很多新手写错成 h = rmsnorm(x) + attention(rmsnorm(x))——把 residual 也加了归一化值,破坏了梯度直达通道。

细节二:最后一层归一化

整个模型最后还有一个 final RMSNorm(在 LM head 之前)。这个常被忽略,但它对 logits 的稳定有帮助。Llama 的 model.norm 就是这个最后的 RMSNorm(transformers-4815a0a/src/transformers/models/llama/modeling_llama.py:357)。

细节三:bias 还是不要 bias

LayerNorm 通常带 γ\gamma 和 β\beta 两个可学习参数;RMSNorm 只带 γ\gamma。FFN 的 W1,W2W_1, W_2 在原始 Transformer 里有 bias,但 Llama / Mistral 等现代模型把 bias 全部去掉——参数减少、训练略加速、对质量几乎无影响。

细节四:QKV 投影是否带 bias

同样,BERT / GPT-2 这一代的 WQ,WK,WV,WOW_Q, W_K, W_V, W_O 都带 bias,现代模型几乎都去掉了:transformers-4815a0a/src/transformers/models/llama/configuration_llama.py:81 的 attention_bias 与 :83 的 mlp_bias 缺省都是 False。少数例外是 Qwen2——transformers-4815a0a/src/transformers/models/qwen2/modeling_qwen2.py:189 只给 Q/K/V 三个投影留了 bias,输出投影和 MLP 仍然不带。

细节五:embedding 维度和 head 维度

经典设计里每个头维度 dk=dmodel/hd_k = d_{\text{model}} / h,所以 dmodeld_{\text{model}} 要能被 hh 整除;也有模型单独指定 head_dim,这时 h⋅dkh \cdot d_k 不必等于 dmodeld_{\text{model}}(如 transformers-4815a0a/src/transformers/models/gemma/configuration_gemma.py:65–:70 的缺省:hidden 3072、16 头、head_dim 256)。dmodeld_{\text{model}} 最好也能被某些硬件友好的数(如 128)整除以让 GPU 矩阵乘高效。

细节六:FFN 的 dffnd_{\text{ffn}} 实际值

SwiGLU 配 83d\frac{8}{3}d,但实际上工程会向上对齐到某个 2 的幂的倍数让 GPU 友好(Llama 1/2 的 7B/13B 是 256)。Llama 7B 的 d=4096d = 4096,理论 dffn=4096⋅8/3≈10923d_{\text{ffn}} = 4096 \cdot 8/3 \approx 10923,实际取 11008(256 的整数倍)。Llama 2 70B 和 Llama 3 还会再乘一个额外系数:transformers-4815a0a/src/transformers/models/llama/convert_llama_weights_to_hf.py:170 的 compute_intermediate_size(n, ffn_dim_multiplier, multiple_of) 从 Meta 原始 params.json 读这两个参数(同文件 :362–:363),70B 的取值是 ffn_dim_multiplier=1.3、对齐到 4096——代入 ⌈1.3×⌊8×8192/3⌋⌉4096=28672\lceil 1.3 \times \lfloor 8 \times 8192/3 \rfloor \rceil_{4096} = 28672(3.5d3.5d)。所以「83d\frac{8}{3}d」是起点而不是铁律。

5.8 把整个 Transformer 模型串起来

到这里,整个模型可以一目了然:

flowchart TB
  TOK[输入 token ids] --> EMB["Token Embedding"]
  EMB --> X0[x_0]
  X0 --> B1[Block 1]
  B1 --> B2[Block 2]
  B2 --> BD[...]
  BD --> BL[Block L]
  BL --> XL[x_L]
  XL --> FN[Final RMSNorm]
  FN --> HEAD[LM Head]
  HEAD --> LOGITS["logits 形状<br/>(T, V)"]
  LOGITS --> SM[softmax]
  SM --> P["词表上的<br/>下一 token<br/>概率分布"]

每个 Block 的内部就是 5.5 节的那套数据流。整个模型就是「embedding → N 层 Block → final RMSNorm → LM head → logits」。LM head 把 dmodeld_{\text{model}} 维向量投到词表大小 VV 维(常见从 3.2 万(Llama 2)到 25.6 万(Gemma)不等),softmax 之后是下一 token 的概率分布。

LM head 共享权重(weight tying)是常见优化:把 LM head 的权重矩阵设为 token embedding 的转置,可以省一份 V⋅dV \cdot d 的参数(模型越小占比越大:GPT-2 small 里约占三成,Llama-3 70B 里只占约 1.5%)。不是所有模型都共享,而且方向常被记反:GPT-2 共享,Llama 1 / 2 / 3 都不共享(原始 checkpoint 里有独立的 output.weight),只有 Llama 3.2 的小模型(1B / 3B)才启用共享(转换脚本里 tie_word_embeddings=llama_version == "3.2",transformers-4815a0a/src/transformers/models/llama/convert_llama_weights_to_hf.py:400)——在那个参数量级上,省下一份 V⋅dV \cdot d 的收益才够大。

本章小结

  1. Self-Attention 是线性混合,必须配 FFN 引入非线性——FFN 是「逐 token 独立的两层 MLP」,提供模型主要的非线性表达力和大约 2/3 的参数容量。
  2. 激活函数演化:ReLU → GELU → SwiGLU。SwiGLU 用门控分支增强表达力,配合 dffn=83dd_{\text{ffn}} = \frac{8}{3}d 保持参数量。
  3. Residual 是深度网络可训的必要条件——残差连接给梯度提供恒等通道,让 80 层堆叠也能稳定收敛。
  4. LayerNorm 把每层每个 token 的特征分布约束住——和 BatchNorm 不同,LayerNorm 不跨样本、不依赖 batch_size。
  5. Pre-LN 远比 Post-LN 稳定——Post-LN 把 LN 接到 residual 路径上破坏了梯度的恒等性。今天的大模型几乎全部用 Pre-LN。
  6. RMSNorm 是 LayerNorm 的简化版——去掉均值减法和偏移参数,原论文报告运行时间减少 7%~64%(取决于 Norm 占总计算的比重),质量几乎不损失。
  7. 完整 Block 的数据流:x → RMSNorm → MHA → +residual → RMSNorm → FFN → +residual。每个 Block 输入输出形状一致,可任意堆叠。
  8. 参数量分解:MHA 占 1/3,FFN 占 2/3。计算量上短上下文 FFN 主导,长上下文 Attention 的 T2T^2 项主导——这预示了第六部分推理优化的两条路线。

到这里第二部分结束。我们已经把 Transformer 的「数学层」拆完了——Self-Attention、Multi-Head、位置编码、Block 组装,所有零件都讲清楚了。

下一章进入第三部分「架构家族」。同样的 Transformer Block,可以搭出三种不同的架构:Encoder-only(BERT 系)、Encoder-Decoder(T5 系)、Decoder-only(GPT 系)。它们各自适合什么任务?为什么 Decoder-only 最终统治了大模型时代?这是第 6 章要回答的问题。

延伸阅读

  • Vaswani et al., Attention Is All You Need, NeurIPS 2017,3.3 节是 FFN,3.1 节交代残差与 LayerNorm 的放法(5.4 节是 residual dropout 等正则化)。
  • Ba et al., Layer Normalization, 2016. arXiv:1607.06450——LayerNorm 原始论文。
  • Zhang & Sennrich, Root Mean Square Layer Normalization, NeurIPS 2019. arXiv:1910.07467——RMSNorm 原始论文。
  • Xiong et al., On Layer Normalization in the Transformer Architecture, ICML 2020. arXiv:2002.04745——Pre-LN vs Post-LN 的严格分析。
  • Shazeer, GLU Variants Improve Transformer, 2020. arXiv:2002.05202——SwiGLU 论文。
  • Geva et al., Transformer Feed-Forward Layers Are Key-Value Memories, EMNLP 2021——FFN 作为 key-value 记忆的解读。
  • He et al., Deep Residual Learning for Image Recognition, CVPR 2016——Residual 的奠基论文(虽然是 CV 的,思想一致)。
  • Hendrycks & Gimpel, Gaussian Error Linear Units (GELUs), 2016——GELU 论文。