从 Julia 到 Rust:一个用于科学计算的可微分张量栈
从 Julia 到 Rust:在代理 AI 时代的科学计算的可微分张量栈 tenferro-rs 是一个本地化的 Rust 密集张量栈:线性代数、PyTorch 风格的急切自动微分、JAX 风格的追踪变换、NumPy 风格的 einsum、FFT、可扩展操作库,以及显式的 CPU/CUDA 后端。第一个 crate 在 2026 年 6 月 23 日(日本标准时间)在 crates.io 上发布。作者:篠冈宏志(埼玉大学),属于 tensor4all 团队 🌐 英语 · 日本语 · 简体中文 大多数张量网络代码是用 Julia 编写的,我们的代码也不例外。ITensors 及其周边生态系统适合原型开发:代码保持与数学的接近,迭代很容易。我们在 IR 基础、稀疏建模,以及 tensor4all 张量交叉插值和量子堆栈上的工作最初都是从这里开始的。然而,一旦代码库变得庞大,Julia 的开发速度就开始放缓:运行时才显现出的类型不稳定性,延长的编译和预编译时间,使得编辑/测试循环变得更加漫长,随着代码的增长,正确性检查的感觉变得更加困难。当我们开始将张量网络栈嵌入到一个更大的系统中时,这一点变得更难以忽视。于是我们开始将计算引擎转移到 Rust。这立即暴露出了第二个问题。我们希望构建的张量库尚未出现。Rust 拥有用于单个工作的库:用于数组的 ndarray,深度学习的 Burn,线性代数的 faer。缺少的是一个张量层,能够通过 einsum 提供自动微分,并且在科学计算中感觉可用。我们的目标不是替换那些库,而是连接已经存在的部分。Rust 生态系统在过去几年中发生了很大变化。crates.io 从 2015 年的 602 个 crate 增长到 2026 年的约 210,000 个(数据)。对于密集线性代数,有 faer;对于 GPU 内核,有 CubeCL;对于通用数值计算,有 num-traits 和 num-complex。还有一些邻近层的库:用于数组的 ndarray,线性代数的 nalgebra 和 faer,深度学习的 Burn 和 candle,以及类似 NumPy 的数组 API 的 numr。我们需要的是它们之间的层:一个具有列优先存储、动态形状、急切和追踪自动微分、einsum、FFT、CPU/CUDA 后端和可扩展操作的科学计算张量栈。这就是 tenferro-rs 的目的。我们构建在 faer 和 CubeCL 的基础上,添加缺失的部分,而不是重新发明它们。将 SparseIR.jl 和 Julia 张量网络代码移植,使得缺失的层在哪里变得更加清晰。这就是 tenferro-rs 的背景。本文解释了我们为什么构建它,以及我们为什么选择 Rust,而不是继续用 Julia。为什么现在选择 Rust,而之前用 Julia 就很好?几年前我可能会告诉学生从 Julia 开始。Julia 代码可以与数学保持接近,内存管理简单,数值库已经存在。Rust 学习的东西更多,生态系统仍然缺失一些组件。而我现在不会给出相同的建议。并不是因为 Rust 改变了,而是因为我不再是大多数代码的编写者。Fortran、Python 和 Julia 都围绕着降低人类手动编写、阅读和维护代码的成本而发展。可读性、REPL、接近数学的符号和低入口门槛对这一点都很重要。随着 AI 编写越来越多的代码,权衡发生了变化。写作速度的重要性降低了。大部分学习成本可以由代理处理。但“它读起来像数学”并不能保证正确性:别名、变异和分配在一行的表面上是不可见的。对我们来说,问题不再是“人类能多快写出这些?”而是“我们能多确信它是正确的?”这种重新思考就是为什么 Rust 成为更实际的选择。具体来说:所有权和类型在编译时排除了广泛的错误。cargo check 在秒内给出答案,因此当代理出错时,我们能在运行程序之前发现。Cargo 在一个地方处理构建、依赖解析、测试和基准测试。没有 CMake,没有连接时的版本冲突。从头构建完整的栈加上依赖项在笔记本上只需几分钟,而编辑/测试循环只需几十秒。Rust 控制符号在模块和 crate 边界的可见性。代理只能在一个层内工作;它不能进入另一个 crate 的内部并悄悄破坏抽象。在大约 130K 行的 AI 编写的代码库中,这个边界非常重要。生命周期和所有权机制可以大部分留给代理,因此人类的注意力可以集中在算法、设计和正确性上。早期对 Rust 造成的学习成本现在不再是问题。在 C++、Python 和 Julia 中,大型代码库往往带来验证难度增加的担忧。而在 Rust 中,这种担忧明显更小。
本站免费、广告极少。如果觉得有帮助,可以请我们喝杯咖啡 —— 任何金额都对持续运营有实际帮助。
☕请我喝杯咖啡