Rust in Everything
基于 candle-core 0.11.0

自动微分

这一部分把求导主线拆开讲。candle 的自动微分是反向模式(reverse mode),只做一阶导数。代码很集中:candle-core 的 backprop.rs(809 行,几乎全是一个 backward 函数里的 match)、op.rs 里的 Op 枚举和 BackpropOp、variable.rs(152 行的 Var),再加上 candle-nn 的 optim.rs(201 行)和 var_map.rs。没有单独的图对象,没有全局登记表,也没有「打开 autodiff」的设备或后端:每个张量自己带着「我是怎么算出来的」。

  1. 计算图怎么记下来:Tensor_ 里的 op: BackpropOp 是一个 Option<Op>,Op 的 36 个变体各自存着输入张量;只有某个输入是 Var 或者自己记了 Op 时才记;Var 是 is_variable 为真的张量加一个原地写入的 set;detach 换一个新 id、丢掉 Op;图活得和最后一个结果张量一样久。
  2. backward 与 GradStore:sorted_nodes 深度优先、后序收集通向 Var 的节点,already_seen 让共享的节点只走一次;backward 倒着套每个 Op 的规则,梯度累加进以 TensorId 为键的 GradStore;用有限差分验证;对同一个 loss 可以 backward 两次。
  3. 优化器与训练循环:Optimizer trait、没有动量的 SGD、AdamW;VarMap 按名字存 Var,模型拿到的权重和 Var 是同一个张量,GradStore 直接用它的 id 查;一个两层 MLP 的手写训练循环,在 CPU 和 Metal 上各训 200 步。
flowchart TB
  var["Var 权重"] --> fwd["前向 每个结果记 Op"]
  fwd --> loss["loss 张量"]
  loss --> sort["sorted_nodes"]
  sort --> rev["倒序套求导规则"]
  rev --> gs["GradStore"]
  gs --> opt["SGD 或 AdamW"]
  opt -->|"Var set"| var

前向的每个算子仍然走算子主线;求导规则本身也是用张量算子写的,所以反向在 Metal 上一样跑在 Metal 上。和 burn 的做法(装饰器后端 + 全局图登记)的整体对照放在设计取舍。

💬 关联讨论

发起讨论
加载中...