层、残差与深度
为什么模型要堆几十个一模一样的块,以及让深堆叠能训起来的那两个小技巧。
一个 transformer 就是把同一个块重复几十遍——先是注意力,然后是前馈。大模型可能堆到 80 个。
接下来有两个问题。深度为什么有用?而在那两个特定技巧出现之前,为什么深度根本做不了?
深度买到了什么
每一层都在细化它收到的表示。去观察一个训练好的模型,会看到一个大致的分工递进:
浅层做局部的事。这里的 bank 是哪个意思?这是什么词性?哪些相邻的 token 该归在一起?主要是语法和消歧。
中间层建立关系。she 指的是谁?这个动词的主语是什么?这依赖前面哪一个从句?大部分的实体跟踪就发生在这里。
靠后的层收敛到当前任务上:把预测下一个 token 所需的东西拼装起来。
一个好用的概括:浅层决定每个词是什么,中间层决定词与词怎么关联,靠后的层决定接下来是什么。
深度还让「组合」成为可能。一次注意力就能连起两个位置。两次叠加的注意力可以连起 A 到 B 再到 C——信息经由一个中间位置中转。多步推理似乎就需要这个,这也是能力为什么不只看宽度、还要看深度的部分原因。
深堆叠以前为什么训不起来
天真地堆 50 层,训练就崩了。原因是梯度消失。
训练从误差往回走,计算每个参数贡献了多少。这个信号在回传路上要穿过每一层,每穿一层就被乘上一个数。连乘 50 个略小于 1 的数,结果趋近于 0——浅层几乎收不到信号,也就永远学不到东西。连乘略大于 1 的数,则会直接爆掉。
两种情况下深度都废了。有两个机制解决了这个问题。
残差连接
解法简单到有点过分。不把层的输出往前传,而是传 输入 + 输出。
output = x + Layer(x)这个加法造出了一条通路,信号能不打折扣地到达更早的层——梯度直接沿 x 这一项流回去,不会被这一层的变换乘掉。深度不再劣化训练信号。
这也重新定义了一层在做什么。它不再从零算出一个新表示,而是对收到的东西算出一个修改量。每一层都在一份持续流动的表示上写一笔调整。
这份持续流动的表示常被称作残差流,它是一个真好用的心智模型:一条从输入流向输出的宽阔信息通道,每个注意力和前馈块都从中读取、再写回去。层与层之间的交流方式是把东西留在共享通道里,而不是严格的接力交接。
它也解释了为什么层会冗余得让人意外。既然每层只写一个增量,从训练好的模型里拿掉一层,输出往往只轻微变差——其他层并没有依赖它那一份确切的贡献。
归一化
第二个机制管的是尺度。没有它,数值在穿过各层时会漂移——不断变大或变小,直到撞上数值问题。
层归一化(LayerNorm) 把每个 token 的向量重新缩放成一致的统计分布,再施加学习到的缩放与平移参数。每个块都做一次,整摞就数值稳定了。
有一个摆放位置的细节,后果很实际。在残差相加之后做归一化(post-norm)是最初的设计,层一深就训不好。在进入层之前做归一化(pre-norm),残差通路就完全干净,这也是当前模型的做法。这个改动,是极深的 transformer 之所以能训起来的很大一部分原因。
深度与宽度之争
给定一份参数预算,你可以花在更多层上,也可以花在更宽的层上。
更深有利于组合与多步推理。更宽有利于单步的容量,在真实硬件上也并行得更好。深度还会在推理时增加延迟,因为层必须按顺序跑——100 层的模型没法在第 49 层没跑完之前开始跑第 50 层。
实际架构都落在中间某个区间,靠实验调出来,而不是推导出来。没有干净的公式。
要点回顾
- 各层的分工从局部语法,到关系,再到拼装下一个 token;深度还让多步组合成为可能。
- 天真的深堆叠会失败,是因为梯度在多次连乘中消失或爆炸。
- 残差连接(
x + Layer(x))给梯度一条干净的通路,并把每层变成在共享残差流上的一笔增量。 - 归一化让数值保持稳定;把它放在层的入口而不是出口,才让极大的深度变得可行。
- 深度有助于推理,但会带来串行延迟。
下一篇: 什么是 KV cache?