use topos::{Numerics, Tape, Tensor};
fn main() {
let tape: Tape<f32> = Tape::new();
let weights = tape.parameter(Tensor::new([2, 2], [0.5_f32, -0.25, 0.25, 0.4]));
let input = tape.input(Tensor::new([1, 2], [1.0_f32, 2.0]));
let hidden = input.matmul(weights).tanh();
let loss = (hidden * hidden).sum();
banner("1. the spec");
println!("One line per recorded operation: its number, its opcode, the numbers of");
println!("the operands it reads, and its shape. Shapes were checked as each line");
println!("was written; a mismatch panics at the expression that records it.\n");
print!("{}", tape.describe());
let spec_nodes = tape.len();
let (weights, hidden, loss) = (weights.symbol(), hidden.symbol(), loss.symbol());
banner("2. the derivative, as more spec");
let adjoints = tape.differentiate(loss, [weights]);
let gradient = adjoints.of(weights);
println!(
"`differentiate` appended {} nodes to the same tape; nothing above them changed.",
tape.len() - spec_nodes
);
println!("The last one is d loss / d weights.\n");
for line in tape.describe().lines().skip(spec_nodes) {
println!("{line}");
}
println!("\nHow to read them:");
println!(" - a Leaf holding one is the seed, spread over the shape the sum reduced");
println!(" - `hidden * hidden` gets the product rule once per operand");
println!(" - `1 - hidden * hidden` is the derivative of tanh");
println!(" - the matmul rule pushes the result back as two transposed products,");
println!(" one per operand");
println!(" - each Leaf-then-Add pair starts a gradient at zero and adds one");
println!(" consumer's share; that is how a value used twice collects both");
let network = tape.into_network();
let parameters = network.parameters();
banner("3. the plan");
let declare = || network.entry(adjoints.roots()).observe([hidden]);
let plan = declare().lower();
println!("The same operations, scheduled. Each line also says what happens to the");
println!("value once computed: kept for reading, or freed after its last use. The");
println!("last line is the plan's own memory accounting: the most elements alive at");
println!("once, against the total if nothing were ever freed.\n");
print!("{}", plan.describe());
let scheduled: Vec<usize> = plan.nodes().map(|node| node.symbol().index()).collect();
let skipped: Vec<String> = network
.nodes()
.filter(|node| !scheduled.contains(&node.symbol().index()))
.map(|node| node.symbol().index().to_string())
.collect();
if skipped.is_empty() {
println!("\nEvery node is scheduled.");
} else {
println!(
"\nNot scheduled: nodes {}. The rules also produced the gradient with",
skipped.join(", ")
);
println!("respect to the input; nobody declared it, so it is never computed.");
}
let patterns = plan.patterns();
if patterns.is_empty() {
println!("Elected patterns: none. Fusion is an offer the catalog makes for shapes it");
println!("recognizes (windows, batch-norm); a dense layer this small runs as primitives.");
} else {
println!("Elected patterns:");
for pattern in &patterns {
println!(
" {:?} rooted at node {}",
pattern.kind(),
pattern.root().index()
);
}
}
banner("4. the plan as StableHLO");
let module = plan
.emit_stablehlo()
.expect("every operation in this graph lowers");
print!("{module}");
println!("\nThe function takes the parameter and the input, in recording order, and");
println!("returns exactly what the entry declared, in that order: the loss, the");
println!("gradient, then `hidden`.");
assert_eq!(plan.results(), &[loss, gradient, hidden][..]);
assert!(module.contains("stablehlo.dot_general"));
assert!(module.contains("stablehlo.tanh"));
banner("5. two runs that must agree");
let oracle = network.forward(¶meters, []);
let engine_gradient = oracle.backward(loss).parameters(¶meters);
let exact = declare().numerics(Numerics::Exact).lower();
let exact_run = exact.forward(¶meters, []);
let fast_run = plan.forward(¶meters, []);
let recorded_gradient = exact_run.recorded_gradients(&adjoints);
println!("loss:");
println!(" interpreter {}", oracle.of(loss));
println!(" exact plan {}", exact_run.of(loss));
println!(" fast plan {}", fast_run.of(loss));
println!("d loss / d weights:");
println!(" engine scan {}", engine_gradient.of(weights));
println!(" recorded gradient {}", recorded_gradient.of(weights));
println!("hidden, declared readable: {}", exact_run.of(hidden));
assert!(bits_equal(oracle.of(loss), exact_run.of(loss)));
assert!(bits_equal(
engine_gradient.of(weights),
recorded_gradient.of(weights)
));
println!("\nexact plan == interpreter, bit for bit: asserted");
println!("recorded gradient == engine scan, bit for bit: asserted");
if bits_equal(fast_run.of(loss), exact_run.of(loss)) {
println!(
"fast plan == exact plan: yes; no backend claimed any of this graph in this build"
);
} else {
println!("fast plan == exact plan: no; a backend served part of this graph under Fast,");
println!("and Exact is how you ask for the reference bits back");
}
let learning_rate = Tensor::from(0.05_f32);
let mut current = parameters.clone();
let mut losses = Vec::new();
for _ in 0..5 {
let run = plan.forward(¤t, []);
losses.push(run.of(loss).scalar());
let gradients = run.recorded_gradients(&adjoints);
current = current.step(&gradients, |weight, gradient| {
weight.clone() - gradient.clone() * learning_rate.broadcast_like(gradient)
});
}
losses.push(plan.forward(¤t, []).of(loss).scalar());
let trajectory: Vec<String> = losses.iter().map(|value| format!("{value:.4}")).collect();
println!("\nloss before training, then after each of five steps of size 0.05:");
println!(" {}", trajectory.join(", "));
assert!(losses[losses.len() - 1] < losses[0]);
banner("6. the spec is executable");
let values = Numerics::exactly(|| {
let mut values: Vec<Tensor<f32>> = Vec::new();
for node in network.nodes() {
let value = if node.is_source() {
network
.payload(node.symbol())
.expect("sources hold payloads")
.clone()
} else {
let operands: Vec<&Tensor<f32>> = node
.operands()
.iter()
.map(|symbol| &values[symbol.index()])
.collect();
node.opcode().express(&operands)
};
values.push(value);
}
values
});
assert!(bits_equal(oracle.of(loss), &values[loss.index()]));
println!(
"Walking all {} lines by hand, outside the crate, reproduces the interpreter's",
values.len()
);
println!("loss {} bit for bit.", values[loss.index()]);
}
fn banner(title: &str) {
println!("\n== {title} ==\n");
}
fn bits_equal(left: &Tensor<f32>, right: &Tensor<f32>) -> bool {
left.shape() == right.shape()
&& left
.to_vec()
.iter()
.zip(right.to_vec())
.all(|(left, right)| left.to_bits() == right.to_bits())
}