上一课结束以后,我们其实已经走到了一个非常微妙的位置。
模型预测:
今天天气真 → 冷
训练数据告诉它:
正确答案应该是“好”。
于是 Cross Entropy(交叉熵)算出了 Loss。
然后 Backpropagation(反向传播)一路往回算。
LM Head 有 Gradient。
MLP 有 Gradient。
Attention 有 Gradient。
W_Q、W_K、W_V也可能有 Gradient。
甚至最前面的 Embedding,都可能收到属于自己的梯度信号。
到这里,一个很容易让人产生错觉的念头就出现了:
梯度都有了,训练是不是已经结束了?
还真没有。
因为 Gradient(梯度)解决的只是:
往哪边改。
可真正训练一个模型,还必须回答另一个问题:
到底改多少?
这两个问题看起来只差几个字,实际上隔着整个 Optimization(优化)世界。
先记住今天第一句话:
梯度只负责告诉你“哪边是下坡”,优化器负责决定“这一步到底怎么下”。
这就是我们今天要讲的东西。
SGD、Momentum、Adam、AdamW,看起来像四个算法。
但如果真的从第一性原理往下推,你会发现:
它们不是四个孤立的名词。
而是一条非常自然的进化路线。
前一个方案暴露一个问题。
下一个方案来补这个洞。
继续暴露问题。
再继续修。
学会这条推导链以后,你基本不需要死背 AdamW 的公式。
把整条优化器演化路线压缩成一眼能看懂的关系:
| SGD | → | Momentum | → | Adam | → | AdamW |
| 看当前梯度 | 加入方向历史 | 再加入尺度历史 | 解耦 Weight Decay |
一、先别想几百亿参数,我们把整个大模型压缩成一个数字
假设现在模型只有一个 Parameter(参数):
θ
当前:
θ = 2
经过前向传播、Loss、反向传播以后,我们算出:
∂L / ∂θ = 0.3
上一课已经知道,这个0.3并不是在说:
“参数错了 0.3。”
它真正表达的是:
站在当前这个位置附近,如果 θ 往增大的方向移动,Loss 倾向于上升。
那我们想让 Loss 下降怎么办?
当然应该反方向走。
所以最自然的更新:
θ_new = θ_old − η · ∂L / ∂θ
这里出现了一个以后几乎会贯穿整个训练过程的符号:
η
Learning Rate。
中文叫:
学习率。
假设:
η = 0.1
那么:
θ_new = 2 − 0.1 × 0.3 = 1.97
就这么一下。
参数从:
2
变成:
1.97
训练真正发生了。
注意这里特别容易被忽略的一件事:
Gradient 是:
0.3
但 Parameter 并没有直接:
2 → 1.7
而是:
2 → 1.97
为什么?
因为:
Gradient ≠ Parameter Update
Gradient 是坡度。
Learning Rate 是步幅。
所以第一层关系一定分清:
梯度决定方向,学习率决定你敢走多大一步。
二、Optimizer 到底是什么?把它从“神秘算法”还原成一句人话
很多人第一次接触 Optimizer(优化器),容易觉得它非常玄学。
Adam。
AdamW。
Momentum。
名字一个比一个像论文黑话。
其实站在最底层看:
Optimizer 干的事情非常朴素。
它拿到:
当前 Parameter。
当前 Gradient。
以及自己保存的一些历史状态。
然后计算:
Δθ
最后执行:
θₜ₊₁ = θₜ + Δθ
仅此而已。
它不知道什么叫中文。
不知道什么叫 Attention。
也不知道模型刚刚是在预测天气,还是在写 Python。
它甚至不知道自己正在训练一个 Large Language Model。
对 Optimizer 来说:
W_Q
和:
MLP Weight
本质上都是 Tensor。
它真正做的是:
把 Gradient 翻译成 Parameter Update。
这也是今天非常重要的一句话:
Loss 决定“错得多严重”,Gradient 决定“往哪里改”,Optimizer 决定“具体怎么改”。
三、如果 Gradient 已经有方向,最简单的方法当然是直接走
最朴素的更新就是:
θₜ₊₁ = θₜ − ηgₜ
其中:
gₜ = ∇θ Lₜ
这就是 Gradient Descent:
梯度下降。
如果每一次更新都使用完整训练集计算 Gradient,我们可以把它理解成普通 Gradient Descent。
但大模型训练显然不可能这么干。
想象一下。
如果你的预训练数据有:
几万亿 Token。
每修改一次 Parameter 之前,都要求:
“先把几万亿 Token 全跑一遍,算出整个 Dataset 的精确 Gradient。”
训练基本不用做了。
所以真实训练会把数据切成一个个:
Batch。
甚至是更小的:
Micro Batch(微批次)。
当前这个 Batch 计算一个 Loss:
L_B
然后得到:
gₜ = ∇θ L_B
用这个 Gradient 更新 Parameter。
下一批再重新算。
这就是为什么我们经常看到:
SGD。
Stochastic Gradient Descent。
随机梯度下降。
这里的“随机”不是:
Optimizer 随便猜方向。
而是:
每一次更新看到的只是整个训练数据的一部分。
这一点一旦理解,下面所有问题就自然出来了。
四、麻烦来了:一个 Batch 说往左,下一个 Batch 可能叫你往右
假设某个 Parameter 连续收到四次 Gradient:
第一次:
+0.8+0.8
第二次:
+0.7+0.7
第三次:
+0.9+0.9
第四次突然:
−0.1-0.1
如果使用最朴素的 SGD:
前三步一直往一个方向走。
第四步看到:
−0.1-0.1
马上掉头。
这里就出现了一个特别值得想的问题:
前三批都说:
往这边。
第四批只是轻轻说了一句:
好像应该反过来一点。
你真的应该立刻掉头吗?
不一定。
因为当前 Batch 给出的 Gradient 并不是“宇宙真理”。
它只是:
这一批数据的意见。
换一批样本,意见就可能发生变化。
换句话说:
Mini-Batch Gradient 天生带 Noise:
噪声。
于是最朴素 SGD 的第一个问题出现了:
它太健忘。
每一步都像刚出生一样。
只看现在。
不看过去。
这就很奇怪了。
如果过去连续十步都在告诉你:
“这个方向是对的。”
为什么第十一步来了一点小波动,你就立刻把前面的历史全部推翻?
于是,人们给 Optimizer 加了第一种“记忆”。
Momentum。
五、Momentum:别让优化器每走一步都失忆
Momentum:
动量。
名字其实取得特别好。
想象一个球正在山坡上滚。
如果它已经连续往东滚了很长一段距离,突然撞上一颗很小的石头。
它不会瞬间掉头往西飞。
因为它有:
Inertia。
惯性。
优化也是一样。
我们不希望 Parameter Update 只取决于:
“当前 Batch 刚刚说了什么。”
我们还希望它参考:
“最近很多 Batch 一直在说什么。”
于是可以维护一个历史状态:
mₜ = βmₜ₋₁ + (1 − β)gₜ
然后:
θₜ₊₁ = θₜ − ηmₜ
不要急着背公式。
先看:
mₜ
和:
gₜ
有什么区别。
g_t:
这一轮 Gradient。
m_t:
最近一段时间 Gradient 的平滑趋势。
这就是 Momentum。
它第一次让 Optimizer 拥有了:
历史。
六、Momentum 最厉害的地方,不是“更快”,而是能压住无意义震荡
想象一个狭长山谷。
左右两侧特别陡。
真正的谷底却在前方。
如果你每一步完全按照当前 Gradient 走,很可能出现:
左。
右。
左。
右。
左。
右。
一边向前。
一边疯狂横跳。
为什么?
因为左右方向很陡。
只要稍微偏一点,Gradient 就会猛烈把你往另一边推。
于是很多计算都消耗在:
来回震荡。
Momentum 会发生什么?
如果某个方向:
这一轮向左。
下一轮向右。
再下一轮又向左。
长期来看:
互相抵消。
反过来,如果另一个方向:
连续十几步都指向前方。
它就会不断积累。
于是:
不稳定方向被削弱。
长期一致方向被强化。
这时候再看 Momentum,就不应该只记:
“它可以加速 SGD。”
更值得记住的是:
当前 Gradient 是现场投票,Momentum 开始统计长期民意。
这句话基本就是 Momentum 的灵魂。
七、可 Momentum 只解决了“方向”,还没解决“尺度”
现在模型稍微聪明了一点。
它知道:
“不能只看这一批,要看看前面很多批是不是都支持这个方向。”
可马上又会碰到第二个问题。
假设模型只有两个 Parameter:
θ₁
和:
θ₂
它们长期 Gradient 大概分别是:
g₁ ≈ 100
和:
g₂ ≈ 0.001
两者差了:
十万倍。
如果统一用:
η = 0.001
那么粗略来看:
第一个 Parameter:
Δθ₁ ≈ 0.1
第二个 Parameter:
Δθ₂ ≈ 0.000001
一个迈大步。
一个几乎没动。
问题就来了:
几十亿个 Parameter,凭什么必须拿同一把尺子解释 Gradient?
不同 Parameter:
所在层不同。
承担功能不同。
Gradient 的典型 Scale(尺度)也可能完全不同。
能不能让 Optimizer 自己学会判断:
对这个 Parameter 来说,
当前的 Gradient 到底算大,
还是算小?
这就是 Adaptive Learning Rate:
自适应学习率
真正想解决的问题。
而这条路线最后把我们带到了:
Adam。
八、第一次学 Adam,千万别从公式开始
Adam:
Adaptive Moment Estimation。
翻译过来:
自适应矩估计。
看到“矩”这个字,很多人已经开始感觉:
高数要来了。
其实如果只是理解 Adam 的工作原理,你先不用管严格概率统计定义。
把 Adam 想成:
每个 Parameter 旁边放了两本账。
第一本账记录:
最近大家总体让我往哪边走。
第二本账记录:
我这里的 Gradient 平时到底有多大。
就这两件事。
只要把这两本账看懂,Adam 已经懂了七成。
九、第一本账:最近到底一直在往哪走?
Adam 会维护:
mₜ = β₁mₜ₋₁ + (1 − β₁)gₜ
这其实和刚才的 Momentum 非常接近。
它追踪的是:
First Moment。
一阶矩。
在这里可以先粗略理解为:
Gradient 的平滑平均方向。
假设最近几步:
0.50.5 0.60.6 0.40.4 0.70.7
虽然每一批具体数字不同,
但总体趋势非常明显:
都是正。
那:
mₜ
就会告诉 Optimizer:
这个 Parameter 最近长期看,Gradient 偏正。
于是更新应该长期倾向于:
负方向。
所以 Adam 第一只眼睛看的是:
方向。
十、第二本账:这个 Parameter 平时的 Gradient 有多大?
Adam 还会维护:
vₜ = β₂vₜ₋₁ + (1 − β₂)gₜ²
这里最关键的是:
gₜ²
为什么平方?
因为:
(+10)2=100(+10)^2=100 (−10)2=100(-10)^2=100
方向没了。
留下的是:
Magnitude。
大小。
所以:
vₜ
可以先理解成:
这个 Parameter 最近 Gradient 的典型平方尺度。
于是 Adam 第二只眼睛回答的是:
这里平时的坡到底有多陡?
现在两本账就齐了。
第一本:
最近往哪走。
第二本:
这里的 Gradient 平时有多大。
Adam 真正有意思的地方,就从这里开始。
十一、Adam 并不只问“Gradient 大不大”
它还会问一句更聪明的问题:
这个 Gradient 对你来说算不算大?
假设:
Parameter A
长期 Gradient 都在:
10
附近。
今天来了一个:
12
Parameter B
长期 Gradient 都在:
0.01
附近。
今天来了一个:
0.03
如果只看绝对值:
12 比 0.03 大了不知道多少。
可如果看相对于自己历史的变化:
A:
10 → 12。
B:
0.01 → 0.03。
B 今天的 Gradient 相对自己的正常水平,反而变化得更明显。
Adam 就是在试图捕捉这种东西。
它最终的核心更新形式:
θₜ₊₁ = θₜ − η · m̂ₜ / (√v̂ₜ + ε)
第一次看到这个公式,不要去盯:
帽子。
平方根。
epsilon。
先把它压缩成一句话:
历史方向 / 历史尺度
Adam 会根据这个 Parameter 自己过去的 Gradient Scale,对当前更新进行重新缩放。
这叫:
Adaptive Scaling。
自适应缩放。
所以一个很值得记住的理解是:
SGD 看 Gradient 有多大;Adam 还会问,这个 Gradient 相对于你自己的历史,到底算不算大。
这就是 Adaptive。
十二、为什么公式里还要有两个“帽子”?
Adam 里经常看到:
m̂ₜ = mₜ / (1 − β₁ᵗ)
以及:
v̂ₜ = vₜ / (1 − β₂ᵗ)
这叫:
Bias Correction。
偏差修正。
为什么需要?
因为 Adam 的两本账刚开始时都是:
m₀ = 0,v₀ = 0
可问题是:
刚开始根本没有历史。
你只是人为把历史初始化成了:
0。
例如:
β₁ = 0.9
第一步:
m₁ = 0.9 × 0 + 0.1g₁ = 0.1g₁
看到了吗?
Gradient 明明是:
g₁
结果历史统计只有:
0.1g₁
并不是 Gradient 突然变小了。
而是:
你的历史账本刚开张。
所以 Bias Correction 本质是在修正:
初始值为 0 带来的早期低估。
如果一定要用人话说:
新店刚开张时,不能因为“过去没有客人”,就说这家店历史平均客流特别低。
这就是帽子。
没有想象中神秘。
十三、现在把 SGD、Momentum、Adam 摆在一起,事情已经非常清楚了
SGD:
我看当前 Gradient。
Momentum:
我不只看现在,我还记得过去很多步大概往哪走。
Adam:
除了过去的方向,我还记住这个 Parameter 的 Gradient 平时是什么尺度。
所以它们的演化并不是:
算法越来越花哨。
而是 Optimizer 获得的信息越来越多。
SGD:
Current Gradient。
Momentum:
Current Gradient + Direction History。
Adam:
Current Gradient + Direction History + Scale History。
这也是为什么我不建议死背 Adam。
一旦把问题链理解了,它的设计几乎是顺着逻辑自己长出来的。
十四、可事情到 Adam 还没结束
Adam 已经很好用了。
为什么现代 Transformer Training Recipe 中又经常出现:
AdamW?
那个 W:
Weight Decay。
权重衰减。
到底又出了什么问题?
我们先不碰 Adam。
先理解 Weight Decay 本身。
假设一个 Parameter:
θ = 10
除了正常 Gradient Update 以外,我们额外希望它每一步稍微往:
0
收一点。
最直观可以写成:
θ ← (1 − ηλ)θ
其中:
λ
控制衰减强度。
例如只是非常轻微地:
10
变成:
9.999。
再变:
9.998。
不是把 Parameter 清零。
而是持续给它一点:
向 0 收缩的压力。
这就是 Weight Decay 最直观的理解。
十五、为什么要让 Parameter 稍微往 0 收?
这里别把它说成玄学。
Training Loss 只关心:
怎样把当前训练目标做得更好。
理论上 Parameter 可以通过各种数值组合去降低 Loss。
Weight Decay 相当于再增加一个偏好:
在完成任务的同时,不要毫无代价地让 Parameter Scale 一路膨胀。
所以 Weight Decay 经常被放在:
Regularization。
正则化
的语境下理解。
但是一定要严谨。
用了 Weight Decay:
不代表一定不会 Overfitting(过拟合)。
也不代表参数越小越好。
Data。
Architecture。
Training Duration。
Learning Rate。
Scale。
Objective。
这些东西都会影响最终结果。
Weight Decay 只是 Training Recipe 中的一环。
十六、AdamW 真正难懂的地方来了:Weight Decay 为什么不能直接塞进 Adam?
以前学习机器学习,经常会看到:
L2 Regularization
和:
Weight Decay
放在一起说。
在普通 SGD 中,它们确实可以出现非常漂亮的等价形式。
假设我们给 Loss 加一个 L2 项:
L′ = L + (λ/2)‖θ‖²
对 Parameter 求导:
∇θL′ = ∇θL + λθ
SGD 更新:
θ ← θ − η(∇L + λθ)
拆开:
θ ← (1 − ηλ)θ − η∇L
非常漂亮。
一部分:
正常 Gradient Update。
另一部分:
Parameter 往 0 收缩。
所以普通 SGD 里,把 L2 Regularization 和 Weight Decay 联系起来理解,问题不大。
可 Adam 不一样。
别忘了 Adam 最大的特点是什么:
它会根据 Gradient 历史做 Adaptive Scaling。
问题就出在这里。
十七、如果把 Weight Decay 塞进 Gradient,会发生一件很奇怪的事
假设我们直接写:
g′ = g + λθ
然后把整个:
g′g'
交给 Adam。
Adam 并不知道:
前面那一部分:
来自真正 Task Loss。
后面:
λθ
是我们人为加进去的 Parameter Shrink。
它只看见:
Gradient。
于是:
λθ
同样会被放进:
First Moment。
Second Moment。
平方。
平滑。
Adaptive Scaling。
这就有一点奇怪了。
我们原本只想:
让 Parameter 每一步独立往 0 收一点。
可现在这股收缩力量也被 Adam 按照 Gradient 的历史尺度重新加工了一遍。
也就是说:
Adaptive Gradient Update
和:
Weight Decay
被耦合在一起了。
这正是 AdamW 要处理的问题。
十八、AdamW 最值钱的地方,其实只有两个字:拆开
AdamW 一个非常核心的思想:
Decoupled Weight Decay。
解耦权重衰减。
翻译成人话:
Gradient Update:
你算你的。
Weight Decay:
我做我的。
别搅在一起。
粗略写成:
θ ← θ − η · m̂ₜ/(√v̂ₜ + ε) − ηλθ
前半部分:
Adam 根据 Gradient History 得到的 Adaptive Update。
后半部分:
独立的 Weight Decay。
Weight Decay 不需要先假装成 Gradient,再跟着 Adam 的 Moment Estimation 一起走一遍。
这就是 AdamW 那个 W 真正重要的地方。
所以以后别人再问:
Adam 和 AdamW 最大区别是什么?
不要只回答:
“AdamW 加了 Weight Decay。”
这个说法太粗。
更准确地说:
AdamW 把 Weight Decay 从 Adam 的自适应 Gradient 更新中解耦了。
这才是重点。
十九、到这里还有一个比 AdamW 更重要的旋钮:Learning Rate
其实 SGD、Momentum、Adam、AdamW 讨论这么久,有一个东西一直没离开:
η
Learning Rate。
你可以有非常准确的 Gradient。
也可以有非常优秀的 Adam Moment Estimate。
但如果 Learning Rate 设置得离谱:
训练照样会崩。
Learning Rate 太小:
每一步方向虽然正确。
但像蚂蚁一样挪。
Training Budget 用完了,
可能还没走到足够好的区域。
Learning Rate 太大:
一步跨出去太远。
好不容易找到一个低 Loss 区域,
直接从这一边跨到另一边。
下一步 Gradient 又把你往回推。
于是:
来回震荡。
再严重一点:
Loss Spike。
NaN。
Training Divergence。
训练直接炸掉。
所以这里有一句特别适合记住:
方向正确,不代表步子可以随便迈。
Gradient 决定哪边低。
Learning Rate 控制你一脚能跨多远。
二十、这时候 Warmup 就一点都不神秘了
很多 Transformer Training Recipe 里会看到:
Warmup。
学习率预热。
例如最终计划使用:
3 × 10⁻⁴
但训练刚开始的时候,并不直接上:
3 × 10⁻⁴
而是从更小的值开始。
慢慢增加。
最后到 Peak Learning Rate(峰值学习率)。
为什么?
不是:
“GPU 需要热身。”
真正原因还是 Optimization Stability:
优化稳定性。
训练刚开始:
Parameter 刚刚进入学习状态。
Adam 的:
mₜ
和:
vₜ
也刚开始积累。
网络内部各种 Representation 还没有形成稳定结构。
这个时候如果上来就:
“全油门!”
很容易前几步就给 Parameter 造成过大的扰动。
所以 Warmup 的思想很简单:
刚起步的时候,先小心走。
这里有一句特别值得留下:
Warmup 不是因为刚开始没有方向,而是因为刚开始还没有足够理由相信这个方向。
二十一、那为什么训练后期 Learning Rate 又会慢慢下降?
前面小心。
中间加速。
后面又减速。
为什么?
因为训练早期:
Parameter 可能离比较好的区域非常远。
这时候:
步子太小反而浪费时间。
所以允许更大的 Learning Rate:
赶路。
训练后期:
Parameter 已经进入一个相对不错的区域。
这时候如果还一脚跨特别远:
很容易在附近来回震荡。
于是 Learning Rate 通常开始下降。
这就是:
Learning Rate Schedule。
学习率调度。
例如:
Linear Decay。
Cosine Decay。
具体曲线以后再细讲。
现在只需要理解背后的直觉:
训练前期解决“去哪”,训练后期解决“怎么停得更准”。
或者更生活化一点:
Warmup 是刚起步别猛踩油门,Decay 是快到终点记得开始刹车。
二十二、如果某一次 Gradient 突然特别离谱怎么办?
这时候又会看到一个常见训练词:
Gradient Clipping。
梯度裁剪。
正常情况下:
Gradient Norm(梯度范数)
可能一直在一个相对稳定的范围。
结果某一个 Batch 突然来了一次特别大的 Gradient。
如果 Optimizer 完全照单全收:
Parameter 可能一下被推得非常远。
所以我们可以规定:
如果 Gradient Norm 超过某个阈值,
就按比例缩小。
注意。
Gradient Clipping 并不是在说:
“这次 Backward 算错了。”
它真正表达的是:
即使数学上得到这个 Gradient,我也不允许单个 Training Step 拥有把整个模型掀翻的权力。
你会发现真实训练已经越来越像一个控制系统。
Gradient:
提出修改意见。
Momentum:
参考长期趋势。
Adam:
参考历史尺度。
Learning Rate:
控制步幅。
Warmup:
控制开局风险。
Gradient Clipping:
防止极端冲击。
Weight Decay:
控制 Parameter Scale。
所以真正的大模型优化绝对不是一句:
“算梯度,然后更新参数。”
它是一整套:
把 noisy Gradient 安全转换成 Parameter Update 的机制。
二十三、学到这里,回头看optimizer.step()就完全不一样了
很多人第一次写 PyTorch:
optimizer.zero_grad() loss.backward() optimizer.step()点击并拖拽以移动
写几十次以后,就感觉这几行代码天生应该长这样。
现在再看:
意义已经完全不同。
optimizer.zero_grad():
把上一轮 Gradient 清掉。
因为 PyTorch Gradient 默认可以累积。
loss.backward():
做的是 Backpropagation。
计算:
∇θL
注意:
这里还没有真正更新 Parameter。
真正动 Parameter 的是:
optimizer.step()。
如果 Optimizer 是 AdamW,它内部会做很多事情。
读取当前 Gradient。
更新:
First Moment。
更新:
Second Moment。
做 Bias Correction。
进行 Adaptive Scaling。
结合当前 Learning Rate。
执行 Decoupled Weight Decay。
最后:
Parameter 真正发生变化。
所以:
loss.backward()
和:
optimizer.step()
完全不是一回事。
前者:
算怎么改。
后者:
真的改。
二十四、还有一个很现实的问题:Adam 的两本账不是免费的
现在假设模型有:
几十亿 Parameter。
Adam 给很多 Parameter 都要维护:
mₜ
以及:
vₜ
Parameter 本身已经占显存。
Gradient 也要占。
现在又多了两份 Optimizer State(优化器状态)。
还没算:
Activation(激活值)。
Backward 中间状态。
Temporary Buffer。
分布式训练通信 Buffer。
所以你以后再看到一句:
“这个模型权重只有 20GB,24GB 显卡应该可以 Full Training 吧?”
应该马上警觉。
推理和训练完全不是同一份显存账。
推理的时候:
主要是在运行 Parameter。
训练的时候:
你不只是在运行模型。
你还要:
保存现场。
保存 Gradient。
保存 Optimizer History。
准备 Backward。
再真正更新 Parameter。
这里可以记一句:
推理是把模型跑起来,训练是把模型跑起来之后,还要把“它为什么得到这个结果”完整追一遍,再保存下一步怎么改的历史。
这就是为什么 Training Memory 往往比单纯权重体积复杂得多。
以后讲:
ZeRO。
FSDP。
Optimizer State Sharding。
你会再次回到今天这里。
二十五、那么 AdamW 到底“优化”了什么?
这个问题很重要。
AdamW 并不知道:
“智能”是什么。
它不知道:
“数学能力”是什么。
不知道:
“写代码更好”是什么意思。
它甚至不知道当前 Output 是:
中文。
英文。
还是 Python。
它真正做的是:
根据当前 Training Objective 产生的 Gradient,调整 Parameter,让这个 Objective 更容易下降。
如果当前 Objective 是:
Next-Token Prediction。
那 AdamW 就在帮助降低:
Next-Token Prediction Loss。
以后到了 SFT:
Objective 变化。
到了 DPO:
Objective 又变化。
到了 RL:
Reward 和优化机制再次变化。
Optimizer 可以还是 AdamW。
但模型正在学习的东西已经不同。
所以这里一定把三层分清:
Objective:
到底希望模型学什么。
Gradient:
当前 Parameter 往哪里变,更可能让 Objective 下降。
Optimizer:
怎样把 Gradient 真正转换成 Parameter Update。
一句话:
Optimizer 决定“怎么学”,Objective 决定“学什么”。
这句话以后到了 Post-Training 仍然非常重要。
二十六、现在把第十七课和第十八课真正接成一条完整训练链
还是那句话:
今天天气真好。
当前 Prefix:
今天天气真
模型 Forward。
经过:
Embedding。
Transformer。
Attention。
MLP。
Residual。
RMSNorm。
LM Head。
最后得到 Vocabulary Logits。
Softmax 以后假设:
“冷”:52.5%
“好”:23.6%
“热”:9.6%
……
Training Target:
好。
于是:
L = −log(0.236)
大约:
1.441.44
Cross Entropy 告诉模型:
正确 Token 获得的 Probability 太低。
Backward 开始。
Gradient 沿着:
Logits
→ LM Head
→ Final Hidden State
→ Transformer Block
→ MLP
→ Attention
→ Q/K/V
→ 前一层 Transformer
→ …
一路向回。
于是各种参与当前有效路径的可训练 Parameter 获得:
∇θL
到这里,是第十七课。
然后第十八课开始。
AdamW 接过这些 Gradient。
对于某一个 Parameter,它会结合:
当前 Gradient。
历史 First Moment。
历史 Second Moment。
Bias Correction。
Learning Rate。
Weight Decay。
计算真正的 Parameter Update。
最终:
θₜ → θₜ₊₁
注意这一刻。
模型才真正改变。
下一次遇到类似 Context:
它已经不是刚才那一台模型。
也许:
P(好)
从:
23.6%
变成:
23.61%。
非常小。
甚至下一个 Batch 来了以后,它可能又变成:
23.59%。
看起来不像“学习”。
可真实 Pretraining 从来不是靠一个 Batch 完成的。
而是:
无数 Sequence。
无数 Token。
无数 Batch。
无数 Gradient。
无数 Parameter Update。
长期拉扯。
慢慢累积。
二十七、这其实是大模型训练最反直觉、也最迷人的地方
我们最后看到的大模型可能会:
写代码。
做数学。
翻译语言。
总结论文。
理解上下文。
甚至完成复杂推理。
可你如果把训练过程一帧一帧扒开,会发现:
根本没有哪一个 Training Step 在做:
“写入数学能力。”
没有:
“安装编程模块。”
也没有:
“保存一条知识。”
每一步看到的只有:
θₜ → θₜ₊₁
一堆浮点数:
稍微变了一点。
然后下一批。
又稍微变一点。
再下一批。
继续变。
所以我特别喜欢这样理解大模型训练:
单独看任何一次 Parameter Update,都笨得惊人;真正不可思议的是,足够多这种微小、机械、局部的修正叠加以后,竟然能长出复杂能力。
所谓 Learning,
并不存在一个神奇瞬间。
更多时候只是:
比上一秒好一点点。
然后把这个“一点点”,重复到不可思议的次数。
二十八、最后,别背 AdamW,把这条“问题链”带走
如果过两天你忘记:
β₁
是多少。
忘记:
β₂
是多少。
甚至忘记 Bias Correction 的完整公式。
都没关系。
真正应该留下的是下面这条推导。
第一步。
Gradient 已经有了。
最自然的想法:
沿反方向走。
于是:
SGD。
第二步。
发现 Mini-Batch Gradient 有 Noise。
当前一步不能完全代表长期方向。
于是:
记住历史趋势。
Momentum 出现。
第三步。
发现不同 Parameter 的 Gradient Scale 相差巨大。
不能只看绝对 Gradient。
于是:
给不同 Parameter 自己维护历史尺度。
Adam 出现。
第四步。
又发现 Weight Decay 如果直接塞进 Adam Gradient,也会被 Adaptive Scaling。
可我们本来只想单独对 Parameter 做收缩。
于是:
把两件事拆开。
AdamW 出现。
再往外:
步子太大怎么办?
Learning Rate。
刚开始训练不稳定怎么办?
Warmup。
后期需要精调怎么办?
Learning Rate Decay。
偶尔 Gradient 突然异常怎么办?
Gradient Clipping。
到这里你会发现:
这些概念根本不是散的。
它们都在回答同一个问题:
一个 noisy、局部、只代表当前 Batch 的 Gradient,到底应该怎样安全地变成真正的 Parameter Update?
这就是 Optimization。
二十九、如果只允许带走三句话
第一句:
Gradient 只是坡度,不是参数更新量。
第二句:
Adam 的核心不是公式复杂,而是同时记住“最近往哪走”和“这里的 Gradient 平时有多大”。
第三句:
AdamW 真正的 W,不是简单“多了 Weight Decay”,而是把 Weight Decay 从 Adam 的自适应 Gradient 更新里解耦出来。
如果这三句话你真的理解了,
以后再看任何训练代码里的:
AdamW。
Learning Rate。
Warmup。
Scheduler。
Weight Decay。
Gradient Clipping。
就不会再觉得是一堆神秘配置。
它们其实都在管理同一件事:
模型这一步,到底该怎么改。
三十、下一课,我们终于要把镜头拉远了
到现在为止,我们一直盯着一件很微观的事情:
一个 Token 错了以后,
Loss 怎么出来。
Gradient 怎么出来。
Parameter 又怎么被 Optimizer 更新。
也就是说:
我们终于把:
Data → Prediction → Loss → Gradient → Optimizer → Parameter Update
这条最底层训练闭环真正走通了。
但接下来,一个更大的问题马上出现:
既然模型就是这样一点点学,
那为什么过去几年整个行业几乎都在疯狂扩大:
Parameters。
Data。
Compute。
100M。
1B。
10B。
100B。
甚至更大的模型规模。
难道:
参数越多,就一定越聪明?
如果模型扩大 10 倍,
数据不变,
会怎么样?
如果数据增加 10 倍,
模型却太小,
又会怎么样?
如果今天突然多给你 10 倍 GPU,
究竟应该:
训练更大的模型,
还是:
让同一个模型看更多 Token?
这些问题最后把现代大模型带向了一个非常重要的概念:
Scaling Law。
缩放定律。
下一课,我们不急着背任何 Scaling 公式。
我们先回答一个更根本的问题:
大模型训练原理(19)|同样都是烧 GPU,为什么有的人把模型做大,有的人却选择让模型多读书?
因为大模型真正的 Scaling,从来不是一句:
“参数越多越强。”
它真正研究的是:
Parameters、Data、Compute 三种资源,到底应该怎样交换,才能把每一份算力变成尽可能多的能力。
这会是我们第一次从:
“一个 Parameter 怎么学”
走向:
“一个 Frontier Model 为什么要被训练成今天这个规模”。
而从这里开始,大模型训练会正式进入另一个世界。