Design
How the engine is put together, the decisions worth defending, and an honest account of what is and is not verified.
Repository layout
proofinfer/
Cargo.toml no [dependencies]; release profile: opt-level 3, lto, codegen-units 1
LICENSE MIT
CONTEXT.md design notes: format, forward pass, gotchas, TCB
src/
lib.rs module map
ops.rs kernels: rmsnorm, softmax, silu, matmul, RoPE, GQA
model.rs config, hardened checkpoint loader, State, forward, generate
tokenizer.rs BPE vocabulary reader, encoder, decoder
main.rs CLI
tests/
engine.rs forward/generation properties
loader.rs hostile-input totality
tokenizer.rs conformance to Meta's token vectors
reference/
diff_test.py differential harness vs PyTorch
mutation_check.py proves the differential harness can fail
mutation_check_tests.py proves the Rust test suite can fail
bench.py benchmark + byte-identity check
llama2c/ vendored UNMODIFIED from karpathy/llama2.c (MIT)
verus/ Verus proof of KV-cache index safety (24 conditions,
machine-checked; see its README for the scope)
plus check_citations.py, which keeps the proof
from silently drifting away from src/
.github/workflows/ci.yml four jobs, no `|| true`, no continue-on-error
Design decisions worth defending
Zero dependencies, seriously. Cargo.lock contains exactly one package, and CI asserts it before anything else runs. Arg parsing, file IO, the f32 kernels, the BPE tokenizer and the CLI are all built on std. For a project about machine-verified inference, the smallest possible dependency surface is the point: every crate in the lock file widens the trusted base by another transitive unsafe blob.
The loader treats the file as hostile. The checkpoint header dictates how much memory gets allocated, so it cannot be trusted to be right. Three properties, each with a test that fails if it breaks:
- Totality. For any byte string,
from_bytesreturnsOkorErr, never a panic.tests/loader.rsfeeds it 2000 pseudo-random buffers, half prefixed with a plausible header so the cursor gets past validation into the size arithmetic, which is where the bugs actually live. - No allocation from an unvalidated number. A header claiming 4096 dim × 32000 vocab on a 64 KiB file is a perfectly legal set of numbers, so the only thing preventing a multi-gigabyte allocation is checking the bytes are present before creating the
Vec. - Overflow is an error, not a wraparound. Every size product goes through
checked_mul. This forced a design change: computing each tensor's length inline at the point of reading meant a header that overflowed atwqreported a confusing "token_embedding needs 8589934584 bytes" instead.TensorSizesnow computes the whole shape table up front.
forward allocates nothing. Every buffer lives in State and is written in place, so the memory footprint of decoding is a sum you can write down: scratch is a handful of vectors, and the cache dominates at 2 * n_layers * seq_len * kv_dim.
Greedy only, no sampling. Sampling needs an RNG, and an RNG would make the byte-identical comparison against run.c impossible unless both sides consumed the identical stream. Greedy decoding is a pure function of the weights and the prompt, so two implementations that agree on the maths produce byte-identical output — a far stronger claim than "the samples look similar".
Bugs found by the byte-identity check that no unit test caught. Generation originally stopped on EOS; llama2.c stops on BOS, which is the document delimiter these models are trained with, so with stories15M our output ran straight past the end of the first story. Output was also printed by decoding the whole sequence at once, whereas run.c prints per token through safe_printf, which drops single non-printable bytes. Both were found by bench.py and neither was findable from the differential test, which does not look at text at all.
Trusted computing base
Stated plainly, because it is the honest answer to "how much do you actually trust this?": the Rust compiler and std, the f32 semantics of the CPU, the checkpoint parsing code, and the reference implementation being tested against. Everything above those is tested. Those are assumed, and the assumption is written down rather than implied.
What is formally verified, and what is not
The first thing worth proving mechanically is index safety of the KV-cache slicing in forward: that for any pos < seq_len and any layer, every cache index computed in ops::attention and State::forward is in bounds.
verus/ contains a Verus development for exactly that, and it is machine-checked: Verus 0.2026.09.20.aef82ed reports 24 verified, 0 errors under --no-cheating, which rejects assume, admit and external_body outright. That is a real run, not a claim.
What is proved: the bounds of every KV-cache index expression for all shapes satisfying the loader's structural preconditions and all pos < seq_len; that those preconditions follow from the checks the loader actually performs; that the GQA mapping h / kv_mul lands in 0..n_kv_heads; and that the index arithmetic cannot overflow usize. The real slice expressions are transcribed into exec fns operating on actual Vec<f32>, so Verus discharges genuine slice-indexing obligations rather than assertions about symbolic expressions.
Keeping the proof honest
A green Verus run is not enough on its own, because the proof verifies a model. If someone changed h / kv_mul in ops.rs, the proof would keep verifying, faithfully, about code that no longer exists — and the green tick would be actively misleading.
So verus/check_citations.py checks the link. Every source line the proof and its README cite must still contain the construct it is cited for. It is bidirectional — an uncited citation and an unused expectation are both failures, so neither the prose nor the table can quietly stop being checked. Stdlib only, so it costs nothing in CI.
The check has been negative-tested, which matters more than it passing: it goes red when the GQA mapping is mutated in place, when an unrelated edit shifts the line numbers, when the attention window changes, and when a cache write is deleted from forward.
What it still does not check is the reasoning. An edit that preserves a cited expression's text while changing what it computes elsewhere would pass, as would a gap in which expressions were transcribed at all. It is a tripwire on the likely accident, not a proof of correspondence.
What is not proved
verus/README.md lists eleven items; these are the two that matter most:
- It is a proof about a transcription, not about the crate.
src/ops.rsandsrc/model.rscontain noverus!macro and are not compiled by Verus. The citation check above narrows how far this can go wrong; it does not close it. - Bounds safety does not catch wrong-but-in-bounds bugs. Mutant #3 (
h / kv_mul->h % n_kv_heads) is also in range and sails straight through. That is the boundary between what a verifier can say and what only the differential test can say, and the proof file says so itself.
Running the proof
The proof CI job fetches the prebuilt Verus release, verifies it against the sha256 GitHub publishes for that asset, installs the toolchain Verus asks for (it prints the exact rustup install line, which the job parses rather than hardcoding a version), and runs the verifier. Verus is not a dependency of this crate and is not vendored.
That job was executed, not just reasoned about. Its steps were run verbatim in an emulated x86_64 Linux container — the same architecture and OS family as GitHub's ubuntu-latest — and produced:
--- 2. Fetch and unpack Verus (x86-linux) ---
-rw-r--r-- 1 root root 485678212 verus.zip
verus.zip: OK # sha256 matched the pinned digest
unpacked: 1.6G
--- 3. Locate the Verus binary ---
Found Verus at verus-dist/verus-x86-linux/verus
--- 4. Install the toolchain Verus requires ---
verus: required rust toolchain 1.98.1-x86_64-unknown-linux-gnu not found
rustup install 1.98.1-x86_64-unknown-linux-gnu
Verus requires: 1.98.1-x86_64-unknown-linux-gnu
--- 5. Verify the KV-cache index-safety proof ---
verification results:: 24 verified, 0 errors
That run is also what justified the ANSI-colour fix in the toolchain-discovery step: on Linux the parse still had to strip the escape sequence to recover 1.98.1-x86_64-unknown-linux-gnu from the same coloured error message.
What is still unexecuted, precisely: the GitHub-hosted actions themselves — actions/checkout, actions/cache, actions/setup-python and dtolnay/rust-toolchain. Those are standard, they are not written here, and the container provided its own curl/unzip/python3/rustup in their place. The remaining risk on a first CI run is therefore in the YAML and the runner environment, not in the commands this repository contributes. If the proof job does go red, the other three jobs are unaffected and the log will name the step.
Nothing else here is machine-checked. The loader's totality rests on 2000 fuzz iterations plus reasoning, not a proof; the differential test establishes agreement with a reference on six configurations and one trained checkpoint, not equivalence to the architecture in general.
Two other things were scoped and deliberately not done, rather than half-done: a differential test against vLLM (needs a GPU and a Hugging Face export path, and the harness is written so that adding a third oracle is a new config table rather than a new file), and Triton kernels for rmsnorm, softmax and matmul (a benchmarking exercise with no bearing on whether the engine is correct, which is the claim this repository makes). The measurement in this repository is CPU and single-threaded throughout; nothing here says anything about GPU inference.
Credits
The PyTorch reference model, the legacy export format, the tokenizer format, the C engine, and the expected token id vectors all come from karpathy/llama2.c, MIT licensed. The files under reference/llama2c/ are vendored byte-for-byte unmodified; reference/llama2c/PROVENANCE.md records the sha256 of each so a reviewer can verify that, and it matters more than it might look — a differential test against a reference that has been edited is worth nothing.
The token id expectations in tests/tokenizer.rs originate in Meta's example_text_completion.py and are checked by llama2.c's test.c.
Source: https://github.com/blackdragoon26/proofinfer/blob/main/docs/design.md in the repository.