tenferro-rs 源码解析
全卷只对着一个版本写。tenferro 自己是一个仓库;它下面的 CPU 收缩、线性代数、线程、视图、计算图和收缩规划各在自己的仓库里单独发布。版本按一个新项目从 crates.io 实际解析到的锁定:
| 项 | 版本 | commit | 日期 | 说明 |
|---|---|---|---|---|
| tenferro-rs 工作区(16 个 crate,同一个版本号) | 0.8.0,tag v0.8.0 | a9f494cc | 2026-10-30 | tag 指向「release: prepare v0.8.0 (#2331)」,和 v0.7.1 之间 838 个提交 |
| tcontract、tcontract-exec(二元张量收缩、执行资源) | 0.9.0 | 9f793ede | 2026-10-30 | tensor4all/tcontract-rs |
| tcontract-kernel(GEMM 微内核) | 0.3.0 | 3239c86d | 2026-10-18 | 同一个仓库,较早的 commit |
| cpueinsum(CPU 上的 einsum 与二元收缩计划) | 0.9.0 | 124aa12d | 2026-10-30 | tensor4all/cpueinsum-rs |
| tlinalg(基于 faer 的线性代数内核) | 0.8.0 | 53d1f0d9 | 2026-10-30 | tensor4all/tlinalg-rs |
| threadteam(线程池与放置令牌) | 0.2.0 | 3f95f617 | 2026-10-30 | tensor4all/threadteam-rs |
strided-rs:strided-kernel、-fused、-basic、-nn | 0.9.0 | 6bdf8e75 | 2026-10-30 | tensor4all/strided-rs |
strided-rs:strided-view 0.7.1、strided-perm 0.9.1 | 7053f9d1 | 2026-10-30 | 同一个仓库,晚一个提交;tenferro 仓库的锁文件里还是 0.7.0 / 0.9.0 | |
| computegraph | 0.1.0 | 594cdcec | 2026-06-20 | 和 0.7.1 时相同;工作区不再用 git rev 锁它 |
| omeco(收缩顺序优化) | 0.2.6 | 6c83996f | 2026-07-10 | 和 0.7.1 时相同 |
| t4a-faer(tensor4all 的 faer 分叉) | 0.27.0 | 81f3b1d3 | — | 只从 cargo 缓存读,不建源码链接;tenferro 以 tenferro_cpu::faer 再导出 |
| t4a-cuda-runtime、t4a-cuda-kernels(CUDA) | 0.1.0 | — | — | 只从 cargo 缓存读,本机不编译 |
commit 取自每个 crate 包里的 .cargo_vcs_info.json。tenferro 的仓库从 2026-02-13 开始,到 v0.8.0 共 2546 个提交。0.8.0 是一次破坏性的大改:后端会话换成调用方持有的 engine,CPU 计算搬到上表那几个单独发布的 provider 上,WebGPU 后端删除。本卷描述的是 0.8.0 本身,只在讲设计理由或迁移陷阱时提一句 0.7.1。
tenferro 给自己的一句话是「A Rust-native tensor computation stack with opt-in autodiff for scientific workloads.」README 接着给出它的位置:「tenferro-rs sits between low-level array crates and full deep-learning frameworks for Rust scientific code that needs column-major storage, LAPACK/Fortran/Julia-friendly layouts, dynamic-shape traced programs, explicit device control, and operation families whose AD rules can live outside the core tensor type.」它不是又一个深度学习框架,而是给张量网络这类科学计算用的张量栈。这句话怎么落到代码上、和 ndarray、faer、burn、candle 有什么不同,见 tenferro 解决什么问题。
本卷讲 tenferro-tensor-core / tenferro-tensor(数据模型、列主序、视图、存储)、tenferro-cpu 和三个内部 CPU crate(engine、threadteam 池、provider、缓冲池、融合逐元素)、tenferro-runtime(traced 张量、编译、prepare / run、缓存)、tenferro-ad(primitive AD、semantic AD、eager 与 traced)、tenferro-einsum、tenferro-linalg、tenferro-fft、激活 / softmax / 归一化这一族新运算、扩展算子,以及 tenferro-gpu 的 CUDA 路径和 tenferro-xla。实测平台只有 macOS 上的 CPU。0.8.0 已经没有 WebGPU 后端(README 第一段仍写着「experimental WebGPU backend」,和代码不符);CUDA 后端和 tenferro-xla(StableHLO 与 PJRT 插件)本机没有条件运行,只读源码,凡是只读了源码的地方都会写明。C API、CI 与基准设施、ext/ 下的 sparse、tropical 和几个标量示例 crate 不展开。
全卷沿三条主线和一条贯穿的线展开:
- 直接计算主线(一次
a.matmul(&b, &mut engine)):TypedTensor::from_vec_col_major建列主序张量 → 调用方建CpuEngine::serial()或CpuEngine::new(pool)(池是 threadteam 的Pool)→matmul变成 engine 上的dot_general_read,在调用线程上执行 → cpueinsum 的二元计划 → tcontract 选中 faer 策略 → faer 写进未初始化的输出 →host_data借出主机内存。 - traced 主线(一次
y.grad(&x)再执行):TracedTensor的运算只往 computegraph 的Graph里加节点 →grad/vjp/jvp按 AD 规则生成新图 →GraphCompiler::compile物化、去重,冻结成SemanticProgram→Runtime::builder()注册 CPU engine 的描述 →runtime.prepare(&program, &[], &[engine.target()])→runtime.run(&mut [&mut engine], ..)→into_tensor。 - eager 主线(一次
loss.backward(&mut engine)):EagerRuntime::new()(不持有 engine)→variable_from建可求导变量 →with_eager_session(&mut engine, |s| ..)里每个运算立即执行并记录 →backward(&mut engine)把梯度累加进grad()槽,clear_grad()清掉。 - einsum 线:
"ij,jk->ik"→ 解析成EinsumNotation、展开省略号 → 两个输入且恰好是一次dot_general时直接发出,不建收缩树;三个及以上输入时ContractionTree定收缩顺序(默认是 tenferro 自己的确定性贪心,开了退火才调 omeco 的 TreeSA)→ 每一步两两收缩 → 它自己的 semantic AD 规则。
每条线的每一步落在哪个函数、哪一页讲,见怎么读这一卷。
flowchart TB intro[00 导读] --> overview[01 全景] overview --> tensor[02 张量] tensor --> cpu[03 CPU 后端] cpu --> rt[04 图与运行时] rt --> ad[05 自动微分] ad --> ops[06 运算族] cpu --> gpu["07 GPU 与 XLA"] ops --> design[08 设计取舍] gpu --> design design --> pit[09 常见坑] pit --> site[10 在本站中] site --> hands[11 动手]
实验环境
关于行为的说法(一次调用落到哪个函数、各阶段多久、报什么错)都配一个小程序和它的真实输出,平台是 M2 Max(8 个性能核、4 个能效核)、macOS 27、rustc 1.99.0。实验依赖 crates.io 上的 =0.8.0 各 crate,CPU provider 用默认的 native(faer);和 BLAS 对照时另建一个 default-features = false、开 blas-accelerate(Apple Accelerate)的实验,它在本机能编能跑。一律离线构建,每个实验写明 debug 还是 release、engine 的线程数和重复次数。机器上同时有别的构建在跑,计时取多次里的最小值,并注明当时的负载。
各部分
- 00 导读:锁定的版本;三条主线和 einsum 线每一步落在哪;README「Conventions you must know」五条逐条用程序核实;feature 各打开什么,
native和blas为什么必须在所有 crate 上一致;crate 分层、规模和外部 provider 仓库各管什么。 - 01 全景:tenferro 在 ndarray、faer 和 burn、candle 之间的位置,四种张量 API 为什么都在;一次 matmul、一次 grad 各从头到尾走一遍,带调用栈和分阶段计时。
- 02 张量:数据模型、列主序与视图、存储与所有权。
- 03 CPU 后端:CpuEngine 与线程池(engine 持有什么、threadteam 池、批处理策略、缓冲池上限)、provider(tcontract / cpueinsum / tlinalg / t4a-faer 与 BLAS)、逐元素(SIMD、融合、缓冲池回收)。
- 04 图与运行时:computegraph、
TracedTensor、编译、prepare/run/warm_up与缓存。 - 05 自动微分:primitive AD、traced AD、eager AD、semantic AD 与扩展。
- 06 运算族:einsum、线性代数、FFT、激活、softmax 与归一化、扩展算子。
- 07 GPU 与 XLA:CUDA(kernel-catalogue engine、厂商库)、XLA(StableHLO 与 PJRT),两页都只读源码。
- 08 设计取舍:列主序、没有门面 crate、调用方持有的 engine、两套 AD、运算族在核心之外,和 burn、candle 集中对照。
- 09 常见坑:用报错原文或现象做标题,每个都先跑出最小复现。
- 10 在本站中:用 tenferro 对本站文章的 TF-IDF 矩阵做截断 SVD(潜在语义分析),和 burn 卷、candle 卷的做法对照。
- 11 动手:不依赖 tenferro,约 300 行写一个能规划收缩顺序的 mini einsum,再和 tenferro-einsum 的结果、规划代价对照。