tensor4all

Guide B

The stack for backend developers

What happens below the tensor network algorithms: a tensor runtime with automatic differentiation, and underneath it small, tensor-free libraries for strided contraction and batched linear algebra that you can use on their own, from Rust or from C. If you maintain a quantum chemistry or many-body code and need fast contractions without transposes, the middle of this page is for you.

Status as of 2026-10-13. Several crates described here are not on crates.io yet; this page says so where it matters.

The highlighted bands are the subject of this guide. Arrows from hataori show where execution resources enter the stack.

Who owns what

The backend is split along one line: a host that knows about tensors, and providers that only see borrowed strided memory. Most design decisions follow from keeping that line clean.

LayerOwnsLeaves to others
tenferro-rs (host)tensor types, allocation, dtype dispatch, device placement, AD rules, graph tracing, gauge conventions, error wrappingthe inner loops of contraction and factorization
cpueinsumexecuting an N-ary einsum in a given order: which labels each intermediate keeps, one reusable scratch bufferchoosing the order, parsing "ij,jk->ik", GPUs
tcontractone binary contraction over arbitrary strides: planning, packing, microkernels, threading inside the callN-ary einsum, ordering, linear algebra, AD
tlinalg, tlinalg-blasbatched factorizations and solves, the batch loop and its lane fan-outtensors, allocation policy, dtype and device dispatch
strided-rsthe strided view types and traits everyone exchanges; permutation and elementwise kernelseverything above

Design principles

tenferro: the host

tenferro-rs is a Rust tensor runtime for scientific code: typed and dynamic dense tensors, einsum, linear algebra, FFT, and automatic differentiation, on CPU, CUDA (through a CubeCL fork) and experimental WebGPU, with an XLA path. tensor4all-rs uses it for every dense operation.

The tenferro documentation has the quickstarts. Version 0.7.1 is on crates.io; the main branch already builds on the unpublished providers described below.

cpueinsum: N-ary einsum in a given order

cpueinsum runs an einsum over integer labels with a contraction order supplied by the caller in SSA form. For each binary step it plans a tcontract call. On top it decides which labels every intermediate must keep (those in the output or needed later; everything else is summed as early as possible) and places all intermediates in one reusable scratch buffer.

It intentionally does not search for contraction orders and does not parse string notation. Those belong to the caller (tenferro, or your own code with an order from opt_einsum, cotengra or a hand-derived factorization).

d[i, l] = Σ a[i, j] b[j, k] c[k, l], contracting (a b) first. From the cpueinsum README:

use cpueinsum::strided_view::{StridedView, StridedViewMut};
use cpueinsum::{EinsumPlan, EinsumSpec, Exec, Layout, Scratch};

// Inputs are operands 0, 1, 2; step 0 makes operand 3.
let spec = EinsumSpec::new(&[&[0, 1], &[1, 2], &[2, 3]], &[0, 3], &[[0, 1], [3, 2]])?;
let cm = Layout::new(&[2, 2], &[1, 2])?;
let plan = EinsumPlan::<f64>::new(&spec, &[cm, cm, cm], cm)?;
let mut scratch = Scratch::new();
// ... build StridedViews over your buffers, then:
plan.execute_into(&Exec::serial(), &views, &mut out, &mut scratch)?;

Threading is explicit: Exec::rayon(&Pool::borrow(&pool)) runs on a Rayon pool you own. For workloads made of many small heterogeneous contractions, BinaryPlan and GroupedPlan prepare jobs once and run a whole group under one scheduler.

cpueinsum links no BLAS. The separate cpueinsum-blas crate can take over binary steps large enough to profit from a vendor GEMM (at least 215 multiply accumulates per GEMM) and leaves smaller steps on tcontract. It depends on cblas-sys only, so the final binary chooses the BLAS provider.

tcontract: binary contraction without transposes

tcontract computes

D = alpha * op(A) * op(B) + beta * op(C)

for strided operands of f32, f64, c32 and c64, with contracted, batch and free indices, repeated indices (diagonals), reductions and conjugation. Operands are never transposed or copied into a canonical layout first. It was previously named tprims-rs and includes kernels contributed from Lukas Devos's tensorprimitives work.

How a contraction runs

  1. Describe the problem. A Problem is built either from a DotGeneral (contracting and batch axes, as in XLA) or from index labels, together with the layouts (dims and strides) of A, B and D.
  2. Plan once. Plan<T>::new validates the problem and chooses a strategy and a kernel. The plan is reusable for every call with the same layouts.
  3. Execute. execute_into takes an execution context (Exec::serial() or a Rayon pool the host owns) and the views. contract_batched runs many independent problems of one shape.
StrategyUsed whenHow
packedgeneral strides and index ordersa TBLIS-style block-scatter driver packs strided operands straight into microkernel panels, with cache blocking
faerthe problem fuses into one batched GEMM without copiescalls faer's matrix multiply directly on the views
elementwiseevery index is a batch indexa single elementwise pass

Microkernel families cover AVX2, AVX-512 and NEON register tiles, native complex kernels, and portable reference kernels; the planner resolves the family for the target and freezes the blocking in the plan.

D = A B with column-major 2 × 2 operands. Abridged from crates/tcontract/examples/readme.rs, which CI compiles and runs:

use strided_view::{StridedView, StridedViewMut};
use tcontract::api::{DType, DotGeneral, LayoutSpec, OperandSpec, Problem};
use tcontract::{Plan, PlanConfig};
use tcontract_exec::Exec;

let layout = |dims: &[usize], strides: &[isize]| {
    Ok(OperandSpec::new(LayoutSpec::new(dims, strides, 0)?))
};
// contract axis 1 of A with axis 0 of B; no batch axes
let dot = DotGeneral::new(&[1], &[0], &[], &[]);
let problem = Problem::from_dot_general(
    DType::F64,
    layout(&[2, 2], &[1, 2])?,
    layout(&[2, 2], &[1, 2])?,
    layout(&[2, 2], &[1, 2])?,
    &dot,
)?;
let plan = Plan::<f64>::new(&problem, &PlanConfig::default())?;
println!("strategy: {:?}", plan.report().algorithm);
plan.execute_into(&Exec::serial(), 1.0, &av, &bv, &mut dv)?;

The C ABI

tcontract builds into one shared library, libtcontract_capi, that implements TAPP, the community standard C interface for tensor products, declared by the pinned upstream headers. A C or C++ program that includes only <tapp.h> can create tensor descriptors, plan and execute a contraction serially. On top of the standard, operands can be passed as DLPack tensors, and a few named extensions expose executors for multithreaded runs. Because the ABI is a standard, a code written against TAPP can switch between implementations without source changes.

tlinalg: batched linear algebra

tlinalg-rs holds the factorizations that tensor algorithms reduce to once a tensor is unfolded into matrices. It is the layer tenferro's CPU linear algebra has been extracted into.

The batched interface

Every entry point is batched in the style of PyTorch: one call takes a strided descriptor of rank 2 + B with dimensions [rows, cols, b...] and processes all B batch items. The library owns the loop over the batch, so it can decide whether to run many small matrices in parallel or give all threads to one large matrix. Outputs are compact and column-major. When items fail, the reported error is deterministic: the failing item with the lowest index.

FamilyIncludes
SVDthin, full, values only
QRQR, column-pivoted QR (rank revealing), compact Householder form
LU and solvespacked LU, factor once and solve, full-pivot LU, triangular solve
Choleskyfactorization and solves
EigensolversHermitian (eigh, eigvalsh) and general (eig, eigvals)

Two sibling providers

tlinalg

Pure Rust, on tensor4all's fork of faer. Takes a Parallel token describing the threads it may use; with the default Auto policy the library decides between fanning out over batch items (for small matrices, up to 64 rows or columns) and letting faer parallelize inside one item.

tlinalg-blas

The same batched signatures on LAPACK and BLAS. The batch loop is serial and the vendor library owns its threading. Scratch comes from a host workspace queried once per call and reused for every item. The vendor is chosen by the final binary.

Neither provider depends on the other, and they share no trait crate. tenferro defines the interface it needs and adapts each provider to it. A parity suite runs every family through both providers for all four element types and several batch shapes (including transposed and broadcast batch strides) and compares the results in a gauge-aware way.

strided-rs: the shared vocabulary

strided-rs defines StridedView, StridedViewMut and the raw descriptors that every CPU layer passes across crate boundaries, plus fast permutation and elementwise kernels. Arbitrary strides are allowed, including negative and zero (broadcast).

Because these types cross crate boundaries, every crate in a build must resolve to the same strided-rs release, currently 0.4.8; two copies would produce two incompatible view types. To make this cheap for downstream users, the plan is to version the core types and traits separately from the permutation engine and stabilize them toward 1.0 (strided-rs#296). A longer term direction is to make strided-rs embeddable from other languages through its own C API (strided-rs#234).

The faer fork

The pure Rust dense kernels come from tensor4all/faer, a fork of faer developed independently and published as the t4a-faer crates. It is where workload specific changes are tried quickly: strided and batched entry points, uninitialized output destinations, caller-owned workspaces, an explicit thread policy, and numerical fixes such as the unpivoted QR fix that tlinalg relies on. Many of these are not in upstream faer. Every change is recorded in PATCHSET.md; the fork keeps upstream's license and attribution, and consumers pin a revision or a published t4a-faer version.

Execution contexts and hataori

Each layer has its own explicit context, and the contexts nest:

Something has to own the processes and threads at the top. That is hataori: map and a dynamically scheduled pmap over serial, Rayon, MPI and hybrid execution, inspired by Distributed.jl. hataori splits the machine into ranks and thread domains and passes each task the pool it may use, which flows down through the contexts above. MPI can be linked at build time or loaded at run time (through rsmpi-rt), so a Rust extension can share the MPI library of a Julia or Python host. The synchronous API is usable now; an asynchronous runtime with futures, remote objects and migration is in development in the same repository.

For quantum chemistry and other host codes

Coupled cluster, integral transformations, density fitting and many-body perturbation theory spend their time in contractions of tensors whose index order rarely matches what GEMM wants. The usual workaround (permute, call GEMM, permute back) costs memory traffic and extra buffers. tcontract is built to avoid exactly that, in the same spirit as TBLIS, and is designed to be called from code that has its own tensor containers.

None of tcontract, cpueinsum and tlinalg is on crates.io yet, and their interfaces may still change. Pin a git revision. If you are evaluating them for a production code, opening an issue with your typical tensor shapes is the most useful thing you can do: it feeds directly into the benchmark campaigns below.

Benchmarks

RepositoryWhat it measures
tenferro-benchmarktenferro against PyTorch and JAX on CPU and GPU, per workload class
tcontract-benchmarkbinary contractions across shapes and strides, keyed by commit and hardware profile
tlinalg-benchmarkbatched factorizations, faer provider against LAPACK, keyed by commit and hardware profile
tensor4all-benchmarktensor network level algorithms: QTT elementwise products, MPO MPO contraction

Status and contributing

ComponentState
tenferro-rscrates.io 0.7.1 main depends on the unpublished providers
cpueinsum, tcontractgit only early, interfaces settling
tlinalg, tlinalg-blasgit only extraction from tenferro in progress
strided-rscrates.io 0.4.8 core versioning split planned
faer forkcrates.io t4a-faer 0.24.4 independent of upstream, no compatibility promise
computegraphcrates.io 0.1.0
hataorigit only synchronous API usable, runtime in development

All repositories accept issues and pull requests, including AI assisted contributions; each one documents its rules in AGENTS.md and its contributing guide. Good first contributions are benchmark cases from real applications, kernels for hardware we have not tuned for, and bindings for other languages.