Rust in Everything
基于 tenferro-ad 0.8.0

with_eager_session 返回元组:E0283

主线位置:变换主线的「grad」一步,eager 写法的前向:一个回调里算出中间结果和 loss,一起带出来。
版本:PyTorch 2.15.0(CPU)、tenferro-rs 0.8.0(CPU,native)、rustc 1.99.0
flowchart TB
  cb["回调返回 Ok p, loss"] --> e{"E 能推出来吗"}
  e -->|不能| err["E0283"]
  cb2["回调末尾 s.reduce_sum"] --> e2["E 从它推出"]
  e2 --> ok["编译通过"]
  fix["写明错误类型"] --> ok

实验代码:PyTorch 脚本一个,tenferro 程序 tuple_closure(编不过的写法)、tuple_closure_hint(照编译器提示改)、tuple_closure_fixed(修好的写法),release。平台 M2 Max、macOS 27、rustc 1.99.0。

现象

PyTorch 的前向函数常常顺手把中间结果也返回出来,留着检查或画图:

def forward(x):
  p = x * x
  return p, p.sum()

x = torch.tensor([1.0, 2.0, 3.0], dtype=torch.float64, requires_grad=True)
p, loss = forward(x)
loss.backward()
p = [1.0, 4.0, 9.0]  x.grad = [2.0, 4.0, 6.0]

tenferro 的 eager 前向写在 with_eager_session 的回调里,照样返回一个元组:

let (p, loss) = rt.with_eager_session(&mut eng, |s| {
  let p = s.mul(&x, &x)?;
  let loss = s.reduce_sum(&p, None)?;
  Ok((p, loss))
})?;
error[E0283]: type annotations needed
    --> src/bin/tuple_closure.rs:12:5
     |
   9 |   let (p, loss) = rt.with_eager_session(&mut eng, |s| {
     |                      ------------------ type must be known at this point
...
  12 |     Ok((p, loss))
     |     ^^ cannot infer type of the type parameter `E` declared on the enum `Result`
     |
     = note: the type must implement `From<tenferro_ad::Error>`
note: required by a bound in `EagerRuntime::with_eager_session`
...
help: consider specifying the generic arguments
     |
  12 |     Ok::<(EagerTensor, EagerTensor), E>((p, loss))
     |       +++++++++++++++++++++++++++++++++

出处

EagerRuntime::with_eager_session 的签名是 with_eager_session<T, E: From<Error>>:回调返回 Result<T, E>,E 由调用方决定,只要能从 tenferro_ad::Error 转过来。回调里的每个 ? 都经 From 转成 E,外面 main 的 ? 又把 E 转成 Box<dyn Error>,没有一处把 E 定下来。回调最后一个表达式是 s.reduce_sum(..) 这类调用时,它的返回类型 Result<EagerTensor, tenferro_ad::Error> 就定下了 E,所以只返回一个张量的回调从来不用写类型;改成自己构造的 Ok(..),E 就悬空了。这个签名的来历(0.8.0 把两层 Result 压成一层)见 backward。

📝NOTE
没有蠢问题

问: 编译器的 help 已经给出了写法,照抄不就行了?

答: 照抄编不过:

error[E0425]: cannot find type `EagerTensor` in this scope
  --> src/bin/tuple_closure_hint.rs:12:11
...
error[E0425]: cannot find type `E` in this scope
   --> src/bin/tuple_closure_hint.rs:12:38

help 里的 E 是签名里那个类型参数的名字,不是一个能在调用处写出来的类型;EagerTensor 也没有导入。要写的是一个具体的错误类型。

修法

给 Ok 写上错误类型,元组的类型交给推断:

let (p, loss) = rt.with_eager_session(&mut eng, |s| {
  let p = s.mul(&x, &x)?;
  let loss = s.reduce_sum(&p, None)?;
  Ok::<_, tenferro_ad::Error>((p, loss))
})?;
p = [1.0, 4.0, 9.0], x.grad = Some([2.0, 4.0, 6.0])
single-tensor closure: [14.0]

第二行是只返回一个张量、末尾直接写 s.reduce_sum(&p, None) 的回调,不用标注。回调里用了 einsum 时,错误类型要换成更宽的 tenferro_einsum::Error,那是 tenferro 卷的常见坑 the trait From<tenferro_einsum::Error> is not implemented。

怎么确认没中招

  • 这是编译期错误,不会漏到运行时;看到 E0283 指向回调里的 Ok(,就是这一条。
  • 回调里混用了几个 crate 的运算时,错误类型选能从所有这些错误转过来的那个。

小结

  • with_eager_session 的错误类型 E 由回调决定;返回自己构造的 Ok((p, loss)) 时没有东西定下 E,报 E0283。
  • 编译器 help 里的 Ok::<(EagerTensor, EagerTensor), E> 不能照抄。
  • 写 Ok::<_, tenferro_ad::Error>((p, loss));用了 einsum 就换成 tenferro_einsum::Error。

💬 关联讨论

发起讨论
加载中...