Rust in Everything
基于 tenferro-runtime 0.8.0

自动微分

图与运行时 讲了 traced 程序怎么建、怎么编译、怎么在 engine 上执行。这一部分讲 tenferro-ad:在那之上怎么求导。tenferro 有两种用法,JAX 式的 traced(y.grad(&x) 返回一张新图)和 PyTorch 式的 eager(loss.backward(&mut engine) 之后 x.grad())。两者表面差别很大,底下是同一件事:把计算写成一个语义程序(SSA),对程序做一次 JVP 或 VJP 变换,得到导数程序,再编译执行。eager 只是在每次运算时顺手把这个程序记下来。变换本身不碰 engine,engine 只在执行导数程序时由调用方借进来。

代码集中在 tenferro-ad/src/:traced.rs、context.rs(traced 入口和 AdContext)、semantic_transform.rs(变换和核心算子的规则)、eager.rs、eager_ops.rs、eager/residuals.rs、eager/composite.rs(eager)、semantic_extension.rs、extension.rs(运算族的规则和扩展算子)。tenferro-internal-ops/src/ad/ 里还有一张图级的规则表,grad 不走它,第一页讲为什么。

  1. primitive AD 规则:核心算子的规则写了两份,tenferro-internal-ops 按 PrimitiveOpKind 登记的图级 linearize / transpose_rule,和 grad 实际调用的 linearize_core / vjp_core;ResidualSpec 声明反向要留哪些张量;新的 erf 是 primitive,两份规则各一条,sigmoid、gelu、softmax、layer_norm 是组合运算,按分解求导;maximum / clamp 的掩码零不再被非有限余切污染成 NaN;梯度与有限差分差 2.3e-9。
  2. traced AD:grad 是种子为 1 的 vjp;输出先编成 FrozenProgram,semantic_vjp 倒序遍历,导数程序沿用原叶子的输入键再包成 TracedTensor,所以能再求导(HVP 与有限差分差 7.3e-10);导数程序自带前向重算;vjp_many 一次变换求多个梯度;AdContext 的变换缓存按结构指纹命中,变换从约 175 µs 降到约 86 µs。
  3. eager AD:EagerRuntime 不持有 engine,运算写在 with_eager_session(&mut engine, |s| ...) 的回调里,立即执行,同时往 semantic trace 追加节点、按 ResidualSpec 留住残差;组合运算只在不跟踪时走融合内核;backward 对所有活着的梯度槽做一次语义 VJP,残差当输入不重算前向,梯度累加进槽位,可以调两次;值记录表在训练循环里不再增长。
  4. semantic AD 与扩展:einsum、线性代数、FFT 的规则按 family id 注册进 SemanticExtensionRuleSet,经 AdContext 交给 traced、经 EagerRuntime::with_ad_context 交给 eager;反向优先用 primal_vjp,否则先线性化再转置;没注册时两边报的错误类型不同;eager einsum 在需要长度 1 广播时梯度形状仍然错,三操作数时前向就被 CPU provider 拒绝。
flowchart TB
  tt["TracedTensor 运算"] --> prog["语义程序 FrozenProgram"]
  et["EagerSession 运算"] -->|立即执行并记录| trace["semantic trace"]
  trace --> prog
  prog --> xf["semantic_jvp 或 semantic_vjp"]
  core["核心算子规则"] --> xf
  ext["运算族规则集"] -->|AdContext| xf
  xf --> dp["导数程序"]
  dp -->|traced| back["包回 TracedTensor"]
  dp -->|eager| run["在 engine 上执行 累加进梯度槽"]

第一页讲规则,后三页按「traced 怎么用规则、eager 怎么用规则、运算族的规则从哪来」排列。和 burn、candle 的反向在行为上的差别(能不能 backward 两次、梯度存在哪),eager 页各用一句话对照(burn 的 backward、candle 的 backward),完整的对照在 设计取舍。

💬 关联讨论

发起讨论
加载中...