candle_einops/lib.rs
1//! Compile-time einops-style tensor transformations for Candle.
2//!
3//! The [`einops!`] macro combines rearrange, reduce, repeat, composition, and
4//! decomposition operations. [`einsum!`] provides explicit-output,
5//! arbitrary-arity Einstein summation. Backend failures are returned as Candle
6//! errors.
7//!
8//! Einsum equations require exactly one `->`, use whitespace-delimited named
9//! axes, and have one comma-separated input list per operand. Axes omitted from
10//! the output are summed, `..` captures right-aligned runtime axes, and repeated
11//! labels select diagonals. See the repository's `docs/einsum-contract.md` for
12//! the complete supported contract.
13//!
14//! ```
15//! use candle_core::{Device, Result, Tensor};
16//! use candle_einops::einops;
17//!
18//! # fn main() -> Result<()> {
19//! let input = Tensor::arange(0f32, 6f32, &Device::Cpu)?.reshape((2, 3))?;
20//! let output = einops!("rows columns -> columns rows", &input)?;
21//! assert_eq!(output.dims(), &[3, 2]);
22//! # Ok(())
23//! # }
24//! ```
25//!
26//! A `..` captures zero or more axes. Captures from multiple operands align
27//! from the right and broadcast, while omitting `..` from the output reduces
28//! those axes:
29//!
30//! ```
31//! use candle_core::{Device, Result, Tensor};
32//! use candle_einops::einsum;
33//!
34//! # fn main() -> Result<()> {
35//! let input = Tensor::arange(0f32, 12f32, &Device::Cpu)?.reshape((2, 2, 3))?;
36//! let reduced = einsum!(".. feature -> feature", &input)?;
37//! assert_eq!(reduced.to_vec1::<f32>()?, [18., 22., 26.]);
38//! # Ok(())
39//! # }
40//! ```
41//!
42//! Contractions lower through Candle matrix multiplication, including batch
43//! broadcasting. Equations with more than two operands use deterministic,
44//! shape-aware greedy pair selection:
45//!
46//! ```
47//! use candle_core::{Device, Result, Tensor};
48//! use candle_einops::einsum;
49//!
50//! # fn main() -> Result<()> {
51//! let left = Tensor::new(&[[1f32, 2., 3.], [4., 5., 6.]], &Device::Cpu)?;
52//! let right = Tensor::new(&[[1f32, 2.], [3., 4.], [5., 6.]], &Device::Cpu)?;
53//! let output = einsum!("row inner, inner column -> row column", &left, &right)?;
54//! assert_eq!(output.to_vec2::<f32>()?, [[22., 28.], [49., 64.]]);
55//! let weights = Tensor::new(&[1f32, 1.], &Device::Cpu)?;
56//! let projected = einsum!(
57//! "row inner, inner column, column -> row",
58//! &left,
59//! &right,
60//! &weights,
61//! )?;
62//! assert_eq!(projected.to_vec1::<f32>()?, [50., 113.]);
63//! # Ok(())
64//! # }
65//! ```
66//!
67//! Repeating a label within one operand extracts its diagonal. Omitting that
68//! label from the output computes a trace:
69//!
70//! ```
71//! use candle_core::{Device, Result, Tensor};
72//! use candle_einops::einsum;
73//!
74//! # fn main() -> Result<()> {
75//! let matrix = Tensor::arange(0f32, 9f32, &Device::Cpu)?.reshape((3, 3))?;
76//! let diagonal = einsum!("index index -> index", &matrix)?;
77//! assert_eq!(diagonal.to_vec1::<f32>()?, [0., 4., 8.]);
78//! let trace = einsum!("index index ->", &matrix)?;
79//! assert_eq!(trace.to_scalar::<f32>()?, 12.);
80//! # Ok(())
81//! # }
82//! ```
83//!
84//! ## Dtypes, devices, and gradients
85//!
86//! `einsum!` never casts operands or transfers them between devices. Every
87//! operand in a multi-operand equation must have the same dtype and reside on
88//! the same device; mismatches return a contextual [`candle_core::Error`].
89//! Unary permutations preserve every dtype supported by the corresponding
90//! Candle operation. Binary equations without contracted labels use Candle
91//! multiplication, including its integer and BF16 support. True contractions
92//! lower through Candle matrix multiplication and therefore inherit its dtype
93//! and device support: unsupported combinations return an error rather than
94//! being silently converted.
95//!
96//! Einsum execution is assembled from tracked public Candle operations, so
97//! floating-point inputs participate in Candle autograd. Accelerator execution
98//! likewise follows the features and devices made available by Candle; results
99//! remain on the operands' original device.
100//!
101//! Unary einsum equations use whitespace-delimited named axes. Axes omitted
102//! from the explicit output are summed:
103//!
104//! ```
105//! use candle_core::{Device, Result, Tensor};
106//! use candle_einops::einsum;
107//!
108//! # fn main() -> Result<()> {
109//! let input = Tensor::arange(0f32, 6f32, &Device::Cpu)?.reshape((2, 3))?;
110//! let columns = einsum!("rows columns -> columns", &input)?;
111//! assert_eq!(columns.to_vec1::<f32>()?, [3., 5., 7.]);
112//! # Ok(())
113//! # }
114//! ```
115
116extern crate self as candle_einops;
117
118mod backend;
119mod einsum;
120
121/// The result type returned by [`einops!`] and [`Backend`] transformations.
122pub use candle_core::Result;
123pub use candle_einops_macros::{einops, einsum};
124
125pub use backend::Backend;
126pub use einsum::PreparedDiagonalPlan;
127
128/// Implementation details used by macros generated for this crate.
129///
130/// This module is not a stable public API.
131#[doc(hidden)]
132pub mod __private {
133 #[cfg(feature = "benchmark-internals")]
134 pub use crate::einsum::{
135 BenchmarkBinaryGraphEstimate, benchmark_binary_graph_estimate,
136 benchmark_nary_planner_selects_exact, benchmark_pack_canonical_operand,
137 };
138 pub use crate::einsum::{
139 BinaryEinsumSpec, EinsumAxisPattern, EllipsisEinsumSpec, UnaryEinsumSpec,
140 einsum_operand_ref, execute_binary_einsum, execute_binary_ellipsis_einsum,
141 execute_binary_multiply, execute_canonical_binary_einsum, execute_nary_einsum,
142 execute_unary_einsum, execute_unary_ellipsis_einsum,
143 };
144}
145
146/// Specifies the operation used to reduce an axis
147#[derive(Copy, Clone, Debug)]
148pub enum Operation {
149 /// Take the minimum value
150 Min,
151 /// Take the maximum value
152 Max,
153 /// Sum all elements
154 Sum,
155 /// Get the mean value
156 Mean,
157 /// Multiply all elements
158 Prod,
159}