2026-04-15 · transformer
【Transformer 与注意力机制】06|梯度下降与反向传播
神经网络真正会「学习」靠的是两件事:把误差变成可微分的损失函数,再沿着这个损失对参数的梯度方向一点点往下挪。本文从一维抛物线讲到多变量梯度,从两层网络的手算反向传播讲到为什么 backprop 是 O(参数量),再到 Transformer 为什么几乎一律选 Adam/AdamW,希望把「网络是怎么学的」这件事彻底讲透。




























上一篇我们把 Transformer block 的最后一个零件——前馈网络——讲透了。到这里,整个模型的结构已经完全摆开:embedding、位置编码、多头注意力、残差、LN、FFN、stack 6 层。
但「结构对了」不等于「能训出来」。深度学习里有一个让人头疼的事实:同一个模型架构,配方稍微一动,结果可能差几个 BLEU。学习率怎么调、warmup 多长、batch 怎么算、dropout 放哪儿、label 平滑多少——这些超参数的取值,比模型结构本身更难猜对。
2017 年原论文之所以能让人信服,不只是因为提出了新结构,更是因为他们公开了一套完整的训练配方,并在 WMT2014 这个公认的 benchmark 上跑出了当时的 SOTA。这份配方后来被几乎所有 Transformer 实现奉为「标准答案」,直到现代 LLM 时代才慢慢演化出新版本。
这一篇要做的事情,是把这个配方一项一项地拆开,讲清楚每一个数字、每一个公式背后的含义。读完之后你应该能做到:
先把整套配方一次性贴出来,然后我们一节一节地拆:
模型: Transformer base / big
数据集: WMT 2014 En-De(4.5M 句对)/ En-Fr(36M 句对)
硬件: 8 × NVIDIA P100 GPU
训练时长:base 12 小时(100k 步) / big 3.5 天(300k 步)
batch: 每步约 25000 source token + 25000 target token
优化器: Adam, β₁=0.9, β₂=0.98, ε=1e-9
学习率: lr = d^(-0.5) · min(step^(-0.5), step · warmup^(-1.5))
warmup: 4000 步
正则化: dropout 0.1(embedding 之后、每个子层输出)
label smoothing 0.1
推理: beam search, beam=4, length penalty α=0.6
最终: En-De BLEU 27.3 (base) / 28.4 (big)
En-Fr BLEU 38.1 (base) / 41.8 (big)
每一行都有故事。
WMT(Workshop on Machine Translation)从 2006 年开始举办,是机器翻译领域的标准评测。每届会发布若干语言对的训练数据、开发集、测试集,参赛者提交翻译结果,组委会统一评分(人工或 BLEU)。
2017 年原论文用的是 WMT 2014 的两组数据:
两个任务的官方测试集是 newstest2014,每对约 3000 个句子。BLEU(Bilingual Evaluation Understudy)分数是在这个测试集上算出来的——具体说,是用 SacreBLEU 或 multi-bleu 这样的工具,把模型翻译和参考译文比较 n-gram 匹配率。
为什么选这两组而不是别的?因为它们在 2017 年是「公认的硬骨头」——之前几年的 SOTA 模型(GNMT、ConvS2S、ByteNet)都在这上面打榜,已经形成了稳定的对比基线。
原论文用的是字节对编码(Byte Pair Encoding,BPE)做子词切分,词表大小约 37000(En-De)或 32000(En-Fr)。这个细节后面 29 篇会专门讲,这里只要记住:
句子按长度排序后再分 batch(后面讲 batching 时再展开),最大长度截到 100 token 左右。
在 GPT-3 那个 570GB 数据、1750 亿参数的时代,回头看 WMT 2014 的几百万句对、6 千万参数的 Transformer,会觉得「就这?」
但要明白:2017 年这是相当不小的工程。8 张 P100 在那时候是顶级配置(A100、H100 还要等 4-5 年才出来),单机 8 卡训 12 小时跑出 SOTA,是一个大组才能做的实验。
而且更关键的是:原论文的训练配方在它的尺度下是经过仔细调过的——后来人复现,照搬这套配方,结果稳定可复现;这是一个工程上的重要保证。
NVIDIA P100(2016 年发布):
对比 2024 年的 H100:
也就是说,今天一张 H100 大概顶 50-60 张 2016 年的 P100。原论文「8 张 P100」的总算力,按今天看大约相当于 0.15 张 H100。这能训出 SOTA 翻译模型,确实是工程上的奇迹。
两个模型的训练时长,主要由模型大小、序列长度、batch 大小三个因素决定。换到现代 GPU,整个训练时长会被压缩到几小时甚至更短——但配方本身不会变。
8 卡之间的同步方式是「数据并行」(Data Parallel):每张卡拿一份 batch 子集,独立前向反向,最后用 all-reduce 把梯度加到一起,再各自更新参数(参数完全相同)。
具体的 all-reduce 实现,原论文没细说,应该用的是 NCCL 或类似的工具。因为只有 8 卡同机,all-reduce 的通信成本不大;如果跨机,就要考虑 ring all-reduce、梯度压缩等。
后来 GPT-3、LLaMA 的训练用了「数据并行 + 流水线并行 + 张量并行」三套并行方案叠加(3D Parallelism),那是为千卡规模准备的;2017 年单机 8 卡的尺度,纯数据并行就够了。
Adam(Adaptive Moment Estimation,Kingma & Ba, 2014)是当时最流行的自适应优化器。它维护两个移动平均:
更新规则:
m_t = β₁ · m_{t-1} + (1 - β₁) · g_t
v_t = β₂ · v_{t-1} + (1 - β₂) · g_t²
m̂_t = m_t / (1 - β₁^t) # bias correction
v̂_t = v_t / (1 - β₂^t)
θ_t = θ_{t-1} - lr · m̂_t / (√v̂_t + ε)
直觉:m 给方向(带动量),v 给步长(在梯度方差大的方向走小步、方差小的方向走大步)。Adam 比纯 SGD 在多数任务上更稳更快,缺点是参数多一份(每个参数要存 m 和 v,显存占用 3x)。
Adam 默认 β₂ = 0.999,但原论文用了 β₂ = 0.98。这是个小但重要的差异。
β₂ 控制 v(梯度平方的移动平均)的「记忆长度」。β₂ 越大,记忆越长,对噪声越鲁棒,但对学习率变化的响应越慢;β₂ 越小,记忆越短,对最近梯度更敏感。
β₂ = 0.999 对应「记忆 1000 步」,这个对长训练(百万步)很合适,但对 100k 步的训练就太长了——v̂ 的 bias correction 在前几千步还很大,可能导致更新不稳定。
β₂ = 0.98 对应「记忆 50 步」,更适合短训练,让 v 能更快跟上学习率的变化。这是原论文调出来的——后人复现 Transformer 翻译都沿用 0.98。
到了 GPT-3 时代,因为训练步数大幅增加(百万级),β₂ 又调回了 0.95 或 0.999。
Adam 的 ε 是分母里防止除零的小常数,默认 1e-8。原论文用 1e-9——比默认小一个数量级。
这个差异更细,影响主要在「梯度平方接近 0」时的步长。ε 小,分母可以更小,更新步长更大;ε 大,分母被压住,更新步长偏小但更稳。
实际复现时,ε 用 1e-8 还是 1e-9 影响不大,但要保持配方一致以便对照论文结果。
原论文没有 weight decay(权重衰减),这是另一个值得注意的点。
现代大模型(GPT-3、LLaMA)几乎都用 AdamW(Adam + decoupled weight decay),weight decay 设到 0.1 或 0.01。原论文那时还没有 AdamW(Loshchilov & Hutter 2019 才提出),他们也没用普通的 L2 正则。
为什么没用?我猜测两个原因:
到了现代大模型,因为参数量爆炸、数据相对模型容量未必充裕,weight decay 就重新成为标配了。
原论文的学习率公式是:
lr(step) = d_model^(-0.5) · min(step^(-0.5), step · warmup_steps^(-1.5))
第一次看这个公式,多数人都会愣一下:为什么是这种形式?三个超参数(d_model、step、warmup_steps)混在一起,min 还把两支函数粘在一起。
把它拆开看就清楚了。min 里面是两个函数:
两者交点在 \(\text{step} = \text{warmup\_steps}\) 处——你可以代入验证:当 step = warmup_steps 时,A = warmup_steps · warmup_steps^(-1.5) = warmup_steps^(-0.5) = B。
所以这个公式描述的是一条「先线性升、再 1/√step 衰减」的曲线,转折点在 warmup_steps 步:
「为什么要 warmup」是个非常有意思的问题,2017 年的时候大家也没完全说清楚,到 2020 年前后才有比较像样的理论解释。
直觉版的解释是这样的:
训练刚开始时,模型参数是随机初始化的,梯度方向「指向哪儿」非常嘈杂。Adam 的 v(梯度平方移动平均)需要积累几百到几千步才能给出一个相对靠谱的「方差估计」。
如果你一开始就用大学习率,会发生什么?
warmup 的作用是「给优化器和模型几千步缓冲,让 v 估计稳定下来、让参数从随机初始化温和地移动」。等到 v 比较准了,再放开学习率。
为什么是 4000 步?更准确地说,4000 是原论文在那套模型规模、batch 和硬件条件下选中的经验值。
从论文和后续公开复现里能看到一个稳定现象:warmup 太短,甚至直接去掉,训练更容易不稳定;warmup 太长,又会让前期大量 step 都停留在偏低的 lr 上,收敛速度变慢。4000 在当时那套配方里处在一个比较稳的折中点。
所以 4000 不是神秘常数,也不是所有 Transformer 都该照抄。它只是把「先稳住,再放开」这件事具体化到原论文那一档训练尺度上。
到了现代大模型,显式 warmup 仍然普遍存在,只是汇报口径常常不同:有的按 step 写,有的按 token 写,不能直接拿一个比例和原论文对齐。比如 GPT-3 写的是 375M tokens warmup,LLaMA-2 报的是 2000 steps。更稳妥的比较是:这些训练都没有把 warmup 彻底拿掉,只是在各自的训练长度和 batch 规模下重新选了尺度。
公式里 d_model^(-0.5)
这个因子的作用是「让学习率随模型宽度自动适配」。
直觉是:模型越宽,每个矩阵乘的输出方差越大;为了让参数更新幅度不随宽度爆炸,学习率要随宽度的某个幂次降下来。Vaswani 等人选了 -0.5 这个幂次,有点类似 Glorot/He 初始化的 1/√d 那个量。
具体说,对于 d=512:lr_peak = 512^(-0.5) · 4000^(-0.5) =
(1/22.6) · (1/63.2) ≈ 7e-4。
对于 d=1024(big):lr_peak = 1024^(-0.5) · 4000^(-0.5) ≈
4.95e-4。
big 模型的峰值学习率自动比 base 低一些——这正是「宽模型不能用太大 lr」这条经验的数学化。
warmup 之后按 1/√step 衰减。这个衰减率比 1/step(线性衰减)慢,比常数(不衰减)快——属于「中等强度」的衰减。
直觉是:
后来有些工作(GPT-3、LLaMA)改用「cosine decay」——按 cos 函数从峰值降到一个最小值(通常是峰值的 10%)。cosine 在工程上更平滑,但和 1/√step 在效果上差异不大。
标准的语言建模损失函数是交叉熵:模型给每个词输出一个概率分布 p,目标是让 p 与「真实分布 q」尽可能接近。
但「真实分布 q」长什么样?通常是 one-hot——目标词的概率是 1,其它所有词的概率是 0。
这个 one-hot 目标有两个问题:
label smoothing(Szegedy et al., 2016, Rethinking the Inception Architecture)的做法是把 one-hot 目标稍微「软化」:
q_i = (1 - ε) · 1[i = target] + ε / V
其中 ε 是平滑系数(原论文取 0.1),V 是词表大小。
直观上:目标词的概率从 1 变成 0.9,剩下 0.1 平均分给所有其它词。
这样一来:
这是 label smoothing 的「反直觉」之处。
PPL(perplexity)= exp(交叉熵),它衡量的是「模型对真实数据的概率估计」。如果你让模型「不那么自信」,它给目标词的概率自然降低,PPL 自然变差。
但 BLEU 衡量的是翻译质量——具体说,beam search 解码出的句子和参考译文的 n-gram 匹配率。这里关键的是 beam search 的多样性:当模型分布太 sharp 时,beam search 早期就被某条路径锁定,错过更好的路径;分布柔和一点,beam search 能保留更多备选,最终找到更优解。
label smoothing 的好处主要体现在 beam search 的过程中——它让模型给「次优词」保留合理概率,beam search 能在这些次优词上展开更深的搜索。
原论文的 ε = 0.1 是经验值。后来很多工作沿用,但也有些尝试不同值(0.05、0.15)的,差异不大。
GPT 系列、LLaMA 系列基本不用 label smoothing。原因之一是:自回归生成(一步一个 token)在 beam search 不再常用(greedy 或 sampling 是主流);原因之二是:超大词表(50k+)下,label smoothing 把 ε 平摊到几万个词上,每个词得到的「软概率」太小,效果不明显。
但翻译任务、专门用 beam search 解码的场景,label smoothing 仍然是标配。
dropout(Srivastava et al., 2014)是经典的正则化方法:训练时按一定概率随机把激活值置零,强制网络不依赖特定神经元。
原论文的 dropout 配置是 P_drop = 0.1,加在以下位置:
具体到一个 block 的写法:
x' = x + dropout(Attention(x))
y = x' + dropout(FFN(x'))
注意 dropout 是在「子层输出」上做的,不是在 attention 的内部 softmax 上。后来很多实现还会额外加:
这些是从 fairseq、Tensor2Tensor 等开源实现里来的微调,原论文没明确说。
经典视觉模型(VGG、ResNet 上的 dropout)常用 0.5。Transformer 用 0.1,差很多。
直觉是:Transformer 的层数和参数量已经很大,加上 LN、残差、label smoothing、weight tying 等多重正则,再加重的 dropout 反而让训练不稳定、收敛变慢。0.1 是「轻量正则化」,给训练加点扰动就够。
GPT-3 训练时 dropout=0(取消了),LLaMA 系列也是。原因:
但如果你做小数据量的 fine-tune,dropout 又会重新有用。它是「数据-模型容量比」的函数:数据相对模型小时有用,反过来没用。
原论文里有一句话:「Each training batch contained a set of sentence pairs containing approximately 25000 source tokens and 25000 target tokens.」
这句话的意思是:每个 batch 的大小不是「固定句对数」,而是「固定 token 数」。
这是一个非常重要的工程细节,但很多教程一带而过。
机器翻译的句子长度差异巨大——短的 5 个词,长的 100 个词。如果你按固定 batch_size = 64 做:
更糟糕的是,显存占用主要由 batch 内最长句子决定——padding 让 batch 实际大小是 64 × max_len,短句白白占着位置。
按 token 数 batch,行为完全不同:
显存占用稳定,GPU 利用率最大化。
典型实现是把训练数据按长度分桶(bucket),每个桶内长度相近:
bucket 0: 句子长度 1-10
bucket 1: 句子长度 11-20
...
bucket 9: 句子长度 91-100
每次从一个 bucket 抽样,凑够 25000 token 就提交一个 batch。这样既保证 batch 内长度均匀(padding 浪费小),又能在 token 维度上保持稳定的 batch 大小。
fairseq、Tensor2Tensor 的实现都是这一套。
GPT-3、LLaMA 训练时同样按 token 数算 batch,但因为是单语自回归(不是翻译),处理简单一些:把所有训练文本拼成一长串、按固定上下文长度(2048、4096)切,每片就是一个样本。这样每个样本天然等长,padding 完全不需要。
但 token-based batching 的核心思想——「用 token 作为基本计算单位、而不是 sample」——一直延续到今天。
训练时模型用「teacher forcing」——每一步看真实历史预测下一个词。但推理时没有真实历史,必须用模型自己生成的词作为下一步输入。
最直接的做法是 greedy:每一步选概率最高的词。但 greedy 有一个致命问题——「早期局部最优锁死后续」。比如:
greedy 看不到这个,它只看每一步当下的最优。
beam search 的想法是「保留多个候选路径」:
beam search 不能保证找到全局最优解(那需要遍历所有路径,exponential),但比 greedy 显著提升翻译质量。
beam search 有一个臭名昭著的 bias:短句子总概率高。因为每生成一个词都要乘一个 P < 1,句子越长概率越低,beam search 倾向输出短句。
length penalty 的作用是抵消这个 bias。原论文用 GNMT 提出的版本:
score(y) = log P(y) / lp(|y|)
lp(|y|) = ((5 + |y|) / 6)^α
α = 0.6 时,lp(|y|) 会随着长度增加而增大;用 log P(y) 除以它,相当于对更长的候选少一些惩罚,从而抵消 beam search 对短句的天然偏好。
实际效果:α = 0 时 beam search 输出明显偏短,α = 1 时偏长,α = 0.6 是中间的甜点。
GPT-3、LLaMA 的对话生成几乎都不用 beam search,而是用 temperature sampling、top-k、top-p(nucleus sampling)。原因:
但翻译、摘要这类「有标准答案」的任务,beam search 仍然占主流。
原论文公开了大部分细节,但仍然有一些边角细节没明确写。后人的复现实现(Google 自家的 Tensor2Tensor、Facebook 的 fairseq、HuggingFace 的 transformers)在以下点上有差异:
原论文是 Post-LN(每个子层之后做 LN)。但后来发现这个写法在大尺度下训练不稳定——梯度在深层会爆炸或消失。
Pre-LN(先 LN 再过子层)由 Xiong et al. 2020 系统分析后成为主流。GPT-3、LLaMA 都用 Pre-LN。
但在 base 6 层的尺度上,Post-LN 和 Pre-LN 表现相近,原论文用 Post-LN 没问题。
原论文有一行:「In the embedding layers, we multiply those weights by √d_model.」
也就是 token embedding 出来后要乘以 √d。这是为了让 embedding 的方差和位置编码的方差对齐——位置编码 sinusoidal 的幅度大约是 1,而 embedding 初始化方差 1/d,乘 √d 后方差变成 1。
这是论文里写了的,但很容易漏。fairseq 默认乘了,HuggingFace 的早期 transformers 实现里有过 bug。
前面提到,原论文的 dropout 加在「子层输出」「embedding 之后」。但具体到细节:
这些差异让不同实现的 BLEU 在 ±0.2 范围内浮动。
原论文没明确说初始化方法。Xavier 是默认。但具体是 Xavier-uniform 还是 Xavier-normal?fan_in 还是 (fan_in + fan_out) / 2?这些细节不同实现有差异。
后来 GPT-2 系列用了一个特殊缩放:W₂(FFN 输出投影)和 W_O(attention 输出投影)的初始化额外乘 1/√(2 n_layers),目的是让深层残差累积方差不爆。这是 GPT-2 的细节,原 Transformer 没用。
如果你照着论文复现,真正要对齐的不是某一个「神奇参数」,而是一串小细节是不是同时一致:LN 放在哪里、embedding 是否乘 \(\sqrt{d}\)、dropout 放在哪一层、解码时用什么 BLEU 脚本,以及最后是否做 checkpoint averaging。fairseq 可以作为一个对照实现,但更重要的是把「论文明确写出的部分」和「开源实现补出来的部分」分开看。
原论文没有给出完整的 step-by-step 曲线,所以这里更合适的说法不是「精确到每个阶段 loss 多少」,而是「看曲线形状」。公开复现通常会看到三件事:warmup 阶段下降最快;中段进入稳定下降区;后段 BLEU 继续涨,但边际收益明显变小。
把图当成示意图更合适,而不是某一份官方日志的逐点复刻。你真正该关心的是:曲线有没有在 warmup 结束前后突然失稳,验证集是否和训练集大体同向,以及 50k 之后收益是否已经开始明显变慢。
时隔 8 年,原论文的训练配方哪些被沿用、哪些被改了?
最有意思的观察是:虽然 scale 变了 1000 倍,但训练配方的「形状」基本没变。
这背后其实有一个深层原因:梯度下降优化的几何性质并不随模型规模变化太多。warmup 还是要 warmup(梯度估计需要稳定时间),衰减还是要衰减(后期需要小步精修)——这些「软规律」在不同 scale 下都有效。
把原论文 vs LLaMA-2 vs GPT-3 的配方放到一张表里,差异看得很清楚:
| 配方项 | Transformer base (2017) | GPT-3 175B (2020) | LLaMA-2 70B (2023) |
|---|---|---|---|
| 优化器 | Adam | Adam | AdamW |
| β₁ | 0.9 | 0.9 | 0.9 |
| β₂ | 0.98 | 0.95 | 0.95 |
| ε | 1e-9 | 1e-8 | 1e-5 |
| weight decay | 0 | 0.1 | 0.1 |
| warmup steps | 4000 | 375M tokens | 2000 |
| LR schedule | inverse √step | cosine | cosine |
| LR peak | ~7e-4 (d=512) | 6e-5 (d=12288) | 1.5e-4 (d=8192) |
| dropout | 0.1 | 0 | 0 |
| label smoothing | 0.1 | 0 | 0 |
| batch (tokens) | 25k src + 25k tgt | 3.2M | 4M |
| 训练 token | ~3B (En-Fr) | 300B | 2T |
| 训练步数 | 100k (base) / 300k (big) | ~95k | ~500k |
| 精度 | FP32 | FP16 + dynamic loss scale | BF16 |
| 梯度 clip | 0.0 (无) | 1.0 | 1.0 |
读这张表的几个有趣观察:
把 base 模型的训练 FLOPs 估算一下:
8 张 P100 的 FP16 算力 ≈ 8 × 19 = 152 TFLOPS。如果利用率 50%,单步耗时 ≈ 1.95e13 / 7.6e13 ≈ 0.26 秒;100k 步 ≈ 7.2 小时。考虑数据加载、通信开销,实际 12 小时合理。
原论文 Table 2 给出了和 ConvS2S、GNMT、ByteNet 的对比。Transformer base 在 BLEU 上同时打过这三家,而且训练成本只有 GNMT 的 ~1/10、ConvS2S 的 ~1/30。
这个「算力 × 性能」的甜蜜点,是 Transformer 当年 take over 整个 NLP 的核心动力——不是它能做新事情,而是它能用更少算力做得更好。
到 2024 年,Attention Is All You Need 的引用数已经超过 12 万次(Google Scholar 统计),是 21 世纪被引最多的 AI 论文之一。
更重要的是:几乎所有现代大语言模型——GPT 系列、Claude、Gemini、LLaMA、Qwen、DeepSeek——都基于这个 2017 年提出的架构,只是 scale 大了 1000 倍、加了一些边角改进(RoPE 位置编码、SwiGLU FFN、RMSNorm 等)。
架构稳定 8 年是非常罕见的。深度学习历史上,每隔几年就有「这次真的不一样」的新架构出现(LSTM → GRU → Transformer),但 Transformer 的根基一直没被撼动。
为了让公式从纸上走到具象,把原论文的配方带几个具体的 step 进去算一下。
设 d_model = 512, warmup_steps = 4000。
step = 1: - 函数 A:1 · 4000^(-1.5) = 1 / 252982 ≈ 3.95e-6 - 函数 B:1^(-0.5) = 1.0 - min(A, B) = A = 3.95e-6 - lr = 512^(-0.5) · 3.95e-6 = (1/22.627) · 3.95e-6 ≈ 1.75e-7
非常小的学习率。这是 warmup 第一步的样子——模型几乎不动,等 v 估计稳定。
step = 1000: - 函数 A:1000 · 4000^(-1.5) ≈ 3.95e-3 - 函数 B:1000^(-0.5) ≈ 0.0316 - min = A = 3.95e-3 - lr ≈ (1/22.627) · 3.95e-3 ≈ 1.75e-4
已经不算小,但还在 warmup 中(step < 4000),仍按线性增长。
step = 4000(warmup 完成的拐点): - 函数 A:4000 · 4000^(-1.5) = 4000^(-0.5) ≈ 0.01581 - 函数 B:4000^(-0.5) ≈ 0.01581 - 两者相等 - lr = (1/22.627) · 0.01581 ≈ 6.99e-4
这是峰值学习率 ≈ 7e-4。论文配方就是按这个峰值的。
step = 16000(4 倍 warmup): - 函数 B:16000^(-0.5) ≈ 7.91e-3 - lr ≈ (1/22.627) · 7.91e-3 ≈ 3.5e-4
峰值的一半。
step = 100000(base 训练结束): - 函数 B:100000^(-0.5) ≈ 3.16e-3 - lr ≈ (1/22.627) · 3.16e-3 ≈ 1.4e-4
降到峰值的 1/5 左右。这是训练后期的精修阶段。
把这 5 个 step 连起来看,就是图里那条曲线——先线性快速升到 7e-4,然后慢慢降到 1.4e-4,整个过程跨越 100k 步。
把 warmup 从 4000 改成 8000,重算:
step = 4000(中点): - A:4000 · 8000^(-1.5) = 4000 · 1/715541 ≈ 5.59e-3 - B:4000^(-0.5) ≈ 0.0158 - min = A = 5.59e-3 - lr ≈ 5.59e-3 / 22.627 ≈ 2.47e-4
比 warmup=4000 的峰值(7e-4)小 2.8 倍。也就是说,warmup 拉长一倍,整个训练前期的 lr 都偏低,模型「学得慢」——这就是图里 warmup=16000 那条曲线偏右下的原因。
新峰值出现在 step = 8000: - lr_peak = 512^(-0.5) · 8000^(-0.5) = (1/22.627) · 0.01118 ≈ 4.94e-4
比 warmup=4000 的 7e-4 低 30%。
直观结论:warmup 长度越长,峰值学习率越低。这是公式 lr_peak = d^(-0.5) · warmup^(-0.5) 直接给出的。
如果你的训练总步数本来就少(比如只有 10k 步),warmup 取 4000 已经占了 40%——前期太慢,后期没几步收敛。这时该把 warmup 缩到 1000 左右。
把 d 从 512 改成 1024(big 模型):
step = 4000 处的峰值: - lr_peak = 1024^(-0.5) · 4000^(-0.5) = (1/32) · (1/63.25) ≈ 4.95e-4
比 d=512 的 7e-4 低约 30%。这印证了 「模型越宽、学习率越低」 的经验,是公式自动给出的。
实际工程里你会看到很多大模型的峰值 lr 在 1-3e-4 量级——它们的 d 通常 4096-12288,按 d^(-0.5) 缩,比 d=512 的 7e-4 低好几倍。
把整个训练 loop 用伪代码写一遍,所有上面提到的部件放到位:
import math, torch
from torch.optim import Adam
def lr_schedule(step, d_model, warmup_steps):
return d_model ** -0.5 * min(step ** -0.5,
step * warmup_steps ** -1.5)
# 模型与优化器
model = Transformer(d_model=512, n_layers=6, n_heads=8, d_ff=2048,
vocab_size=37000)
optim = Adam(model.parameters(), lr=1.0,
betas=(0.9, 0.98), eps=1e-9)
# 注意:lr=1.0 是占位,真实 lr 由 schedule 给
# Label smoothing 损失
loss_fn = LabelSmoothingLoss(eps=0.1, vocab_size=37000, pad_id=0)
step = 0
for batch in dataloader: # batch 已经 token-balanced
step += 1
# 1. 设定本步学习率
lr = lr_schedule(step, d_model=512, warmup_steps=4000)
for g in optim.param_groups:
g["lr"] = lr
# 2. 前向
logits = model(batch.src, batch.tgt[:, :-1]) # teacher forcing
loss = loss_fn(logits, batch.tgt[:, 1:])
# 3. 反向 + 更新
optim.zero_grad()
loss.backward()
optim.step()
# 4. 日志
if step % 1000 == 0:
print(f"step {step} lr {lr:.2e} loss {loss.item():.3f}")
if step >= 100_000:
break这段代码概念上完整,但工程上还差几样东西:
loss.backward() 之后要做
DDP 的梯度同步(PyTorch DistributedDataParallel
自动处理);torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0),防止偶发的大梯度把训练弄崩。原论文没明确说,但
fairseq 默认开启;加上这些工程细节,单文件训练脚本大概 300-500 行,可读性还可以——这正是 fairseq 早期版本的水平。
原论文报最终 BLEU 时用了 checkpoint averaging:不是直接拿最后一个 checkpoint,而是把训练末尾若干 checkpoint 的参数做平均,再用平均后的模型解码。这个细节和前面的学习率、dropout 一样,都属于「论文结果为什么能对上」的一部分。
它的作用不是创造新的能力,而是把训练末期参数抖动带来的噪声压平。不同实现里平均几个 checkpoint、间隔多久保存一次会有差异,所以这里更适合把它当成复现细节,而不是脱离上下文的「万能提分术」。
在收尾之前,我想再补几个原论文文本里写了、但读快了容易错过的工程细节。这些细节单看每个不大,但加在一起决定了你能不能复现到 BLEU 27.3 这个具体值。
原论文 §3.4 提到一句:「we share the same weight matrix between the two embedding layers and the pre-softmax linear transformation」。
意思是:
这三个矩阵共享同一份参数——共用一个 [vocab_size, d_model] 的矩阵。
为什么这么做?
第一,省参数。词表 37000、d=512,单矩阵 19M 参数;如果三个独立,就是 57M。共享省下 38M,相当于一个完整的 base 模型大小。
第二,正则化效果。三个矩阵被强制保持一致,相当于「输入 → 表示」和「表示 → 输出」用同一个映射的转置——这种对称性约束让训练更稳。
第三,在小词表上几乎不损失性能;在大词表(GPT-3 的 50k)上仍然有效。
后来 Press & Wolf(2017)写了一篇专门的文章 Using the Output Embedding to Improve Language Models 系统验证了 weight tying 的好处。
实现上:
# 共享一个 nn.Embedding
shared_embed = nn.Embedding(vocab_size, d_model)
# 输出投影把 embedding 矩阵转置
output_logits = hidden @ shared_embed.weight.T很多后来的实现会把 W_Q、W_K、W_V、W_O 的 bias 关掉,但这不是原论文正文里明确规定的训练配方。复现时它可以作为需要核对的实现差异之一,但优先级应低于 LN 位置、embedding 缩放、dropout 和 averaging 这些更直接影响论文结果对齐的细节。
前面说过 dropout 加在「子层输出」。但实际 fairseq 实现还在 attention 的 softmax 之后加了一个 dropout:
attn_weights = softmax(QK^T / sqrt(d_k))
attn_weights = dropout(attn_weights) # 这一步原论文没明说
output = attn_weights @ V这个细节很微妙——它让某些 token 在某些 head 上的「看到」被随机屏蔽,强迫不同 head 不依赖单一来源。后来很多实现沿用,HuggingFace 默认开启。
2017 年 P100 已经支持 FP16,但混合精度训练还不成熟(Apex 是 2018 年的事)。原论文用的是纯 FP32。
但他们提到:「We used 8 NVIDIA P100 GPUs.」P100 的 FP16 算力是 FP32 的 2 倍——意味着训练时其实没用上 FP16 的加速。
到了 2020 年之后,BF16 训练成为大模型主流(更少精度问题),FP16 + loss scaling 也很流行。这块工程演化非常快。
对这篇论文来说,评测里最重要的不是再讲一遍 BLEU 公式,而是记住:不同脚本和分词口径算出来的 BLEU 不能直接横比。原论文 2017 年用的是 multi-bleu.perl 一类的脚本,今天很多人习惯用 SacreBLEU;同一个模型,数字差零点几到 1 分并不稀奇。
所以当你说「我复现到 27.3 了吗」时,至少要先对齐三件事:测试集是不是同一个,分词和大小写口径是不是同一个,checkpoint averaging 和解码超参数是不是同一个。否则你比较的可能不是模型,而是评测脚本。
误解一:warmup 可有可无,去掉只是慢一点。
错。对 Transformer 这类模型来说,warmup 不是装饰项。把它缩得过短,甚至直接去掉,训练稳定性通常会明显变差,因为 Adam 的二阶统计在起步阶段还没站稳。
误解二:Transformer 用 Adam 默认参数就行。
错。原论文用的是 β₂ = 0.98、ε = 1e-9,而不是很多框架里常见的默认值。是不是一定会掉很多分,取决于实现和数据,但如果你想复现论文结果,先把这些超参数对齐是更稳的做法。
误解三:label smoothing 让 PPL 变差,所以是个错误的设计。
不对。label smoothing 让训练 PPL 变差是预期内的(因为最优分布被人为软化),但 BLEU 变好——这才是机器翻译关心的指标。PPL 和 BLEU 不总是同向。
误解四:beam search size 越大越好。
错。beam=4 是原论文的选择。beam 再继续加大,并不会自动带来更好的翻译,很多时候只是把搜索算力花在非常相近的候选上;是否继续受益,还要看任务、length penalty 和评测口径。
误解五:现代 LLM 训练完全用了不一样的配方。
部分对。变了很多细节(dropout=0、weight decay=0.1、cosine 衰减等),但「形状」没变——仍然是 Adam 系、warmup、token-based batching、按学习率峰值的某个分数衰减。Transformer 训练的「骨架」是 2017 年定下来的。
下一篇 28|原论文实验结果 会从「训练配方」切到「实验结果」——具体数字、消融实验、注意力可视化。我们会讲:
再往后:
← 上一篇:26|前馈网络 | 下一篇:28|原论文实验结果 →
把当前热点继续串成多页阅读,而不是停在单篇消费。
2026-04-15 · transformer
神经网络真正会「学习」靠的是两件事:把误差变成可微分的损失函数,再沿着这个损失对参数的梯度方向一点点往下挪。本文从一维抛物线讲到多变量梯度,从两层网络的手算反向传播讲到为什么 backprop 是 O(参数量),再到 Transformer 为什么几乎一律选 Adam/AdamW,希望把「网络是怎么学的」这件事彻底讲透。
2026-04-15 · transformer
这篇文章只比较文本模型里最重要的三类预训练目标:GPT 的自回归语言建模、BERT 的掩码语言建模、T5/BART 的去噪式序列到序列。重点不是背定义,而是看清它们各自优化什么接口、为什么迁移方式不同,以及为什么通用 LLM 最后大多落到 decoder-only 的 next-token prediction。
2026-04-15 · transformer
把《Attention Is All You Need》的实验结果拆开看:WMT14 英德与英法任务上的 headline number 到底意味着什么,为什么 8 张 P100、3.5 天训练就足以压过当时最强的 RNN 与 CNN 路线,注意力可视化又真实说明了什么,哪些地方是结论,哪些地方只是 2017 年特定 benchmark 下的胜利。
2026-04-15 · transformer
把 Transformer block 里那个看起来最不起眼的两层 MLP 真正讲清楚——4 倍扩张比的来历、逐位置而不是跨位置的设计、Geva 等人 2021 年提出的「键值记忆」视角、SwiGLU/GLU/GeGLU 的现代变体、参数量分布、可解释性研究、量化时的瓶颈,以及它和 MoE 的关系。
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。