Regression gates
The frozen reference for the hardware-agnostic pivot. Every number here was
measured on the pre-pivot tree at commit c3351d1 (v0 .trit, scalar kernel,
single-threaded) before any pivot code landed.
Rule: nothing in the pivot may change a number in this file without an explicit, argued entry in the "Sanctioned changes" section below.
Environment of record
| Host | AMD Ryzen 9 8945HX (Zen 4), 32 threads |
| ISA | avx2 avx512f avx512bw avx512dq avx512vl avx512_vnni avx512_vpopcntdq avx512_bf16 gfni |
| rustc | 1.95.0 (59807616e 2026-04-14) |
| Verilator | 5.020 |
| Yosys | 0.33 |
| Model | models/bitnet-2b4t.trit, 1,836,137,811 bytes, .trit v0 |
Other hosts will differ on timing and RSS. The correctness gates are host-independent and must hold everywhere.
G1 — Unit and integration tests
cargo test --workspace
All pass. trit-core 12, tritsim lib 19, tiny_model.rs 6.
G2 — Real-checkpoint logit agreement (the headline gate)
tritsim compare --model models/bitnet-2b4t.trit --dump logits.json
6 positions: mean cosine 0.9991, top1 match 100.0%
Last position, top-3 both sides:
ours [(12366, 18.34601), (539, 14.126393), (264, 13.815544)]
theirs [(12366, 18.5), (539, 14.25), (264, 13.75)]
Single-position variant (--dump bos_dump.json): 1 positions: mean cosine 0.9977, top1 match 100.0% — identical under TRITSIM_INT_MLP=1.
CLI acceptance thresholds (crates/tritsim/src/main.rs): cosine >= 0.98,
top-1 >= 0.90. The measured values sit far above them; the gate is the
measured value, not the threshold.
G3 — Greedy text, byte-exact
tritsim run --model models/bitnet-2b4t.trit \
--tokenizer models/bitnet-2b4t/tokenizer.json \
--prompt "The capital of France is" --steps 16
Paris. Paris is a city in the north of France, and it is the
Must reproduce byte-for-byte in Reference, Folded and IntMlp modes, and
on every backend (cpu, rtl) and every CPU kernel.
G4 — RTL bit-exactness
make -C rtl lint # clean
make -C rtl test # all sets, exact i32 equality
SET vectors/exact_64x64: 64 rows OK
SET vectors/padded_5x100: 5 rows OK
SET vectors/extremes_4x128: 4 rows OK
SET vectors/zeros_3x64: 3 rows OK
SET vectors/wide_2x6912: 2 rows OK
SET vectors/model_k_proj_l0: 8 rows OK
SET <invalid-code>: err raised OK
extremes_4x128 feeds x = -128 deliberately. Any CPU kernel that negates in
the i8 domain fails here. This set must never be weakened.
G5 — Multiplier-free synthesis
make -C rtl synth
Yosys asserts zero $mul and zero $macc cells. Baseline cell count 33,659
(MAX_COLS chparam'd to 512; $_DFFE_PP_ 4096 is the flattened activation
memory). Generic synthesis, no device target.
G6 — Hardware-in-the-loop parity
cargo test -p tritsim --features rtl --release
random_shapes_match_cpu_exactly compares the Verilated core against the CPU
kernel on 8 shapes (1x64, 3x64, 2x100, 5x129, 7x640, 2x2560, 2x6912, 16x61) and
requires exact equality. All pass.
G7 — Resource baseline (the numbers the pivot exists to move)
| Metric | Pre-pivot | Why |
|---|---|---|
| Peak RSS, real model | 6,401,024 KB (6.10 GiB) | read_trit unpacks to Vec<i8> (~2.08 GB), embeddings f32 (~1.31 GB), lm_head cloned from embed when tied (another ~1.31 GB) |
| Model load + 1 forward | ~33 s | dominated by unpack + copy |
| Decode | ~6.3 s/token | scalar, branch-per-weight, single-threaded |
| Bytes touched per token | 521 MB ternary + 1,313 MB lm_head f32 |
lm_head is 2.5x the ternary weights |
These are targets to improve, not invariants. G1-G6 are invariants.
G8 — trit-core against the tritsim oracle
Two independent implementations of the same model must agree.
cargo test -p tritsim --release --test cross_implementation # tiny fixtures
cargo test -p tritsim --release --test cross_implementation -- --ignored # real checkpoint
On the real checkpoint, 8 positions, all three numerics rungs: cosine 1.000000 and identical top-1 at every position.
This gate found a real bug. The production path had precomputed
w_scale * x_scale into a single constant and multiplied once, where the
reference multiplies twice: acc as f32 * w_scale * x_scale. f32 multiplication
is not associative, so acc * (w * x) and (acc * w) * x round differently.
The drift was invisible at short contexts (cosine 1.000000 for four positions),
grew with position to 0.999624 by position 7, and changed the 16th generated
token from "the" to "known". Ordering the multiplications to match the reference
restored cosine 1.000000 everywhere.
Post-pivot results
The pivot's purpose was to move the G7 numbers without moving G1-G6. Measured
on the environment of record with models/bitnet-2b4t.trit (v1):
| Metric | Pre-pivot | Post-pivot | Change |
|---|---|---|---|
| Decode | 0.16 tok/s | 14.89 tok/s | 93x |
| Time to first token | ~33 s | 470 ms | 70x |
| Peak RSS | 6,401,024 KB | 1,891,184 KB | 3.4x less |
| Achieved bandwidth | ~0.3 GB/s | 27.3 GB/s | 66% of the 41.1 GB/s measured on this host |
Kernel throughput on a 2560x6912 tensor, planes resident in L3:
| Kernel | ms | GB/s | vs scalar |
|---|---|---|---|
| scalar | 9.10 | 0.5 | 1.00x |
| avx512vnni | 0.28 | 15.8 | 32.45x |
| avx512bw | 0.38 | 11.6 | 23.91x |
| avx2 | 0.66 | 6.7 | 13.87x |
| bitserial (popcount) | 6.12 | 0.7 | 1.49x |
Threading is measured, not assumed. A decode step issues 210 ternary matvecs
whose largest is 4.4 MB, and splitting each across the machine costs more in
fork/join than it saves: 14.13 tok/s at one thread against 2.40 tok/s at 32
before PARALLEL_MIN_BYTES was introduced. It now peaks at 4 threads (14.89)
and stays flat to 32 (14.34).
G1-G6 are unchanged throughout. G2 still reports 0.9991/100%, G3 is still
byte-identical in every numerics mode on both implementations, and the real
checkpoint still decodes byte-identically through the Verilated RTL core --
24.4 s/token in simulation, via tritd --backend rtl.
Sanctioned changes
Numbers here move only with an entry below, naming the phase, the reason, and the before/after.
| Phase | What changed | Before | After | Reason |
|---|---|---|---|---|
| B | .trit v0 -> v1 bit planes |
v0 codes | v1 planes | The migration is proven exact: tritc upgrade of the v0 file is byte-identical to a fresh convert. G2 and G3 unchanged. |
| B | Recorded zero fraction | 0.377 | 0.4219 | The old figure was hand-transcribed into checkpoint-notes.md and wrong. The byte-identity of the upgrade proves the trits themselves did not change. |
| C | RTL weight interface | w_data[127:0] |
w_pos/w_neg |
Synthesis is unchanged at 33,659 cells, still multiplier-free. |
The one change already anticipated: RoPE currently computes
theta.powf(-2.0 * i / head_dim) in f32 (crates/tritsim/src/math.rs),
giving angle errors up to ~1e-3 rad at position 2048. Moving both
implementations onto a shared f64 inv_freq/rope_angle is expected to shift
G2 slightly — in the direction of the reference, since the reference is more
accurate. That gets its own isolated phase and its own row above.