An autodiff compiler stack in Rust. Record a graph, inspect it, differentiate it, compile it, emit it. The spec is an immutable value and the state is yours. The network never moves; a training step is a pure data transform of caller-owned parameters.
The interpreter is the executable spec; everything faster must match it, bit for bit by default. The stack stays small enough to read. Every result is provable.
A typical compiler rewrites the program until it can run. Topos
does not. The tape is written once; everything after it is a named
way of reading the same spec. Fusion is an offer, not a rewrite.
Emission writes the plan as text for an industrial backend — a
sibling of describe, not a second compiler.
The design is in docs/vision.md. Constraints
it assumes live in docs/principles/.
use topos::{Detach, Tape, Tensor};
let (network, [w, x, y, loss]) = Tape::record(|tape| {
let w = tape.parameter(0.0_f64);
let x = tape.input(0.0);
let y = tape.input(0.0);
let error = w * x - y;
[w, x, y, error * error].detach()
});
let mut parameters = network.parameters();
let samples = [(1.0, 2.0), (2.0, 4.0), (3.0, 6.0)];
for step in 0..100 {
let (sample_x, sample_y) = samples[step % samples.len()];
let run = network.forward(
¶meters,
[(x, sample_x.into()), (y, sample_y.into())],
);
let gradients = run.backward(loss).parameters(¶meters);
parameters = parameters.step(&gradients, |w, g| {
w.clone() - g.clone() * Tensor::from(0.02)
});
}
assert!((parameters.of(w).scalar() - 2.0).abs() < 1e-6);The graph is recorded once. Every step feeds a sample and steps the parameters. Training never touches the network.
use topos::{Detach, Tape};
let (network, [loss]) = Tape::record(|tape| {
let w = tape.parameter(1.0_f64);
[w * w].detach()
});
println!("{}", network.describe());
let plan = network.entry([loss]).lower();
println!("{}", plan.emit_stablehlo().expect("every operation lowers"));Two rustdoc maps, nothing moves:
topos::model
to train,
topos::compiler
to inspect and emit.
cargo add toposWrite networks, losses, optimizers, and element types against the public surface. A hand-rolled layer behaves identically to a facade; a custom optimizer has the same standing as Adam. The opcode set, fusions, and backends stay in the crate — the core is closed on purpose.
Opt-in backends (accelerate, simd, metal, cuda) are
documented in docs/acceleration.md.
Vocabulary: docs/terminology.md.
Notebooks: docs/notebooks.md. API:
docs.rs/topos.
examples/ runs from a scalar chain to a transformer:
gradient_descent— one spec, many statesmakemore/— Karpathy's classroom, through facades, compiled plans, and StableHLOmnist/andcifar10/gpt2/— 124M, recorded from the public op surfacellama/
A topos is a place — here, one where the whole compiler stack stays in view.
Licensed under either of MIT or Apache-2.0, at your option.