1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
// RLX — versatile ML compiler + runtime.
// Copyright (C) 2026 Eugene Hauptmann, Nataliya Kosmyna.
// SPDX-License-Identifier: MIT OR Apache-2.0
//! # rlx-tensor
//!
//! Symbolic tensor API for RLX — expression syntax with compiler-backed
//! execution. Compared to NumPy / ndarray / PyTorch / JAX:
//!
//! | | ndarray / NumPy | PyTorch | JAX | **rlx-tensor** |
//! |-|-----------------|---------|-----|----------------|
//! | Default execution | eager, host | eager, GPU | trace + XLA | **trace → fuse → backend** |
//! | Intermediate buffers | always materialized | often kept for autograd | XLA decides | **fusion + memory plan** |
//! | Device | host only | CUDA-centric | TPU/GPU | **CPU / Metal / CUDA / ROCm / wgpu / MLX / TPU** |
//! | Variable batch/seq | manual padding | manual | `jax.vmap` / shapes | **first-class `Dim::Dynamic`** |
//! | Slicing | views (cheap) | views | slice ops | **`s![]` → narrow (fusable)** |
//!
//! Symbolic [`Tensor`] handles carry **zero payload bytes** until you compile
//! and run — but with the `eval` feature you can also build from host data
//! NumPy-style and read results straight back, the graph staying lazy (so the
//! compiler fuses + memory-plans the whole expression) until you materialize:
//!
//! ```ignore
//! use rlx_tensor::Tensor; // requires feature = "eval"
//!
//! let a = Tensor::from_vec(vec![1.0, 2.0, 3.0], [3]);
//! let b = Tensor::ones([3]);
//! let c = (&a + &b).relu(); // graphs auto-merge, nothing runs yet
//! assert_eq!(c.to_vec(), vec![2.0, 3.0, 4.0]); // auto-selects fastest backend
//! // c.on(Device::Metal).to_vec() // or pin an explicit backend
//! ```
//!
//! Reverse-mode autodiff (feature `grad`) returns gradient tensors:
//!
//! ```ignore
//! let loss = (&a * &b).sum([0], false);
//! let g = loss.grad(&[&a, &b]); // [∂loss/∂a, ∂loss/∂b]
//! assert_eq!(g[0].to_vec(), b.to_vec());
//! ```
//!
//! JAX-shaped composable transforms (feature `transforms`) operate on [`Func`]
//! (a traced function of named inputs) and chain — `vmap(grad(f))`:
//!
//! ```ignore
//! let f = Func::new("f", |s| { let x = s.input("x", shape![3]); (&x * &x).sum([0], false) });
//! let batched_grad = f.grad(&["x"]).vmap(&["x"], 4); // Func -> Func -> Func
//! let compiled = batched_grad.jit(); // compile once
//! let out = compiled.run(&[("x", &xs)]); // run many, no recompile
//! ```
//!
//! Materialization is compile-cached per thread (keyed by graph + constants +
//! device), so even plain `to_vec` / `run` reuse the compiled artifact.
//!
//! ```rust
//! use rlx_tensor::{ax, graph, rg, s, shape};
//!
//! // static shape
//! let g = graph("mlp", |g| {
//! let x = g.input("x", shape![2, 4]);
//! let w = g.param("w", shape![4, 3]);
//! (&x.matmul(&w)).gelu()
//! });
//!
//! // dynamic batch + window slice (no copy in IR)
//! let g2 = graph("batched", |g| {
//! let x = g.input("x", shape![?, 128]);
//! x.slice(s![ax(), rg(0, 64)])
//! });
//! # let _ = (g, g2);
//! ```
//!
//! Or, with the `dsl` feature, declare the whole graph in a compact little
//! language with the `rlx!` macro — `@` is matmul, shapes and outputs are
//! inferred.
/// Generic low-precision numerics — the `float_format!` macro + `fXmYeZ`
/// quantizer for emulated any-precision (nvf4 / f8 / bf8 / …) training.
pub use ;
pub use ;
pub use ;
/// Optimizers for [`Func::train_step`] (re-exported from `rlx_optim`).
/// Available with the `optim` feature.
pub use ;
pub use LrSchedule;
pub use ;
pub use ;
pub use Tensor;
pub use Func;
pub use Jitted;
pub use ;
/// A compiled, runnable model with by-name weight binding. Available with the
/// `eval` feature.
pub use Model;
/// Device selector for [`Tensor::on`] / [`Tensor::to_vec_on`] (re-exported
/// from `rlx_runtime`). Available with the `eval` feature.
pub use Device;
/// Backend detection (re-exported from `rlx_runtime`): which devices this build
/// can use, and the fastest one. `to_vec` / `Func::run` pick automatically.
pub use ;
pub use Materialize;
/// Build a [`Shape`]. Default dtype is `F32`.
///
/// Each static dimension may be **any `usize` expression** — a literal, an
/// identifier, or arithmetic like `w / 2`, `w + heads`, `cfg.width * 2` — so graph
/// builders can size params/inputs from computed dimensions directly (no need to
/// hoist every derived size into a `let`). Dynamic dimensions use `?` → symbol 0
/// (repeat `?` for the same unknown size), `?1` → symbol 1, etc. A leading
/// `Dtype;` sets the element type (default `F32`).
///
/// ```rust
/// use rlx_tensor::{shape, DType};
///
/// let a = shape![2, 4];
/// let b = shape![F32; ?, 128];
/// let w = 64usize;
/// let c = shape![w + 1, w / 2, 3 * w]; // computed dimensions
/// assert_eq!(c.dtype(), DType::F32);
/// assert!(c.is_static());
/// assert!(!b.is_static());
/// ```
/// Token-tree muncher backing [`shape!`]: accumulates `Dim`s left to right,
/// dispatching `?`/`?N` to dynamic dims and everything else to a `usize` expression.
/// (A `tt`-list can't mix the `?` syntax with multi-token expressions, so we munch.)
pub use ;
/// Declare a computation [`Graph`] in a compact, readable little language.
///
/// `rlx! { … }` is a thin, versatile front-end over the shape-inferring graph
/// builder: you write the *math*, and shapes, node wiring, and outputs are
/// filled in for you. It expands to ordinary [`GraphScope`] / [`Tensor`]
/// calls (zero runtime cost) and evaluates to an [`rlx_ir::Graph`].
///
/// # Statements
/// | form | meaning |
/// |------|---------|
/// | `graph "name";` | *(optional, first)* names the graph |
/// | `input x: [dims];` | a graph input; the binding is auto-named `"x"` |
/// | `param w: [dims];` | a trainable parameter, auto-named `"w"` |
/// | `param w[N]: [dims];` | a parameter *family* `w_0 … w_{N-1}`, indexed `w[i]` |
/// | `param w @ "ir.name": [dims];` | override the IR/`set_param` name (`{i}` = family index) |
/// | `const eps = 1e-6 : F32;` | a broadcastable scalar constant |
/// | `const m = [[1,0],[0,1]] : F32;` | an N-D constant from a nested literal |
/// | `bind t;` / `bind a, b;` | adopt outer-scope Rust `Tensor`(s) of the same name (a baked constant / codebook param) as in-scope bindings |
/// | `let h = …;` | bind an intermediate to an expression |
/// | `fn blk(x, w) { … }` | a reusable subgraph, inlined at each call site (call positionally or named: `blk(w: b, x: a)`) |
/// | `repeat N { … }` | unroll the body `N` times at macro time (weight-tied stacks) |
/// | `repeat n { … }` | *runtime* count `n` → a `for _ in 0..n` loop; config-driven depth, rebound bindings are loop-carried |
/// | `repeat i in 0..N { … }` | unroll with index `i` (drives `w[i]`, distinct-weight stacks) |
/// | `scan h = h0 for N { … }` | compact `Op::Scan` loop (one body graph, not unrolled) |
/// | `tap a, b;` | expose intermediates as extra outputs (debugging) |
/// | `out y;` / `out a, b;` | mark graph outputs (defaults to the last `let`) |
///
/// `[dims]` uses the [`shape!`] grammar — literals, **runtime `usize`
/// expressions** (`[bt, d]`, `[d, ff]` size a graph from config values), `?`
/// for a dynamic axis, and an optional `DType;` prefix (`[F32; ?, 128]`).
///
/// # Expressions
/// * `a @ b` — matrix multiply (also `matmul(a, b)` / `mm(a, b)`)
/// * `+ - * / %` `**` — elementwise, with broadcasting and scalar promotion
/// (`x * 2.0`, `x ** 2`); `**` is power, `%` is `fmod`
/// * `== != < <= > >=` — elementwise comparison → a `Bool` mask (`x > 0.0`)
/// * `f(x)` — any no-extra-arg [`Tensor`] method: `gelu(x)`, `sqrt(x)`, …
/// * `maximum(a,b)` `minimum(a,b)` `pow(a,b)` `atan2(a,b)` `rem(a,b)` — binary
/// tensor ops (a scalar operand is promoted); `clamp(x, lo, hi)`;
/// `select(cond, a, b)` (mask select, aka `where_`)
/// * `linear(x, w, b)` — HF-style `x·Wᵀ + b` (W is `[out,in]`) as one fused
/// matmul+bias op; a wrapping activation folds in (`gelu(linear(x,w,b))`).
/// `matmul_t(a, b)` = `a·bᵀ`
/// * a user `fn`, called `blk(a, w)`, inlines its body here
/// * `x.method(args)` — the escape hatch: **any** `Tensor` method. A bare
/// argument naming a binding is validated and auto-borrowed, so
/// `q.attention(k, v, 8, 64, MaskKind::Causal)` reads naturally (and a typo'd
/// name is a clear DSL error). Other args are raw Rust; wrap an external
/// value as `(value)`, or prefix it with `~` (`~eps`, `~num_heads`), to pass
/// it BY VALUE — the escape a scalar identifier/const needs to reach an
/// `f32`/`usize`/enum parameter (`h.layer_norm(g, b, ~eps)`,
/// `q.attention(k, v, ~nh, ~dh, MaskKind::Causal)`) instead of `&f32`.
///
/// A definite static matmul shape mismatch (both operands statically 2-D with
/// incompatible inner dims) is reported at macro-expansion time.
///
/// Precedence follows NumPy: `.method(…)` > unary `-` > `**` > (`@` `*` `/` `%`)
/// > (`+` `-`) > (`== != < <= > >=`), left-associative except right-associative
/// `**` — so `x @ w * s` is `(x @ w) * s`.
///
/// # Example
/// ```rust
/// use rlx_tensor::rlx;
///
/// let g = rlx! {
/// graph "mlp";
/// input x: [?, 784];
/// param w1: [784, 256]; param b1: [256];
/// param w2: [256, 10]; param b2: [10];
///
/// let h = gelu(x @ w1 + b1);
/// let y = h @ w2 + b2;
/// out y;
/// };
///
/// assert_eq!(g.name, "mlp");
/// assert_eq!(g.outputs.len(), 1);
/// ```
///
/// The macro works identically whether imported from this crate
/// (`rlx_tensor::rlx!`) or the umbrella (`rlx::rlx!`) — the wrapper resolves
/// every emitted path through `$crate`.
///
/// Requires the `dsl` feature (on for umbrella `rlx` users via `tensor`).
/// Evaluate a single `rlx!`-grammar expression over in-scope Rust [`Tensor`]
/// values — the "Rust bridge". Every identifier is a normal Rust variable, so
/// ordinary `for` loops and config drive the graph structure while the DSL
/// expresses the per-step math (e.g. `h = rlx_expr!(gelu(linear(h, w, b)))`).
/// This is what a macro-expanded `rlx! { … }` can't do: runtime-length loops.
/// Requires the `dsl` feature.