Rust in Everything
基于 tenferro-runtime 0.8.0

tenferro-rs 源码解析

全卷只对着一个版本写。tenferro 自己是一个仓库;它下面的 CPU 收缩、线性代数、线程、视图、计算图和收缩规划各在自己的仓库里单独发布。版本按一个新项目从 crates.io 实际解析到的锁定:

项版本commit日期说明
tenferro-rs 工作区(16 个 crate,同一个版本号)0.8.0,tag v0.8.0a9f494cc2026-10-30tag 指向「release: prepare v0.8.0 (#2331)」,和 v0.7.1 之间 838 个提交
tcontract、tcontract-exec(二元张量收缩、执行资源)0.9.09f793ede2026-10-30tensor4all/tcontract-rs
tcontract-kernel(GEMM 微内核)0.3.03239c86d2026-10-18同一个仓库,较早的 commit
cpueinsum(CPU 上的 einsum 与二元收缩计划)0.9.0124aa12d2026-10-30tensor4all/cpueinsum-rs
tlinalg(基于 faer 的线性代数内核)0.8.053d1f0d92026-10-30tensor4all/tlinalg-rs
threadteam(线程池与放置令牌)0.2.03f95f6172026-10-30tensor4all/threadteam-rs
strided-rs:strided-kernel、-fused、-basic、-nn0.9.06bdf8e752026-10-30tensor4all/strided-rs
strided-rs:strided-view 0.7.1、strided-perm 0.9.17053f9d12026-10-30同一个仓库,晚一个提交;tenferro 仓库的锁文件里还是 0.7.0 / 0.9.0
computegraph0.1.0594cdcec2026-06-20和 0.7.1 时相同;工作区不再用 git rev 锁它
omeco(收缩顺序优化)0.2.66c83996f2026-07-10和 0.7.1 时相同
t4a-faer(tensor4all 的 faer 分叉)0.27.081f3b1d3—只从 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 的线程数和重复次数。机器上同时有别的构建在跑,计时取多次里的最小值,并注明当时的负载。

各部分

  1. 00 导读:锁定的版本;三条主线和 einsum 线每一步落在哪;README「Conventions you must know」五条逐条用程序核实;feature 各打开什么,native 和 blas 为什么必须在所有 crate 上一致;crate 分层、规模和外部 provider 仓库各管什么。
  2. 01 全景:tenferro 在 ndarray、faer 和 burn、candle 之间的位置,四种张量 API 为什么都在;一次 matmul、一次 grad 各从头到尾走一遍,带调用栈和分阶段计时。
  3. 02 张量:数据模型、列主序与视图、存储与所有权。
  4. 03 CPU 后端:CpuEngine 与线程池(engine 持有什么、threadteam 池、批处理策略、缓冲池上限)、provider(tcontract / cpueinsum / tlinalg / t4a-faer 与 BLAS)、逐元素(SIMD、融合、缓冲池回收)。
  5. 04 图与运行时:computegraph、TracedTensor、编译、prepare / run / warm_up 与缓存。
  6. 05 自动微分:primitive AD、traced AD、eager AD、semantic AD 与扩展。
  7. 06 运算族:einsum、线性代数、FFT、激活、softmax 与归一化、扩展算子。
  8. 07 GPU 与 XLA:CUDA(kernel-catalogue engine、厂商库)、XLA(StableHLO 与 PJRT),两页都只读源码。
  9. 08 设计取舍:列主序、没有门面 crate、调用方持有的 engine、两套 AD、运算族在核心之外,和 burn、candle 集中对照。
  10. 09 常见坑:用报错原文或现象做标题,每个都先跑出最小复现。
  11. 10 在本站中:用 tenferro 对本站文章的 TF-IDF 矩阵做截断 SVD(潜在语义分析),和 burn 卷、candle 卷的做法对照。
  12. 11 动手:不依赖 tenferro,约 300 行写一个能规划收缩顺序的 mini einsum,再和 tenferro-einsum 的结果、规划代价对照。

适合谁读

  • 写过 Julia、Fortran 或 NumPy 的科学计算,想在 Rust 里要一套能求导、能做 einsum 和线性代数的张量栈的读者:从导读开始,先读怎么读这一卷里的五条约定,再读 01 全景。
  • 读过 burn 卷、candle 卷,想看一个不以深度学习为目标的张量库怎么分层的读者:先读 tenferro 解决什么问题,最后读 08 设计取舍。
  • 需要的前置知识:Rust 的 trait、&mut 借用和 ?,Cargo feature;线性代数和反向传播的基本概念;知道 JAX 的 grad / jvp 和 PyTorch 的 backward() 各是什么会更好读,但不是必需。

💬 关联讨论

发起讨论
加载中...