从一条直线到大模型输出一个token(五):Transformer Block 全景与层归一化
建议先看:从一条直线到大模型输出一个token(四):位置编码 RoPE
上一篇末尾留了个悬念:「苹果」在"我吃了一个苹果"里是水果,在"苹果发布新手机"里是公司——同一个 token,含义要靠旁边的词决定。从这一篇开始,我们正式走进 Transformer 大厦,看词和词怎么"发生关系"。
从一条直线到大模型输出一个token(五):Transformer Block 全景与层归一化
- 从一条直线到大模型输出一个token(五):Transformer Block 全景与层归一化
- 1. 先看全景:一个 Block 长什么样
- 1.1 Block 是大模型的"标准层"
- 1.2 数据怎么流过 Block
- 1.3 N 个 Block 堆叠成完整模型
- 2. 层归一化 LayerNorm
- 2.1 它解决什么问题:数值会爆炸
- 2.2 定义:一行的"体检"
- 2.3 贯穿案例:「西安」行的完整手算
- 2.4 完整矩阵:全部 6 行的 LayerNorm 结果
- 3. LayerNorm 与 BatchNorm:两种归一化的对比
- 小结
- 下一篇预告
1. 先看全景:一个 Block 长什么样
1.1 Block 是大模型的"标准层"
把大模型想成一栋楼,Block 就是标准楼层——整栋楼就是把同一个 Block 重复盖 N 遍。LLaMA-3-8B 盖了 32 层,Qwen2.5-7B 盖了 28 层,DeepSeek-V3 盖了 61 层。
一个 Block 内部只有两种核心部件,结构如图 5-1 所示:
- 多头自注意力:负责"词和词之间交换信息"(第 6、7 篇拆解)
- 前馈网络 FFN:负责"每个词自己做深度加工"(第 8 篇拆解)
外加两个配角:层归一化(LayerNorm,本篇主角)和残差连接(第 8 篇讲)。
图5-1 一个 Block 的完整流程(Pre-Norm 结构)
1.2 数据怎么流过 Block
矩阵进,矩阵出,形状始终是 6×4096。这六步是严格串行的——第②步注意力的输出是第③步归一化的输入,一步都不能跳、不能换序(图 5-1 的垂直流程就是这个意思):
- ① 层归一化:把矩阵的数值"拉回稳定区间"
- ② 多头自注意力:每个词环顾四周,吸收其他词的信息
- 加残差:注意力加工的结果,加上"未经加工的原始输入"(快车道直通)
- ③ 再层归一化:再拉一次
- ④ 前馈网络 FFN:每个词独立深度加工
- 再加残差:又一条快车道
注意图 5-1 中绕在主流程外侧的两条蓝色旁路——这是残差连接:不管中间的加工多复杂,原始信息永远保留一条高速通道。它的作用第 8 篇细讲。
1.3 N 个 Block 堆叠成完整模型
嵌入层 + RoPE 之后,矩阵依次流过 Block 1、Block 2……Block N,最后进输出层,如图 5-2 所示。
图5-2 N 个 Block 堆叠:矩阵 6×4096 从头流到尾,逐层加深语义
矩阵的形状从头到尾不变,变的是数字里编码的语义:浅层 Block 加工语法,中层加工语义,深层加工到任务级别(哪些层负责什么,第 9 篇展开)。
顺带一提结构细节:图 5-1 画的是 Pre-Norm(先归一化再进子层)——2017 年原始论文是 Post-Norm(先算子层再归一化),但深层模型训练时 Post-Norm 容易梯度不稳,现代主流大模型(LLaMA 系及之后)全部改用 Pre-Norm。这也是"原始论文 ≠ 现代实现"的一个典型例子。
这里展开说下"梯度不稳"。训练时,误差信号要从输出层往输入层逐层回传(反向传播),每经过一层,梯度都要乘一次该层的系数:
- Post-Norm:归一化在残差相加之后,等于主干上每一层都插了一个"除以 σ"的关卡。梯度回传时连乘几十次这种系数,要么越乘越小(梯度消失,浅层学不到东西),要么突然放大(梯度爆炸,训练发散)——32 层以上就很难训
- Pre-Norm:归一化挪到子层入口,主干变成干净的直通加法线(x + f ( L N ( x ) ) x + f(\mathrm{LN}(x))x+f(LN(x)))。梯度沿主干回传时不受归一化干扰,多深都稳
一句话:Post-Norm 把关卡设在主干上,梯度每层都被"过安检";Pre-Norm 把关卡挪进支路,主干一路绿灯。这就是现代大模型全部选 Pre-Norm 的原因。
2. 层归一化 LayerNorm
2.1 它解决什么问题:数值会爆炸
Block 里的每一步都是矩阵运算,数字会一层层被放大。做个实验:设每层让数值扩大 1.5 倍再加一点偏移(真实模型里的放大系数远不止 1.5),不加归一化连续过 10 层,结果如图 5-3 所示。
图5-3 为什么必须归一化:无 LayerNorm 时数值指数爆炸
初始 1.0 的数值,10 层后变成 114.3——放大了 114 倍。真实模型 60 层起步、数值跨度更大,不控制的后果是:前向传播数值溢出(变成∞ \infty∞或N a N \mathrm{NaN}NaN,浮点数表示不了这么大的数),反向传播梯度爆炸,模型根本训不动。
LayerNorm 的作用就是每层入口都做一次"数值体检":不管进来的是 1 还是 114,都拉回均值 0、方差 1 的标准状态。红色爆炸曲线 vs 绿色稳定曲线,就是"有没有归一化"的天壤之别。
2.2 定义:一行的"体检"
LayerNorm 只对矩阵的每一行独立做:「今天」这一行的 4096 个数算一个均值一个方差,「西安」那一行自己算自己的,行与行互不干扰。
LayerNorm ( x ) = x − μ σ ⊙ γ + β \text{LayerNorm}(\mathbf{x}) = \frac{\mathbf{x} - \mu}{\sigma} \odot \boldsymbol{\gamma} + \boldsymbol{\beta}LayerNorm(x)=σx−μ⊙γ+β
- x \mathbf{x}x:某一行向量(一个 token 的 4096 维)
- μ \muμ、σ \sigmaσ:这一行自己的均值和标准差
- γ \boldsymbol{\gamma}γ、β \boldsymbol{\beta}β:可学习的缩放和平移参数(下面专门解释)
γ 和 β 到底是什么,掰开说:
它们是两个长度 4096 的数字列表(和输入向量一样长),不是单个数。第 1 维有第 1 维的γ 1 , β 1 \gamma_1, \beta_1γ1,β1,第 2 维有第 2 维的γ 2 , β 2 \gamma_2, \beta_2γ2,β2……每个维度配自己的一对。一个 LayerNorm 层总共 2×4096 = 8192 个参数。
"可学习"的意思:这 8192 个数字不是人定的,是训练出来的。训练开始前初始化为γ \gammaγ全 1、β \betaβ全 0(等于"先不干预");训练过程中,每次预测错了,误差会反传回来告诉每个γ i \gamma_iγi、β i \beta_iβi该调大还是调小——和嵌入表的 5.3 亿参数一样,靠梯度下降一点点学。
γ 管"拉多宽"(缩放):乘法系数。γ i > 1 \gamma_i > 1γi>1把第 i 维放大,< 1 < 1<1压缩。比如归一化后某维的值是 1.2,乘γ = 1.5 \gamma=1.5γ=1.5变成 1.8(更突出),乘γ = 0.5 \gamma=0.5γ=0.5变成 0.6(更淡化)。
β 管"往哪挪"(平移):加法偏移。β i > 0 \beta_i > 0βi>0整体右移,< 0 < 0<0左移。比如 1.2 加β = 0.3 \beta=0.3β=0.3变成 1.5。
为什么需要它们:第 3 步的"减 μ 除 σ"把每行都强制拉成均值 0、方差 1——数值是稳了,但也把维度之间的差异抹平了。可实际上不是每个维度同等重要:语义空间里"是不是地名"这个维度可能很关键,“语气强弱"可能次要。γ 和 β 就是给模型留的"后悔药”:标准化之后,再让模型自己决定每个维度恢复多大的幅度、往哪个方向偏。
用「西安」行的真实数字走一遍(γ = [ 1.2 , 0.8 , 1.0 , 1.1 ] \gamma = [1.2, 0.8, 1.0, 1.1]γ=[1.2,0.8,1.0,1.1],β = [ 0.1 , − 0.1 , 0.05 , 0.2 ] \beta = [0.1, -0.1, 0.05, 0.2]β=[0.1,−0.1,0.05,0.2]):
| 维度 | 归一化后(第 3 步结果) | γ(缩放) | β(平移) | 输出 |
|---|---|---|---|---|
| d₁ | -1.289 | ×1.2 | +0.1 | -1.446 |
| d₂ | 0.704 | ×0.8 | -0.1 | 0.463 |
| d₃ | 1.207 | ×1.0 | +0.05 | 1.257 |
| d₄ | -0.622 | ×1.1 | +0.2 | -0.485 |
表5-1 γ缩放与β平移的逐维效果(「西安」行)
d₁ 被放大并微移(-1.289 → -1.446),d₂ 被压缩(0.704 → 0.463)——同一行里不同维度受到的"待遇"各不相同,全由训练学出来的 γ、β 决定。
用代码描述(大模型实现细节,跳过不影响理解):
classLayerNorm:def__init__(self,d_model=4096):self.gamma=np.ones(d_model)# 初始化为 1self.beta=np.zeros(d_model)# 初始化为 0defforward(self,x):# x: (6, 4096)mu=x.mean(axis=-1,keepdims=True)# 每行各自的均值sigma=x.std(axis=-1,keepdims=True)# 每行各自的标准差return(x-mu)/sigma*self.gamma+self.beta2.3 贯穿案例:「西安」行的完整手算
拿 RoPE 后的「西安」向量[ − 1.079 , 0.004 , 0.277 , − 0.717 ] [-1.079,\ 0.004,\ 0.277,\ -0.717][−1.079,0.004,0.277,−0.717],四步手算如图 5-4 所示。
图5-4 LayerNorm 手算:「西安」行的完整四步
第 1 步:取「西安」这一行[ − 1.079 , 0.004 , 0.277 , − 0.717 ] [-1.079,\ 0.004,\ 0.277,\ -0.717][−1.079,0.004,0.277,−0.717]。
第 2 步:算这行的均值和标准差:
μ = − 1.079 + 0.004 + 0.277 − 0.717 4 = − 0.379 \mu = \frac{-1.079 + 0.004 + 0.277 - 0.717}{4} = -0.379μ=4−1.079+0.004+0.277−0.717=−0.379
σ 2 = ( − 0.700 ) 2 + ( 0.383 ) 2 + ( 0.656 ) 2 + ( − 0.338 ) 2 4 = 0.295 , σ = 0.543 \sigma^2 = \frac{(-0.700)^2 + (0.383)^2 + (0.656)^2 + (-0.338)^2}{4} = 0.295, \quad \sigma = 0.543σ2=4(−0.700)2+(0.383)2+(0.656)2+(−0.338)2=0.295,σ=0.543
第 3 步:每个数减 μ、除以 σ:
x ^ 1 = − 1.079 − ( − 0.379 ) 0.543 = − 1.289 \hat{x}_1 = \frac{-1.079 - (-0.379)}{0.543} = -1.289x^1=0.543−1.079−(−0.379)=−1.289
四个数依次得到[ − 1.289 , 0.704 , 1.207 , − 0.622 ] [-1.289,\ 0.704,\ 1.207,\ -0.622][−1.289,0.704,1.207,−0.622]——现在这行的均值是 0、方差是 1。
第 4 步:乘可学习的γ \gammaγ、加可学习的β \betaβ(γ \gammaγ、β \betaβ的完整解释见 2.2 节和表 5-1;本例取γ = [ 1.2 , 0.8 , 1.0 , 1.1 ] \gamma = [1.2, 0.8, 1.0, 1.1]γ=[1.2,0.8,1.0,1.1],β = [ 0.1 , − 0.1 , 0.05 , 0.2 ] \beta = [0.1, -0.1, 0.05, 0.2]β=[0.1,−0.1,0.05,0.2]):
1.2 × ( − 1.289 ) + 0.1 = − 1.446 1.2 \times (-1.289) + 0.1 = -1.4461.2×(−1.289)+0.1=−1.446
最终输出[ − 1.446 , 0.463 , 1.257 , − 0.485 ] [-1.446,\ 0.463,\ 1.257,\ -0.485][−1.446,0.463,1.257,−0.485]——正是表 5-1 最右列。每个数都可手算验证。
2.4 完整矩阵:全部 6 行的 LayerNorm 结果
「西安」只是其中一行。整张矩阵每一行独立做一遍同样的四步(各算各的 μ 和 σ),结果如下(γ、β 取同一组演示值):
X L N = [ 0.565 − 1.356 1.226 0.208 − 1.446 0.463 1.257 − 0.485 − 1.218 0.026 1.599 − 0.469 0.740 − 1.353 − 0.038 1.433 0.133 − 0.539 − 1.025 1.955 1.265 0.170 0.417 − 1.643 ] \mathbf{X}_{LN} = \begin{bmatrix} 0.565 & -1.356 & 1.226 & 0.208 \\ -1.446 & 0.463 & 1.257 & -0.485 \\ -1.218 & 0.026 & 1.599 & -0.469 \\ 0.740 & -1.353 & -0.038 & 1.433 \\ 0.133 & -0.539 & -1.025 & 1.955 \\ 1.265 & 0.170 & 0.417 & -1.643 \end{bmatrix}XLN=0.565−1.446−1.2180.7400.1331.265−1.3560.4630.026−1.353−0.5390.1701.2261.2571.599−0.038−1.0250.4170.208−0.485−0.4691.4331.955−1.643
各行自己的 μ 和 σ(体现"每行独立"):
| token | μ(这行的均值) | σ(这行的标准差) |
|---|---|---|
| 今天 | 0.045 | 0.710 |
| 西安 | -0.379 | 0.543 |
| 的 | 0.410 | 0.346 |
| 天气 | -0.147 | 0.715 |
| 怎么样 | -0.404 | 0.505 |
| ? | 0.128 | 0.692 |
表5-2 六行各自的均值与标准差(每行独立统计)
注意「西安」行的[ − 1.446 , 0.463 , 1.257 , − 0.485 ] [-1.446,\ 0.463,\ 1.257,\ -0.485][−1.446,0.463,1.257,−0.485]正是上节手算的结果——第 2 行。这就是矩阵进 Block 前的第一次"数值体检":形状不变(还是 6×4096),但每一行都被拉回了各自的稳定区间。
3. LayerNorm 与 BatchNorm:两种归一化的对比
有机器学习基础的读者会问:归一化不是有现成的 BatchNorm 吗?图像领域用得好好的,为什么 NLP 要另起炉灶?
先把两者的定义摆出来。归一化的对象是一个"数据组",区别在于怎么分组:
- BatchNorm(批量归一化,2015 年提出,CV 领域标配):把同一个批次里、不同样本的同一个维度分成一组。比如一个批次 32 张图片,把 32 张图的"红色通道第一行像素"拿在一起,算这 32 个数的均值方差来归一化——跨样本、按维度
- LayerNorm(层归一化,2016 年提出):把同一个样本自己的全部维度分成一组。比如「西安」这个 token 的 4096 维,算它自己 4096 个数的均值方差——单样本、按层内全部维度
放进我们的矩阵里看(矩阵一行是一个 token、一列是一个维度),分组方式如图 5-5 所示。
图5-5 为什么 NLP 选 LayerNorm 而不是 BatchNorm
矩阵的每一行是一个 token,每一列是一个维度:
- BatchNorm 按列归一化:把「今天」「西安」「的」「天气」的第 1 维拿在一起比——跨 token 统计。问题来了:句子有长有短(这次 6 个 token,下次 600 个),每批的统计量乱跳;更致命的是生成场景是逐 token 出结果的,根本没有"一批句子"可比
- LayerNorm 按行归一化:每个 token 自己的 4096 维内部统计——句长变化不影响,来一个 token 归一化一个,完全稳定
表5-3 LayerNorm vs BatchNorm 对比
| 维度 | BatchNorm | LayerNorm |
|---|---|---|
| 归一化方向 | 按列(跨样本同一维度) | 按行(单个样本全部维度) |
| 依赖批大小 | 强依赖(batch 太小统计不稳) | 完全无关 |
| 变长序列 | 每次统计量不同,不稳 | 每行独立,天然适配 |
| 逐 token 生成 | 无法用(没有完整批次) | 完美支持 |
| 适用场景 | CV(固定尺寸图像) | NLP / 大模型(变长文本) |
一句话总结:文本是变长的、生成是逐个的,只有"按行独立"的 LayerNorm 能同时扛住这两点。
小结
这一篇,我们拿下了 Transformer 大厦的结构图和第一块砖:
- Block = 标准楼层:层归一化 → 多头自注意力 → 残差 → 层归一化 → FFN → 残差,Pre-Norm 是现代主流
- N 个 Block 堆叠,矩阵形状 6×4096 从头流到尾,语义逐层加深
- LayerNorm 解决数值爆炸:不归一化 10 层放大 114 倍,模型根本训不动
- LayerNorm 按行独立:减 μ 除 σ 再乘 γ 加 β,「西安」行四步手算全程可验证
- 不用 BatchNorm 的原因:文本变长 + 逐 token 生成,只有按行归一化扛得住
下一篇预告
Block 结构里,注意力排第一个位置——它是"词和词交换信息"的唯一通道,也是整个 Transformer 名字里 Attention 的由来。
下一篇拆注意力的第一步:Q、K、V 三剑客。「西安」想知道自己是"城市名",它得先发出一张"查询单"(Q:我要找地名相关信息);其他词得亮出自己的"名片"(K:我有什么信息)和"干货"(V:我的具体内容)。三张单子怎么从同一个矩阵变出来?这是第 6 篇的故事。
系列目录:
- 从一条直线到高维空间
- 分词与 token 表
- 隐藏层与嵌入
- 位置编码 RoPE
- Transformer Block 全景与层归一化(当前篇)
- QKV 三剑客
- 注意力权重与多头机制
- 残差连接与前馈网络
- 输出矩阵与多层堆叠
- 首 token 诞生与 KV-Cache
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。