the trait From<tenferro_einsum::Error> is not implemented
主线位置:eager 主线的第 2 步。eager 运算写在
rt.with_eager_session(&mut engine, |s| ...)的闭包里,闭包返回Result<T, E>;einsum 是tenferro-einsum加在会话上的扩展方法,返回自己的错误类型。会话 API 见 eager AD。
flowchart TB cl["闭包末尾 s.reduce_sum"] --> e["E 推成 runtime Error"] q["s.einsum 后写 ?"] --> need["需要 From einsum Error"] e --> need need --> no["没有这个 From"] no --> err["E0277"] fix["Ok 标注 einsum Error"] --> ok["编译通过"]
实验 crate exp-tenferro8/pit1-pit,bin p15_plain、p15_guide、p15_guide_map(debug,编译失败)、p15_fix、p15_fix_split(debug 运行);M2 Max、macOS 27、rustc 1.99.0。报错里 cargo 缓存的路径前缀写成 ...。
现象
在一个会话里先 einsum 再求和,照常写 ?:
let rt = EagerRuntime::new()?;
let mut engine = CpuEngine::serial();
let a = rt.variable_from(Tensor::from_vec_col_major(vec![2, 3], vec![1.0_f64, 4.0, 2.0, 5.0, 3.0, 6.0])?)?;
let b = rt.constant_from(Tensor::from_vec_col_major(vec![3, 2], vec![1.0_f64, 0.0, 1.0, 0.0, 1.0, 1.0])?)?;
let loss = rt.with_eager_session(&mut engine, |s| {
let out = s.einsum(&[&a, &b], "ij,jk->ik")?;
s.reduce_sum(&out, None)
})?;
error[E0277]: `?` couldn't convert the error to `tenferro_ad::Error`
--> src/bin/p15_plain.rs:11:47
|
11 | let out = s.einsum(&[&a, &b], "ij,jk->ik")?;
| ------------------------------^ the trait `From<tenferro_einsum::Error>` is not implemented for `tenferro_ad::Error`
| |
| this can't be annotated with `?` because it has type `Result<_, tenferro_einsum::Error>`
|
= note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait
help: `tenferro_ad::Error` implements trait `From<T>`
--> .../tenferro-runtime-0.8.0/src/error.rs:215:21
|
215 | TensorRuntime(#[from] tenferro_tensor::Error),
| ^^^^ `From<tenferro_tensor::error::Error>`
...
tenferro_ad::Error 就是 tenferro_runtime::Error(tenferro-ad 的 lib.rs 里 pub use tenferro_runtime::{ContextId, Error, ErrorPhase, Result};),所以 help 指向 tenferro-runtime 的源码。
照迁移指南第 90 行的建议改:「Annotate Ok::<_, tenferro_ad::Error>(..) when the error type is not otherwise inferred; map tensor-level errors with .map_err(tenferro_ad::Error::from)」。两种都编不过:
// p15_guide
let out = s.einsum(&[&a, &b], "ij,jk->ik")?;
Ok::<_, tenferro_ad::Error>(s.reduce_sum(&out, None)?)
// p15_guide_map
let out = s.einsum(&[&a, &b], "ij,jk->ik").map_err(tenferro_ad::Error::from)?;
Ok::<_, tenferro_ad::Error>(s.reduce_sum(&out, None)?)
p15_guide 报的是和上面一字不差的 E0277;p15_guide_map 报两个:
error[E0277]: the trait bound `tenferro_ad::Error: From<tenferro_einsum::Error>` is not satisfied
--> src/bin/p15_guide_map.rs:11:15
|
11 | let out = s.einsum(&[&a, &b], "ij,jk->ik").map_err(tenferro_ad::Error::from)?;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `From<tenferro_einsum::Error>` is not implemented for `tenferro_ad::Error`
...
error: could not compile `pit1-pit` (bin "p15_guide_map") due to 2 previous errors
出处
with_eager_session 的签名把闭包的错误类型交给调用方选:
pub fn with_eager_session<T, E: From<Error>>(
self: &Arc<Self>,
engine: &mut dyn ExecutionEngine,
f: impl FnOnce(&mut EagerSession<'_>) -> std::result::Result<T, E>,
) -> std::result::Result<T, E> {
E 只要求能从 runtime 的 Error 转换。闭包最后一个表达式 s.reduce_sum(..) 是核心运算,返回 Result<_, tenferro_runtime::Error>,E 就被推成它。EagerSessionEinsumExt::einsum 返回的是 tenferro_einsum::Result,错误类型是 einsum 自己的 Error;? 要把它转成 E,需要 From<tenferro_einsum::Error> for tenferro_runtime::Error,而这个实现不存在。
方向是反的:einsum 的 Error 有一个 Runtime(#[from] tenferro_runtime::Error) 变体,能从 runtime 错误转过来;反过来的 From 没有写。tenferro-runtime 看不见 einsum 的类型,这个实现只能写在 tenferro-einsum 里,孤儿规则允许(From 的类型参数是 einsum 的本地类型),tenferro-fft 就在 eager_in_place.rs 里给自己的 EagerFftInPlaceError 写了 impl From<EagerFftInPlaceError> for Error;einsum 没有写。linalg 和 fft 的会话扩展方法(EagerSessionLinalgExt、EagerSessionFftExt)直接返回 tenferro_ad::error::Result,和核心运算同一个错误类型,所以只有 einsum 会这样。迁移指南的写法把 E 显式定成了 tenferro_ad::Error,也就是同一个 runtime 错误,所以和不写一样。
上游教程 einsum_subscripts_to_gradients 没有踩到,是因为它的闭包只有一个表达式 |s| s.einsum(&[&a, &b], "ij,jk->ik"),E 直接推成了 einsum 的错误。
修法
把闭包的错误类型定成 einsum 的,核心运算的 runtime 错误经 #[from] 自动转进来:
let loss = rt.with_eager_session(&mut engine, |s| {
let out = s.einsum(&[&a, &b], "ij,jk->ik")?;
Ok::<_, tenferro_einsum::Error>(s.reduce_sum(&out, None)?)
})?;
或者把 einsum 单独放进一个会话:
let out = rt.with_eager_session(&mut engine, |s| s.einsum(&[&a, &b], "ij,jk->ik"))?;
let loss = rt.with_eager_session(&mut engine, |s| s.reduce_sum(&out, None))?;
两种都能编译、能求导(loss.backward(&mut engine)? 之后读 a.grad()):
loss = [30.0], a.grad = [1.0, 1.0, 1.0, 1.0, 2.0, 2.0]
(p15_fix、p15_fix_split,输出相同。)外层函数返回 Box<dyn std::error::Error> 或 anyhow::Result 时,tenferro_einsum::Error 照样能用 ? 传出去。
怎么确认没中招
- E0277 的文字是
From<tenferro_einsum::Error>is not implemented fortenferro_ad::Error(或tenferro_runtime::Error),出错位置在with_eager_session的闭包里:把闭包的Ok标成Ok::<_, tenferro_einsum::Error>。 - 不要照迁移指南写
Ok::<_, tenferro_ad::Error>,它和 runtime 的错误是同一个类型。
小结
with_eager_session的闭包错误类型E由闭包推断;末尾是核心运算时推成 runtime 的Error,它不能从tenferro_einsum::Error转换。tenferro_ad::Error就是 runtime 的Error,迁移指南的Ok::<_, tenferro_ad::Error>和.map_err(tenferro_ad::Error::from)都编不过(0.8.0)。- 写
Ok::<_, tenferro_einsum::Error>(..),或者把 einsum 放进单独的会话。