Watching a language model think

I used AI to build an LLM inference engine from scratch, so I could understand the thing that built it. Every number it computes is verified against llama.cpp, and you can watch it work in your browser.

Open the live lab ↗ GitHub ↗
The lab's logit lens at layer 23 of 28: the model's intermediate guess is already ' Paris' at 82%, drawn as the candidate closest to the output direction, with ' French', ' France', and ' city' further out.

Type "The capital of France is" into a language model and it says " Paris". Between those two events, 596,049,920 numbers are read off disk and pushed through 28 rounds of the same arithmetic. Most explanations of that arithmetic use cartoon diagrams or toy weights. I wanted to see the real thing: the actual numbers, from an actual model, at every step.

So I built suiron (推論, "inference"): an inference engine for Apple Silicon written from scratch in Rust, with zero runtime dependencies. No PyTorch, no candle, no tokenizers crate, no ggml, not even serde. The rule was: if a byte gets parsed or a float gets multiplied, code in this repo did it. On top of the engine sits the part I actually set out to make, a browser-based microscope that shows every stage of next-token prediction running live, on real weights, with nothing simulated.

The elephant, first

An AI wrote most of this code. I directed it, questioned it, and made it prove everything; Claude typed. I read that as the only sensible way to do this in 2026: hand-writing code an AI can write faster is a bad use of my hours, but shipping code neither of us understands is worse. So the project ran under two hard rules that made the AI's speed safe to use:

  1. From scratch, std only. A framework call hides exactly the parts worth learning. If the tokenizer is a library import, you never learn that "The" is assembled by two byte-pair merges. Zero dependencies was never about purity; it was about leaving no place for understanding to hide.
  2. Faithful or nothing. Every compute path is gated on matching a reference, llama.cpp first, then our own f32 path once faster paths existed. Nothing ships on "looks right."

Using AI to help me understand AI turned out to be the project's most honest description. The engine explains the model; building the engine explained the AI that helped build it; and the verification regime is what kept both honest. More on that below, because the regime earned its keep in a way I did not expect.

What got built

The stack, bottom to top, each piece gated before the next began:

56 Rust tests and 19 web tests guard all of it (CI runs them on every push; the model-dependent ones self-skip without the 640 MB file and run locally); the important ones assert exact reconstruction, not vibes, and several reconstruct engine values exactly.

The war stories that actually happened

The demo that lied. The lab has a "worked unembedding" demo: the final hidden vector, the real embedding rows for the top candidates, and the dot products that become their logits, so you can check the model's last step by hand. The first version of it, AI-written, ranked candidates by re-running the scoring helper on a vector that had already been normalized: the final RMSNorm applied twice. The rankings looked completely plausible. It got caught because every teaching surface in the lab carries an invariant test against the engine, and this one asserts that the demo's top candidate equals the engine's actual argmax. That is the whole thesis in one bug: an AI will occasionally produce something subtly, convincingly wrong, and "every number on screen must reproduce the engine's number" is what turns that from a quiet lie into a red test.

The memory wall. The browser build's naive plan was to load the model the way the native engine does, dequantized to f32: 2.4 GB, which kills a phone tab instantly. The fix was to keep the Q8 blocks themselves resident (the same 640 MB as the file) and dequantize rows on demand, with the browser running the same Q8 compute path as native. The gate before shipping: a Node harness ran the wasm engine and the native engine side by side. Identical tokens, identical top-k probabilities, all 28 lens layers agreeing.

The payoff: the microscope

The lab inspecting the token ' Paris' in the recorded run: the token strip shows the read head at ' is' with real attention arcs onto ' France', and the layer stack below reports each layer's attention pattern, with layer 0 locking onto ' of'.

The lab is one page, organized as a token's life: prompt → tokens → 28 layers → predictions → geometry → the draw. Inspecting any token shows how it was produced: which earlier tokens attention actually read (real weights, drawn as arcs), what the model predicted, and the exact random draw that picked the winner, seed and all.

The centerpiece is the logit lens: stop the model at any layer, apply the final unembedding early, and see what it would predict so far. You can watch " Paris" surface partway up the stack and take the lead, layer by layer. This is where "28 layers of the same arithmetic" stops being abstract; you can see the answer form.

Every stage opens into a worked demo on the live numbers: a real query·key dot product you can step through, RoPE visibly rotating a query as position changes, softmax → weights → weighted values reconstructing attention's output with zero measured difference from the engine's recorded value, and that once-lying unembedding demo, now test-pinned.

Two things I have not seen elsewhere in a teaching tool:

The browser version boots in seconds on a 7.5 MB recording of one real run, honestly labeled as a recording; one click downloads the model and replays the same seeded run live, and every moment in the lab is a shareable URL.

What it is not

It is not fast, and it is not trying to be; llama.cpp exists and suiron's f32 path is deliberately the simplest correct implementation of each operation. The claim I will defend is narrower: real numbers, from a real model, at every visible step, verified, in your browser. Static explainers with toy weights teach the shapes of things; this shows one particular model actually doing it.

If you want to see a language model think, the lab is open. Start with the tour, then click " Paris" and force it to say something else.

suiron is Qwen3-0.6B under the hood: small enough for a browser, big enough to be a real transformer. The engine, lab, and this writeup are on GitHub.