云镜收藏

稍后阅读

清单保存在当前浏览器,方便下次回来继续阅读。

清单还是空的

在笔记卡片或正文页点击“加入稍后阅读”即可收藏。

展开课程与本章目录
12 章学习路线01零基础准备:Python、NumPy 与正确安装02起步:环境、设备与第一枚 Tensor03张量基本功:索引、广播与线性代数04自动求导:从计算图到反向传播05神经网络工程:Module、损失与训练循环06数据管线:Dataset、DataLoader 与批处理07亲手搭 Transformer:从 Embedding 到注意力08稳定训练:初始化、AMP、裁剪与排错09性能与显存:Profiler、compile 与检查点10分布式训练:从 DDP 到 FSDP 与 TP11大模型生态:Transformers、PEFT、torchao 与 TorchTitan12方向实战:训练并优化一个迷你语言模型本章课程01动态计算图如何记录运算02叶子 Tensor、grad_fn 与 retain_grad03为什么每步都要清空梯度04停止梯度:no_grad、inference_mode 与 detach05梯度钩子与调试梯度流06自定义 autograd Function 何时需要

第 04 章 · 基础

自动求导:从计算图到反向传播

拆开 autograd 的黑盒:看懂动态计算图、叶子节点、梯度累积、钩子和自定义求导,真正掌握训练为何有效。

6 节课程校订于 2026年8月31日
autogradbackpropagationgradienthook

预计用时100 分钟

前置知识基础微积分直觉 · Tensor 运算

完成标准解释动态计算图 · 定位梯度是否流动 · 安全停止梯度

本章进度0 / 6

动态计算图如何记录运算

本节目标

本节围绕“动态计算图如何记录运算”展开。运行示例后查看 requires_grad、grad_fn 与 .grad,记录计算图和梯度在各步的变化。

清晰讲解

当输入需要梯度时,PyTorch 会在前向过程中记录产生结果的算子,形成运行时计算图。调用 backward 后,它按链式法则从标量损失反向传播。图是动态构建的,所以普通 Python 分支和循环依然可用。

核对“动态计算图如何记录运算”中的叶子节点、计算图连接、梯度累积和图释放时机。

代码示例

import torch
x = torch.tensor(2.0, requires_grad=True)
y = x**3 + 2*x
y.backward()
print(x.grad)  # 3*x^2 + 2 = 14

运行结果与观察

x=2 时梯度应为 14;它等于手算导数而不是当前函数值。修改函数后要先手算,再与 .grad 对照。

常见错误

  • 误以为 PyTorch 先编译一张永久静态图;在前向中把 Tensor 转为 Python 数值后还期待梯度回传。
  • 排查“动态计算图如何记录运算”时只看 loss,没有检查 requires_grad、grad_fn、.grad 或梯度是否意外累积。

与大模型方向的连接

Transformer 是大量可微算子的组合;你不必手写每层梯度,但必须知道哪些操作切断了图。

动手练习

把函数改成 sin(x) * x**2,手算导数并与 autograd 对照。

查看参考答案与验收点

验收:梯度值与手算或有限差分一致,并能说明“动态计算图如何记录运算”中计算图何时建立、累积和释放。

官方资料


叶子 Tensor、grad_fn 与 retain_grad

本节目标

本节围绕“叶子 Tensor、grad_fn 与 retain_grad”展开。运行示例后查看 requires_grad、grad_fn 与 .grad,记录计算图和梯度在各步的变化。

清晰讲解

用户创建且需要梯度的参数通常是叶子 Tensor,梯度默认累积在它的 .grad。中间结果是非叶子节点,虽然有 grad_fn,但默认不保留 .grad;调试时可调用 retain_grad

核对“叶子 Tensor、grad_fn 与 retain_grad”中的叶子节点、计算图连接、梯度累积和图释放时机。

代码示例

import torch
x = torch.ones(3, requires_grad=True)
y = x * 2
y.retain_grad()
loss = (y**2).sum()
loss.backward()
print(x.is_leaf, y.is_leaf)
print(x.grad, y.grad)

运行结果与观察

x 是叶子、y 是非叶子;调用 retain_grad 后两者都能观察到梯度。删除它后 y.grad 应为空。

常见错误

  • 把非叶子节点的空 .grad 误判为梯度消失;滥用 retain_grad 导致额外内存。
  • 排查“叶子 Tensor、grad_fn 与 retain_grad”时只看 loss,没有检查 requires_grad、grad_fn、.grad 或梯度是否意外累积。

与大模型方向的连接

检查某层激活梯度时,不要因为 .grad is None 就断言没有梯度流;先区分叶子与非叶子。

动手练习

打印一个两层 MLP 中参数和激活的 is_leafgrad_fn

查看参考答案与验收点

先把每个轴写成语义:例如 [B,T,C] 分别是批次、序列和隐藏维;再用 assert tensor.shape == (...) 固化预期。

官方资料


为什么每步都要清空梯度

本节目标

本节围绕“为什么每步都要清空梯度”展开。运行示例后查看 requires_grad、grad_fn 与 .grad,记录计算图和梯度在各步的变化。

清晰讲解

PyTorch 默认把新梯度加到现有 .grad 上,这便于梯度累积,但普通训练若不清空就会混入历史批次。推荐在反向前使用 optimizer.zero_grad(set_to_none=True)

核对“为什么每步都要清空梯度”中的叶子节点、计算图连接、梯度累积和图释放时机。

代码示例

import torch
w = torch.tensor(1.0, requires_grad=True)
for _ in range(2):
    (w * 3).backward()
print(w.grad)  # 6:两次各累积 3
w.grad = None
(w * 3).backward()
print(w.grad)  # 3

运行结果与观察

连续两次 backward 后梯度由 3 累积为 6;清空后再次反向回到 3,证明累积是默认行为。

常见错误

  • 每个 micro-batch 都 step 却称为梯度累积;累积时忘记缩放 loss。
  • 排查“为什么每步都要清空梯度”时只看 loss,没有检查 requires_grad、grad_fn、.grad 或梯度是否意外累积。

与大模型方向的连接

梯度累积正是利用这一行为模拟更大 batch,但损失通常要除以累积步数。

动手练习

实现 4 个 micro-batch 累积后再更新一次参数,并验证更新幅度。

查看参考答案与验收点

用一个极小 batch 过拟合,并记录 loss、梯度范数和参数更新;断点恢复后比较下一步结果,而不只检查文件存在。

官方资料


停止梯度:no_grad、inference_mode 与 detach

本节目标

本节围绕“停止梯度:no_grad、inference_mode 与 detach”展开。运行示例后查看 requires_grad、grad_fn 与 .grad,记录计算图和梯度在各步的变化。

清晰讲解

no_grad 暂停记录梯度,适合验证;inference_mode 进一步关闭部分 autograd 开销,适合纯推理;detach 返回与原 Tensor 共享存储但脱离当前图的视图。三者目的相近,作用范围不同。

核对“停止梯度:no_grad、inference_mode 与 detach”中的叶子节点、计算图连接、梯度累积和图释放时机。

代码示例

import torch
x = torch.randn(2, 3, requires_grad=True)
with torch.no_grad():
    a = x * 2
with torch.inference_mode():
    b = x * 3
c = (x * 4).detach()
print(a.requires_grad, b.requires_grad, c.requires_grad)

运行结果与观察

三个结果的 requires_grad 均为 False;但 detach 仍可能共享存储,不能据此认定发生了数据复制。

常见错误

  • detach 后原地修改共享数据;验证时忘记关闭梯度导致显存逐步上涨。
  • 排查“停止梯度:no_grad、inference_mode 与 detach”时只看 loss,没有检查 requires_grad、grad_fn、.grad 或梯度是否意外累积。

与大模型方向的连接

生成阶段应避免构建反向图;记录指标时可 loss.detach(),防止日志列表无意持有整张计算图。

动手练习

写一个验证函数,确保模型切到 eval、关闭梯度并返回 Python 浮点指标。

查看参考答案与验收点

用一个极小 batch 过拟合,并记录 loss、梯度范数和参数更新;断点恢复后比较下一步结果,而不只检查文件存在。

官方资料


梯度钩子与调试梯度流

本节目标

本节围绕“梯度钩子与调试梯度流”展开。运行示例后查看 requires_grad、grad_fn 与 .grad,记录计算图和梯度在各步的变化。

清晰讲解

hook 能在梯度经过 Tensor 或模块时观察、记录甚至修改它。它适合诊断梯度爆炸、某层无梯度等问题,但生产代码要及时移除,避免额外开销和隐蔽副作用。

核对“梯度钩子与调试梯度流”中的叶子节点、计算图连接、梯度累积和图释放时机。

代码示例

import torch
x = torch.randn(4, requires_grad=True)
handle = x.register_hook(lambda grad: print('grad norm:', grad.norm().item()))
loss = (x**2).mean()
loss.backward()
handle.remove()

运行结果与观察

反向时只打印一次有限的梯度范数;移除 handle 后再次反向不应再触发该输出。

常见错误

  • 在 hook 中返回错误形状;注册后从不 remove;日志同步 GPU 造成训练变慢。
  • 排查“梯度钩子与调试梯度流”时只看 loss,没有检查 requires_grad、grad_fn、.grad 或梯度是否意外累积。

与大模型方向的连接

可按 Transformer 层记录梯度范数,判断深层梯度是否异常;不要每步打印完整梯度。

动手练习

给一个 nn.Linear 的权重注册 hook,只在梯度非有限时报警。

查看参考答案与验收点

用一个极小 batch 过拟合,并记录 loss、梯度范数和参数更新;断点恢复后比较下一步结果,而不只检查文件存在。

官方资料


自定义 autograd Function 何时需要

本节目标

本节围绕“自定义 autograd Function 何时需要”展开。运行示例后查看 requires_grad、grad_fn 与 .grad,记录计算图和梯度在各步的变化。

清晰讲解

大多数模型应组合已有算子,让 autograd 自动求导。只有接入自定义 CUDA 算子、特殊数值近似或需要手写反向时,才实现 torch.autograd.Function,并用 gradcheck 校验。

核对“自定义 autograd Function 何时需要”中的叶子节点、计算图连接、梯度累积和图释放时机。

代码示例

import torch

class Cube(torch.autograd.Function):
    @staticmethod
    def forward(ctx, x):
        ctx.save_for_backward(x)
        return x**3
    @staticmethod
    def backward(ctx, grad_out):
        (x,) = ctx.saved_tensors
        return grad_out * 3 * x**2

x = torch.tensor(2., requires_grad=True)
Cube.apply(x).backward()
print(x.grad)

运行结果与观察

x=2 时自定义反向应得到梯度 12,并应继续通过双精度 gradcheck。

常见错误

  • 在 forward 保存所有中间值造成显存浪费;反向漏乘上游梯度 grad_out
  • 排查“自定义 autograd Function 何时需要”时只看 loss,没有检查 requires_grad、grad_fn、.grad 或梯度是否意外累积。

与大模型方向的连接

高性能大模型算子可能有自定义 forward/backward;理解保存哪些中间量,就是理解它的显存代价。

动手练习

x^2 + x 写自定义 Function,并用双精度 gradcheck 验证。

查看参考答案与验收点

验收:梯度值与手算或有限差分一致,并能说明“自定义 autograd Function 何时需要”中计算图何时建立、累积和释放。

官方资料

章末资料

小测、项目与相关面试题

1. 为什么连续 backward 后 grad 会变大?

PyTorch 默认累积梯度,新梯度会加到现有 .grad。

2. 非叶子 Tensor 的 grad 为 None 是否代表无梯度?

不一定。非叶子默认不保留 .grad,可用 retain_grad 调试。