Expand description
Exact programs as neural nets: a Rust port of the core of handwired.
A program is a fixed list of f32 operations, mostly weighted sums and products with a ReLU, written with builder code that runs once. Because it uses only these primitives, it compiles into layers of sparse matrix multiplications and stays differentiable, while computing exactly what the equivalent ordinary code computes.
use handwired_rs::{emit_compare, Circuit, Relation};
// const result = (a > 4) ? 10 : 12, for an integer a within 0..100.
let mut circuit = Circuit::new(1);
let a = circuit.source(0);
circuit.assume_range(a, 0.0, 100.0);
circuit.assume_grid(a, 1.0);
let four = circuit.constant(4.0);
let above_four = emit_compare(&mut circuit, a, Relation::Greater, four);
let result = circuit.affine(above_four * -2.0 + 12.0);
for value in 0..=100 {
let values = circuit.evaluate_reference(&[value as f32]).unwrap();
assert_eq!(values[result.index()], if value > 4 { 10.0 } else { 12.0 });
}Builder methods panic when a contract is violated, e.g. a comparison on an
operand without a finite declared range, since that is a bug in the
program being built. Evaluation returns Error for bad input data.
Modules§
- dsl
- The runtime half of
#[handwired]. - timing
- Host timing for springs whose step matrix the host writes into source cells,
ported from handwired’s
host-timing.ts.
Structs§
- Animated
Object - Animated
Object Options - Animated
Spring - One spring of an
emit_animated_object. - Backprop
Options - Batch
Executor - Reusable buffers for running one program on batches of records.
- Candidate
Table - Cell
Spec - Circuit
- Compare
Options - Comparison
- A comparison bit’s test:
d > 0,d >= 0ord == 0for the differenced = bias + Σ coefficient × operand. - Compile
Options - Gather
Compute - Resolves a one-hot group’s selected position once per evaluation, at the
first layer that reads the group:
Σ position × bitandΣ bit, exact for 0/1 bits. - Gather
Read - A lowered masked sum: the row’s bias plus the selected candidate, which is first multiplied by the group’s sum of bits when the group allows all zeros.
- Index
Read - Index
Read Row - Lin
- An affine form:
bias + Σ coefficient × wire, with terms in the order they were written. Ordinary builder methods sort and merge terms; the rounding-preserving ones, e.g.Circuit::round_affine, keep this order. - OneHot
Group - Exact bits of which exactly one is 1, or, with
allow_all_zero, at most one. A promise from the caller, like a range, checkable withCircuit::validate_one_hot_groups. Compilation lowers a sum of products of the group’s members with candidate values to a gather: a read of the selected candidate. - Operation
- Range
- The range of values a wire may hold. An open side is infinite.
- Source
Table - Spring
State - A spring’s next state after four explicit semi-implicit Euler steps, with every constant a wire.
- Tensor
Executor - Reusable buffers for running one program many times.
- Tensor
Layer - Tensor
Program - Wire
- The position of a source cell or of an operation’s result.
Enums§
- Boolean
Gradient - Gradient rule at a boolean atom, which is a step function.
- Comparison
Gradient - Gradient rule for the bit an
emit_comparereturns, a step function of its operands. - Error
- Index
Gradient - Gradient rule for the index of an index read, which is a step function.
- Macro
- The builder operation a wire came from, so the backward pass can differentiate the whole product or select instead of its ReLUs one by one.
- OpKind
- OutOf
Range - What an index read returns when floor(index) is outside the table.
- Policy
- What happens to a cell around an evaluation.
- Relation
- Rounding
Point Kind - Why an operation’s FP32 evaluation, term order or operand wires carry meaning. See handwired’s docs/execution-model.md, “Rounding points”.
Constants§
- LANES
- Records evaluated together.
Functions§
- brent_
kung_ prefix - Brent-Kung (1982): an upward pass combines neighbors, then pairs of pairs, and so on, which completes the prefixes of the first 2, 4, 8, … items. A downward pass then completes every other prefix by combining one completed prefix with one smaller block. For k items, a power of two and at least 4, that’s 2k - 2 - log2(k) combines, and a longest chain of 2 × log2(k) - 2.
- constant
- emit_
and bits.all():relu(sum - (n - 1)), exact for exact bits.- emit_
animated_ object - Springs of one animated object, e.g. a window’s x, y, width, height and scale, stepped by their matrices and settled together with one exact bit. Each spring stores its displacement from its destination rather than a position, so FP32 rounds the state at the size of the displacement, finely near rest even far from the origin. When the object settles or snaps, every displacement and velocity becomes 0, up to the sign of zero.
- emit_
apply_ spring_ matrix - One spring’s displacement from its destination and its velocity after every step the matrix describes, e.g.
all of one evaluation’s 4 ms steps at once. The matrix cells need finite declared ranges, e.g.
±
spring_matrix_bound, so a host that writes an overflowing matrix fails the source range check. Each product goes throughmultiply, so the backward pass differentiates whole products. Cost: 14 operations in 3 layers. - emit_
argmax - The highest of
scores, each declared within 0..1, as a one-hot selection and the winning score.unitmust be declared exactly 1. Up a balanced tree, each pair compares its scores with a boolean atom and keeps the larger one; down, the unit follows the comparison bits, so exactly one output is 1 and the selection is declared as a one-hot group. Exact ties keep the left score, and so does a right score larger by less than about 9e-8 (for some scores up to 2.4e-7), a band that isn’t transitive, so the tree can pick differently from a loop. Needs an executor that keeps FP32 rounding at the atoms. O(n) operations in O(log n) layers. - emit_
clamp_ exact min(max(value, minimum), maximum), as exactly one of the operands.- emit_
compare [a relation b]as an exact bit, equal to the native f32 comparison for every pair of values within the declared ranges. Both operands need finite declared ranges. When both lie on grids, the bit is built from the integerD = (a - b) / gridin 2 or 3 operations; otherwise a sign cascade lifts every nonzerof(a - b), down to 2^-149, to at least 1.- emit_
compare_ with - emit_
first_ active - The first of
candidatesthat is 1, gated byactive, as a selection: up a balanced tree, each pair keeps theemit_maximumof its masses; down, a node’s routergivesr × left maximumto its left child and the rest to its right child, starting fromactive × root maximum. An exact one-hot, or all zeros, when the gate and every candidate are exact bits; fractional candidates split the route, e.g. [0.5, 0] sends 0.25 to each output. The selection is declared as a one-hot group (allowing all zeros) only when the gate and every candidate are proven bits (seeis_proven_bit). O(n) operations in O(log n) layers. - emit_
maximum max(a, b)rebuilt through a rounded difference:b + relu(a - b). Not always exactly one of the operands; seeemit_maximum_exact.- emit_
maximum_ exact max(a, b)as exactly one of the operands. Ties returna.- emit_
minimum min(a, b)rebuilt through a rounded difference:a - relu(a - b), e.g. 0.29998779 for min(f(1000.37), f(0.3)). Seeemit_minimum_exact.- emit_
minimum_ exact min(a, b)as exactly one of the operands, e.g. f(0.3) for min(f(1000.37), f(0.3)). Ties returna. Needs finite declared ranges, likeemit_compare.- emit_
not !bitfor an exact bit.- emit_or
bits.any():1 - relu(1 - sum), exact for exact bits.- emit_
relu max(0, value).- emit_
round Math.round(x)for |x| below 2^22: FP32 values within 2^23..2^24 are whole numbers, so adding and then subtracting 2^23 + 2^22 rounds (ties to even). Both steps are marked rounding points.- emit_
select gate ? on : offfor a gate of exactly 0 or 1. Both sides always compute.- emit_
spring - Four steps of
velocity += (-stiffness × (position - destination) - damping × velocity) × timestep; position += velocity × timestep, then a soft settle:activeisclamp01(relu(|position - destination| - settle_distance) × distance_scale + relu(|velocity| - settle_speed) × speed_scale), the position moves to the destination asactivegoes to 0, and the velocity scales by it.unitmust hold 1.emit_animated_objectis the exact, matrix-stepped form. - event
- input
- input_
bit - is_
proven_ bit - Whether a wire provably holds exactly 0 or 1: its declared range is a single 0 or 1, a boolean atom made it,
emit_comparereturned it, it belongs to a declared one-hot group, or it’s an index read whose reachable candidates are all proven bits (a side that reads zero adds +0, also a bit). A 0..1 range alone isn’t enough. - sklansky_
prefix - Sklansky (1960): for blocks of 2, 4, 8, … items, combine the last prefix of the block’s left half into every prefix of its right half. For k items, a power of two, that’s (k / 2) × log2(k) combines, and a longest chain of log2(k).
- state
- state_
bit
Type Aliases§
- Spring
Matrix Wires - The 4 cells of a spring matrix that the host writes before each evaluation, e.g.
spring_matrix_power(spring_step_matrix(constants, 4.0), steps)fromcrate::timing:displacement' = m[0] × displacement + m[1] × velocity,velocity' = m[2] × displacement + m[3] × velocity. - Term
- One term of an affine form: a wire and its coefficient. Coefficients are kept in f64 like handwired’s, and rounded to f32 when evaluated.