Skip to main content

Crate candela

Crate candela 

Source
Expand description

A lazy, graph-based tensor engine for the CPU, with f32/f64 element types and a pluggable backend (pure-Rust by default, Intel MKL behind the mkl feature).

Operations on a Tensor don’t compute anything - they build a computation graph and return a TensorPromise. Calling .materialize() plans the whole graph (ordering, buffer reuse, scalar-op fusion) and runs it in one pass, handing back a finished Tensor.

use candela::{arange, Tensor};

// Building the expression allocates nothing; only `.materialize()` runs it.
let x: Tensor<f64> = arange!(4);          // [0, 1, 2, 3]
let y = (x * 2.0 + 1.0).materialize();    // 2x + 1, fused into one pass
assert_eq!(y.data(), &[1.0, 3.0, 5.0, 7.0]);

§The types

  • Tensor - a materialized buffer with a shape and stride.
  • TensorPromise - an unevaluated computation graph; .materialize() runs it.
  • CachedTensorPromise - a promise that keeps its result alive for reuse across separate materializations.
  • Skeleton - a graph compiled once over SkeletonSlot placeholders and run many times against new data, skipping per-call planning. See the skeleton module for the dynamic-shape and caching variants.

§Building a graph

A promise is built up op by op and stays inert until you materialize it. Fallible ops (anything shape-dependent, like matmul) return a Result at construction time, so a graph that could never run is rejected before any compute happens.

use candela::{srange, Dimension, OpError, Tensor};

let a: Tensor<f32> = srange![2 * 3, &[2, 3]];   // 2x3, values 0..6
let b: Tensor<f32> = srange![3 * 2, &[3, 2]];   // 3x2, values 0..6

let c = a.matmul(&b)?.materialize();            // shape mismatch would fail here
assert_eq!(c.shape(), &[2, 2]);

§Reusing intermediate results

.materialize() consumes the graph and frees every intermediate buffer. When you want to keep a mid-graph value alive - to inspect it, or to branch off it more than once - call .cache() to turn the promise into a CachedTensorPromise. It computes at most once and hands the stored result to everyone downstream.

use candela::Tensor;

let a = Tensor::from_scalar(1.0_f64, &[3, 3]);
let b = (a + 2.0).cache();               // will hold onto its result

let peek = b.snapshot();                 // computes b once, fills the cache
assert_eq!(peek.data(), &[3.0; 9]);

let c = (b * 10.0).materialize();        // reuses the cached b, no recompute
assert_eq!(c.data(), &[30.0; 9]);

§Errors

Candela splits failures by how likely they are to be a bug:

  • The inline arithmetic operators (+, -, *, /) panic on a shape mismatch. A mismatch there is almost always a programming error, and making them fallible would force an .unwrap() onto every expression.
  • Everything else that can fail returns Result<_, OpError> - and, because the graph is built eagerly, it fails at construction time rather than deep inside .materialize().

§Feature flags

  • mkl - swap the default pure-Rust backend for Intel MKL. Links against MKL (handled by intel-mkl-src), so the libraries must be available on the build host. See the backends docs.
  • tracing - emit tracing spans through the planner and execution for profiling and debugging.

§Concepts

The docs module shows in detail how the processing pipeline actually works, from expression to computed tensor. Start with the overview for the general behaviour; it links each subsystem from there.

Modules§

arange
backend
docs
Design documentation.
skeleton

Macros§

arange
Build a 1D tensor of evenly spaced values, NumPy-arange style.
branch_duo_fast_iter
branch_fast_iter
ones
Build a tensor of the given shape filled with ones.
s
Build the SliceRange list for slice, one entry per axis.
srange
Build a tensor of evenly spaced values and reshape it in one step.
zeros
Build a tensor of the given shape filled with zeros.

Structs§

CachedTensorPromise
A lazy computation whose result is kept alive after the first evaluation.
InformedIter
Structural walk over a tensor, yielding a StepInfo per element and per dimension boundary.
Iter
Iterator over a tensor’s elements in logical (row-major) order.
Layout
How a tensor’s logical shape maps onto its flat backing buffer.
SliceRange
A per-axis range for slice, one entry per axis.
Tensor
Allocated tensor data exposed through the public API.
TensorPromise
A lazy computation that runs when you call .materialize().

Enums§

OpError
The error returned by fallible tensor operations.
StepInfo
A single event in a structural walk of a tensor, produced by Tensor::informed_iter.

Traits§

Composable
A subset of all tensor types that can be materialized
Dimension
Shape, stride, and layout queries shared by every tensor-like value.
FloatLikeTensorElement
Sealed marker for floating-point tensor element types: f32 and f64.