读懂一份有代表性的 LLM 训练配置(学习率、预热、裁剪、权重衰减),并解释每个旋钮在防止什么出错。
在深层 Transformer 上,交叉熵 + AdamW 还不够——没有特定的工程技巧,损失会发散、梯度会爆炸、模型会过拟合。
术语表 · 6 个术语
- AdamW
- LLM 预训练的主流优化器。Adam(逐参数自适应步长)加上解耦的权重衰减(即那个 W)。
- learning rate warmup
- 在训练最初约 1-10% 的步数里,从 0 线性爬升到 lr_max。让优化器的滑动平均先稳定下来,再迈大步。
- cosine decay
- 预热之后,学习率沿半个余弦波从 lr_max 降到 lr_min,覆盖剩余的全部步数。LLM 预训练的标准做法。
- gradient clipping
- 若 ||g|| > c(通常为 1.0),就把 g 缩放为 c/||g||。当坏 batch 产生巨大梯度时给步长封顶。
- weight decay
- 与 ||θ||^2 成正比的惩罚项,加进损失(或者像 AdamW 那样,直接从权重里减去)。把权重拉向零,起正则化作用。
- dropout
- 训练时随机把一部分激活置零,防止模型过度依赖任何单一特征。旧模型里常见;现代 LLM 预训练往往不用——数据集的多样性完成了正则化。
扩展与正则化:让训练循环真正收敛
第 13 章把训练化简成一行交叉熵。这个描述正确,但作为配方惊人地不够用。一个用朴素 SGD 在数千亿 token 上训练的 24 层 Transformer 会:在前 100 步内发散;因为单个坏 batch 让损失飙到 NaN(Not-a-Number——算术爆炸到浮点数装不下);落进一个泛化很差(训练文本上表现好、没见过的文本上表现差)的局部极小值。解法是一小撮工程技巧——没有一个属于模型架构,但每一次现代训练都全部用上。
先问一句:这里说的“扩展”到底是什么?标题里这个词指的是参数量。一条直线 y = a·x + b 有两个参数。这个模型有将近十亿个。最大的研究模型则有几千亿个。下面这个阶梯把它们全都放到同一条对数轴上,好让这道鸿沟看得清楚——也好对“这个模型究竟处在什么位置”保持诚实。
从 GPT-2 到 GPT-3 这一跳几乎是纯粹的扩展:同一套配方,参数约 100 倍,输出就从“语法通顺但说胡话”变成了出人意料的连贯。
这根横档为什么放在本章:堆参数不会自动导致过拟合。在足够多文本上训练的更大模型,泛化得更好而非更差——这正是下面权重衰减和 dropout 这些旋钮成为一个有讨论价值的问题(而不是显而易见的稳赢)的原因。你是在“模型本就巨大、数据本就海量”的前提下,去调正则化的。
在讲技巧之前,先说整个扩展故事赖以成立的一个前提:Transformer 之所以值得扩展,是因为它的训练计算几乎全是对所有位置同时进行的矩阵乘法。RNN 必须算完第 i 个 token 才能碰第 i+1 个;Transformer 一次并行前向就处理整个序列——因果掩码正是让这次并行保持诚实的那道闸门——而巨大的批量矩阵乘法恰好是 GPU 为之而生的工作负载。(我们这个模型的 GatedDeltaNet 层在解码时确实是逐 token 循环运行的;并行优势说的是训练和预填充。)这才是这个架构真正买到的东西:不是某种单一的聪明行为,而是便宜到可以一路扩展、直到聪明行为自己涌现的计算。
优化器:AdamW,而不是 SGD
朴素的随机梯度下降(θ := θ - η·g——把每个权重 θ 沿其梯度 g 的反方向微调,按学习率 η 缩放)在 Transformer 上效果不佳。不同参数看到的梯度量级天差地别——单一的全局步长,要么对响亮的参数太大,要么对安静的参数太小。Adam 为每个参数维护 g 和 g² 的滑动平均,再按 √g² 归一化迈步,于是每个参数的步长由它自己的梯度历史重新标定,而不是共用一个速率。“滑动平均”(论文称之为矩,moments)一点也不玄: avg ← 0.9·avg + 0.1·g——保留 90% 的昨日估计,混入 10% 的新梯度。AdamW 加上了解耦权重衰减:不是通过损失去惩罚 ||θ||²,而是优化器在每一步直接从 θ 里减去它的一小部分。为什么要把权重往零拉?大权重让单个特征压过一切;保持权重小,迫使模型把证据分散到许多特征上。这是每个现代 LLM 预训练的标准配方。
学习率调度:预热 + 余弦
这是 LLM 训练里最普及的技巧。学习率不是常数——它走一条山丘形的曲线:
- 线性预热:训练最初约 1-10% 的步数里,从 0 升到
lr_max。 - 余弦衰减:剩余步数里,从
lr_max降到lr_min(≈1e-5)。
预热存在,是因为 AdamW 的滑动平均需要几百步的梯度历史才有意义;第 1 步就迈满步长,基本等于朝随机方向开枪。余弦衰减存在,是因为训练后期受益于越来越小的步长——模型离极小值更近,大步会把它弹出去。
标准配方:最初约 10% 的步数里从 0 线性预热到 lr_max,随后余弦衰减到约 1e-5。预热让早期梯度不至于在优化器的滑动平均尚未积累时爆掉;衰减让训练后期安顿在一个低损失的平台上。
把预热拉到 0,看曲线直接从 lr_max 起步——这就是没有预热时发生的事。把峰值学习率调得更高(例如 1e-3),你就能看出为什么没有梯度裁剪时训练初期会发散:在一个全新的模型上,任何一记大步都会把权重带到无法恢复的地方。
足够大的学习率作用在近乎随机的初始权重上,可能产生一次巨大的首步,让损失不降反升。预热在训练最初约 1–10% 的步数里把学习率从 0 拉到 lr_max,让这些早期步保持温和;随后由余弦衰减接手。下方的无预热曲线是刻意激进的设定——峰值学习率 1e-3,远高于上方调度所用的 3e-4——为的是让爆炸清晰可见;更温和的设定可能只是抖一抖又恢复,而不会彻底发散。许多「损失在第 1 步就爆炸了」的故事背后,就是这个形状。
仅为示意——两条曲线都是脚本化的公式(指数衰减 vs 被钳制的爆炸),不是真实训练输出。损失轴为了易读而被压缩:对这么大的词表,真实随机初始化交叉熵约为 ln(248,320) ≈ 12.4,而不是 6.5。
梯度裁剪:一行代码的护栏
单个坏 batch——比如一段全是空白的文本,或一个分词器的边角案例——能产生巨大的梯度。没有保护时,AdamW 会尽职尽责地朝那个方向迈出巨大一步,损失从 2.5 跳到 8.0,模型要花几百步才能恢复(如果还能恢复的话)。
按范数裁剪(clip-by-norm)是通用答案。计算模型全部梯度的全局 L2 范数,若超过裁剪阈值 c(几乎总是 1.0),就把每个分量按 c / ||g|| 缩放。方向不变、幅度封顶,训练平稳继续。下面的小部件让你把两个旋钮都拧拧看。
反向传播算出逐参数梯度后,取它们的全局 L2 范数 ||g||——每个分量平方、求和、再开方。若超过裁剪阈值 c,就把每个分量按 c / ||g|| 缩放。方向保持不变;只有幅度被封顶。一个小算例:g = [3, 4] → ||g|| = √(9 + 16) = 5;裁剪到 c = 1.0 → 乘以 1/5 = 0.2 → [0.6, 0.8]——方向相同,长度只剩五分之一。
把梯度缩放往上拉——超过 ~0.07 后你会看到 ||g|| 超出阈值、裁剪面板变成琥珀色。没有这道护栏,单个坏 batch(序列中段的损失尖峰)就能把 24 层堆叠的权重推进训练无法恢复的区域。c = 1.0 是 LLM 预训练的默认值。
正则化:dropout 的缓慢退场
原始 Transformer 论文激进地使用 dropout——每个注意力层、每个 MLP、每条残差连接。现代 LLM 预训练配置通常把 dropout 设为 0。两个原因:
- 预训练数据极其充裕。在数万亿 token 的语料上,模型对每个 token 大约只看一次。不存在可供 dropout 预防的“背下训练集”失效模式。
- 权重衰减覆盖了大部分相同的地盘。AdamW 的衰减项把权重拉向零,防止任何单一特征独大。
微调是另一回事——小而精的数据集可能被过拟合(模型背下那些具体样例,而不是学到可迁移的模式),dropout 在微调配方里常以非零值(通常 0.05-0.1)重新出现。
读一份有代表性的配置
上面这些技巧的组合,把一次训练从“立刻发散”变成“终于收敛”。一份有代表性的预训练配置——泛指,不是任何特定模型公布的设置——大致长这样:
optimizer: AdamW(beta1=0.9, beta2=0.95, eps=1e-8, weight_decay=0.1) lr: 3e-4 peak, 2000 warmup steps, cosine decay to 1e-5 grad_clip: 1.0 (clip by global norm) dropout: 0.0 (pretraining) batch_size: 4M tokens (gradient accumulation across many devices) seq_len: 8192 total_steps: 500,000
其中两个数字值得乘出来。“4M token 的 batch”是每个优化器步 512 sequences × 8,192 tokens = 4,194,304 ≈ 4M 个 token。而整次训练是 4M × 500,000 steps ≈ 2 trillion tokens——这就是训练一章反复念叨的那个“数万亿”。
这块配置里不那么显然的旋钮,用大白话说:
beta1 = 0.9、beta2 = 0.95——AdamW 两个滑动平均的遗忘速度。beta1 平滑梯度g;beta2 平滑梯度的平方g²。值越高 = 记忆越长。eps = 1e-8——加在分母里的微小常数,保证当某个参数的g²平均接近零时,步长不会除以零。weight_decay = 0.1——上文那股拉向零的力的强度;0.1 是典型的预训练取值。- 梯度累积(gradient accumulation)——先把几个小 batch 的梯度加起来,再做一次优化器步,让少量 GPU 也能模拟出一个它们根本装不进内存的 4M token 巨型 batch。
这里的每一行,都是针对过去十年 LLM 训练中用血泪换来的某个具体失效模式的护栏。架构是模型本身;而这份配方,才让架构得以训成。
想学更多
本章是整门课中刻意最轻的一章——不内化这里的每个细节也能训练 LLM,但不认识这些旋钮就读不懂研究论文。这套代码库里可训练的那一侧,见 @mlx-node/trl(GRPO 与 SFT)和 crates/mlx-tui(mlx-train TUI 程序)——它们在 Apple Silicon 上实现的是同一份配方。
- 深层 Transformer 不是用固定学习率训练的——预热接余弦是标准调度,也是训练初期损失曲线保持正常的原因。
- 梯度裁剪是一行代码的护栏,把“模型在第 3,247 个 batch 上发散了”变成损失曲线上的一个小鼓包。
- dropout 基本退出了现代 LLM 预训练;权重衰减 + 数据规模 + 早停纪律取代了它。
在学习率小部件里,把预热设为 0、峰值学习率设为约 1e-3,然后看曲线的开头。对一个全新初始化的模型,用这个学习率直接开训会发生什么?余弦的那一半为什么存在?