↳ / Mental Models快速补充

梯度如何穿过计算图

把 backward、optimizer.step 和训练系统放在同一张图里

你可以熟练调用工具包,却仍不确定训练究竟改变了什么。这一章只补上那张必要的图:前向保存什么,反向返回什么,参数又在哪一步发生变化。

READ WITH A QUESTION

读完这一章,你应该能够

  • 手算一条计算图中每条边的局部梯度
  • 解释反向传播与梯度下降的不同职责
  • 识别梯度累积、mask 和 detach 导致的训练异常
前置理解 Python / PyTorch · 基本微积分
本章目录 · 0 个小节

一步训练,其实是三件不同的事

先给结论:前向传播算输出和损失,反向传播算损失对参数的导数,优化器根据导数更新参数。 loss.backward() 本身通常不会改变参数。调用优化器后,下一次前向才用上新参数。PyTorch 的 autograd 在前向构建操作图,再根据链式法则累积叶子张量梯度。官方文档与源码入口

CODE / 可复制
样本 x、目标 y 与当前参数 θ
前向:保存需要的中间结果,得到损失 L
反向:从 L 出发,沿计算图累积 ∂L/∂θ
优化器:结合梯度、学习率与优化器状态,更新 θ
下一批数据:使用新参数重新构建图

这里的“反向”不是把句子倒着读,也不是重新生成一个答案。它是在回答:刚才的误差,如果某个参数稍微改变,会增加还是减少、改变多少?

用一个参数,把整张图算出来

设输入 x=2x=2,目标 y=5y=5,模型 y^=wx+b\hat y=wx+b,当前 w=0.5,b=0w=0.5,b=0。损失选最简单的平方误差:

L=12(y^−y)2=12(wx+b−y)2.L=\frac12(\hat y-y)^2=\frac12(wx+b-y)^2.

前向得到:乘法输出 1,加法输出 1,误差 -4,损失 8。接着反向走同样的边:

节点本节点的局部导数从后面传回的梯度送给前一节点的梯度
L=12e2L=\frac12e^2∂L/∂e=e\partial L/\partial e=e1-4
e=y^−ye=\hat y-y∂e/∂y^=1\partial e/\partial\hat y=1-4-4
y^=z+b\hat y=z+b对 z、b 都为 1-4z 与 b 各得到 -4
z=wxz=wx对 w 为 x=2-4w 得到 -8

所以 ∂L/∂w=−8\partial L/\partial w=-8,∂L/∂b=−4\partial L/\partial b=-4。取学习率 0.1,梯度下降更新为 w=1.3,b=0.4w=1.3,b=0.4。下一次预测变成 3,损失变成 2。减去负梯度,参数反而增大。 打开梯度实验室,按前向、反向、更新三个按钮逐步确认。

为什么一条路径相乘,多条路径相加

想象同一个参数被两个分支使用:一个影响注意力输出,另一个影响辅助损失。沿一个分支逐层“变化率相乘”;如果两条路径都影响最终损失,就把两条贡献相加。反向自动微分因此需要拓扑顺序,并累计到同一个叶子的梯度中。

一个极小反例:L=w2+wL=w^2+w,第一条路径返回 2w2w,第二条返回 1,最后是 2w+12w+1。若实现用赋值覆盖而不是累加,就会静默丢掉一条路径。这也是教学自动微分实验需要测试共享子图的原因。

矩阵形式没有改变原则。对于 Y=XWY=XW,若 X:[B,D]X:[B,D]、W:[D,H]W:[D,H]、输出梯度 G:[B,H]G:[B,H],则 ∇WL=X⊤G:[D,H]\nabla_WL=X^\top G:[D,H],∇XL=GW⊤:[B,D]\nabla_XL=GW^\top:[B,D]。shape 是读反向代码时最实用的第一项检查。

backward 不等于梯度下降

课程分析:梯度是测量局部变化率的结果;SGD、AdamW 等优化器决定如何使用它。梯度算得完全正确,也可能因为学习率过高而训练失败。对于这个 toy,Hessian 的非零特征值为 x2+1=5x^2+1=5;沿有效方向,固定步长梯度下降需要 0<η<2/50<\eta<2/5 才收敛。界限只属于当前二次目标,不能搬成 LLM 学习率。

把交互实验的学习率调到 0.5,会观察到震荡发散。此时“再算精确一点梯度”解决不了更新步长问题;换成真实训练,要把梯度异常、优化器状态异常与数据异常分别检查。

CODE / 可复制
# 教学 PyTorch 对照;本章纯标准库实验无需安装 torch
import torch
w = torch.tensor(0.5, requires_grad=True)
b = torch.tensor(0.0, requires_grad=True)
optimizer = torch.optim.SGD([w, b], lr=0.1)
optimizer.zero_grad(set_to_none=True)
loss = 0.5 * (w * 2 + b - 5).square()
loss.backward()                 # w.grad=-8, b.grad=-4;参数未改变
optimizer.step()               # w=1.3, b=0.4

zero_grad 清理上一轮累计结果。它的位置要与 microbatch 累积策略配套:每个 microbatch 都清理,会破坏累积;更新后一直不清理,又会把跨更新的梯度混在一起。PyTorch autograd

从一条图走到真实训练 step

生产模型有数十亿参数,但仍然执行同一组职责。变复杂的是中间激活存在哪里、何时重算、梯度在哪个 rank、哪些 token 应该计入 loss。OLMo-core 的 TransformerTrainModule.train_batch 展示有效标签统计、microbatch 与反向入口;优化器步骤独立执行。这是透明实现对照,不是对其模型能力排名的判断。固定源码与调用链

机制为何出现对计算图和更新的影响
Activation checkpointing激活显存不足反向时重新计算部分前向,换显存但增加算量
梯度累积单卡放不下整个 batch多个 microbatch 累积梯度后只更新一次
数据并行多卡处理不同样本梯度需要归约,保证副本执行相同更新
参数分片参数与 optimizer state 太大前向/反向前收集参数,反向后归约并分片梯度
Token maskpadding、工具观测不该直接成为优化动作决定哪些位置参与损失及分母

特别留意平均方式:先对每个不同长度的样本取平均,再对样本平均,不等于对全 batch 有效 token 统一平均。这不是一个无关紧要的数值误差,而是改变样本权重。

Agent RL 里的梯度为什么不会穿过终端

环境执行工具、运行测试、返回奖励,通常不在 PyTorch 可微图内。策略梯度使用采样动作的 log-prob 和 advantage,改变以后选中这些动作的概率;它不需要对“编译器”求导。

教学目标可写为 L=−A∑tmtlog⁡πθ(at∣ht)L=-A\sum_t m_t\log\pi_\theta(a_t|h_t)。mtm_t 把工具观察等非策略 token 排除;这里假定 A 已作为固定权重,不让梯度穿过 advantage。真实 PPO/GRPO 还包含 old log-prob、概率比、clipping、KL 等机制。Agent rollout 中的 response mask

这个连接很重要:反向图只包含策略更新所需的张量计算,训练数据流还包含远处的 CPU 环境、验证器和轨迹存储。下一步阅读一条 rollout 如何变成训练样本。

运行、核对,然后故意破坏它

CODE / 可复制
python3 -m labs.run --experiment gradients
python3 -m unittest discover -s tests -p 'test_*.py'

labs/gradients.py 是本站原创的标量自动微分教学实现。输入和输出都是标量节点;实验把解析梯度与中央有限差分相比较。它没有 GPU tensor、并行反向引擎、mixed precision 或 Adam 状态;不要把其运行时间当成生产性能。

预期观察:初始 loss=8,w 梯度=-8、b 梯度=-4,学习率 0.1 更新后的 loss=2。小步长中央差分应与自动微分相近。差分步长过小会受浮点消减误差影响,过大则不是局部导数。

现象首先检查可验证动作
参数一直没变是否执行 optimizer.step、是否加入 optimizer保存更新前后参数差范数
梯度为 None图是否被 detach/no_grad 打断、是否叶子检查 requires_grad 与 grad_fn
梯度越来越大是否忘了清理、loss分母改变固定同一批重放并记录逐层范数
loss下降却没有任务收益loss覆盖的token是否符合目标可视化mask并检查独立评测
单卡好、多卡坏归约与有效token分母是否一致对齐一个等价全局batch的梯度

工程任务:用共享参数构造 L=w2+wL=w^2+w,写出两条反向路径;再把一个长度为 1、另一个长度为 100 的样本放在同一 batch,解释两种平均损失的权重差异。你能够画出并验证这两件事,就已经拥有阅读后续训练代码所需的核心直觉。

MAKE IT RUN

把这一章放进实验里

调整变量,观察机制,再运行本地 Python 实验验证。

进入实验

本章证据与源码

引用代表相应来源支持的结论,不意味着公开了完整训练配方。

官方事实PyTorch:Autograd mechanicsPyTorch · stable 文档快照源码观察OLMo-core:Transformer 的 token 归一化与训练微批allenai · 0589a034f721源码观察verl:多轮 Agent rollout 的标准化接口volcengine · 8e03c039f9a7

留下你的工程判断

记录一个还没有解释清楚的现象,或下一次实验要控制的变量。笔记仅保存在当前浏览器。

自动保存在本机 · 可在“我的学习笔记”导出
理解机制,比记住结论更重要。