Rust in Everything
基于 candle 0.11.0

自动求导与训练

candle 的训练支持比 PyTorch 薄得多:反向模式、一阶导数、两个优化器(没有动量的 SGD 和 AdamW),没有调度器、没有梯度裁剪。这一卷的主轴是推理,这一部分只讲从 PyTorch 过来、写训练代码时要改的习惯。算出来的数和 PyTorch 对得上:训练主线的 MLP(A3),四组梯度最大差 4.47e-8,100 步 AdamW 后的参数最大差 2.98e-7。

不同的地方集中在「状态放在哪」。PyTorch 把要不要梯度(requires_grad)、梯度(.grad)、图都挂在张量上,用一个全局开关(torch.no_grad())决定记不记图。candle 用类型 Var 标出参数,梯度放在 backward() 返回的 GradStore 里,记不记图只看输入里有没有 Var。

  1. Var 与梯度:requires_grad=True 对 Var;.grad 对 grads.get(&t);普通张量也拿得到梯度,中间结果拿不到;backward 可以跑两次,不累加;非标量、整数、「非叶子参数」三种 PyTorch 报错在 candle 里各变成什么。
  2. 没有 no_grad:加载的预训练权重不是 Var,推理本来就不记图;忘了 no_grad 的 PyTorch 前向峰值内存多 0.93 GB;训练中做评估、留参数快照、冻结参数的写法;融合算子没有反向。
  3. 训练一步:手写训练循环;优化器默认值;AdamW 和 PyTorch 逐步对 100 步,差只来自 f32 舍入;在 burn 卷的两个 Adam 设置上重跑,差比 burn 小一个量级,原因是偏差修正因子在 f64 里算;顺带发现 conv1d 的卷积核梯度在 0.11.0 上是错的。
flowchart TB
  var["Var 参数"] --> fwd["前向 记 Op"]
  plain["普通张量"] --> fwd
  plain --> nog["前向 不记图"]
  fwd --> loss["loss"]
  loss --> bw["backward"]
  bw --> gs["GradStore"]
  gs --> opt["SGD 或 AdamW"]
  opt -->|"Var set"| var

candle 内部怎么记图、backward 怎么走、优化器怎么写回,见 candle 卷的自动微分。和 burn 的做法对照,见 burn 那一卷的自动求导。

💬 关联讨论

发起讨论
加载中...