📚 本文是《Transformer 由浅入深》系列第 6 篇 · 阶段三「搭起完整模型」

前面五篇,我们把注意力这块核心积木打磨透了:从直觉到公式,再到多头和位置编码。但如果你以为 Transformer 就是"一堆注意力",那就漏掉了一半。

真实的 Transformer 是由一个个结构相同的模块叠起来的,每个模块叫一个 Transformer Block。注意力只是其中一个子层;要让几十层稳定地训练起来,还得靠残差连接、层归一化、前馈网络这几个配件。这一篇,我们就把一个完整的 Block 拆开看。

打个比方:如果说注意力是发动机,那么残差、LayerNorm、FFN 就是底盘、变速箱和悬挂。一辆能跑的车,光有发动机是不够的——少了任何一个配件,这台"深度学习机器"要么跑不起来,要么一深就散架。所以这一篇虽然主角换成了"配角",含金量却一点不低:工业界真正让模型从"能跑"走向"跑得稳、堆得深"的,恰恰是这些不起眼的零件。换句话说,注意力决定了模型"能学到什么",而这些配件决定了模型"能不能学得起来、能不能做得足够大"——后者在今天动辄百亿千亿参数的时代,重要性丝毫不亚于前者。

本篇目标

读完你应该能:

  • 画出并解释一个 Transformer Block 的完整数据流;
  • 说清残差、LayerNorm、FFN 各自为什么不可或缺;
  • 知道参数量的大头其实在哪。

阅读前提

第 3~5 篇(注意力、多头、位置编码)。

1. 全景图

先把结论摆出来。一个标准 Transformer Block 的数据流是这样的(以现代常用的 Pre-LN 结构为例):

输入 x
  │
  ├─────────────────────────┐
  │                         │ (残差:把输入原样留一份)
  ▼                         │
LayerNorm                   │
  ▼                         │
多头自注意力                 │
  ▼                         │
  + ◄──────────────────────┘
  │   (相加)
  ├─────────────────────────┐
  │                         │ (残差)
  ▼                         │
LayerNorm                   │
  ▼                         │
前馈网络 FFN                 │
  ▼                         │
  + ◄──────────────────────┘
  │
  ▼
输出(形状与输入 x 完全相同)

可以看出,一个 Block = 两个子层(注意力 + FFN),每个子层都被残差LayerNorm包裹。注意输出形状和输入完全一样(都是 \( n \times d \))——这正是它能被反复堆叠的前提。我们逐个拆。

不妨把这张图想象成一条两段式流水线:第一段(注意力)负责让每个词"交流互通、互相参考",第二段(FFN)负责让每个词"关起门来独立消化"。两段之间,残差像一条贯穿始终的传送带保证原料不丢,LayerNorm 像每段入口的质检员保证进料尺寸统一。理解了这条流水线,你就抓住了整个 Transformer 的骨架——后面无论多大的模型,无非是把这条流水线加宽、加长、复制几十上百遍而已。

2. 残差连接(Residual)

注意图里那两条"绕过去"的旁路:子层的输出不是直接往下传,而是先加回最开始的输入:

$$ \text{output} = x + \text{Sublayer}(x) $$

这就是残差连接(residual / skip connection),来自 ResNet。为什么需要它?

当网络很深(几十层)时,梯度在反向传播中要穿过层层变换,很容易逐渐消失,导致底层学不动。残差给了梯度一条高速公路:那个" \( +x \) “让梯度可以几乎无损地直接流回前面的层

可以这样想象:没有残差的深网络,像一栋只有楼梯、没有电梯的几十层大楼。信息(以及反向传播的梯度)要从顶楼回到一楼,必须一级一级走,每经过一层就被"踩掉"一点,走到底层时早已精疲力竭、面目全非——这就是梯度消失。残差连接相当于在大楼里装了一部直达电梯:梯度可以坐电梯一路畅通地回到任意楼层,信号强度几乎不衰减。

换个角度理解也很直观:有了 \( x + \text{Sublayer}(x) \),每个子层要学的不再是"从零生成新表示”,而只是"在已有表示上做一点修正/增量"。学增量比学全量容易得多,网络因此能稳定地做得很深。

这就像修改一篇文章。“从零写一篇"和"在初稿上改几个词"是完全不同难度的任务。残差让每一层的工作变成后者:上一层已经给出了一份"还不错的草稿”( \( x \) ),这一层只需专注于"哪里该润色一下"( \( \text{Sublayer}(x) \) ),最后把润色叠加上去。哪怕某一层暂时"没想法",它只要让 \( \text{Sublayer}(x) \approx 0 \),输出就约等于原样透传,至少不会把事情搞砸——这是深网络能安全堆叠的关键保险。

还可以用"接力赛"来理解残差为何让深网络不再"越深越差"。没有残差时,信息像一支接力队,每一棒都必须重新跑完全程才能把接力棒交出去,跑了几十棒之后,最初那份信号早被反复改写得认不出来,中途稍有差池还会一路放大,导致深层模型反而比浅层更差(这就是著名的"退化"现象:层数加多,效果不升反降)。有了残差,接力棒被牢牢攥在主干道手里一路传到底,每一棒只是在棒上贴一张小便签做补充。于是"加层"几乎只会带来好处而不会带来灾难——这正是 ResNet 当年能把网络从十几层一举推到上百层、并被 Transformer 全盘继承下来的根本原因。

3. 层归一化(LayerNorm)

每个子层前(Pre-LN)或后(Post-LN)都有一个 LayerNorm(层归一化)。它做的事:把一个向量的各维重新缩放到均值 0、方差 1,再用两个可学习参数 \( \gamma, \beta \) 调整:

$$ \text{LayerNorm}(x) = \gamma \cdot \frac{x - \mu}{\sqrt{\sigma^2 + \epsilon}} + \beta $$

其中 \( \mu, \sigma^2 \) 是对这一个向量自己的所有维度算出来的均值和方差。它的作用是稳定数值分布:训练中各层的数值尺度会乱飘,LayerNorm 把它们拉回稳定区间,让训练更快更稳。

打个生活化的比方:LayerNorm 像一个自动统一音量的调音台。不同来源的音频信号忽大忽小——有的爆音、有的细若蚊蝇,直接混在一起既刺耳又听不清。调音台先把每路信号都拉到一个统一的标准响度,再交给后面的设备处理。神经网络里也是如此:某些层算着算着数值就膨胀到几百几千,或者萎缩到接近零,后面的层就很难"听清"前面在说什么。LayerNorm 在每一步都先"统一音量",让数值始终待在一个温和、可控的范围里,训练自然又快又稳。\( \gamma \) 和 \( \beta \) 则像调音台上保留的两个旋钮:统一音量之后,模型如果发现某些维度确实需要更响或更轻,还能自己微调回来,不至于把有用的差异也抹平。

为什么是 LayerNorm 而不是 BatchNorm? BatchNorm 是"跨一批样本、对同一维"做归一化,它依赖 batch、且对变长序列很不友好。LayerNorm 只在"单个 token 向量内部"归一化,与 batch 大小和序列长度都无关,天然适合处理长短不一的句子。这就是序列模型偏爱 LayerNorm 的原因。

再补一个直观的小例子帮你记住二者的区别。假设我们有一批句子,长短不一:有的 5 个词,有的 50 个词。BatchNorm 的统计方式是"把这一批里所有词的第 \( k \) 维拿出来一起算均值方差",可一旦句子长度参差、batch 又小,这个统计量就会抖得厉害,训练时学到的均值方差到了推理时也对不上号。而 LayerNorm 压根不跨样本、不跨位置,它只盯着一个词向量自己的那几百维做归一化——句子是长是短、这一批有几条样本,统统与它无关。所以它对 NLP 这种"输入形状天天变"的场景特别友好,这也是为什么从原版 Transformer 到今天的大模型,序列建模几乎一律选 LayerNorm(或它的简化版 RMSNorm)。

4. 前馈网络(FFN / MLP)

注意力负责"在词之间搬运、混合信息";但混合之后,还需要对每个词自己的表示做一轮非线性加工——这就是 前馈网络(Feed-Forward Network, FFN) 的活。它其实就是一个两层 MLP,逐位置独立地作用在每个词向量上:

$$ \text{FFN}(x) = \max(0,\ x W_1 + b_1)\, W_2 + b_2 $$

(这里 \( \max(0, \cdot) \) 是 ReLU 激活,现代模型常换成 GELU。)

这里有个容易忽略的细节:FFN 是逐位置(position-wise)独立作用的——同一个 \( W_1, W_2 \) 被复制着套用到每个词向量上,词与词之间在这一步互不干扰。可以把注意力和 FFN 想成一条流水线上的两道工序:注意力是一场"圆桌会议",每个词都去听取、汇总全场其他词的信息;而 FFN 是会议散场后,每个人各自回到工位深加工——把刚才会上收集到的信息咀嚼、提炼、转化成自己的新理解。一个负责"广泛交流",一个负责"独立深思",分工相当清晰。

它的形状很有讲究:先把 \( d \) 维升到一个更宽的中间维度(通常是 \( 4d \)),激活后再降回 \( d \):

$$ d \xrightarrow{\ W_1\ } 4d \xrightarrow{\ \text{激活}\ } 4d \xrightarrow{\ W_2\ } d $$

“先放大、激活、再压缩"给了模型充分的非线性表达空间。为什么要先放大?可以类比成先把材料摊开再精挑细选:把 \( d \) 维信息投射到一个宽得多的 \( 4d \) 空间,相当于把原本挤在一起的特征"摊开"到更大的舞台上,经过激活函数这道非线性"筛子"挑出真正有用的组合,最后再压缩回 \( d \) 维提炼成精华。中间这层越宽,模型能表达的"花样"就越多;如果不放大、直来直去,两个线性层叠在一起其实等价于一个线性变换,白白浪费了一次非线性的机会。

再换个更接地气的比方:FFN 那个"先升到 \( 4d \) 再降回 \( d \)“的结构,很像厨房里的”摊开备料、精炼出锅"。一道菜的原料( \( d \) 维)本来挤在小砧板上,先把它们铺开到一张大案板上( \( 4d \) ),让各种食材有充分的空间被切配、组合、试味(激活函数挑出好搭配),最后再收拢装盘成一道精致的成品( \( d \) 维)。案板越大,能尝试的搭配花样越多,做出来的"菜"也就越有层次。这就是中间层要刻意放宽到 \( 4d \) 的直觉——给非线性加工留足腾挪的空间。

关键认知:FFN 才是参数量的大头。\( W_1, W_2 \) 加起来约 \( 2 \times d \times 4d = 8d^2 \) 个参数,而整个多头注意力的投影(\( W_Q,W_K,W_V,W_O \))合计才约 \( 4d^2 \)。换句话说,模型里大约三分之二的参数在 FFN 里——很多人以为 Transformer 的"智能"全在注意力,其实 FFN 扛了大半的容量。近年不少研究甚至认为,模型记住的大量事实性知识(“法国的首都是巴黎"这类)主要就存储在 FFN 的权重里,FFN 像是一个巨大的、可按内容检索的"知识词典”。所以下次有人说"Transformer 全靠注意力”,你可以反问一句:那占了三分之二参数、装着大半知识的 FFN 算什么呢?

5. Pre-LN vs Post-LN

LayerNorm 到底放在子层之前还是之后,是个有讲究的细节:

  • Post-LN(原版论文):x → 子层 → 残差相加 → LayerNorm。表达力略强,但深层时训练不稳定,往往需要小心的学习率 warmup 才能收敛。
  • Pre-LN(现代主流):x → LayerNorm → 子层 → 残差相加(就是第 1 节那张图)。残差通路上没有 LayerNorm 阻隔,梯度更顺,训练稳定得多,几乎不需要 warmup。

二者的差别,关键在于那条残差"高速公路"上有没有设收费站。Post-LN 把 LayerNorm 压在残差相加之后,等于在高速公路上每隔一层就设一道关卡,梯度每次回流都要被"盘问"一遍,层一多就堵得水泄不通,训练初期极易发散——所以非得靠 warmup 这种"先慢慢热车"的技巧小心伺候。Pre-LN 则把 LayerNorm 挪到子层入口,残差主干道全程畅通无阻,梯度想回哪层就回哪层,稳得多。

今天的大模型几乎清一色用 Pre-LN(或其变体如 RMSNorm),因为在动辄几十上百层的规模下,训练能不能稳住比那一点点表达力更重要。这是个很现实的工程取舍:实验室里小模型上 Post-LN 那点表达力优势看得见,可一旦放到百层规模,Post-LN 可能压根训不收敛——训不出来的"更强模型"等于零,而稳定训出来的 Pre-LN 模型再大也跑得动。工程上,能稳定规模化几乎永远比"理论上更优雅"更值钱。可以记住一个朴素的判断标准:在大模型这个赛道,一个方法值不值得用,首先看它"能不能在更大规模上不出事",其次才轮到比拼那一点点精度上的高下。

6. 堆叠 N 层

单个 Block 的输入输出形状一致,于是可以像乐高一样摞起来:第一个 Block 的输出喂给第二个,以此类推,叠 \( N \) 层。

$$ h_0 = \text{输入嵌入} + \text{位置编码} $$

$$ h_\ell = \text{Block}_\ell(h_{\ell-1}), \quad \ell = 1, \dots, N $$

\( N \) 是模型的核心规格之一:原版 base 是 6 层,GPT-2 有 12 / 24 / 48 层,大模型可达上百层。一个被反复验证的现象是层有分工:浅层倾向于捕捉局部、表层的模式(词形、短语搭配),深层逐渐学到更抽象、更全局的语义和句法结构——有点像 CNN 里浅层学边缘、深层学物体。

这种分工也很像一家公司的层级流水:基层员工处理具体、零碎的事务(这个词是名词还是动词、这两个词是不是固定搭配),中层把局部信息汇总成段落级的理解,高层则负责把握全局的语义、逻辑和意图。信息从一楼逐级上报、不断抽象,到顶层时已经从"一堆词"变成了"一段意思"。正因为有这种逐层抽象的接力,堆得越深,模型能理解的语言结构就越深邃复杂——当然,这并不意味着层数可以无脑往上加,这一点我们在文末 FAQ 里细说。

值得强调的是,这种"层有分工"并不是设计者手工指派的——没人规定第 3 层去管词性、第 20 层去管语义。它完全是模型在海量数据上训练后自发涌现出来的组织方式,后人通过探针实验(probing)才把这种隐藏的分工一层层"解剖"出来。这也是深度学习耐人寻味的地方:你只是把同构的积木摞高、喂足数据,结构化的"理解能力"竟会自己从中长出来。

7. 参数量怎么算

把一个 Block 的参数量拆开,能帮你建立"模型多大"的体感(忽略偏置和 LayerNorm 的少量参数):

组件参数量
注意力 \( W_Q, W_K, W_V, W_O \)\( \approx 4d^2 \)
FFN \( W_1, W_2 \)(中间 \( 4d \))\( \approx 8d^2 \)
单个 Block 合计\( \approx 12d^2 \)

整个模型再乘以层数 \( N \),约 \( 12 N d^2 \)。以 GPT-2 为例(\( d=768, N=12 \)):\( 12 \times 12 \times 768^2 \approx 0.85 \) 亿,加上词嵌入等,正好接近它公布的 1.17 亿参数。你可以拿这个公式去估算任意 Transformer 的大致规模——是不是一下子有体感了?

顺带留意公式里的 \( d^2 \):参数量是随宽度 \( d \) 平方级增长的。把模型加宽一倍(\( d \) 翻倍),参数量直接翻四倍;而把层数 \( N \) 加倍,参数量只翻一倍。这也解释了为什么"加宽"往往比"加深"来得更"贵"——理解了 \( 12 N d^2 \) 这个简单的估算式,你看任何一篇模型发布稿里的参数规模,心里都能立刻有杆秤。

常见疑问与易错点

问:残差为什么是"相加"而不是"拼接"? 答:相加(\( x + \text{Sublayer}(x) \))能保持向量维度不变,输出仍是 \( d \) 维,这样才能层层堆叠、形状始终对齐;而拼接会让维度越叠越大,几层下来就爆炸了。更重要的是,相加在数学上让梯度能"原样"流回前层(那条高速公路),拼接则做不到这种干净的恒等透传。所以残差一定是逐元素相加。

问:LayerNorm 到底对"谁"做归一化? 答:对单个词向量自己的所有维度。也就是说,序列里每个 token 各算各的均值方差,token 之间互不影响,batch 里的样本之间也互不影响。这正是它和 BatchNorm 的本质区别——BatchNorm 跨样本统计,LayerNorm 只在一个向量内部统计,因此天然不怕变长序列和小 batch。

问:FFN 和注意力,到底谁更重要? 答:两者分工不同,缺一不可,谈不上谁取代谁。注意力负责词与词之间的信息交流(横向混合),FFN 负责每个词自身的非线性深加工(纵向提炼)。只有注意力,信息搬来搬去却没人深度消化;只有 FFN,每个词又成了只会自言自语的孤岛。论参数量,FFN 反而占了约三分之二,是名副其实的"容量大户"。

问:层数(N)是不是越多越好? 答:不是。层数变多确实能提升抽象能力,但收益会递减,而且代价高昂:更难训练(尽管有残差和 Pre-LN 兜底)、更慢、更吃显存,还可能过拟合。真实的模型设计是在深度 \( N \)、宽度 \( d \)、数据量、算力之间权衡——很多时候把模型适当加宽、或喂更多数据,比一味堆层数更划算。所以你会看到不同模型的层数差异巨大,而非清一色越堆越高。

问:Pre-LN 既然更稳,为什么原版论文用的是 Post-LN? 答:这是历史和认知演进的结果。2017 年原版论文先用了 Post-LN,后来社区在训练超深、超大模型时才发现它的不稳定问题,Pre-LN 等改进方案随之成为主流。这说明一点:论文里的"原版做法"不等于"最佳做法",工程实践常常会迭代出更稳健的变体(如 Pre-LN、RMSNorm)。

问:为什么每个子层都要单独配一套残差和 LayerNorm? 答:因为注意力和 FFN 是两个独立的子层,各自都可能引入数值波动、各自都需要梯度顺畅回流。给每个子层都套上"残差 + LayerNorm"这层保护壳,等于让每一小步变换都既稳定又可透传,这样几十层串起来才不会在某一处崩掉。把它理解成"每道工序都自带质检和保险"就好。

问:FFN 的中间层为什么偏偏是 \( 4d \),不是 2d 或 8d? 答:\( 4d \) 是原版论文给出、并被后续大量模型沿用的一个经验性默认值,在"表达力"和"参数 / 算力开销"之间取得了不错的平衡,而非什么不可更改的定律。实际工程里这个放大倍数是可调的:有的模型用更小的倍数省参数,有的换用 GLU 类门控结构后会相应调整中间宽度。记住背后的原则就够了——中间层越宽,非线性表达空间越大,但参数和计算也水涨船高,\( 4d \) 只是一个被反复验证好用的折中点。

小结 & 下一篇预告

这一篇,我们把注意力之外的"配件"补齐,拼出了完整的 Transformer Block:

  • 残差给梯度修高速路,让网络能做很深;
  • LayerNorm稳定数值分布(且天然适配变长序列,故不用 BatchNorm);
  • FFN逐词做非线性加工,且是参数量的大头(约 2/3);
  • Pre-LN 因训练稳定成为现代主流;
  • Block 输入输出同形 → 可堆叠 \( N \) 层,且层有分工;
  • 单 Block 约 \( 12d^2 \) 参数,可用来估算模型规模。

现在我们手里有了一块完整的积木。下一篇,我们用它搭出 2017 原版论文的完整 Encoder-Decoder 架构,看清编码器和解码器各自的职责、它们之间的 cross-attention,以及生成时不可或缺的因果掩码


📖 系列目录

第 1 篇 文末完整目录。