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。
问: 编译器的 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。