Testing

How this project establishes that it computes the right thing, and how each of those checks was shown to be capable of failing.

The short version: a differential harness against the PyTorch reference, a byte-for-byte comparison against the reference C engine, and two mutation checks that prove both halves of the test suite can actually fail.

Back to the README

Results

All numbers below were produced by the commands in this file on the machine measured in performance.md. Nothing here is quoted from anywhere else.

Differential test, random weights

python reference/diff_test.py — pass requires |err| <= 1e-4 + 1e-4*|ref| at every logit and argmax agreement at every position.

configdimheads / kvlayersvocabseqclassifiermax |err|max |ref|err/toltop-1
tiny644 / 429732tied3.87e-064.220.03032/32
gqa-2x648 / 4312848tied4.17e-064.170.02848/48
gqa-4x968 / 2220064tied5.72e-064.980.04664/64
mqa644 / 126440tied2.87e-064.570.02640/40
untied-cls1284 / 4451264untied9.66e-065.230.07164/64
full-context482 / 2150128tied5.01e-064.500.029128/128

6/6 pass. Worst case uses 7.1% of the allowed budget. Top-1 agreement is 100% everywhere.

max|ref| is printed on purpose. It is the evidence that the weights were re-randomised: llama2.c initialises matrices with std=0.02, which makes the logits nearly flat, and a flat output lets a whole family of genuinely wrong implementations land inside the tolerance. A max logit of ~5 rather than ~1e-3 means the test is measuring the arithmetic and not the tolerance.

Differential test, real weights

python reference/diff_test.py --checkpoint stories15M.pt -n 200

modelmax |err|max |ref|err/toltop-1result
stories15M (15M params, vocab 32000)3.34e-0526.20.192200/200PASS

The 200-token sequence is chosen by the reference greedy decoder, so the engine is being compared on a sequence an independent implementation produced.

Mutation check

python reference/mutation_check.py

mutantcaught by
RoPE exponent 2i → iall six
RoPE skipped on keysall six
GQA mapping h / kv_mul → h % n_kv_headsgqa-2x, gqa-4x only
Attention window pos + 1 → pos.max(1)all six
Attention scale removedall six
RMSNorm eps 1e-5 → 1e-27all six
SwiGLU gate and up swappedall six
Untied classifier ignoreduntied-cls only
Residual x += d → x = dall six
RoPE theta 10000 → 500000all six

"all six" means tiny, gqa-2x, gqa-4x, mqa, untied-cls, full-context, the order the harness reports them in.

10/10 caught. The whole run takes roughly 20-30 seconds, dominated by rebuilding the crate ten times; every mutant shares one CARGO_TARGET_DIR and runs with LTO disabled, since LTO is the slowest part of the build and has no bearing on which mutants get caught.

The per-mutant column is the useful part, and it is why the configurations are chosen the way they are. gqa-modulo-mapping is caught by the two GQA configurations and nothing else, because modulo and division agree when kv_heads == n_heads — the other four configurations cannot see that bug at all. ignore-untied-classifier is caught by untied-cls alone, for the mirror-image reason. If every mutant had been caught by all six configurations, that would have looked like thoroughness and actually meant the suite had no discrimination.

Two guards keep that number honest:

Rust tests, and proof they can fail

75 tests, passing in both release and debug (debug matters: it enables integer overflow checks, which is where a wrapping size computation in the loader would show up).

suitetestswhat it covers
src/ops.rs23kernels against hand-computed answers, RoPE invariants and its frequency schedule, causality of the attention window, dot across every length mod 8
tests/loader.rs18totality over hostile input, 2000 pseudo-random buffers, overflow, tensor ordering
tests/tokenizer.rs16exact conformance to Meta's published token ids, byte fallback, round trips
tests/engine.rs18causality, determinism, BOS termination, State::reset equivalence, argmax totality on NaN

But a passing suite only says the tests agree with the code. It does not say they can notice disagreement, so reference/mutation_check_tests.py injects 21 realistic bugs one at a time and requires cargo test to fail on every one. 21/21 caught. It is the same discipline as mutation_check.py, pointed at the other half of the project: that one proves the differential harness can fail, this one proves the Rust tests can.

Running it found two genuine blind spots, both of which were real holes in the suite rather than in the mutants:

A third finding was subtler and worth naming: state_rejects_a_config_whose_cache_would_overflow was using i32::MAX as dim, which is odd, so State::new rejected the odd head size and returned InvalidHeader. The test passed without ever reaching the arithmetic it is named after. It now uses an even dim, asserts the config is structurally valid, and asserts the specific Overflow { what: "kv cache" } error rather than merely that some error occurred.

Every check, and how it was shown to fail

A check that has only ever been observed passing is consistent with a check that cannot fail. So each one was also exercised in a state where it had to go red. This is the inventory:

checkhow it was shown to fail
diff_test.py (6 configs)10 injected bugs, mutation_check.py, 10/10
cargo test (75 tests)21 injected bugs, mutation_check_tests.py, 21/21
byte-identical greedy outputa RoPE-less mutant binary: bench.py reports DIFFERENT and exits 1
zero-dependency assertiona Cargo.lock with a second package, with zero packages, and naming a package that is not proofinfer — all three rejected
verus/check_citations.pyGQA mapping mutated in place, nine lines inserted to shift every citation, attention window changed, a cache write deleted; plus a missing source file, an unregistered citation, and a rotted table entry
CI workflow wiringevery steps.<id>.outputs.<name> resolves to a step that writes it, every run: block parses as bash, no || true outside comments
Verus itself--no-cheating rejects assume / admit / external_body; the unmutated baseline is run first so "24 verified" cannot mean "the file does not parse"

The full-run commands are in the repository, not just in a shell history: reference/mutation_check.py, reference/mutation_check_tests.py, and verus/check_citations.py all exit non-zero on failure and all run in CI.

Why the differential test is worth anything

The two implementations share nothing but the file format.

reference (PyTorch)proofinfer (Rust)
forward passbatched over the whole sequenceone token at a time
attentionscaled_dot_product_attention(is_causal=True)explicit loop over the KV cache
RoPEprecomputed freq_cis table in the filecomputed from theta and pos in ops::rope
GQA headstorch.repeat_interleaveh / kv_mul index division
weightsf32 tensors via numpyf32 slices read by a bounds-checked cursor

Two consequences worth spelling out.

RoPE is computed, not read. The checkpoint file contains a freq_cis table, and reading it would have been easier. But then the differential test would compare two programs sharing a precomputed constant, and a bug in our RoPE would be invisible. Computing it means the test genuinely exercises our angle arithmetic. The loader skips those bytes on purpose.

The tolerance is a statement about summation order, not about the model. Both sides sum the same products in different orders, and floating-point addition is not associative, so a bitwise comparison would be testing matmul's inner loop rather than the transformer. Hence 1e-4 + 1e-4*|ref|: the absolute term covers logits near zero, where a relative bound is meaningless because the reference itself is working in f32 and has no significant digits left, and the relative term covers large logits where accumulated rounding grows with magnitude. For fp16 the relative term would have to be around 1e-2, because fp16 carries about three decimal digits.

Source: https://github.com/blackdragoon26/proofinfer/blob/main/docs/testing.md in the repository.