this build selects no CPU numerical backend
主线位置:直接计算主线的第 4 步。
matmul落到dot_general,CPU engine 把它交给编译期选定的 provider(native或blas,见 CPU provider);一个都没选时,运算到这里才失败。
flowchart TB dep["都关默认 没选 blas"] --> cpu["tenferro-cpu 无 provider"] cpu --> build["照样编译"] build --> ew["add exp 正常"] build --> mm["matmul einsum"] mm --> un["运行时 Unsupported"] dep --> la["tenferro-linalg"] la --> ce["compile_error 加 89 个错"] fix["选 native 或一个 blas"] --> ok["matmul 正常"]
实验 crate exp-tenferro8/pit1-none(release 运行)、pit1-nonelinalg(debug,编译失败);M2 Max、macOS 27、rustc 1.99.0,cargo --offline。报错里 cargo 缓存的路径前缀写成 ...。
现象
照 release notes 说的「a BLAS build needs default-features = false」,把每个 tenferro crate 的默认 feature 都关掉,却忘了写选哪个 BLAS:
[dependencies]
tenferro-runtime = "=0.8.0"
tenferro-cpu = { version = "=0.8.0", default-features = false }
tenferro-einsum = { version = "=0.8.0", default-features = false }
能编译,能运行。打印 provider,再做加法、exp、矩阵乘和一次 einsum:
provider: tenferro.cpu.none
add: Ok([2.0, 4.0, 6.0, 8.0])
exp: Ok([2, 2])
matmul: Err(dot_general: unsupported operation: this build selects no CPU numerical backend; enable the `native` or `blas` feature)
debug: Unsupported { op: "dot_general", message: "this build selects no CPU numerical backend; enable the `native` or `blas` feature" }
einsum: Err(dot_general: unsupported operation: this build selects no CPU numerical backend; enable the `native` or `blas` feature)
(pit1-none,release。)逐元素运算不需要 provider,测试只覆盖了逐元素路径时,问题要到第一次收缩才出现。
依赖里再加一个 tenferro-linalg = { version = "=0.8.0", default-features = false }(pit1-nonelinalg),这次在编译期就失败,第一条错误说得很清楚,后面却跟着一大串:
error: tenferro-linalg needs exactly one CPU backend: enable the `native` (default) or `blas` feature
--> .../tenferro-linalg-0.8.0/src/lib.rs:117:1
|
117 | / compile_error!(
118 | | "tenferro-linalg needs exactly one CPU backend: enable the `native` (default) \
119 | | or `blas` feature"
120 | | );
| |_^
error[E0432]: unresolved import `crate::cpu::tlinalg_error`
--> .../tenferro-linalg-0.8.0/src/cpu/linalg/faer_linalg.rs:22:17
...
error: could not compile `tenferro-linalg` (lib) due to 90 previous errors
90 个里除了 compile_error!,是 5 个 E0432 和 84 个 E0433,都是被 #[cfg] 关掉的模块找不到。
出处
tenferro-cpu 的 lib.rs 在互斥规则上面写了这条设计:
// A build with neither backend compiles: the CPU numerical routes report a
// typed `Unsupported` instead. `native` is the default feature, so a normal
// dependency always gets a backend, and selecting `blas` alone is the supported
// alternative. This keeps any subset of the stack compilable, which is what
// consumers that select a backend crate by crate rely on.
没有 provider 时,每条数值路线都调 contraction.rs 里的 no_backend:
#[cfg(not(any(feature = "native", feature = "blas")))]
pub(crate) fn no_backend(op: &'static str) -> crate::Error {
crate::Error::unsupported(
op,
"this build selects no CPU numerical backend; enable the `native` or `blas` feature",
)
}
einsum 的二元收缩最后也落到 dot_general,所以报的是同一个 op。逐元素运算用的是 strided-rs 的内核(见逐元素运算与融合),不经过 provider,照常能算。
tenferro-linalg 走了另一条路:它的 CPU 路线按 provider 分别编译,没有「无 provider」的版本,于是在 lib.rs 用 compile_error! 拒绝。注释说要让它「named as such rather than as a missing internal type」,但 compile_error! 不让编译器停下,那些「missing internal type」的错误还是全部报了出来。
PR #2004(首次发布于 v0.8.0)把 provider 改成编译期二选一。它的第一个提交 04fed1d2 在 tenferro-cpu 里写的是 compile_error!("enable exactly one CPU backend: native or blas"),和 0.7.1 的「enable at least one CPU backend」一样在编译期拦下。同一个 PR 里的提交 b0601554 删掉了这一条,说明是「tenferro-cpu and tenferro-einsum accept a build with neither CPU backend and report a typed Unsupported from every numerical route; consumers select the backend crate by crate (the tutorial examples do), so a subset build must not fail to compile」,同时给 tenferro-linalg 加了上面那条 compile_error!。代价就是这一页:没选 provider 的构建不再在编译期暴露。
修法
要 BLAS,就在每个关了默认的 crate 上开同一个 blas-*(写法和原因见 native and blas are mutually exclusive);不要 BLAS,就别关默认,native 是默认 feature。pit1-nonefix 只把 tenferro-cpu 一行改回 tenferro-cpu = "=0.8.0",einsum 那行和 main 都不动:
provider: tenferro.cpu.faer
add: Ok([2.0, 4.0, 6.0, 8.0])
exp: Ok([2, 2])
matmul: Ok([7.0, 10.0, 15.0, 22.0])
einsum: Ok([2, 2])
(release。)tenferro-einsum 自己关了默认也没关系:provider 是 tenferro-cpu 的 feature,依赖图里有一处打开 native 就够了。
怎么确认没中招
- 程序启动时打印或断言
tenferro_cpu::cpu_provider_id()(#[doc(hidden)]的公开函数):tenferro.cpu.none就是这个坑。 - 测试里至少放一次矩阵乘或 einsum,不要只测逐元素运算。
cargo tree -e features -i tenferro-cpu里应该有tenferro-cpu feature "native"或tenferro-cpu feature "blas",两个都没有就是没选。- linalg 的 90 个错误只看第一条。
小结
- 0.8.0 的
tenferro-cpu和tenferro-einsum没选 provider 也能编译,逐元素运算正常,收缩在运行时返回Unsupported。 tenferro-linalg没有 provider 时compile_error!,后面跟 89 个连带错误。- 关默认 feature 时必须同时选一个
blas-*;启动时检查cpu_provider_id()。