Rust in Everything
基于 tenferro-runtime 0.8.0

native and blas are mutually exclusive

主线位置:直接计算主线开始之前,Cargo.toml 这一步。CPU 的数值 provider 在编译期二选一:native(tcontract、cpueinsum、tlinalg、faer)或 blas(系统 BLAS/LAPACK)。两套各管哪些运算、Accelerate 快多少,见 CPU provider。

flowchart TB
  me["本 crate 开 blas-accelerate"] --> cpu["tenferro-cpu"]
  ein["tenferro-einsum 默认"] --> nat["转发 native"]
  nat --> cpu
  cpu --> both["native 和 blas 同时开"]
  both --> errs["9 个编译错误"]
  errs --> third["第 3 个才是原因"]
  fix["每个 crate 关默认 开同一 blas"] --> ok["编译 用上 Accelerate"]

实验 crate exp-tenferro8/pit1-trap、pit1-mutex(编译失败)、pit1-trapfix(release 运行);M2 Max、macOS 27、rustc 1.99.0,cargo --offline。报错里 cargo 缓存的路径前缀写成 ...。

现象

想让矩阵乘走 macOS 自带的 Accelerate。照 tenferro-cpu 的 feature 名,在它上面关掉默认、打开 blas-accelerate;einsum 照常依赖:

[dependencies]
tenferro-runtime = "=0.8.0"
tenferro-cpu = { version = "=0.8.0", default-features = false, features = ["blas-accelerate"] }
tenferro-einsum = "=0.8.0"

main.rs 只有 fn main() {},cargo build 在依赖 tenferro-cpu 里失败:

error[E0428]: the name `Scalar` is defined multiple times
  --> .../tenferro-cpu-0.8.0/src/contraction.rs:74:1
   |
70 | trait Scalar: PoolScalar + cpueinsum::Scalar {}
   | -------------------------------------------- previous definition of the trait `Scalar` here
...
74 | trait Scalar: PoolScalar + cpueinsum_blas::BlasScalar {}
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `Scalar` redefined here
   |
   = note: `Scalar` must be defined only once in the type namespace of this module

error[E0252]: the name `BinaryPlan` is defined multiple times
  --> .../tenferro-cpu-0.8.0/src/contraction.rs:28:5
...
error: native and blas are mutually exclusive; use --no-default-features for blas
  --> .../tenferro-cpu-0.8.0/src/lib.rs:68:1
   |
68 | compile_error!("native and blas are mutually exclusive; use --no-default-features for blas");
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error[E0119]: conflicting implementations of trait `contraction::Scalar`
...
error[E0308]: mismatched types
   --> .../tenferro-cpu-0.8.0/src/lib.rs:142:9
    |
142 |         "tenferro.cpu.faer"
    |         ^^^^^^^^^^^^^^^^^^^ expected `()`, found `&str`
...
error: could not compile `tenferro-cpu` (lib) due to 9 previous errors

(pit1-trap。中间省掉的是两个 work_len 方法不存在的 E0599、两个参数个数不对的 E0061。)9 个错误全在 tenferro-cpu 内部,第一个说某个 trait 重复定义,看上去像上游的代码坏了。真正的原因是第三个,而且它提示的 --no-default-features 这里已经写了。

更直接的写法,tenferro-cpu = { version = "=0.8.0", features = ["blas-accelerate"] } 不关默认(pit1-mutex),报的是同样的 9 个错误,顺序也一样。

出处

tenferro-cpu 的 lib.rs 只有一条规则管这件事:

#[cfg(all(feature = "native", feature = "blas"))]
compile_error!("native and blas are mutually exclusive; use --no-default-features for blas");

两个 feature 是怎么同时打开的,cargo tree -e features -i tenferro-cpu 说得很清楚:

tenferro-cpu v0.8.0
└── tenferro-einsum v0.8.0
    ├── tenferro-einsum feature "default"
    │   └── pit1-trap v0.1.0 (...)
    │       └── pit1-trap feature "default" (command-line)
    └── tenferro-einsum feature "native"
        └── tenferro-einsum feature "default" (*)
├── tenferro-cpu feature "blas"
│   └── tenferro-cpu feature "provider-src"
│       └── tenferro-cpu feature "blas-accelerate"
...
├── tenferro-cpu feature "native"
│   └── tenferro-einsum feature "native" (*)

tenferro-einsum 的 Cargo.toml 默认 feature 是 native,它转发 "tenferro-cpu/native";tenferro-ad、-linalg、-fft 也这样转发。cargo 对同一个依赖的 feature 取并集,所以只要依赖图里有一个 tenferro crate 留着默认,tenferro-cpu 就同时拿到 native 和 blas。在自己的 tenferro-cpu 那一行写 default-features = false 管不到别人那一行。

为什么还有另外 8 个错误:两套 provider 的代码都靠 #[cfg] 选,contraction.rs 里

#[cfg(feature = "native")]
trait Scalar: PoolScalar + cpueinsum::Scalar {}
#[cfg(feature = "native")]
impl<T: PoolScalar + cpueinsum::Scalar> Scalar for T {}
#[cfg(feature = "blas")]
trait Scalar: PoolScalar + cpueinsum_blas::BlasScalar {}

两个 feature 都开时两份定义都在,于是有重复定义、冲突实现;BinaryPlan 同时从 cpueinsum 和 cpueinsum_blas 导入,后面按 BLAS 版签名调用的地方就报方法不存在、参数个数不对;cpu_provider_id 的函数体里两个 #[cfg] 块都在,第一个块的值不在尾部位置,成了 E0308。compile_error! 不让编译器停下,这些错误照样报出来;在这次输出里它排在两个重复定义之后。

0.7.1 的 feature 叫 cpu-faer 和 cpu-blas,两者可以同时开(同时开时选 BLAS)。0.8.0 改名为 native 和 blas,并改成互斥(release notes:「The two are mutually exclusive, so a BLAS build needs default-features = false」),从 0.7.1 照搬 features = ["cpu-blas"],cargo 会直接拒绝(tenferro-cpu does not have that feature,pit1-oldfeat),改成新名字后就落进这个坑。

修法

依赖图里每个转发 provider feature 的 tenferro crate 都关默认,并开同一个 blas-*:

[dependencies]
tenferro-runtime = "=0.8.0"
tenferro-cpu = { version = "=0.8.0", default-features = false, features = ["blas-accelerate"] }
tenferro-einsum = { version = "=0.8.0", default-features = false, features = ["blas-accelerate"] }

main 里做一次 2×3 乘 3×2,再打印 tenferro_cpu::cpu_provider_id()(#[doc(hidden)] 的公开函数):

provider tenferro.cpu.blas
a*b col-major [4.0, 10.0, 5.0, 11.0]

(pit1-trapfix,release。)用到 tenferro-ad、tenferro-linalg、tenferro-fft 时它们也要同样写;tenferro-runtime 发布出去的清单里 native、blas-* 都是空列表(tenferro-cpu 只是它的 dev-dependency),照常写就行。开了 autodiff 的运算族 crate 会把 blas-accelerate 再转发给 tenferro-ad("tenferro-ad?/blas-accelerate"),但直接依赖的 tenferro-ad 那一行仍然要自己关默认。

怎么确认没中招

  • 报错落在 tenferro-cpu 内部、有 Scalar 重复定义或 BinaryPlan 重复导入,先在输出里找 mutually exclusive。
  • cargo tree -e features -i tenferro-cpu | grep native 应该没有输出;有输出时,它上面那一行就是谁打开了 native。
  • 运行期确认:tenferro_cpu::cpu_provider_id() 是 tenferro.cpu.blas 还是 tenferro.cpu.faer。

小结

  • native 和 blas 在 0.8.0 互斥;einsum、ad、linalg、fft 的默认 feature 都会替 tenferro-cpu 打开 native。
  • 只在 tenferro-cpu 上关默认不够,9 个错误里 compile_error! 排第三,前面是两套 #[cfg] 代码同时编译的连带错误。
  • 每个转发 provider 的 tenferro crate 都写 default-features = false 加同一个 blas-*。

💬 关联讨论

发起讨论
加载中...