How to Write an Inference Engine
The Muser book — Muse Glimmer on Apple Metal, kvpack, and disaggregated prefill
Scope of this book. This book explains, end to end, how the Muser inference engine generates text from the pinned Muse Glimmer model (52 layers, ~30B parameters) on Apple Silicon, with a kquant DFlash speculative lane, a native NVFP4 lane, and a disaggregated lane where a remote NVIDIA GB10 (“GX10”) node prefills NVFP4 under vLLM and hands the KV cache to the Mac over the authenticated Handoff V2 transport. Every line of Metal and Rust quoted here is read from the pinned source tree; every number is tagged with its measurement source.
This is a zero-to-hero book. The first time a concept appears — a softmax, a SIMD group, a nibble, a residual stream, a rotary embedding, a KV cache page, an NVFP4 block — it is defined in place, with a diagram and a worked example, before it is ever used to explain Muser code. A reader who knows Rust but has never written a GPU kernel, and who has never read a transformer paper, should be able to finish this book understanding the math of LLM inference, the Metal that makes it fast on Apple Silicon, the precision discipline that makes remote prefill trustworthy, and the evidence culture that keeps all of it honest.
We wrote it the way we worked. When a chapter reaches a real decision, it does not hand you the winning answer and move on. It sets up the fork, tells you what we tried and what we expected, lets the attempt fail on the page, names what the failure taught us — and only then shows what shipped. Put the other way round: the dead ends are not confessions tucked into an appendix, they are where the reasoning is visible, and they are the whole reason the shipped answer deserves your trust instead of merely your patience. Wherever we tell you one of those stories, the run that proved it is retained and cited, so you can go and check us.
How to read this book
You are holding eight parts, and they are not all the same kind of reading. Which order, then, and how much do you have to swallow before you are allowed to skip around?
Linearly, the first time. The chapters build on each other:
| Part | What it teaches |
|---|---|
| I — The problem and the Metal compute model | No transformer knowledge assumed. By the end of Part I you can read any Metal shader in this book and know what every line means. |
| II — Quantization | How ~30 billion weights fit in a ~17 GB GGUF, and what each lane pays. |
| III — The model | Muse Glimmer’s 52-layer graph, and the forward pass at a glance. |
| IV — The decode path, kernel by kernel | One chapter per stage, in execution order. The spine of the book. |
| V — The KV cache and kvpack | The cache is the engine’s second life: what it costs, how it’s laid out, and how it becomes a durable, portable asset. |
| VI — The disaggregated lane | Why a Mac and a GB10 are better together than either alone — and what it costs to trust someone else’s prefill. |
| VII — Orchestration and serving | How 52 layers × a dozen kernels × 4 slots become one disciplined owner of one accelerator. |
| VIII — Measurement and evidence | How to know anything at all. The part that makes the other seven honest. |
The appendices (A–F) hold the glossary, the kernel dispatch table, the lane and flag surface, the bibliography, the pin record, and the writing contract.
As a reference, afterwards. Each kernel chapter is self-contained: it states the math, the access pattern, the Rust dispatch, the Metal shader, the measured tradeoffs, and its own bibliography. You can jump to any one chapter once you have read Part I.
The measured reality
A book about performance is worth exactly as much as its numbers, so the fair question to ask before reading another sentence is: which machine made them, under what load, and can anyone else check? We would rather answer that here, at the front, than have you wonder about it for eight parts.
All Muser numbers in this book come from the retained evidence of the 2026-08
campaigns — five-repetition synthetic matrices, the kvpack ladder, the pacing
ladder, and the qualification wizard runs — recorded in the campaign ledger
(docs/goal-parity-ledger-2026-08.md in the Muser repo) and cited here with
receipt paths under muser-receipt://. The decode hardware is
one Apple Silicon Mac with an M3 Ultra and 96 GB of unified memory; the
prefill hardware is one ASUS GX10 (NVIDIA GB10) node on the same wired
10GbE fabric; the comparator is pinned llama.cpp. The shipped lane matrix
at the pin [docs/muser-architecture.md]:
| Lane | Prefill | Decode | Speculative | Intended use |
|---|---|---|---|---|
| Native NVFP4 | Spark tensor-core FP4 | Mac NVFP4 weights, FP16 KV, 35.491 tok/s | Rejected (fail-closed) | Fast product lane |
| kquant/reference | Reference path | kquant, 35.440 tok/s | 107.9 tok/s | Speculative + reference lock |
| Exact NVFP4 flag | Integer-dot verification producer | Mac NVFP4 | Verification only | Deterministic anchor |
Read that table for its gaps as much as for its throughputs. Only the kquant lane carries a speculative number; the fast product lane’s speculative cell says rejected (fail-closed), which is this engine’s way of saying that we tried the combination, it did not hold, and the code now refuses it outright rather than quietly serving you something weaker under the same name. That refusal has a chapter of its own. It is the shape of most stories in this book: an attempt we believed in, a measurement that disagreed, and a piece of machinery that now says no on purpose.
The book’s recurring question: what does one token cost, where does the
time go, and what may be moved — into a draft model, into a cache, or across
the wire — without breaking the exactness contract? Every kernel chapter
returns to the dispatch-gap accounting that keeps Muser’s decode at
parity-within-noise while rejecting fusions that would change logprobs
[docs/decode-dispatch-gap-20260815.md]; every systems chapter returns to
what the evidence actually permits us to believe.
The pinned source of truth
Every book written against living code goes stale. The only choice an author has is whether it goes stale visibly or invisibly, and a book whose line numbers drift silently is worse than no book at all. So we pinned it.
This book is written against one pinned revision of Muser (see
PINNED.md). When the book and the code disagree, the code
wins. Every quoted line is tagged file:line relative to the Muser
repository root so you can verify it. The canonical paths:
- Metal shaders:
crates/muser-engine/src/shaders/(including theferrite/lineage directory) - Engine and forward pass:
crates/muser-engine/src/ - Benchmarks and harness:
crates/muser-bench/ - kvpack adapter:
crates/muser-kvpack/ - Server and sessions:
crates/muser-server/ - Cluster/transport:
crates/muser-cluster/ - Vendored kvpack:
third_party/kvpack/ - Evidence:
muser-receipt://(append-only; cited by path)
Lineage and attribution
Muser is a from-scratch Rust workspace with no Ferrite runtime dependency,
but its CPU text-extraction lineage and select shaders/kernels were adapted,
with attribution, from the private Ferrite research tree
(docs/extraction-manifest.md, NOTICE). This book has its own ancestor
too: the Inference Book on Apple Metal written against Ferrite and
Qwen2.5-1.5B on an A18 Pro. This book keeps that book’s pedagogical spine —
zero-to-hero, define-everything-on-first-use, cite-everything — and rebuilds
the content for Muser’s model, lanes, and measured reality. Where a Ferrite
lesson survives on Muser’s live path, the book says so and cites it.
The reason that genealogy matters while you read, rather than only in the credits: the ancestor ran a far smaller model on a phone-class chip. Its numbers describe a different machine and do not transfer to this one. So when an A18 Pro figure appears in these pages it is labelled as ancestry, never as a Muser result, and the two never share an unmarked sentence.
What this book is not
Some promises are easiest to keep as prohibitions. Each of the three below is a genre this book could have slid into without anyone noticing, so we named them early and held each other to them.
- It is not marketing. Every number states what was measured and under what scope — device, model, quantization, reps — and cites its receipt. If you want a sales pitch, this is the wrong book.
- It is not a survey of dead ends for their own sake. Rejected designs (the linear distributed-speculative lane, native NVFP4 speculative decode under Fallback B, the ANE route for v0.1) appear where their failure teaches a tradeoff that survives. When one does appear, it is told as what it was — a fork we walked down, an expectation we were holding, the measurement that ended the argument — and not as a verdict in a list, because a list of verdicts teaches nobody how to make the next decision. The path is as important as the destination.
- It is not speculation. Where a “why” cannot be cited from source or a
measurement, it is marked
[unverified]rather than smoothed over.
Bibliography convention
Each chapter ends with a References section of its own, because a reader finishing one kernel chapter should not have to hunt the back of the book. You will usually meet these tags at the end of a paragraph, or gathered in a section’s evidence trail, rather than wedged into the middle of a sentence: the evidence is not optional, but neither is being readable, and a claim you cannot follow is no better cited than one you cannot check. Citation tags:
[crates/.../file.rs:LINE]— Muser source, pinned revision.[docs/<file>.md]— Muser engineering documents.[ledger §N]—docs/goal-parity-ledger-2026-08.md, the campaign ledger.[claims #N]—docs/launch-claims.mdrow N.[receipt <path>]— evidence undermuser-receipt://.[ferrite-book Ch N]— the ancestor book (pedagogical lineage).[Metal-SS §N]/[Metal-PG §…]— Apple Metal Shading Language Specification / Programming Guide.[CUDA §…]/[PTX …]— NVIDIA CUDA documentation, where the disaggregated lane demands the comparison.[arxiv:XXXX.YYYYY]— the paper.[vLLM …]/[llama.cpp …]— upstream projects Muser interoperates with.
Status of each chapter
The book is written in passes, and we would rather admit which pass a chapter is in than let you assume it has been checked. So every chapter declares itself before it says anything else — a status line at the top:
status: polished— reviewed, citable, ready to read.status: draft— written, not yet through review passes.status: stub— placeholder, to be filled.
Building
The book lives in src/ as plain Markdown with an mdBook-style
SUMMARY.md. To read linearly, start at
Ch 1. With mdbook
installed, mdbook build and mdbook serve work out of the box
(book.toml pins the metadata and the mermaid preprocessor; the site is
built without the repository’s _research/ working artifacts, which sit
outside src/ by design). GitHub Pages deploys on every push to main
via .github/workflows/pages.yml.
That is the apparatus — the scope, the machines, the pin, the tags. The rest of the book is the walk itself, and Part I starts where the work started: with the question of where a token’s time actually goes, and the discovery that the answer has almost nothing to do with arithmetic.
Chapter 1 — The problem: why inference is a memory problem
status: polished · path: Muse Glimmer, pinned Muser tree
Prerequisites: none. This is the first chapter of the book. It assumes you have never run a transformer and have never thought about memory bandwidth.
1.1 The promise of this book, and the question that runs through it
This book explains, end to end, how the Muser inference engine generates text from one pinned model — Muse Glimmer, 52 layers, roughly 30 billion parameters — on one Apple Silicon Mac with an M3 Ultra GPU and 96 GB of unified memory. Every line of Metal and Rust quoted in these chapters is read from the pinned Muser source tree; every number carries a tag that says where it was measured. When the book and the code disagree, the code wins.
Here is the thesis, in one sentence, before any definitions:
Generating a single token of text means streaming sixteen gigabytes of model weights through the GPU, and the GPU finishes the arithmetic long before the bytes have finished arriving.
That is the whole book in one line. LLM inference — the act of a model producing text — looks like a computation problem. It is not. It is a bandwidth problem wearing a computation costume. This chapter derives that fact on the back of an envelope, from the model’s own geometry, so that by the end you can reproduce every number yourself. We will do the arithmetic together, in roughly the order we did it ourselves — including the step where we went looking for a number we assumed existed, did not find it, and had to change how we asked the question.
And here is the question that recurs in every chapter after this one — the book’s standing question:
What does one token cost, where does the time go, and what may be moved — into a draft model, into a cache, or across the wire — without breaking the exactness contract?
Every kernel chapter in Part IV returns to that question (“is this kernel where the time goes?”), every systems chapter in Parts V–VII returns to it (“what may be moved?”), and Part VIII is about the evidence culture that keeps every answer honest. Keep the question in your pocket. We will collect answers as we go.
1.2 The cast: a few words you need first
Before the arithmetic, eight one-sentence definitions. We are about to count two quantities and divide them by two speeds, and every word below is load-bearing in one of those counts. Each is expanded in the glossary and, where it matters, in a later chapter.
- A parameter is one learned number inside the model — one knob tuned during training. A “30B model” holds on the order of 30 billion of these numbers.
- Weights are the parameters collectively — the giant tables of learned numbers stored on disk. “Reading the weights” means loading those tables so the math can use them.
- A token is one unit of text — roughly a piece of a word. “The cat sat” is about four tokens. The model emits tokens one at a time.
- Decode is the act of generating those tokens one by one, after the prompt has been read. Reading the prompt is called prefill; the two regimes have opposite bottlenecks, and the contrast becomes important in Ch 36.
- A matvec (matrix × vector) is the one operation decode does, over and over: multiply a big table of weights by a single vector representing the current token. The hero kernel of this book, Ch 13, is a matvec.
- A FLOP (Floating-point OPeration) is one unit of arithmetic — one multiply or one add. A matvec over a matrix with N elements costs about 2N FLOPs.
- A GGUF is the on-disk model-file format Muser reads: a small header, then the weight tensors packed end to end.
- Quantization is storing each learned number in fewer bits than a full 32-bit float — four-ish bits per weight instead of sixteen or thirty-two. Part II (Ch 5) is devoted to it; here you only need the fact that it shrinks the file.
The argument below is just: count the FLOPs, count the bytes, divide each by the machine’s speed at that thing, compare the two times.
1.3 One token, costed by hand
This is the arithmetic the rest of the book rests on. We cost out generating one token of Muse Glimmer on the M3 Ultra. No hand-waving: every number is derived, and the ones that are measurements say so.
Step 1 — how many parameters?
The label on the box says thirty billion parameters. Labels round, and we are about to divide bytes by this number, so we would rather read the model’s true shape off the artifact than off the marketing.
Happily, we do not have to take anyone’s word for the shape. Muse Glimmer’s
geometry is parsed fail-closed from the GGUF at load time, and a test asserts
every parsed field against the pinned release artifact. The table below is
therefore not a transcription from a model card — it is the geometry the
engine refuses to start without [crates/muser-engine/src/config.rs:169-181]
[crates/muser-engine/tests/muse_golden.rs:97-108]:
| Field | Value | Source |
|---|---|---|
| layers | 52 | muse_golden.rs:97 |
| hidden dim | 6,656 | muse_golden.rs:98 |
| attention heads : KV heads | 32 : 2 (head_dim 128) | muse_golden.rs:99-101 |
| attention width (32 × 128) | 4,096 | derived |
| KV width (2 × 128) | 256 | derived |
| FFN intermediate dim | 19,968 | muse_golden.rs:102 |
| vocab size | 202,048 | muse_golden.rs:103 |
| embedding / LM head | untied (two separate 6,656 × 202,048 tables) | config.rs:295-297 |
Table 1.1: Muse Glimmer geometry, read from the pinned GGUF contract.
Now count parameters from the shapes. The loader asserts a per-tensor shape
contract at config.rs:294-318, so the dimensions multiplied out below are
the ones the engine itself checks on the way in: a wrong figure here fails
the load rather than quietly producing a wrong book. Per layer:
attention projections:
q 6,656 × 4,096 = 27,262,976
k 6,656 × 256 = 1,703,936
v 6,656 × 256 = 1,703,936
gate 6,656 × 4,096 = 27,262,976
o 4,096 × 6,656 = 27,262,976
85,196,800
feed-forward (gate, up, down):
3 × (6,656 × 19,968) = 3 × 132,907,008 = 398,721,024
norms (4 × 6,656) + per-head QK norms (2 × 128) = 26,880
per layer: 483,944,704
× 52 layers = 25,165,124,608
+ token embedding 6,656 × 202,048 = 1,344,831,488
+ LM head 6,656 × 202,048 = 1,344,831,488
+ final norm = 6,656
─────────────
total = 27,854,794,240
So Muse Glimmer is 27.85 billion parameters by hand — the “~30B” on the label is rounded up. The exact count matters less than the habit: every bandwidth number in this book divides through a total like this one, and you should be able to re-derive it.
Step 2 — how many bytes on disk?
Parameters are not bytes, and it is bytes that the memory system has to move. So the next question is what all those learned numbers actually weigh.
Full-precision f32 would be 4 bytes each: 27.85e9 × 4 ≈ 111 GB. That does not fit the plan. The pinned kquant GGUF stores most weights in 4-to-6-bit blocks instead (Part II explains the formats), and its exact size is asserted by the release-gate test:
#![allow(unused)]
fn main() {
// crates/muser-server/src/chat_template.rs:250
assert_eq!(metadata.len(), 16_756_681_056, "release GGUF byte size");
}
That assertion is not decoration. The size is written down in two independent
places that have to agree, and the second is the engine’s own crate doc,
which states it in words: “The pinned target artifact is 16,756,681,056 bytes
on disk.” [crates/muser-engine/src/lib.rs:14] If a rebuild ever changed the
artifact, the gate would fail loudly rather than let this chapter’s
arithmetic quietly drift.
Units, because they will bite you otherwise:
16,756,681,056 bytes
= 16.757 GB (decimal, 10⁹ bytes — the convention this book uses)
= 15.61 GiB (binary, 2³⁰ bytes)
Cross-check against Step 1: 16,756,681,056 × 8 bits ÷ 27,854,794,240 parameters ≈ 4.81 bits per parameter — quantized blocks plus their per-block scale bytes plus the two big tables, averaged out. A 30B model at ~4.8 effective bits is what makes a 96 GB Mac a plausible host at all.
Step 3 — how much do you read to produce ONE token?
This is the insight that surprises people. To generate one token, every weight matrix is used exactly once: the matvec for each projection touches every row of every weight table a single time. The weights are 16.76 GB and used once per token, so they cannot live in any cache — caches are megabytes. They must be streamed, in full, out of main memory, for every single token.
Say it the other way round, because this is the part that trips people up. The weights are not a dataset the GPU loads once and then keeps close. They are a river. One token is one complete pass of that river past the ALUs, and the next token starts the river again from the top, byte for byte the same.
1 token → read ~16.76 GB of weights, once.
10 tokens → ~167.6 GB.
500-token answer → ~8.4 TB of reads.
Not because the model grows — because every token re-reads all of it.
A reader who already knows what a KV cache is will be objecting by now: the weights are not the only thing decode reads. Quite right, and the reason it matters here is that our envelope is only honest if the other traffic is negligible — if it were comparable, every ratio in this chapter would be wrong. So it is worth spending a paragraph proving that it is not.
The non-weight traffic exists but is small at shallow context. The KV cache
(a per-layer memory of past tokens; Ch 15 is
its chapter) costs 1,024 bytes per layer per cached token — the formula
2 KV heads × 128 × 2 bytes × (K + V) is derived in
[docs/memory-footprint.md §KV formula]. At the benchmark cell we use below
(a ~98-token context), attention reads 52 × 98 × 1,024 B ≈ 5.2 MB per token
— 0.03 % of the weight stream. At the full 131,072-token context it grows
to ~1.83 GB per token [docs/memory-footprint.md] — still 9× smaller than
the weights; Ch 22 costs it out properly. The
objection is real, then, but it does not change the shape of the answer: at
the depths this chapter reasons about, the weight stream is the traffic.
Step 4 — how much arithmetic is that?
Bytes counted. Now the other side of the comparison, the side everyone assumes is the expensive one: how much arithmetic does a single token actually demand?
A matvec y = W·x over a matrix with N elements costs ~2N FLOPs — one
multiply-add pair per element. Summing only the matrices (norms and the
embedding lookup contribute ~nothing):
matmul parameters = 52 × (85,196,800 + 398,721,024) = 25,163,726,824
+ LM head 6,656 × 202,048 = 1,344,831,488
─────────────
26,508,558,312
FLOPs per token ≈ 2 × 26.5e9 ≈ 53.0 GFLOP
(The embedding table contributes 0 FLOPs — the lookup is a gather, not a multiply.) That is the entire compute budget for one token: ~53 GFLOP.
Step 5 — what does the machine actually do?
We have the bytes and we have the FLOPs. To turn either into a time we need the machine’s own speeds — and this is where the book departs from the ancestor text it descends from. The fork is worth walking slowly, because what we chose here governs every bandwidth claim in the rest of the book.
The Ferrite book, written for a small phone-class chip, could quote a measured DRAM ceiling for its hardware, and we expected to do the same: run a read microbenchmark, take the ceiling, divide the weight bytes by it, done. We could not. For this M3 Ultra, no Muser document records a measured pure-read DRAM ceiling. That left two roads. We could borrow a specification figure from a datasheet and let the reader assume it was ours — fluent, authoritative, and unearned. Or we could work the equation backwards from a number we had genuinely measured, and say plainly that it is a derivation.
We took the second road, and it is the reason the number below carries a label everywhere it appears: this book derives the effective read rate from measured decode throughput rather than asserting a bus speed it never observed.
The measurement we do have is the decode throughput itself. The kquant lane’s headline decode number — five repetitions after warmup, synthetic fixture, F16 KV, 66-token prefix / 32 teacher-forced tokens (teacher-forced: the harness feeds known prior tokens rather than model-generated ones) — is:
35.440 tok/s (35.439527527, CV 0.037 %) — kquant plain decode
[ledger P1.3], echoed in[docs/benchmarks.md §1]
(“CV” is coefficient of variation, std/mean across the repetitions — the lower, the steadier the cell.)
Two derived numbers fall out, and both are derivations, not measurements:
token time = 1 / 35.439527527 = 28.22 ms per token
effective read rate
= 16,756,681,056 bytes × 35.439527527 tokens/s
= 593.85 GB/s (decimal; 553 GiB/s)
This 594 GB/s is an effective rate derived from measured throughput —
the machine’s demonstrated average weight-stream rate at this operating
point. It is not a specification number and not a measured pure-read
ceiling. For orientation only: the campaign ledger refers to the M3 Ultra’s
“~800 GB/s” memory class when discussing kernel occupancy
[ledger L0] — a class label, again not a measured ceiling. Against that
class, decode runs at ≈ 74 % (593.85 / 800). Where this book needs an honest
bandwidth reference, it uses the derived effective rate and says so.
Step 6 — compute time vs. memory time
Now the punchline. At the derived effective rate, just reading the weights for one token costs:
16.756681056 GB / 593.85 GB/s = 28.22 ms
That is the entire measured token time — 28.22 ms, from Step 5. The weights alone, streamed at the rate the machine actually sustains, consume the whole token. The 53 GFLOP of arithmetic has no room of its own; it must hide completely underneath the byte stream (Figure 1.1).
Put in plainer words: the GPU is not slow, and it is not busy. It is waiting. The whole engine described in this book is an argument about what to do with a processor that finishes early.
Sanity-check the roofline direction. The workload’s arithmetic intensity — FLOPs per byte read — is:
53.0e9 FLOP / 16.757e9 bytes ≈ 3.2 FLOP / byte
A machine whose memory moves ~800 GB/s [ledger L0, class reference] needs
only ≥ 800 × 3.2 ≈ 2.6 TFLOP/s of sustained FP32 to keep its ALUs busy on
this workload. The M3 Ultra GPU’s exact FP32 peak is not recorded in any
Muser document [unverified], but any GPU paired with an ~800 GB/s memory
system clears 2.6 TFLOP/s many times over. Decode sits deep on the memory
side of the roofline — and, more directly: the measured token time equals
the weight-stream time. There is nothing left over for compute to own.
DRAM (one unified 96 GB pool) GPU (M3 Ultra)
┌────────────────────────────────────┐ ┌────────────────────────────┐
│ mapped GGUF 16.76 GB (kquant) │ │ ~53 GFLOP of matvec math │
│ ════════════════════════════════ │ ───▶ │ hides entirely under the │
│ ════════════════════════════════ │ 594 │ byte stream; the token │
│ ════════════════════════════════ │ GB/s │ time IS the read time │
│ read ALL of it, ONCE per token │ │ (28.22 ms measured) │
└────────────────────────────────────┘ └────────────────────────────┘
52 layers × {Q,K,V,gate,O, gate,up,down}
+ final norm + LM head (202k-wide)
Figure 1.1: One token = stream ~16.76 GB once. The bytes leave DRAM at the
~594 GB/s effective rate (derived from the measured 35.440 tok/s
[ledger P1.3]); the math finishes underneath. The GPU is byte-starved,
not ALU-starved.
1.4 The bandwidth wall
The consequence is brutal and it dictates the entire engine.
If a workload is compute-bound, you speed it up with more ALUs, higher clocks, a bigger GPU. If it is memory-bound, none of that helps — the ALUs you already have are waiting for bytes most of the time; adding more adds more waiting. Muse Glimmer decode does 3.2 FLOPs per byte read; the machine’s balance point is an order of magnitude higher. It is on the wrong side of the roofline, and no kernel cleverness moves it to the right side — the matvec is one multiply-add per weight byte; its intensity is fixed by the model’s format.
So only three things can make decode faster:
- Read fewer bytes per token — smaller weights. This is why Part II
exists: the kquant blocks that get Muse Glimmer to 4.81 effective
bits/param, and the NVFP4 native lane. It is also where the argument’s
first tempting expectation died. Fewer bits per weight ought to mean
fewer bytes per token, and fewer bytes per token ought to mean faster
decode; we ran the two lanes against each other expecting the newer
format to pull ahead. NVFP4 decode landed at 35.491 tok/s against
kquant’s 35.440 — parity within noise, never claimed faster
[ledger P1.3][docs/benchmarks.md §1]. At batch-1 decode both lanes read nearly the same bytes at nearly the same rate, so the win we actually bought was capacity, not decode speed. That distinction — a lane that buys you room is not a lane that buys you time — is one this book refuses to let slide, in either direction. - Read bytes faster — a different memory system. Not a lever you have on a fixed Mac; the ceiling is the machine’s.
- Avoid re-reading — reuse computed results. The KV cache exists so
attention does not recompute the past every token
(Ch 15); kvpack (the durable cache format
of Part V) warm reuse collapses a
68.6 s cold prefill at 65,536 tokens to a 0.6132 s warm first token,
bit-identical text — a single-sample cell, not a distribution
[claims #11][ledger "Kvpack ladder stage-5 isolated-depth verdict"].
Muser’s era adds a fourth lever the ancestor book did not have: move the work somewhere else.
- Move it into a draft model: DFlash speculative decoding drafts cheap
tokens and verifies a whole window of them in one batched pass against
the full model — the verify matvec reintroduces weight-reuse into a loop
that had none. This lever also produced the book’s most instructive
retraction. An early headline put the speedup at 1.3273×; then the
measurement window itself turned out to be wrong, and requalifying
against the fixed window brought the honest figure down to 1.23692× vs
the pinned llama.cpp comparator at 2,048 depth
[claims #15]. The old headline is superseded and must not be cited as a current result, and the famous 107.9 tok/s figure survives only as the kquant spec bar[ledger "Spec-prefill funded-fix requalification"]. Both were kept in the ledger rather than quietly deleted, which is the habit Part VIII is about: a number you have to explain is worth more than a number you have to trust. - Move it across the wire: disaggregated prefill runs the
compute-friendly half of the job on a remote NVIDIA GB10 node and hands
the KV back — 4.26× faster to first token at 2,048 depth
[docs/benchmarks.md §3][ledger "Phase 4 disaggregated GX10→Mac context matrix", 5 reps]. - Move only what’s new: delta handoff ships just the suffix — 54.2851 %
of full bytes with bit-exact output at the 32,768-of-65,536 cell
[claims #12][receipt kvpack-ladder-20260820/attempt-10-…-stage6-delta/ stage6-delta-65536/stage6-verdict.json].
Every chapter of this book is about one of these four levers, or about the evidence culture that decides whether a lever actually worked.
1.5 “96 GB is still a budget”
The ancestor Ferrite book opened with a provocation: on its 8 GB phone-class
chip, “8 GB is a lie” — capacity was never the binding constraint,
bandwidth was [ferrite-book Ch 1]. On this machine the provocation flips
sign but keeps its shape: 96 GB is real, and it is still a budget —
capacity answers “does it fit, and what else fits”; bandwidth answers “how
fast”. You need both, and the second one governs decode.
What must fit in the 96 GB [docs/memory-footprint.md]? The arithmetic,
all of it derivable:
| Resident thing | Size | Notes |
|---|---|---|
| target GGUF (weights) | 16,756,681,056 B | mmap/page-cache backed |
| KV planes, 4 slots × 131,072 ctx | 7.306 GB | 4 × (39 × 2,048 + 13 × 131,072) × 1,024 B |
| DFlash draft GGUF | 1,631,205,312 B | loaded only when configured |
| vision projector | 1,400,328,928 B | loaded only when configured |
| f32 batch-activation widths | ~0.99 GB | reused scratch; prefill chunks at 512 |
| macOS + everything else | the rest | not the engine’s to spend |
Table 1.2: The 96 GB budget [docs/memory-footprint.md] — with that
document’s own caveats: these are on-disk/topology-derived numbers,
“summing artifacts + KV is only a lower bound,” and no smaller-memory
configuration may be advertised as supported.
Two budget facts worth internalizing now. First, the release contract is
four full-context slots on this one machine
[docs/memory-footprint.md §Release requirement] — KV (7.3 GB at the
ceiling) plus weights (~16.8 GB) plus optional draft/vision (~3 GB) plus
activations is what “four slots” costs; the KV term, not the weights, is
what decides how many slots fit (Ch 22
derives this). The engine shares one mapped weight arena across all slots
precisely so the 16+ GiB target is loaded once, not once per slot
([crates/muser-engine/src/decode.rs:954-957]). Second, capacity and
bandwidth fail differently: run out of bandwidth and you get 28 ms tokens;
run out of capacity and you get nothing.
1.6 How to read every number in this book
One thing remains before the descent: how to read a number when you meet one. None of the rules below is an abstract principle. Each was learned by watching a figure that was perfectly true of a particular run turn into a claim about the product, and then having to take it back. Stated once here, obeyed everywhere after:
- Ratios are
llama ÷ muser— above 1.0 means muser is faster[docs/benchmarks.md §Methodology]. Absolute tok/s drifts with machine state; the same-session interleaved ratio is the trustworthy cross-engine statistic. - Counted cells are five repetitions after the stated warmup convention, means with coefficient of variation; single-sample cells say so (Ch 38 covers the protocol).
- Synthetic vs. natural is load-bearing. The spec-decode ratios above
are synthetic-fixture numbers; on natural text, cross-engine outputs
diverge, and spec decode loses on high-acceptance shallow text (rust at
2,048: 0.931)
[ledger "Spec re-measurement at the fixed window"]. Never let a synthetic number become a workload claim. - Never cite the all-accept 110.59 tok/s control as serving
performance, the retired 5.83× remote-prefill figure, the superseded
1.3273× spec headline, or the barred 1.64960× decode-at-131k accounting
[docs/launch-claims.md]. Every figure on that list was once the true result of a real run; each became misleading the moment it was quoted outside the cell that produced it. They are retained, and retained visibly, so nobody has to rediscover on their own why they were pulled. - Ferrite-lineage numbers (the ancestor lab’s A18-class measurements) are labeled as lineage when they appear, never as Muser results.
1.7 Where the book goes from here
You now hold the book’s central derivation. The rest is a guided descent into the machinery that lives under it.
- Part I (this part) teaches the Metal compute model —
Ch 2 (devices, queues, command buffers,
threads, threadgroups, SIMD groups),
Ch 3 (unified memory and the buffer
substrate that maps 16.76 GB of weights with zero copies), and
Ch 4 (how
.metalsource becomes runnable kernels — from three distinct sources). - Part II is quantization: how 27.85 billion parameters fit in 16.76 GB, and what each lane pays for it.
- Parts III–IV build the model and walk the decode loop kernel by kernel — the place where “one token = one pass over the weights” becomes fifty-two layers of named dispatches.
- Part V is the KV cache as an asset; Part VI is the disaggregated lane — the fourth lever, taken seriously; Part VII is serving; Part VIII is measurement and the evidence culture.
By the end, the sentence at the top of §1.1 will be something you have proven, kernel by kernel, from source — not something you were told.
But there is a prerequisite. Everything in this book after this page happens on the GPU, and to follow it you must speak Metal: what a device is, what a command buffer records, what a threadgroup is, why the SIMD group is the unit that actually matters on Apple Silicon. That language is the next chapter.
References
[crates/muser-engine/tests/muse_golden.rs:97-108]— the pinned-artifact geometry assertions (52 layers, hidden 6,656, heads 32:2, head_dim 128, FFN 19,968, vocab 202,048).[crates/muser-engine/src/config.rs:169-181]— fail-closed GGUF metadata parsing of the same fields;:294-318the per-tensor shape contract used in the parameter count.[crates/muser-engine/src/lib.rs:14]— “The pinned target artifact is 16,756,681,056 bytes on disk.”[crates/muser-server/src/chat_template.rs:237-261]— therelease_gguftest: byte size, chat-template length (7,167 B), and the three SHA-256 identities.[docs/memory-footprint.md]— KV formula, the 96 GB budget table, the four-slot release contract, and its own “lower bound only” caveat.[docs/benchmarks.md]— §1 (35.44/35.49 parity-within-noise), §Methodology (ratio and repetition conventions), §3 (disaggregated payoff band).[ledger P1.3]—docs/goal-parity-ledger-2026-08.md, kquant/NVFP4 plain-decode table: 35.440 (CV 0.037 %) / 35.491 (CV 0.130 %), five reps, 66-token prefix / 32 teacher-forced tokens, F16 KV.[ledger L0]— same ledger, “microbenchmark-first apparatus and the occupancy bound”: the “~800 GB/s M3 Ultra” memory-class reference.[claims #11],[claims #12],[claims #15]—[docs/launch-claims.md]: warm reuse at depth, delta handoff, current spec-decode restatement.[receipt kvpack-ladder-20260820/attempt-10-…-stage6-delta/ stage6-delta-65536/stage6-verdict.json]— the 54.2851 % delta-share evidence (verifieddelta_share_of_full: 0.5428507652…,exact_against_full_handoff: true).[ferrite-book Ch 1]— the ancestor’s “one token, costed by hand” device this chapter ports; its 1 GB / 45.95 GB/s / 33.20 tok/s numbers are Ferrite-lineage and do not transfer.- glossary — terms introduced this chapter: parameter, weights, token, decode, prefill, matvec, FLOP, GGUF, quantization, bandwidth, arithmetic intensity.
Chapter 2 — The Metal compute model
status: polished · path: Muse Glimmer, pinned Muser tree
Prerequisites: Ch 1. You know why decode streams ~16.76 GB per token. This chapter assumes you have never written a GPU shader.
2.1 What a GPU actually is
Chapter 1 ended on a prerequisite: everything after that page happens on the GPU, and following it means speaking Metal — devices, command buffers, threads, threadgroups, SIMD groups. This chapter teaches that language from zero. A CPU is good at doing one hard thing fast. A GPU is good at doing ten thousand easy things at the same time. Inference is, almost entirely, ten thousand easy things: multiply this row of weights by this vector, add up the products, do it again for the next row. That is why the GPU does the work, not the CPU.
The “ten thousand easy things” are called threads. A thread is one execution lane: it runs your program once, sees its own small slice of the data, and writes its own small slice of the answer.
But a GPU is not a magical parallel computer where every thread is independent. Threads are organized, and the organization is what makes a kernel fast or slow. On Apple Silicon the organization that matters most is the SIMD group, which we meet in §2.6. First, the API.
2.2 Metal: Apple’s GPU API
Metal is Apple’s API for talking to the GPU. It is to Apple GPUs what CUDA is to NVIDIA GPUs: the vendor’s own first-class compute path. If you want compute work done on an Apple Silicon Mac, Metal is that path.
Metal gives you three things:
- A shading language (MSL, Metal Shading Language) — a C++ dialect in
which you write the code each thread runs. That per-thread program is
called a kernel: a single function, written
once, that every thread in a launch runs once over its own slice of the
data. Muser’s kernels live in
crates/muser-engine/src/shaders/, and the census there is small enough to say out loud: 29.metalfiles, of which two are Muser-authored and 27 sit in theferrite/lineage directory, plus one bench-only candidate shader undercrates/muser-bench/. Where an inherited kernel came from is recorded rather than remembered; the extraction manifest is retained[docs/extraction-manifest.md]. - A host API (Objective-C underneath, wrapped by the Rust crate
metal) — the code the CPU runs to compile kernels, allocate memory, and submit work. - A memory model — on Apple Silicon, a single physical DRAM pool shared by CPU and GPU. Ch 3 is devoted to it.
The single most important mental model for the host API is the next section.
2.3 “Record a tape, then press play”
The CPU does not tell the GPU “do this now.” Instead the CPU records
a sequence of instructions onto an object called a
command buffer, and then hands the
whole buffer to the GPU in one shot. Think of recording a cassette tape:
you can record many songs onto it, in order, and only when you press play
does anything actually happen [Metal-PG, "Command Buffers"].
The object that records the tape is the compute command encoder. You ask it to do four things, over and over:
bind the kernel you want to run (set_compute_pipeline_state)
bind the memory buffers it will read/write (set_buffer, repeated)
bind any small constants (set_bytes)
launch N copies of the kernel (dispatch_thread_groups)
That four-line sequence is one dispatch — one kernel running once across many threads. A Muser decode token is a dozen-plus dispatches per layer across the model’s 52 layers, plus a head and tail — hundreds of dispatches in all, recorded onto one command buffer per token, then played. The per-group counts are reconciled and measured in Ch 35; here you only need the shape.
Here is the CPU side, verbatim — forward_token, the function that owns
one whole decode token:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/decode.rs:5448
let command_buffer = queue.new_command_buffer();
// One concurrent encoder owns the complete token. Graph dependencies
// are explicit barriers; independent projection groups share a barrier
// interval and may overlap, matching the accepted Ferrite/llama route.
let serial = GraphEncoder::concurrent(
command_buffer
.compute_command_encoder_with_dispatch_type(metal::MTLDispatchType::Concurrent),
);
self.encode_token(&serial, &token_view, self.n_past)?;
serial.encoder.end_encoding();
command_buffer.commit();
self.context
.wait_for_completion(command_buffer, Duration::from_secs(300))?;
}
Steps, in order: get a blank tape from the queue; get a recorder with
concurrent dispatch semantics (§2.9); record the entire token with
encode_token (the 52-layer graph — hundreds of encode_* calls, each of
which records one or a few dispatches); stop recording; press play
(commit); wait — bounded, in Muser, by a 300-second deadline rather than
an unbounded block. Recording is pure CPU bookkeeping measured in
microseconds; the wall-clock time is paid between commit and the wait
returning.
One deliberate difference from the ancestor Ferrite book’s engine is worth
a paragraph, because it is the reason the cost model you just learned is
the only one you need: Muser does not have a dispatch_* family of
test-only wrappers that each pay a full commit-and-wait round trip. In an
engine that has them, a kernel can be submitted two ways — the cheap
shared-tape way on the hot path and the expensive one-tape-per-kernel way
in tests — and the two drift. Here, tests hand-roll the same five steps
inline when they need them; the multi-column kernel tests are the example
to read [crates/muser-engine/src/metal/encode/multicol.rs:373-390].
Everything on the hot path is an encode_* call onto a shared encoder,
and there is no second submission shape to keep in your head.
2.4 The handles: one struct, five GPU objects
Which pieces of GPU state deserve to be created once and kept forever, and
what does getting that wrong cost? The question is not academic: opening a
device, building a command queue, and compiling a library of shaders are
all slow enough that doing any of them per token would swamp the token.
Muser’s answer is to hoard exactly the handles that are expensive to make
and cheap to share, in one struct, MetalContext:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/metal/context.rs:32
pub struct MetalContext {
pub device: Device,
pub queue: CommandQueue,
pub library: Library,
/// Strict-f32 copy of the standalone Muse kernels. The cross-vendor
/// Q8 projection and integer NVFP4 routes must match CUDA's explicit
/// scalar boundaries, while the ordinary serving kernels retain fast math.
pub cross_vendor_library: Library,
pub ggml_library: Option<Library>,
pub ggml_library_path: Option<PathBuf>,
}
}
Walk the fields. The Rust types come from the metal crate: Device
wraps MTLDevice, CommandQueue wraps MTLCommandQueue, Library
wraps MTLLibrary.
device(MTLDevice) — the handle to the GPU. On Apple Silicon there is exactly one system-default device:Device::system_default()at[crates/muser-engine/src/metal/context.rs:46]. You ask it to allocate memory and to compile shaders.queue(MTLCommandQueue) — the queue that accepts command buffers, created once at startup:device.new_command_queue()([crates/muser-engine/src/metal/context.rs:47]). Every command buffer comes off this queue.library(MTLLibrary) — a bundle of compiled kernel functions, addressable by name. This one is built at startup from a concatenation of 24.metalsource files, with fast math on ([crates/muser-engine/src/metal/context.rs:59-110]). Ch 4 covers how, including why there are three libraries where a simple engine would have one.cross_vendor_library— the same two source files recompiled with fast math off, so a handful of kernels match CUDA’s arithmetic boundaries bit for bit when the remote-producer lane needs them ([crates/muser-engine/src/metal/context.rs:111-121]).ggml_library— an optional third library loaded from a prebuilt llama.cpp.metallib(a kernel library serialized to disk; Ch 4 is its chapter) whenMUSER_GGML_METALLIBis set — the pinned upstream kernels Muser dispatches for numerical parity ([crates/muser-engine/src/metal/context.rs:122-131]).
There is a timing rule hiding in that list, and it is the part to carry
forward. At construction time, every kernel is looked up by name and
compiled once into a cached pipeline state — the MetalKernels
constructor and its PIPELINES registry of 66 names
[crates/muser-engine/src/metal/encode.rs:21-88], which is
Ch 4’s subject again. At dispatch
time you never touch a library at all. You reach for the pipeline the
registry already holds, and it costs a line:
self.bind(encoder, "sigmoid_gate_inplace")
[crates/muser-engine/src/metal/encode/gate.rs:17].
Ask the registry for a name nobody put in it and the process dies on the
spot: PsoCache::get refuses to return silently for an unregistered name
[crates/muser-engine/src/metal/pso_cache.rs:45-49]. That loudness is on
purpose — a missing pipeline is a programming error, never a runtime
condition — and the tradeoffs section returns to why the crash is the
kinder outcome.
These five objects are the only long-lived GPU state. Everything else — buffers, command buffers, encoders — is created per-use or per-token (Ch 3 for the buffers).
2.5 One queue, one owner
When several sequences are decoding at once, who is allowed to talk to the GPU, and in what order? Metal answers half of that: command buffers from one queue run in FIFO order, so the GPU itself will not interleave two tokens’ work. It does not answer the other half. Several resident sequences can still stampede the queue from many threads, and FIFO then means only “whoever got there first, repeatedly” — a hot slot can starve its peers while staying perfectly ordered. Muser’s answer is a single owner:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/decode.rs:1020
/// One owner for the shared Metal queue. Decode work is selected first and
/// resident sequence IDs rotate in ascending cyclic order, preventing a hot
/// slot from repeatedly reacquiring the accelerator ahead of its peers.
struct AcceleratorScheduler {
state: Mutex<AcceleratorSchedulerState>,
ready: Condvar,
}
}
The AcceleratorScheduler ([crates/muser-engine/src/decode.rs:1023-1026])
is a Mutex+Condvar gate: acquire(sequence_id, work) blocks until the
accelerator is free and this sequence is the chosen next one — decode work
is selected before prefill, and decode sequences rotate in ascending
cyclic order ([crates/muser-engine/src/decode.rs:1040-1059]). Every graph
— every tape — is recorded and committed while holding an
AcceleratorPermit. The shared execution resources live next to it, in
MetalShared:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/decode.rs:954
/// Immutable Metal execution resources shared by every resident sequence.
/// Metal command submission is scheduler-serialized; retaining one context,
/// pipeline set, mapped weight arena, and GPU vector set avoids loading the
/// 16+ GiB target once per serving slot.
pub struct MetalShared {
}
That comment is Ch 1’s capacity
argument made structural: four serving slots share one context, one
pipeline set, one mapped 16.76 GB weight arena, and one scheduler. The
per-sequence state (KV planes, activations, position) lives elsewhere, in
MetalMuseModel ([crates/muser-engine/src/decode.rs:989-998]).
2.6 Threads, threadgroups, and the SIMD group
This is the part that trips people up. There are three nested units of parallelism on an Apple GPU, and you must understand all three to read Muser’s dispatch sizes.
┌─────────────────────────────────────────────────────────────────────────┐
│ GRID = the entire launch (what dispatch_thread_groups fixes) │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ (n threadgroups) │
│ │ threadgroup│ │ threadgroup│ │ threadgroup│ ... │
│ │ ┌────────┐ │ │ ┌────────┐ │ │ │ │
│ │ │SIMD grp│ │ │ │SIMD grp│ │ │ │ each SIMD group = │
│ │ │ 32 lanes│ │ │ 32 lanes│ │ │ │ 32 threads in lockstep │
│ │ └────────┘ │ │ └────────┘ │ │ │ │
│ │ ┌────────┐ │ │ │ │ │ │
│ │ │SIMD grp│ │ │ │ │ │ │
│ │ └────────┘ │ │ │ │ │ │
│ └────────────┘ └────────────┘ └────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
Figure 2.1: The three nested units. A grid of threadgroups, each threadgroup of SIMD groups, each SIMD group of 32 lockstep threads.
- Thread — the unit that runs the kernel once. Each thread has a
unique
thread_position_in_gridso it can pick its own slice of the data. Nothing is promised about the order threads run in. - Threadgroup — a block of threads
(32–1024) that share on-chip threadgroup memory
and can synchronize with each other using
threadgroup_barrier(...). A threadgroup is the unit of co-scheduling: all its threads live on one compute unit together. - SIMD group (Apple-specific, the important one) — exactly 32
threads that execute in lockstep on one SIMD ALU. All 32 lanes run the
same instruction at the same instant. The superpower: lanes within a
SIMD group can exchange data in one cycle via intrinsics like
simd_sum(x)(a 32-way sum) andsimd_shuffle(val, lane). A reduction that would takelog₂(32) = 5barrier-separated passes between independent threads takes onesimd_suminside a SIMD group.
Here is a real Muser kernel that uses all three units — rms_norm_batch,
the batched RMSNorm reduction (RMSNorm itself is
Ch 12’s subject; here it is
just “reduce one row of the residual stream to one scalar, then scale”):
// crates/muser-engine/src/shaders/ferrite/rmsnorm_batch_tail.metal:1
kernel void rms_norm_batch(
device const float* x [[ buffer(0) ]], // [B × n]
device const float* weight [[ buffer(1) ]], // [n] (shared)
device float* out [[ buffer(2) ]], // [B × n]
constant uint& n [[ buffer(3) ]],
constant float& eps [[ buffer(4) ]],
uint tgid [[ threadgroup_position_in_grid ]],
uint tid [[ thread_index_in_threadgroup ]],
uint sgitg [[ simdgroup_index_in_threadgroup ]],
uint lid [[ thread_index_in_simdgroup ]],
threadgroup float* shared [[ threadgroup(0) ]])
{
const uint batch = tgid;
device const float* xb = x + batch * n;
device float* ob = out + batch * n;
device const float4* xb4 = (device const float4*)xb;
device const float4* wb4 = (device const float4*)weight;
device float4* ob4 = (device float4*)ob;
const uint n4 = n >> 2u;
float sum_sq = 0.0f;
for (uint i = tid; i < n4; i += 128u)
sum_sq += dot(xb4[i], xb4[i]);
sum_sq = simd_sum(sum_sq);
if (lid == 0u) shared[sgitg] = sum_sq;
threadgroup_barrier(mem_flags::mem_threadgroup);
if (tid == 0u)
shared[4] = rsqrt((shared[0] + shared[1] + shared[2] + shared[3]) / float(n) + eps);
threadgroup_barrier(mem_flags::mem_threadgroup);
const float inv_rms = shared[4];
for (uint i = tid; i < n4; i += 128u)
ob4[i] = xb4[i] * inv_rms * wb4[i];
}
Read it with the vocabulary. Each threadgroup handles one row of the
batch (tgid), and the launch is 128 threads = 4 SIMD groups. The
[[ ]] attributes are MSL’s way of receiving precomputed coordinates:
tgid = which threadgroup (which row), tid = thread 0..127 within it,
sgitg = which of the 4 SIMD groups, lid = lane 0..31 within the SIMD
group. The reduction inside is the classic two-stage pattern:
- Each thread accumulates partial sums of squares over
float4(4-wide) loads, striding by 128 — the strided loop that shares work across the threadgroup. simd_sum(sum_sq)collapses each SIMD group’s 32 partials to one value in a single instruction.- Lane 0 of each SIMD group writes its group’s value into threadgroup
memory (
shared[sgitg]). threadgroup_barrier— every thread in the threadgroup waits until all writes to threadgroup memory are visible. This is the thread-level barrier; it is the only way to synchronize across SIMD groups.- Thread 0 combines the 4 group values, computes the inverse root mean
square, parks it at
shared[4]. - A second barrier, then every thread re-reads
inv_rmsand scales its slice of the row.
Say the same thing the other way round, because this asymmetry is the one idea that shapes every kernel in the rest of the book: inside a SIMD group, threads talk to each other for free, in a single instruction. Between SIMD groups, they cannot talk at all — they can only leave messages in threadgroup memory and agree, via a barrier, on when it is safe to read them. Two levels of communication, two very different prices. A kernel that does its reducing inside SIMD groups and its combining across them pays the expensive price as few times as possible; that is what the two-stage pattern above is for, and it is why the geometry of a launch is never an arbitrary choice.
The Rust side that launches it:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/metal/encode/norm.rs:270
self.bind(encoder, "rms_norm_batch");
encoder.set_buffer(0, Some(input.metal()), 0);
encoder.set_buffer(1, Some(weight.metal()), 0);
encoder.set_buffer(2, Some(output.metal()), 0);
set_value(encoder, 3, &(dim as u32));
set_value(encoder, 4, &eps);
encoder.set_threadgroup_memory_length(0, 32);
encoder.dispatch_thread_groups(MTLSize::new(rows as u64, 1, 1), MTLSize::new(128, 1, 1));
}
In prose: grid = rows threadgroups (one per row of the batch), threadgroup
= 128 threads (4 SIMD groups of 32), and 32 bytes of threadgroup memory
(shared[0..4]). Note set_threadgroup_memory_length on the host side
matching threadgroup(0) in the kernel — the buffer-slot contract of §2.8,
one slot namespace over.
The 32sg tell
Muser’s kernel names carry their geometry. The live decode tail kernel is
muser_fused_norm_residual_rms_norm_32sg — “32sg” = 32 SIMD groups =
1,024 threads, because it fuses two norms plus a residual add over Muse
Glimmer’s 6,656-wide rows and wants the whole row resident in the
threadgroup. The dispatch comment says exactly that:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/metal/encode/norm.rs:236
// 32 SIMD groups keep the 6,656-wide Muse tail resident and match the
// accepted Ferrite geometry. 33 floats are padded to Metal's 16-byte
// dynamic-threadgroup-memory alignment.
encoder.set_threadgroup_memory_length(0, 144);
encoder.dispatch_thread_groups(MTLSize::new(rows as u64, 1, 1), MTLSize::new(1024, 1, 1));
}
(The kernel body at
[crates/muser-engine/src/shaders/ferrite/rmsnorm_batch_tail.metal:147-201]
is the same two-barrier pattern as rms_norm_batch, with 1024u strides
and a 32-entry combine loop — read it now, it will hold no surprises.)
Whenever you meet a Muser kernel in this book, decode the suffix first:
_4sg, _32sg, _4r2s (4 rows, 2 SIMD groups), _n32 (32 output rows
per threadgroup). The suffix is the geometry.
2.7 The grid is the output shape
How many threads should a kernel launch? There is a rule, and it is short enough to memorize: the grid is the output shape. You launch one thread per thing you intend to write, and the shape of the launch follows the shape of the answer, never the shape of the input. Getting it wrong is not a performance bug, it is a correctness bug — too small a grid leaves part of the output holding whatever was there before, and too large a grid runs off the end of the buffer unless the kernel guards. Two contrasting real geometries from Muser’s decode path show the rule doing its work:
An elementwise kernel — one thread per element. Muse Glimmer’s sigmoid attention-output gate multiplies one 4,096-wide vector by an elementwise-gated copy of itself (the architecture’s oddity, covered in Ch 17). The whole kernel:
// crates/muser-engine/src/shaders/ferrite/sigmoid_gate.metal:7
kernel void sigmoid_gate_inplace(
device float* attn_out [[ buffer(0) ]],
device const float* gate [[ buffer(1) ]],
constant uint& n [[ buffer(2) ]],
uint gid [[ thread_position_in_grid ]])
{
if (gid < n) {
attn_out[gid] *= 1.0f / (1.0f + exp(-gate[gid]));
}
}
And the whole dispatch:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/metal/encode/gate.rs:7
pub fn encode_sigmoid_gate(
&self,
encoder: &ComputeCommandEncoderRef,
values: &GpuBuffer,
gate: &GpuBuffer,
) {
debug_assert_eq!(values.len(), gate.len());
if std::env::var_os("MUSER_CROSS_VENDOR_QK").is_some() {
encoder.set_compute_pipeline_state(&self.cross_vendor_sigmoid_gate);
} else {
self.bind(encoder, "sigmoid_gate_inplace");
}
encoder.set_buffer(0, Some(values.metal()), 0);
encoder.set_buffer(1, Some(gate.metal()), 0);
set_value(encoder, 2, &(values.len() as u32));
dispatch_1d(encoder, values.len());
}
}
dispatch_1d is Muser’s one-line elementwise helper:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/metal/encode.rs:1337
pub(super) fn dispatch_1d(encoder: &ComputeCommandEncoderRef, count: usize) {
if count == 0 {
return;
}
let width = count.min(256) as u64;
encoder.dispatch_threads(MTLSize::new(count as u64, 1, 1), MTLSize::new(width, 1, 1));
}
}
Note it uses dispatch_threads — the non-tiled form where you state the
exact total thread count and a threadgroup width, and Metal works out the
grid (256-wide groups here, and Metal pads the tail so the gid < n guard
matters). One thread per output element; idle guard-exited threads cost a
branch, not a stall.
A batched pointwise kernel — 1,024-wide groups. The prefill-side
residual_add_batch (dst[i] += src[i] over a batch) instead fixes the
geometry explicitly, one thread per element in groups of 1,024:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/metal/encode.rs:515
self.bind(encoder, "residual_add_batch");
encoder.set_buffer(0, Some(destination.metal()), 0);
encoder.set_buffer(1, Some(source.metal()), 0);
set_value(encoder, 2, &(total as u32));
encoder.dispatch_thread_groups(
MTLSize::new(total.div_ceil(1024) as u64, 1, 1),
MTLSize::new(1024, 1, 1),
);
}
Same rule — grid = ⌈total/1024⌉ groups of 1,024 threads covers exactly
total outputs. And a matvec inverts the aspect ratio entirely: it emits
one output element per weight-matrix row, so it launches many small
threadgroups (one or a few per row). The exact matvec geometries are
Ch 13’s subject; remember only the
rule.
2.8 Binding memory: slots are the contract
Before launching, you tell each thread where its inputs live. You bind
MTLBuffer objects to numbered buffer slots (0, 1, 2, …) that the
kernel reads as [[ buffer(N) ]]. Look back at sigmoid_gate_inplace:
its parameters carry [[ buffer(0) ]], [[ buffer(1) ]], [[ buffer(2) ]],
and those numbers line up one-for-one with the CPU-side calls
set_buffer(0, …), set_buffer(1, …), set_value(encoder, 2, …). The
slot index is the whole contract between a kernel and its dispatch code.
Small constants — a uint count, an eps float, a small argument struct —
do not get their own MTLBuffer; they are inlined into the command buffer
with set_bytes, which is what the set_value helper wraps
([crates/muser-engine/src/metal/encode.rs:1329-1335]).
The subtle half of set_buffer is its third argument, a byte offset into
the buffer. Muser’s weight dispatch binds views — one giant mapped
buffer plus per-tensor offsets — through exactly this argument:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/metal/encode/multicol.rs:192
encoder.set_buffer(0, Some(weights.metal()), weights.offset() as u64);
}
What weights is, why one buffer serves the whole model, and what the
offset arithmetic must respect are Ch 3’s
story — the next chapter.
2.9 Serial vs concurrent dispatch
So far the encoder looked like a strict sequence: dispatch N, then dispatch N+1. Metal offers two flavors of compute encoder, and Muser uses both deliberately:
- A serial encoder (
new_compute_command_encoder()) runs its dispatches in order, one at a time. Simple, and leaves the GPU under-occupied when consecutive dispatches are independent. - A concurrent encoder
(
compute_command_encoder_with_dispatch_type(MTLDispatchType::Concurrent)) may overlap dispatches that have no dependency between them — exactly what a decode graph wants, because its four input projections (Q, K, V, gate) read the same input and write disjoint outputs.
You saw forward_token create the concurrent encoder in §2.3. Freedom is
not free: with overlap, you must say where dependencies live. Muser wraps
that rule in one type, GraphEncoder, whose entire job is to insert an
explicit memory barrier between dispatch groups:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/decode.rs:6256
impl EncodeTarget for GraphEncoder<'_> {
fn before_dispatch(&self) {
if self.concurrent && self.has_dispatch.replace(true) {
// Broad buffer scope exactly matches llama.cpp's dependency reset.
// Independent kernels are deliberately grouped into one dispatch
// closure, so every closure boundary is a real graph dependency.
unsafe {
let _: () = objc::msg_send![self.encoder, memoryBarrierWithScope: 1u64];
}
}
}
// …
}
}
Read it as a protocol: the 52-layer graph is encoded as a sequence of
closures (dispatch(command, |encoder| { … }) at
[crates/muser-engine/src/decode.rs:6273-6279]); independent kernels are
deliberately packed into one closure (they may overlap inside it); every
closure boundary is a real dependency, and before_dispatch plants a
memoryBarrierWithScope there so the GPU drains prior writes before the
next group starts. The comment names the provenance: this is llama.cpp’s
own dependency-reset discipline, adopted wholesale.
So why does the serial encoder still exist at all? Because which flavor wins is not obvious from first principles, and the losing side was kept so the comparison can be re-run. The decode path had already been converted: its projections sit together in one closure and overlap. Prefill was the open question, and the argument for leaving it serial was respectable — prefill kernels are large, a large kernel should saturate the GPU on its own, and concurrency would then be buying overlap nobody needed while still paying for barriers. The comment records how that argument fared:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/decode.rs:976 (fields of MetalShared)
// Prefill-only concurrent Q/K/V/gate and FFN gate+up. Decode already
// groups those projections; serial prefill paid a launch tax on PP128.
// `MUSER_SERIAL_PREFILL_DISPATCH` restores the previous encoder for A/B.
concurrent_prefill_dispatch: bool,
}
Serial prefill lost. The launch tax it paid was visible in the
prompt-processing cell, so concurrent became the default — and the old
encoder was not deleted. MUSER_SERIAL_PREFILL_DISPATCH restores it
[crates/muser-engine/src/decode.rs:1332], which means the two policies
can still be raced against each other on one binary, on whatever machine
doubts the result. That is the flag-for-measurement culture you will meet
throughout this book: a decision that was made by a number keeps the switch
that produced the number.
The same instinct, pushed to its diagnostic extreme, gives every dispatch
group its own command buffer. That is deliberately the expensive shape this
section argued against — and it is exactly why it is useful, because Metal
then reports an exact GPU interval per group instead of one interval per
token. It lives in the PhaseProfiler
[crates/muser-engine/src/decode.rs:6224-6236], gated by
MUSER_METAL_PHASE_PROFILE, diagnostic-only and never on the serving path.
2.10 The full cycle, and a bounded wait
Putting the whole chapter together, the lifetime of one piece of GPU work in Muser:
- Acquire the accelerator (
AcceleratorScheduler::acquire,[crates/muser-engine/src/decode.rs:1040]). queue.new_command_buffer()— get a blank tape.- Create the encoder (concurrent on the token path; serial where the A/B flag says so).
- (Hundreds of times) bind pipeline + bind buffers + set constants +
dispatch — via
encode_*functions, grouped into barrier-delimited closures. encoder.end_encoding()— stop recording.command_buffer.commit()— press play; the GPU starts, asynchronously.- Wait for completion — bounded.
Step 7 deserves its own paragraph, because it is where Muser’s fail-closed
culture reaches even the wait. CommandBufferRef::wait_until_completed()
blocks a thread unboundedly — a wedged GPU would freeze the serving
thread forever. Muser’s wait_for_completion parks that blocking call on a
detached watcher thread and bounds the caller’s wait with a condvar, so a
hang becomes a logged Deadline error carrying the command buffer’s label
and status, not a frozen box ([crates/muser-engine/src/metal/context.rs:149-211]).
The 300-second deadline you saw in forward_token is that mechanism.
2.11 Tradeoffs
Three decisions in this chapter had a plausible alternative. Here is what happened when we took each alternative seriously.
One command buffer per token vs. per dispatch. Start with the easy half. The obvious way to run a decode graph is one command buffer per kernel — record, commit, wait, repeat — and it is what a first engine writes, because every step is independently debuggable. It also pays a CPU↔GPU round trip per dispatch, hundreds of them per token, which is why Muser records the whole token (embedding through softcap) onto one buffer and waits once.
The hard half is that the cheap shape is not free either: batching a token
onto one tape makes encode-side work and dispatch count grow, and both
are measurable. So the campaign measured them. The production one-token
decode graph reconciles to 760 profiling closures vs the legacy route’s
564 — a +196 difference — reconciled exactly into 104 separated
norm-boundary groups + 39 SWA wrapped-ring staging groups + 52
KV-publication/attention splits + 1 last-row copy
[docs/decode-dispatch-gap-20260815.md §Corrected closure-count diff]. (Closures there are Rust profiling closures, not raw
Metal dispatches; the two counts are not interchangeable.)
Read that reconciliation and one term looks like free money. The 104
separated norm-boundary groups are the largest single line in the diff, and
they exist only because a boundary was drawn between two norms. So we built
the fix: a hybrid that fused across the boundary and deleted the separated
groups. The expectation was that the graph would shed the biggest slice of
its extra structure while computing exactly the same numbers, since fusing
adjacent arithmetic is supposed to be a rearrangement, not a change. It was
not. The hybrid changed bits — normalized-logprob max error 3.197e-4
against a 1e-4 contract, with the first divergence tracked down to one f16
ULP in layer-1 V — and we kept the postmortem instead of the patch
[docs/decode-dispatch-gap-20260815.md §Rejected hybrid postmortem]. (In
that sentence a logprob is the logarithm of the probability the model
assigns a token, and a ULP — unit in the last place — is the smallest
step a float format can take; the parity contract is measured in exactly
those units. Ch 38 owns it.)
The lesson is worth stating plainly, because the rest of the book keeps
running into it: dispatch structure is a lever, but exactness is the gate.
A restructuring that changes the output is not a faster engine, it is a
different engine, and it does not get to compete. What survived from the
same investigation was the one removal that was provably exact — a single
6,656-element copy — which bought −0.136 ms GPU (−0.34 %)
[docs/decode-dispatch-gap-20260815.md §Landed and rejected reductions]. That is the honest size of the prize when you refuse to
trade bits for it.
Concurrent vs serial encoding. Concurrency buys overlap between
independent projections and sells you a new obligation in return: every
real dependency now needs its barrier, spelled out by hand, or the engine
computes garbage fast (§2.9). That is a bad trade whenever the overlap is
worth nothing — which is what the “prefill kernels are large enough
already” argument predicted, and what the measurement contradicted:
“serial prefill paid a launch tax on PP128”
[crates/muser-engine/src/decode.rs:976-979], PP128 being the 128-token
prompt-processing benchmark cell. Concurrent is therefore the default, and
MUSER_SERIAL_PREFILL_DISPATCH survives for one purpose only: re-running
the comparison that settled it.
Panicking on an unregistered pipeline. The gentle-looking alternative
is to hand back nothing and carry on — return an Option, skip the
dispatch when the name is unknown, keep the server up. Follow that path to
its end, though, and a typo’d kernel name produces no error at all: the
output buffer keeps whatever was in it, the graph continues, and the model
goes on emitting plausible tokens computed from a step that never ran.
Muser takes the crash instead. PsoCache::get panics on a miss
[crates/muser-engine/src/metal/pso_cache.rs:45-49], so the mistake
surfaces at the first dispatch, in development, as a programming error
with a name attached. Fail-closed, even here.
2.12 What’s next
You now know every Metal concept needed to read any kernel in this book:
device, queue, command buffer, encoder, thread, threadgroup, SIMD group,
grid, buffer slots, the encode/commit/wait cycle, and Muser’s
barrier-between-groups protocol. One big thing was deferred: §2.8’s
set_buffer(slot, buffer, offset) hid the entire memory story — what a
MTLBuffer is on this machine, why one buffer can hold the whole
16.76 GB model, and how the weights get from disk into it without a single
copy. That is unified memory, and it is the next chapter.
References
[crates/muser-engine/src/metal/context.rs:32-42]—MetalContext(device, queue, and the three libraries);:46-47device/queue creation;:149-211the deadline-boundedwait_for_completion.[crates/muser-engine/src/decode.rs:5432-5463]—forward_token: the one-command-buffer-per-token cycle with the concurrent encoder.[crates/muser-engine/src/decode.rs:954-984]—MetalShared: one context/pipeline-set/arena per engine, and theconcurrent_prefill_dispatchA/B comment.[crates/muser-engine/src/decode.rs:1020-1030]—AcceleratorScheduler, the one owner of the shared queue.[crates/muser-engine/src/decode.rs:6124-6279]—EncodeTarget,GraphEncoder(concurrent +memoryBarrierWithScope),PhaseProfiler, and thedispatchclosure helper.[crates/muser-engine/src/shaders/ferrite/rmsnorm_batch_tail.metal:1-33]—rms_norm_batch(the three-units reduction kernel);:147-201the32sgdual-eps fused tail.[crates/muser-engine/src/metal/encode/norm.rs:236-240, 270-277]— the 1×128 and rows×1024 dispatches and the 6,656-wide geometry comment.[crates/muser-engine/src/shaders/ferrite/sigmoid_gate.metal:7-16]and[crates/muser-engine/src/metal/encode/gate.rs:7-23]— the elementwise kernel and its dispatch.[crates/muser-engine/src/metal/encode.rs:500-523]—encode_residual_add_batch(explicit 1,024-wide geometry);:1329-1343—set_valueanddispatch_1d;:21-88— the 66-namePIPELINESregistry.[crates/muser-engine/src/metal/pso_cache.rs:45-49]— the panic-on-miss pipeline cache accessor.[crates/muser-engine/src/metal/encode/multicol.rs:192]— a real offset-view bind (forward pointer to Ch 3).[docs/decode-dispatch-gap-20260815.md]— the +196-closure reconciliation, the rejected hybrid (3.197e-4 vs the 1e-4 contract), and the one exact removal (−0.136 ms GPU).[Metal-PG]— Apple, Metal Programming Guide: “Command Buffers,” “Compute Processing,” “Dispatching Threads.”[Metal-SS]— Apple, Metal Shading Language Specification: “SIMD-group Functions,” “Threadgroup Functions,” address-space attributes.- glossary — terms introduced this chapter: Metal, MSL, kernel, thread, threadgroup, threadgroup memory, threadgroup barrier, SIMD group, grid, MTLDevice, MTLCommandQueue, command buffer, compute command encoder, dispatch.
[ferrite-book Ch 2]— the ancestor’s tape-recorder analogy and three-units pedagogy this chapter ports; its 215-shader, ~171-dispatch and encode-percentage figures are Ferrite-lineage.
Chapter 3 — Unified memory and the buffer substrate
status: polished · path: Muse Glimmer, pinned Muser tree
Prerequisites: Ch 2. You know what an
MTLDevice, a command buffer, an encoder, andset_buffer(slot, buffer, offset)are. This chapter assumes you know nothing about memory architecture.
3.1 The big picture: one DRAM, two brains
Chapter 2 closed on set_buffer(slot, buffer, offset) and deliberately
deferred the memory story — what an MTLBuffer is on this machine, and why
one buffer can hold the whole 16.76 GB model. That deferral leaves a
question standing, and it is the question this chapter answers: where do
the weights actually live while the GPU is reading them, and what does it
cost to put them there? On this machine the answer is short enough to be
surprising, and it starts from the single most important fact about Apple
Silicon for inference.
On a discrete GPU — an NVIDIA card in a desktop — the CPU and the GPU do
not share memory. The CPU sits next to one pool of memory chips
(CPU DRAM, what people call RAM), the GPU sits
next to a separate pool on the card (VRAM),
and the two pools are wired together by a bus (PCIe).
Every time the CPU wants the GPU to see a weight tensor, it must memcpy
the bytes across that bus. A 16.76 GB model means shipping 16.76 GB over
PCIe before the first token can even be thought about.
Apple Silicon is built differently. The CPU cores and the GPU are on the same piece of silicon (a system-on-chip, SoC), and they both point at the same physical pool of DRAM. There is no second pool and no bus to copy across. This single-pool design is called unified memory; Figure 3.1 draws the contrast with the discrete world.
APPLE SILICON (unified memory) DISCRETE GPU (e.g. NVIDIA)
┌─────────────────────────────┐ ┌───────────┐ PCIe ┌───────────┐
│ SoC │ │ CPU │◄════════►│ GPU │
│ ┌──────┐ ┌──────┐ │ │ ┌─────┐ │ bus + │ ┌─────┐ │
│ │ CPU │ │ GPU │ │ │ │ DRAM │ │ memcpy()│ │ VRAM │ │
│ │cores │ │cores │ │ │ └─────┘ │ │ └─────┘ │
│ └──┬───┘ └───┬──┘ │ └───────────┘ └───────────┘
│ │ │ │ two physical DRAM pools;
│ └───────┬────────┘ │ you memcpy weights
│ ┌────┴─────┐ │ across the bus
│ │ one DRAM │ │
│ │ pool │ │
│ │ (96 GB) │ │
│ └──────────┘ │
└─────────────────────────────┘
one physical DRAM; CPU and GPU
read/write the SAME bytes
Figure 3.1: Unified memory (Apple Silicon) vs. the discrete split. On Apple Silicon “uploading weights to the GPU” is a no-op — the bytes are already there. (This diagram is also the seed of Part VI: the remote GB10 node is a discrete-memory machine across a 10GbE wire, which is why the disaggregated lane moves KV tiles, not work-in-progress.)
For inference the consequence is transformative: a model file on disk can be mapped into the process’s address space once, and — because the GPU shares that address space — the GPU can read the weights directly from those pages. There is no second copy. §3.6 shows the exact calls; first, what Metal calls its buffers.
3.2 The three Metal storage modes, and the one Muser uses
Unified memory settles where bytes can live. It does not tell Metal what you intend to do with them, and Metal insists on being told: every allocation carries a declaration. So the question for this section is which declaration Muser makes — for every buffer in the engine, without exception — and why a faster-looking alternative was tried and then handed back.
When you ask the MTLDevice for a buffer, you must tell it a
storage mode — where the bytes physically
live and who can see them. Metal defines three [Metal-PG, "Resource Objects: Storage Modes"]:
StorageModeShared— one copy of the bytes, in unified memory. CPU and GPU read and write the same physical pages; a CPU write is instantly visible to the GPU. This is the Apple Silicon mode.StorageModePrivate— bytes only the GPU can touch; the CPU must stage data through a Shared buffer and a GPU blit copy (a block copy run on the GPU through a dedicated blit encoder, as opposed to the compute encoder that runs kernels). This is how discrete VRAM works.StorageModeManaged— two caches, one per side, explicitly synchronized withsynchronize/didModifyRangecalls. It exists for older discrete-Mac setups.
On Apple Silicon, Private and Managed are simply irrelevant: there is no
separate VRAM to be private to and no two caches to manage. Muser uses
StorageModeShared for every buffer it creates, through one function:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/metal/buffer.rs:7
fn shared_tracked() -> MTLResourceOptions {
// Several accepted Muse paths still cross compute encoders (notably
// target-hidden prefill/capture). Untracked resources are only valid when
// every such dependency has an explicit fence/barrier. b9678d4 enabled
// untracked mode globally before that contract existed and empirically
// changed DFlash conditioning while leaving final greedy IDs unchanged.
MTLResourceOptions::StorageModeShared
}
}
A function that returns one enum constant should not need a paragraph of
commentary above it. This one does, because the interesting part is what
the return value omits: it is just StorageModeShared, with no
HazardTrackingModeUntracked flag beside it. The comment is a scar, and
the story behind it is worth telling in full, because the failure it
records is one this book will keep meeting: a change that produces
correct-looking output and is wrong anyway.
Lineage — the untracked-hazards gamble, and why we declined it. Metal can optionally mark buffers untracked (
HazardTrackingModeUntracked), which tells the driver to skip its automatic dependency tracking between dispatches. Bookkeeping you skip is work you do not pay for, and we had reason to expect a win: the ancestor Ferrite engine ran untracked and made it safe with its own conflict-driven barrier planner, measured there at a real but modest win[ferrite-book Ch 3](Ferrite-lineage numbers, ~2–4 % on that lab’s A18-class hardware). So we took the fork. Commitb9678d4flipped the switch engine-wide — and, in the comment’s own words, did it “before that contract existed”: before every cross-encoder dependency in Muser had an explicit fence standing in for the tracker we had just turned off.What we expected was a small speedup and byte-identical output. What we got was identical output and different internal state. The build empirically changed DFlash [speculative-draft] conditioning while leaving final greedy IDs unchanged (
[crates/muser-engine/src/metal/buffer.rs:7-14]). Read that twice. The tokens matched — so every greedy-output test we had would have gone green. What moved was the speculative draft’s conditioning, which is watched on its own precisely so that a change like this has somewhere to show up.A silent conditioning change with correct-looking output is exactly the failure this engine exists to prevent, so the switch went back. Muser runs Metal’s default tracked storage plus the explicit
memoryBarrierWithScopegroups you met in Ch 2 §2.9. The general rule survives from the ancestor book, and it is worth stating in the abstract because it recurs for the rest of the engine: turning off a safety net is safe if and only if something else provably enforces the ordering. Ferrite had that something — a barrier planner. Muser, at that commit, did not, and the cheaper provable thing was to keep the net on.
3.3 The buffer substrate: three types, one view
Unified memory decides where bytes live; it says nothing about what they mean. An engine that keeps f32 activations, f16 KV planes and immutable quantized weights in a single address space needs some way of stopping itself from confusing them — and it has to do that without a runtime type tag on the hot path, because the hot path runs per token. That is the job of the buffer module, and it is small enough to read in one sitting. Three concrete buffer types and one view type carry the whole engine:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/metal/buffer.rs:28
#[derive(Clone)]
pub struct GpuBuffer {
inner: Buffer,
len: usize,
}
/// Shared Metal storage whose logical elements are IEEE-754 binary16 bits.
///
/// Keeping this distinct from [`GpuBuffer`] prevents an F16 KV plane from
/// being accidentally fingerprinted or indexed as an F32 activation buffer.
#[derive(Clone)]
pub struct GpuHalfBuffer {
inner: Buffer,
len: usize,
}
#[derive(Clone)]
pub struct GpuBytes {
inner: Buffer,
len: usize,
_mmap: Option<std::sync::Arc<memmap2::Mmap>>,
}
#[derive(Clone, Copy)]
pub struct GpuByteView<'a> {
buffer: &'a GpuBytes,
offset: usize,
len: usize,
}
}
Walk them:
GpuBuffer— f32 scratch: the residual stream, activations, logits.lencounts floats. CPU access goes through checkedas_slice/as_mut_sliceviews over the shared storage (buffer.rs:216-232).GpuHalfBuffer— f16 storage (binary16, the 16-bit “half” float; Ch 5 covers its bit layout). The doc comment says why it is a separate type and not a flag: keeping it distinct “prevents an F16 KV plane from being accidentally fingerprinted or indexed as an F32 activation buffer” (buffer.rs:34-37). A KV plane is one layer’s key/value cache buffer pair; every Metal KV plane is f16 on all lanes, and Ch 15 explains the layout.GpuBytes— raw bytes, and the only type that can carry an mmap._mmapholds the file mapping alive; the leading underscore suppresses a lint for a field that exists for its lifetime, not its value — drop the lastGpuBytes, theArccount hits zero, the mapping is released.GpuByteView— a borrowed slice of aGpuBytes:(buffer, offset, len), created by the checkedview(offset, len)method (buffer.rs:153-160, which refuses a view that would run past the end). This is the weight-tensor handle the kernels of Part IV receive.
If you know the ancestor Ferrite book: its GpuBuffer was a six-field
struct with a dtype, a view length, and an arena offset. Muser’s substrate
is deliberately flatter — three typed wrappers instead of one typed field,
and views only where views are needed (byte storage). It can afford to be:
the engine serves one model, so the dispatch code already knows every
buffer’s type statically, and a dtype field would only be describing at
runtime what the compiler could have enforced. That simplification was made
on purpose when the substrate was pulled across from the ancestor, and the
crate documentation records the “pull-and-simplify” provenance
[crates/muser-engine/src/lib.rs:163-171].
3.4 Zero-init is policy, not luck
What is in a buffer at the moment you receive it, and who is allowed to read it before anyone has written it? That sounds like a pedant’s question until you notice what the wrong answer looks like in an inference engine: not a crash, but a plausible token. Garbage that happens to be zero is indistinguishable from a real value, and a stale row in a KV cache is a sentence the model half-remembers from someone else’s conversation.
Metal’s new_buffer does not zero the memory it hands you; contents
are undefined. Inference is full of buffers that are partially written
and then fully read (reductions with guard lanes; ring buffers whose
tail rows are not yet meaningful), so Muser makes initialization explicit:
zerosallocates and CPU-memsets — the default for everything (GpuBytes::zerosatbuffer.rs:59-73,GpuBuffer::zerosat:182-196,GpuHalfBuffer::zerosat:238-244). The cost is paid once at allocation, never on the hot path.uninitializedexists onGpuHalfBufferonly, and its doc comment is a contract, not an invitation:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/metal/buffer.rs:246
/// Allocate without CPU-touching the backing bytes.
///
/// This is for multi-gigabyte KV planes ONLY. Their ring/logical
/// metadata (`MetalKvPlane::origin_logical`/`origin_physical`/`len`)
/// guarantees every row is written by a `store_kv_*` dispatch before any
/// row within `[origin, origin + len)` is ever read back, so the CPU
/// zero-fill `zeros()` performs is pure startup-time cost on an
/// allocation that can be many gigabytes. Every other caller must keep
/// using `zeros()` -- this path leaves stale bytes behind in release
/// builds.
///
/// In debug/test builds the bytes are poisoned (never plain zero)
/// instead of left stale, so a bug that lets a read reach a
/// not-yet-written row is conspicuous rather than silently reading
/// zeros; see `kv_uninitialized_write_then_read_round_trips` below.
pub fn uninitialized(context: &MetalContext, len: usize) -> Result<Self, MetalError> {
}
That last paragraph is the elegant part, and it is the answer to the
question this section opened with. In debug builds the unwritten rows are
filled with 0xDEAD (buffer.rs:270-275), so a contract violation reads
back conspicuously poisoned values instead of plausible zeros. Put the
other way round: the fast path is allowed to leave stale bytes behind
precisely because the slow path refuses to leave believable ones. A bug
that lets a read reach a not-yet-written row cannot hide as a slightly odd
number — it is loud, in exactly the builds where somebody is looking.
Where each path is used, precisely:
- Live session KV planes zero-fill by design. The session constructor
allocates through
MetalKvPlane::new→GpuHalfBuffer::zeros([crates/muser-engine/src/decode.rs:229-244]), with an explicit in-source justification atdecode.rs:1350-1352: “Zero-filled on purpose: wrapped SWA rows must never expose uninitialized storage during a sequence boundary transition.” - Detached remote-install generations use
uninitialized. When a kvpack tile arrives from the GB10 producer, a fresh plane is built withMetalKvPlane::uninitializedand every retained row is uploaded before the plane goes live ([crates/muser-engine/src/decode.rs:1862-1888]) — a bulk-write-then-publish pattern where the write-before-read guarantee is structural.
One correction while we are here, because we nearly walked into it
ourselves. Writing this section we started where you would start — from the
engineering doc — and docs/memory-footprint.md says flatly that “Metal KV
buffers are allocated without a CPU memset.” Taken at face value that
inverts everything above: it makes the fast path the default and the
zero-fill the exception, and this section would have been a footnote.
The two constructors above say otherwise, and an audit had already caught
the same discrepancy. The doc sentence is wrong for live planes —
those zero-fill, as above — and right only about the detached
remote-install generations. We kept the receipt for the correction
[docs/kvpack-merge-handoff.md §3 D2, the 2026-08-20 audit]. The book
inherits the fix, not the error; the general lesson is that source outranks
prose about source, including prose written by the same people.
3.5 Page alignment: the 16 KB contract
Before the mmap story, one piece of arithmetic — the kind that works for
years and then doesn’t. The question is narrow, and it is the one to ask
whenever you hand a driver a pointer to memory you did not allocate: what
exactly are you promising it about that memory? Apple Silicon uses a
16 KB virtual-memory page (not the 4 KB you may know from x86). The
Metal call that wraps external memory — new_buffer_with_bytes_no_copy —
requires a page-aligned pointer and a page-aligned length. A file’s byte
length is not, in general, a multiple of 16,384.
Muser handles this by rounding the Metal-facing length up to the page boundary while keeping the logical length exact — and the code is unusually careful about why that is safe:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/metal/buffer.rs:91
pub fn from_mmap(
context: &MetalContext,
mmap: std::sync::Arc<memmap2::Mmap>,
) -> Result<Self, MetalError> {
// Metal documents that `newBufferWithBytesNoCopy:length:options:
// deallocator:` requires a page-aligned length (the pointer is
// already page-aligned -- POSIX `mmap` always returns one). The raw
// file length has "worked" here only because `mmap` itself reserves
// whole pages under the hood and zero-fills the tail of the last
// one -- that's an undocumented tolerance, not a guarantee, so round
// the length Metal sees up to the page boundary explicitly. Bytes
// between the real file length and that boundary are the kernel's
// own zero-filled mmap tail, so this never reads outside the
// mapping. `GpuBytes::len()` keeps reporting the exact, unrounded
// file length; only the Metal-facing allocation grows.
let mmap_len = mmap.len();
let rounded_len = if mmap_len == 0 {
0
} else {
let page = page_size();
mmap_len
.checked_add(page - 1)
.map(|padded| padded / page * page)
.ok_or(MetalError::Allocation(mmap_len))?
};
let inner = context.device.new_buffer_with_bytes_no_copy(
mmap.as_ptr() as *const std::ffi::c_void,
rounded_len as u64,
shared_tracked(),
None,
);
// … (allocation check elided: see buffer.rs:122-124) …
Ok(Self {
inner,
len: mmap_len,
_mmap: Some(mmap),
})
}
}
(the allocation-size check at buffer.rs:122-124 is elided; everything
else is verbatim.)
That paragraph of comment is doing real epistemic work, and it is worth
slowing down for, because the bug it fixes had never once fired. Passing
the raw file length straight to Metal worked. It worked because mmap
reserves whole pages under the hood and zero-fills the tail of the last
one, so the bytes past the file’s end were always mapped and always zero —
and no test we could write would tell that apart from being right. The comment
separates the two anyway: what Metal guarantees (a page-aligned length is
required) from what merely happened to work (“an undocumented tolerance,
not a guarantee”). Then the code goes and obeys the documented contract.
Nothing was failing when that change was made; the point is that the
alternative was a load path resting on an OS behaviour nobody ever promised
us, and a future macOS is under no obligation to keep providing it.
Two details finish the section. A unit test pins the rounding down
(from_mmap_rounds_metal_length_up_to_the_page_boundary,
buffer.rs:347-376): a 10-byte file yields len() == 10 and a
Metal-facing length that is a page multiple — the logical length stays
exact, only the allocation grows. And the page size itself comes
from POSIX getpagesize() via a one-line extern "C" — avoiding a libc
dependency for one constant (buffer.rs:16-26).
Why does the pointer never need rounding here, where the ancestor engine had to round tensor offsets down to page boundaries? Because of what gets wrapped — the next section.
3.6 Zero-copy at 16.76 GB scale: mmap → one buffer → offset views
This is the payoff, end to end — the section where “the CPU and the GPU share memory” stops being an architecture diagram and becomes a load path that copies nothing. Hold the question a discrete-GPU engineer would ask first while you read it: when do the weights get uploaded? The answer is that there is no upload, and three code locations are enough to show why.
1. The engine mmaps the whole GGUF once. Loading does not read the weights into RAM; it maps the file and records where each tensor lives:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/weights.rs:171
pub fn open(path: &Path, gguf: &GgufFile) -> Result<Self, MuseConfigError> {
let file = File::open(path)
.map_err(|e| MuseConfigError::Geometry(format!("open {}: {e}", path.display())))?;
// SAFETY: the checkpoint is a read-only immutable input for the
// lifetime of this process; we never write through the mapping.
let mmap = unsafe { Mmap::map(&file) }
.map_err(|e| MuseConfigError::Geometry(format!("mmap {}: {e}", path.display())))?;
let mut index = HashMap::with_capacity(gguf.tensors.len());
for t in &gguf.tensors {
let start = (gguf.data_offset + t.offset) as usize;
let n_elem: usize = t.shape.iter().product::<u64>() as usize;
let be = t.dtype.block_elements();
let len = n_elem.div_ceil(be) * t.dtype.block_size();
if start + len > mmap.len() {
return Err(MuseConfigError::Geometry(format!(
"tensor {} runs past end of file ({} + {} > {})",
t.name,
start,
len,
mmap.len()
)));
}
index.insert(t.name.clone(), (start, len, t.dtype, t.shape.clone()));
}
}
The index maps every tensor name to (file_offset, byte_len, dtype, shape) — with a fail-closed bounds check per tensor. The CPU reference
path reads weights straight out of this mapping via TensorView
(weights.rs:40-48: raw bytes plus geometry, “weights are never
materialized as f32” per the module doc).
2. The Metal driver wraps that mapping in one MTLBuffer. The decode
path takes the same Arc<Mmap> and hands it to GpuBytes::from_mmap from
§3.5 — the entire 16.76 GB file becomes one Metal buffer, zero bytes
copied:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/decode.rs:1199
let context = MetalContext::new()?;
let kernels = MetalKernels::new(&context)?;
let mapped_weights = GpuBytes::from_mmap(&context, weights.mapped_file())?;
let residency_set = crate::metal::residency::create_and_attach(
&context.device,
&context.queue,
&[mapped_weights.metal()],
);
}
3. Every weight tensor is a checked offset view of that one buffer.
Each projection remembers only its GGUF layout (TensorLayout, parsed at
load, weights.rs:350-373); at dispatch time it asks the arena for a
slice:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/decode.rs:148
fn view<'a>(&self, mapped: &'a GpuBytes) -> GpuByteView<'a> {
mapped
.view(self.layout.file_offset, self.layout.byte_len)
.unwrap_or_else(|| panic!("validated GGUF tensor {} left mapped file", self.name))
}
}
And the bind — the line Ch 2 §2.8 promised to explain — hands the GPU “which buffer” and “where in it” in a single call:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/metal/encode/multicol.rs:192
encoder.set_buffer(0, Some(weights.metal()), weights.offset() as u64);
}
Put the three together and the discrete-GPU question dissolves rather than gets answered. There is no upload step because there is nowhere to upload to: the file’s pages, the Metal buffer, and the bytes a kernel dereferences are one region of physical memory wearing three names. The figure below is the entire load path — a mapping, a wrapper around it, and arithmetic.
GGUF file on disk (16,756,681,056 B)
┌──────────────────────────────────────────────────────────────────────┐
│ [header] blk.0.attn_q blk.0.attn_k … blk.51.ffn_down output (LM) │
└───────────────────────────────┬──────────────────────────────────────┘
│ mmap() (one Arc<Mmap>, read-only)
▼
unified memory: ONE MTLBuffer over the whole mapping (no copy, ever)
┌──────────────────────────────────────────────────────────────────────┐
│ file start +q_off +k_off … file end │
│ ├ q.weight ──┤ ├ k.weight ─┤ ………………………………………… ├ output.weight ──┤│
└─▲──────────────▲──────────────────────────────────────────▲─────────┘
│ │ │
GpuByteView{ GpuByteView{ GpuByteView{
offset:q_off, offset:k_off, offset:out_off,
len:q_len, len:k_len, len:out_len,
buffer:arena} buffer:arena} buffer:arena}
└─── all views share the SAME MTLBuffer; set_buffer passes the offset ─┘
Figure 3.2: One mmap → one MTLBuffer → one checked offset view per tensor
(offsets symbolic; each is the tensor’s TensorLayout.file_offset from the
GGUF index). No weight byte is copied at load or at dispatch; pages are
demand-fetched by the OS the first time the GPU reads them.
Why one giant buffer plus views, instead of one MTLBuffer per tensor
(hundreds of them)? Each distinct buffer costs Metal bookkeeping, a
virtual-memory mapping, and TLB pressure (the TLB —
translation-lookaside buffer — is the small cache of virtual→physical page
translations; each buffer consumes entries). With one arena there is
exactly one mapping, and the per-tensor “allocation” is a 24-byte struct.
The ancestor book demonstrated the same trade on its engine’s arena
[ferrite-book Ch 3]; Muser’s version is the same idea expressed through
GpuByteView over GpuBytes. We are not alone in the choice: llama.cpp’s
Metal backend maps the whole file and slices it the same way. That
corroboration reaches us second-hand, through the ancestor book’s audit of
ggml_metal_buffer_map [ferrite-book Ch 3] — this book has not re-read
llama.cpp’s source, so we mark it lineage rather than verification.
Keeping 16 GB resident: the residency set
One more substrate piece, and it exists only because of the scale — at a
few hundred megabytes nobody would bother. The mapped arena is
bound by every projection in every command buffer — per token. Rather
than let Metal redo residency bookkeeping for a 16+ GiB allocation each
time, Muser attaches it to an MTLResidencySet once at load:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/metal/residency.rs:1
//! Minimal `MTLResidencySet` owner extracted from Ferrite's Metal substrate.
//!
//! The immutable GGUF arena is bound by every projection in every command
//! buffer. Attaching it once lets Metal skip repeating residency work for the
//! 16+ GiB allocation on every token. The Objective-C surface is public on
//! macOS 15+, and absence fails open to the ordinary Metal residency path.
}
create_and_attach (residency.rs:65-108) builds the set with raw
objc::msg_send! calls — the metal crate does not wrap this API — adds
the arena’s buffer, commits, requests residency, and attaches the set to
the queue. On any macOS release without the API it returns None and the
engine proceeds on Metal’s ordinary residency path. That distinction
matters more than it looks: this is an optimization that fails open.
Lose it and tokens still come out, correct, with Metal doing the residency
bookkeeping the long way — which is exactly the opposite of the untracked
gamble earlier in this chapter, where dropping the safety net changed
behaviour silently. Both are engine paths that may not be taken; only one
of them is permitted to be quiet about it.
3.7 What 96 GB buys, revisited from the buffer side
Ch 1 §1.5 budgeted the 96 GB by artifact — how much each thing takes. Now that you know what a buffer actually is, the same budget answers a sharper question: which of these bytes does the engine own, and which is it merely borrowing from the operating system? From the substrate’s point of view the same budget reads:
- Weights are not “used” memory in the ordinary sense — they are a
read-only file mapping, page-cache backed
[docs/memory-footprint.md §Other material allocations]. The OS can evict clean pages under pressure and re-fault them on next access. What the residency set adds is a request that the pages stay resident while serving. - Everything the engine allocates itself — activations, logits,
workspaces, the f16 KV planes, staging shadows — is a
StorageModeSharedbuffer from §3.3. The KV planes dominate: 1.827 GB per slot at full 131,072 context, ×4 slots = 7.306 GB[docs/memory-footprint.md]. - One arena serves all slots. The
MetalShareddesign ([crates/muser-engine/src/decode.rs:954-957]) exists because unified memory makes it legal: four serving sequences share one context, one pipeline set, one mapped arena. On a discrete-GPU machine the same sharing would be possible but the copy story would not — each GPU residency of the weights would be a distinct 16.76 GB VRAM occupation.
3.8 Tradeoffs
Tracked storage vs the untracked gamble. The story is in §3.2’s lineage
box; here is what it costs. Muser pays for Metal’s dependency tracker on
every encode, plus the explicit barrier groups, and gets back a safety net
that the one experiment we ran showed it needed: the engine-wide untracked
build (b9678d4) “empirically changed DFlash conditioning while leaving
final greedy IDs unchanged” [crates/muser-engine/src/metal/buffer.rs:7-14].
Now notice what that sentence does not say. It does not say the untracked
path was slower, or faster — no Muser document isolates the
tracked-vs-untracked encode cost on this machine [unverified]. The ruling
was made on correctness alone, and it would stand unchanged if untracked
turned out to measure quicker.
One arena + views vs one buffer per tensor. Views are cheap (24 bytes, one bounds check) and make the whole model one mapping — but they collapse identity: Metal sees a single buffer, so its tracker cannot distinguish a write to one tensor from a write to another. That is exactly why the weights are views (immutable after load; a write would be a bug worth a crash) while activations and KV planes get their own typed buffers with distinct identity. The split is the design: share what is immutable, individualize what is written.
uninitialized KV planes: speed with a proof obligation. Skipping the
CPU memset on multi-gigabyte planes saves real startup time on allocations
that can be many gigabytes (buffer.rs:248-254) — in exchange, the ring
metadata must guarantee write-before-read for every row in
[origin, origin + len). Muser makes the obligation visible three ways:
the doc comment’s “ONLY”, the debug-build 0xDEAD poison, and the test
that demonstrates it (buffer.rs:328-344). That is the shape of the whole
bargain, and it generalizes past this buffer: when you take a shortcut
whose safety lives in some other module’s invariant, spend part of the
winnings making that invariant loud.
3.9 What’s next
You now know the entire memory story: one DRAM pool, StorageModeShared
everywhere, three typed buffer types plus checked byte views, zero-copy
mmap of the 16.76 GB GGUF into one Metal buffer, a residency set to keep it
warm, and an initialization policy that decides byte-for-byte what may be
read before it is written. One object from Ch 2
is still a black box: MetalContext.library — “a bundle of compiled kernel
functions, addressable by name.” How .metal source text becomes that
bundle, why Muser deliberately keeps three different kernel sources
(fast-math source, strict-f32 source, and a pinned llama.cpp metallib),
and how the engine makes a silent kernel fallback impossible — that is
Ch 4.
References
[crates/muser-engine/src/metal/buffer.rs:7-14]—shared_tracked()and theb9678d4untracked-mode incident comment;:28-56the four substrate types;:59-73, 182-196, 238-244the zero-initializing allocators;:246-277GpuHalfBuffer::uninitializedand the0xDEADpoison;:91-130from_mmapwith the page-rounding contract;:328-376the poison and page-rounding tests;:16-26getpagesize.[crates/muser-engine/src/weights.rs:163-218]—MuseWeights::open(whole-file mmap + per-tensor index with bounds checks);:40-48TensorView;:350-373TensorLayout;:375-378mapped_file().[crates/muser-engine/src/decode.rs:1195-1206]—load_shared: from_mmap + residency set;:148-161Projection::view/nvfp4_scale_view;:229-244, 246-261the twoMetalKvPlaneconstructors;:1350-1352the live-plane zero-fill rationale;:1862-1888the detached remote-install generation;:954-984MetalShared.[crates/muser-engine/src/metal/residency.rs:1-107]— theMTLResidencySetowner (rawmsg_send!, fails open below macOS 15).[crates/muser-engine/src/metal/encode/multicol.rs:192]— the real offset-viewset_bufferbind.[docs/memory-footprint.md]— artifact sizes, KV formula, the on-disk-vs-resident caveat.[docs/kvpack-merge-handoff.md §3 D2]— the 2026-08-20 audit correcting memory-footprint.md’s memset claim for live planes.[ferrite-book Ch 3]— the ancestor’s unified-memory chapter: the arena-view and zero-copy devices this chapter re-grounds; its untracked-hazards A/B (−2 to −4 % on A18-class hardware) and GpuHeap / packed-activations history are Ferrite-lineage.[Metal-PG]— Apple, Metal Programming Guide: “Resource Objects: Storage Modes,” “Tracking Resource Dependencies.”[Metal-SS]— Apple, Metal Shading Language Specification: “Address Spaces.”- glossary — terms introduced this chapter: unified memory, SoC, DRAM, VRAM, PCIe, storage mode, blit, mmap, page fault, zero-copy, page alignment, TLB, MTLBuffer, GpuBuffer, GpuHalfBuffer, GpuBytes, GpuByteView, residency set.
Chapter 4 — Pipeline state objects and the three kernel sources
status: polished · path: Muse Glimmer, pinned Muser tree
Prerequisites: Ch 2 and Ch 3. You know what
MetalContext’slibrary,cross_vendor_library, andggml_libraryfields are for, and what adispatch_thread_groupscall looks like.
4.1 Three stages between text and machine code
In Ch 2 we said the host API gives you “a
bundle of compiled kernel functions, addressable by name,” and deferred
how a .metal text file becomes that bundle. That deferral is now due,
and it turns out to matter for more than curiosity: Muser feeds three
different kernel sources into one dispatch path, and a benchmark that
cannot say which of the three ran is not a measurement of anything. So
this chapter goes in two movements — first the machinery, then the
discipline that keeps the machinery honest.
Start with the machinery. There are three stages between “text a human wrote” and “a kernel the GPU can run”:
.metalsource text — a C++ dialect a human writes. Pure text.MTLLibrary— a bundle of compiled functions: the text has been parsed, type-checked, and lowered to an intermediate representation. No machine code for any specific GPU exists yet. A.metallibfile is exactly this stage serialized to disk — a library you can load without ever running the frontend compiler.MTLComputePipelineState— the PSO — one function taken from the library and lowered all the way to machine code for this specific GPU. This PSO is the handle you hand toset_compute_pipeline_stateon an encoder.
Figure 4.1 shows how Muser’s three kernel sources feed that last stage.
flowchart LR
A["24 .metal files<br/>include_str! + concat!"] -->|"new_library_with_source<br/>fast-math ON"| B["library<br/>(serving kernels)"]
A2["muse_reference + nvfp4<br/>(same source)"] -->|"new_library_with_source<br/>fast-math OFF"| C["cross_vendor_library"]
D["pinned llama.cpp .metallib<br/>MUSER_GGML_METALLIB"] -->|"new_library_with_file"| E["ggml_library<br/>(optional)"]
B --> F["get_function(name,<br/>± function constants)"]
C --> F
E --> F
F --> G["new_compute_pipeline_state<br/>PSO per kernel"]
G --> H["MetalKernels registry<br/>(PsoCache + typed fields)"]
Figure 4.1: Muser’s kernel pipeline. Three sources feed one PSO-building
path; every kernel lands in the MetalKernels registry before any dispatch
happens.
Why split source-to-runnable into two compiles? The library stage is
frontend work, identical for every GPU model, done once for all kernels
together. The PSO stage is backend work, specific to this device’s
instruction set, done once per kernel [Metal-PG, "Functions"]. Splitting
them lets you pay each at a different time — and lets a prebuilt
.metallib skip the frontend entirely on the user’s machine.
One design fact up front, because it shapes everything below: Muser has
no build-time shader-compilation step. The engine’s module doc is
explicit — the substrate keeps “runtime shader compile (include_str! the
.metal sources in shaders/, newLibraryWithSource on first use, cache
PSOs — no Xcode step, pure-source checkout)”
([crates/muser-engine/src/lib.rs:163-171]). The ancestor Ferrite engine
precompiled a .metallib in build.rs and kept an on-disk
MTLBinaryArchive PSO cache [ferrite-book Ch 4]; Muser deliberately
dropped both machines’ worth of build plumbing — cold-start compile is
paid at engine init, once, and the PSO cache is in-process. The tradeoffs
section (§4.9) costs this out.
4.2 Source 1: the concatenated fast-math library
Begin with the library the engine actually serves tokens from. The question is a small one with a long shadow: what exactly gets compiled, under which compiler settings, and what could the order of the files possibly have to do with anything?
The main library is one giant source string built from 24 .metal files
with include_str! and concat!, compiled once at MetalContext::new():
#![allow(unused)]
fn main() {
// crates/muser-engine/src/metal/context.rs:48
let options = CompileOptions::new();
// Ferrite's accepted production kernels and llama.cpp both compile
// with fast math enabled. Exact Muse parity is guarded at the token
// boundary; disabling this here materially slows attention, FFN, and
// the norm/tiny-op stack without changing the imported GGML PSOs.
options.set_fast_math_enabled(true);
options.set_language_version(MTLLanguageVersion::V3_1);
// The fixed Muse driver is local, while the operation kernels below
// are clean extractions from Ferrite at a85048a90. Keeping the exact
// source files separate makes their provenance and future diffing
// auditable without bringing over Ferrite's runtime or route VM.
let source = concat!(
include_str!("../shaders/muse_reference.metal"),
"\n",
include_str!("../shaders/nvfp4.metal"),
"\n",
include_str!("../shaders/ferrite/sigmoid_gate.metal"),
"\n",
// … (twenty more ferrite/ files elided, in dependency order —
// see context.rs:66-106) …
include_str!("../shaders/ferrite/flash_attn_decode_reduce_v2.metal"),
);
let library = device
.new_library_with_source(source, &options)
.map_err(MetalError::ShaderCompile)?;
}
(the elided lines are consecutive include_str! entries; nothing else is
removed.)
Three choices are buried in those few lines, and each one is a place where the obvious alternative would have cost us something.
Fast math ON for the serving library. set_fast_math_enabled(true)
lets the compiler assume NaN/Inf never happen and reorder or fuse
floating-point operations. The comment records both the justification
(llama.cpp compiles the imported kernels with fast math; turning it off
“materially slows attention, FFN, and the norm/tiny-op stack”) and the
safety argument — “exact Muse parity is guarded at the token boundary,”
i.e., the exactness contract is enforced by comparing generated tokens
against the pinned comparator, not by hoping the arithmetic is bit-stable.
Fast math is not safe everywhere, though, and the ancestor found the edge
the hard way. Ferrite let the fast-math compiler near RoPE’s trigonometry,
where the rotation angle grows with position; at large angles the fast
approximations drift, and the repair was to force precise::cos and
precise::sin on exactly those calls [ferrite-book Ch 4 §10]. Muser
does not repeat the repair — it removes the hazard. The RoPE frequency
table is precomputed on the CPU at load
([crates/muser-engine/src/decode.rs:1256-1263]), so no powf and no
trig call survives into the kernel to be approximated in the first place.
The distinction is worth holding on to: fast math is cheap insurance on a
long chain of multiply-adds whose result you check at the token
boundary, and a liability wherever the absolute accuracy of a single
transcendental is the thing you depend on.
MSL V3.1, pinned. set_language_version(V3_1) fixes the dialect the
compiler accepts. A shader using a newer feature fails loudly at
library-build time instead of silently miscompiling.
Concatenation order is load-bearing. The .metal fragments share
types and helpers across files (the Q4_K block structs in matmul.metal,
the shared MAC helpers in _q4k_helpers.metal), and MSL here has no
#include resolution — the files are one translation unit precisely
because they are concatenated in dependency order. The provenance comment
is part of the design: which files are Muser-authored
(muse_reference.metal, nvfp4.metal) versus clean extractions from
Ferrite at a85048a90 is readable straight from the concat
([crates/muser-engine/src/metal/context.rs:55-58]), backed by the
per-file SHA-256 manifest in [docs/extraction-manifest.md].
4.3 Source 2: the strict-f32 cross-vendor library
Most engines have one shader library. Muser has a second copy of some of the same kernels, and the reason has nothing to do with this Mac — it has to do with a machine on the other end of a network cable. Watch for the inversion: here a compiler flag stops being a build setting and becomes part of an API.
The second library is the same two source files, recompiled with fast math off:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/metal/context.rs:111
let cross_vendor_options = CompileOptions::new();
cross_vendor_options.set_fast_math_enabled(false);
cross_vendor_options.set_language_version(MTLLanguageVersion::V3_1);
let cross_vendor_source = concat!(
include_str!("../shaders/muse_reference.metal"),
"\n",
include_str!("../shaders/nvfp4.metal"),
);
let cross_vendor_library = device
.new_library_with_source(cross_vendor_source, &cross_vendor_options)
.map_err(MetalError::ShaderCompile)?;
}
Why would an engine compile its own kernels twice with different compiler flags? Because of Part VI. When a remote NVIDIA GB10 producer computes KV (or a draft) and the Mac must reproduce or verify it, the arithmetic must match CUDA’s explicit scalar boundaries — and a fast-math compiler is free to fuse and reassociate exactly where CUDA’s kernel did not. The struct field’s doc comment states the contract:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/metal/context.rs:36
/// Strict-f32 copy of the standalone Muse kernels. The cross-vendor
/// Q8 projection and integer NVFP4 routes must match CUDA's explicit
/// scalar boundaries, while the ordinary serving kernels retain fast math.
}
The strict copies are selected per route, not globally: dispatch wrappers
check MUSER_CROSS_VENDOR_QK and swap in the strict pipeline for exactly
the ops that must match the producer (e.g.
[crates/muser-engine/src/metal/encode/gate.rs:14-18],
[crates/muser-engine/src/metal/encode/norm.rs:257-260]). One source, two
compilation contracts, selected at dispatch — the compiler flag becomes
part of the numerical API. Put it the other way round, because this is
the part that trips people up: a fast-math setting is normally something
you choose once and never think about again, buried in a build file. Here
it is a value the dispatch code reads at encode time, as consequential to
the result as a dtype. Ch 32 is
where this discipline earns its keep.
4.4 Source 3: the pinned llama.cpp metallib
The third source answers a question the first two cannot. What do you do about a kernel whose output you must reproduce exactly, when the thing you have to match is somebody else’s compiled binary? Rewriting it in your own dialect gets you close, and close is the one answer this engine cannot use. So Muser does not rewrite it at all.
Which is why the third source is not Muser source at all. When MUSER_GGML_METALLIB
points at a prebuilt llama.cpp .metallib, the device loads it as a
library:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/metal/context.rs:122
let ggml_library_path = std::env::var_os("MUSER_GGML_METALLIB").map(PathBuf::from);
let ggml_library = match ggml_library_path.as_ref() {
Some(path) => Some(device.new_library_with_file(path).map_err(|message| {
MetalError::GgmlLibrary {
path: path.clone(),
message,
}
})?),
None => None,
};
}
This library supplies llama.cpp’s own kernels — kernel_mul_mv_q{4,5,6}_K_f32
matvecs, kernel_mul_mm_* batch matmuls, kernel_rms_norm_mul_f32_4,
kernel_rope_norm_f32, the whole flash_attn_ext family — which Muser
dispatches instead of re-expressing them. The reasoning is recorded in
the registry:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/metal/encode.rs:278
ggml_q4k: ggml_matvec_pipeline(context, "kernel_mul_mv_q4_K_f32")?,
ggml_q5k: ggml_matvec_pipeline(context, "kernel_mul_mv_q5_K_f32")?,
ggml_q6k: ggml_matvec_pipeline(context, "kernel_mul_mv_q6_K_f32")?,
// … (matmul / norm / rope / flash-attn families elided — encode.rs:281-293) …
// crates/muser-engine/src/metal/encode.rs:370
pub(crate) fn supports_projection(&self, dtype: crate::gguf::GgmlType) -> bool {
match dtype {
// Standalone fallbacks cover Q4_K and Q5_K for both decode and
// batch prefill. Q6_K intentionally uses the pinned upstream
// llama kernels so its math and dispatch remain comparator-exact.
crate::gguf::GgmlType::Q4_K | crate::gguf::GgmlType::Q5_K => true,
crate::gguf::GgmlType::Q6_K => {
self.ggml_q6k.is_some()
&& self.ggml_q6k_mm_aligned.is_some()
&& self.ggml_q6k_mm_bounds.is_some()
}
// …
}
}
}
Two different policies live in that one match. Q4_K and Q5_K have Muser-authored standalone kernels — the metallib versions are preferred (for parity) but the engine can run without them. Q6_K has no fallback: it runs on llama’s kernels or it does not run. If a GGUF carries a Q6_K projection and the metallib is missing, model load aborts with a specific, actionable error:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/decode.rs:113
#[error(
"tensor {name} uses {dtype:?}, which requires the pinned llama.cpp Metal library; set MUSER_GGML_METALLIB"
)]
MissingProjectionKernel { name: String, dtype: GgmlType },
}
raised per-tensor at load ([crates/muser-engine/src/decode.rs:1294-1310]).
This is a fail-closed no-fallback policy: the engine refuses to
silently substitute different arithmetic for a dtype whose exactness is
contractual.
None of that strictness means anything if the metallib is itself a
mystery binary — pinning your arithmetic to a file whose origin nobody
can state is not pinning at all. So the build of that file is the
strictest step in the whole chain. The metallib itself is built by
[scripts/compile_llama_metallib.sh], and the script is almost a
manifesto of provenance discipline: it refuses to run unless the llama.cpp
checkout’s HEAD equals the requested revision and the three Metal source
files are clean in index and working tree (:57-69); it refuses to
replace an existing output or receipt, making artifacts append-only
(:78-85); and it writes a muser.llama_metallib.source_receipt.v1 JSON
binding the binary’s SHA-256 and size to the source commit, the source
tree hash, per-file SHA-256s, the merged-source hash, the SDK version,
the Metal compiler, and the Xcode version (:129-180). Two refusals and
a receipt: the artifact cannot come from a dirty tree, cannot be quietly
replaced by a newer one wearing the same name, and cannot be used without
a record of which commit and which toolchain produced it.
4.5 From functions to PSOs: the registry and the cache
A library is not runnable; the last stage turns named functions into machine code for this GPU. The interesting question about any cache is what it does when it misses, and this one answers it in a way most caches would not dare to.
Once the libraries exist, every kernel is compiled to a PSO exactly once,
at MetalKernels::new. The fixed serving set is a compile-time-checked
list of 66 names:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/metal/encode.rs:21
const PIPELINES: [&str; 66] = [
"rms_norm_batch",
"fused_rms_norm_residual_add_batch",
"muser_fused_norm_residual_rms_norm_batch_dual_eps",
"muser_fused_norm_residual_rms_norm_32sg",
"sigmoid_gate_inplace",
// … (61 more names elided — encode.rs:27-88) …
"muser_embedding_f16",
];
}
and the cache that builds them is 40 lines:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/metal/pso_cache.rs:9
pub struct PsoCache {
states: HashMap<&'static str, ComputePipelineState>,
}
impl PsoCache {
pub fn new(
context: &MetalContext,
names: impl IntoIterator<Item = &'static str>,
) -> Result<Self, MetalError> {
let mut states = HashMap::new();
for name in names {
// Metal requires functions that declare function constants to be
// obtained through the constant-values API even when every value
// intentionally remains undefined and the shader uses its default
// branch. This mirrors Ferrite's `make_fc_default` constructor.
let constants =
matches!(name, "ffn_q4k_gate_up_silu_4r2s").then(FunctionConstantValues::new);
let function = context
.library
.get_function(name, constants)
// … (error mapping elided) …
let state = context
.device
.new_compute_pipeline_state_with_function(&function)
// … (error mapping elided) …
states.insert(name, state);
}
Ok(Self { states })
}
pub fn get(&self, name: &'static str) -> &ComputePipelineStateRef {
self.states
.get(name)
.unwrap_or_else(|| panic!("unregistered Muse Metal pipeline {name}"))
}
}
}
Three decisions are visible in those forty lines. The first one costs
time: the cache is in-process only — there is no on-disk
MTLBinaryArchive, so every process start recompiles all 66 PSOs plus
the specialized families below. The second buys safety at the price of
politeness. The panic on a miss is the policy: a typo’d kernel name
takes the process down at first use, loudly, rather than resolving to
something else and dispatching it. In an engine whose correctness gate is
“same logits as the comparator,” running the wrong kernel successfully is
the failure you least want to survive. The third decision is the comment
about function constants, and it is the whole of the next section.
Beyond the 66, MetalKernels holds typed fields for everything optional
or specialized: the cross-vendor PSOs (one field each,
[crates/muser-engine/src/metal/encode.rs:92-116]), the Option<...>
ggml/llama families (:117-132), and the Ferrite f16 attention
specializations (:135-138). Optionality is visible in the type — an
absent metallib is None, not a missing string in a map.
4.6 Function constants: one source, many specialized kernels
A function constant is a value a
shader declares with [[ function_constant(N) ]] and the host supplies at
PSO-build time. The compiler then specializes: with the value known, it
unrolls loops, eliminates dead branches, and folds the constant into
machine code. Muser uses this everywhere the imported llama.cpp kernels
demand it, because llama.cpp’s own kernels are written as one source with
dozens of specialization points.
The pattern, on the Ferrite f16 attention family:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/metal/encode.rs:798
fn ferrite_f16_pipeline(
context: &MetalContext,
name: &str,
nsg: u32,
) -> Result<ComputePipelineState, MetalError> {
let constants = FunctionConstantValues::new();
let head_dim = 128u32;
let decode_params = false;
constants.set_constant_value_at_index(
&head_dim as *const u32 as *const std::ffi::c_void,
MTLDataType::UInt,
40,
);
constants.set_constant_value_at_index(
&decode_params as *const bool as *const std::ffi::c_void,
MTLDataType::Bool,
92,
);
constants.set_constant_value_at_index(
&nsg as *const u32 as *const std::ffi::c_void,
MTLDataType::UInt,
98,
);
let function = context
.library
.get_function(name, Some(constants))
// …
}
Call it three times with nsg = 1, 2, 4 and you get three PSOs of the
same source — which is exactly what MetalKernels::new does
([crates/muser-engine/src/metal/encode.rs:298-313]), mirroring llama.cpp’s
own “launch 32 workgroups and grow simdgroups” dispatch table noted at
[crates/muser-engine/src/decode.rs:41-46].
The llama kernels go further. The matvec constructor pins four slots:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/metal/encode.rs:844
let constants = FunctionConstantValues::new();
for (value, index) in [(2i16, 600u64), (1, 602), (1, 603), (1, 604)] {
constants.set_constant_value_at_index(
&value as *const i16 as *const std::ffi::c_void,
MTLDataType::Short,
index,
);
}
}
and every specialized constructor carries a label that states the specialization, which becomes the error string if the function is absent from the metallib:
kernel_flash_attn_ext_vec_f16_dk128_dv128[ns=128,nsg=1,kvpad=false,mask=true]
kernel_flash_attn_ext_f16_dk128_dv128[mask=true,kvpad=false,ns=128,nsg=4]
kernel_flash_attn_ext_blk[nqptg=8,ncpsg=32]
kernel_mul_mv_ext_q4_K_f32_r1_2[nsg=2,nxpsg=8,ne12=1,r2=1,r3=1]
(the four labels as constructed at
[crates/muser-engine/src/metal/encode.rs:985, 1116, 1123, 1192])
That label convention is doing double duty: it is a debug message, and it
is a fingerprint — §4.7 makes that load-bearing. The slot numbers are
llama.cpp’s own function-constant indices (600+ for mul_mv, 700+ for
mul_mm, 800+ for RoPE, 1000+ for the flash-attn family), so the Rust
side reads as a table of the upstream kernel ABI. The mul_mv_ext group
even documents why it exists: “llama.cpp’s source-pinned small-batch
K-quant projection pipelines… Upstream changes from repeated mul_mv to
these mul_mv_ext kernels at batch size four. Keeping that dispatch
boundary is required for numerical API parity as well as performance
parity” ([crates/muser-engine/src/metal/encode.rs:169-178]).
One family sits outside the concat, and we got it wrong on the first
pass. The detour is worth taking, because the mistake is the kind this
book exists to prevent.
The multi-column matvec family (matvec_multicol.metal) is compiled by
its own new_library_with_source call rather than living in the main
source string. Its module doc at multicol.rs:12-14 states plainly that
“nothing here is compiled unless MUSER_MULTI_COL_VERIFY is set”, and we
took that at face value — an experimental family behind an env var is
exactly what you would expect, and it meant this whole group could be
left out of the startup accounting.
Then we read the constructor, and it does not agree with the doc. The
multicol builder is called unconditionally, because the exact
multi-sequence decode route is always on; only the experimental DFlash
verify route is still gated by that variable
([crates/muser-engine/src/metal/encode.rs:294-297],
[crates/muser-engine/src/metal/encode/multicol.rs:90-132]). The comment
is simply stale. The lesson is the cheap one to learn here rather than
downstream: a module doc describes an intent at the moment it was
written, and the constructor describes what your process does tonight.
The code wins, as ever in this book — and the multicol pipelines are
counted in the cold-start tally of the next section, which they would not
have been if we had trusted the prose.
4.7 Fingerprinted selection: making a silent fallback impossible
Here is the chapter’s real subject, and the question the machinery above exists to answer: when you run the engine, how do you know which kernels ran? An engine with three kernel sources has four ways to not use the kernel you think it is using: the env var is unset, the metallib failed to load, the flag is inert, the fallback route took over. None of those four announces itself. Each silently changes the arithmetic — and therefore the numbers — of every benchmark that follows, which means a performance result and a correctness result can both be true of a route nobody intended to measure. Muser’s answer is layered.
Layer 1 — absence is loud where absence matters. We have already met
the sharpest version of this in §4.4: a Q6_K projection with no metallib
does not fall back, it aborts the load with MissingProjectionKernel.
Where a fallback is permitted, the same honesty moves into the test
suite. A test that needs the pinned kernels does not quietly pass on the
fallback path and let you read its green tick as evidence about llama’s
kernels; it says "skipping: MUSER_GGML_METALLIB is unset" and declines
to run ([crates/muser-engine/src/metal/encode/multicol.rs:462]).
Layer 2 — the record states what ran, by hashing it. The benchmark harness resolves its actual route and records a SHA-256 of the metallib that actually loaded — a resolved signal, not an echo of the environment:
#![allow(unused)]
fn main() {
// crates/muser-bench/src/main.rs:317
let Some(path) = std::env::var_os("MUSER_GGML_METALLIB").map(PathBuf::from) else {
return Ok(RouteIdentity {
matvec_route: "muser-local-q4k-q5k",
// … (other route fields elided) …
ggml_metallib_sha256: None,
});
};
let bytes = std::fs::read(&path).map_err(|error| {
format!(
"cannot fingerprint GGML metallib {}: {error}",
path.display()
)
})?;
let digest = Sha256::digest(bytes);
Ok(RouteIdentity {
matvec_route: "llama-ggml-metallib",
// …
}
matvec_route is derived from whether the library loaded, and
ggml_metallib_sha256 binds the run to the exact artifact bytes. A
qualifier binary goes further and refuses ambiguity: if
MUSER_GGML_METALLIB is already set but differs from the --ggml-metallib
argument, it errors out rather than running with either
([crates/muser-bench/src/composite_dflash.rs:246-251]).
Layer 3 — artifacts are append-only and receipted. A hash of the
loaded file only helps if that file has a history, which is the job of
the build script from §4.4: it will not overwrite an existing output, and
it emits the source receipt that binds the binary to its commit, its
tree, its per-file hashes, and its toolchain. That receipt is not just
for whoever built the file. Its path travels — the node-onboarding
orchestrator accepts it via MUSER_GGML_METALLIB_RECEIPT
([crates/muser-server/src/node/mod.rs:80-83]), so even the remote-lane
qualification knows which provenance it measured.
Lineage — the hole that made this discipline necessary. The ancestor Ferrite engine had an optional llama.cpp metallib bridge, and when the env var was unset it “falls back to JIT with a warning”
[ferrite-book Ch 4 §4.4]— a silent fallback in practice, because nothing in the benchmark record said which path had run. Its correction register records the confirmed defect: “silent metallib fallback can time the ~12 %-slower native path unknowingly” (D06-3, this book’s_research/ferrite-port-map-2.mddigest of the ancestor’s CORRECTIONS register; the ~12 % figure is a Ferrite-source-comment number, Ferrite-lineage). The fix there was a fingerprint line printing resolved signals (lib=metallib+ggml-bridge), and the same fix caught a second hole — an inert flag that changed nothing while appearing to[ferrite-book Ch 4 §4.4.4]. Muser’s Layer-2 hashing and Layer-1 refusals are that lesson, built in from day one: a benchmark that cannot state which kernels ran is not evidence.
4.8 What it costs to start the engine
Every choice in this chapter — compile at runtime, keep two contracts of the same kernels, load a third library from disk — is paid in the same currency, at the same moment: process start, while somebody waits. So what does the reader’s engine actually do before the first token appears?
At MetalMuseModel load, the
engine compiles: the 24-file fast-math library, the 2-file strict-f32
library, optionally loads the metallib, then builds 159 PSOs — 66
registry PSOs, 25 cross-vendor PSOs, 55 ggml/llama family PSOs (matvecs,
matmuls, mul_mv_ext, and the flash-attention specializations), 9
multicol PSOs, and 4 Ferrite f16 PSOs, counted from the constructor at
[crates/muser-engine/src/metal/encode.rs:202-315]. All of that happens
before any weight page is touched
(Ch 3’s mmap is demand-paged behind
this) — the model file is barely being read while the compiler works.
We went looking for the wall-clock figure that belongs at the end of that
paragraph, and it does not exist: no Muser document measures the total
cold-start compile time [unverified]. The absence is worth naming rather
than papering over, because it is where this chapter’s evidence runs out.
What is recorded is the bet — that a one-time compile at init is
simpler than shipping and versioning a build artifact — in the module
doc’s own words, “no Xcode step, pure-source checkout”
([crates/muser-engine/src/lib.rs:163-171]). The book can tell you the
bet was taken deliberately; it cannot yet tell you the premium.
4.9 Tradeoffs
Runtime JIT vs prebuilt metallib (vs the ancestor’s binary archive).
Muser compiles its own kernels at every start; the ancestor precompiled at
build time and cached PSOs on disk [ferrite-book Ch 4]. Muser’s win is
provenance simplicity: the kernel source is the shipped artifact, and
there is no .metalbin to go stale or drift from the binary. The cost is
the per-start compile of the 159 PSOs counted in §4.8 — unmeasured in any
Muser doc [unverified], paid once per process. Note the asymmetry with the llama kernels: for
those, Muser does ship a prebuilt metallib — because there the goal is
not convenience but bit-parity with a pinned upstream build, which a
local recompile could not guarantee (the source-receipt toolchain of
§4.4 exists to make that guarantee auditable).
Fast-math and strict-f32 side by side. Compiling
muse_reference + nvfp4 twice doubles the frontend work for those two
files and splits the kernel namespace across two libraries. The payoff is
that numerical parity with the CUDA producer becomes a compilation flag
selected per dispatch, not a rewrite. The alternative — one strict
library everywhere — was measured by implication in the source comment:
disabling fast math “materially slows attention, FFN, and the
norm/tiny-op stack” ([crates/muser-engine/src/metal/context.rs:49-52]);
the exact percentage is not recorded [unverified]. The deeper alternative
is tempting enough to name plainly: trust fast-math everywhere and rely
on token-level tolerance for cross-vendor checks, and the second library
disappears entirely. Ch 32 is the
chapter that closes that door. During the wizard’s arithmetic-ABI chase,
one f16 ULP in a layer-1 V tile was enough to cascade into 51.7 M
differing logits [ledger §2b, attempts 10–31]. A tolerance wide enough
to absorb a divergence like that is a tolerance wide enough to absorb a
bug, which is the same as having no gate at all.
Pinning llama’s kernels vs re-expressing them. Muser could have
rewritten the Q6_K matvec or the flash-attention family in its own source
and dropped the metallib dependency. The registry comment gives the
reason it did not: Q6_K “intentionally uses the pinned upstream llama
kernels so its math and dispatch remain comparator-exact”
([crates/muser-engine/src/metal/encode.rs:370-375]). Re-expressed
kernels agree only to ULP — the multi-column family documents its own
Q6_K case, where the separately compiled body “differs by a few ULP” and
is therefore excluded from the bitwise-exact route
([crates/muser-engine/src/metal/encode/multicol.rs:208-211]). When your
correctness gate is “same logits as the comparator,” ULP is not a detail;
it is the whole game — so the engine pins the comparator’s own machine
code.
4.10 What comes next
You now know the complete Metal substrate: how work is submitted (Ch 2), where memory lives (Ch 3), and how source text becomes the three kernel libraries whose selection is fingerprinted and fail-closed (this chapter). Part I is done — you can now read every shader and every dispatch in this book.
Kernels are language. The thing they read is the problem: 27.85 billion parameters that must fit in 16,756,681,056 bytes and still produce exact arithmetic. How four-ish bits per weight can carry a 30B model, what a block scale buys, and why NVFP4 and kquant land at parity — that is Part II, and it starts with Ch 5.
References
[crates/muser-engine/src/metal/context.rs:32-42]—MetalContextwith the three libraries;:46-110device/queue + the 24-file fast-math concat;:111-121the strict-f32 recompile;:122-131metallib loading;:149-211the deadline-bounded wait (Ch 2).[crates/muser-engine/src/lib.rs:163-171]— the “no Xcode step, pure-source checkout” substrate design note.[crates/muser-engine/src/metal/encode.rs:21-88]— the 66-namePIPELINESregistry;:90-139MetalKernels’ typed fields;:202-315the constructor wiring all three sources;:278-293ggml pipeline construction;:370-385supports_projection(the Q6_K no-fallback policy);:798-835ferrite_f16_pipeline(function-constant slots 40/92/98);:837-866ggml_matvec_pipeline(slots 600+);:957-1003themul_mv_extgroup andFC_MUL_MV;:1079-1210the flash-attn families with labeled specializations.[crates/muser-engine/src/metal/pso_cache.rs:9-50]— the in-process PSO cache and its panic-on-miss accessor.[crates/muser-engine/src/metal/encode/multicol.rs:90-132, 208-211, 458-464]— the lazy multi-column library, the Q6_K ULP note, and the honest skip.[crates/muser-engine/src/decode.rs:41-46]— llama’s nwg=32/nsg-growth dispatch note;:113-116, 1294-1310—MissingProjectionKernel;:1256-1263— CPU-built RoPE frequency table.[scripts/compile_llama_metallib.sh]— revision pinning, clean-tree checks, append-only outputs, thesource_receipt.v1schema.[crates/muser-bench/src/main.rs:304-341]—route_identity: resolved route + metallib SHA-256 fingerprint.[crates/muser-bench/src/composite_dflash.rs:246-251]— the env-vs-arg ambiguity refusal.[crates/muser-server/src/node/mod.rs:80-83]—MUSER_GGML_METALLIB/MUSER_GGML_METALLIB_RECEIPTplumbing in node onboarding.[docs/extraction-manifest.md]— per-file provenance for theferrite/shader extractions.[ledger §2b]— the wizard attempts 10–31 arithmetic-ABI chase (one f16 ULP → 51.7 M differing logits; attempts 9/31 verdicts).[ferrite-book Ch 4]— the ancestor’s compilation chapter: the build-time metallib +MTLBinaryArchivecache Muser dropped, theprecise::fast-math lesson, and the silent-metallib-fallback hole (D06-3) whose fix is this chapter’s §4.7.[Metal-PG]— Apple, Metal Programming Guide: “Functions,” “Pipeline State Objects,” “Binary Archives.”[Metal-SS]— Apple, Metal Shading Language Specification: “Function Constants.”- glossary — terms introduced this chapter: PSO, MTLLibrary, metallib, JIT compilation, function constant, fast-math, cross-vendor library, PsoCache, fingerprint, fail-closed.
Chapter 5 — Quantization from scratch
status: polished · path: Muse Glimmer, pinned Muser tree
Prerequisites: Chapters 1–4. You know why one decode token costs roughly “the whole model in bytes” (Chapter 1’s bytes-per-token spine), and you know that Metal kernels are compiled from one of three fingerprinted sources (Chapter 4). This chapter never launches a kernel — it is pure arithmetic on how a number becomes fewer bytes. The concrete formats arrive in Ch 6 and Ch 7.
Chapter 4 left you with a working compute pipeline: .metal source becomes a
runnable kernel through the concatenated fast-math library, the strict-f32
cross-vendor library, or the pinned llama.cpp metallib — and a fingerprint
line tells you which one actually ran. Ready to feed it work, we now face the
problem that dwarfs every kernel decision: the weights have to fit, and
they have to be read — every one of them, every token.
This chapter builds quantization from zero, with no allegiance to any particular format. We will invent a tiny 4-bit scheme, pack a block by hand, dequantize it with every multiply written out, and measure the error we just introduced. The real formats — kquant in Chapter 6, NVFP4 in Chapter 7 — are industrial versions of exactly this construction.
5.1 Why fp16 alone cannot carry a 30B model
Before we can argue about formats we have to know what we are carrying. Two questions decide everything that follows: how many weights are there, and how many bytes may each one cost? The first has an exact answer, and it is worth counting ourselves rather than trusting the nameplate on the box — every byte estimate in this chapter is built on that count, so an error here would quietly poison the whole argument.
A weight (also called a parameter) is one learned coefficient of the
model. Muse Glimmer is nominally a 30B-class model; counting the tensors the
loader actually validates gives a precise number. The engine’s config
asserts every tensor’s shape at load
[crates/muser-engine/src/config.rs:286], and the shapes are (Figure 5.1):
per layer (52 layers):
attn_q [hidden 6656 → 4096] attn_gate [6656 → 4096]
attn_k, attn_v [6656 → 256] attn_output[4096 → 6656]
ffn_gate, up [6656 → 19968] ffn_down [19968 → 6656]
embedding table [6656 → vocab 202,048] lm_head [6656 → 202,048]
Figure 5.1: The tensor inventory implied by assert_tensor_shapes
(config.rs:294-318). Dimensions are GGUF ne order, [in_dim, out_dim].
Multiply it out (Figure 5.2 — we show the arithmetic so you can re-derive it):
attention per layer : 3 × (6656×4096) + 2 × (6656×256) = 85,196,800
ffn per layer : 3 × (6656×19968) = 398,721,024
per layer total : 483,917,824
× 52 layers : 25,163,726,848
embedding + lm_head : 2 × (6656×202,048) = 2,689,662,976
TOTAL : 27,853,389,824 ≈ 27.85 B
Figure 5.2: Parameter count by hand. The “30B” nameplate is nominal; the counted total is ≈ 27.85 B. Norm vectors (~1.7 M values) are lost in the rounding here.
That count is the input to everything else in the chapter. Two walls stand between it and a working decode loop, and they fail in different ways: one asks whether the model fits, the other whether it can be read fast enough. Only the second one settles the format question, but the first is where most people’s intuition starts, so we take it first.
The capacity wall. f16 — a 16-bit IEEE float, the “half precision” format — is the smallest widely-used floating-point representation, at 2 bytes per weight:
27,853,389,824 × 2 B = 55,706,779,648 B ≈ 55.7 GB
The decode host is one Mac with an M3 Ultra and 96 GB of unified memory
([docs/memory-footprint.md] intro). Fifty-odd gigabytes against ninety-six
looks like room to spare, and that is exactly the trap: the weights never
occupy memory alone. Everything needed to serve one request has to live
beside them — the KV cache, the per-token
Key/Value attention memory of Chapter 1’s cost model
(Ch 22 owns it in depth), which for the release
configuration (four full-context slots at 131,072 tokens) is 7.306 GB; the
DFlash draft artifact 1,631,205,312 B; the vision projector
1,400,328,928 B; and ~0.99 GB of f32 batch-activation widths for prefill
[docs/memory-footprint.md].
That sum is already ≈ 67 GB, and it is a floor, not a total.
memory-footprint.md is explicit about its own incompleteness — “summing
artifact sizes with the KV formula is … only a lower bound” — because the
operating system, Metal’s pipelines and workspaces, and per-slot sampler
state all draw on the same 96 GB and none of them appear in the addition.
An fp16 model is not a plan; it is a hope.
The bandwidth wall — the one that settles it. Chapter 1 established that decode is ~99% reading weights: every token’s forward pass streams the whole model through the GPU. There is no cache trick and no clever ordering that lets a decode step skip a weight: if it is in the model, it goes across the bus, once per token. Quantization is how Muser attacks that stream.
The pinned kquant artifact is 16,756,681,056 bytes. We hold that figure
twice over — it is the size recorded in the artifact manifest
[docs/memory-footprint.md], and the identical constant is pinned in the
engine crate itself [crates/muser-engine/src/lib.rs:14]. Per weight, that
is:
16,756,681,056 B × 8 bits/B ÷ 27,853,389,824 weights ≈ 4.81 bits/weight
Versus fp16’s 16 bits: the artifact is 3.33× lighter per token. If
decode stayed bandwidth-bound at the same effective rate — the regime
Chapter 1 proved — an fp16 model would run at roughly 35.4 ÷ 3.33 ≈ 10.6
tok/s at best, versus the measured kquant 35.440 tok/s [claims #11].
(Derived ceiling from the bytes ratio, not a measurement.) Capacity might
survive an fp16 model on a lucky day. Bandwidth does not.
Quantization is the answer: store each weight in fewer than 16 (or 32) bits, accept a small, controlled error per weight, and buy a 3.3× reduction in the per-token byte stream. Everything else in Part II is the engineering of “small and controlled.”
5.2 Numbers as bits: f32 and f16
Fewer bits per weight, then. But which bits, and taken from where? To shrink a number we must first say what a number is in memory. An IEEE 754 float is a sign, an exponent, and a mantissa (fraction):
f32 (32 bits): [ sign:1 ][ exponent:8 ][ mantissa:23 ] ~7 decimal digits
f16 (16 bits): [ sign:1 ][ exponent:5 ][ mantissa:10 ] ~3 decimal digits
Figure 5.3: IEEE float layouts. The mantissa fixes the relative precision; the exponent fixes the dynamic range. f16 spans 2^-14 to 65504.
The mantissa is a binary fraction between 1 and 2 (for normal numbers), so an f16 weight with 10 mantissa bits is known to about one part in 1,024 — roughly three decimal digits. Real transformer weights live near zero, commonly within ±0.05, and f16 represents that range comfortably. What f16 cannot do is fit two of them in a byte. For that we leave floating point behind.
5.3 The codebook idea: a number as an index
Here is the single idea behind every format in this book. Instead of storing the weight’s value, store an integer index into a small table of allowed values — a codebook. Four bits select among 2⁴ = 16 possible values. Each index is a nibble — half a byte, values 0–15 — and two nibbles pack into one byte, which is where the storage win comes from: 0.5 bytes per weight.
Said the other way round: we stop recording what the weight is and start recording which of a short agreed list of values it sits nearest to. The list itself never goes over the bus, because sender and reader already know it. That substitution — value for index, memory for agreement — is the whole of quantization; everything after it is bookkeeping about how the list is chosen.
Two families of codebook exist, and Part II contains one of each:
- Integer (uniform) codebooks. The 16 values are evenly spaced: a
base value plus
index × step. Every kquant format (Chapter 6) is integer codebooks all the way down. - Float codebooks. The 16 values are themselves tiny floats, spread with relative (multiplicative) spacing. NVFP4’s e2m1 (Chapter 7) is exactly a 16-entry float table.
Dequantization is the act of turning an index
back into a value: look up the table (or compute base + index × step), and
out comes an approximation of the original weight. Quantization
is the inverse: pick the index whose value is closest to the original.
The gap between the original value and its reconstruction is the quantization error — the price of the whole enterprise. The rest of this chapter is about driving that error down without spending bytes.
5.4 One scale for everything: too crude
The first scheme everyone writes down is the one that needs no bookkeeping at all: one global step size for the whole tensor, chosen from the largest magnitude weight, shared by every weight in every row. Take it seriously for a moment, because on paper it looks unimprovable — a single stored number of overhead for hundreds of millions of weights, which rounds to no overhead at all.
So follow it through and see where it lands. Say a tensor’s weights span roughly ±0.5, so a symmetric 4-bit grid with 16 levels would use a step of 1.0/15 ≈ 0.067. Any weight is then replaced by the nearest multiple of 0.067. The arithmetic is sound; the assumption buried underneath it is not. Real weight blocks are much narrower than the global span — a few dozen neighboring weights typically cluster inside a band a tenth as wide. A global grid spends most of its 16 levels on values that never occur in that neighborhood, and the local error is needlessly large.
That is the lesson worth carrying out of the dead end: a codebook is worth only as much as its agreement with the values it is actually asked to encode. What you save on the header, you pay back with interest in error. We will watch the same failure play out concretely in §5.7 (the DC-offset problem), and it is why no format in this book uses a single global scale.
The fix is to quantize locally, not globally.
5.5 Blocks and scales
Split the tensor into small, contiguous blocks and give each block its own scale (a local step size). Now the 4 bits express a position inside the narrow band this block actually uses:
value ≈ scale × index (symmetric: one number per block)
value ≈ scale × index + min (asymmetric: two numbers per block)
A block of 32 weights might span only ±0.05, so its private scale is ~0.1/15 ≈ 0.0067 — ten times finer than the global grid above, from the same 4-bit index. Any slice of a smooth distribution spans less than the whole distribution; that is the entire trick, and it is the idea behind every “K-family” format in Chapter 6.
Turn it around once more, because this is the sentence the rest of Part II leans on: the narrower the range a scale has to cover, the more of your sixteen levels land where the data actually is. Bits do not become more precise by being more numerous. They become more precise by being asked a narrower question.
The scale itself is stored in floating point (typically f16, 2 bytes), because it must cover a wide range of magnitudes across blocks with only a few values of precision — the same division of labor as Figure 5.3, now between the scale (coarse, wide-range) and the index (fine, local).
5.6 Symmetric vs asymmetric: the min+offset trick
One stored number per block, or two? The choice looks like arithmetic and is really a question about what you are willing to assume about the data before you have looked at it.
Symmetric quantization assumes the
block’s values are roughly centered on zero. One number per block — the
scale — and the codebook spans −scale·max_index … +scale·max_index with 0
landing exactly on 0.
Asymmetric quantization adds a second number per block — the min (offset) — so the codebook can start wherever the data starts: `value = scale × index
- min`. This is the min+offset trick: it costs one extra stored number per block and buys correct handling of blocks that do not live around zero.
Symmetric (1 number/block) Asymmetric (2 numbers/block)
──────────────────────────── ────────────────────────────────
grid: −A ··· 0 ··· +A grid: min ··· min + 15·scale
assumes: zero-centered assumes: nothing
index 0 → −A (or 0) index 0 → min (offset!)
waste: shifted blocks lose waste: one extra f16 per block
half their levels
value = scale × index value = scale × index + min
Figure 5.4: The two codebook geometries. Q4_0 and Q8_0 (Chapter 6) are symmetric; Q4_K and Q5_K are asymmetric; NVFP4’s float codebook is symmetric by construction (its table is ± pairs).
When does the difference bite? When a block has a DC offset — a mean
far from zero. A block whose values all lie in, say, [0.30, 0.42] forces a
symmetric grid to cover ±0.42, and every negative level is wasted: only
about half the grid is ever addressed. The asymmetric grid puts min = 0.30
at index 0 and uses all 16 levels inside the 0.12-wide band — a resolution
roughly 3.5× finer for the same 4 bits.
There is an honest gap in that argument, and it matters for what we are allowed to claim later. Whether weight blocks in a given checkpoint actually carry enough offset to make the trick pay is an empirical property of the checkpoint, and it is not one we measured for this one [unverified for Muse Glimmer]. What we can say is that the format designers thought it worth the bytes: the release artifact pays for asymmetric blocks on most tensors, which is Chapter 6’s byte-level story.
5.7 The worked example: an 8-element block, 4 bits, every step
Everything so far has been argument. Now we do the thing itself, slowly, with nothing hidden — because a reader who has packed one block by hand can read any format in this book, and a reader who has not will be taking the byte layouts ahead on faith. This is the heart of the chapter — the template every later quant chapter reuses. We quantize one block by hand. The numbers below are schematic, chosen so the arithmetic is clean; they are not taken from any checkpoint. Watch two things as they go past: where the error ends up landing, which is not where most people guess, and how few bytes the block costs when we are done.
The block. Eight weights:
x = [ -0.15, 0.29, 0.12, -0.03, 0.20, 0.06, -0.09, 0.17 ]
Step 1 — scan the block.
min = −0.15 max = 0.29 range = max − min = 0.44
Step 2 — fit the scale. 4 bits give 16 levels, indices 0–15. We want
index 15 to land exactly on max:
scale = range / 15 = 0.44 / 15 = 0.029333…
For hand arithmetic, round the scale up to 0.03 — quantizers really do pick a convenient scale (Chapter 6’s scales are 6-bit integers times an f16; Chapter 7’s are powers-of-two floats), and a slightly padded range is safe: indices stay in 0–15 without clamping.
Step 3 — quantize each element. index_i = round((x_i − min) / scale):
x[0] = −0.15 → ( 0.00)/0.03 = 0.0 → 0
x[1] = 0.29 → ( 0.44)/0.03 = 14.67 → 15
x[2] = 0.12 → ( 0.27)/0.03 = 9.0 → 9
x[3] = −0.03 → ( 0.12)/0.03 = 4.0 → 4
x[4] = 0.20 → ( 0.35)/0.03 = 11.67 → 12
x[5] = 0.06 → ( 0.21)/0.03 = 7.0 → 7
x[6] = −0.09 → ( 0.06)/0.03 = 2.0 → 2
x[7] = 0.17 → ( 0.32)/0.03 = 10.67 → 11
Step 4 — pack. Eight indices, 4 bits each, two per byte, low nibble
first (the convention Chapter 6 will meet in real code —
q & 0x0F is the low element, q >> 4 the high one):
pairs : (0,15) (9,4) (12,7) (2,11)
bytes : 0x0F 0x49 0x7C 0x2B
block : [ min as f16 ][ scale as f16 ][ 0F 49 7C 2B ]
2 bytes 2 bytes 4 bytes → 8 bytes total
Step 5 — dequantize. x̂_i = min + scale × index_i. Every multiply
shown:
x̂[0] = −0.15 + 0.03 × 0 = −0.15 (exact)
x̂[1] = −0.15 + 0.03 × 15 = −0.15 + 0.45 = 0.30 (true 0.29)
x̂[2] = −0.15 + 0.03 × 9 = −0.15 + 0.27 = 0.12 (exact)
x̂[3] = −0.15 + 0.03 × 4 = −0.15 + 0.12 = −0.03 (exact)
x̂[4] = −0.15 + 0.03 × 12 = −0.15 + 0.36 = 0.21 (true 0.20)
x̂[5] = −0.15 + 0.03 × 7 = −0.15 + 0.21 = 0.06 (exact)
x̂[6] = −0.15 + 0.03 × 2 = −0.15 + 0.06 = −0.09 (exact)
x̂[7] = −0.15 + 0.03 × 11 = −0.15 + 0.33 = 0.18 (true 0.17)
Step 6 — measure the error. Per-element error x̂_i − x_i:
errors: 0, +0.01, 0, 0, +0.01, 0, 0, +0.01
max abs error = 0.01
mean abs error = 0.00375
Three observations that generalize far beyond this toy:
- The elements that defined the range are exact or nearly so. The block minimum reconstructed perfectly; the maximum was off by exactly one scale-step because we padded the scale. A quantizer’s error is worst for values in the middle of the range, never for the extremes that set it.
- Error is bounded by scale/2. Every true value lies within half a step
of a level, so
|error| ≤ scale/2by construction. Here 0.015; we measured ≤ 0.01. - Five of eight elements came out exact because they happened to sit on
the grid. Real weights don’t sit on grids; real mean error lands near
scale/4. The toy flatters us — keep that in mind when extrapolating.
Step 7 — the symmetric control. A worked example that only ever runs one way proves nothing, so before accepting the asymmetric header we send the same block through the other geometry and compare. Quantize it symmetrically: scale = amax/7, reading the 4 bits as signed indices −7…+7, amax = 0.29, so scale = 0.29/7 ≈ 0.0414 — coarser than 0.03 even before accounting for the wasted negative levels this almost-centered block barely uses. The extra stored number has already earned its keep — and it earned it on a block that was nearly centered to begin with, which is the weakest case we could have handed it. The offset here is mild (min = −0.15, max = 0.29); for a strongly one-signed block the symmetric penalty is the full factor-of-two of Figure 5.4.
5.8 What the error costs downstream
A hundredth here and a hundredth there — does any of it survive contact with the model, or does it wash out? The question matters because the answer decides how nervous to be, and the honest answer is: it does not wash out, but it also does not behave like noise.
A weight’s quantization error is not an isolated blemish — it is a deterministic perturbation of the model. Each dot product in the forward pass — the multiply-and-add pairing of two equal-length vectors, the atom under every weight matrix in this book — mixes in one error term per weight. Through 52 layers the perturbations compound multiplicatively (the drift argument Chapter 12 makes for normalization). Three honest statements about the cost:
- The error is fixed and knowable. Weights are quantized once, offline;
the dequantized value is the same every token. It is a permanent, exact
bias — not noise. That is what makes cross-engine parity possible at all:
Muser’s kquant lane reproduces llama.cpp’s numbers bit-for-bit on the
shared format precisely because the bytes and the arithmetic order are
pinned
[crates/muser-engine/src/quant/k_block.rs:169-177]. - Quality loss is real, but it is bounded by measurements rather than by
vibes. Muser does not assert that quantization is harmless; it budgets
the harm and then checks the budget. NVFP4-versus-kquant relative
perplexity and top-token disagreement are gated per depth and per content
class, and where one content class came in above its calibrated gate, the
number was published rather than buried: docs text at 65,536 tokens,
15.134% against a 13.339% calibrated gate, carried as part of the claim
[claims #10]. Chapter 7 tells that story. - You cannot compare formats by one number. The same 4-bit-class quantization is invisible in plain decode — the lanes land inside each other’s noise, as §5.9 will show — and decisive the moment the shape of the work changes: in batched speculative verification the same bytes produce a 6.81 tok/s no-go, the story Chapter 7 tells. Precision, in other words, does not have a price. It has a price per batch shape and per content class, which is why the gates are written to localize the cost instead of returning a single verdict.
5.9 Block size: the memory-vs-overhead dial
How big should a block be? It sounds like a tuning knob and is really a fork in the road, because the two ends of the dial fail for opposite reasons — and the formats waiting in the next two chapters are best read as two different answers to this one question.
The block header (min + scale, say 4 bytes as two f16s) is paid once per block. The bitrate — bits per weight — is:
bitrate = payload bits + header bits / block size
= 4 + 32 / N
Turn the dial (Figure 5.5):
block size N header/weight total bits/weight local range
───────────── ───────────── ───────────────── ───────────────
8 4.000 8.00 very narrow
32 1.000 5.00 narrow
256 0.125 4.125 moderate
1024 0.031 4.03 wide
Figure 5.5: The block-size dial. Smaller blocks buy finer local scales and lower error; bigger blocks amortize the header. (This table assumes the naive one-scale-one-min-per-block header of §5.7.)
Both directions fail. At N = 1,024 the header is nearly free, but a block that wide spans much of the tensor’s dynamic range and the local-scale advantage evaporates — you drift back toward the global grid of §5.4. At N = 8 the error is superb and you have doubled the storage. Every real format parks somewhere in between and then engineers the header down:
- Q4_K (Chapter 6): N = 256 with an asymmetric header, made cheap by a two-level hierarchy — one f16 super-scale and one f16 super-min for the whole 256, plus six-bit sub-scales per 32-element sub-block. Total header: 16 bytes per 256 weights = 0.5 bits/weight → 4.5 bits/weight.
- NVFP4 (Chapter 7): N = 16 with a symmetric float header of a single one-byte e4m3fn scale, plus one f32 per tensor. Total: 4 + 8/16 = 4.5 bits/weight — the same bitrate as Q4_K by entirely different means.
That coincidence is worth pausing on: two formats, two codebook families, two block sizes — and the same 4.5 bits. Format design is the art of spending a fixed half-bit of overhead (on top of the 4 payload bits) in different places. And 4.5-ish bits is also where the real artifact lands on average: the whole-GGUF figure computed in §5.1 was 4.81 bits/weight (the excess over 4.5 is the deliberately more precise tensors Chapter 6 identifies — Q6_K’s 6.5625 and Q5_K’s 5.5).
One more axis the table hides: who pays to use the format. A tiny block with a cheap codebook dequantizes with one multiply (good for a hot kernel); a 256-block with 6-bit bit-packed sub-scales costs real decode work per block (Chapter 6 shows the kernel-side machinery). Block size is a deal between storage, error, and kernel complexity — not just a storage number.
5.10 Tradeoffs
Asymmetric vs symmetric, measured in bytes. What does honesty about
offsets actually cost, in the only currency decode cares about? The
min+offset trick costs one extra stored number per block. At N = 32 with f16
headers that is
4 + 64/32 = 6 bits/weight symmetric vs 4 + 96/32 = 7 bits/weight
asymmetric — a 17% storage tax for offset robustness. Q4_K’s two-level
hierarchy is precisely the invention that recovers the tax: min+offset at
4.5 bits/weight, the same bitrate a plain symmetric 32-block would waste
(4 + 16/32 = 4.5). Chapter 6 walks the real bytes.
4 bits vs 16, measured in tokens. Does the shrinking actually buy
tokens, or does the decode side hand the savings straight back as unpacking
work? The lane throughputs answer it: kquant (≈4.81 bits/weight average)
35.440 tok/s and native NVFP4 (4.5 bits/weight) 35.491 tok/s, measured in
the same paired five-rep cell — parity within noise, never claimed
faster [claims #11]. The measured existence of two independent ~4.5-bit
artifacts running at parity with the f16-KV llama.cpp comparator is the
strongest statement this book can make that quantization, done at this rate,
does not tax decode throughput.
The scope of that cell matters as much as the figures do, so we carry it rather than round it away: a 66-token prefix, 32 teacher-forced tokens, F16 KV, an adjacent lease window, and a +0.1444% edge for NVFP4 — a margin no one should read as a win, and the reason the claim says parity and stops there. What 4 bits does tax — the batched speculative verify path — is a Chapter 7 measurement.
Why not 2 bits? The dial in the previous section has a lower end too,
and after a chapter spent shrinking things the fair question is why we
stopped where we stopped. Nothing in this chapter’s arithmetic forbids going
further — 2-bit codebooks exist in the wild. But at 4 levels per block the
quantization error approaches the size of the local scale itself, and §5.8’s
error-compounding has nowhere to hide: the error stops being a correction to
the weight and starts being most of the weight. Muser’s own gates localized
quality cost at 4-bit-class formats to specific content classes
[claims #10], and even that took a calibrated per-content gate to see at
all. So the ending here is an admission rather than a verdict: we do not
know what the next step down would have cost on this model, because no
2-bit lane was ever qualified in this program [unverified — no
measurement exists in the retained evidence].
5.11 What comes next
You now own the complete template: a codebook, a local scale, an optional
min, a hand-packed block, a dequant with every multiply visible, and an
error budget. Chapter 6 deploys it on the bytes Muser actually ships — the
kquant family (Q4_K, Q5_K, Q6_K) that fills the 16,756,681,056-byte
reference artifact, byte layouts first, then the pinned
kernel_mul_mv_q*_K_f32 Metal kernels that consume them and the per-tensor
map of which class of weight gets which format.
References
[crates/muser-engine/src/config.rs:286]—assert_tensor_shapes: every tensor and shape the loader validates (Figure 5.1’s source).[crates/muser-engine/src/lib.rs:14]— the pinned artifact byte size 16,756,681,056 asserted in the crate docs.[docs/memory-footprint.md]— 96 GB M3 Ultra host, KV formula and the 7.306 GB four-slot figure, artifact manifest (16,756,681,056 / 1,631,205,312 / 1,400,328,928 B), “lower bound” caution.[claims #11]—docs/launch-claims.mdrow 11: plain Mac NVFP4 35.491 tok/s vs adjacent kquant 35.440 tok/s at original scope, parity within noise; also the five-rep cell description via the ledger (P1.3).[claims #10]— native NVFP4 quality gates and the published docs@65,536 content-local sensitivity (15.134% vs 13.339%).[crates/muser-engine/src/quant/k_block.rs:169-177]—dot_q4_k_f32_llamadoc: the pinned llama.cpp accumulation-order contract for Q4_K.- Ch 6 — the kquant family: real byte layouts, the 6-bit scale packing, and the dispatch table.
- Ch 7 — NVFP4: the float codebook and the native lane.
- [ferrite-book Ch 5] — the ancestor’s Q4_K chapter, whose hand-built-superblock method this chapter ports (pedagogical lineage only; all numbers here are re-derived from the Muser tree).
Chapter 6 — The kquant family on the reference lane
status: polished · path: Muse Glimmer, pinned Muser tree
Prerequisites: Chapter 5. You have hand-packed an 8-element 4-bit block, dequantized it with every multiply shown, and measured the error. This chapter runs the same procedure on the real bytes Muser ships — no more schematic numbers. Two Metal kernels get quoted; the rest is byte layout and arithmetic.
Chapter 5 ended on a promise: the concrete formats. Here is the first one.
The kquant family — llama.cpp’s “K-quant” block formats — is what fills
the 16,756,681,056-byte reference artifact that Muser’s kquant lane decodes
from, the lane measured at 35.440 tok/s [claims #11] and used as the
program’s reference lock.
Everything that follows answers one question in different disguises: when a weight has been squeezed down to a handful of bits inside a packed block, what exactly must the machine read, and in what order, to get a usable number back out? So we read the byte layouts straight from Muser’s own dequantizers, dequantize real-format elements by hand, map which tensor class carries which member of the family, and then watch the dispatch code choose a kernel for each batch shape. The receipts are all here; they simply arrive after the idea they support rather than in the middle of it.
6.1 One model, several dtypes — decided by the GGUF
Before any bytes, a question of authority: who decides that this tensor is Q4_K and that one is Q6_K? Not the engine, and not an operator flag at startup. The file decides, and it decides tensor by tensor. The stake is higher than it sounds — guess wrong here and you do not get a slightly worse model, you get an engine that refuses to start, because every admission check below is fail-closed.
A GGUF file carries a dtype per tensor, not per model. Muser’s parser enumerates the types it is willing to meet:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/gguf/types.rs:9
pub enum GgmlType {
F32 = 0,
F16 = 1,
Q4_0 = 2,
Q4_1 = 3,
Q5_0 = 6,
Q5_1 = 7,
Q8_0 = 8,
// …
Q4_K = 12,
Q5_K = 13,
Q6_K = 14,
// …
/// Muser-native NVFP4 E2M1 payload, two logical values per byte.
/// Per-16 E4M3FN scales and the per-tensor f32 scale2 live in bound
/// companion tensors; this keeps the serving representation at exactly
/// 4.5 bits/weight without relying on llama.cpp's experimental format.
NVFP4_E2M1 = 1000,
/// Raw E4M3FN bytes used only by NVFP4 companion scale tensors.
F8_E4M3FN = 1001,
}
}
Every type knows its geometry — bytes per block and elements per block —
and those two numbers define the format’s
bitrate [crates/muser-engine/src/gguf/types.rs:75-127]:
format block bytes elements bits/element min+offset? codebook
─────── ─────────── ──────── ──────────── ─────────── ────────
Q4_0 18 32 4.50 no (sym) 4-bit int
Q8_0 34 32 8.50 no (sym) 8-bit int
Q4_K 144 256 4.50 yes 4-bit int
Q5_K 176 256 5.50 yes 5-bit int
Q6_K 210 256 6.5625 no (signed) 6-bit int
F16 2 1 16.0 exact float
Figure 6.1: The kquant family as registered in GgmlType::block_size /
block_elements. Bits/element = block_bytes × 8 ÷ elements — derive each
one yourself: Q4_K is 144×8/256 = 4.5; Q6_K is 210×8/256 = 6.5625.
Two paths read these types, and they are not equally permissive. The CPU
reference path will dequant anything on the list — quant/dispatch.rs simply
fans out per dtype. The live Metal decode path is deliberately narrower. A
projection tensor must be one of Q4_K | Q5_K | Q6_K | NVFP4_E2M1 | F16, and
the embedding table is narrower still: Q4_K or F16, nothing else.
Anything outside those sets is not quietly converted on the way in; it is
refused. The two fences are drawn at
[crates/muser-engine/src/decode.rs:136-139] for projections and
[crates/muser-engine/src/decode.rs:1209] for the embedding.
The lane itself is chosen the same fail-closed way at load. The GGUF must
declare muser.weight_precision, and a kquant artifact becomes the default
only when the file contains no native NVFP4 tensors at all
[crates/muser-engine/src/loader.rs:72-91]. The artifact picks the lane —
not a flag, not a heuristic, not a fallback that guesses. Chapter 7 covers
the nvfp4 pairing; this chapter stays on q4_k_xl.
6.2 Q4_K: the 144-byte super-block
Q4_K is the format most of this artifact is written in, which makes it the one worth knowing to the byte. It answers the question the previous chapter left open: how do you give every small group of weights its own local scale without the scales themselves eating the savings you went to all this trouble for? Watch where each header byte goes, and the answer is arithmetic rather than magic.
The unit of Q4_K storage is a super-block: 256 weights in 144 bytes, which is the 4.5 bits/weight of Figure 6.1. As in Chapter 5, we read the layout off the dequantizer — this is Muser’s complete, real function:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/quant/k_block.rs:12
pub fn dequant_q4_k(block: &[u8], out: &mut [f32]) {
debug_assert!(block.len() >= 144);
debug_assert!(out.len() >= 256);
let d = f16_to_f32(u16::from_le_bytes([block[0], block[1]]));
let dmin = f16_to_f32(u16::from_le_bytes([block[2], block[3]]));
let scales = &block[4..16];
let qs = &block[16..144];
// get_scale_min_k4: extract 6-bit sc and m for sub-block j (0..7)
let get_scale_min = |j: usize| -> (f32, f32) {
let (sc, m) = if j < 4 {
(scales[j] & 0x3F, scales[j + 4] & 0x3F)
} else {
let sc = (scales[j + 4] & 0x0F) | ((scales[j - 4] >> 6) << 4);
let m = (scales[j + 4] >> 4) | ((scales[j] >> 6) << 4);
(sc, m)
};
(d * sc as f32, dmin * m as f32)
};
// 4 outer groups of 64 elements, 2 sub-blocks per group, 32 qs bytes per group.
let mut q_off = 0usize;
let mut is = 0usize;
let mut base = 0usize;
while base < 256 {
let (d1, m1) = get_scale_min(is);
let (d2, m2) = get_scale_min(is + 1);
for l in 0..32 {
let q = qs[q_off + l];
out[base + l] = d1 * (q & 0x0F) as f32 - m1;
out[base + l + 32] = d2 * (q >> 4) as f32 - m2;
}
q_off += 32;
is += 2;
base += 64;
}
}
}
The byte map (Figure 6.2), in the ASCII style Chapter 5 promised:
Q4_K super-block — 144 bytes for 256 elements
offset size field meaning
─────────────────────────────────────────────────────────────────
0x00 2 d f16 super-scale (shared by 8 sub-scales)
0x02 2 dmin f16 super-min-scale (shared by 8 sub-mins)
0x04 12 scales 8× 6-bit sc + 8× 6-bit m, packed (Fig 6.3)
0x10 128 qs 256 nibbles (2 per byte) — the weights
0x90 — (end) 2+2+12+128 = 144 bytes
─────────────────────────────────────────────────────────────────
0x10 = 16, 0x90 = 144
Figure 6.2: The Q4_K super-block. Four regions: two f16 headers, a 12-byte packed scale/min strip, 128 bytes of nibbles. Compare the 8-byte toy block of Chapter 5 — same idea, one more level of hierarchy.
The structure is Chapter 5’s min+offset scheme with the two-level header
that keeps it at 4.5 bits: the 256 elements split into eight
sub-blocks of 32, each with its own 6-bit scale
sc (0–63) and 6-bit min m (0–63), all multiplied by the shared f16 d
and dmin. The value of element i in sub-block j is:
y = d × sc_j × nibble − dmin × m_j
Exactly scale × index + min from Chapter 5 — as d·sc_j (effective
scale) and −dmin·m_j (effective min) with a sign flip folded in. The
header budget: 4 bytes of f16s + 12 bytes of packed 6-bit fields = 16 bytes
per 256 weights = 0.5 bits/weight of overhead.
Chapter 5’s dial table charged 0.125 bits for a naive one-f16-pair header
per 256; Q4_K spends 0.5 — and buys eight independent local scales/mins
instead of one.
The 96 bits of scale/min strip are packed with zero padding. Sub-blocks 0–3 live wholly in bytes 0–7; sub-blocks 4–7 split their values between the low/high halves of bytes 8–11 and the top two bits of bytes 0–7:
byte b7 b6 │ b5 b4 b3 b2 b1 b0 contents
──────────────────────────────────────────────────────────────────────
scales[0..4] sc4+ │ sc0 (6 bits) top2 of scales[j] = sc_{j+4} hi
scales[4..8] m4+ │ m0 (6 bits) top2 of scales[4+j] = m_{j+4} hi
scales[8..12] m_lo │ sc_lo lo4|hi4 reassembles sc/m of 4..7
Figure 6.3: The 6-bit packing, compressed. The get_scale_min closure
above is the authoritative spec: sub-blocks ≥4 reassemble sc from the low
nibble of scales[j+4] plus the top 2 bits of scales[j-4], and m from
the high nibble plus the top 2 bits of scales[j].
Note also the interleaving in the dequant loop: within each 64-element
group, the low nibble of byte qs[q_off+l] feeds element base+l
(even sub-block) and the high nibble feeds element base+l+32 (odd
sub-block). Said the other way round, because this is the part that trips
people up: a byte of qs does not belong to any single sub-block. Its low
half is a weight in one sub-block and its high half is a weight in the
neighbouring one, and the two halves are reconstructed with different
effective scales and different mins. One byte, two sub-blocks, two different
scales — the property Chapter 5’s toy deliberately did not have.
6.3 A worked dequant of real-format bytes
Reading a layout table is not the same as believing it. The only way to be sure you have the packing right is to build a block out of nothing, run the extraction the shipping code runs, and watch the values come back. That is the exercise here, and it is worth doing with a pencil rather than skimming.
Hand-build one super-block (values schematic in magnitude — real weights dequant near ±0.05; the mechanics are identical):
Headers. Pick d = 0.03125 = 2⁻⁵ and dmin = 0.0078125 = 2⁻⁷, both
exact in f16. f16 bits: exponent = power+15, mantissa 0 → d = 0x2800,
dmin = 0x2000; little-endian bytes 00 28 and 00 20.
Sub-block scales. Choose (all 0–63):
sc = [20, 12, 44, 8, 33, 25, 17, 9] m = [ 4, 16, 8, 24, 12, 20, 28, 36]
Pack per Figure 6.3 — each line is the formula with the arithmetic shown:
scales[0] = sc0 | ((sc4 >> 4) << 6) = 20 | 128 = 148 = 0x94
scales[1] = sc1 | ((sc5 >> 4) << 6) = 12 | 64 = 76 = 0x4C
scales[2] = sc2 | ((sc6 >> 4) << 6) = 44 | 64 = 108 = 0x6C
scales[3] = sc3 | ((sc7 >> 4) << 6) = 8 | 0 = 8 = 0x08
scales[4] = m0 | ((m4 >> 4) << 6) = 4 | 0 = 4 = 0x04
scales[5] = m1 | ((m5 >> 4) << 6) = 16 | 64 = 80 = 0x50
scales[6] = m2 | ((m6 >> 4) << 6) = 8 | 64 = 72 = 0x48
scales[7] = m3 | ((m7 >> 4) << 6) = 24 | 128 = 152 = 0x98
scales[8] = (sc4 & 0x0F) | ((m4 & 0x0F) << 4) = 1 | 192 = 193 = 0xC1
scales[9] = (sc5 & 0x0F) | ((m5 & 0x0F) << 4) = 9 | 64 = 73 = 0x49
scales[10] = (sc6 & 0x0F) | ((m6 & 0x0F) << 4) = 1 | 192 = 193 = 0xC1
scales[11] = (sc7 & 0x0F) | ((m7 & 0x0F) << 4) = 9 | 64 = 73 = 0x49
Round-trip check for sub-block 5, substituting into get_scale_min exactly
as the code does:
sc5 = (scales[9] & 0x0F) | ((scales[1] >> 6) << 4) = 9 | (1 << 4) = 25 ✓
m5 = (scales[9] >> 4) | ((scales[5] >> 6) << 4) = 4 | (1 << 4) = 20 ✓
Nibbles. Set qs[0] = 0x5E (offset 16) and qs[32] = 0x3C
(offset 48); all other qs bytes zero.
Dequantize three elements. Precompute the effective scale/min per
sub-block (d·sc, dmin·m):
| sub-block | sc | m | eff. scale = d·sc | eff. min = dmin·m |
|---|---|---|---|---|
| 0 | 20 | 4 | 0.03125×20 = 0.625 | 0.0078125×4 = 0.03125 |
| 1 | 12 | 16 | 0.03125×12 = 0.375 | 0.0078125×16 = 0.125 |
| 2 | 44 | 8 | 0.03125×44 = 1.375 | 0.0078125×8 = 0.0625 |
Element 0 (sub-block 0, low nibble of qs[0]):
q = 0x5E → nibble = q & 0x0F = 14
y0 = 0.625 × 14 − 0.03125 = 8.75 − 0.03125 = 8.71875
Element 32 (sub-block 1, high nibble of the SAME byte):
q = 0x5E → nibble = q >> 4 = 5
y32 = 0.375 × 5 − 0.125 = 1.875 − 0.125 = 1.75
Element 64 (sub-block 2, low nibble of qs[32]):
q = 0x3C → nibble = q & 0x0F = 12
y64 = 1.375 × 12 − 0.0625 = 16.5 − 0.0625 = 16.4375
One byte, two sub-blocks, two different effective scales — Chapter 5’s “zoom into the local band,” now twice per byte. You have dequantized Q4_K by hand against the shipping code.
6.4 Q5_K and Q6_K: the siblings
Q4_K has two siblings on this artifact, and each one changes exactly one thing. Both are answering the same question — where do you put the bits that did not fit in a nibble? — and they answer it differently: Q5_K adds a plane, Q6_K changes the codebook. Keep that distinction in hand; it is what makes their layouts feel different when they are really not.
Q5_K is Q4_K plus one extra bit per element, stored in a separate
plane. Its 176 bytes (Figure 6.4): the same d/dmin/12-byte scale strip, then 32
bytes qh holding one high bit per element (256 bits), then the same 128
bytes of low nibbles [crates/muser-engine/src/quant/k_block.rs:51-60]:
Q5_K — 176 bytes / 256 elements y = d·sc·(nibble | qh_bit<<4) − dmin·m
────────────────────────────────────────────────────────────────────────
0x00 2 d 0x02 2 dmin 0x04 12 scales
0x10 32 qh 0x30 128 qs 0xB0 — (end = 176)
Figure 6.4: Q5_K layout (offsets hex: 0x30 = 48, 0xB0 = 176). The 5-bit
codebook lifts the index range from 0–15 to 0–31; 176×8/256 = 5.5 bits/element
([crates/muser-engine/src/quant/k_block.rs:61-105] for the dequant).
Q6_K changes shape more: a signed 6-bit codebook split across two planes, with the per-sub-block scales as plain int8 and the single f16 super-scale at the end of the block (Figure 6.5):
Q6_K — 210 bytes / 256 elements y = d · sc_j · q , q = 6-bit signed
────────────────────────────────────────────────────────────────────────
0x00 128 ql low 4 bits of each code, packed 2/byte
0x80 64 qh high 2 bits of four codes per byte
0xC0 16 sc 16 × int8 sub-block scales (sub-blocks of 16 elements)
0xD0 2 d f16 super-scale (LAST field — bytes 208..210)
Figure 6.5: Q6_K layout (0x80 = 128, 0xC0 = 192, 0xD0 = 208). The Muser
kernel reads these offsets verbatim: ql = &block[0..128]; qh = &block[128..192]; sc = &block[192..208]; d = f16(block[208..210])
[crates/muser-engine/src/quant/k_block/q6.rs:21-26].
Each of the 16 sub-blocks (16 elements each) reconstructs its codes as
((ql_nibble) | (qh_2bits << 4)) − 32 — an unsigned 0–63 payload shifted
to signed −32…+31 — then scales once: 210×8/256 = 6.5625 bits/element.
No min term: a signed symmetric grid, but with 64 levels the zero-waste
argument of Chapter 5 matters less. The code-extraction quadruple
(q1..q4 pairing ql low/high nibbles with two-bit qh fields) is at
[crates/muser-engine/src/quant/k_block/q6.rs:45-52], in the llama-pinned
deferred-scaling order (§6.7).
6.5 Which tensor carries which format
So the family has members. Which tensor gets which, and who is keeping score? This is where a quantization recipe stops being theory: a few tensors get promoted to more bits, most do not, and every promotion is paid for out of the per-token byte budget this book opened with.
The release artifact is a mix, like llama.cpp’s “Q4_K_M” recipes. The
authoritative in-repo map is the shape table of the M=16 microbenchmark
harness, which enumerates the exact verify/draft projection shapes with
their dtypes and how often each fires per speculative cycle
([crates/muser-bench/src/m16.rs:137-226]):
label dtype shape (n_in→n_out) per-cycle mult
─────────────────────── ───── ────────────────── ──────────────
attn_q/gate Q4_K 6656→4096 104 (= 2 × 52 layers)
attn_k/v Q4_K 6656→256 78
attn_v Q6_K 6656→256 26
attn_output Q4_K 4096→6656 52
ffn_gate/up Q4_K 6656→19968 104
ffn_down Q4_K 19968→6656 26
ffn_down Q6_K 19968→6656 26
lm_head Q5_K 6656→202048 1
draft.k Q4_K 6656→1024 5 (draft layers)
draft.v Q6_K 6656→1024 5
draft.fc Q4_K 33280→6656 1
Figure 6.6: Dtypes and shapes on the release path, from the M16 bench
SHAPES table. The multiplicities are the harness’s own counts of
dispatches per verify cycle / draft block.
Read the mix off the counts. Attention k/v projections: 78 + 26 = 104
tensors = two per layer, so 26 of the 104 k/v tensors are Q6_K and the
rest Q4_K. FFN down-projections: 26 + 26 = 52, so ffn_down alternates
Q4_K/Q6_K by layer. Everything bandwidth-dominant — q, gate, output,
ffn_gate/up — is Q4_K; the FFN gate/up tensors are pinned Q4_K by the fused
kernel’s own guard ([crates/muser-engine/src/decode.rs:5819-5821]). The
lm_head is Q5_K — one tensor, but a 6656×202,048 one, worth
924,571,648 B by the arithmetic: 26 blocks/row × 176 B × 202,048 rows. The
embedding table rides Q4_K on this artifact through the dedicated
muser_embedding_q4k kernel (§6.7). This is Chapter 5’s bitrate analysis
made flesh: bulk at 4.5 bits, promoted tensors at 5.5/6.5625, averaging the
whole-artifact 4.81 bits/weight computed in Chapter 5.
The draft rows (draft.*) preview Ch 8: the
DFlash assistant is itself kquant — its loader requires Q4_K/Q5_K/Q6_K
([crates/muser-engine/src/dflash/weights.rs:148-158]).
6.6 The bytes-per-token tie-back to Chapter 1
Chapter 1’s whole thesis was “one token ≈ stream the model.” Now you can
compute that stream precisely for one projection. A
matvec — a matrix-by-vector multiply, one dot product
of the weight row against the input vector per output element; the shape
every projection takes when exactly one token is in flight (Ch 13
derives it from zero) — reads n_in / 256 super-blocks per row; each row
of the q projection (n_in = 6656) is:
26 blocks × 144 B = 3,744 B per row × 4,096 rows = 15,335,424 B per q matrix
Do the same for every tensor class in Figure 6.6 and sum: you converge on
the artifact’s own 16,756,681,056 B (≈ 16.76 GB decimal, ≈ 15.6 GiB — this
book follows docs/memory-footprint.md in using decimal GB). That file
is Chapter 1’s per-token weight read, and its size is not an accident:
it is 27.9 B parameters at the mixed 4.5/5.5/6.5625-bit rates of Figure
6.6. The block layout is row-major — each output row
is a contiguous run of super-blocks — which is exactly why a matvec kernel
can stream it (§6.7) and why prefill of T tokens costs roughly the same
DRAM traffic as one token ([crates/muser-engine/src/weights.rs:4-7]).
6.7 The kernels that eat these bytes
A format is only worth what the code reading it is worth, so the next question is: whose kernel actually touches these bytes? The answer is not “ours,” and that turns out to be a deliberate choice with a numerical argument behind it rather than a shortcut.
Muser deliberately runs kquant matmuls through three sources
(recall Chapter 4). For a single-token decode projection
(encode_quantized_matmul, tokens = 1), the first choice is the pinned
llama.cpp metallib:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/metal/encode/qkv.rs:429
if tokens == 1 {
if let Some(pipeline) = self.ggml_matvec(dtype) {
let (block_bytes, rows_per_group) = match dtype {
GgmlType::Q4_K => (144, 2),
GgmlType::Q5_K => (176, 1),
GgmlType::Q6_K => (210, 2),
_ => unreachable!("ggml_matvec returned only for K-quant projections"),
};
// …
}
ggml_matvec resolves to kernel_mul_mv_q4_K_f32, kernel_mul_mv_q5_K_f32,
or kernel_mul_mv_q6_K_f32 from the metallib loaded via
MUSER_GGML_METALLIB
([crates/muser-engine/src/metal/encode.rs:278-280]). The launch geometry
is named in prose as the contract requires: grid = n_out ÷ (rows_per_group × 2) threadgroups, threadgroup size (32, 2) — 64
threads, two SIMD groups (Chapter 2’s 32-lane
hardware execution unit), Q4_K/Q6_K computing two rows per group and Q5_K
one ([crates/muser-engine/src/metal/encode/qkv.rs:444-448]).
Now look at what the unreachable! arm quietly admits. Without the
metallib there is no Muser-authored single-token Q6_K matvec. Only Q4_K and
Q5_K have hand-written siblings — muser_matvec_q4k_4r2s and
muser_matvec_q5k_4sg — so when the pinned metallib is absent the fallback
switch does not degrade gracefully on Q6_K, it panics
[crates/muser-engine/src/metal/encode/qkv.rs:451-459]. That is the
fail-closed reflex again, and it is the right reflex: a wrong logit is worse
than no logit.
When the ferrite-lineage fallback does run, here is the Q4_K kernel’s core — read it against §6.3:
// crates/muser-engine/src/shaders/muse_reference.metal:735
kernel void muser_matvec_q4k_4r2s(
device const uchar *weights [[buffer(0)]],
device const float *input [[buffer(1)]],
device float *output [[buffer(2)]],
constant uint &rows [[buffer(3)]],
constant uint &cols [[buffer(4)]],
uint group [[threadgroup_position_in_grid]],
uint lane [[thread_index_in_simdgroup]],
uint simd [[simdgroup_index_in_threadgroup]]) {
uint block_count = cols / 256;
uint row_bytes = block_count * 144;
uint base_row = group * 8 + simd * 4;
// …
for (uint block_index = 0; block_index < block_count; ++block_index) {
for (uint row_index = 0; row_index < active_rows; ++row_index) {
device const uchar *block = row[row_index] + block_index * 144;
uint delta = *reinterpret_cast<device const uint *>(block);
float d = float(as_type<half>(ushort(delta & 0xffff)));
float dmin = float(as_type<half>(ushort(delta >> 16)));
uint sd0 = *reinterpret_cast<device const uint *>(block + 4);
uint sd1 = *reinterpret_cast<device const uint *>(block + 8);
uint sd2 = *reinterpret_cast<device const uint *>(block + 12);
float d_scale[8];
float neg_min[8];
muser_decode_all_q4k_scales(d, dmin, sd0, sd1, sd2, d_scale, neg_min);
uint input_base = block_index * 256;
for (uint quant_group = 0; quant_group < 4; ++quant_group) {
uint packed = uint(block[16 + quant_group * 32 + lane]);
float low = input[input_base + quant_group * 64 + lane];
float high = input[input_base + quant_group * 64 + 32 + lane];
accumulator[row_index] +=
fma(d_scale[quant_group * 2], float(packed & 0x0f), neg_min[quant_group * 2]) * low;
accumulator[row_index] +=
fma(d_scale[quant_group * 2 + 1], float(packed >> 4), neg_min[quant_group * 2 + 1]) * high;
}
}
}
(Lines elided: the row-pointer setup and the final simd_sum reduction —
see the file.) Every offset you computed by hand is there: bytes 0–3 read
as one uint and split into the two f16s, bytes 4–15 as three uints
feeding the 8-way scale decode (muser_decode_all_q4k_scales,
muse_reference.metal:41), nibbles from byte 16 onward with the low/high
sub-block pairing of §6.3. The lane geometry (one lane per byte, 32 lanes
covering one 64-element group per iteration) is Part IV material —
Ch 13 walks it properly.
What a matvec must read per block, then: 4 bytes of headers, 12 bytes of packed scales, and 128 bytes of nibbles, per 144-byte super-block, per row — plus the activation vector it is dotted against. Nothing else exists to read; the block is entirely self-describing. That is the access pattern in one sentence, and it is why the bytes-per-token accounting of §6.6 is exact.
Batch shapes get their own kernels, all selected in
encode_quantized_matmul — the dispatch ladder of Figure 6.7
([crates/muser-engine/src/metal/encode/qkv.rs:414-641]):
token count route (kquant) source
─────────── ────────────────────────────────────────────── ──────────
1 kernel_mul_mv_q{4,5,6}_K_f32 llama metallib
2–3 same kernel, one launch per token llama metallib
4–8 mul_mv_ext family (rows-per-TG 2/3/4/5) llama metallib
16 m16_q{4,5,6}k_n32 weight-stationary tile muser (ferrite)
Q4_K, ≥16, matmul_q4k_batch_sgm_aligned muser (ferrite)
aligned
any (else) kernel_mul_mm_q{4,5,6}_K_f32 aligned/bounds llama metallib
Figure 6.7: The kquant dispatch ladder. MUSER_CROSS_VENDOR_QK=1 swaps
any rung for the strict-f32 muser_cross_vendor_q* kernels (Chapter 4’s
second source), and MUSER_MULTI_COL_VERIFY gates an exact multi-column
verify route ([crates/muser-engine/src/metal/encode/multicol.rs:12-14]).
Why keep llama’s batch boundaries at all? Look at the ladder and an obvious simplification suggests itself: the middle rungs could just call the single-token kernel repeatedly, once per activation row, and a whole family of kernels would disappear from the engine. It is the kind of cleanup that looks free. The comment in the dispatch exists to close that door, and it is the chapter’s most important citation:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/metal/encode/qkv.rs:476
// Match the source-pinned llama.cpp Metal dispatch boundary exactly:
// K-quant projections with four through eight activation rows use
// `mul_mv_ext`, with a token-count-specific number of rows per
// threadgroup. This changes the floating-point reduction order, so
// substituting repeated decode GEMVs here breaks embedding/logprob
// numerical parity even when every other layer is identical.
}
That is worth restating slowly, because it is the second genuinely hard idea in this chapter. Floating-point addition is not associative: add the same products in a different order and you get a different sum, usually differing only in the last bits. A different kernel means a different reduction order, a different order means different last bits, and different last bits mean a logprob that no longer matches the comparator’s. So these batch boundaries are not a performance-tuning quirk inherited by accident. They are part of the numerical contract. Match the comparator’s kernels and you inherit its arithmetic; improvise, and parity is gone before anyone has asked a speed question.
The embedding kernel completes the single-token picture. muser_embedding_q4k
([crates/muser-engine/src/shaders/muse_reference.metal:961]) is one
thread per output element: it resolves row_bytes = (hidden_dim/256) × 144
— the same 3,744-byte arithmetic as §6.6 — and calls the shared
muser_q4k_value scalar dequant (muse_reference.metal:946), which is
§6.3’s formula indexed by element rather than streamed by block.
6.8 Tradeoffs
The n32 tile: measured occupancy over intuition. Start with the fork.
The 16-row batch — the DFlash verify shape of Chapter 8 — had to run on
something, and the retained kernels already covered it. Keeping them was
the cheap answer, and on paper it looked like the right one: same shapes,
same arithmetic, only the batch width had changed. The bandwidth counter
disagreed. Those projections came back at ~50–180 GB/s on an ~800 GB/s-class
M3 Ultra, and the reason was structural rather than arithmetic: the
n_out/64 K-serial threadgroup shape carried 6–20 KiB of
threadgroup memory, and a threadgroup that fat could not fill a core
[ledger §Stage B L0]. The kernel was not doing too much work. It was
leaving most of the machine standing idle.
That reframed the problem from arithmetic to occupancy — and occupancy is a
shape question, so the microbenchmark-first L-series went shape-hunting
before anything touched the engine. The t128/t64/cross-sg designs were
iterated and none of them won. What won was a weight-stationary tile,
m16_q*k_n32: 32 output rows per threadgroup, 64-K stages, 6 KiB of
threadgroup memory. The verify-cycle matmul estimate fell 148.3 →
82.8–85.0 ms against the retained SGM tile, with the pinned mul_mm
measured at ~177 for scale [ledger §Stage B L0]; in the integrated engine
the verify forward followed it down, 202.3 → 128.4 ms
[ledger §Stage B L1].
Speed alone would not have shipped it. Exactness was gated, not assumed: the
tile landed “within the accepted half-staged error envelope (max-abs
identical to the pinned kernels on the same data, zero argmax flips on every
shape)” [ledger §Stage B L0]. That sequence is the house style — hypothesis
(occupancy), apparatus (muser-m16-bench at the exact Figure 6.6 shapes),
then a gate — and the lesson outlives this particular tile: when a kernel
runs far under the machine’s rated bandwidth, suspect its shape before you
suspect its math.
Q5_K at 5.5 bits on the lm_head — and the transcode temptation. The promoted lm_head is the one place this artifact pays a premium you can see from orbit, so it is fair to ask what that premium buys and whether a cheaper trick could have dodged it. The 5-bit lm_head costs 924,571,648 B (§6.5 arithmetic) — 1.22× a Q4_K equivalent (756,467,712 B).
There is a tempting move available here, and the tree already contains a
worked example of it. The Q5_0 sibling has an instructive in-tree trade:
transcode_q5_to_q8 expands 5-bit blocks to Q8_0 at GPU upload time,
“trading 30 % more memory bandwidth for 3× fewer GPU decode instructions”
[crates/muser-engine/src/quant/blocks.rs:100-103]. The reason that
digression matters for the lm_head is that it is the same class of trade
the n32 tile makes, run in the opposite direction: one spends bandwidth to
buy decode instructions, the other spends decode work to buy occupancy. On
the live path the lm_head takes neither deal. It stays Q5_K and rides
m16_q5k_n32 at 16 rows: 5.87 → 4.11 ms per dispatch against mul_mm
[ledger §Stage B L0].
Why not Q6_K everywhere? By Figure 6.1, Q6_K is 6.5625/4.5 = 1.458× the bytes of Q4_K. Every weight byte is read per token (§6.6), so Q6_K-everywhere would scale the per-token stream by ~1.46× and, in the bandwidth-bound regime Chapter 1 proved, cut throughput by the same factor. The artifact spends 6.5625 bits only where the recipe says it pays (ffn_down on half the layers, 26 k/v tensors — Figure 6.6); whether that specific allocation is the quality optimum is the recipe designer’s claim, not a Muser measurement [unverified].
6.9 Where the gap lives
Every chapter in this book has to answer the same question about its own subject: is this where the missing time hides? Here the answer is no — and the way that answer was reached matters more than the answer itself, because it was reconciled rather than assumed.
The quant format is not the decode gap. When the engine’s one-token graphs
were reconciled closure-by-closure (the +196 dispatch-gap diagnosis), the
families were 104 norm-boundary groups, 39 SWA staging groups, 52
KV-publication splits, one copy — not matvec arithmetic
[docs/decode-dispatch-gap-20260815.md]. The kquant matvecs themselves run
on llama.cpp’s own pinned kernels for numerical parity
([crates/muser-engine/src/metal/encode.rs:278-280]), so on this lane the
weight-format question was settled before it could become a gap: same
bytes, same kernels, same arithmetic order as the comparator. The lane’s
measured decode ratios — 1.0274–1.0504× across the six-depth plain matrix,
five exact-token reps per depth [docs/benchmarks.md §1] — carry the
paradox this book keeps meeting: parity engines can still differ in
dispatch, and the gap lives there, not here.
6.10 What comes next
The kquant family is integer codebooks: uniform grids, local scales, a min where the format wants one. Chapter 7 meets the other family — NVFP4, where the 4-bit payload is itself a tiny float, the block scale is itself a tiny float, and the whole lane was built for the same bytes-per-token argument with a different arithmetic inside. You will pack one of its groups by hand (using the repo’s own fixture bytes), watch the fail-closed loader bind its scale tensors, and read the Metal kernel that keeps the whole contraction in integers — and you will see why this lane’s 35.491 tok/s must never be called “faster” than kquant’s 35.440.
References
[crates/muser-engine/src/gguf/types.rs:9-127]—GgmlType, block sizes/elements (Figure 6.1’s source), the NVFP4_E2M1/F8_E4M3FN doc.[crates/muser-engine/src/quant/k_block.rs:12-49]—dequant_q4_k, this chapter’s primary source (§6.2, §6.3).[crates/muser-engine/src/quant/k_block.rs:51-105]—dequant_q5_kand the 176-byte layout doc.[crates/muser-engine/src/quant/k_block/q6.rs:15-75]—dot_q6_k_f32_llama: the 210-byte layout (ql/qh/sc/d offsets), signed-code extraction, deferred-scaling order.[crates/muser-engine/src/loader.rs:72-91]—weight_precision: fail-closed lane selection (q4_k_xldefault).[crates/muser-engine/src/decode.rs:136-139],:1209— Metal-path dtype admissions for projections and the embedding.[crates/muser-bench/src/m16.rs:137-226]— theSHAPEStable: dtypes, real dimensions, per-cycle multiplicities (Figure 6.6).[crates/muser-engine/src/metal/encode/qkv.rs:414-641]—encode_quantized_matmul: the full dispatch ladder (Figure 6.7), including the source-pinned boundary comment at:476.[crates/muser-engine/src/metal/encode.rs:278-280]— the pinnedkernel_mul_mv_q{4,5,6}_K_f32metallib PSOs.[crates/muser-engine/src/shaders/muse_reference.metal:735-788]—muser_matvec_q4k_4r2s;:41-62muser_decode_all_q4k_scales;:961-977muser_embedding_q4k.[crates/muser-engine/src/quant/blocks.rs:100-103]— the Q5→Q8 transcode trade comment.[crates/muser-engine/src/weights.rs:4-7]— row-contiguity / prefill DRAM-amortization note.[docs/decode-dispatch-gap-20260815.md]— the +196-closure reconciliation (§6.9).[ledger §Stage B L0],[ledger §Stage B L1]—docs/goal-parity-ledger-2026-08.md, the M16 microbenchmark and integration entries (§6.8’s ms figures).[docs/benchmarks.md §1]— the six-depth plain matrix ratios.[claims #11]— kquant 35.440 / NVFP4 35.491 tok/s scope (§6.1, §6.9).- Ch 5 — the format-agnostic template this chapter instantiated.
- [ferrite-book Ch 5] — the ancestor Q4_K chapter; Muser’s
dequant_q4_kis the same extraction lineage (NOTICE,docs/extraction-manifest.md).
Chapter 7 — NVFP4: the native lane
status: polished · path: Muse Glimmer, pinned Muser tree
Prerequisites: Chapters 5 and 6. You know what a codebook, a block scale, and a bitrate are; you have unpacked kquant super-blocks by hand. This chapter does the same for the other weight lane — NVFP4 — and then tells the lane’s measured story, which is a story about what a format may and may not claim.
Chapter 6 left the kquant family as integer codebooks: uniform grids under local scales, in a 16.76 GB artifact that the reference lane streams every token. NVFP4 — Native Float Precision 4-bit, the format of NVIDIA’s Blackwell FP4 tensor cores — is the same budget spent on a different idea: the 4-bit payload is itself a tiny floating-point number, and so is its block scale. Muser’s native lane decodes the Muse Glimmer weights directly from this format on the Mac, and the same format runs the remote GX10 prefill producer (Ch 28).
The chapter has two halves. The first is bytes and arithmetic: the e2m1 codebook, the e4m3fn scale, the fail-closed loader, and the Metal decode kernels. The second is discipline: what the measurements permit this lane to claim — and the one claim it must never make.
7.1 The format in one line — and its price tag
What does a weight format actually have to promise before anyone can trust it? Three things: how to read the payload, how to scale it, and in what order to multiply the pieces back together. Skip the third and you do not have a format, you have a suggestion — two implementations can agree on every byte on disk and still disagree on the answer.
Muser’s own CPU oracle states all three in its module doc, order included:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/quant/nvfp4.rs:1
//! CPU oracle for NVIDIA NVFP4 weights.
//!
//! The product format keeps E2M1 values packed two per byte, one raw E4M3FN
//! scale per 16 values, and one f32 `scale2` per tensor. The operation order
//! is pinned to the ModelOpt/MLX reference: `(e2m1 * e4m3fn) * scale2`.
}
Deconstruct each term:
- E2M1 is the 4-bit float codebook: 1 sign, 2
exponent, 1 mantissa bit. Sixteen entries, and Muser lists them all
(
[crates/muser-engine/src/quant/nvfp4.rs:7-9]):
code : 0 1 2 3 4 5 6 7
value : 0.0 0.5 1.0 1.5 2.0 3.0 4.0 6.0
code (|S) : 8 9 10 11 12 13 14 15
value :-0.0 -0.5 -1.0 -1.5 -2.0 -3.0 -4.0 -6.0
Figure 7.1: The complete E2M1 codebook — there is nothing else it can mean. Note the spacing: 0.5-steps up to 2, then 3, 4, 6 — coarser as magnitude grows, the signature of a float grid.
- E4M3FN is the 8-bit block scale: 1 sign, 4 exponent, 3 mantissa, bias
7, no infinities, maximum magnitude 448, and — the “FN,” finite — a
NaN at only two encodings (
0x7f/0xff), which the loader rejects outright ([crates/muser-engine/src/weights.rs:244-248]). - One scale per 16 values, packed two values per byte, plus one f32
scale2per tensor.
The bitrate arithmetic, shown as always:
4 bits/weight (payload) + 8 bits / 16 weights (E4M3FN scale) = 4.5 bits/weight
Exactly 4.5 — the same figure as Q4_K, reached with a 16-element block and
a one-byte float scale instead of a 256-element super-block with 6-bit
integer sub-scales. (The per-tensor f32 scale2 adds 4 bytes per tensor:
at the q projection’s 27.3 M weights, that is 1.2×10⁻⁷ bits/weight —
nothing.) The GGUF type registry says it out loud: the companion-tensor
design “keeps the serving representation at exactly 4.5 bits/weight”
([crates/muser-engine/src/gguf/types.rs:33-36]).
One NVFP4 row (n_in values) — three separate regions:
packed E2M1 E4M3FN scales (per-tensor, once)
┌────────────────┐ ┌──────────────┐ ┌─────────┐
│▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓│ ... │▓ ▓ ▓ ▓ ... ▓ │ ... │ scale2 │
└───────┬────────┘ └──────┬───────┘ └─────────┘
n_in/2 bytes n_in/16 bytes f32
2 values per byte 1 scale per 16 values whole tensor
16-value group detail:
[ w0w1 ][ w2w3 ][ w4w5 ][ w6w7 ][ w8w9 ][w10w11][w12w13][w14w15] [ s ]
1 B 1 B 1 B 1 B 1 B 1 B 1 B 1 B 1 B
lo nibble = even index, hi nibble = odd index; s decodes by Figure 7.1.
Figure 7.2: The NVFP4 layout. A group is 9 bytes of storage for 16 values
— 4.5 bits each — and the dequant order is pinned: (e2m1 × e4m3fn) × scale2, never regrouped.
7.2 A worked dequant, using the repo’s own fixture
A codebook you have only read about is a codebook you do not yet trust. So
before the loader, before the kernels, before any measurement, we decode a
real group by hand and then check our answer against the repo’s own.
Muser’s unit test for the loader carries a canonical group, and we will
dequantize its first bytes with every multiply shown. The fixture: eight
packed bytes 10 32 54 76 98 BA DC FE, one scale byte 0x38, and
scale2 = 0.25 ([crates/muser-engine/src/weights.rs:462-465, 519-536]).
Decode the scale first. 0x38 = 0b0011_1000: sign 0; exponent =
(0x38 >> 3) & 0xF = 7; mantissa = 0x38 & 7 = 0. Normal case
([crates/muser-engine/src/quant/nvfp4.rs:118]):
scale = (1 + 0 × 0.125) × 2^(7−7) = 1.0
Then the weights, in pinned order — look up Figure 7.1, multiply by the scale, multiply by scale2:
byte 0x10 → lo nibble 0x0 → 0.0 → 0.0 × 1.0 × 0.25 = 0.000
→ hi nibble 0x1 → 0.5 → 0.5 × 1.0 × 0.25 = 0.125
byte 0x32 → lo nibble 0x2 → 1.0 → 1.0 × 1.0 × 0.25 = 0.250
→ hi nibble 0x3 → 1.5 → 1.5 × 1.0 × 0.25 = 0.375
byte 0x54 → lo 0x4 → 2.0 → 0.500 hi 0x5 → 3.0 → 0.750
byte 0x76 → lo 0x6 → 4.0 → 1.000 hi 0x7 → 6.0 → 1.500
byte 0x98 → lo 0x8 → −0.0 → −0.0 hi 0x9 → −0.5 → −0.125
The test asserts exactly this row — 0, 0.125, 0.25, 0.375, 0.5, 0.75, 1.0, 1.5, −0.0, −0.125, … — bit-for-bit
([crates/muser-engine/src/weights.rs:527-535]). The GPU agrees: a
dedicated fixture kernel muser_nvfp4_dequant_fixture computes
(muser_e2m1(nibble) * muser_e4m3fn(scales[index/16])) * scale2 per
element and is tested bit-exact against this CPU oracle for every finite
E4M3FN byte — all 254 of them
([crates/muser-engine/src/shaders/nvfp4.metal:775-788],
[crates/muser-engine/src/metal/encode/qkv.rs:674-699]). The sweep is
exhaustive on purpose: a decode table this small can be checked completely
instead of sampled, and anything that can be checked completely should be.
Two format details worth internalizing before the loader:
- The rounding contract is pinned too. Quantizing into e2m1 uses
ModelOpt’s ties-to-even midpoints (
e2m1_from_f32, with the boundary table at[crates/muser-engine/src/quant/nvfp4.rs:150-171]; the GPU twinmuser_e2m1_roundatnvfp4.metal:121). A format is not just its decode table — it is also how you got there. - The
scale2is a real second factor, not decoration: it is what lets every per-16 scale stay small while tensors span orders of magnitude. The loader requires it to be finite and positive ([crates/muser-engine/src/weights.rs:263-267]).
7.3 The loader path: fail-closed companions
The format promises a payload, a scale, and an order. What breaks if one of those pieces is missing, mis-shaped, or hiding a NaN? Nothing loud, and that is exactly the danger. A wrong scale tensor does not crash the model. It makes it quietly and unevenly worse, in a way no smoke test catches and no user can report precisely. The native lane’s answer is to refuse to start at all.
NVFP4 tensors never stand alone. Each weight matrix carries two (or three)
companion tensors, named by suffix ([crates/muser-engine/src/weights.rs:25-27]):
token_embd.weight ← NVFP4_E2M1 payload (n_in/2 × n_out bytes)
token_embd.weight.nvfp4_scale ← F8_E4M3FN, shape [n_in/16, n_out]
token_embd.weight.nvfp4_scale2 ← F32 scalar, one per tensor
token_embd.weight.nvfp4_input_scale_inv ← optional F32 scalar (W4A4 only)
The lane itself is selected by metadata, and the pairing is strict in both
directions ([crates/muser-engine/src/loader.rs:72-91]): native NVFP4
tensors present ⇒ muser.weight_precision must say nvfp4; the string
nvfp4 with no native tensors ⇒ error; absent or q4_k_xl ⇒ the kquant
lane of Chapter 6. Then MuseWeights::open validates every companion
before any inference: the scale tensor must exist, be F8_E4M3FN, and
match the exact [n_in/16, n_out] shape; it must contain no NaN scale
byte; scale2 must be scalar F32, finite, positive
([crates/muser-engine/src/weights.rs:220-267]). A missing companion is a
load-time MissingTensor error — there is no “degrade and continue.”
The optional fourth tensor decides the arithmetic mode — and the loader
enforces that too. input_scale_inv present ⇔ muser.activation_precision = nvfp4; a mismatch fails the load
([crates/muser-engine/src/weights.rs:200-216]). Its meaning:
- absent ⇒ weight-only W4A16: activations stay wide, and the Mac
quantizes them per super-block to a Q8-K-style integer grid at compute
time (§7.5). This is the product configuration — the selected candidate
is “weight-only W4A16 and has no activation-global-scale tensor”
[ledger §P1.4]. - present ⇒ W4A4: activations are dynamically quantized to FP4 in groups of 16 before the dot — the compressed-tensors scheme the Blackwell producer’s tensor cores execute natively. The Mac supports it (for batched verify parity with the producer), and §7.6 shows where that permission ends.
Row addressing falls out of Figure 7.2 and is worth one glance at real
code — dequant_row for a NVFP4 tensor slices n_in/2 packed bytes and
n_in/16 scale bytes per row ([crates/muser-engine/src/weights.rs:82-94]),
the same row-major contiguity kquant enjoys.
7.4 The Metal lane, end to end
Bytes on disk are only half the contract; the other half is which kernel reads them. And that choice is not the kernel author’s to make at runtime — it was already settled at load time, by the presence or absence of the optional fourth companion tensor.
On the Metal side, every projection routes through
encode_projection, which dispatches on exactly the dtype set Chapter 6
listed ([crates/muser-engine/src/decode.rs:6044-6091]): F16 →
encode_f16_matmul, NVFP4 → encode_nvfp4_matmul, else the kquant
ladder. The NVFP4 encoder is where the mode split becomes kernel selection
(Figure 7.3; [crates/muser-engine/src/metal/encode/qkv.rs:128-227]):
encode_nvfp4_matmul(packed, scales, scale2, input_scale_inv, …)
input_scale_inv = None → muser_nvfp4_a16_q8_matvec (weight-only, W4A16)
grid (n_out/8 × columns), 256 threads
input_scale_inv = Some → 16-column batch, n_in % 64 == 0:
muser_nvfp4_w4a4_m16_n32 (weight-stationary tile)
else widths 16/8/4/2/1:
muser_nvfp4_w4a4_matvec_c{1,2,4,8,16}
grid n_out, 32 threads (one SIMD group per row)
Figure 7.3: The native-lane projection dispatch. The Nvfp4Args triple
(n_in, n_out, col0) rides buffer 4; scale2 buffer 5; input_scale_inv
buffer 6. All three kernels live in the no-fast-math cross-vendor library
(Chapter 4’s second source) — the integer contractions must not be
reassociated by the compiler ([crates/muser-engine/src/metal/encode/qkv.rs:203-205]).
7.4.1 The plain decode kernel (muser_nvfp4_matvec_c1 family)
The A16 matvec template (the Ch 6 term — one dot product per output row), trimmed to its arithmetic core:
// crates/muser-engine/src/shaders/nvfp4.metal:183
template <ushort NC>
inline void muser_nvfp4_matvec_impl(
device const uchar *packed,
device const uchar *scales,
device const float *input,
device float *output,
constant muser_nvfp4_args &args,
constant float &scale2,
uint row,
ushort lane) {
// …
for (uint group = uint(lane); group < args.n_in / 16; group += 32) {
const float block_scale = muser_e4m3fn(scales[scale_row + group]);
const uint packed_base = packed_row + group * 8;
const uint element_base = group * 16;
for (ushort column = 0; column < NC; ++column) {
device const float *x = input + (args.col0 + uint(column)) * args.n_in;
float block_sum = 0.0f;
muser_nvfp4_accumulate_codes8(
packed + packed_base, x + element_base, block_sum);
muser_nvfp4_accumulate_codes8(
packed + packed_base + 4, x + element_base + 8, block_sum);
const float scaled = block_sum * (block_scale * 16384.0f);
sums[column] += scaled;
}
}
for (ushort column = 0; column < NC; ++column) {
const float total = simd_sum(sums[column]) * scale2;
if (lane == 0) {
output[(args.col0 + uint(column)) * args.n_out + row] = total;
}
}
}
One lane pair per 16-value group, the group’s scale applied once per block,
simd_sum across the 32 lanes of a SIMD group
(Ch 2), scale2 once per row — the exact pinned order
of §7.2, vectorized. The one mystery is the 16384.0f (= 2¹⁴). The
accumulate helper decodes eight E2M1 nibbles as f16 bit patterns
shifted into place — each value embedded as an exact half multiple of
2⁻¹⁴ — so the helper’s sum is 2⁻¹⁴ × the true block sum, and the caller
“folds 2^14 into the block scale after accumulating one complete 16-value
block” ([crates/muser-engine/src/shaders/nvfp4.metal:155-158]). The
comment names the lineage: “the same half-bit embedding used by MLX’s
native NVFP4 kernels.” This is how you turn a 16-entry float LUT into pure
bit-slicing on a GPU.
Turn that around, because it is the kind of trick that only clicks the second time: the kernel never looks a value up. It shifts nibbles straight into half-precision bit patterns, which is fast, but leaves every decoded value shrunk by one fixed power of two. The kernel pays that debt back exactly once per block, folded into the block scale, where it costs a single multiply instead of one per element.
7.4.2 The weight-only contraction (muser_nvfp4_a16_q8_matvec)
Ask what can go wrong inside a dot product and the pedantic answer turns out to be the right one: the order in which you add the terms. On a single machine that order is a rounding footnote nobody reads. Across two machines it becomes a disagreement nobody can debug.
So the product lane’s decode kernel is stricter than floats: it makes the
whole block contraction integer-exact. Per 256-value activation
super-block, the threadgroup quantizes the activation to a Q8-K-style
integer grid once — signed first absolute maximum, iscale = −127/first_max, magic-number round-to-nearest-even, clamp at 127 — then
shares the integers across eight output rows. Inside a 16-value group the
dot runs on raw integer codes:
// crates/muser-engine/src/shaders/nvfp4.metal:682
for (ushort pair = 0; pair < 8; ++pair) {
const uchar byte = row_packed[packed_base + uint(pair)];
dot += muser_e2m1_q1(byte & 15) * int(q8[quant_base + uint(pair) * 2u]);
dot += muser_e2m1_q1(byte >> 4) * int(q8[quant_base + uint(pair) * 2u + 1u]);
}
weighted = long(dot) * long(muser_e4m3_q9(row_scales[scale_group]));
The helpers are the trick from §7.2 turned algebraic: muser_e2m1_q1
decodes each weight as an integer in units of 2⁻¹, muser_e4m3_q9 each
scale as an integer in units of 2⁻⁹ (nvfp4.metal:28-42; the CPU oracle’s
e2m1_q1/e4m3fn_q9 at [crates/muser-engine/src/quant/nvfp4.rs:17-51]
prove the equivalence exhaustively). Their products share one fixed
denominator, so the entire block contraction is an order-free i64 sum.
Only the epilogue is floating point — and it is pinned to four scalar
operations:
// crates/muser-engine/src/shaders/nvfp4.metal:691
float contribution = fma(float(integer_total), 0x1p-10f, 0.0f); // 2^-10 = Q1 × Q9
const float q8_scale = 1.0f / block_iscale;
contribution = fma(contribution, q8_scale, 0.0f); // activation scale
contribution = fma(contribution, weight_scale2, 0.0f); // tensor scale
total = fma(1.0f, contribution, total); // sequential accumulation
— then one final float(half(...)) at the output, because the producer’s
linear layers write F16 results and “otherwise Q/K RMS normalization
amplifies hidden low bits” ([crates/muser-engine/src/quant/nvfp4.rs:331-337]).
The CPU oracle dot_nvfp4_a16_q8_f32 performs the identical sequence
([crates/muser-engine/src/quant/nvfp4.rs:349-379]).
Now the question this section opened with has its answer. The lane must
match a CUDA producer whose reduction topology no Metal simd_sum can
reproduce — so rather than chase that topology, the design removes the need
for it. Make the parallel part exactly associative, and the only rounding
decisions left are the four named scalar FMAs above, in an order both
machines can agree to in writing. Chapter 32 tells the full trust story;
file the technique now.
7.4.3 The W4A4 contraction and the F16 tail
The other mode is the one the producer speaks, and the Mac keeps it so the
two machines can be compared on identical arithmetic rather than on faith.
When input_scale_inv is present, the kernel quantizes each 16-value
activation group to its own E2M1 + E4M3FN pair (the CPU oracle at
[crates/muser-engine/src/quant/nvfp4.rs:174-238]), then contracts
weight-Q1 × activation-Q1 × weight-scale-Q9 × activation-scale-Q9 as one
i64 integer sum per group — denominator 2⁻²⁰ — with the epilogue × 2^-20, × weight_scale2, × (1/input_scale_inv), and the same F16
boundary ([crates/muser-engine/src/shaders/nvfp4.metal:295-308]). The
16-column batch form (muser_nvfp4_w4a4_prequant_m16_n32) splits the
activation quantization into its own pass so each group is quantized once
and reused across every N=32 output tile
([crates/muser-engine/src/metal/encode/qkv.rs:8-65]).
The lane’s F16 tail is real code, not a footnote: the unquantized F16
LM head runs on muser_f16_matvec_c* — plain half4 dot products
([crates/muser-engine/src/shaders/nvfp4.metal:704-752]) — and the
embedding on muser_embedding_f16 (nvfp4.metal:755), both dispatched by wrappers that pick the F16 route
by byte length or dtype ([crates/muser-engine/src/metal/encode/qkv.rs:348-360]).
“Checkpoint mandates an unquantized F16 language head” is a property of
this artifact, recorded in the ledger (§7.5).
7.5 The measured lane: parity within noise — never “faster”
So: is the native lane faster? That is the question the whole format was supposed to answer, and answering it honestly turns out to require the most carefully worded sentence in this chapter.
Here is the lane’s headline measurement, quoted from the ledger’s P1.3
decode gate — five-rep cell, same 66-token prefix, 32 teacher-forced
tokens, F16 KV, flash attention, release binary, adjacent lease window
([ledger §P1.3]):
lane mean ns / 32 tokens CV tok/s
─────────── ───────────────────── ───────── ─────────────
native NVFP4 901,644,358.4 0.13 % 35.490711722
kquant ctrl 902,946,575.0 0.037 % 35.439527527
native = 1.001444269× the adjacent kquant control (+0.144427 %)
Figure 7.4: The P1.3 paired decode cells
([docs/goal-parity-ledger-2026-08.md]; receipt SHAs in the entry).
Read the discipline in the numbers. A 0.14% difference between cells whose
CVs are 0.03–0.13% is parity within noise, and the claims register locks
the wording: “plain Mac NVFP4 35.491 tok/s versus adjacent kquant 35.440
tok/s remain valid at their original scopes” — with the standing
instruction “Never call decode faster” [claims #11]. The book will
not phrase it loosely either: the native lane’s decode is
parity-within-noise, full stop.
Why is the gate so narrow — shouldn’t 4.5-bit float weights with an
integer-exact kernel fly? The ledger’s retained diagnostics answer:
“the NVFP4 layer stack is faster, but this checkpoint mandates an
unquantized F16 language head (about 3.46 ms/token versus the kquant
head’s 1.75 ms)” [ledger §P1.3]. The F16 head alone eats most of what
the 52-layer FP4 stack saves. So much for speed. The lane’s other two
anchors are about quality, and they are gates rather than boasts.
The first is determinism. The standard 2,048-token fixture is
deterministic and token-identical versus the exact anchor, with bounded
nonzero logit drift: max/mean absolute error 7.270581/1.040619 at 32
tokens, and 10.884401/1.233789 over the five-rep 2,048/256 comparator
[docs/nvfp4-fast-lane-evidence-20260817.md §Determinism]. Nonzero is
the load-bearing word. The tokens match; the logits underneath them do
not, and saying “zero drift” — the easier, rounder sentence — is
explicitly prohibited wording [claims #10].
The second anchor is where the cost of the format finally becomes visible.
At depth, one content class — documentation and digest text at 65,536
tokens — exceeds its calibrated top-token band, 15.134% against a 13.339%
gate. It did not replicate across documents, so it is published as exactly
what it is: a content-local sensitivity, with the kquant lane
selectable as the reference route [claims #10]. The cost of this
quantization is real, measured, and localized — Chapter 5’s §5.8 promise,
kept.
7.6 Speculative NVFP4: measured, rejected, fail-closed
The lane’s one forbidden fruit is speculation — and how we found that out teaches more than the verdict does.
The fork looked inviting from both sides. Speculative decoding earns its speed by checking many drafted tokens in a single batched forward pass, and batched verification is precisely the shape W4A4 was built for: the activations already arrive quantized in groups, and it is the arithmetic the Blackwell producer runs natively on its tensor cores. Every argument pointed the same direction. So we ran the diagnostic — native NVFP4 W4A4 batched verification, measured against the kquant speculative bar of 107.9 tok/s — expecting, at worst, the same neighborhood.
It came back at 6.805 tok/s. That is not a regression you tune away; it
is a different order of magnitude, and the retained run says plainly where
the time went. Verification alone consumed 35.915 s of a 37.619 s decode
span [ledger §F-series remediation] — the drafting was effectively free
while the verify pass ate the whole budget. We kept the run and labeled it
for what it is, “one diagnostic, explicitly unqualified”
([docs/nvfp4-fast-lane-evidence-20260817.md §Measured product numbers]). Both halves of that label are load-bearing:
one measurement is enough to stop a launch claim, and not enough to
characterize a lane.
The lesson was about shape, not about format. A lane can sit at parity-within-noise in plain decode, as this one does, and still be unqualified the moment the batch shape changes underneath it. Nothing in the decode gate above was ever evidence about verification, and reading it as such is how a slow route ships quietly.
So the disposition is recorded as Fallback B: “speculative serving
stays on the qualified
kquant lane; native NVFP4 plus DFlash is rejected by the receiver
configuration rather than silently serving the measured 6.81 tok/s route”
[docs/nvfp4-fast-lane-evidence-20260817.md §Product route], and the
claims register repeats it: “Native NVFP4 speculative decode has no launch
claim and remains fail-closed” [claims #4].
And it is code, not policy prose. A receiver configuration declaring the
native producer mode cannot even enroll a DFlash identity
([crates/muser-cluster/src/config.rs:128-131]), and the server refuses
the combination at startup:
#![allow(unused)]
fn main() {
// crates/muser-server/src/state.rs:1667
fn validate_remote_dflash_policy(
producer_mode: Option<Nvfp4ProducerMode>,
dflash_configured: bool,
) -> Result<(), InferenceLoadError> {
if producer_mode == Some(Nvfp4ProducerMode::Native) && dflash_configured {
return Err(InferenceLoadError::Remote(
"native NVFP4 fast-lane speculative decode is unqualified; omit --dflash and use plain NVFP4 decode, or route speculative serving to the kquant lane"
.into(),
));
}
Ok(())
}
}
The operator sees that message and the process refuses to serve. Ch 33 owns the full postmortem — including why a 240/240 all-accept target+DFlash diagnostic still proved nothing about throughput.
7.7 Exact vs Native: the producer-mode preview
That refusal leaves one question dangling. If the Mac decodes weights that
someone else’s GPU also understands, who decides what the numbers are
supposed to be? The Nvfp4ProducerMode enum you just saw is where that
decision is written down, and it is this chapter’s bridge to Part
VI, so meet it on its own terms ([crates/muser-cluster/src/config.rs:10-18]):
#![allow(unused)]
fn main() {
/// Numeric contract selected by the Spark NVFP4 producer. Legacy receiver
/// configurations predate the split and therefore deserialize as `None`;
/// newly generated F-series configurations must name the mode explicitly.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Nvfp4ProducerMode {
Exact,
Native,
}
}
- Native is the product: the GX10’s vLLM producer runs the deterministic Blackwell W4A4 path, prefilling KV that the Mac receives over Handoff V2 and decodes with the weights of this chapter.
- Exact is the verification anchor: an integer-dot producer mode whose
KV exists to be compared against, not served. It is selected on the
producer side —
MUSER_NVFP4_EXACTis a Python environment flag for the producer container (scripts/gx10/vllm/benchmark_native_prefill.py:99-102even refuses to benchmark native with it set; the resident daemon pinsMUSER_NVFP4_EXACT=0atscripts/gx10/vllm/muser_native_prefilld.py:446). It does not exist anywhere in Rust — a reviewer checking the Mac tree for it will find nothing, and that is by design.
The two modes use mode-separated target-cache identities, “so exact and
native KV entries cannot alias”
[docs/nvfp4-fast-lane-evidence-20260817.md §Product route]. What “good
enough” must mean when someone else’s GPU computed your prefill — bounded
logit envelopes, exact-token policies, the wizard’s gates — is
Ch 32, the trust chapter.
7.8 Tradeoffs
Four decisions in this lane could each plausibly have gone the other way. What follows is what choosing cost — measured, not argued.
Float codebook vs integer codebook at the same 4.5 bits. Chapter 5’s
two families meet head-on here. E2M1 spends its 16 codes as 15 magnitudes
plus a signed zero — one fewer value than Q4_K’s uniform grid — but buys
relative spacing (Figure 7.1: each octave halves the resolution instead
of losing it absolutely) and a float block scale (E4M3FN spans
2⁻⁹·mantissa subnormals up to 448) where kquant’s 6-bit sub-scale is an
integer under an f16 super-scale. The measured consequence of the whole
package is Figure 7.4: parity-within-noise in decode, with quality
sensitized by content class rather than collapsed
[claims #10] [claims #11]. A clean “float beats int at 4 bits” claim
is not what this program’s evidence supports — both 4.5-bit formats
land at the same throughput, and the int lane is the reference lock.
Group 16 vs super-block 256. NVFP4’s 0.5 header bits buy a scale refresh every 16 weights — 16× finer than kquant’s 32-element sub-blocks — but the scale is a full byte each. Q4_K’s two-level packing spends its 0.5 bits on more integers under two f16s. The two formats agree on the budget and disagree on everything else, which is the best evidence that 4.5 bits is a genuine economic point and not an artifact of one design.
Integer-exact contraction vs fast floats. §7.4.2’s a16-q8 kernel gives
up vectorized-float convenience to make the block dot associativity-free.
The alternative — matching CUDA’s reduction by luck — was measured out in
this program’s own wizard campaign, where a one-ULP F16 divergence in
layer-1 V snowballed into 51.7 M differing logits before the arithmetic
ABI was pinned [ledger §2b, 2026-08-24]. The cost of the integer path is
kernel complexity; the measured benefit is that “deterministic, bounded
drift” became a property rather than a hope
[docs/nvfp4-fast-lane-evidence-20260817.md §Determinism].
W4A4 on the Mac: supported, qualified for batch parity, forbidden for
speculation. The same input_scale_inv that enables
producer-parity M16 verification (§7.4.3) is the mode whose serving use
measured 6.805 tok/s (§7.6). The lane keeps the kernels, gates the
deployment — Fallback B as a design pattern: fail-closed beats
silently-slow.
7.9 Where the gap lives
Every kernel chapter owes the same accounting: where does this component’s
time go, and is it the deficit? For the native lane, “the gap” in the
dispatch-gap sense barely exists —
plain decode sits at parity-within-noise with both the kquant lane and the
comparator (§7.5), and the format is not the deficit. Where this lane’s
own numbers diverge from its hopes is quantization-shaped but not
format-shaped: the mandated F16 LM head (3.46 vs 1.75 ms/token) narrowing
the decode gate [ledger §P1.3], and the W4A4 batched verify collapsing
to 6.81 tok/s where kquant’s verify was engineered to 107.9-class
[ledger §F-series remediation]. Both are batch-shape stories — the
cost of precision hides in batch shapes and content classes, and the gates
exist to localize it.
7.10 What comes next
Two weight lanes now stand complete: kquant for the reference and speculative lanes, NVFP4 for the native product lane. The next chapter follows the one component that runs on exactly one of them — the DFlash draft model, a five-layer kquant assistant that reads the target’s hidden states and proposes tokens for exact verification. It is the smallest model in this book, the cheapest thing in memory-footprint.md’s manifest after nothing, and — thanks to one wrong constant that survived an entire campaign — the best teacher of what a draft must guarantee.
References
[crates/muser-engine/src/quant/nvfp4.rs:1-6]— the pinned format contract (§7.1);:7-51E2M1 LUT and the Q1/Q9 integer encodings;:109-171E4M3FN decode and the ModelOpt rounding boundaries;:241-255dequant_nvfp4_row;:349-379dot_nvfp4_a16_q8_f32.[crates/muser-engine/src/gguf/types.rs:33-39]—NVFP4_E2M1/F8_E4M3FNregistration and the “exactly 4.5 bits/weight” comment.[crates/muser-engine/src/loader.rs:72-91]— fail-closed lane pairing.[crates/muser-engine/src/weights.rs:25-27]— companion-tensor suffixes;:220-290nvfp4_auxvalidation (shape, dtype, NaN rejection, positive scalar scale2, activation-precision pairing);:519-536the fixture test this chapter’s worked example mirrors.[crates/muser-engine/src/shaders/nvfp4.metal:1-5]— the GPU format doc;:17-42LUT and integer twins;:155-158the MLX half-bit embedding;:183-242the A16 matvec family;:613-702muser_nvfp4_a16_q8_matvec;:704-770the F16 matvec/embedding kernels;:775-788the dequant fixture kernel.[crates/muser-engine/src/metal/encode/qkv.rs:128-227]—encode_nvfp4_matmuldispatch and geometries;:8-65the two-pass M16 prequant route;:348-360F16-layout detection;:674-699the bit-exact E4M3FN sweep test.[crates/muser-engine/src/decode.rs:6044-6091]— projection routing;:3254-3291the verify-route banner (mode names as the engine prints them).[crates/muser-cluster/src/config.rs:10-18],:128-131—Nvfp4ProducerMode; native mode cannot enroll DFlash geometry.[crates/muser-server/src/state.rs:1667-1678]—validate_remote_dflash_policy, the fail-closed Fallback B refusal.[ledger §P1.3],[ledger §P1.4],[ledger §F-series remediation],[ledger §2b 2026-08-24]—docs/goal-parity-ledger-2026-08.md: the paired decode cells, the weight-only artifact correction, the 6.81 no-go, the one-ULP wizard chase.[docs/nvfp4-fast-lane-evidence-20260817.md]— Fallback B disposition, measured product table (incl. 6.805), determinism/drift envelopes.[claims #4],[claims #10],[claims #11]—docs/launch-claims.md: native spec fail-closed; quality gates and prohibited wording; the 35.491/35.440 parity scope.[scripts/gx10/vllm/benchmark_native_prefill.py:99-102],[scripts/gx10/vllm/muser_native_prefilld.py:446]—MUSER_NVFP4_EXACTis producer-side Python only.- Ch 5, Ch 6 — the codebook/block template and the kquant counterpart.
- Ch 32, Ch 33 — the trust and speculation chapters this one forward-points to.
Chapter 8 — The DFlash draft
status: polished · path: Muse Glimmer, pinned Muser tree
Prerequisites: Chapters 5–7. You know the kquant blocks the draft is made of, the lanes it may run on, and — from Chapter 7’s Fallback B — the lane it may not. This chapter is about a model small enough to hold in your head, and about the contract that lets something approximate participate in exact inference.
Chapter 7 ended with a refusal: native NVFP4 plus DFlash is rejected by the receiver configuration, so speculative serving stays on the qualified kquant lane. This chapter opens that last clause. DFlash is Muser’s draft model — the small assistant that guesses tokens cheaply so the 52-layer target only has to confirm them — and it is a kquant artifact from end to end, down to the very blocks you unpacked in Chapter 6.
One boundary statement before anything else. Speculative decoding has two
halves: drafting (this chapter) and accept/verify (the algorithm that
decides which guesses become output). The algorithm —
verify_full_speculative_mt_ordered, the carried-frontier state machine,
the rollback choreography — gets its full treatment in
Ch 33. Here we build the
draft, load it, condition it, and pin down what it must guarantee so that
Chapter 33’s verification can stay exact.
8.1 Why a draft model at all
Start with the question the rest of the chapter is an answer to: why run a second model at all, when the whole problem is that we already cannot afford to run the first one fast enough?
Chapter 1’s fourth lever: move the work somewhere cheaper. Decode is bandwidth-bound because each token reads the whole 16.76 GB model for one token’s worth of math. Speculation attacks the denominator: if a cheap model proposes k tokens and the expensive target verifies them in one batched forward pass, then each accepted proposal cost a fraction of a full target read. The target still does all the deciding — that is what keeps the output exact — but it does k+1 rows of decision per read instead of one. The draft is pure overhead that pays for itself only when its guesses are good; the entire engineering of this chapter is making the guesses good and the overhead small.
The measured stakes, stated with the campaign’s own scope language: in
retained fixed-window synthetic packets, kquant DFlash decode ratios
(llama ÷ muser means) are 1.23692× at 2,048, 1.20323× at 16,384, and
1.19616× at 32,768 tokens, with 5/5 exact-token reps per depth
[claims #15]. Hold onto both halves of that sentence — the ratios and
the scope. We return to them in §8.6.
8.2 What DFlash is
So what does a model small enough to be worth guessing with actually look like? Small enough to describe on one page — and odd enough that the description is worth reading slowly, because DFlash is not a miniature language model in the ordinary sense. It is a passenger.
The module doc says it in five lines:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/dflash.rs:1
//! Five-layer DFlash assistant extracted from Ferrite's accepted CPU oracle.
//!
//! The target hook is Muse-specific, but the assistant math and artifact
//! format supports both the development SafeTensors export and the official
//! llama.cpp-compatible k-quant GGUF sidecar.
}
Concretely, from the config contract (DFlashConfig,
[crates/muser-engine/src/dflash/config.rs:69-98]) and the bench shape
table Chapter 6 quoted:
- Five layers, enforced:
validaterefuses anything else — “release assistant must have exactly 5 layers” (config.rs:263-268). - It reads the target’s mind. This is DFlash’s defining trick and the
reason it can be so small: it does not re-derive the target’s thinking
from raw tokens. It is fed the target’s hidden states from five pinned
target layers, and the engine captures those rows as the target computes
them. The capture is gated rather than opportunistic:
DFlashHiddenCache::write_rowsaccepts a row only when its layer is on the pinned list. The list itself lives inDFlashConfig.dflash_config.target_layer_ids(config.rs:66), and the gate that enforces it is[crates/muser-engine/src/dflash/hidden.rs:44-52]. - Its front door is
fc.weight, shape 33,280 → 6,656 ([crates/muser-bench/src/m16.rs:219-225]). Do the arithmetic and the architecture falls out: 33,280 = 5 × 6,656 — five sampled target hidden states (the target’s hidden width, Chapter 6) concatenated, projected into the draft’s 6,656-wide hidden stream. - Its own attention is small GQA: k/v projections are 6,656 → 1,024
(eight KV heads × head-dim 128 — the same per-token element count the
context-geometry test uses,
config.rs:380-386), with QK-norms and RoPE like an ordinary transformer block (config.rs:310-335lists the tensor names: q/k/v/o projections, q_norm/k_norm, SwiGLU MLP). - It proposes in the target’s vocabulary by borrowing the target’s own
machinery:
draft_greedyembeds its 16-token block withtarget.embed_tokensand scores the draft’s block output withtarget.project_hidden([crates/muser-engine/src/dflash/spec.rs:744-752]) — the draft owns only its five layers, and the shared embedding/LM-head work is booked under assistant time in the telemetry (spec.rs:62-64). A proposal the target cannot score would be worthless, so the draft never needed a vocabulary of its own. - It is kquant:
draft.kis Q4_K,draft.vQ6_K,draft.fcQ4_K (Figure 6.6), and the GGUF loader requires every dense projection to be Q4_K/Q5_K/Q6_K ([crates/muser-engine/src/dflash/weights.rs:148-158]). - It is cheap to keep: 1,631,205,312 B on disk
(
[docs/memory-footprint.md]artifact manifest), “loaded only when configured.”
Read that front-door arithmetic once more, because it is the entire design compressed into a single projection. The draft’s input is not text and not a summary of text; it is the target caught mid-thought, five times over. Every other economy in the list — the few layers, the narrow hidden stream, the borrowed embedding and LM head — is affordable only because that first matrix hands the assistant a running start it never had to earn for itself. A model that had to understand the conversation could not be this small.
The draft runs 16-row blocks: block_size defaults to 16
(config.rs:138-140), and drafting produces up to 15 proposals in one
block forward — which is exactly why the M=16 batch kernels of Chapter 6
exist.
8.3 The context ABI: a 64-row sink plus a trained window
Two questions govern this section, and the second is the dangerous one. How much of the conversation does the draft get to see? And who is allowed to answer that question — the artifact, or the machine that happens to be serving it? Hold the second one; the next section is a postmortem of what happened when the wrong party answered.
The draft’s attention does not see the full conversation. Its context is a fixed ABI: the first 64 rows are pinned forever, and a trailing window slides over the rest:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/dflash/config.rs:59
/// The sink span used by the released DFlash cache ABI. It is made explicit
/// in every newly enrolled combined identity; it is not a receiver default.
pub const DFLASH_CONTEXT_SINK_SIZE: usize = 64;
// crates/muser-engine/src/dflash/config.rs:9
/// The context-cache shape bound to one enrolled DFlash identity.
///
/// The trained window comes from the sidecar metadata. The 64-row sink is
/// part of Muser's DFlash cache ABI rather than GGUF metadata, so enrollment
/// stamps it into both peers' identity configs explicitly. Receivers must
/// never infer either value from a local fallback.
pub struct DFlashContextGeometry {
pub layers: usize,
pub elements_per_token: usize,
pub sink_size: usize,
pub window_size: usize,
}
}
The sink-plus-window shape matters because the draft conditions on target-derived context rows. Figure 8.1 draws the ABI:
DFlash context — one row per committed token, per layer (5 layers, f32)
position: 0 …………………… 63 │ 64 …………………………………… (64 + W − 1)
┌───────────────┐ ┌────────────────────────────────┐
│ SINK │ │ WINDOW (W) │
│ pinned rows │ │ trailing rows, slides as the │
│ never evicted│ │ conversation grows │
└───────────────┘ └────────────────────────────────┘
sink_size = 64 window_size = trained sliding_window
(2,048 on the release sidecar)
rows the ABI may ever hold, per layer: sink + window = 64 + 2,048 = 2,112
buffered bytes (both planes, all layers):
5 × 2 × 2,112 × 1,024 × 4 B = 86,507,520 B
Figure 8.1: The draft’s context cache. The sink is ABI (64, stamped at
enrollment); the window comes from dflash.attention.sliding_window in the
sidecar metadata. The byte bound is identity-derived, not a constant
(DFlashContextGeometry::buffered_byte_limit,
[crates/muser-engine/src/dflash/config.rs:42-56]).
The geometry declares how many buffered rows the ABI may ever hold, with
that exact byte bound computed from the identity itself — for the release
geometry (5 layers, 1,024 elements per token, sink 64, window 2,048) the
arithmetic above yields 86,507,520 B, asserted by the config’s own test
(config.rs:380-395).
That doc comment’s last line — “Receivers must never infer either value
from a local fallback” — is fail-closed culture in one sentence: a remote
handoff that stamped one geometry must never be served by a receiver that
quietly assumed another. The same struct is bound into the cluster config
alongside the draft’s
SHA-256 identity ([crates/muser-cluster/src/config.rs:44-49]).
8.4 The window bug — read this twice
Where does window_size come from? The sidecar metadata
(dflash.attention.sliding_window), and the code documents what happens
when you don’t read it:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/dflash/config.rs:86
/// Trained sliding-window span for the draft's context attention, from
/// `dflash.attention.sliding_window`. The draft must be conditioned on
/// exactly this many trailing target rows: measured 2026-08-21, feeding
/// it half (the previously hardcoded 1024) or far more (32768) collapses
/// natural-text acceptance from 72.5% to 2.2%.
}
For an entire campaign, we never asked the artifact. Muser hardcoded sink 64 + window 1,024 and never read the key; the sidecar had been trained at 2,048. The draft was running on half its trained window, and nobody knew, because every instrument we owned said the lane was healthy.
That is the part to sit with. The synthetic fixtures kept passing. A
period-8 synthetic stream is predictable from token identity alone, so a
draft conditioned on the wrong stretch of history can still guess it
perfectly; the fixture is structurally incapable of detecting a
conditioning defect [ledger §ROOT CAUSE FOUND AND FIXED]. We had built a
gate that could not fail for the one reason we most needed it to.
When we finally read the real key, the numbers moved in two directions at
once. Natural-text acceptance on the python suffix-8,192 cell went from
1.1% to 72.7%, and natural-text cells became token-exact — the expected
half of the result. The unexpected half: every synthetic spec number
dropped by ~5%. A regression that is good news takes a moment to accept,
and then it is the most convincing evidence in the postmortem. The draft
had stopped coasting on a fixture and started doing real work
[ledger §Spec re-measurement at the fixed window].
Two durable decisions came out of the postmortem, and both are still in
the code. First, a sidecar without the key still loads, but loudly:
resolve_sliding_window prints a warning
that “draft conditioning may be wrong” rather than silently defaulting
(config.rs:109-125). Second, the effective geometry now travels with
every result — DFlashSpecStats carries draft_sink_size and
draft_sliding_window “so every receipt self-identifies. Conditioning
the draft on the wrong window silently invalidated a whole campaign’s
spec numbers (2026-08-21)”
([crates/muser-engine/src/dflash/spec.rs:44-50]).
The general lesson is Chapter 5’s error discipline at systems scale: a draft that is conditioned wrongly does not crash and does not fail parity on easy fixtures — it just gets quietly worse at the one job it has.
8.5 Loading: one validated loader, two artifacts
The draft has to arrive from somewhere, and there are two somewheres: the artifact we ship and the artifact we debug against. The real question a loader like this answers is how to serve both honestly — how to keep the development export useful without ever letting it become the thing that ships.
DFlashWeights::load dispatches on what you point it at — a file is the
GGUF sidecar, a directory is the SafeTensors development export
([crates/muser-engine/src/dflash/weights.rs:35-39]). Both paths share
one config contract (DFlashConfig::from_artifact,
config.rs:143-150), and the GGUF path is strict about metadata: the
architecture must be dflash, the target-layer list arrives one-based
(llama.cpp converter convention) and is converted to zero-based with
rejection of invalid entries (config.rs:200-215), and the resolved
dtype is recorded as "gguf-kquant" (config.rs:251).
The production Metal path loads a projection shell: norm vectors are expanded to f32, and nothing else is — every dense projection stays mmap’d in its kquant representation for the GPU to consume directly:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/dflash/weights.rs:42
/// Load the official assistant without expanding its projection matrices.
/// Norm vectors remain f32; all large matrices stay mmap'd in their GGUF
/// k-quant representation and are consumed directly by Metal.
}
Why “without expanding” is worth a doc comment: the f32 compatibility path
costs real memory — “Expanding the official 1.5 GiB k-quant assistant
into ~6 GiB of f32 matrices wastes startup time and resident memory”
(weights.rs:55-59). Chapter 6’s block formats are not just a disk story;
they are what makes a resident draft affordable. The SafeTensors path
remains the development oracle — the CPU forward (dflash/forward.rs) is,
like the target’s, the correctness spec the Metal drivers are checked
against.
8.6 What the draft must guarantee for exact verification
Speculation is only lossless if the target makes every decision. The draft participates in exactness by guaranteeing four things.
1. It proposes; it never decides. Acceptance runs on the CPU against
full target distributions: every proposed token is scored against the
target’s complete probability row by
verify_full_speculative_mt_ordered
([crates/muser-engine/src/sampling.rs:1033]). The rule is the
Leviathan-style accept if rng ≤ min(p/q, 1), with a residual-corrected
resample whenever a proposal is rejected (sampling.rs:1051-1088).
Every one of those coin flips is drawn from the source-pinned MT19937
stream — MT19937 being a reproducible random generator that Muser
re-implements bit-for-bit to match llama.cpp’s,
Ch 21. Keeping it isolated from the
engine’s generic RNG is deliberate, so that “a rand algorithm or
conversion change” cannot quietly alter which tokens come out
(sampling.rs:1001-1007).
Now the payoff, and it is the load-bearing property of the entire chapter: a draft can be arbitrarily bad and the output distribution stays exact — badness only costs speed. Say it the other way round and it is stranger still: nothing the assistant does can make the answer wrong, only slow. That single property is why the window bug (§8.4) was a performance catastrophe and not a correctness one.
2. Its rounds are deterministic and replayable. Proposals are
submitted “in deterministic round order. Qualification compares this
trace exactly” (DFlashSpecStats.draft_token_trace,
[crates/muser-engine/src/dflash/spec.rs:26-28]). The remote qualifier
compares 256 greedy tokens plus every full target-logit row, with an
acceptance floor of 0.95 (DFLASH_ACCEPTANCE_MINIMUM,
[crates/muser-bench/src/remote.rs:3-8, :33]) — exactness as a gate,
not a hope.
3. Its block size and verify lengths are pinned. draft_greedy
builds a 16-token block (a seed token followed by 15 mask tokens),
forwards it once, and takes the argmax of rows 1..=verify_length
(the index of each row’s largest score);
verify lengths are exactly 3, 7, or 15 — anything else is an error
before any GPU work ([crates/muser-engine/src/dflash/spec.rs:730-741]).
While tracing is enabled, the engine even checks the block head’s
sanity: row 0 saw the real seed, and “a correctly conditioned block
head must reconstruct the seed there; if it does not, the conditioning
carries no usable signal” (spec.rs:753-757).
4. Its context writes are transactional. A speculation round may fail
and roll back, so the target’s KV planes checkpoint before the block:
MetalSpeculativeCheckpoint rewinds NoPE-plane metadata only, while
SWA rings retain “the ≤16 rows a block may overwrite”
([crates/muser-engine/src/decode.rs:213-226]). Sixteen is not a magic
number — it is the block size; a speculative block can touch at most one
window’s worth of rows, and the checkpoint is sized to exactly that ABI.
And conditioning — the window story of §8.4 — is the fifth guarantee in disguise: not exactness-breaking, but throughput-breaking, and therefore gated (§8.7).
8.7 The measured scope — and its honest edges
Now the numbers — and the harder question of what each one is allowed to mean. Every figure below is true of a fixture, a depth, and a date. Almost none of them is true of “DFlash” full stop. Carrying the scope along is not pedantry in this chapter; it is the difference between a result and a press release, and the section you just read is the evidence for why we became strict about it.
The synthetic matrix (current, post-window-fix). “In retained
fixed-window synthetic packets, exact-token decode ratios (llama/Muser
means) are 1.23692× at 2,048, 1.20323× at 16,384, and 1.19616× at 32,768,
with 5/5 exact reps per depth” [claims #15]. The runs behind that row
are retained rather than summarized —
muser-receipt://spec-prefill-fix-20260822/aggregate-a2/…
and …/respec2-deep-20260822/aggregate-a1/… — so anyone who doubts the
means can go and recount them. The claims row’s own
instruction is part of the claim: “Never generalize this to natural
text, native NVFP4, or untested depths.” The deeper cells of the same
family: exact-token in 5/5 reps at all four depths including 131,008
[claims #3], and the funded-fix 131,008/48 packet crossed end-to-end
wall parity for the first time at 1.02536× [claims #16].
The old bar, and why you must not use it as a result. The campaign’s
famous 107.9 tok/s (107.9136 median, ratio 1.3273 vs llama’s 81.3047) was
measured before the 2026-08-21 draft-window fix; it survives in the
record only as the kquant spec bar that later lanes were judged against
[ledger §L2 Stage B verdict]. The pre-fix ratios 1.3273/1.3012 are
superseded numbers — quoting them as current performance is one of this
book’s standing landmines.
Natural text is a different regime. We expected the window fix to make
the draft uniformly better. What it actually did was make the draft
legible: once conditioning was right, the wins and losses stopped looking
like noise and sorted themselves by the kind of text being written. On
real corpora, cross-engine
outputs diverge (so speed stands without an exactness gate), and the
picture splits: spec decode wins python-like content (16,384: 1.186;
8,192 suffix: 1.321) and loses high-acceptance shallow text (rust at
2,048: 0.931, improving only to 0.945 at verify-length 7) — llama’s
lighter draft wins there [docs/benchmarks.md §2]. That asymmetry froze
the serving verify-length at 7 while the comparison harness pins 15:
“the best decode and the most robust acceptance on natural text”
[docs/benchmarks.md §2].
The engine distrusts its own draft. One question is left: what should
the engine do when the draft stops paying for itself on a particular
request? The obvious answer is to track acceptance across the request and
switch drafting off once the rate falls too low. We built exactly that,
and it carried a defect that only surfaces in production — a cumulative
average that has sunk can never climb back, because once drafting stops
there are no new proposals to lift it. Disabling speculation was
therefore permanent. The comment on the redesign still records that
one-way latch, dated like a scar: “a cumulative rate cannot recover once
drafting stops… (2026-08-21 root cause)” (spec.rs:184-190).
So the gate that shipped reads only recent evidence: eight rounds, after
a two-round warmup, because “the rounds immediately after prefill are the
coldest of the request”. It requires ≥32 proposals before it will judge
anything at all, and it closes when windowed acceptance drops below 0.25
([crates/muser-engine/src/dflash/spec.rs:117-133]). The latch is undone
in the other direction too: a disabled request re-qualifies after a
doubling cooldown (64 → 512 tokens), so a cold start costs a session some
throughput instead of costing it speculation for the rest of its life
(spec.rs:199-230).
8.8 Tradeoffs
Draft cost vs verify cost, measured. Where should optimization energy go — into making the guess faster, or into making the guess right? The question sounds like a matter of taste until you look at the split, and then it answers itself.
In the L1 in-process qualification (five reps × 256 tokens, verify length 15): median cycle ~157.2 ms, of which draft ≈ 26.9 ms (embed 0.04 + block forward 22.3
- LM-head/argmax 4.5) and verify ≈ 130.2 ms (forward 128.4, decision
1.8)
[ledger §Stage B L1]. The draft is the small side of its own loop — which is why Chapter 6’s optimization energy went to the 16-row verify kernels (the n32 tile: verify forward 202.3 → 128.4 ms), and why making the draft correctly conditioned (§8.4) was worth more than making it faster. A draft that proposes 15 tokens per 27 ms is already cheap; a draft whose proposals are accepted is priceless.
Synthetic vs natural — the fixture that lied. The period-8 synthetic
stream “certified a broken draft lane for an entire campaign”
[ledger §ROOT CAUSE FOUND AND FIXED, consequence 2]. Since 2026-08-21,
natural-text cells are a standing part of the matrix even though they
cannot carry an exactness gate. The general rule this book keeps
re-stating: a measurement is only as good as its fixture’s ability to
detect the failure mode you care about.
Why the draft is kquant-only (Fallback B, again). Chapter 7 measured
the alternative: native NVFP4 batched verification at 6.805 tok/s against
the 107.9 bar [claims #4]. The draft itself could be anything — the
barrier is the target’s verify arithmetic in the NVFP4 lane’s W4A4
batch shape. So the smallest model in the system inherits the reference
lane’s format: Q4_K/Q6_K blocks, llama-pinned batch kernels, exactness
gated by lossless token equality. When your only exactness instrument is
bitwise comparison, you build on the lane that can support it.
8.9 What comes next — two hooks, one promise
The accept/verify algorithm itself — the min(p/q, 1) rule applied to
full distributions, the carried-frontier state machine, the Mirror-SD
overlap that splits the target graph at a capture layer, and the measured
rejection of the distributed verifier — is
Ch 33’s subject; this
chapter deliberately stopped at the draft’s edge of the contract.
But Part II now closes, and Part III opens with a debt we have been accumulating for four chapters: we have quantized, packed, and dispatched the weights of a model we have never actually met. What is the 52-layer graph these formats encode? Which layers slide, which layers have no position at all, why are there two KV heads for thirty-two query heads, and why does a sigmoid gate sit on the attention output? Ch 9 is the Muse Glimmer architecture — the model that all three lanes exist to serve.
References
[crates/muser-engine/src/dflash.rs:1-5]— the module contract (five layers, SafeTensors + kquant GGUF, Ferrite oracle lineage).[crates/muser-engine/src/dflash/config.rs:59-61]—DFLASH_CONTEXT_SINK_SIZE = 64;:15-57DFlashContextGeometryand the exact byte bound;:86-91the window-collapse doc (72.5% → 2.2%);:109-136the loud fallback;:180-255the GGUF config path (one-based target layers,gguf-kquant);:263-268the five-layer enforcement.[crates/muser-engine/src/dflash/weights.rs:35-66]— the dual loader and the projection shell;:115-161validate_quantized_gguf_layouts(Q4_K/Q5_K/Q6_K requirement, exact shapes).[crates/muser-engine/src/dflash/hidden.rs:44-52]— target-layer-gated hidden-state capture.[crates/muser-engine/src/dflash/spec.rs:16-98]—DFlashSpecStats(trace, geometry self-identification);:117-134the disable-gate constants;:184-230windowed gate + re-qualification;:730-770draft_greedy(verify lengths 3|7|15, seed echo).[crates/muser-engine/src/sampling.rs:1001-1097]— the MT-pinned stream andverify_full_speculative_mt_ordered.[crates/muser-engine/src/decode.rs:213-226]—MetalSpeculativeCheckpoint(≤16 SWA rows, NoPE metadata rewind).[crates/muser-cluster/src/config.rs:44-49]— enrollment-stampeddflash_context_geometrybound to the component digest.[crates/muser-bench/src/m16.rs:202-225]— draft shapes (fc 33280→6656, k/v →1024) and dtypes.[crates/muser-bench/src/remote.rs:3-8, :33]— the 256-token exact compare andDFLASH_ACCEPTANCE_MINIMUM = 0.95.[docs/memory-footprint.md]— DFlash GGUF 1,631,205,312 B.[claims #15],[claims #3],[claims #16],[claims #4]—docs/launch-claims.md: the fixed-window synthetic ratios and their prohibited generalizations; exact-token 5/5 depths; 131,008 wall parity 1.02536; native spec fail-closed.[docs/benchmarks.md §2]— verify-length conventions and the natural-text wins/losses (1.186/1.321 vs 0.931/0.945).[ledger §L2 Stage B verdict],[ledger §Stage B L0/L1],[ledger §ROOT CAUSE FOUND AND FIXED],[ledger §Spec re-measurement at the fixed window]—docs/goal-parity-ledger-2026-08.md: the pre-fix 107.9136/1.3273 bar, the M16 microbenchmark lineage, and the window postmortem.- Ch 6 — the blocks the draft is built from and the M16 kernels its 16-row blocks run on.
- Ch 7 — Fallback B and the 6.805 no-go.
- Ch 33 — the accept/verify algorithm this chapter deferred.
Chapter 9 — The Muse Glimmer architecture
status: polished · path: Muse Glimmer, pinned Muser tree
Prerequisites: Part I (Metal) and Part II (quantization). No transformer paper assumed; every term is defined in place, and the kernel-deep treatment starts in Ch 11.
Chapter 8 left us with a small mystery: the DFlash draft was described purely by what it must guarantee — a 64-row sink context, a matching sliding window, exact-enough drafting. What it drafts for stayed off-stage. Now the target takes the stage: Muse Glimmer, the 52-layer, ~30-billion-parameter model every lane in this book exists to serve. We build its architecture from zero — no transformer knowledge assumed — and verify every number against the pinned Muser tree, because this chapter’s tables are the anchors every kernel chapter in Part IV points back at.
9.1 What a transformer is (one page, from zero)
A transformer turns a sequence of tokens (integers, each standing for a word fragment) into a prediction of the next token. Internally it is a stack of identical blocks — layers — wired one after another. Each layer reads one vector per token, the hidden state (or residual stream), does two things to it, and writes the updated vector back out:
- Attention mixes information across tokens: each token looks at the tokens before it and pulls in what it needs. It is the only operation in the model that moves information between positions.
- A feed-forward network (FFN) transforms the vector in place, one token at a time — a per-token “thinking” step.
Everything else is plumbing: normalizations that keep the vector’s magnitude tame, projections that reshape it, positional machinery that tells attention what “before” means. Muse Glimmer is exactly this stack, 52 layers deep, with a handful of unusual choices in the plumbing — the repeating sliding/full attention pattern, a sigmoid gate on attention output, a dual-epsilon norm scheme — each taken one at a time below. The name “transformer” is just the 2017 paper’s label for the attention-plus-FFN stack [arxiv:1706.03762]. Muser implements one specific transformer, and the contract is one sentence in the code — the CPU oracle is “the bit-level spec for every trap in the architecture (inverted-Gemma RoPE/NoPE split, sigmoid gate placement, dual-eps sandwich norms, softcap-after-scale ordering, GPT-J RoPE pairing)” [crates/muser-engine/src/lib.rs:103-108]. Every trap in that sentence gets a section.
9.2 The exact hyperparameters (verified from source)
Before a single kernel can be written, one question has to be settled: how wide is this model, exactly? Every kernel chapter in Part IV sizes its buffers, its threadgroups and its loop bounds from the answer, so one wrong row here does not fail loudly — it propagates quietly into a dozen chapters and into an engine that runs and emits plausible text.
So we did not transcribe these values from a model card. Each one is read twice: once by the live GGUF metadata reader, which fails closed on a missing key, and once by the release-model gate test, which asserts what the pinned artifact actually carries. That is the same double-citation discipline we apply to the pinned SHA-256 identity, and we kept the receipt: [crates/muser-engine/tests/muse_golden.rs:14-15].
Table 9.1 — Muse Glimmer hyperparameters (pinned release GGUF)
| Hyperparameter | Value | Parsed from GGUF key | Release-test assertion |
|---|---|---|---|
n_layers | 52 | muse-glimmer.block_count [crates/muser-engine/src/config.rs:169] | [crates/muser-engine/tests/muse_golden.rs:97] |
hidden_dim | 6,656 | muse-glimmer.embedding_length [config.rs:170] | [muse_golden.rs:98] |
n_heads (query) | 32 | muse-glimmer.attention.head_count [config.rs:171] | [muse_golden.rs:99] |
n_kv_heads | 2 | muse-glimmer.attention.head_count_kv [config.rs:172-173] | [muse_golden.rs:100] |
head_dim | 128 | muse-glimmer.attention.key_length (falls back to hidden/n_heads) [config.rs:174-176] | [muse_golden.rs:101] |
intermediate_dim (FFN) | 19,968 | muse-glimmer.feed_forward_length [config.rs:180] | [muse_golden.rs:102] |
vocab_size | 202,048 | muse-glimmer.vocab_size (falls back to the GGUF token list) [config.rs:209-211] | [muse_golden.rs:103] |
sliding_window | 2,048 | muse-glimmer.attention.sliding_window [config.rs:189] | [muse_golden.rs:104] |
context_length | 131,072 | muse-glimmer.context_length [config.rs:181] | [muse_golden.rs:105] |
| SWA / full layers | 39 / 13 | muse-glimmer.attention.sliding_window_pattern = 4 [config.rs:347-361] | [muse_golden.rs:109-116] |
rms_eps | 1e-5 | muse-glimmer.attention.layer_norm_rms_epsilon (required) [config.rs:184-188] | [crates/muser-engine/src/lib.rs:11-13] |
post_norm_eps | 1e-8 | not in the GGUF — llama.cpp graph constant [config.rs:23-28] | [config.rs:28] |
rope_base_swa | 500,000 | muse-glimmer.rope.freq_base_swa (falls back to rope.freq_base) [config.rs:200-202] | [crates/muser-engine/src/rope_nco.rs:14] |
logit_scale | 0.196116 (= 1/√26) | muse-glimmer.logit_scale (required) [config.rs:190-192] | [docs/release-provenance.md:823] |
final_logit_softcap | 20 | muse-glimmer.final_logit_softcapping (llama.cpp default 30) [config.rs:195-197] | [crates/muser-engine/src/lib.rs:186] |
| end-of-generation | EOS 200,001 + EOT 200,008 | tokenizer.ggml.{eos,eot,eom}_token_id [config.rs:213-226] | [muse_golden.rs:106-108] |
Three properties of this table matter more than the numbers. First,
nothing is hard-coded: MuseConfig::from_gguf “fails closed” on any
missing key, bad shape, or non-constant QK-norm [config.rs:158-162]; the
Rust constants that exist (MUSE_LAYER_COUNT, MUSE_SWA_WINDOW, …) are
the same values as asserts, not a second source of truth [config.rs:13-21].
Second, head_dim is independent of
hidden_dim: with 32 heads over a 6,656-wide hidden state,
hidden/n_heads would be 208, but the GGUF declares key_length = 128
and the loader prefers the declared key [config.rs:174-176] — so query
space is 32 × 128 = 4,096 while the residual stream is 6,656, and the Q
projection is not square. Third, one value is famously not in the
file: the 1e-8 post-norm epsilon is a llama.cpp graph constant, not GGUF
metadata — §9.9 explains why that is a landmine.
Two rows use names you have not met yet: SWA, sliding-window attention, and
NoPE, “no positional embedding.” They earn their own section rather than a
parenthesis, because they are the reason this model’s memory cost does not
grow the way its depth suggests — that is §9.7, and the rope_base_swa key
belongs to the same story. And one last thing before we leave the table: it
describes one specific file and no other — the artifact with SHA-256
7e9b74b7c8875e9e265695df9613bf6290f2392e479ce740495a129019c488d8,
16,756,681,056 bytes on disk
[crates/muser-server/src/chat_template.rs:237-250]. Every value above was
read out of that artifact; held against a different one, the whole geometry
is a guess.
Derived quantities used everywhere below:
attn_dim = n_heads × head_dim = 4,096;kv_dim = n_kv_heads × head_dim = 256[config.rs:268-273]. Q, gate, o_proj live in 4,096 space; K, V in 256.
9.3 One Muse Glimmer block — the anatomy
Figure 9.1 is the most important diagram in Part III — every decode kernel in chapters 11–21 implements one box in it. Read it slowly, then again after §9.5.
flowchart TD
hin["residual in [6,656]"]
rn1[" RMSNorm (attn_norm), eps 1e-5 — Ch 12 "]
subgraph ATT["Attention"]
direction TB
qkv["Q/K/V/gate projections — one concurrent group of 4 matvecs<br/>Q, gate: [6656→4096] K, V: [6656→256] — Ch 13"]
qkn["per-head QK-norm, eps 1e-5<br/>Q weight ≈ 3.87, K weight 1.0 — Ch 14"]
rope["RoPE — ONLY on sliding layers<br/>theta 500,000, interleaved pairs — Ch 14"]
attn["attention over the KV cache: scale 1/√128, causal,<br/>GQA 32:2; SWA layers see last 2,048 tokens — Ch 16"]
sgop["sigmoid gate: attn_out ⊙ σ(gate_proj),<br/>then o_proj [4096→6656] — Ch 17"]
qkv --> qkn --> rope --> attn --> sgop
end
pn1[" RMSNorm (post_attention_norm)<br/>eps 1e-8 — sandwich norm "]
add1((" + residual "))
rn2[" RMSNorm (ffn_norm), eps 1e-5 "]
subgraph FFN["Feed-forward — SwiGLU — Ch 18"]
ffn["gate [6656→19968], up [6656→19968]<br/>→ SiLU(gate) ⊙ up → down [19968→6656]"]
end
pn2[" RMSNorm (post_ffw_norm)<br/>eps 1e-8 — second sandwich norm "]
add2((" + residual "))
hout["residual out [6,656] → next layer"]
hin --> rn1 --> ATT
hin -. residual .-> add1
ATT --> pn1 --> add1
add1 --> rn2 --> FFN
add1 -. residual .-> add2
FFN --> pn2 --> add2
add2 --> hout
Figure 9.1: One Muse Glimmer block. Two residual adds (dashed), and — the signature of the architecture — a norm on the output of each sub-block before it is added rather than on the residual sum. This Gemma-2-style “sandwich” placement [crates/muser-engine/src/config.rs:51-53] gives both post-norms the different 1e-8 epsilon (§9.9), and the sigmoid gate (§9.8) sits between attention and o_proj — nowhere else.
Three things to internalize. The residual adds are the spine: each sub-block only ever adds — nothing overwrites the stream, which is what lets a 52-layer stack train at all (the skip-connection argument of [arxiv:1512.03385]). Attention is the only cross-token mixer; the FFN and all the norms are strictly per-position. And every sub-block reads the stream through a norm — four weighted norms per block plus one per attention head, 209 norm applications per token. Norms are cheap per vector but everywhere at 52 layers, which is why the fused norm tails of Ch 12 exist.
9.4 The full model — 52 blocks in a repeating pattern
Now zoom out one level. If the figure above is the cell, the one below is the organism: everything that happens to a token id between the moment it arrives and the moment a successor is chosen. The question this section answers is where the blocks sit in that path, and what decides which kind of block a given layer is.
flowchart TD
tok["token_ids (u32)"]
emb["token_embd.weight [6656 × 202048]<br/>row lookup — Ch 11 (untied from LM head, §9.11)"]
entry["entry RMSNorm — weightless<br/>(weight = all ones), eps 1e-5"]
blocks[" 52 blocks × Figure 9.1<br/>collar: [SWA, SWA, SWA, FULL] × 13<br/>(full layers at indices 3, 7, …, 51) "]
fnorm[" final RMSNorm (output_norm), eps 1e-5 —<br/>fused into the last block's tail "]
lmh[" LM head: output.weight [6656 × 202048] — Ch 20 "]
tail[" × 0.196116 (1/√26), then tanh cap at 20 — Ch 20 "]
samp[" sample / argmax — Ch 21 → next token id "]
tok --> emb --> entry --> blocks --> fnorm --> lmh --> tail --> samp
Figure 9.2: The full Muse Glimmer forward pass. The residual stream is
[6,656] between all blocks; the final norm is not a separate dispatch on
the decode path — the last block’s fused tail produces the normed output
directly [crates/muser-engine/src/decode.rs:5869-5876].
The collar in Figure 9.2 follows one rule, and the type documenting it also records the inversion relative to Gemma 3:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/config.rs:51-59, 84-93 (the enum's variant bodies
// and the rule's bounds-check arm are elided)
/// Per-layer attention kind. Muse Glimmer alternates
/// `[sliding, sliding, sliding, full]`, and — unlike Gemma 3 — it is the
/// *sliding* layers that carry RoPE while the *full* layers are NoPE.
pub enum MuseLayerKind { /* SlidingRope, FullNoPe */ }
pub const fn layer_kind(layer: usize) -> Result<MuseLayerKind, LayerIndexError> {
// …
if layer % 4 == 3 {
Ok(MuseLayerKind::FullNoPe)
} else {
Ok(MuseLayerKind::SlidingRope)
}
}
}
So 52 layers = 13 groups of [sliding, sliding, sliding, full]: 39 SWA
layers and 13 NoPE full layers at indices 3, 7, …, 51 — exactly the
set the remote-prefill schedule ships as position-free tiles
[crates/muser-cluster/src/schedule.rs:20]. The counts are asserted in code
[config.rs:14-15] and in the release test [muse_golden.rs:109-116].
There was a fork at the resolver, and it is worth naming, because the tempting branch is the friendly one. If a GGUF arrives without its sliding-window pattern key, the loader could simply assume every layer is a full-attention layer. The model would load. It would run. It would emit fluent English. We took the unfriendly branch instead: a missing pattern key panics. The comment in the resolver gives the reason in one line — “an all-full model runs and emits plausible text while being wrong” [config.rs:378-380]. That is the fail-closed posture in miniature, applied here not to a tensor or a hash but to the shape of the graph itself, and it rests on a judgement we will keep returning to: an answer that looks right and is wrong costs more than a refusal.
9.5 The components, one short tour each
Before the traps, a quick pass over the parts by name. Nothing in this section is peculiar to Muse Glimmer; it is the shared vocabulary the rest of the book leans on, so skim what you already know and slow down where a term is new.
The embedding is a lookup table — a token id picks
one row of token_embd.weight [6656 × 202048]; that row is the initial
residual stream. RMSNorm computes
x / sqrt(mean(xᵢ²) + ε) ⊙ γ (⊙ is elementwise multiply) with a learned
per-channel scale γ (here ε = 1e-5); no mean subtraction, and it rescales the stream to ~unit
magnitude so 52 layers of matrix products neither explode nor vanish. In
attention, each token computes a Query (what
it looks for) while every past token carries a Key (what it offers) and a
Value (what it hands over); a query-key score is a dot product over 128
dimensions scaled by 1/√128 ≈ 0.0884 [config.rs:279-281], scores pass
through softmax (exp(xᵢ)/Σⱼexp(xⱼ) — positive
weights summing to 1), and the output is the weighted sum of Values under a
causal mask that forbids seeing future tokens. The
KV cache records every past token’s Keys and
Values so token N+1 does not recompute tokens 0..N — one cached row in one
layer costs 2 KV heads × 128 × 2 B × (K + V) = 1,024 bytes
[docs/memory-footprint.md §KV formula]. The FFN’s activation
SiLU is x·σ(x) (⊙ is elementwise product), giving
the SwiGLU shape down(SiLU(gate·x) ⊙ (up·x))
[crates/muser-engine/src/reference.rs:493-522].
9.6 GQA — 32 query heads, 2 KV heads
The single biggest memory lever in the model’s geometry. Attention runs per head — each an independent 128-dimensional attention with its own slice of the Q projection. In plain multi-head attention every query head would also have its own Key and Value (32 KV heads here); Grouped-Query Attention instead gives the 32 query heads only 2 KV heads to share (Figure 9.3):
query heads: Q0 Q1 ... Q15 | Q16 ... Q31 32 heads × 128 = attn_dim 4,096
\ ... / \ ... /
KV heads: K0 , V0 K1 , V1 2 heads × 128 = kv_dim 256
Figure 9.3: The GQA grouping. Each KV head serves
heads_per_kv = n_heads / n_kv_heads = 32 / 2 = 16 query heads
[config.rs:274-276].
Said the other way round, because this is the idea the whole memory story rests on: the model keeps thirty-two independent ways of asking a question, but only two distinct sets of answers on file, and every question is served from one of those two sets. Asking is cheap — it is arithmetic on a vector the model already has. Answering is expensive, because the answers have to be fetched from memory. Grouped-Query Attention makes the expensive half small.
Why this is a bandwidth win. During decode, attention must read the K
and V of every visible past token out of the KV cache. With full multi-head
attention one cached row in one layer would cost
32 × 128 × 2 B × (K+V) = 16,384 B; with GQA it costs
2 × 128 × 2 B × (K+V) = 1,024 B [docs/memory-footprint.md §KV formula] —
a 16× reduction in both the per-token KV read and the cache size. On a
bandwidth-bound decode path (the argument of
Ch 1) that is enormous; the
quality cost of 16 query heads sharing each KV pair is the trade GQA was
invented to make [arxiv:2305.13245]. Whenever a later chapter says “KV
head,” it means one of these 2. GQA also skews the projection widths —
K and V read only [6656 → 256], skinny matrices that get a different
matvec geometry than Q in Ch 13 and a
16-way fan-in per KV head in attention Ch 16.
9.7 The two attention classes: 39 SWA rings, 13 NoPE planes
This is the section to read twice. Almost everything Part VI does — remote prefill, warm reuse, the delta handoff — is downstream of a single fact established here, and the fact is easy to miss because it hides inside a routine-looking layer schedule. The question: what does a layer of this model see when it looks backwards, and does the answer depend on where the tokens it sees happened to sit?
Muse Glimmer’s most consequential structural choice: its 52 layers do not all attend the same way (Figure 9.4).
layer: 0 1 2 3 | 4 5 6 7 | 8 9 10 11 | ... | 48 49 50 51
kind: S S S F | S S S F | S S S F | ... | S S S F
S = SlidingRope : window 2,048 tokens, RoPE applied (39 layers)
F = FullNoPe : full causal attention, NO rotation (13 layers)
Figure 9.4: The repeating collar — 13 copies of [sliding, sliding, sliding, full] (layer % 4 == 3 is full [config.rs:88]).
The 39 sliding layers use sliding-window attention: a
query at position p1 sees a key at p0 only if p1 ≥ p0 and
p1 − p0 < 2,048 — “Mask follows llama.cpp LLAMA_SWA_TYPE_STANDARD”
[crates/muser-engine/src/reference.rs:577-580]. Their KV caches never need
more than 2,048 rows, so each SWA layer’s cache is a fixed 2,048-row
ring of min(max_context, sliding_window) capacity, token-major
[crates/muser-engine/src/decode.rs:1346-1348]. Bounded KV: one SWA layer
holds 2,048 × 1,024 B = 2 MiB — per layer, forever, at any depth.
The 13 full layers attend over the entire context — and they carry no positional rotation at all: NoPE means “no positional embedding,” not “a different positional embedding.” The code is categorical:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/config.rs:66-70
/// RoPE runs iff the layer is a sliding layer (`muse-glimmer.cpp:93`:
/// `const bool use_rope = hparams.is_swa(il);`).
pub const fn uses_rope(self) -> bool {
matches!(self, Self::SlidingRope)
}
}
Because a NoPE layer’s K/V rows do not encode absolute position, its cache
rows are relocatable bytes — a KV tile for positions [a, b) can be
planted anywhere in the plane without recomputation: “the 13 NoPE layers
are position-free (relocate = memcpy) — the whole kvpack free lunch”
[crates/muser-engine/src/lib.rs:8-10].
Put it side by side with the other class, because the contrast is the whole point. In a sliding layer, where a token sat has been rotated into its numbers; move its cached row to a new position and the numbers are now lies, so the row must be recomputed. In a NoPE layer nothing about position was ever written down; the row means the same thing wherever it lands, so moving it is a copy and nothing more. One class stores meaning-at-a-place, the other stores meaning. Only the second can be shipped over a wire.
That single property is what pays for Part VI. It is why remote prefill can
stream 512-token NoPE tiles while the producer is still computing, why warm
reuse restores a 130,815-token prefix in ~1 s instead of ~148 s [ledger,
kvpack ladder stage-5], and why that lane exists in the shape it does.
(Why the authors trained full layers without rotation is a modeling
question this codebase does not answer — [unverified]; the code proves
the rotation is absent and the engine exploits that.)
Meanwhile the sliding layers do rotate — with the interleaved (GPT-J)
pairing: dimension 2i rotates with 2i+1, adjacent in memory:
// crates/muser-engine/src/shaders/ferrite/rope.metal:651-656
// NORM convention: the pair is adjacent, at 2*pi and 2*pi + 1.
const uint i0 = 2u * pi;
// …(v0/v1 loads elided)
base[i0] = v0 * cos_a - v1 * sin_a;
base[i0 + 1u] = v0 * sin_a + v1 * cos_a;
The base frequency is theta = 500,000 (read from
muse-glimmer.rope.freq_base_swa [config.rs:200-202]; baked into the
deterministic trigonometry tables as “Exact f32 bits for 500000^(-2/128)”
[crates/muser-engine/src/rope_nco.rs:14]). Larger theta ⇒ angles grow more
slowly with position ⇒ positions stay distinguishable over longer ranges
(the mechanical trade is Ch 14’s). The two-class
split also fixes the cache arithmetic for
Ch 22, derived here once:
slot_kv_bytes(C) = (39 × min(C, 2,048) + 13 × C) × 1,024
C = 131,072: (39 × 2,048 + 13 × 131,072) × 1,024
= (79,872 + 1,703,936) × 1,024
= 1,826,619,392 B ≈ 1.827 GB per slot [docs/memory-footprint.md]
The 13 NoPE layers dominate that sum (1,703,936 of 1,783,808 rows ≈ 95 %): full attention pays for depth, sliding attention does not.
9.8 The sigmoid attention gate
Two questions hang over this component, and only one of them has an answer in this repository. Where does the gate sit, exactly? — answerable to the line, and the answer matters, because getting the placement wrong changes every logit downstream. Why is it there at all? — not answerable from code, and we will say so plainly rather than fill the gap with a plausible story.
Take the placement first. Standard attention ends with o_proj; Muse Glimmer slips a learned gate in ahead of it, and the CPU oracle shows the seam exactly — after attention, before the output projection:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/reference.rs:446-455 (the trailing o_proj
// `matmul(&self.w("blk.{il}.attn_output.weight"), &attn_out, t, &mut proj)`
// at :456-461 is elided)
// ── sigmoid gate, then o_proj ─────────────────────────────────
for g in gate.iter_mut() {
*g = 1.0 / (1.0 + (-*g).exp());
}
// …(capture recording elided)
for (a, g) in attn_out.iter_mut().zip(gate.iter()) {
*a *= *g;
}
}
The gate vector comes from a fourth attention projection —
attn_gate.weight [6656 → 4096], the same width as Q [config.rs:307] —
computed concurrently with Q/K/V from the same normed input
[crates/muser-engine/src/decode.rs:5569-5598]. After the sigmoid, each of
the 4,096 attention-output channels is multiplied by a learned value in
(0, 1): a per-channel valve that can pass, dampen, or effectively mute what
attention just computed; then o_proj maps the gated result back to 6,656
(reference.rs:456-461). The Metal kernel:
// crates/muser-engine/src/shaders/ferrite/sigmoid_gate.metal:7-16
kernel void sigmoid_gate_inplace(
device float* attn_out [[ buffer(0) ]],
device const float* gate [[ buffer(1) ]],
constant uint& n [[ buffer(2) ]],
uint gid [[ thread_position_in_grid ]])
{
if (gid < n) {
attn_out[gid] *= 1.0f / (1.0f + exp(-gate[gid]));
}
}
What the gate buys the model — dampening unreliable attention channels —
is the standard reading of gated-attention variants, but this tree contains
no evidence for the authors’ intent: [unverified]. What the code proves is
the placement (attention output, pre-o_proj), the width (4,096, matching
Q), and the concurrency (the fourth matvec of the QKVG group — the hero
chapter Ch 13; kernel in
Ch 17).
9.9 The dual-epsilon norm sandwich
Look again at Figure 9.1: the post-attention norm applies to o_proj’s output, then the result joins the residual — ditto the post-FFN norm. This Gemma-2-style sandwich normalizes each sub-block’s contribution on its way into the stream, not the stream after the add. The oracle’s tail:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/reference.rs:466-480
// ── post-attention norm (eps 1e-8) + residual ─────────────────
// …(weight fetch elided)
rms_norm_mul(&mut proj, h_dim, cfg.post_norm_eps, &post_attn_w);
// …(capture recording elided)
for (hv, (p, r)) in hidden.iter_mut().zip(proj.iter().zip(residual.iter())) {
*hv = *p + *r;
}
}
The second tail, after the FFN, is identical in shape with post_ffw_norm
[reference.rs:527-538]. Now the landmine: the two post-norms use a
different epsilon than every other norm in the graph — 1e-8 versus the
GGUF’s 1e-5 — and that 1e-8 is not in the checkpoint:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/config.rs:23-28
/// Post-attention / post-FFN RMSNorm epsilon.
///
/// llama.cpp uses a *different* epsilon for the two "post" norms of the
/// Gemma-2-style sandwich than for every other RMSNorm in the graph, and it is
/// not carried in the GGUF. See `src/models/muse-glimmer.cpp:67`.
pub const MUSE_POST_NORM_EPS: f32 = 1e-8;
}
This is a llama.cpp graph constant, transcribed into Muser so the two engines compute identical bytes [crates/muser-engine/src/lib.rs:103-108]. Reading one epsilon from the GGUF and using it everywhere — the “obvious” simplification — would produce logits that differ from the comparator’s at every layer boundary.
We found out how little slack there is here the expensive way. A block’s tail is two norm stages with a device-memory boundary between them, which reads like an open invitation to fuse: same data, same thread, one kernel instead of two, and the algebra on paper does not change. So we built it — a hybrid schedule that fused the tail and retained activations across the two stages — and we expected a dispatch saving for free, on the reasoning that arithmetic which is equal on paper is equal in the machine. It was not. Public logprobs moved past the 1e-4 contract: max normalized-logprob error 3.197e-4, with the first divergence a single f16 ULP in layer-1 V, which then had the entire rest of the stack to grow in. The lesson is the one to carry out of this section: an epsilon this small is not a tolerance knob, it is part of the model’s definition — rounding boundaries are the spec, and “the same algebra” is not “the same bits.” The hybrid was rejected rather than waved through under a widened tolerance, and we retained the postmortem: [docs/decode-dispatch-gap-20260815.md §Rejected hybrid postmortem].
The full norm story — including why the fused tail kernel that did ship reproduces “the two pinned ggml f32x4 norm reductions and their intervening f32 device-memory boundary” [crates/muser-engine/src/decode.rs:1328-1330] — is Ch 12.
A related fail-closed check lives at load time: the GGUF’s
attn_q_norm/attn_k_norm tensors are converter-synthesized constant
broadcasts — every Q-norm weight the same scalar (qk_scale_factor ≈ 3.87), every K-norm weight 1.0 — materialized only so llama.cpp’s
weighted-RMSNorm op can carry a scalar
[crates/muser-engine/src/lib.rs:74-79]. The loader verifies this
tensor-by-tensor and aborts on a genuinely learned norm
[crates/muser-engine/src/loader.rs:98-138]. The per-head QK-norm itself
(RMS over each 128-wide head vector, eps 1e-5, before RoPE) is
Ch 14’s; its measured constant
3.87 × 0.0883883… ≈ 0.3420623 is asserted in [config.rs:426-430].
9.10 Final logits: multiply by 1/√26, then cap at tanh(20)
One question is left before a token can be chosen: what happens to the raw scores the LM head produces? Muse Glimmer does two unusual things to them, and the part that catches people is not either step on its own — it is that their order is contract, not taste:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/reference.rs:559-570
let mut logits = vec![0.0f32; t * cfg.vocab_size];
matmul(&self.w("output.weight"), &hidden, t, &mut logits);
for l in logits.iter_mut() {
*l *= cfg.logit_scale;
}
if cfg.final_logit_softcap > 0.0 {
let cap_v = cfg.final_logit_softcap;
let inv = 1.0 / cap_v;
for l in logits.iter_mut() {
*l = cap_v * (*l * inv).tanh();
}
}
}
First, every logit is scaled by logit_scale = 0.196116 — 1/√26 to
five decimals (1/√26 = 0.19611613…). This is GGUF metadata, not a code
constant: MuseConfig requires the key muse-glimmer.logit_scale
[config.rs:190-192], and the pinned artifact’s value is recorded in the
provenance doc (“…muse-glimmer.final_logit_softcapping=20 and
logit_scale=0.196116” [docs/release-provenance.md:823]). The literal
1.0 / 26.0f32.sqrt() appears in the tree only as a fixed test input
[crates/muser-engine/src/metal.rs:68] — cite the metadata read, not the
test. Second, a soft cap l → 20·tanh(l/20) confines every logit to
(−20, 20) without a cliff; the GPU kernel is one thread per logit:
// crates/muser-engine/src/shaders/muse_reference.metal:15-27 (the buffer
// parameter list is elided)
kernel void muser_scale_softcap_inplace( /* … */ ) {
if (index < count) {
float value = logits[index] * scale;
logits[index] = softcap > 0.0f ? softcap * tanh(value / softcap) : value;
}
}
Scale first, cap second — “softcap-after-scale ordering” is one of the named traps the oracle pins down [crates/muser-engine/src/lib.rs:103-108]. Backwards, the cap barely bites (0.196 × 20 ≈ 3.9 effective ceiling instead of 20). The cap also changes what “comparing logits across engines” means — differences near the ceiling are compressed non-linearly, one reason the parity campaign compares tokens and logprobs, not raw logit deltas, at the tail [docs/muser-architecture.md §Model and engine; deep treatment in Ch 20].
9.11 The per-layer tensor inventory
What does one layer actually consist of, as bytes on disk? The table below
is the answer, and it is also a promise the loader keeps: the shape checker
asserts that every tensor listed exists with exactly this shape, in every
layer, and fails closed if one is missing or misshapen
[crates/muser-engine/src/config.rs:286-333]. Read the shapes the way GGUF
writes them, [in, out].
Table 9.2 — Per-layer tensors (×52, l = 0..51)
Tensor (blk.{l}.*) | Shape | Role |
|---|---|---|
attn_norm.weight | [6656] | pre-attention norm γ, eps 1e-5 |
attn_q.weight | [6656, 4096] | query projection (32 heads × 128) |
attn_k.weight | [6656, 256] | key projection (2 KV heads × 128) |
attn_v.weight | [6656, 256] | value projection |
attn_q_norm.weight | [128] | per-head Q norm — constant ≈ 3.87 (§9.9) |
attn_k_norm.weight | [128] | per-head K norm — constant 1.0 |
attn_gate.weight | [6656, 4096] | sigmoid-gate projection (§9.8) |
attn_output.weight | [4096, 6656] | o_proj |
post_attention_norm.weight | [6656] | sandwich post-norm, eps 1e-8 |
ffn_norm.weight | [6656] | pre-FFN norm, eps 1e-5 |
ffn_gate.weight | [6656, 19968] | SwiGLU gate |
ffn_up.weight | [6656, 19968] | SwiGLU up |
ffn_down.weight | [19968, 6656] | SwiGLU down |
post_ffw_norm.weight | [6656] | second sandwich post-norm, eps 1e-8 |
Three global tensors sit outside the loop [config.rs:294-298]:
token_embd.weight and output.weight, both [6656, 202048], and
output_norm.weight [6656]. The embeddings are untied: output.weight
(the LM head) is a required tensor in the shape contract, not an optional
one — the model carries two independent [6656 × 202048] matrices, one to
enter and one to leave. Whether untying buys quality is a modeling claim
this tree cannot support ([unverified]); the cost is countable: both
matrices are 2.69 B of the 27.85 B parameters (§9.12).
9.12 Parameter accounting — what “~30B” actually is
The model is sold as a thirty-billion-parameter model. Is it? Everything needed to check now sits in the tables above, so rather than quote the label, we count — and the counting is worth doing slowly, because the shape of the total tells the kernel chapters where to spend their effort.
Deriving the count from Table 9.2: per layer, the five attention
projections sum to 3 × (6656×4096) + 2 × (6656×256) = 85,196,800, the
FFN adds 3 × (6656×19968) = 398,721,024, the norms 26,880 — 483,944,704
per layer × 52 = 25,165,124,608. The global tables add
2 × (6656×202048) = 2,689,662,976 plus the output norm, for
≈ 27,854,794,240 ≈ 27.85 B parameters. The repo labels the model “Muse
Glimmer-30B” [crates/muser-engine/src/lib.rs:1]; the honest arithmetic
gives ≈ 27.85 B — same class, not the same number, and this book uses the
derived one.
The distribution matters more than the total: 82 % of each layer’s
parameters are FFN, the attention projections 18 %, the two untied vocab
matrices 9.7 % — decode bandwidth is dominated by the FFN matvecs
(Ch 18), and the DFlash verify path of
Ch 33 is a batching story.
One more division for Ch 1’s
roofline: the artifact is 16,756,681,056 bytes [muse_golden.rs:15], so the
average weight occupies 16,756,681,056 / 27,854,794,240 ≈ 0.60 B ≈ 4.81 bits — the q4_k_xl mix of Ch 6 slightly above
pure Q4_K’s ~4.5 bits.
9.13 Tokenizer and template identity
Geometry is not the whole of a model’s identity. Something with exactly the right shape, that reads text with a slightly different tokenizer or wraps it in a slightly different chat template, is a different model in every way a user would notice — same weights, different answers. So Muser serves one model with one tokenizer and one template, binding the whole surface by hash at load time:
- Tokenizer — a GGUF BPE tokenizer, merge-order aware: “BPE tokenizer
that respects merge priority order from GGUF metadata”
[crates/muser-engine/src/tokenizer/bpe.rs:8-10], with GPT-2 byte-mapping
and byte-fallback paths plus an SPM-style mode selected by
tokenizer.ggml.pre[bpe.rs:33-41; loader.rs:39-40]. The loader fails closed on a vocab-length mismatch [loader.rs:41-47], and the whole tokenizer metadata surface — vocab, merges, token types — carries one SHA-256,61e73226502f8f54455555990c0000852247bbec32b107730ec544bc0b738055[loader.rs:61], asserted against the pinned artifact [crates/muser-server/src/chat_template.rs:258-261]. - Chat template — a Jinja-style template ships inside the GGUF
(
tokenizer.chat_template). The pinned artifact’s is exactly 7,167 bytes, SHA-256114f55ebdc1804c1af371197b9fdf2d6bb925966c9dfe46b73782a71bc07965e, asserted at load [chat_template.rs:252-257; loader.rs:48-55]. Why hash it? Rendering drift changes prompts → tokens → everything downstream; template identity is bound into durable bundles [crates/muser-server/src/session_store.rs:26]. - End-of-generation — two control tokens are declared EOG, EOS
200,001and EOT200,008, with theeot/eommetadata keys merged into one set [config.rs:213-226; muse_golden.rs:106-108] — “dual EOS” is an asserted model fact [crates/muser-engine/src/lib.rs:11-13].
The point of this battery of hashes is the same fail-closed posture as §9.2: an engine that quietly accepted a different tokenizer or template would produce plausible text with the wrong identity.
9.14 Tradeoffs
The decisions above were made by the model’s authors, not by Muser — but each dictates what the kernels must be good at, and several have measured consequences here.
GQA 16:1 — the 16× KV lever. Sharing 2 KV heads across 32 query heads shrinks the per-token KV read and the cache by 16× (§9.6 arithmetic). At the 131,072-position limit that is ≈ 1.827 GB versus ≈ 29 GB of KV per slot [docs/memory-footprint.md] — four full-context slots on 96 GB, or none. The measured consequence of the NoPE half of that saving is the kvpack delta handoff: a 32,768-of-65,536 request moved 54.2851 % of full bytes with a bit-exact output [ledger stage-6; benchmarks.md §4] — possible only because NoPE tiles are position-free bytes (§9.7).
The sandwich norms buy training stability and cost an exactness contract. We told this one as a war story a few sections back; here it is as a ledger entry. Every “obvious” fusion across the 1e-8 post-norms has been measured to change public logprobs beyond the 1e-4 contract, and the hybrid retained-activation schedule that reached max normalized-logprob error 3.197e-4 was removed rather than hidden behind a widened tolerance [docs/decode-dispatch-gap-20260815.md]. What ships instead is a fusion built backwards from the requirement: the dual-eps fused tail reproduces the pinned kernels’ rounding boundaries exactly [decode.rs:1328-1330] — a fusion whose design constraint is bits, not speed (Ch 12, Ch 35).
The soft cap changes logit comparison rules. Because 20·tanh(l/20) is
non-linear, equal logit differences near the ceiling are not equal
evidence differences; cross-engine comparisons at the tail must use tokens
and logprobs, not raw logits [docs/muser-architecture.md §Model and engine]
— one reason Ch 38’s parity gates are
built the way they are.
9.15 What comes next
You now hold the complete map: 52 sandwich-norm blocks in a
[sliding, sliding, sliding, full] collar, GQA 32:2 at head_dim 128 over a
6,656-wide residual stream, a sigmoid gate before every o_proj, dual-epsilon
norms, and a scale-then-cap logit tail — every number double-cited, every
kernel chapter from here pointing back at a row in Tables 9.1–9.2. The open
question is movement: how does one token walk this graph on the Metal
side — which kernels run, in what order, what is fused, where every buffer
lives, and how the DFlash draft and the remote-prefill handoff overlay the
loop? Ch 10 answers with one picture.
References
crates/muser-engine/src/config.rs— :13-28 constants and the 1e-8 post-norm; :51-93MuseLayerKind/uses_rope/layer % 4 == 3; :158-211MuseConfig::from_gguf(every key of Table 9.1); :268-281attn_dim/kv_dim/heads_per_kv/attn_scale; :286-333 tensor-shape contract; :347-381 SWA-pattern resolver; :390-403 andloader.rs:98-138QkNormProbe.crates/muser-engine/src/reference.rs:297-580— the CPU oracle: layer walk, gate placement, sandwich tails, scale-then-softcap, SWA visibility rule.crates/muser-engine/src/lib.rs— :1-15 asserted model facts; :74-79 QK-norm provenance; :99-109 oracle-as-spec; :126-131 tokenizer scope; :175-188 the graph summary.crates/muser-engine/src/decode.rs— :5569-5606 QKVG concurrency; :5793-5818 sigmoid gate and fused tail; :5869-5905 final-norm fusion and softcap; :1344-1358 two-class KV allocation.crates/muser-engine/src/loader.rs:28-63— load-time identity hashing.crates/muser-engine/src/metal.rs:46-68,src/rope_nco.rs:12-14— fixed fixture values (theta 500,000; the test-only1/√26literal).crates/muser-engine/src/shaders/—ferrite/rope.metal:624-657(adjacent-pair comment);ferrite/sigmoid_gate.metal:7-17;muse_reference.metal:15-27.crates/muser-engine/tests/muse_golden.rs:14-117— release-model identity, geometry assertions, dual EOS.crates/muser-server/src/chat_template.rs:235-263,src/session_store.rs:26— template/tokenizer hashes; bundle binding.crates/muser-cluster/src/schedule.rs:1-21— the 13 NoPE tiles[3,7,…,51]; relocate-as-bytes framing.docs/muser-architecture.md §Model and engine;docs/memory-footprint.md;docs/release-provenance.md:823;docs/decode-dispatch-gap-20260815.md.- [arxiv:1512.03385] He et al., Deep Residual Learning. [arxiv:1706.03762] Vaswani et al., Attention Is All You Need. [arxiv:2305.13245] Ainslie et al., GQA.
Chapter 10 — The forward pass at a glance
status: polished · path: Muse Glimmer, pinned Muser tree
Prerequisites: Ch 9 (the architecture — this chapter walks exactly that graph), Part I (command buffers, encoders, dispatch). This is the spine chapter: one diagram of the whole decode loop. The kernel-by-kernel walk begins in Ch 11.
Chapter 9 gave you the map — 52 sandwich-norm blocks, two attention classes,
a gated output, a scaled-and-capped logit tail — every number cited to the
pinned tree. This chapter makes it move. One token will walk the whole
graph on the Metal side, and we will watch the real code open every gate for
it: which kernel runs at each step, what is fused into what, which buffers
the token’s data flows through, and how the DFlash draft loop and the remote
prefill handoff wrap around the plain one-token walk without changing it.
Everything quoted here was read from the pinned tree; the op order comes from
a real walk of encode_token and its serving twin, not from a design doc.
10.1 Three regimes, one family of graphs
Before we can follow a token anywhere, we have to answer the question the engine itself answers first: what kind of work is this? An LLM generates text in regimes that differ enough that Muser compiles them as different routes over the same weights. Define them precisely, because every performance claim in this book is scoped to one of them — a throughput number quoted under the wrong regime is not a rounding error in the storytelling, it describes a different machine:
- Prefill — the first step of a generation: the entire prompt, all at once. Many query tokens hit each weight matrix, so the projections become GEMMs (matrix × matrix) with heavy arithmetic reuse per weight byte. Prefill is compute-bound.
- Decode — everything after: one token in, one prediction out, repeat. A single vector hits every weight matrix — a matvec with almost no reuse. Decode is bandwidth-bound; it is the regime this book follows, and the regime the whole engine is shaped around Ch 1.
- Speculative verify — decode’s batch-shaped cousin: when the DFlash
draft proposes a block, the target must score up to
MAX_DFLASH_BLOCK = 16tokens in one pass [crates/muser-engine/src/decode.rs:47]. For those few dispatches decode temporarily looks like prefill (multiple query rows), which is exactly why speculation is the fourth lever of [Ch 1] — it reintroduces reuse into a serial loop.
Why the regimes differ, in one sentence. Prefill has parallelism across tokens, so it is compute-bound; decode is one token, so it is bandwidth-bound; speculative verify buys back a slice of prefill’s reuse for the decode path. Same matrices, different bottleneck.
10.2 Two routes through the same 52 layers
So which graph does a single token actually run through — and why is the
answer not simply “the fastest one we have”? Here is the single most
important routing fact in the engine, and it is stated in a code comment at
the exact decision point. When serving hands the engine one token,
MetalMuseModel::forward_into does this:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/decode.rs:2077-2093
pub fn forward_into(
&mut self,
tokens: &[u32],
logits: &mut Vec<f32>,
) -> Result<(), MetalModelError> {
if tokens.len() == 1 {
let scheduler = Arc::clone(&self.shared.scheduler);
let _permit = scheduler.acquire(self.sequence_id, AcceleratorWork::Decode)?;
// The legacy one-token graph uses Ferrite fused residual/norm and
// gate-up kernels whose rounding diverges from the source-pinned
// llama Metal graph enough to breach public logprob tolerance.
// The one-row batch graph dispatches the exact pinned kernels and
// has the same KV transition, so it is the serving correctness
// path until each fused kernel independently passes full-logit
// parity.
*logits = self.forward_batch(tokens)?;
return Ok(());
}
}
Read that comment twice, because it records a fork we walked into with the opposite expectation. Muser inherited a single-token graph from Ferrite: fused residual/norm kernels, a fused gate-up kernel, fewer dispatches, less traffic. It was obviously the graph serving should use. We expected a free win and wired it into the hot path.
What came back was arithmetic that no longer matched the comparator. Fusing a norm into an add changes the order the partial sums are accumulated in, and floating-point addition is not associative, so the same weights produced logits that were close — and not close enough. The divergence from the pinned llama.cpp Metal graph breached the public-logprob tolerance. The lesson we took was not “fusion is bad”; it was that on this engine a fused kernel earns the serving path by passing full-logit parity on its own, one kernel at a time, and until it does, its speed is unspendable. Correctness gates speed. So serving refuses to use the fast graph, and the consequences of that refusal ripple through this whole chapter:
- The serving decode path is the one-row batch graph:
forward_into→forward_batch(decode.rs:2857) →forward_batch_hidden(decode.rs:3788) →encode_batch_hidden_range(decode.rs:3858), withtoken_count = 1. One row, exact pinned kernels. - The legacy single-token graph survives as the teacher-forced route:
forward_token(decode.rs:5432) →encode_token(decode.rs:5515). It is the benchmark lane that matches the comparator’s no-readback policy (forward_teacher_forced,decode.rs:2118-2137) and the phase-profiling route (MUSER_METAL_PHASE_PROFILE,decode.rs:5440). Its op sequence is what Part IV narrates, because it is the cleanest straight-line telling of the graph. Narrating it costs the reader nothing: the batch route mirrors it row for row, and the packed multi-sequence encoderencode_decode_group(decode.rs:4954) exists precisely to run “batch rows of the same op sequence”. - Multi-sequence decode packs up to four sequences into one graph:
forward_decode_group(decode.rs:4869) accepts1..=4models sharing oneMetalSharedexecutor, encodes all rows with one concurrent encoder, commits once, waits once (decode.rs:4920-4937). This is the rendezvous the server’s 250 µsDecodeBatchercoalesce window feeds [crates/muser-server/src/state.rs:231-254].
Whichever route runs, it stops at the same door before it encodes anything:
it acquires the scheduler. A single token asks for AcceleratorWork::Decode,
a prefill chunk asks for AcceleratorWork::Prefill per chunk
(decode.rs:2083-2084, decode.rs:2109). The reason is ownership — one
scheduler owns one accelerator — and when both kinds of work want that
accelerator the contest is not a tie: decode outranks prefill
[decode.rs:1040-1074].
10.3 The master diagram — one decode token, end to end
So far we have argued about which route runs. Now the walk itself: what
happens to one token, in the order the GPU is actually told to do it? This
is the figure the rest of the book zooms into. Boxes are dispatch groups as
the encoder issues them; FUSED marks a fusion that is live by default,
and the two opt-in fusions are marked as such. The op order is
encode_token’s, which the serving batch route mirrors.
flowchart TD
START([token_id, position n_past]) --> EMB
EMB["<b>① Embedding lookup — GPU</b><br/>muser_embedding_q4k<br/>token_embd row → residual [6656] — Ch 11"]
EMB --> ENTRY["<b>② Entry RMSNorm — GPU</b><br/>weight = all ones (weightless), eps 1e-5<br/>residual → normed"]
subgraph LOOP["per-layer — repeated ×52 (layers 0..51): [SWA, SWA, SWA, FULL] collar"]
direction TB
A0["<b>③ attn-norm</b> (layer 0 only —<br/>later layers receive it fused from ⑪)"]
QKV["<b>④ QKVG projections — one concurrent group</b><br/>Q [6656→4096], K [6656→256],<br/>V [6656→256], gate [6656→4096] — Ch 13"]
QKN["<b>⑤ per-head QK-norm</b><br/>32 Q-heads + 2 K-heads, eps 1e-5 — Ch 14"]
ROPE["<b>⑥ RoPE — SWA layers only</b><br/>rope_norm_batch_cached, theta 500,000<br/>NoPE layers skip this box — Ch 14"]
ATT["<b>⑦ KV store + attention — route ladder</b><br/>store K,V row into the plane, memory barrier,<br/>then llama-vec / splitk / ferrite-interleaved — Ch 15–16"]
SG["<b>⑧ sigmoid gate</b><br/>attn ⊙ σ(gate) — Ch 17"]
OP["<b>⑨ o_proj</b> [4096→6656] — Ch 17"]
T1["<b>⑩ FUSED dual-eps tail</b><br/>muser_fused_norm_residual_rms_norm_32sg:<br/>residual += post_attn_norm(⑨); then ffn_norm → FFN input — Ch 12, 19"]
FFN["<b>⑪ FFN gate+up — split by default</b><br/>2 matvecs [6656→19968] + muser_silu_mul_inplace<br/>OPT-IN fusion: ffn_q4k_gate_up_silu_4r2s — Ch 18"]
DOWN["<b>⑫ ffn_down</b> [19968→6656] — Ch 19"]
T2["<b>⑬ FUSED dual-eps tail ×2</b><br/>residual += post_ffw_norm(⑫); then the NEXT<br/>layer's attn-norm (or the final norm) — Ch 12, 19"]
A0 --> QKV --> QKN --> ROPE --> ATT --> SG --> OP --> T1 --> FFN --> DOWN --> T2
end
T2 --> LMH["<b>⑭ LM head</b><br/>output.weight [6656→202048] matvec — Ch 20"]
LMH --> CAP["<b>⑮ FUSED scale + soft cap</b><br/>muser_scale_softcap_inplace:<br/>× 0.196116 (1/√26), then 20·tanh(l/20) — Ch 20"]
CAP --> RB(["logits read back to CPU — one vocab row<br/>sampling / argmax on CPU — Ch 21"])
T2 -. "last layer only: ⑬ writes the final<br/>normed hidden — ⑭ reads it" .-> LMH
Figure 10.1: One decode token through Muse Glimmer on the Metal side, as
encode_token [crates/muser-engine/src/decode.rs:5515-5907] issues it. Boxes
③–⑬ run 52 times; the SWA layers run ⑥, the 13 NoPE layers skip it. The
residual stream lives in two ping-ponging buffers (§10.4). Sampling is CPU
work on the read-back row (§10.9).
Read the fusions explicitly. Fusions are where the diagram stops looking like the textbook, and they are where a reader gets lost: you go hunting for a step and it is not there, because it happens inside another one. So it is worth knowing exactly which logical steps the route collapses into single kernels, and what gates each collapse:
- (a) The dual-eps fused tails (boxes ⑩ and ⑬) — live by default. One
kernel,
muser_fused_norm_residual_rms_norm_32sg[crates/muser-engine/src/shaders/ferrite/rmsnorm_batch_tail.metal:147], does three logical ops:residual += post_norm(sub_block_out)with eps 1e-8, thennext_norm(residual)with eps 1e-5 — two norms and an add in one dispatch, emitting the next sub-block’s already-normed input. The same kernel closes every layer: box ⑬’s second output is the next layer’s attention-norm result (next_normselected atdecode.rs:5869-5876) — which is why box ③ runs only for layer 0. The dispatch is one threadgroup of 1,024 threads per row, 32 SIMD-group partials per reduction [crates/muser-engine/src/metal/encode/norm.rs:163-235]. The batch/serving route uses its batch twinmuser_fused_norm_residual_rms_norm_batch_dual_eps[rmsnorm_batch_tail.metal:250], bound atdecode.rs:4510-4525. - (b) Scale + soft cap (box ⑮) — live.
muser_scale_softcap_inplacemultiplies bylogit_scaleand applies the tanh cap in one pass over the logits [crates/muser-engine/src/shaders/muse_reference.metal:15-27; decode.rs:5898-5905]. - (c) The final norm is fused into the last layer’s tail. For layer 51,
box ⑬’s “next norm” is the model’s
output_normand its residual output goes to the buffer the LM head reads (decode.rs:5869-5876): the final RMSNorm is not a separate dispatch on this route. - (d) FFN gate+up+SiLU (box ⑪) — opt-in, OFF by default. The Ferrite
four-row/two-SIMD-group kernel
ffn_q4k_gate_up_silu_4r2s[shaders/ferrite/ffn_fused_tail.metal:496] reads both Q4_K weight matrices once and emits the finishedSiLU(gate) ⊙ uprow — but only underMUSER_FERRITE_FFN_GATE_UPand only when both matrices are Q4_K (decode.rs:5819-5836). The reason it is opt-in is measured, not aesthetic: “The pinned baseline packet regressed with it on this model, so keep the imported route explicitly opt-in for experiments” [decode.rs:980-983]. The default is two matvecs plusmuser_silu_mul_inplace[muse_reference.metal:4]. - (e) Q/K/V/gate share one concurrent dispatch group (box ④). Four
independent matvecs read one shared normed input and write disjoint
activations — issued as one group so the concurrent encoder can overlap
them (
decode.rs:5566-5598; the comment: “llama.cpp and Ferrite issue the four independent attention projections as one concurrent set”). Note what is deliberately not fused here: there is no fused QKV+RoPE mega-kernel on this path, unlike the ancestor book’s engine [ferrite-book Ch 13] — the pinned-kernels correctness rule (§10.2) forecloses it. - (f) KV store and attention are separate dispatches with an explicit
barrier (box ⑦) on the vec routes: store K/V,
memory_barrier_with_resources, attend (decode.rs:5660-5670). The barrier, not a fused kernel, is what orders the store before the read.
Everything from ① to ⑮ is recorded onto one command buffer with one concurrent encoder per token, committed once:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/decode.rs:5448-5458
let command_buffer = queue.new_command_buffer();
// One concurrent encoder owns the complete token. Graph dependencies
// are explicit barriers; independent projection groups share a barrier
// interval and may overlap, matching the accepted Ferrite/llama route.
let serial = GraphEncoder::concurrent(
command_buffer
.compute_command_encoder_with_dispatch_type(metal::MTLDispatchType::Concurrent),
);
self.encode_token(&serial, &token_view, self.n_past)?;
serial.encoder.end_encoding();
command_buffer.commit();
}
This is command-buffer amortization (Ch 2): the whole 52-layer tape is recorded, then “play” is pressed exactly once per token.
10.4 The residual stream — two buffers in a relay
The master diagram moves a token through boxes. But where do the token’s bytes actually sit while that happens, and who is allowed to overwrite them? Get this wrong and the symptom is not a crash — it is a layer reading a value one dispatch too late, which shows up much later as logits that are subtly, and unfixably, the wrong ones.
In the ancestor book the residual stream was one buffer, mutated in place 56 times. Muser’s decode graph runs a two-buffer relay instead, and the code documents it at the top of the layer loop:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/decode.rs:5546-5548
// `normed` is the current hidden buffer at layer entry. Each layer
// writes the attention residual to `hidden`, then the FFN result back
// to `normed`, so no full-width copy dispatch is needed.
}
One naming trap before the figure, because it costs an afternoon if you walk
into it: the hidden/normed in that comment are the kernel parameters of
box ⑩/⑬, not buffer names — the buffers are activations.normed and
activations.post_norm. Per fused tail — Figure 10.2 — the kernel does:
activations.normed activations.post_norm layer.post_attn_norm (w1)
(residual, 6656 f32) (o_proj out, 6656 f32) layer.ffn_norm (w2)
│ │
▼ ▼
┌────────────────────────────────────────┐ eps1 = 1e-8 (post norm)
│ residual += src · rsqrt(mean(src²)+eps1) · w1 │ eps2 = 1e-5 (ffn norm)
└────────────────────────────────────────┘
│ │
▼ ▼
normed (updated) → post_norm = normed · rsqrt(mean(normed²)+eps2) · w2
(the FFN's already-normed input)
Figure 10.2: One fused dual-eps tail (boxes ⑩ and ⑬). Two RMSNorms, one
residual add, one kernel — semantically exactly the CPU oracle’s
rms_norm_mul(proj, post_norm_eps); hidden = proj + residual; rms_norm_mul(hidden, rms_eps) [crates/muser-engine/src/reference.rs:466-489].
So the residual bytes live in activations.normed from the entry norm until
layer 51’s tail, which writes the final normed stream into
activations.hidden for the LM head. Each layer reads the stream twice
(once per tail) and rewrites it twice — 104 full-width reads plus 104 writes
per token across the model, all inside fused kernels, zero standalone copies.
Why the relay instead of in-place mutation? Because each tail must read the pre-add residual to norm it for the next sub-block while simultaneously writing the post-add stream — and because the fused kernel’s two reductions must reproduce the pinned ggml kernels’ rounding boundaries exactly, float4 lane for float4 lane [decode.rs:1328-1330; norm.rs:163-235]. The buffer layout is a consequence of the exactness contract, not a style choice.
10.5 The attention route ladder (box ⑦, up close)
Box ⑦ is the only box in the master diagram that hides a decision. Attention is not one kernel but a ladder: per layer, per token, the engine picks a rung by reading the live KV plane’s metadata. Two questions settle it — is the pinned llama vec kernel safe on this plane, and does this layer’s window let it run without padding? Here are the predicates that answer them, verbatim from the route walk:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/decode.rs:5646-5657
let llama_vec_rows = (strict_attention || self.kernels.has_llama_flash_attn_vec())
&& plane.len > 0
// The pinned vec kernel rounds KV reads to a 32-row block.
// A deliberately tiny raw session can have a smaller backing
// allocation, so taking the vec path would read past it and
// poison the full distribution with NaNs.
&& plane.capacity >= 32
&& (plane.origin_physical == 0 || plane.len == plane.capacity);
// Token-major SWA cannot use llama's pad kernel (nb11 is a full
// token row, not one head). Only take that path when the window
// is a multiple of 32 so the vec kernel never pads.
let llama_swa = llama_vec_rows && plane.len.is_multiple_of(32);
}
Read that middle comment as the scar it is. The vec kernel is both the fast rung and the exact rung — it is the comparator’s own arithmetic — so the tempting rule is “take it whenever it exists.” A deliberately tiny raw session breaks that rule. The kernel rounds its KV reads up to a fixed block size; a session whose backing allocation is smaller than one block gets read past its end; and the failure does not announce itself as a crash. It comes back as NaNs spread across the full distribution, which is the worst failure mode there is — silent, and it looks like a model bug rather than a routing bug. So the predicate is fail-closed: the plane must prove the fast rung is safe, or the engine takes a rung it can defend.
The four rungs the predicates select between (decode.rs:5658-5792):
| Layer kind | Vec-eligible | Kernel sequence | Fallback |
|---|---|---|---|
| SWA (39) | yes, window % 32 == 0 | muser_kv_store_f16 → barrier → llama-pinned flash_attn_ext_vec | — |
| SWA (39) | no | muser_kv_store_f16 → muser_attention_decode_splitk_f16 + reduce | token-major ring walk |
| NoPE (13) | yes | muser_kv_store_batch_f16 → barrier → llama-pinned vec | — |
| NoPE (13) | no | flash_attn_decode_vec_f16_gqa_interleaved (Ferrite lineage) + reduce | — |
Table 10.1: The decode attention ladder. “llama-pinned” kernels come from
the prebuilt llama.cpp metallib (MUSER_GGML_METALLIB) so their arithmetic
is the comparator’s own [crates/muser-engine/src/metal/context.rs:122-131].
The capacity ≥ 32 guard is fail-closed: the pinned vec kernel reads in
32-row blocks, and a tiny backing allocation would be read past its end
[decode.rs:5648-5652].
Which planes exist in the first place is Ch 9’s two-class story made
concrete: SWA layers get a 2,048-row token-major ring, NoPE layers a growing
head-major plane, allocated zero-filled so wrapped rows never leak stale
storage [decode.rs:1344-1358]. The ring is carried as explicit
origin_logical/origin_physical metadata, and append refuses any
position that is not exactly the next one — a CacheDiscontinuity error —
so no physical placement is ever derived from absolute position
[decode.rs:265-284].
10.6 Overlay one: the DFlash draft/verify/accept loop
Everything so far buys exactly one token per trip through the master diagram. Can a trip be made to pay for more than one? That is the question the speculative lane asks, and its answer is a bet: guess ahead cheaply, then check the guesses against the real model instead of trusting them.
Plain decode proposes one token at a time. The speculative lane wraps the same graph in a three-beat loop — draft, verify, accept — and its Metal machinery is a split of Figure 10.1, not a different model (Figure 10.3):
flowchart TD
subgraph ROUND["one speculative round (verify-length ≤ 16 rows)"]
direction TB
D["<b>Draft</b> — DFlash, the 5-layer assistant of Ch 8<br/>draft_greedy: reads target hidden states captured at<br/>pinned target layers (DFlashConfig.target_layer_ids)"]
B["<b>begin_dflash_verify_suffix</b> decode.rs:3298<br/>prefix command buffer: run the block through layers 0..capture_end<br/>synchronously; copy exact hidden rows out for the draft; then<br/>submit the REMAINING layers + LM head without waiting"]
W["<b>Wait-free overlap</b><br/>draft consumes the captured rows while the<br/>target suffix is still on the GPU"]
F["<b>finish_dflash_verify_suffix</b> decode.rs:3635<br/>wait for the suffix command buffer;<br/>read back the block's full-vocab logit rows"]
V["<b>Verify on the CPU — exact</b><br/>verify_full_speculative_mt_ordered sampling.rs:1033<br/>accepts/rejects each drafted token against the<br/>target's own full distributions"]
C{"accept prefix?"}
OK["<b>commit_speculative_prefix</b> decode.rs:1413<br/>accepted K/V rows stay exactly where the<br/>block's execution wrote them"]
RB["<b>Rollback</b> — restore MetalSpeculativeCheckpoint:<br/>NoPE planes rewind metadata only; SWA planes<br/>restore the ≤16 rows a block may overwrite"]
D --> B --> W --> F --> V --> C
C -- "prefix accepted" --> OK
C -- "first rejection" --> RB
end
OK --> D
RB --> D
Figure 10.3: The DFlash speculative overlay on the decode graph. The target’s KV planes are protected by a transactional checkpoint whose design comment is worth quoting: “Growing NoPE planes only need their logical metadata rewound. SWA planes may overwrite live ring rows, so the small set of destinations touched by the candidate block is retained here instead of copying the complete multi-gigabyte cache on every DFlash round” [crates/muser-engine/src/decode.rs:208-212].
Three properties of this loop carry the engine’s exactness culture:
- Verification is exact and CPU-side. Acceptance compares every drafted
token against the target’s full distributions — the very same read-back
vocab rows that plain decode samples from. Nothing cheaper is allowed
near the accept decision: no approximate verifier, no shortcut on the
logits. One lane did try the shortcut, and the distributed verifier was
measured and rejected for it; that war story is
Ch 33. The code to read
is
verify_full_speculative_mt_ordered[crates/muser-engine/src/sampling.rs:1033], reached from the engine entry pointsSession::verify_batchandbegin/finish_dflash_verify_suffix[crates/muser-engine/src/api.rs:913; decode.rs:3298, 3635]. - The split point is a real command-buffer boundary with a correctness rule. The suffix re-materializes its entry norm from the authoritative residual instead of trusting a cross-command-buffer temporary: “Do not rely on the fused layer-49 tail’s secondary normalized output surviving as an implicit input to layer 50” [decode.rs:3388-3393].
- KV mutation is transactional. Commit keeps accepted rows in place; rollback restores only what a ≤16-row block could have overwritten [decode.rs:1413-1419].
So what does the bet actually pay? The kquant speculative lane is the
engine’s speed lane: the 107.9 tok/s figure survives as the qualification
bar, and the current synthetic restatement at the fixed draft window is
decode ratio 1.23692 at 2,048 context. The scope language around that ratio
matters as much as the ratio does — five of five exact reps, synthetic only,
never a natural-text workload claim — and the row that holds that scope is
retained: [claims #15]. The loop’s deep treatment, including why native
NVFP4 speculation is fail-closed by construction, is
Ch 33.
10.7 Overlay two: the handoff that plants KV before decode starts
The second overlay changes what happens before Figure 10.1 runs at all. On the disaggregated lane, the GX10 producer prefills NVFP4 and ships the resulting KV across the wire; the Mac’s job is to plant those bytes and start decoding from a nonzero position (Figure 10.4):
flowchart LR
P["GX10 producer<br/>vLLM NVFP4 prefill"] -->|"Handoff V2<br/>mTLS + HMAC-sealed tiles"| R["Mac receiver"]
R --> S["scatter-on-arrival:<br/>each authenticated tile unpacked into a<br/>DETACHED Metal generation as it arrives"]
S --> C["validate_complete — every expected<br/>K/V row arrived and verified"]
C --> SW["commit: n_past = tokens.len();<br/>cache = install.planes<br/>(atomic swap, decode.rs:1990-1994)"]
SW --> D["Figure 10.1 starts at position n_past —<br/>no local prefill ever ran"]
Figure 10.4: The remote-KV install path
([crates/muser-engine/src/decode.rs:1852-1994]). “Detached” is the operative
word: the install builds its own MetalKvPlane set alongside the live one,
and the swap happens only after the seal validates — live decode never
observes a half-planted cache.
The engine-side entry is begin_remote_kv_install, and its ring handling
encodes a subtle exactness rule — the planted SWA ring must sit at the same
physical rotation a sequentially-built one would have:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/decode.rs:1880-1885
plane.origin_logical = origin;
// Match a sequentially-built ring exactly. Physical scan order is
// numerically observable, so remote restore cannot repack the
// retained logical tail at row zero.
plane.origin_physical = origin % live.capacity;
plane.len = len;
}
“Physical scan order is numerically observable” is a sentence to sit with:
because the attention kernels accumulate in physical row order, where the
ring rows sit changes floating-point sums — so a remote install that
prettily repacked the tail to offset 0 would produce different bits than a
local prefill of the same tokens. The delta variant
(begin_remote_kv_install_delta, decode.rs:1908) extends this to warm
prefixes: the held [0, cut) span is copied out of the live planes with the
exact ring mapping and only suffix tiles are accepted.
The wire schedule turns Ch 9’s two-class split into a scheduling advantage. The 13 NoPE layers’ tiles are position-free bytes — nothing in them depends on where in a ring they will eventually sit — so they need not wait for the producer to finish. They stream during CUDA prefill, one HMAC/TLS frame per 512-token NoPE tile, ~6.5 MiB. The SWA tiles have no such freedom, and the tail groups ship in the last ubatches (micro-batches) [crates/muser-cluster/src/schedule.rs:1-12, 20].
What that overlap buys is the Part VI headline: TTFT (time to first token)
4.149× faster than local prefill at 130,815 tokens, remote 137.405 s against
local 570.122 s. The arm matters as much as the ratio — this is the EEE-off
arm, Energy-Efficient Ethernet disabled on the link, which is
Ch 31’s invariant — and it is five counted
reps. We kept the claims row that carries the whole scope: [claims #6].
Reusing a planted cache across requests is its own ladder, in
Ch 25; the transport underneath — mTLS, the HMAC-sealed
manifest, the replay ledger — is Ch 30; and
whether NVFP4-produced KV can be trusted at all is
Ch 32.
10.8 Where every buffer lives
Figure 10.1 names buffers; this section sizes them — and on a machine with
unified memory, sizing is design. How many sequences a Mac can serve at
once is settled less by the kernels than by which allocations every sequence
must own and which can be paid for once. So two scopes matter here.
Per-sequence state: each of up to four slots owns a full set.
The shared executor MetalShared: one context, one kernel set, one
mmap’d weight arena, retained deliberately because “retaining one context,
pipeline set, mapped weight arena, and GPU vector set avoids loading the
16+ GiB target once per serving slot” [decode.rs:954-957]. Sizes below are
derived; the arithmetic is shown so you can re-derive it.
Table 10.2 — Per-sequence activation pool (Activations::new,
[decode.rs:897-952]; all GPU, all allocated once at session construction)
| Buffer | Width (elements) | Bytes | Written by |
|---|---|---|---|
token_ids | 64 × u32 | 256 | host staging (teacher-forced width) |
normed | 6,656 | 26,624 | residual stream (§10.4), entry norm, tails |
post_norm | 6,656 | 26,624 | sub-block outputs / next normed input |
projected | 6,656 | 26,624 | o_proj / ffn_down results |
hidden | 6,656 | 26,624 | final normed stream (LM head input) |
q | 4,096 | 16,384 | ④⑤⑥ |
k, v | 256 each | 1,024 each | ④⑤⑥ |
gate | 4,096 | 16,384 | ④ (sigmoid source) |
attention | 4,096 | 16,384 | ⑦ (gated in place by ⑧) |
attention_partials | 32 heads × 32 groups × (2+128) | 532,480 | splitk / vec reduce scratch |
attention_mask | 131,072 × u16 | 262,144 | vec-kernel mask |
swa_llama_mask | 131,072 × u16 | 262,144 | preset −∞ f16 pattern |
attention_kv_pad (+_masked) | 32-row pad blocks | 65,536 + 65,600 | vec-kernel 32-row rounding |
ffn_gate, ffn_up | 19,968 each | 79,872 each | ⑪ (gate becomes SiLU⊙up in place) |
logits | 202,048 | 808,192 | ⑭⑮, read back once per token |
dflash_hidden | 16 × 6,656 | 425,984 | speculative capture |
dflash_logits | 16 × 202,048 | 12,931,072 | verify-block logit rows |
dflash_argmax_* | partials + results | ≈ 25,400 | no-readback draft lanes |
Table 10.2: The one-token activation pool totals ≈ 15 MB dominated by the speculative block’s logit rows — noise next to the weights (below), and every buffer is reused every token with zero hot-path allocation.
Table 10.3 — Per-layer KV planes (from_shared, [decode.rs:1344-1358];
f16 element type on every Metal lane [decode.rs:236-237])
| Layer kind | Count | Capacity | Plane pair size (K+V) |
|---|---|---|---|
| SWA ring | 39 | min(max_context, 2,048) | 2,048 × 256 × 2 B × 2 = 2,097,152 B = 2 MiB |
| NoPE growing | 13 | max_context | C × 1,024 B (131,072 → 128 MiB) |
One slot at the 131,072 limit: (39 × 2 MiB) + (13 × 128 MiB) ≈ 1.827 GB
[docs/memory-footprint.md]; four slots ≈ 7.306 GB.
Shared, GPU-mapped, read-only during decode: the mmap’d weight arena —
16,756,681,056 bytes of GGUF, zero-copy views
[crates/muser-engine/src/lib.rs:14; docs/muser-architecture.md]; the entry
norm’s ones vector (6,656 × f32); the RoPE frequency table
(head_dim/2 = 64 f32 values built once at startup,
[decode.rs:1240-1264]) and the position table (131,072 u32); per-layer norm
weights. Prefill-only workspace (BatchWorkspace, [decode.rs:799-856]):
token-scaled activation twins plus two SWA staging shadow planes of
131,072 × 256 × 2 B = 64 MiB each and flash-attention scratch — allocated
per chunk width, reused across chunks. Packed decode workspace
(DecodeBatchWorkspace, [decode.rs:858-866]): up to 4 rows × vocab logits
(4 × 808,192 B ≈ 3.2 MB).
CPU side: the retained distribution Vec<f32> (202,048 × 4 = 808,192 B)
refilled in place per token, sampler/RNG/grammar state, the detokenizer —
all outside the accelerator owner [docs/muser-architecture.md §Slots and
scheduling].
10.9 CPU vs GPU — the division of labor
Where does the seam between the two processors fall, and who is holding the token when something goes wrong? The GPU owns the entire arithmetic graph — embedding through softcap, one command buffer per token (§10.3). The CPU owns everything around it. The serving loop shows both the handoff and the failure policy in the same few lines:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/api.rs:699-708
// The decode path refills the retained distribution in place, so a
// token costs one vocabulary-sized copy for the result instead of two
// fresh allocations.
let mut logits = self.last_logits.take().unwrap_or_default();
if let Err(error) = self.forward_into(&[input.token_id], &mut logits) {
// A failed forward leaves the buffer untouched, so the
// distribution installed before the call is still the current one.
self.last_logits = (!logits.is_empty()).then_some(logits);
return Err(error);
}
}
After forward_into returns, the CPU validates finiteness
(ensure_finite_logits — a NaN row installs no distribution, fail-closed),
takes the argmax or samples (api.rs:715-737), and hands the token back.
Speculative acceptance (§10.6) is likewise CPU work on read-back rows. A
GPU two-phase argmax exists (argmax_f32_phase{1,2},
greedy_argmax_f32_phase{1,2} [shaders/ferrite/argmax_f32.metal:7,41,77,125])
but serves the no-readback benchmark lanes, not the sampling path — the
read-back here is one vocab row per token, 808 KB, which the CPU must see
anyway to sample.
10.10 Prefill is a different graph
Everything above is the decode route. Prefill earns a section even in a
decode book for a reason worth stating plainly: the remote lane above is an
argument about this graph, and you cannot judge what shipping a cache
across a wire saves until you know what the local prefill it replaces would
have cost. When forward_into receives more than one token it becomes
prefill, and the route changes shape at every joint (decode.rs:2095-2113):
- Chunking: prompts stream through a 512-row physical batch
(
PREFILL_BATCH_TOKENS,decode.rs:53); once any decoder is queued the boundary shrinks to 64 rows (MAX_TEACHER_FORCED_TOKENS,decode.rs:54) so decode can take the accelerator “without another long accelerator interval in front of it” [decode.rs:2098-2101]. - Projections become batch GEMMs with weight-row reuse
(
encode_batch_projection,decode.rs:5946-5980; the M16 NVFP4 route fires at 16-row multiples). This is the roofline flip of §10.1: same weights, arithmetic per byte now dominates. - Attention routes differ: contiguous cache ranges take llama’s pinned
prefill kernels or the local FA2 (
decode.rs:4090-4365); an SWA ring that has wrapped stages old rows into a detached F16 shadow first (encode_stage_swa_prefill_f16) and commits ring metadata only after the chunk attends from the shadow. - The one-row output graph gets an exactness special case: when the last
row of a deep prompt produces the output logits, the fused dual-eps tail
splits back into the exact pinned three-dispatch sequence
(
llama_final_row_boundary,decode.rs:4473-4478) — the same correctness-first instinct as §10.2, applied at a batch boundary.
Prefill’s full treatment is Ch 36; the remote lane that replaces Mac-side prefill entirely is Part VI.
10.11 Tradeoffs
Three structural bets shaped this code. None was obvious in advance, and each was settled by a measurement that contradicted what somebody — usually us — expected.
Bet 1 — route serving decode through the one-row batch graph, not the fused single-token graph. The routing section told this fork from the code’s side; here is what it cost and what settled it. The cost is real: the Ferrite-lineage fused kernels sit off the hot path, and the dispatch savings they were written for go unspent. The first piece of evidence is the routing comment itself — the fused kernels’ rounding “breaches public logprob tolerance” against the pinned llama graph [decode.rs:2085-2091]. The second came from trying a variant and watching it fail. We ran a hybrid retained-activation schedule expecting the divergence to stay inside the parity contract, and it did not: max normalized-logprob error 3.197e-4 against a 1e-4 contract, with 201,970 of 202,048 logits differing, and the first divergence traced to a single f16 ULP in layer-1 V. It was removed [docs/decode-dispatch-gap-20260815.md]. Sit with that ratio for a moment — one unit in the last place, in one tensor, in one early layer, and nearly the entire vocabulary row comes out different. A verified-slow path beats an unverified-fast path.
Bet 2 — keep the fused FFN gate-up kernel opt-in. This one we expected
to win outright. The imported ffn_q4k_gate_up_silu_4r2s reads both Q4_K
weight matrices once rather than twice, which is strictly less traffic in
the hungriest part of the layer, and on the ancestor engine it paid. So we
enabled it and ran the pinned baseline throughput packet — and the packet
regressed on this model [decode.rs:980-983]. Fusing is not automatically
faster; the arithmetic on paper does not overturn a measurement. The code
keeps the experiment behind a flag instead of deleting it, because what lost
here is a result about this model on this machine, not a verdict on the
kernel forever.
Bet 3 — one concurrent command buffer per token, explicit barriers, no scheduler surgery to close the dispatch gap. The gap announced itself as waste. The one-token diagnostic at a 2,048-token fixture counted 760 profiling closures against the legacy graph’s 564 — and a delta that size looks like something you can simply delete. So we went hunting for what to remove, and the first surprise was that nothing was left over: the +196 delta reconciles exactly into 104 norm-boundary groups + 39 SWA staging groups + 52 KV-publication splits + 1 bookkeeping copy [docs/decode-dispatch-gap-20260815.md §label table]. Every one of those groups exists because something must be ordered before something else. The second surprise was the price list. Every cheap removal changed bits — those are Bet 1’s numbers — and the one exact removal, the last row copy, bought −0.136 ms GPU (−0.34 %) on a 40.330 ms token [docs/decode-dispatch-gap-20260815.md §Landed and rejected reductions]. That is what a structural gap looks like as opposed to a sloppy one: the engine chose exactness over the ~3 % it could have stolen. Ch 35 tells the whole story.
10.12 What comes next
You have now seen one token’s whole journey: staged, embedded, normed, and
sent through 52 layers of concurrent matvecs, gated attention, and fused
norm tails to a scaled, capped, read-back distribution — plus the two
overlays (speculative draft/verify, remote KV install) that wrap the loop
without altering its arithmetic. The map is complete; the descent begins.
The first kernel the token actually meets is the humblest one in Figure
10.1 — box ①, the embedding lookup, a single-row gather from a
1.3-billion-value quantized table ([6656 × 202048], Table 9.2) that turns
an integer into the residual stream.
Ch 11 opens it.
References
crates/muser-engine/src/decode.rs:41-54— split-workgroup cap, DFlash block width, chunk constants.crates/muser-engine/src/decode.rs:182-226—MetalKvPlaneand the speculative checkpoint contract.crates/muser-engine/src/decode.rs:265-314— fail-closedappend/append_batch.crates/muser-engine/src/decode.rs:799-866— batch and packed-decode workspaces.crates/muser-engine/src/decode.rs:897-984— the per-sequence activation pool;MetalShared.crates/muser-engine/src/decode.rs:1216, 1240-1268— entry-norm ones; RoPE tables.crates/muser-engine/src/decode.rs:1328-1334— fused-tail / concurrent-prefill / FFN-fusion env defaults.crates/muser-engine/src/decode.rs:1344-1358— two-class KV allocation.crates/muser-engine/src/decode.rs:1386-1419— speculative checkpoint and prefix commit.crates/muser-engine/src/decode.rs:1852-1994— remote KV install: begin, delta variant, commit/swap.crates/muser-engine/src/decode.rs:2077-2137—forward_intoand the serving-route comment; teacher-forced sink.crates/muser-engine/src/decode.rs:2857-2927—forward_batchentry.crates/muser-engine/src/decode.rs:3298-3407, 3635-3666—begin/finish_dflash_verify_suffix; the boundary-norm rule.crates/muser-engine/src/decode.rs:3788-3912—forward_batch_hidden/encode_batch_hidden_range.crates/muser-engine/src/decode.rs:4473-4549, 4570-4611—llama_final_row_boundary; batch dual-eps tails; split FFN.crates/muser-engine/src/decode.rs:4869-4937, 4954-4975— packed decode group and its mirror encoder.crates/muser-engine/src/decode.rs:5432-5513—forward_token; phase labels (the op census).crates/muser-engine/src/decode.rs:5515-5907—encode_token, the full op walk (Figure 10.1’s source).crates/muser-engine/src/metal/encode/norm.rs:163-235— fused dual-eps dispatch wrappers; 1,024-thread geometry.crates/muser-engine/src/shaders/ferrite/rmsnorm_batch_tail.metal:147, 250— the 32sg and batch dual-eps kernels.crates/muser-engine/src/shaders/ferrite/ffn_fused_tail.metal:496— opt-inffn_q4k_gate_up_silu_4r2s.crates/muser-engine/src/shaders/muse_reference.metal:4, 15-27—muser_silu_mul_inplace;muser_scale_softcap_inplace.crates/muser-engine/src/shaders/ferrite/argmax_f32.metal:7,41,77,125— GPU argmax phases (benchmark lanes).crates/muser-engine/src/api.rs:696-737— serving decode: retained distribution, fail-closed finiteness, CPU argmax.crates/muser-engine/src/sampling.rs:1033— exact CPU speculative verification.crates/muser-engine/src/dflash/spec.rs:730—draft_greedy(verify-length 3/7/15).crates/muser-cluster/src/schedule.rs:1-21— NoPE-tiles-during-prefill schedule.crates/muser-server/src/state.rs:231-254— the 250 µs decode rendezvous.docs/decode-dispatch-gap-20260815.md— 760/564 closure reconciliation; the rejected fusion’s logprob error; the one exact removal.docs/muser-architecture.md— lane matrix, scheduler ownership, buffer residency framing.docs/memory-footprint.md— KV plane arithmetic; artifact sizes.[claims #15],[claims #6]—docs/launch-claims.md: speculative restatement scope; TTFT disaggregation scope.- [ferrite-book Ch 8] — the ancestor spine chapter (pedagogical lineage; its Figure 8.1 fusion list is Ferrite’s, not Muser’s).
Chapter 11 — Token embedding lookup
status: polished · path: Muse Glimmer, pinned Muser tree
Prerequisites: Ch 2 (the Metal compute model), Ch 3 (mmap’d GGUF, zero-copy weight views), Ch 5 and Ch 6 (Q4_K blocks), Ch 9 (the model’s shapes), Ch 10 (the decode loop and the residual stream). This is the first kernel chapter of Part IV: the operation is small, and the chapter is sized to match.
Chapter 10 ended where the descent begins: box ① of Figure 10.1, the humblest kernel in the whole graph — one integer in, one 6,656-float vector out. This chapter opens it.
We put the smallest kernel first on purpose. Every habit the rest of Part IV leans on — read the shapes before the code, count the bytes before believing a cost, quote the dispatch instead of your memory of it — can be practised here, on an operation that has no arithmetic to hide behind. If a chapter about a table lookup feels like more scaffolding than the lookup deserves, that is the point: the scaffolding is what we are teaching.
11.1 What it computes
The kernel answers one question: how does an integer become a vector? The tokenizer hands the engine a number — the id of a piece of text inside a fixed vocabulary — and everything downstream wants floats. Something has to bridge the two, and this is the something.
Given one token id — an integer in 0 .. vocab_size —
the embedding lookup produces the initial hidden vector for that token:
hidden = EmbeddingTable[token_id] // a [hidden_dim] f32 vector
That is the whole formula. There is no dot product, no reduction, no
nonlinearity: it is a row gather out of a learned table, followed by a
dequantization from Q4_K (the 144-byte super-blocks of
Ch 6) into f32. For Muse Glimmer,
hidden_dim = 6,656 and vocab_size = 202,048
(crates/muser-engine/tests/muse_golden.rs:96,101), so the output of this
step is 6,656 floats — the seed of the residual
stream that all 52 layers will then accumulate
into.
This chapter is about muser_embedding_q4k, the Metal kernel that does the
gather and the dequant in one dispatch, and about the one structural way
Muser’s embedding differs from the ancestor book’s: Muser embeds on the
GPU, inside the same command buffer as everything else
(crates/muser-engine/src/decode.rs:5523-5533).
11.2 Why it exists — the residual stream is born here
Recall the residual stream from Ch 10:
one [6,656] f32 buffer that every layer reads from and adds a delta to. The
embedding lookup is the only step in the entire forward pass that writes
the initial value of that buffer rather than adding into it. Everything
downstream — 52 layers of attention and FFN, the final norm, the LM head —
starts from the 6,656 floats this kernel produces.
What breaks if you skip it: nothing downstream has an input. The residual stream would hold whatever the buffer was zero-filled with, every token would embed to the same vector, and the model would emit a constant. The lookup is trivial arithmetic carrying non-trivial content — the “intelligence” is in the learned numbers of the table, not in the operation.
11.3 The row gather, explained from zero
Two questions decide everything about this kernel. Where in memory does a token’s row live, and how many bytes long is it? Answer both and the kernel more or less writes itself; get either one wrong and you will read a perfectly plausible row belonging to some other word entirely.
An embedding is a lookup table: a matrix of
learned numbers with one row per vocabulary entry. In the GGUF the tensor is
token_embd.weight with shape [hidden_dim, vocab_size]. The loader does
not take that shape on trust — it asserts it at load
(crates/muser-engine/src/config.rs:295) — and for this checkpoint it
accepts the table in two dtypes only, Q4_K or F16 (decode.rs:1209-1214).
Memory is vocab-major: token t’s 6,656 values are contiguous
(Figure 11.1), so the byte offset of a row is just t × row_bytes.
token_embd.weight (vocab-major rows)
┌────────────────────────────────────────────┐
t = 0 │ 6,656 quantized values … (3,744 B, Q4_K) │
t = 1 │ 6,656 quantized values … │
⋮ │ ⋮ │
t = 202,047│ 6,656 quantized values … │
└────────────────────────────────────────────┘
To embed token id t: read row t → dequant → hidden[0..6,656] (f32)
Figure 11.1: The embedding table. One row per token id; one row is read per token. Drawn in memory order this time (each token’s values are contiguous), unlike the transposed pictures some papers prefer.
How big is one Q4_K row? Show the arithmetic, because the same multiply returns in every kernel chapter:
Q4_K super-block = 256 elements in 144 bytes (Ch 6)
row_bytes = (6,656 / 256) × 144 = 26 × 144 = 3,744 B
table total = 202,048 × 3,744 = 756,467,712 B ≈ 756 MB
That multiply is not ours to invent, incidentally — the dispatch computes
row_bytes with the same expression
(crates/muser-engine/src/metal/encode/qkv.rs:361).
The whole table is ~756 MB of the 16,756,681,056-byte pinned artifact
(crates/muser-engine/src/lib.rs:14) — about 4.5 % of the model — but only
one 3,744-byte row of it is read per token. Large in memory, trivial in
bandwidth; both facts are true simultaneously. Put the other way, because the
whole book rests on the distinction: a tensor’s size tells you what it costs
to hold, and tells you nothing whatever about what it costs to use. The
embedding table is the cleanest illustration in the model — three quarters of
a gigabyte resident, under four kilobytes touched per token — and
Ch 1 hangs its whole budget on
exactly that gap.
11.4 The Metal kernel
Now the code. Read it holding one question: which line is the actual gather? There is exactly one, a pointer expression, and it is outnumbered about a dozen to one by nibble bookkeeping. That ratio is not a defect of this kernel. It is what quantization costs at every kernel in this book, visible here only because there is nothing else going on to hide it.
Here is the kernel, complete, with the dequant helper it calls:
// crates/muser-engine/src/shaders/muse_reference.metal:946
inline float muser_q4k_value(device const uchar *row, uint element) {
uint block_index = element / 256;
uint within_block = element % 256;
uint group = within_block / 64;
uint within_group = within_block % 64;
uint scale_index = group * 2 + (within_group >= 32 ? 1 : 0);
uint lane = within_group % 32;
device const uchar *block = row + block_index * 144;
uchar2 scale_min = muser_scale_min(block + 4, scale_index);
uchar packed = block[16 + group * 32 + lane];
uint quant = within_group < 32 ? uint(packed & 0x0f) : uint(packed >> 4);
return muser_f16(block) * float(scale_min.x) * float(quant)
- muser_f16(block + 2) * float(scale_min.y);
}
// crates/muser-engine/src/shaders/muse_reference.metal:961
kernel void muser_embedding_q4k(
device const uchar *weights [[buffer(0)]],
device const uint *token_ids [[buffer(1)]],
device float *output [[buffer(2)]],
constant uint &hidden_dim [[buffer(3)]],
constant uint &vocab_size [[buffer(4)]],
constant uint &tokens [[buffer(5)]],
uint index [[thread_position_in_grid]]) {
uint total = hidden_dim * tokens;
if (index < total) {
uint token_slot = index / hidden_dim;
uint element = index % hidden_dim;
uint token_id = min(token_ids[token_slot], vocab_size - 1);
uint row_bytes = (hidden_dim / 256) * 144;
output[index] = muser_q4k_value(weights + token_id * row_bytes, element);
}
}
Line by line:
- One thread per output element. Thread
indexproducesoutput[index]. For a single decode token that is 6,656 threads; for a 512-row prefill chunk it is 512 × 6,656 ≈ 3.4 M threads, all from the same kernel.token_slot = index / hidden_dimpicks which token of the batch this thread serves. - The gather itself is the pointer line
weights + token_id * row_bytes: one integer multiply, one add. Themin(token_ids[token_slot], vocab_size - 1)clamp is a bounds guard — a hostile or corrupt id reads the last row instead of running off the table. muser_q4k_valueis the Q4_K dequant from Ch 6, spelled out scalar-style. Each element lives in one of the row’s 26 super-blocks (element / 256); each super-block has 8 sub-blocks of 32 (group = within_block / 64, two sub-blocks per 64 elements, one per nibble-half of each byte);muser_scale_minunpacks the 6-bit scale/min pair for the sub-block (muse_reference.metal:32-39); the nibble is the low or high 4 bits of one byte depending on which half of the sub-block the element falls in. The value isd × sc × nibble − dmin × m, whered/dminare the two f16 header values of the super-block (muser_f16,muse_reference.metal:27-30).- Nothing is cached across threads; each thread independently decodes the scale bytes it needs. That is ~26 super-block-header reads per thread in the worst case, all served from L2 (the GPU’s shared on-chip cache) after the first lane touches them — fine for a kernel that runs once per token.
Said the other way round, because this is the part that trips people up: an element index is really a four-level address — which super-block, which sub-block inside it, which byte inside that, and which half of that byte. The kernel stores none of those levels. No lookup table, no precomputed offsets: it re-derives all four from a single integer using divisions and masks, independently, in every thread. Quantized formats trade memory for address arithmetic, and this helper is that trade with the lid off.
A worked example: one element of one row
Take token id t = 42, element e = 300, and a made-up super-block whose
header is d = 0.5, dmin = 0.01 and whose sub-block scales decode to
sc = 40, m = 12, with the packed byte holding nibble 9 in the half
that covers element 300. Then:
block_index = 300 / 256 = 1 (the second super-block)
within = 300 % 256 = 44
group = 44 / 64 = 0 (first 64-element group)
within_grp = 44 % 64 = 44 ≥ 32 → high nibble, scale_index 1
value = d·sc·nibble − dmin·m
= 0.5 × 40 × 9 − 0.01 × 12 = 180 − 0.12 = 179.88
Multiply that by 6,656 threads and the row is dequantized into
hidden[0..6,656]. The numbers here are illustrative — the real bytes live
in the pinned GGUF — but every index computation above is the kernel’s own
arithmetic, quoted from muse_reference.metal:946-959.
And the thread-to-element picture for one token (Figure 11.2):
thread index: 0 1 2 … 255 | 256 257 … 6,655
token_slot: 0 0 0 … 0 | (token 1's threads, in batch mode)
element: 0 1 2 … 255 | 0 1 … 6,655
reads: row 42, bytes 0..3,744 — every thread re-derives its own
block/group/lane indices into the same contiguous row
Figure 11.2: the per-thread decomposition for tokens = 1. Grid is the
output shape (dispatch_1d(hidden_dim × tokens)); the gather is one
pointer computation per thread.
11.5 The Rust dispatch
The kernel says what to compute. It does not say how many threads run it, where the weight bytes come from, or what should happen if the buffer you bound turns out to be the wrong size. That is the wrapper’s job. It is the layer where a mistake is expensive and silent, which is why it is also the layer carrying the assertions.
The wrapper is encode_embedding_q4k:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/metal/encode/qkv.rs:338
pub fn encode_embedding_q4k(
&self,
encoder: &ComputeCommandEncoderRef,
weights: GpuByteView<'_>,
token_ids: &GpuByteView<'_>,
output: &GpuBuffer,
hidden_dim: usize,
vocab_size: usize,
tokens: usize,
) {
// …(F16-table branch elided: a 2-byte-per-element table dispatches
// `muser_embedding_f16` instead — see file)…
let row_bytes = hidden_dim / 256 * 144;
debug_assert_eq!(weights.len(), row_bytes * vocab_size);
debug_assert_eq!(token_ids.len(), tokens * std::mem::size_of::<u32>());
debug_assert_eq!(output.len(), hidden_dim * tokens);
self.bind(encoder, "muser_embedding_q4k");
encoder.set_buffer(0, Some(weights.metal()), weights.offset() as u64);
encoder.set_buffer(1, Some(token_ids.metal()), token_ids.offset() as u64);
encoder.set_buffer(2, Some(output.metal()), 0);
set_value(encoder, 3, &(hidden_dim as u32));
set_value(encoder, 4, &(vocab_size as u32));
set_value(encoder, 5, &(tokens as u32));
dispatch_1d(encoder, hidden_dim * tokens);
}
}
And the call site — the first dispatch of every token graph:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/decode.rs:5523
dispatch(command, |encoder| {
self.kernels.encode_embedding_q4k(
encoder,
self.embedding.view(&self.mapped_weights),
token_view,
&self.activations.hidden,
cfg.hidden_dim,
cfg.vocab_size,
1,
);
});
}
Three details matter, and each one is a cost that isn’t there. First,
self.embedding.view(&self.mapped_weights): the table is a view into the
single mmap’d GGUF buffer already mapped onto the GPU (decode.rs:1201), so
the “upload” of 756 MB of embedding is a no-op — the zero-copy promise of
Ch 3, collecting for the first time.
Second, the token id arrives as token_view — four bytes in a small staging
buffer the CPU wrote before the command buffer existed
(decode.rs:5433-5438), so the GPU never waits on the host. Third, the grid:
dispatch_1d(hidden_dim × tokens)
(encode.rs:1337-1343) uses dispatch_threads with a threadgroup width of
at most 256 — for one token, 6,656 threads in 26+ threadgroups of 256. The
GPU, not the CPU, sizes the launch.
The batch-graph sibling encode_embedding_q4k_from_u32_buffer
(qkv.rs:376) is the same kernel with the id buffer already GPU-resident —
that is the variant the 512-row prefill chunks use, and the reason one kernel
serves both regimes.
11.6 The access pattern
Every kernel chapter reaches this point and asks the same question: where does the time go? For the embedding the honest answer is “nowhere,” and it is worth seeing precisely how nowhere, because the shape of the accounting is the one we will reuse on kernels where the answer is not so comfortable.
Per token:
read : 1 row × 3,744 B (Q4_K) + 4 B (token id) ≈ 3.75 KB
write: 6,656 × 4 B = 26,624 B ≈ 26 KB (f32 hidden)
The read is a single contiguous 3,744-byte span; the write is a single
contiguous 26 KB span. Against the ~16.76 GB of weight bytes the token graph
reads in total (lib.rs:14), the embedding row is
3,744 / 16,756,681,056 ≈ 2 × 10⁻⁷ of the traffic. The activation write is
larger than the weight read here — the only chapter in Part IV where that is
true — and both are rounding error. Note also what is not read: the other
202,047 rows of the table stay untouched until some other token id asks for
them.
The F16 sibling and the lane matrix
The dispatch you just read has an elided branch: if the table’s byte length
says two bytes per element, the wrapper binds muser_embedding_f16 instead
(qkv.rs:348-359). Same one-thread-per-element shape, same gather, no
dequant — a 2-byte half is widened to f32 directly. Which branch runs is a
property of which lane you loaded, not of any flag: the kquant
(q4_k_xl) artifact carries the table quantized, and a lane whose tables
ship F16 takes the sibling. The lane matrix of
Ch 7 decides; the kernel follows the bytes. That
is the book’s first example of a pattern that recurs through Part IV — the
dispatch adapts to the artifact, while the graph structure (one gather,
one norm, four matvecs, …) stays fixed.
11.7 Tradeoffs
GPU gather vs CPU lookup — Muser inverts the ancestor’s choice. We
inherited a decision here, and then we reversed it. The Ferrite book did the
embedding on the CPU — one quantized row dequantized into a unified-memory
buffer, no dispatch at all — and argued the work was too small to amortize a
GPU dispatch over [ferrite-book Ch 9]. That argument is sound on its own
terms, and going in we expected to keep the CPU path: it is less code, and
none of the arithmetic had changed.
What had changed was everything around the arithmetic. Muser runs the lookup on the GPU anyway. Two structural facts, visible in the code rather than in any A/B ledger, turn the cheap-looking CPU path into the expensive one:
- The token id is already a GPU buffer. Muser’s decode graph is one
command buffer from embedding to softcap (
decode.rs:5448-5460), fed by teacher-forced or batched token arrays that arrive as staging buffers. A CPU lookup would need to read the id back, dequantize 3,744 B, and write 26 KB through unified memory — crossing the host/device boundary to save one dispatch that was going to be recorded anyway. - One kernel serves every batch width. The same
muser_embedding_q4kcovers 1-token decode, 64-row teacher-forced blocks, and 512-row prefill chunks (qkv.rs:376’stokensparameter); a CPU path would need a per-width host loop.
There is no measured A/B of CPU-vs-GPU embedding in the campaign ledger
[unverified] — the choice is justified structurally, and at ~30 KB of
traffic either side would be invisible against the per-token budget.
The lesson we took from the reversal is worth carrying into the rest of the kernel chapters: a dispatch’s cost is not a property of the dispatch. It is a property of what is already queued beside it. An argument that held for an engine recording a handful of command buffers stops holding for one that records the entire token graph as a single buffer.
Q4_K table vs an f32 table. Pre-dequantizing the table to f32 at load
would cost 202,048 × 6,656 × 4 ≈ 5.4 GB of resident memory — about a third
of the whole artifact again — to save 3,744 bytes of dequant work per token.
The loader instead accepts the table in exactly the dtypes the pinned
artifact carries (Q4_K or F16, decode.rs:1209-1214) and dequantizes one row
on demand. The same trade the ancestor book described, at 7× the scale
[ferrite-book Ch 9].
The clamp instead of a fail. A Metal kernel has no way to raise an error.
A thread handed a bad index simply reads whatever those bytes happen to be.
So what should a shader do with a token id that is out of range? Muser’s
answer is the min on the pointer line: an out-of-range id silently maps to
the last row rather than erroring (muse_reference.metal:973). It is not the
correctness gate — the CPU-side session validates token ids before the graph
is ever built (Session::decode,
crates/muser-engine/src/api.rs:696-741), so by the time a thread reaches the
clamp a bad id should already be impossible. That makes the clamp a second
line of defense. It also makes it a silent one, and a silent defense is a
defense you never learn has fired. Which of those two descriptions the clamp
was actually written for is [unverified] from the tree alone.
The untied twin at the other end of the model
token_embd.weight is not the only [6,656 × 202,048] tensor in the
checkpoint. output.weight — the LM head — has the same shape and is a
separate tensor: Muse Glimmer’s embeddings are untied, and the loader
demands both (config.rs:295-297). The two tensors live opposite lives.
The embedding table is read one row per token on the GPU, ~3.75 KB at a
time (this chapter). The LM head is read as a full matvec over all
202,048 rows, every token, in the tail of the graph
(decode.rs:5892-5897) — at 2 bytes/element on the native lane that is
~2.7 GB of traffic and, measured, ~3.46 ms/token versus ~1.75 ms for the
kquant lane’s quantized head [docs/nvfp4-fast-lane-evidence §Measured product numbers]. Same shape, three orders of magnitude different
bandwidth. The LM head gets its own chapter,
Ch 20; this chapter’s table is the
quiet one.
11.8 Where the gap lives
Every kernel chapter closes by facing the same suspicion: is this the one eating the decode time? Here the answer is a flat no, and the accounting is specific enough to say exactly why.
This kernel is not the gap. The dispatch-gap accounting of
[docs/decode-dispatch-gap-20260815.md] reconciles the production-vs-legacy
closure delta (760 vs 564, +196 at position 2,048) into exactly four
families: 104 norm-boundary groups, 39 SWA staging groups, 52 KV-publication
splits, and 1 last-row copy — the embedding appears in the common math
row (406 closures, delta 0, “required math / Keep”). Its bytes are
nanoscopic and its one-dispatch cost is shared by both graphs. If you are
hunting decode time, this is the last place to look.
11.9 What comes next
The residual stream now holds 6,656 f32s and the real work can begin. The first consumer is the entry RMSNorm — and on Muse Glimmer the norm story is unusual: a dual-epsilon sandwich with a llama.cpp hard-coded 1e-8 that the GGUF does not even carry. That is Ch 12.
References
crates/muser-engine/src/shaders/muse_reference.metal:946-959—muser_q4k_value, the scalar Q4_K dequant used by the embedding (and the batch matvec fallbacks).crates/muser-engine/src/shaders/muse_reference.metal:961-977—muser_embedding_q4k, the kernel this chapter dissects.crates/muser-engine/src/shaders/muse_reference.metal:27-39—muser_f16andmuser_scale_min(the f16 header read and the 6-bit scale/min unpack).crates/muser-engine/src/metal/encode/qkv.rs:338-373—encode_embedding_q4k, the dispatch wrapper;:361therow_bytesformula;:376-411the batch sibling.crates/muser-engine/src/metal/encode.rs:1337-1343—dispatch_1d(thedispatch_threadsgrid used here).crates/muser-engine/src/decode.rs:5523-5533— the call site, first dispatch ofencode_token.crates/muser-engine/src/decode.rs:1207-1216— embedding loaded as aProjectionfromtoken_embd.weight; dtype gate (Q4_K or F16).crates/muser-engine/src/decode.rs:5433-5438— the four-byte token staging view the kernel reads.crates/muser-engine/src/config.rs:294-297—token_embd.weightshape[hidden_dim, vocab_size]asserted at load.crates/muser-engine/tests/muse_golden.rs:96,101—hidden_dim = 6,656,vocab_size = 202,048asserted against the pinned GGUF.crates/muser-engine/src/lib.rs:14— the 16,756,681,056-byte artifact.crates/muser-engine/src/api.rs:696-741—Session::decode, the host-side id validation that runs before any of this.[docs/decode-dispatch-gap-20260815.md]— the closure reconciliation this chapter’s gap section cites (embedding in the 406-closure common-math row).- Ch 3 — mmap + zero-copy views.
- Ch 6 — the Q4_K super-block this kernel dequantizes.
- Ch 10 — where this dispatch sits in the one-command-buffer token graph.
- Ch 12 — the next kernel.
- Ch 20 — the untied LM head, the other boundary tensor.
[docs/nvfp4-fast-lane-evidence §Measured product numbers]— the ~3.46 ms/token F16 LM head vs ~1.75 ms kquant comparison (§11.7).[ferrite-book Ch 9]— the ancestor’s CPU-lookup embedding and the too-small-to-amortize argument (pedagogical lineage only).
Chapter 12 — RMSNorm and the dual-epsilon sandwich
status: polished · path: Muse Glimmer, pinned Muser tree
Prerequisites: Ch 2 (SIMD groups,
simd_sum,threadgroup_barrier), Ch 9 (the sandwich-norm architecture), Ch 10 (the per-layer kernel chain), Ch 11 (the residual stream’s birth). This chapter defines normalization from zero; no transformer-paper background is assumed.
Chapter 11 ended with the residual stream freshly born — 6,656 f32s from one quantized table row — and named its first consumer: the entry RMSNorm. This is the norm story, and on Muse Glimmer it is an unusual one: two different epsilons, a llama.cpp constant the checkpoint does not carry, and a fused kernel that exists because fusing carelessly would change the model’s public numbers.
Normalization is the least glamorous operation in a transformer. It invents no information; it only puts the numbers back into a range the next matmul can work with. So it is a good joke on us that this is the chapter where we lost the most work: two of the optimizations we were most confident about survive here as war stories rather than as shipped code.
12.1 What it computes
RMSNorm takes a vector x of length n and produces
a vector of the same length whose magnitude is predictable — close to unit
scale — regardless of how large or small x was:
mean(x²) = (1/n) Σⱼ xⱼ² (mean of squares)
rms = √(mean(x²) + ε) (root-mean-square, ε-guarded)
y_i = x_i / rms × γ_i (normalize, then per-channel gain)
Three of the symbols in those lines are settled by the checkpoint, and one
is not. On Muse Glimmer n = hidden_dim = 6,656 for the
stream norms, and γ is a learned per-channel weight — attn_norm.weight,
ffn_norm.weight, and their post-norm siblings
(crates/muser-engine/tests/muse_golden.rs:96, config.rs:300-314).
Then there is ε, and ε is the reason this chapter exists: it is not one
value but two. 1e-5 is the value the checkpoint carries, and it governs
every norm the GGUF declares. 1e-8 is hard-coded for the two “post” norms
of the sandwich — a
llama.cpp graph constant the checkpoint does not carry at all
(config.rs:23-28).
Hold on to that, because it decides the shape of the code further down. A
fused kernel that assumes a single epsilon per layer cannot serve this
model, so one kernel in this chapter,
muser_fused_norm_residual_rms_norm_32sg, exists for no other reason
(shaders/ferrite/rmsnorm_batch_tail.metal:142-146).
12.2 Why it exists — 52 layers of multiplication drift
Why spend four normalizations per layer on an operation that adds no information to the stream? Ask it the other way: what breaks if we leave them out?
A transformer layer is a chain of multiplies. The matvec
of Ch 13, an activation, another matvec —
and at every step the numbers on the residual
stream get multiplied together.
Multiplications compound. If each of Muse Glimmer’s 52 layers scaled the
stream by 1.2, after the stack the magnitudes have grown by
1.2^52 ≈ 39,000. If each scaled by 0.8, the signal has shrunk to
0.8^52 ≈ 1.4 × 10⁻⁵. Either way the network drifts out of a usable range,
and the deeper layers see garbage. Normalization is the valve: it pins the
magnitude back to a known band before each sub-block, so the layer stack
stays numerically stable. Skip it and the model still runs but its
late-layer arithmetic saturates or vanishes — on this model, exact-parity
decode would be the first casualty, because every ULP that drifts here is
amplified by everything downstream.
Muse Glimmer is a Gemma-2-style sandwich: each layer normalizes four
times, not two (config.rs:300-314 names all four tensors):
attn_norm (1e-5) → attention → post_attention_norm (1e-8) → ffn_norm (1e-5)
→ FFN → post_ffw_norm (1e-8)
→ next layer's attn_norm (1e-5)
The two “post” norms normalize each sub-block’s output before it is added to the residual; the “pre” norms normalize the stream before each sub-block reads it. That is the sandwich — pre-norm, block, post-norm, residual — and the two epsilons live on opposite sides of it.
Why RMS and not standard deviation?
LayerNorm centers and scales: it subtracts the mean and divides by the standard deviation:
μ = (1/n) Σ xⱼ ; σ² = (1/n) Σ (xⱼ − μ)² ; y_i = (x_i − μ)/√(σ²+ε) × γ_i
RMSNorm drops the mean subtraction and divides by the root-mean-square only. Two reasons, one costed and one empirical:
- Cost. LayerNorm needs two global reductions over
x— one forμ, one forσ²(which itself needsμ). RMSNorm needs one: the sum of squares. On a GPU a reduction is the expensive part of normalization — it is the one place every thread must wait for every other thread — so one reduction instead of two is a real saving, repeated 100+ times per token on this model. - Empirical equivalence. The RMSNorm paper reports that dropping the
mean changes downstream quality negligibly across the models tested
[arxiv:1910.07467]. [unverified] for Muse Glimmer specifically — that is the paper’s claim, not a measurement in this campaign; the architecture chose RMSNorm upstream and Muser implements what the GGUF declares.
A useful identity links the two: mean(x²) = σ² + μ², so
rms = √(σ² + μ²) ≥ σ, with equality exactly when μ = 0. When x is
already mean-centered, dividing by rms and dividing by σ are the same
operation — the sense in which the two norms’ scaling steps coincide.
The two epsilons, precisely
Here is the fact the whole chapter turns on. Where does each epsilon come from, and what happens to an engine that assumes there is only one?
rms_eps = 1e-5is read from the GGUF keymuse-glimmer.attention.layer_norm_rms_epsilon; absence is a hard load error (config.rs:184-188). It feeds every norm this chapter dispatches except the two post norms.post_norm_eps = 1e-8is not in the GGUF. llama.cpp’s graph builder hard-codes it (src/models/muse-glimmer.cpp:67,const float post_norm_eps = 1e-8f;), and Muser mirrors the constant with a doc comment saying exactly that (config.rs:23-28). This is a rare thing: a numerical constant that is part of the comparator’s identity rather than the checkpoint’s. An engine that read1e-5everywhere — the “obvious” simplification — would disagree with pinned llama.cpp in the last bits of every post-norm, and the parity gates of Ch 38 exist to catch exactly that class of drift.
The ε inside the root has one job: if x is all zeros, mean(x²) = 0 and
√0 = 0, which would divide by zero. + ε keeps the denominator strictly
positive.
Why two different tiny values matter numerically is harder to
say — the difference between √(m + 1e-5) and √(m + 1e-8) is invisible
for any m of real magnitude — but the exactness contract does not grade
on “of real magnitude”: it grades on bits, and llama’s graph says 1e-8,
so 1e-8 it is. Put that another way, because it is the rule behind every
decision in this chapter: we are not implementing normalization here, we
are implementing llama’s normalization. Where the two differ, the
comparator wins — even when it has no numerical argument on its side. The
deeper motivation for the sandwich itself is a
model-design question this engine inherits [unverified].
12.3 RMSNorm by hand: x = [3, 4, 0, 0], ε = 1e-5
Take n = 4, x = [3, 4, 0, 0], ε = 1e-5, and first γ = [1,1,1,1]:
Step 1 — sum of squares
x² = [9, 16, 0, 0] ; Σx² = 25 ; mean(x²) = 25/4 = 6.25
Step 2 — root-mean-square and its reciprocal
rms = √(6.25 + 0.00001) ≈ 2.500002
inv_rms = 1/rms ≈ 0.3999997 (call it 0.4)
Step 3 — normalize, then apply γ
y_i = x_i × inv_rms × γ_i
y = [3×0.4×1, 4×0.4×1, 0, 0] = [1.2, 1.6, 0, 0]
Figure 12.1: RMSNorm worked example. The input whose RMS was 2.5 comes out with RMS 1 (before γ).
Check: mean(y²) = (1.44 + 2.56)/4 = 1, so rms(y) = 1. Now change γ to
[2, 0.5, 1, 1]: y = [2.4, 0.8, 0, 0] — the RMS is no longer 1, on
purpose. Normalization fixes the scale; γ re-learns the shape. On Muse
Glimmer, one special γ is programmer-chosen rather than learned: the entry
norm binds an all-ones vector (decode.rs:1216), because the graph needs
the RMSNorm operation but the model has no entry-norm tensor.
Everything the kernels do below is a parallel implementation of those three
steps, at n = 6,656.
12.4 The Metal kernels
That was arithmetic you could do on paper. What follows is the same three steps run by a thousand threads at once — and what separates the kernels below is not the math, which is identical in all of them, but which other implementation each one has to agree with, bit for bit.
Four kernels from two lineages serve the stream norms. Read them in order of increasing specialization.
12.4.1 rms_norm_batch — the ferrite-lineage base kernel
Start with the plainest of the four: one row in, one row out, one epsilon,
and nobody to agree with. This is the unfused workhorse, byte-for-byte from
the Ferrite shader pull at a85048a90 (docs/extraction-manifest.md):
// crates/muser-engine/src/shaders/ferrite/rmsnorm_batch_tail.metal:1
kernel void rms_norm_batch(
device const float* x [[ buffer(0) ]], // [B × n]
device const float* weight [[ buffer(1) ]], // [n] (shared)
device float* out [[ buffer(2) ]], // [B × n]
constant uint& n [[ buffer(3) ]],
constant float& eps [[ buffer(4) ]],
uint tgid [[ threadgroup_position_in_grid ]],
uint tid [[ thread_index_in_threadgroup ]],
uint sgitg [[ simdgroup_index_in_threadgroup ]],
uint lid [[ thread_index_in_simdgroup ]],
threadgroup float* shared [[ threadgroup(0) ]])
{
const uint batch = tgid;
device const float* xb = x + batch * n;
device float* ob = out + batch * n;
device const float4* xb4 = (device const float4*)xb;
device const float4* wb4 = (device const float4*)weight;
device float4* ob4 = (device float4*)ob;
const uint n4 = n >> 2u;
float sum_sq = 0.0f;
for (uint i = tid; i < n4; i += 128u)
sum_sq += dot(xb4[i], xb4[i]);
sum_sq = simd_sum(sum_sq);
if (lid == 0u) shared[sgitg] = sum_sq;
threadgroup_barrier(mem_flags::mem_threadgroup);
if (tid == 0u)
shared[4] = rsqrt((shared[0] + shared[1] + shared[2] + shared[3]) / float(n) + eps);
threadgroup_barrier(mem_flags::mem_threadgroup);
const float inv_rms = shared[4];
for (uint i = tid; i < n4; i += 128u)
ob4[i] = xb4[i] * inv_rms * wb4[i];
}
This is the reduction pattern of Ch 2 in miniature, and every kernel in this chapter repeats it:
- Vectorized load — the
float4cast reads four floats per transaction;n4 = n/4iterations instead ofn. - Strided partial sum — thread
tidwalksi = tid, tid+128, …, accumulatingdot(xb4[i], xb4[i])(a 4-way sum of squares per load). simd_sum— one hardware instruction collapses 32 lanes to their sum, no barrier, in-lockstep.- Threadgroup handoff — lane 0 of each of the 4 SIMD groups publishes
its group’s partial into
shared[sgitg]; one barrier makes all four visible. - Single-thread combine — thread 0 sums the four partials and computes the reciprocal root; a second barrier broadcasts it.
- Normalize-and-scale — every thread re-walks its elements and writes
x × inv_rms × γ.
Two barriers per row is the price of crossing SIMD-group boundaries;
simd_sum inside a group is free. Note rsqrt(...) — that single function
name is load-bearing, and §12.7 comes back to it.
12.4.2 The pinned ggml norm — kernel_rms_norm_mul_f32_4
So which of the four actually fires when you serve a token? Not, as a rule, the one you have just read.
On the serving path the standalone norm almost never runs, because
encode_rms_norm_mul prefers the pinned llama.cpp metallib kernel
whenever the library is loaded and n is a multiple of 4:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/metal/encode/norm.rs:244
pub fn encode_rms_norm_mul(
&self, encoder: &ComputeCommandEncoderRef,
input: &GpuBuffer, weight: &GpuBuffer, output: &GpuBuffer,
dim: usize, eps: f32, rows: usize,
) {
// …(cross-vendor strict branch elided — §12.7)…
if dim.is_multiple_of(4) {
if let Some(pipeline) = self.ggml_rms_norm_mul() {
self.encode_ggml_rms_norm(
encoder, pipeline, input, weight, weight, output, dim, eps, rows, false,
);
return;
}
}
self.bind(encoder, "rms_norm_batch");
// …(buffers 0–2, dim, eps; elided)…
encoder.set_threadgroup_memory_length(0, 32);
encoder.dispatch_thread_groups(MTLSize::new(rows as u64, 1, 1), MTLSize::new(128, 1, 1));
}
}
The ggml pipeline is kernel_rms_norm_mul_f32_4 from the metallib pinned by
MUSER_GGML_METALLIB (metal/encode.rs:287, loaded per
Ch 4), and encode_ggml_rms_norm
(norm.rs:53-94) packs llama’s own GgmlMetalKargsNorm argument struct and
picks llama’s own thread count — (dim/4).next_power_of_two().clamp(32, 1024), which is 1,024 threads at dim = 6,656 (norm.rs:89). The
ferrite-lineage rms_norm_batch at 128 threads is the fallback when the
metallib is absent. Same math, two reduction shapes — and since
floating-point addition is not associative, the two shapes can differ in the
last bits.
Read that last clause slowly; the rest of the chapter is built on it. The same values, added in a different order, are not the same float. A reduction shape is therefore not an implementation detail we are free to choose — it is part of the answer. On the serving route the pinned one is the point.
12.4.3 The dual-eps fused tail — muser_fused_norm_residual_rms_norm_32sg
The heart of the chapter. After each sub-block, the graph must do three things to the stream: add the block’s post-norm-normalized output into the residual, then produce the next sub-block’s pre-normed input. The fused kernel does all three in one dispatch, with two different epsilons:
// crates/muser-engine/src/shaders/ferrite/rmsnorm_batch_tail.metal:142
// Decode-only dual-epsilon tail fusion:
// hidden += rms_norm(src, eps1) * weight1
// output = rms_norm(hidden, eps2) * weight2
// Muse uses eps1=1e-8 for sandwich post-norms and eps2=1e-5 for the
// following pre-norm, so the older single-epsilon batch kernel is not valid.
kernel void muser_fused_norm_residual_rms_norm_32sg(
device float* hidden [[buffer(0)]],
device const float* src [[buffer(1)]],
device float* output [[buffer(2)]],
device const float* weight1 [[buffer(3)]],
device const float* weight2 [[buffer(4)]],
constant uint& n [[buffer(5)]],
constant float& eps1 [[buffer(6)]],
constant float& eps2 [[buffer(7)]],
uint row [[threadgroup_position_in_grid]],
uint tid [[thread_index_in_threadgroup]],
uint sgitg [[simdgroup_index_in_threadgroup]],
uint lid [[thread_index_in_simdgroup]],
threadgroup float* shared [[threadgroup(0)]]) {
const uint n4 = n >> 2u;
device float4* hidden4 = (device float4*)(hidden + row * n);
device const float4* src4 = (device const float4*)(src + row * n);
device float4* output4 = (device float4*)(output + row * n);
device const float4* weight14 = (device const float4*)weight1;
device const float4* weight24 = (device const float4*)weight2;
float sum_src = 0.0f;
for (uint i = tid; i < n4; i += 1024u)
sum_src += dot(src4[i], src4[i]);
sum_src = simd_sum(sum_src);
if (lid == 0u) shared[sgitg] = sum_src;
threadgroup_barrier(mem_flags::mem_threadgroup);
if (tid == 0u) {
float total = 0.0f;
for (uint group = 0u; group < 32u; ++group) total += shared[group];
shared[32] = rsqrt(total / float(n) + eps1);
}
threadgroup_barrier(mem_flags::mem_threadgroup);
const float inv_src = shared[32];
float sum_hidden = 0.0f;
for (uint i = tid; i < n4; i += 1024u) {
const float4 value = hidden4[i] + src4[i] * inv_src * weight14[i];
hidden4[i] = value;
sum_hidden += dot(value, value);
}
sum_hidden = simd_sum(sum_hidden);
if (lid == 0u) shared[sgitg] = sum_hidden;
threadgroup_barrier(mem_flags::mem_threadgroup);
if (tid == 0u) {
float total = 0.0f;
for (uint group = 0u; group < 32u; ++group) total += shared[group];
shared[32] = rsqrt(total / float(n) + eps2);
}
threadgroup_barrier(mem_flags::mem_threadgroup);
const float inv_hidden = shared[32];
for (uint i = tid; i < n4; i += 1024u)
output4[i] = hidden4[i] * inv_hidden * weight24[i];
}
Read it as §12.3 twice, with a residual add between:
- Pass 1 reduces
src(the sub-block’s output, e.g. the o_proj result) and computesinv_srcwith eps1 = 1e-8 — the post-norm. - Pass 2 does the residual update
hidden += src × inv_src × weight1and, in the same loop, accumulates the sum of squares of the updated hidden — the read ofhiddenand the write and the reduction share one pass over the data. - Pass 3 computes
inv_hiddenwith eps2 = 1e-5 — the next pre-norm — and writes the next sub-block’s input.
The reduction here spans 32 SIMD groups (1,024 threads), not 4: lane 0
of each group writes shared[sgitg], and thread 0 sums 32 slots serially.
Why 32 groups for 6,656 elements? The dispatch wrapper’s comment says it
outright:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/metal/encode/norm.rs:236
// 32 SIMD groups keep the 6,656-wide Muse tail resident and match the
// accepted Ferrite geometry. 33 floats are padded to Metal's 16-byte
// dynamic-threadgroup-memory alignment.
encoder.set_threadgroup_memory_length(0, 144);
encoder.dispatch_thread_groups(MTLSize::new(rows as u64, 1, 1), MTLSize::new(1024, 1, 1));
}
n4 = 6,656/4 = 1,664 float4s; 1,024 threads means each thread owns one or
two float4s — the whole vector stays resident in registers with no second
strided loop iteration for most threads. The threadgroup allocation is 33
floats (32 partials + 1 broadcast slot) = 132 bytes, padded to 144 for
Metal’s 16-byte alignment. This kernel is a Muser addition to the Ferrite
file — the muser_ prefix marks it — because the ancestor had no
dual-epsilon model to need it.
12.4.4 The exact batch-graph tail — …_batch_dual_eps
If the kernel above already does the triple work with two epsilons, why is there a fourth? Because that kernel agrees with nobody. It is correct; it is not identical — and the distinction between these last two variants is the most important routing fact in this chapter.
The serving batch graph uses
muser_fused_norm_residual_rms_norm_batch_dual_eps
(rmsnorm_batch_tail.metal:250). It does the same triple work, and then
goes out of its way to reproduce the two pinned ggml reductions bit for
bit: a 32-SIMD-group reduction, 1.0f / sqrt(...) instead of rsqrt,
llama’s exact multiply/add expression shape, and even a forced
threadgroup_barrier(mem_flags::mem_device) between the two norms, so that
the f32 device-memory publication boundary of the split route survives the
fusion (rmsnorm_batch_tail.metal:277-303). None of that buys speed. Its
comment records what it does buy: “changing any of these moved public
logprobs beyond the contract in the rejected four-group fusion” — the
failure the tradeoffs section below tells in full.
The call sites confirm the split: decode.rs:4513/4656 (batch graph) take
the exact variant; decode.rs:5807/5878 (the legacy one-token
encode_token graph this book narrates) take the 32sg variant.
12.5 The Rust dispatch — where the norms run in one token
Four kernels exist; one token has to choose among them dozens of times. So where do the norms actually fire, and how many of them stand alone? Fewer than the sandwich diagram earlier would lead you to guess — and the reason is the whole point of fusing tails.
From encode_token (decode.rs:5515-5906), the norm dispatches of one
token are:
| # | call | wrapper (file:line) | kernel | ε |
|---|---|---|---|---|
| 1 | entry norm | encode_rms_norm_mul decode.rs:5534-5544 | ggml pinned (or rms_norm_batch) | 1e-5 |
| 2 | layer-0 attn norm | encode_rms_norm_mul decode.rs:5553-5565 | same | 1e-5 |
| 3 | post-attn + FFN pre-norm, ×52 | encode_fused_norm_residual_rms_norm_32sg decode.rs:5806-5818 | …_32sg | 1e-8 then 1e-5 |
| 4 | post-FFN + next attn norm, ×52 | same wrapper decode.rs:5877-5889 | …_32sg | 1e-8 then 1e-5 |
Layers 1..51 receive their attention pre-norm fused into the previous
layer’s tail — the (next_norm, next_output) selection at
decode.rs:5869-5876 binds the next layer’s attn_norm as the tail’s
second weight, and for layer 51 it binds output_norm and writes the final
norm straight to the buffer the LM head reads. Layer 0’s attn norm is the
only standalone pre-norm in the graph, plus the entry norm before it (whose
γ is the all-ones vector of §12.2). Counting dispatches per token: 2
standalone + 2×52 fused = 106 norm-carrying dispatches, each folding
two norms in the fused case — the number the gap accounting of §12.8
reconciles. Two more norms per layer run through the same
encode_rms_norm_mul wrapper, and they belong in this count because they
spend from the same dispatch budget: the per-head QK-norms of
Ch 14 add 2 dispatches per layer (104 per token)
at width head_dim = 128 (decode.rs:5599-5618) — same math, tiny n,
per-head γ that turns out to be a constant broadcast. That is the preview;
Ch 14 owns the full story.
12.6 The access pattern
Every kernel chapter in this Part asks the same two questions of its kernel: what does it read, and is it bandwidth-bound? For the norms the second answer is no — but the arithmetic is worth doing anyway, because why it is no is what decides how the kernels above are tuned.
One row of one stream norm at n = 6,656: read x (26,624 B) + two γ
vectors (26,624 B each for the fused tail) and write hidden (26,624 B) +
output (26,624 B) — roughly 106 KB per fused tail, of which the γ
weights are read by all 52 layers’ different tensors (52 × 2 × 26 KB of
distinct weights per token ≈ 2.7 MB) while the activations are re-touched
per layer. Across a token: ~2×52 fused tails ≈ 5.5 MB of activation traffic
plus ~2.7 MB of γ — against the ~16.76 GB artifact stream (lib.rs:14),
that is ~0.05 %. The norms are latency-motivated (keep 1,024 threads’
worth of the vector resident, cross as few barriers as possible), not
bandwidth-motivated. The γ bytes are the only weight traffic here, and
they are one-thousandth of the per-layer matvec weights of
Ch 13.
12.7 Tradeoffs
The rejected 104-group fusion — the measured heart of this chapter. Stand at the fork with us. The gap accounting of §12.8 counts one separated norm-boundary closure on each side of every layer’s sandwich, and the obvious optimization is to fuse more: sweep those +104 closures into the tails and let one dispatch do the work of three. We expected a cheap win. The reduction would be the same reduction, the epsilons the same epsilons; only the dispatch boundary moves.
It did not survive the gate. The retained-activation hybrid preserved greedy
tokens — every sampled token identical, which is exactly the result that
makes a change look safe — and then breached the public numerical contract
underneath them. Full-logit max absolute error came in at 4.6300888e-4 and
normalized logprob max error at 3.197146176834309e-4, both above the
1e-4 contract, with 201,970 of 202,048 logits differing. Tracing
backwards, the first KV difference was a single f16 ULP in layer 1’s value
plane (bits 39,892 vs 39,893). We kept the postmortem:
[docs/decode-dispatch-gap-20260815.md §Rejected hybrid postmortem], and the runs behind it:
muser-receipt://pinned-token-parity-20260814-v{3,4}/.
The hybrid was removed rather than hidden behind a widened tolerance, and
what it taught is narrower and more useful than “fusion is dangerous”. It
was not wrong about the arithmetic. It was wrong about the order of the
arithmetic — and order is what the contract grades. The corrected fusion
took that lesson literally. The …_batch_dual_eps kernel of §12.4.4
reproduces the pinned ggml reductions exactly; it kept the baseline’s
full-logit SHA-256 and measured 39.274 ms GPU against the 40.330 ms
baseline (655 vs 760 closures) in a single-run diagnostic:
[docs/decode-dispatch-gap-20260815.md §Landed and rejected reductions]. The rule we came away with is the one this chapter keeps
repeating: a fusion is eligible only if it is bit-exact, and “almost the
same reduction” is a different reduction.
rsqrt vs 1.0f / sqrt — one function name, contract-sized
consequences. The next fork is far smaller than the last one: not a
dispatch, not a kernel, a single function name inside a single line.
rsqrt is Metal’s fast-math reciprocal square root — one hardware
instruction, a few ULP short of the IEEE-rounded result; 1.0f / sqrt(x)
is fully rounded. A few ULP on one norm is nothing. A few ULP on every norm
of every layer, each one feeding the multiply that follows it, is not
nothing.
The ferrite-lineage port rms_norm_llamacpp.metal exists entirely to
document and fix this difference: its header explains that Ferrite’s default
rsqrt(mean + eps) “differs from 1/sqrt by a few ULP per call,” that
those ULPs “compound into knife-edge logit flips past ~50 tokens,” and that
llama uses the multi-simdgroup reduction and 1.0f / sqrt
(shaders/ferrite/rms_norm_llamacpp.metal:14-19, 96-101).
Muser’s serving answer is blunter than porting that fix: route the
standalone norms through llama’s own metallib kernel (§12.4.2) and make
the fused batch tail reproduce llama’s reduction bit for bit (§12.4.4). That
leaves the 32sg kernel’s rsqrt where it can do no public harm, and it is
one reason that kernel is confined to the legacy one-token graph — which,
per the routing comment at decode.rs:2085-2091, is itself confined to
teacher-forced and profiling duty because its fused kernels’ rounding
“diverges from the source-pinned llama Metal graph enough to breach public
logprob tolerance.”
One fused dispatch vs three split dispatches. If fusing is this
dangerous, why fuse at all? Because the tail that is exact still pays for
itself, and the bookkeeping is easy to read. Unfused, each tail is:
post-norm (read src+γ1, write normed), residual add (read hidden+normed,
write hidden), pre-norm (read hidden+γ2, write output) — 5 reads and 3
writes over 26 KB buffers plus two extra dispatch boundaries. The fused
kernel: read src, γ1, γ2, hidden; write hidden, output — one pass
fewer over the residual, two fewer dispatches, ×104 per token. That is the
+104-closure saving the rejected fusion chased; the exact tail captures it
for the two norm pairs it covers without touching the reduction order.
The cross-vendor seam — when the norm must split in two. One last fork, and it reframes the other three. Everything so far measured “exact” against pinned llama.cpp. Point the engine at a different partner and the same fused kernel becomes the wrong answer.
Under MUSER_CROSS_VENDOR_QK, every norm wrapper here reroutes to a
decomposed no-fast-math pair — unweighted RMS in one dispatch, explicit
learned-weight multiply in a second, with a barrier between
(norm.rs:25-50, norm.rs:115-146). The reason is the disaggregated lane:
vLLM’s producer materializes the weightless norm output in F16 and applies
its scale as a second F16 operation, so the seam-exact receiver must
preserve that intermediate rounding point (norm.rs:280-284). The fused
kernel “loses that intermediate rounding point and is therefore not
seam-exact.”
That is the sentence to leave this section on. A fusion that is exact against llama is still wrong against CUDA, because exactness is never a property a kernel has by itself — it is always relative to an anchor.
12.8 Where the gap lives
This chapter is the most direct resident of the gap in Part IV. The
one-token dispatch-gap reconciliation counts 104 separated norm-boundary
closures (52 post-attention + 52 post-FFN boundary pairs) in the
production graph’s +196-closure delta — the largest single family
[docs/decode-dispatch-gap-20260815.md §Corrected closure-count diff].
The accounting’s sharpest lesson lives here too. Those 104 groups look like pure waste, and they are the first thing anyone reading the profile wants to delete — but every cheap removal we tried changed bits (§12.7’s 3.197e-4 logprob breach), so they are kept, classed “fusible adjacent ops — existing fusion is not exact; reject.” That is what the retained evidence buys: not a faster engine, but the right to leave an obvious-looking inefficiency in place and be able to say exactly why.
The gap survived bit-exactness until the anchor itself changed — the J0/J1 story of Ch 38 and Ch 40. When a later chapter says “the norm boundary is the gap,” this table row is what it means.
12.9 What comes next
The stream is normalized and sits in activations.post_norm. Four weight
matrices are about to read it — Q, K, V, and the attention gate — in the
one concurrent dispatch set where the token’s real bandwidth bill starts
running. That is Ch 13, the hero
chapter of Part IV.
References
crates/muser-engine/src/shaders/ferrite/rmsnorm_batch_tail.metal:1-33—rms_norm_batch, the ferrite-lineage base kernel.crates/muser-engine/src/shaders/ferrite/rmsnorm_batch_tail.metal:142-201—muser_fused_norm_residual_rms_norm_32sg, the dual-eps fused tail.crates/muser-engine/src/shaders/ferrite/rmsnorm_batch_tail.metal:250-322—muser_fused_norm_residual_rms_norm_batch_dual_eps, the pinned-exact batch-graph tail (and its reproduced-publication comment at :277-303).crates/muser-engine/src/shaders/ferrite/rms_norm_llamacpp.metal:1-37, 49-109— the bit-exact llama port; the rsqrt-vs-1/sqrtheader and the accumulation-order commentary.crates/muser-engine/src/metal/encode/norm.rs:244-278—encode_rms_norm_mul(ggml-pinned preference, 128-thread fallback).crates/muser-engine/src/metal/encode/norm.rs:53-94—encode_ggml_rms_norm(kargs packing, llama’s thread-count rule).crates/muser-engine/src/metal/encode/norm.rs:163-241— the 32sg wrapper and its 1,024-thread / 144-byte geometry comment.crates/muser-engine/src/metal/encode/norm.rs:25-50, 280-303— the cross-vendor split-norm seam (vLLM F16 boundary).crates/muser-engine/src/metal/encode.rs:287—kernel_rms_norm_mul_f32_4from the pinned metallib.crates/muser-engine/src/config.rs:23-28—MUSE_POST_NORM_EPS = 1e-8and themuse-glimmer.cpp:67provenance comment.crates/muser-engine/src/config.rs:117-120, 184-188—rms_eps/post_norm_epsfields; the required GGUF epsilon key.crates/muser-engine/src/config.rs:300-314— the four per-layer norm tensors asserted at load.crates/muser-engine/src/decode.rs:5534-5544, 5806-5818, 5869-5889— the entry norm, both fused-tail call sites, and the next-norm selection.crates/muser-engine/src/decode.rs:2085-2091— the routing comment that confines the 32sg graph to teacher-forced duty.crates/muser-engine/src/decode.rs:1216— the all-ones entry-norm γ.[docs/decode-dispatch-gap-20260815.md]— the +196 reconciliation (104 norm-boundary groups), the rejected hybrid postmortem, and the landed-reductions table.muser-receipt://pinned-token-parity-20260814-v3/,-v4/— retained evidence for the rejected hybrid.[arxiv:1910.07467]— Zhang & Sennrich, Root Mean Square Layer Normalization (RMSNorm; the LayerNorm-equivalence claim).- Ch 2 —
simd_sum, barriers, threadgroup memory. - Ch 14 — the per-head QK-norm at width 128.
- Ch 38 — the parity gates the 1e-8 constant protects.
[ferrite-book Ch 10]— the ancestor’s RMSNorm chapter (worked example and reduction pedagogy ported from it).
Chapter 13 — The QKV + gate matvec family
status: polished · path: Muse Glimmer, pinned Muser tree
Prerequisites: Ch 2 (SIMD groups, dispatch), Ch 6 (Q4_K/Q5_K/Q6_K blocks and the pinned ggml
kernel_mul_mv_q*_K_f32kernels), Ch 9 (GQA 32:2, the sigmoid gate), Ch 12 (the normed input these projections read). This is deliberately the longest kernel chapter so far: this one operation family is where the token’s bandwidth bill is actually incurred, and where Muser’s parity discipline is most visibly different from “write a fast kernel.”
13.1 What it computes
Chapter 12 left the stream normalized in activations.post_norm, ready
for the four weight matrices that read it. Before any code appears, ask
the question this chapter exists to answer: when a token spends its time,
where does the time go? Overwhelmingly, it goes here. This is the family
those matvecs belong to — and the place where the token’s bandwidth bill
is actually incurred. A matrix–vector multiply — matvec, or GEMV — is:
y = W · x y_i = Σ_{j=0..cols-1} W[i,j] · x[j] for each output row i
W is a learned weight matrix, x the input vector, y the output. A
matvec is rows independent dot products, one
per output row, each of length cols. The twist is that W is not f32:
it is packed as a kquant format (Q4_K’s 144 bytes per
256 elements, Q5_K’s 176, Q6_K’s 210 — Ch 6), so
every W[i,j] must be dequantized on the fly:
W[i,j] = d × sc_sub(j) × nibble(i,j) − dmin × m_sub(j) (Q4_K form)
Muser never materializes a dequantized weight. The nibbles fly from DRAM through the ALU into an accumulator without ever landing in a buffer.
Muse Glimmer runs four of these matvecs per layer, as one concurrent
dispatch set, all reading the same normed input (decode.rs:5569-5598):
| projection | shape (in × out) | output feeds |
|---|---|---|
attn_q.weight | 6,656 × 4,096 | the 32 query heads |
attn_k.weight | 6,656 × 256 | the 2 KV heads’ keys |
attn_v.weight | 6,656 × 256 | the 2 KV heads’ values |
attn_gate.weight | 6,656 × 4,096 | the sigmoid attention gate (Ch 17) |
Those shapes are not arbitrary; they fall out of the geometry the
architecture chapter fixed. hidden = 6,656 sets every
matrix’s input side. n_heads × head_dim = 32 × 128 = 4,096 sets the
output side of Q and of the gate. n_kv_heads × head_dim = 2 × 128 = 256
sets K’s and V’s, narrow because grouped-query attention shares each KV
head across many query heads. We read those extents off the golden test,
which is where the anchor is kept
[crates/muser-engine/tests/muse_golden.rs:96-100], and
config.rs:300-307 asserts them again at load, so a checkpoint whose
tensors disagree fails before it can produce a wrong number. Muse Glimmer is
not a fused-QKV architecture: there is no single [6,656 × 4,608] QKV
matrix. There are four independent tensors, dispatched as four independent
matvecs that share one read-only input and write disjoint outputs — “one
concurrent set,” in the call site’s own words (decode.rs:5566-5568). The
gate is the fourth, unusual member; Ch 17
explains what it gates.
13.2 Why it exists — decode is matvec, not matmul
Matvec vs GEMM. A GEMM multiplies a matrix by a matrix of many columns. Prefill is a GEMM: the whole prompt is one batch of tokens, so each weight byte is reused across the batch (Ch 36). Decode is a matvec: there is exactly one token, the batch collapses to one column, and every weight byte is used exactly once before being discarded until the next token.
That single-use property is the whole memory problem of Ch 1. With no reuse across a batch, the work is compute-light (one multiply-accumulate per weight) and memory-heavy (the entire matrix must stream from DRAM). Decode is bandwidth-bound by construction, and the matvec family is the workload that streams the weights: strip the norms, RoPE, attention, and activations out of a token and what remains, line for line, is a sequence of quantized matvecs — these four, the o_proj, the FFN’s three, and the LM head.
13.3 The matrix operation, explained from zero
If you have not multiplied a matrix by a vector recently, do this 2×2 by hand (Figure 13.1). Take
W (2×2) x (2×1) y (2×1)
┌ ┐ ┌ ┐ ┌ ┐
│ 1 2 │ × │ 1 │ = │ y₀ │
│ 3 4 │ │ 5 │ │ y₁ │
└ ┘ └ ┘ └ ┘
y₀ = 1·1 + 2·5 = 11
y₁ = 3·1 + 4·5 = 23
Figure 13.1: a 2×2 matvec by hand. Two dot products of length 2; two
outputs. Every matvec in this chapter is this, with cols = 6,656 and
rows from 256 to 202,048, and with W’s entries unpacked from nibbles.
Now the storage fact that shapes every kernel here: GGUF weight matrices
are row-major — row 0’s elements are contiguous, then row 1’s, and so
on. One output row’s weights are therefore one contiguous span of DRAM,
and contiguous spans are what DRAM serves efficiently. The naive mapping —
one thread per output row, walking all cols bytes serially — is correct
but starves the memory system: a single thread cannot keep enough loads
in flight (memory requests the hardware has accepted but
not yet answered) to saturate the bus. Every real
kernel in this family instead splits each dot product across the 32 lanes
of a SIMD group and splits rows across
threadgroups. The exact split differs per kernel; the shared skeleton is
“dequant-and-MAC in the inner loop, simd_sum to combine the 32 partials,
lane 0 writes the output row.”
One more piece of vocabulary you need for §13.4: ggml’s kargs. llama.cpp’s
Metal kernels do not take rows/cols as friendly scalars — they take a
packed C struct of tensor extents and byte strides (ne00, nb01, …),
the same struct the CPU backend passes. Muser builds that struct in Rust:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/metal/encode/qkv.rs:1292
impl GgmlKargsMulMv {
fn for_matmul(rows: usize, cols: usize, block_bytes: usize, nr0: i32) -> Self {
let row_bytes = (cols / 256 * block_bytes) as u64;
Self {
ne00: cols as i32, // elements per row
ne01: rows as i32, // rows
// …(strides elided: nb00 = block_bytes, nb01 = row_bytes, …)
nr0, // rows reduced per threadgroup
// …
}
}
}
}
(Fields elided; see the file for the full 112-byte struct.)
13.4 The kernels — three sources, one family
Ch 4 established that Muser runs kernels from three libraries. All three meet in this chapter’s dispatch.
13.4.1 The pinned ggml matvec — kernel_mul_mv_q*_K_f32
Start with the question that decides everything else in this section:
whose code actually runs when Muse Glimmer projects a token? Not ours.
The primary decode matvec is llama.cpp’s own kernel, loaded from the
metallib pinned by MUSER_GGML_METALLIB
(crates/muser-engine/src/metal/context.rs:122-131) at llama.cpp commit
89e0aa6fd362… (PINNED.md). Registration names the exact functions:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/metal/encode.rs:278
ggml_q4k: ggml_matvec_pipeline(context, "kernel_mul_mv_q4_K_f32")?,
ggml_q5k: ggml_matvec_pipeline(context, "kernel_mul_mv_q5_K_f32")?,
ggml_q6k: ggml_matvec_pipeline(context, "kernel_mul_mv_q6_K_f32")?,
}
We do not quote these kernels’ bodies in this book — they are not in the
Muser tree; they are pinned binary provenance, compiled from llama.cpp’s
source at the comparator commit and built by
scripts/compile_llama_metallib.sh (scripts/ per the repo’s tooling).
What Muser owns is the dispatch, and here it is, the tokens == 1 branch
of encode_quantized_matmul:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/metal/encode/qkv.rs:429
if tokens == 1 {
if let Some(pipeline) = self.ggml_matvec(dtype) {
let (block_bytes, rows_per_group) = match dtype {
GgmlType::Q4_K => (144, 2),
GgmlType::Q5_K => (176, 1),
GgmlType::Q6_K => (210, 2),
_ => unreachable!("ggml_matvec returned only for K-quant projections"),
};
let args =
GgmlKargsMulMv::for_matmul(n_out, n_in, block_bytes, rows_per_group as i32);
encoder.set_compute_pipeline_state(pipeline);
set_value(encoder, 0, &args);
encoder.set_buffer(1, Some(weights.metal()), weights.offset() as u64);
encoder.set_buffer(2, Some(input.metal()), 0);
encoder.set_buffer(3, Some(output.metal()), 0);
let simdgroups = 2usize;
encoder.dispatch_thread_groups(
MTLSize::new(n_out.div_ceil(rows_per_group * simdgroups) as u64, 1, 1),
MTLSize::new(32, simdgroups as u64, 1),
);
return;
}
// …(standalone fallbacks: §13.4.2)…
}
}
Read the geometry off the dispatch:
- threadgroup
(32, 2)— 64 threads = 2 SIMD groups, llama’s ownN_SGfor these kernels (the function constants pinned at registration bakensg=2,encode.rs:844-851). - grid
ceil(n_out / (rows_per_group × 2))— each threadgroup ownsrows_per_groupoutput rows: 2 rows per group for Q4_K and Q6_K, 1 for Q5_K, matching llama’s per-formatnr0. - Buffers ride in at llama’s slots: kargs at
buffer(0), weights at 1 (with the mmap-arena offset), input at 2, output at 3.
For the Q projection: n_out = 4,096, rows_per_group = 2 →
4,096 / 4 = 1,024 threadgroups of 64 threads. For K and V: n_out = 256
→ 64 threadgroups each. For the gate: 1,024 again.
Why dispatch llama’s kernels instead of writing better ones? We arrived at
this fork holding evidence for the other branch. The ancestor line had
written its own Q4_K GEMV and measured it: it beat llama’s kernel by 6–10 %
in isolated A/B, with bit-identical output
[ferrite-book Ch 11] — a Ferrite-lineage measurement on A18 Pro, never a
Muser result. The expectation we carried into Muser was that the same trick
would transfer, and that owning the inner loop would be strictly better
than borrowing one.
That expectation did not survive contact with Muser’s gate, and the reason has nothing to do with speed. Muser’s gate is stricter than Ferrite’s was: full-logit and logprob parity against a pinned comparator, not token parity. Put plainly, matching the reference is not a property of the answer, it is a property of the route to the answer — reduction order, rounding points, accumulation tree and all. A hand-written kernel must therefore prove bitwise equality with llama’s route, and “bit-identical” is a claim you re-earn at every shape, every dtype, every batch width, forever. That is the lesson: the cheapest way to match a reference’s floating-point behavior is to run the reference’s own compiled code.
It helps that the prize was small anyway. Even in the ancestor’s own telling, the inner loop was proven not to be the engine’s gap — the win we would have been defending was never the one that mattered. Under Muser’s constraint, “pin the kernel” dominates “beat the kernel.”
13.4.2 The standalone fallbacks — muser_matvec_q4k_4r2s and friends
What if the pinned metallib is not on the machine at all? Muser does not refuse to run — it falls back to kernels it owns. That fallback is also the only member of this family whose source we can open on the page, which makes it the place to learn what the pinned kernel is doing behind its compiled wall. Keep one thing in reserve while you read it, though: the tradeoffs section returns to argue that “falls back” and “runs the same computation” are not the same sentence.
When the metallib is absent, the same wrapper falls back to Muser-owned
kernels (qkv.rs:451-474): Q4_K routes to muser_matvec_q4k_4r2s
(grid n_out/8, 64 threads), Q5_K to muser_matvec_q5k_4sg (grid
n_out, 128 threads). The 4r2s kernel — “4 rows, 2 SIMD groups” — is the
Muser-authored adaptation of the Ferrite lineage, and its body is worth
reading because it shows the dequant fused into the MAC concretely:
// crates/muser-engine/src/shaders/muse_reference.metal:735
kernel void muser_matvec_q4k_4r2s(
device const uchar *weights [[buffer(0)]],
device const float *input [[buffer(1)]],
device float *output [[buffer(2)]],
constant uint &rows [[buffer(3)]],
constant uint &cols [[buffer(4)]],
uint group [[threadgroup_position_in_grid]],
uint lane [[thread_index_in_simdgroup]],
uint simd [[simdgroup_index_in_threadgroup]]) {
uint block_count = cols / 256;
uint row_bytes = block_count * 144;
uint base_row = group * 8 + simd * 4;
if (base_row >= rows) return;
uint active_rows = min(4u, rows - base_row);
float accumulator[4] = {0.0f, 0.0f, 0.0f, 0.0f};
device const uchar *row[4] = {
weights + ulong(base_row) * ulong(row_bytes),
// …(rows +1..+3 elided: same pattern)…
};
for (uint block_index = 0; block_index < block_count; ++block_index) {
for (uint row_index = 0; row_index < active_rows; ++row_index) {
device const uchar *block = row[row_index] + block_index * 144;
uint delta = *reinterpret_cast<device const uint *>(block);
float d = float(as_type<half>(ushort(delta & 0xffff)));
float dmin = float(as_type<half>(ushort(delta >> 16)));
uint sd0 = *reinterpret_cast<device const uint *>(block + 4);
uint sd1 = *reinterpret_cast<device const uint *>(block + 8);
uint sd2 = *reinterpret_cast<device const uint *>(block + 12);
float d_scale[8];
float neg_min[8];
muser_decode_all_q4k_scales(d, dmin, sd0, sd1, sd2, d_scale, neg_min);
uint input_base = block_index * 256;
for (uint quant_group = 0; quant_group < 4; ++quant_group) {
uint packed = uint(block[16 + quant_group * 32 + lane]);
float low = input[input_base + quant_group * 64 + lane];
float high = input[input_base + quant_group * 64 + 32 + lane];
accumulator[row_index] +=
fma(d_scale[quant_group * 2], float(packed & 0x0f), neg_min[quant_group * 2]) * low;
accumulator[row_index] +=
fma(d_scale[quant_group * 2 + 1], float(packed >> 4), neg_min[quant_group * 2 + 1]) * high;
}
}
}
for (uint row_index = 0; row_index < active_rows; ++row_index) {
accumulator[row_index] = simd_sum(accumulator[row_index]);
}
if (lane == 0) {
for (uint row_index = 0; row_index < active_rows; ++row_index) {
output[base_row + row_index] = accumulator[row_index];
}
}
}
(Rows +1..+3 of the row[4] initializer elided — identical pointer
arithmetic at muse_reference.metal:750-755.)
The anatomy, in the order the kernel uses it:
- Four output rows per threadgroup, four per SIMD group’s lane set —
base_row = group * 8 + simd * 4: 8 rows per threadgroup (2 SIMD groups × 4), each lane holding four private accumulators, one per row. - Pre-decoded scales —
muser_decode_all_q4k_scales(muse_reference.metal:41-62) unpacks all eight sub-block scale/min pairs once per super-block per row, folding indand pre-negating the min:d_scale[j] = d × sc_j,neg_min[j] = −(dmin × m_j). The inner loop then uses them as constants. - Byte-wise nibble read with
fma— lanelanereads one byteblock[16 + quant_group*32 + lane]; its low nibble covers one 32-element sub-block, its high nibble the partner sub-block (low/highinput elements). Each element costs one fused multiply-add —fma(d_scale, nibble, neg_min)computes the fully dequantized weight with a single rounding — plus one multiply-accumulate into the row’s accumulator. simd_sumper row, lane 0 writes — the 32 lanes each covered 32 of every 256 elements; one hardware instruction per row recombines them.
There was a genuine fork inside that inner loop, and the ancestor line
took both branches. What you just read is the pre-decoded-scales design
point [ferrite-book Ch 12]: unpack every scale up front, then run a loop
with nothing in it but loads and multiply-adds. The sibling v4 kernel chose
deferred scaling instead — accumulate the raw nibble products and apply
the scales once per sub-block at the end, spending fewer multiplies for a
more tangled loop. Neither branch is wrong. They are two ways to spend the
same arithmetic, and on their own terms both are correct.
The reason to care is downstream of correctness. Fold the scales in early
and you round early; defer them and you round late. Same function, same
inputs, different last bit. So the Muser fallback is not a slower copy of
the pinned kernel — it is a different kernel that happens to agree to
within a rounding step. The tree makes that policy explicit rather than
letting it happen quietly: encode.rs:370-385 gives Q6_K no standalone
fallback at all, “so its math and dispatch remain comparator-exact.”
13.4.3 The other two libraries, briefly
The cross-vendor library (strict-f32, no-fast-math recompile of
muse_reference + nvfp4, context.rs:111-121) supplies
muser_cross_vendor_q4k/q5k/q6k — CUDA-parity scalar routes for the
disaggregated lane, gated by MUSER_CROSS_VENDOR_QK
(qkv.rs:300-335). And the native NVFP4 lane replaces this whole
kquant family with muser_nvfp4_*/muser_f16_matvec_c* kernels
(qkv.rs:68-227); Ch 7 owns that story. The
four-projection graph structure is identical on every lane — only the
inner-loop kernels change.
13.5 The Rust dispatch — one concurrent set of four
Four matvecs read the same vector. Do they have to take turns? Nothing in
the math says so, and nothing in the encode says so either — the permission
to overlap is expressed by where the calls sit, not by a scheduler.
encode_token records all four projections inside a single dispatch
closure. The comment at the call site is the design statement:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/decode.rs:5566
// llama.cpp and Ferrite issue the four independent attention
// projections as one concurrent set. They share a read-only input
// and mapped weight arena but write disjoint activations.
dispatch(command, |encoder| {
self.encode_projection(
encoder,
&layer.q,
&self.activations.post_norm,
&self.activations.q,
1,
);
self.encode_projection(
encoder,
&layer.k,
&self.activations.post_norm,
&self.activations.k,
1,
);
// …(v and gate: identical shape, into activations.v / activations.gate;
// elided — decode.rs:5584-5597)…
});
}
encode_projection (decode.rs:6044-6089) is the dtype router: F16 →
encode_f16_matmul, NVFP4 → encode_nvfp4_matmul, kquant →
encode_quantized_matmul of §13.4. Each of the four calls lands on the
same 26,624-byte read of post_norm — cheap after the first, because by
then it is L2-resident — and on its own disjoint weight span and its own
output buffer. That disjointness is the whole permission slip: no kernel
here writes anywhere another kernel reads, so none of them needs to wait.
One closure, four kernel dispatches. That asymmetry matters beyond this
page, because it is the kind of thing that quietly corrupts a measurement.
Count closures in a profile, call the count “dispatches,” and you are
wrong by a factor on exactly this line — and the QKV line is one of the
biggest in the layer, so the error does not stay small. The gap note says
it in its own words, and we kept the correction:
decode-dispatch-gap-20260815.md §Instrumentation correction — “the
qkvg closure encodes four kernel dispatches but contributes one profiler
count”.
13.6 The access pattern — the budget, itemized
This is where the bandwidth story lives. The question is blunt: what does one token cost in bytes, and how much of that bill do these four matvecs sign? Per layer, per token, the four matvecs read:
q : 6,656 × 4,096 = 27,262,976 params
k : 6,656 × 256 = 1,703,936 params
v : 6,656 × 256 = 1,703,936 params
gate : 6,656 × 4,096 = 27,262,976 params
─────────────────────
attention projections total = 57,933,824 ≈ 57.9 M params
At Q4_K’s 0.5625 bytes/param that is ≈ 32.6 MB per layer. Resist writing
that figure down as the number, though, because it is not a property of
the architecture: which tensors ship at which dtype is a property of the
loaded GGUF. The loader reads a dtype per tensor as it maps the file, and
the only anchor the tree gives us is that the release artifact’s FFN
gate/up are Q4_K. Both receipts are retained — decode.rs:1294-1310 for
the per-tensor read, decode.rs:5820-5821 for the anchor.
So parametrize rather than assert. At Q4_K bitrate the four
matvecs are ~32.6 MB/layer; at Q5_K (0.6875 B/param) ~39.8 MB; at Q6_K
(0.8203 B/param) ~47.5 MB. Now put the same layer’s other matvecs beside
them — o_proj (27.3 M params), FFN gate/up/down (3 × 132.9 M params).
Against the layer’s ~483.8 M projection parameters the attention set is
≈ 12 %, o_proj ≈ 6 %, and the FFN ≈ 82 %: the family this chapter is about
is the smaller share of the layer’s traffic, which is worth knowing
before you spend a week optimizing it. Scale by 52 layers and add the
~1.34 B-parameter embedding and LM head each, and the per-token stream is
the artifact’s 16,756,681,056 bytes (lib.rs:14) — the number
Ch 1 turned into a token-time
budget. Every one of those bytes crosses the DRAM bus exactly once per
token; this chapter’s kernels are the mechanism for four of the nine
per-layer fractions of it.
Two structural notes on how the bytes are touched. First, the weights
are a view into the single mmap’d GGUF arena (weights.offset() in every
dispatch) — no staging, no per-tensor copies (Ch 3).
Second, each output row’s bytes are contiguous (row-major), each
threadgroup reads whole rows, and llama’s nr0 = 2 pairing means
consecutive rows stream together — the access pattern is as close to
“linear read of a big buffer” as the layout allows, which is precisely
what a bandwidth-bound kernel wants.
13.7 Tradeoffs
Four separate matvecs vs a fused QKV kernel. The obvious alternative — one kernel reading all four weight matrices and writing all four outputs — saves three dispatch boundaries per layer (×52) and would let one weight fetch serve four accumulators. Muse Glimmer cannot take it: there is no fused QKV tensor to read, and synthesizing one would change the checkpoint. But the deeper answer is in the routing comment that governs which graph serves decode at all:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/decode.rs:2085
// The legacy one-token graph uses Ferrite fused residual/norm and
// gate-up kernels whose rounding diverges from the source-pinned
// llama Metal graph enough to breach public logprob tolerance.
// The one-row batch graph dispatches the exact pinned kernels and
// has the same KV transition, so it is the serving correctness
// path until each fused kernel independently passes full-logit
// parity.
}
Read that comment as the war story it is. We had the fused kernels already — the Ferrite-lineage fused residual/norm and gate-up path, fewer dispatch boundaries per layer, exactly the win the fused-QKV argument above is reaching for. We expected a free speedup, on the intuition that fusing two exact operations ought to stay exact. It was not free. The fused rounding diverged from the source-pinned llama Metal graph by enough to breach public logprob tolerance: no bug, no lost precision anyone could point at, just a different order of operations arriving at a different last bit.
The lesson is the one this chapter keeps re-teaching in new words. Fusion
is not free even when it is numerically exact in isolation, because the
standard here is not “exact” — it is “exact against the pinned
comparator, per kernel.” A fused kernel that is more accurate than the
reference still fails. So serving routes single tokens through the one-row
batch graph (forward_batch, decode.rs:2092) — the same op sequence,
running the pinned kernels — while the legacy encode_token graph this
book narrates survives for teacher-forced harnesses and phase profiling
(decode.rs:2124-2182, gated by MUSER_METAL_PHASE_PROFILE). The cost is
paid in engineering discipline, not in tokens: Muser keeps two graphs
precisely so the cheap one can be held out of serving until it proves
parity.
Batch-width boundaries are numerical boundaries. For multi-token inputs the same wrapper switches kernels by token count, and the switch comment is the campaign in one paragraph:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/metal/encode/qkv.rs:476
// Match the source-pinned llama.cpp Metal dispatch boundary exactly:
// K-quant projections with four through eight activation rows use
// `mul_mv_ext`, with a token-count-specific number of rows per
// threadgroup. This changes the floating-point reduction order, so
// substituting repeated decode GEMVs here breaks embedding/logprob
// numerical parity even when every other layer is identical.
}
Decode never leaves the single-token rung, so it is fair to ask why the rest of the ladder belongs in a decode chapter. It belongs because parity is not a decode-only property: the verify path and the prefill path enter through this same wrapper, and every rung of it is a different arithmetic.
At tokens 4..=8 the pinned kernel_mul_mv_ext_{q4,q5,q6}_K_f32_r1_{2..5}
pipelines run (encode.rs:959-1003); at 16 rows the M16 n32 tiles; at
larger multiples the SGM batch matmuls; at 512-row prefill chunks, the
kernel_mul_mm_* GEMMs (qkv.rs:482-623). Every one of these is a
different floating-point reduction order, and the wrapper keeps llama’s
exact boundaries so the comparator’s bits are reproducible at every batch
width. The DFlash verify path leans on the same ladder — its 16-row
matmuls are why the M16 tile family exists at all
(Ch 33).
Pin the kernel vs write a better one. Covered in §13.4.1, but it
belongs in the tradeoff ledger with its labels straight: the 6–10 %
isolated-kernel win is Ferrite-lineage evidence [ferrite-book Ch 11]
(A18 Pro, Qwen2.5-1.5B), never measured on Muser’s M3 Ultra, and Muser’s
gate (full-logit parity against pinned llama.cpp) is not winnable by a
hand-written kernel that must then prove bitwise equality against
llama’s own. The pinned-kernel strategy converts a hard numerical problem
into a build-provenance problem — solved by the metallib receipt of
Ch 4.
Fallbacks are correct, not equivalent. The standalone 4r2s/4sg kernels
compute the same function but round differently (different lane
decomposition, pre-decoded scales). Running without MUSER_GGML_METALLIB
is therefore a different numerical lane, not merely a slower one —
Q6_K’s lack of any fallback (encode.rs:370-385) makes that policy
explicit rather than accidental.
13.8 Where the gap lives
Does this family contribute to the decode gap? There are two answers, and holding them apart is the whole point of the section.
In the closure accounting of [docs/decode-dispatch-gap-20260815.md],
the answer is no. These four matvecs sit in the common math row — 406
closures, production delta 0, “required math / Keep.” They are not among
the +196 closure families the note calls out (104 norm boundaries, 39 SWA
staging, 52 KV-publication splits, 1 copy). There is nothing here to
delete: no repeated matvec closure doing identical arithmetic exists to
remove.
In the byte accounting of Ch 1, the answer is yes, and emphatically so — this family is the bulk of the budget. The token’s time is the time to stream 16.76 GB through kernels like these, and how close they sit to the machine’s bandwidth ceiling is the recurring question the book returns to at Ch 38.
Both answers are correct, because they answer different questions. “Is there wasted work here?” is a closure question, and the closures say no. “Is this where the time goes?” is a byte question, and the bytes say yes. Keeping those two framings from contaminating each other is exactly the epistemics the gap note exists to enforce.
13.9 What comes next
Q, K, V, and gate now hold raw projection outputs — un-normalized, un-rotated, positionless. Before attention can use them, each head’s slice must be QK-normalized and, on the 39 sliding layers, rotated by RoPE. On the 13 NoPE layers nothing rotates at all — and that asymmetry is about to become the most consequential layout decision in the engine. That is Ch 14.
References
crates/muser-engine/src/metal/encode/qkv.rs:414-450— thetokens == 1pinned-matvec dispatch (quoted in §13.4.1);:451-474the fallbacks;:476-508themul_mv_extboundary;:550-623the M16/SGM/GEMM batch ladder;:1292-1317GgmlKargsMulMv.crates/muser-engine/src/metal/encode.rs:278-280— the pinned kernel names;:837-866ggml_matvec_pipeline(nsg=2 function constants);:959-1003themul_mv_extgroups;:370-385supports_projection(Q6_K pinned-only).crates/muser-engine/src/metal/context.rs:122-131—MUSER_GGML_METALLIBloading of the pinned llama.cpp metallib.crates/muser-engine/src/shaders/muse_reference.metal:735-788—muser_matvec_q4k_4r2s(quoted in §13.4.2);:41-62the scale decoder;:790-831muser_matvec_q5k_4sg.crates/muser-engine/src/decode.rs:5566-5598— the four-projection concurrent set;:6044-6089encode_projection;:2085-2093the serving-routing comment;:1294-1310per-tensor dtype gating at load.crates/muser-engine/tests/muse_golden.rs:96-101— the geometry (6,656 / 32 / 2 / 128) this chapter’s shapes derive from.scripts/compile_llama_metallib.sh— builds the pinned metallib.[docs/decode-dispatch-gap-20260815.md]— closure-vs-dispatch instrumentation; the +196 reconciliation this chapter’s gap section cites.[docs/goal-parity-ledger-2026-08.md]— the parity gates the pinned kernels exist to satisfy.- Ch 1 — the 16.76 GB/token budget this family incurs.
- Ch 4 — the three kernel sources and the metallib receipt.
- Ch 6 — the block formats the kernels unpack.
- Ch 17 — what the gate output does.
- Ch 36 — the GEMM side of the same weights.
[ferrite-book Ch 11, Ch 12]— the ancestor’s v4/4sg GEMV chapters (2×2 worked example, deferred-vs-pre-decoded scaling, and the 6–10 % inner-loop verdict — Ferrite-lineage, labeled as such).
Chapter 14 — QK-norm and RoPE — rotating only the layers that rotate
status: polished · path: Muse Glimmer, pinned Muser tree
Prerequisites: Ch 9 (the two layer classes, GQA), Ch 12 (the RMSNorm machinery this chapter reuses), Ch 13 (the Q/K/V/gate projections that produce this chapter’s inputs). No prior exposure to positional encodings is assumed.
14.1 What this chapter computes
Chapter 13 ended with Q, K, V, and gate holding raw projection outputs — un-normalized, un-rotated, positionless. This chapter is what happens to Q and K next, and it turns on a question that sounds trivial until you try to answer it inside a kernel: how does a machine built entirely out of dot products know which token came first? Muse Glimmer answers that question twice, differently, in the same forward pass — and the second answer is the one the rest of the book keeps cashing in. Two operations run between the projections and the KV store, and a third deliberately does not:
- Per-head QK-norm — an RMSNorm applied to each attention head’s
128-wide slice of Q and K separately (
rms_norm_per_headfamily; the live dispatch reuses Ch 12’s wrapper). On Muse Glimmer it is parameterless in effect: the weight tensors are converter-synthesized constant broadcasts, verified at load. - RoPE (Rotary Positional Embedding) — a per-position rotation of Q and K, applied only on the 39 sliding-window layers.
- NoPE — the 13 full-attention layers (
{3, 7, …, 51}) apply no positional rotation at all (config.rs:51-60). Their position information comes entirely from the causal mask and the KV layout.
In one formula, for one pair of coordinates of a head vector at position
pos on a sliding layer, with pair frequency θ_i:
┌ x₀' ┐ ┌ cos(pos·θᵢ) −sin(pos·θᵢ) ┐ ┌ x₀ ┐
└ x₁' ┘ = └ sin(pos·θᵢ) cos(pos·θᵢ) ┘ └ x₁ ┘
Nothing is added, nothing is learned at runtime; each 2D pair is spun by an
angle that grows with position. The payoff — proved in §14.4 — is that
attention’s Q·K ends up depending only on the position difference.
The two layer classes make this chapter’s structure unusual among Muse engines: the position question has two different answers in one model, and the second answer (NoPE) is what makes the disaggregated lane of Part VI possible.
14.2 Why position must be injected at all
This is the part that trips people up the first time. Attention — the one operation that reaches across tokens (Ch 16) — is permutation-invariant. At its core it is dot products and a weighted sum, and a dot product does not care which token came first. So to raw attention, the sentences
the cat sat on the mat
mat the on sat cat the
look identical — same tokens, order differs, attention cannot see order. But order is the whole point of language. Something must tell the model “this token is at position 0, this one at position 5.” Skip it and the model is order-blind; it will produce plausible-looking gibberish that no logprob gate would forgive. The three classical schemes, in one paragraph each:
- Absolute embeddings — learn one vector per position, add it to the token embedding. Simple; generalizes poorly past trained positions, and distances must be learned.
- Relative biases — add a per-pair bias to each attention score. Generalizes better; modifies the attention kernel itself.
- RoPE — rotate Q and K so their dot product depends only on the position difference. Relative position falls out of the math for free, and the attention kernel is untouched.
Muse Glimmer picks RoPE for its sliding layers — and picks nothing for
its full layers, trusting the window/mask structure to carry order where
the context is unbounded. Why a hybrid like this works is a model-design
claim the engine inherits [unverified]; what the engine must do is
implement each layer’s choice exactly.
14.3 QK-norm first — the parameterless cousin
Before anything spins, a smaller question has to be settled: are all the heads speaking at the same volume? Attention’s softmax is a competition, and a head whose Q and K happen to leave the projection with a large magnitude wins that competition for reasons that have nothing to do with meaning. QK-norm is the answer to that, and on this model it is a strange one — a normalization with no learned parameters, wearing a learned parameter’s clothes. Immediately after the projections, before any rotation, each head of Q and K is RMSNormed across its own 128 dimensions:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/decode.rs:5599
dispatch(command, |encoder| {
self.kernels.encode_qk_norm(
encoder,
&self.activations.q,
&layer.q_norm,
&self.activations.q,
cfg.head_dim,
cfg.rms_eps,
cfg.n_heads,
);
self.kernels.encode_qk_norm(
encoder,
&self.activations.k,
&layer.k_norm,
&self.activations.k,
cfg.head_dim,
cfg.rms_eps,
cfg.n_kv_heads,
);
});
}
encode_qk_norm (metal/encode/norm.rs:286-303) delegates to
encode_rms_norm_mul with dim = head_dim = 128 and rows = 32 (Q) or
2 (K) — i.e. the per-head norm is dispatched as a batched 128-wide
RMSNorm whose rows are heads, in place. The dedicated
rms_norm_per_head kernel exists in the pipeline registry
(shaders/ferrite/rms_norm_per_head.metal:15, one threadgroup per head,
tree reduction in threadgroup memory), and it is the right place to read
the algorithm — but the live decode path reaches the same math through the
shared norm wrapper of Ch 12
(whose pinned-ggml preference applies at this width too).
The Muse-specific wrinkle is the γ. The upstream checkpoint has no
learned q/k norm weights; the GGUF converter materializes
full(qk_scale_factor) for attn_q_norm.weight and ones(...) for
attn_k_norm.weight so llama.cpp’s weighted-RMSNorm op can carry a scalar
(config.rs:383-395).
That leaves a fork at load time, and it is worth walking both branches. The comfortable one is to shrug and multiply: the tensors are present, the norm kernel already takes a weight vector, so read whatever is in the file and move on. The uncomfortable one is to ask what happens the day the file stops being what we assume — the day a converter writes a genuinely learned per-channel norm into those same two slots. Nothing would crash. The kernel would happily consume a vector where we expected a broadcast, the math would quietly become a different model’s math, and the output would stay fluent. That is the failure mode this book keeps meeting: plausible text is not evidence of a correct engine.
Muser takes the uncomfortable branch and proves the assumption instead
of holding it. QkNormProbe fails the load unless both tensors are exactly
the constant broadcasts the converter emits (config.rs:397-403), so a
learned per-channel norm aborts startup instead of silently redefining the
attention scores (loader.rs:28-37). The same probe pins the values it
accepts: qk_scale_factor ≈ 3.87 and k_norm = 1.0 (config.rs:139-141),
with an arithmetic companion test at config.rs:426-430 checking
3.87 × 1/√128 ≈ 0.342. The lesson outlives this one tensor: when a number
reaches the engine from a converter rather than from training, the only
safe way to depend on it is to assert it at the boundary.
So Q’s norm is “normalize, then scale by ≈3.87” and K’s is a plain
normalize. And note what the scale is in addition to: the attention
softmax scale is still 1/√128 ≈ 0.0883883 (config.rs:277-281),
independent of the folded-in 3.87 — two different scales living at two
different points of the graph, easy to conflate, asserted apart by test.
Back to the question this section opened with, now that the machinery is
on the page. Per-head normalization exists to stabilize the score
distribution within each head before the dot product, so that one
hot-headed head cannot dominate the softmax on the strength of its
magnitude alone. On this model the “learned” part of that
stabilization was folded into a single scalar by training upstream
[unverified] for the quality rationale — what is verified is the probe,
the values, and where they are applied.
14.4 RoPE from zero
14.4.1 Pair up, rotate, at a per-pair frequency
So how do you tell a dot product where a token sits, without adding a parameter and without touching the attention kernel? You spin the vector. The mechanism is easier than RoPE’s reputation suggests; what ruins engines is never the rotation itself but the bookkeeping around it, so build the mechanism first and meet the bookkeeping immediately after.
Take one head’s 128-wide vector and split it into 64 pairs. Muse Glimmer
uses the interleaved convention — pair i is the adjacent couple
(x[2i], x[2i+1]) — not the half-split (“NEOX”) convention of Llama/Qwen.
The shader file’s own comment block is the authoritative statement and is
worth quoting whole:
// crates/muser-engine/src/shaders/ferrite/rope.metal:610
// ── NORM-convention RoPE (LLAMA_ROPE_TYPE_NORM) ─────────────────────────
//
// Every other kernel in this file uses the NEOX convention: rotate the pair
// (x[i], x[i + half_hd]). llama.cpp's `rope_norm` instead rotates the
// *interleaved* pair (x[2i], x[2i+1]). Both read the SAME frequency table —
// freq[j] = base^(-2j/head_dim) — so only the element pairing differs, and a
// model that needs one and gets the other still produces fluent text with
// silently wrong positions. That is why these are separate kernels rather
// than a runtime flag threaded through the NEOX ones.
//
// Muse Glimmer is the first NORM-rope architecture in the tree. Its GGUF
// converter un-permutes Q/K at conversion time precisely so the interleaved
// form is the correct one for the stored weights.
Read the warning twice: rotating with the wrong pairing still runs and still produces fluent text — with silently wrong positions. There is no crash to catch; only a parity gate against pinned llama.cpp can see it. A bug with no symptom cannot be found in production, so it has to be made unrepresentable at build time instead — the defense the tradeoffs section at the end of this chapter returns to.
Pair i has a fixed frequency, set once at load:
θ_i = rope_base_swa^(−2i/head_dim) (i = 0..63)
rope_base_swa is read from the GGUF key rope.freq_base_swa (falling
back to rope.freq_base, default 10,000 if absent,
config.rs:120-129, 199-205). The pinned checkpoint’s value is not
asserted anywhere in the tree [unverified] — what is verified is the
key it is read from and the table formula built in Rust:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/decode.rs:1256
(0..cfg.head_dim / 2)
.map(|index| {
1.0 / cfg
.rope_base_swa
.powf(2.0 * index as f32 / cfg.head_dim as f32)
})
.collect::<Vec<_>>()
}
The powf happens once per engine; no kernel ever calls pow — that is
what the _cached in the kernel name means.
14.4.2 The rotation, and one worked pair
The rotation of a 2D vector (v₀, v₁) by angle a is the 2×2 matrix every
graphics programmer has memorized:
v₀' = v₀·cos(a) − v₁·sin(a)
v₁' = v₀·sin(a) + v₁·cos(a)
Length is preserved; the vector spins counterclockwise. Work one toy case,
head_dim = 4 (two pairs), using the config-default base 10,000 so every
number is real:
θ₀ = 10,000^(−0/4) = 1.0 (fastest pair: 1 radian per position)
θ₁ = 10,000^(−2/4) = 0.01 (100× slower)
At pos = 2, take x = [1, 0, 1, 0] (both pairs pointing at their x-axis):
pair 0, angle 2×1.0 = 2.0 rad :
(1, 0) → (cos 2.0, sin 2.0) ≈ (−0.4161, 0.9093)
pair 1, angle 2×0.01 = 0.02 rad :
(1, 0) → (cos 0.02, sin 0.02) ≈ ( 0.9998, 0.0200)
x' ≈ [−0.4161, 0.9093, 0.9998, 0.0200]
Figure 14.1: RoPE at pos 2 on a head_dim-4 toy. The fast pair has been
thrown nearly onto the negative x-axis; the slow pair barely moved. Every
position leaves a different fingerprint of angles. At real width 128,
frequencies θ_i span many orders of magnitude — a bank of 64 clocks at
wildly different speeds, from “full turn every ~6 positions” (i = 0) to
“one turn in ~10⁶ positions” for the slowest pair at base 10⁴ (and further
out at larger bases). A larger base slows the slow clocks, keeping them
from aliasing — completing a full turn and landing two distant positions
on the same angle — within a long context. That is why long-context models
choose large bases [arxiv:2104.09864]; which trade-off Muse Glimmer’s
authors weighed for 131,072 positions is theirs, not the engine’s
[unverified].
14.4.3 The magic: the dot product sees only (m − n)
Why rotate both Q and K? Suppose Q sits at position m, K at position
n; RoPE spins pair i of Q by m·θᵢ and of K by n·θᵢ. Their dot
product over one pair, with raw values (q₀,q₁) and (k₀,k₁), expands
and — using cos(A−B) = cos A cos B + sin A sin B and
sin(A−B) = sin A cos B − cos A sin B — collapses:
q_rot · k_rot = (q₀k₀ + q₁k₁)·cos((m−n)θᵢ)
+ (q₀k₁ − q₁k₀)·sin((m−n)θᵢ)
Every m and n appears only as the difference m − n. Absolute
positions have vanished; relative position falls out for free, with zero
extra parameters and no change inside the attention kernel.
Worth restating in different words, because this is the idea the whole chapter hangs on: RoPE never tells attention where a token is. It arranges matters so that attention cannot ask anything else but how far apart two tokens are. The absolute coordinates go in, cancel against each other on the way through the dot product, and only the gap comes out.
That single identity is why RoPE is the decode-era default — and, in this book, it has a second life in §14.6: it is also the reason NoPE’s cache bytes are relocatable and RoPE’s are not.
14.5 The Metal kernel and the SWA-only dispatch
That is the theory; here is all of it in hardware. The kernel’s job is narrower than the derivation makes it sound — for this token, which floats get spun, and by how much? — and everything interesting in the listing is about answering that cheaply enough for the work to be invisible against the weight stream.
// crates/muser-engine/src/shaders/ferrite/rope.metal:624
kernel void rope_norm_batch_cached(
device float* Q [[ buffer(0) ]],
device float* K [[ buffer(1) ]],
device const float* freq_table [[ buffer(2) ]], // [half_hd]
constant uint& n_heads [[ buffer(3) ]],
constant uint& n_kv_heads [[ buffer(4) ]],
constant uint& head_dim [[ buffer(5) ]],
constant uint& start_pos [[ buffer(6) ]],
uint2 tgid [[ threadgroup_position_in_grid ]],
uint lid [[ thread_index_in_simdgroup ]])
{
const uint batch = tgid.y;
const uint pair_id = tgid.x * 32u + lid;
const uint half_hd = head_dim / 2u;
const uint total_q_pairs = n_heads * half_hd;
const uint total_pairs = total_q_pairs + n_kv_heads * half_hd;
if (pair_id >= total_pairs) return;
const uint pos = start_pos + batch;
const bool is_q = (pair_id < total_q_pairs);
const uint local = is_q ? pair_id : (pair_id - total_q_pairs);
const uint head = local / half_hd;
const uint pi = local % half_hd;
const uint q_stride = n_heads * head_dim;
const uint k_stride = n_kv_heads * head_dim;
device float* base = is_q
? (Q + batch * q_stride + head * head_dim)
: (K + batch * k_stride + head * head_dim);
float angle = float(pos) * freq_table[pi];
float cos_a = precise::cos(angle);
float sin_a = precise::sin(angle);
// NORM convention: the pair is adjacent, at 2*pi and 2*pi + 1.
const uint i0 = 2u * pi;
float v0 = base[i0];
float v1 = base[i0 + 1u];
base[i0] = v0 * cos_a - v1 * sin_a;
base[i0 + 1u] = v0 * sin_a + v1 * cos_a;
}
One thread per pair, in-place on Q and K:
- The decomposition
pair_id → (is_q, head, pi)— flat index into “all pairs of Q then all pairs of K”;head = local / 64,pi = local % 64at width 128.piis exactly theiofθᵢ. - No
pow— one multiply against the cached table. precise::cos/precise::sin— Metal’s fast-math transcendentals can be a few ULP off;precise::selects the higher-accuracy variant for these calls only, without giving up fast-math elsewhere in the library (Ch 4). At large bases and large positions the anglepos·θᵢis itself large and its sine/cosine must be exact to the bit the comparator computed — accuracy, not speed, is the reason (the ancestor book carries the same lesson[ferrite-book Ch 4, Ch 13]).- The interleaved addressing
i0 = 2·pi— the NORM convention of §14.4.1, two adjacent floats rotated as one 2D vector, written straight back.
The dispatch (metal/encode/rope.rs:139-151) launches
ceil(total_pairs/32) × batch threadgroups of 32 — one thread per pair,
and with total_pairs = (32 + 2) × 64 = 2,176 for Muse Glimmer that comes
to 68 threadgroups of 32 threads, once per sliding layer, per token.
The wrapper has more to decide than a grid size, though. The same rotation
exists three times in the tree, and the copies are not interchangeable —
same math, different provenance for the bits, and provenance is precisely
the kind of difference the convention warning above says never surfaces
in the output. When the pinned metallib is loaded, the wrapper prefers
llama’s own kernel_rope_norm_f32 with a packed GgmlMetalKargsRope:
same convention, llama’s own arithmetic, no daylight between the engine
and the comparator it is scored against (rope.rs:88-138). Without that
metallib, the ferrite kernel quoted above is the fallback. Under the
cross-vendor flags the choice changes once more, to the no-fast-math NCO
table route with explicit per-token positions (rope.rs:62-86) — the
seam Ch 32 needs when the tensors
on the other side of the handoff were produced by somebody else’s
hardware.
The dispatch condition — SWA only
Which layers actually pay for all this? Not most of them — and the whole position apparatus hangs off a single predicate in the token graph:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/decode.rs:5621
if cfg.layer_kinds[layer_index].uses_rope() {
dispatch(command, |encoder| {
self.kernels.encode_rope_norm_batch_cached(
encoder,
&self.activations.q,
&self.activations.k,
&self.rope_frequencies,
cfg.n_heads,
cfg.n_kv_heads,
cfg.head_dim,
position,
1,
// …(positions view + freq_base + n_ctx_orig elided)…
);
});
}
}
and uses_rope() is one line, with provenance to the comparator:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/config.rs:66
/// RoPE runs iff the layer is a sliding layer (`muse-glimmer.cpp:93`:
/// `const bool use_rope = hparams.is_swa(il);`).
pub const fn uses_rope(self) -> bool {
matches!(self, Self::SlidingRope)
}
}
On layers {3, 7, …, 51} no rotation dispatch exists at all. Q and K
flow from QK-norm straight to the KV store, positionless by design. The
layer-kind partition itself is fail-closed: the loader panics rather than
guess if the sliding_window_pattern key is missing, because “an
all-full model runs and emits plausible text while being wrong”
(config.rs:378-380). Notice what kind of defense that is. The loader is
not saving itself from a crash — it is saving us from the absence of
one, which is the same reason the QK-norm probe earlier in this chapter
exists. Two different keys, two different tensors, one shared conviction:
on this engine a missing assumption must stop the process, because the
model will never complain on its own.
14.6 NoPE and relocatable KV — the consequence
Here is why the two-class asymmetry is the most consequential layout
decision in the engine. A cached K row for a sliding layer is a function
of its absolute position: the rotation angle pos·θᵢ is baked into the
bytes. Move that row to a different position and the (m−n) identity of
§14.4.3 breaks — the stored rotation is the wrong one. A cached K row for
a NoPE layer is just a projection of the token’s hidden state: it
carries no positional information whatsoever, so the same bytes are valid
at any position. Two facts follow, both load-bearing for Part V and VI:
- NoPE tiles relocate by
memcpy. A 512-token NoPE KV tile can be planted anywhere in any cache of the same identity — prefix reuse, warm sessions, remote handoff — with no recompute and no re-rotation. The engine’s own summary: the 13 NoPE layers are “position-free (relocate = memcpy) — the whole kvpack free lunch” (lib.rs:9-10), and kvpack’s layout keys enforce it fail-closed (the NoPE identity requirestheta = 0; a RoPE layer cannot claim it —crates/muser-kvpack/src/layout.rs, K1 keys[crates/muser-engine/src/lib.rs:7-15]). - SWA planes are position-bound but bounded. The 39 sliding layers only ever need the last 2,048 tokens (§14.7 previews the ring), so their “where” question is a window offset, not an absolute position — that is Ch 15’s whole subject.
When Ch 24 and Ch 26 move KV tiles around the lab, this section is the reason NoPE bytes move freely and SWA bytes move as logical tails with explicit origins.
14.7 Tradeoffs
Four decisions in this chapter could plausibly have gone the other way, and they share a family resemblance: in every one of them the wrong branch still runs, still returns floats, and still reads like language. That is what makes them worth walking rather than tabulating.
Interleaved vs half-split — a convention you must match, not choose.
Same rotation math, different pairing (Figure 14.2). The checkpoint was
trained with one; the GGUF converter un-permuted Q/K “precisely so the
interleaved form is the correct one for the stored weights”
(rope.metal:620-622), and llama.cpp’s rope_norm matches. Mixing
conventions is the classic silent failure — fluent text, wrong positions
(rope.metal:614-617) — and the defense is structural: separate kernels
(rope_norm_batch_cached vs rope_batch_cached, rope.metal:566) rather
than a runtime flag, so no configuration can accidentally cross-wire the
two.
head vector: x0 x1 x2 x3 │ x4 x5 x6 x7 (head_dim = 8)
│
INTERLEAVED (NORM / Muse): │ HALF-SPLIT (NEOX / Llama-family):
pair 0 : x0 ── x1 │ pair 0 : x0 ─────────── x4
pair 1 : x2 ── x3 │ pair 1 : x1 ─────────── x5
pair 2 : x4 ── x5 │ pair 2 : x2 ─────────── x6
pair 3 : x6 ── x7 │ pair 3 : x3 ─────────── x7
Figure 14.2: the two RoPE pairings. Same frequency table, same rotation,
different addressing. Match the checkpoint or corrupt position silently
(rope.metal:610-618).
Rotate-before-store, and rotate Q and K only. RoPE runs before the KV store so the cache holds already-rotated keys — attention then needs no position argument beyond the mask, and the store kernel of Ch 15 is a pure copy. V is never rotated (position lives in the Q·K score, not in the payload), which is why V’s plane is pure payload bytes on both layer classes.
precise:: trig vs fast-math trig. The engine compiles its main
library with fast-math on for speed (Ch 4)
but pays for exact trig here, per call. The cost is a handful of cycles on
2,176 threads × 39 layers — nanoseconds — and the benefit is that
sin(pos·θ) matches llama’s own evaluation of the same angle to the bit.
There is no measured A/B in the ledger [unverified]; the choice is
contract-driven, like most precision decisions in this book.
The QK-norm seam across the handoff. The producer side (vLLM on the
GB10) materializes its weightless QK-norm in F16 and applies the scale as
a second F16 operation; a fused RMS+weight kernel “loses that intermediate
rounding point and is therefore not seam-exact”
(metal/encode/norm.rs:280-284). Under MUSER_CROSS_VENDOR_QK the
QK-norm therefore splits into two strict-f32 dispatches with a barrier
between — slower, deliberately, to preserve the producer’s rounding. One
more example of the book’s recurring rule: exactness is always exactness
against a specific anchor.
14.8 Where the gap lives
Every kernel chapter owes the same accounting answer: does this work show up in the decode gap, or does it disappear into the noise of the layers around it? Here the arithmetic settles it quickly.
This kernel is not the gap. Bytes per token: Q 4,096×4 + K 256×4 read
and written in place, ≈ 17.4 KB in and the same out, ×39 sliding layers ≈
1.4 MB of traffic — six orders of magnitude under the weight stream of
Ch 13. In the closure accounting of
[docs/decode-dispatch-gap-20260815.md], the qk_norm and rope labels
sit in the common-math row (delta 0). The chapter’s gap-adjacent fact is
the one already banked in Ch 12:
the norm-boundary families (+104) are where fusions go to die on
exactness, and QK-norm’s dispatch shape is one of the boundaries that
inherits that discipline.
14.9 What comes next
Q is rotated (on sliding layers), K likewise, V untouched — and the current token’s K and V must now be written into the cache that attention will read. One engine, two storage regimes: a 2,048-slot ring for the 39 sliding layers, a growing head-major plane for the 13 NoPE layers. That is Ch 15.
References
crates/muser-engine/src/shaders/ferrite/rope.metal:610-667— the NORM- convention comment block andrope_norm_batch_cached(quoted).crates/muser-engine/src/shaders/ferrite/rope.metal:566-608—rope_batch_cached, the NEOX sibling kept deliberately separate.crates/muser-engine/src/metal/encode/rope.rs:45-152— the dispatch: cross-vendor NCO route, pinnedkernel_rope_norm_f32route, and the ferrite fallback.crates/muser-engine/src/decode.rs:5599-5641— QK-norm and the SWA-gated RoPE call sites.crates/muser-engine/src/decode.rs:1217-1270— the frequency-table build (and the retained RoPE-cache file check).crates/muser-engine/src/config.rs:51-71—MuseLayerKind,uses_rope()and themuse-glimmer.cpp:93provenance.crates/muser-engine/src/config.rs:84-102, 336-381— theil % 4 == 3partition and the fail-closed pattern resolution.crates/muser-engine/src/config.rs:139-141, 277-281, 383-403, 426-430—qk_scale_factor,attn_scale,QkNormProbe, and the scale test.crates/muser-engine/src/metal/encode/norm.rs:280-303—encode_qk_normand the vLLM F16 seam comment.crates/muser-engine/src/shaders/ferrite/rms_norm_per_head.metal:15-47— the per-head tree-reduction kernel (algorithm reference).crates/muser-engine/src/lib.rs:7-15— the position-free-NoPE / kvpack free-lunch summary.crates/muser-engine/src/loader.rs:28-37— the fail-closed QK-norm probe at load.- Ch 12 — the RMSNorm machinery QK-norm reuses.
- Ch 15, Ch 24, Ch 26 — where the NoPE/SWA asymmetry pays off.
[arxiv:2104.09864]— Su et al., RoFormer (the RoPE origin paper).[arxiv:1910.07467]— RMSNorm (for §14.3’s normalization).[ferrite-book Ch 13]— the ancestor’s RoPE chapter (permutation- invariance motivation, (m−n) proof, convention warning — ported with Muse’s NORM convention and dual-class twist).
Chapter 15 — KV store and the ring
status: polished · path: Muse Glimmer, pinned Muser tree
Prerequisites: Ch 9 (the 39/13 layer split), Ch 14 (rotated K, unrotated V, and why NoPE bytes are position-free), Ch 13 (the K/V projections that produce this chapter’s inputs). This is an infrastructure chapter: the data structure every attention dispatch of Ch 16 reads.
15.1 What the KV store computes
Chapter 14 ended with Q rotated (on the sliding layers), K likewise, V untouched — and the current token’s K and V waiting to be written into the store attention will read. This chapter is that write.
Before any code, the question this chapter answers: where does a long session’s memory actually live, and what does keeping it cost? The answer starts one step earlier, with why a store has to exist at all.
To produce a token, attention must score the current query against the
Key of every visible past token and take a weighted sum of their
Values (Ch 16 does the math from
zero). The Key and Value of past token i depend only on token i — they
are projections of that token’s hidden state (Ch 13)
— and once computed they never change. So every engine faces the same
choice: recompute all past K/V every step (O(n²) work over a generation —
token 100,000 recompute 99,999 keys it already computed) or compute each
once, store it, reuse it forever. The store is the
KV cache.
This chapter is about the write side: the per-layer GPU buffers, the two storage regimes Muse Glimmer’s architecture forces, the kernels that copy this token’s K and V into them, and the ring arithmetic that decides where the write lands. The read side is Ch 16; the portable-asset side is Part V.
The recurrence that motivates it all is per layer class. For the 13
NoPE layers, “every past token” grows without bound — the cache genuinely
grows with context. For the 39 sliding layers, attention only ever sees
the last sliding_window = 2,048 tokens (config.rs:16, 131), so the
recompute-if-you-don’t-cache body is bounded at 2,048 keys — small enough
that a fixed-size ring whose capacity never grows is the natural store.
One model, two memory regimes, decided by layer % 4 == 3
(config.rs:84-93).
15.2 The planes in code — MetalKvPlane
Start with the object, because everything the ring does later is bookkeeping held in its fields — and it is worth knowing up front what a wrong field costs. A plane that loses track of which row is its oldest does not crash; it hands attention some other token’s key and keeps going. The per-layer cache object is seven fields:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/decode.rs:182
struct MetalKvPlane {
key: GpuHalfBuffer,
value: GpuHalfBuffer,
capacity: usize,
len: usize,
origin_logical: usize,
origin_physical: usize,
head_major: bool,
}
}
key/value— two f16 buffers; f16 on every Metal lane (theGpuHalfBuffertype is the constraint). F32 planes exist only in the kvpack interchange, never in live decode.capacity/len— the plane holds at mostcapacitytoken rows;lenare currently live.origin_logical/origin_physical— the ring’s explicit bookkeeping: which logical token position sits at the front of the live window, and which physical slot it occupies. Said the other way round, the plane never asks “where does token n belong?”; it asks “how far has my window slid since I last knew where its front was?”head_major— which of the two layouts this plane uses (§15.3).
Those two origin fields are the whole difference between this ring and
the obvious alternative, addressing straight by “position mod capacity”,
and we did not choose the long way round for elegance. The ancestor
engine took the obvious route and it bit: the extraction manifest records
the naive form as a named out-of-bounds hazard — “Ferrite indexed by
absolute position; the ring modulus was unwired/stubbed” — which is why
the SWA ring-address translation is Muser-owned rather than inherited
(docs/extraction-manifest.md). Carrying the origins explicitly costs
two integers per plane and deletes that whole class of bug.
Allocation is per layer kind, and it is where the two regimes become concrete:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/decode.rs:1344
let mut cache = Vec::with_capacity(cfg.n_layers);
for layer in 0..cfg.n_layers {
let capacity = match cfg.layer_kinds[layer] {
MuseLayerKind::SlidingRope => max_context.min(cfg.sliding_window).max(32),
MuseLayerKind::FullNoPe => max_context.max(32),
};
// Zero-filled on purpose: wrapped SWA rows must never expose
// uninitialized storage during a sequence boundary transition.
cache.push(MetalKvPlane::new(
&shared.context,
capacity,
cfg.kv_dim(),
matches!(cfg.layer_kinds[layer], MuseLayerKind::FullNoPe),
)?);
}
}
A sliding layer gets min(max_context, 2,048) rows, token-major; a
NoPE layer gets max_context rows, head-major.
The zero-fill comment sitting in that loop is worth a detour, because the
reason it matters for anyone reading Muser’s docs beside this book is
that we had the fact backwards in writing before we had it right. An
engineering doc stated that Metal KV buffers were allocated without a CPU
memset. It is a plausible claim — a buffer that is about to be overwritten
row by row looks like a pure waste of a memset — and it stood long enough
to be quoted. The audit that went checking found the opposite for live
planes: they zero-fill on purpose, so that a wrapped SWA row can never
expose uninitialized storage while a sequence boundary is in flight. Only
detached remote-install generations take the uninitialized path. The
correction is retained, and it is the corrected fact that should be cited,
never the stale line:
[docs/kvpack-merge-handoff §3 D2, per the 2026-08-20 audit].
15.3 The two layouts
Why should one model carry two layouts at all? Because a layout is never chosen for the writer’s convenience. It is chosen for whoever reads it most often — and the two layer classes have different readers, so they get different shapes.
kv_dim = n_kv_heads × head_dim = 2 × 128 = 256 halves per token row.
The two planes lay those halves out differently:
TOKEN-MAJOR (SWA ring), capacity C = 2,048:
[token 0 ][token 1 ]…[token C−1 ] each row = 256 halves
k0h0 k0h1 k1h0 k1h1 (h0 = KV head 0's 128,
h1 = KV head 1's 128)
index: physical × kv_dim + element
HEAD-MAJOR (NoPE growing), capacity C = max_context:
KV head 0: [tok 0][tok 1]…[tok C−1] 128 halves per row
KV head 1: [tok 0][tok 1]…[tok C−1]
index: (kv_head × capacity + physical) × head_dim + dim
Figure 15.1: the two KV layouts. Token-major keeps one token’s two head
rows adjacent — a ring rotation moves whole tokens. Head-major keeps each
KV head’s sequence contiguous — a head’s whole history is one linear span,
which is exactly the shape llama.cpp’s flash-attn vec kernel wants for its
head-major ns10 = 128 addressing (metal/encode/attn.rs:503-511). The
index formulas are the store kernels’ own (muse_reference.metal:1224,
:1228); why each layout pairs with its layer class is §15.6’s tradeoff.
15.4 The store kernels
With the shapes settled, the write itself is an anticlimax, and that is the design. The useful question here is not “how does the store work?” but “how little is the store allowed to know?” — because every fact the GPU kernel is told about token positions is a fact that can be wrong on the GPU, where nothing checks it.
Two kernels write K/V; both are pure quantize-free copies — the values are already f32 in the activation buffers and f16 in the planes.
Single-token, token-major — the SWA ring’s steady-state write:
// crates/muser-engine/src/shaders/muse_reference.metal:979
kernel void muser_kv_store_f16(
device const float *key [[buffer(0)]],
device const float *value [[buffer(1)]],
device half *key_cache [[buffer(2)]],
device half *value_cache [[buffer(3)]],
constant uint &kv_dim [[buffer(4)]],
constant uint &write_index [[buffer(5)]],
uint index [[thread_position_in_grid]]) {
if (index < kv_dim) {
uint destination = write_index * kv_dim + index;
key_cache[destination] = key[index];
value_cache[destination] = value[index];
}
}
One thread per element; 256 threads; the row’s position (write_index)
arrives pre-computed from the ring arithmetic of §15.5 — the kernel never
sees an absolute token position. The f32→half conversion is implicit in
the assignment. The wrapper binds buffers and dispatches
dispatch_1d(key.len()) (metal/encode/attn.rs:635-655).
Batched, both layouts — the NoPE planes and prefill chunks:
// crates/muser-engine/src/shaders/muse_reference.metal:1203
kernel void muser_kv_store_batch_f16(
device const float *key [[buffer(0)]],
device const float *value [[buffer(1)]],
device half *key_cache [[buffer(2)]],
device half *value_cache [[buffer(3)]],
constant uint &kv_dim [[buffer(4)]],
constant uint &source_first [[buffer(5)]],
constant uint &source_count [[buffer(6)]],
constant uint &start_position [[buffer(7)]],
constant uint &capacity [[buffer(8)]],
constant uint &origin_logical [[buffer(9)]],
constant uint &origin_physical [[buffer(10)]],
constant uint &head_dim [[buffer(11)]],
constant uint &head_major [[buffer(12)]],
uint index [[thread_position_in_grid]]) {
uint total = source_count * kv_dim;
if (index < total) {
uint source_token = source_first + index / kv_dim;
uint element = index % kv_dim;
uint logical = start_position + source_token;
uint physical = (origin_physical + logical - origin_logical) % capacity;
uint destination = physical * kv_dim + element;
if (head_major != 0u) {
uint kv_head = element / head_dim;
uint dim = element % head_dim;
destination = (kv_head * capacity + physical) * head_dim + dim;
}
uint source = source_token * kv_dim + element;
key_cache[destination] = key[source];
value_cache[destination] = value[source];
}
}
The layout switch is the if (head_major) block: token-major lands at
physical × kv_dim + element; head-major re-interleaves to
(kv_head × capacity + physical) × head_dim + dim — Figure 15.1’s two
index formulas, one kernel. Note that this kernel does see absolute
positions (start_position, origin_logical) but only inside the
explicit-origin translation physical = (origin_physical + logical − origin_logical) % capacity — never as logical % capacity.
15.5 Ring write-position arithmetic — append
Everything so far has deferred one question: which row does this token’s
K and V land in? The stake is unusually quiet. Get the row wrong and
nothing crashes — attention simply scores the query against some other
token’s key, the logits shift, and the only symptom is generated text
that is subtly worse than it should be, at a rate no test that checks for
crashes will ever catch. So the arithmetic that answers the question is
small, explicit, and refuses to guess. The CPU-side reservation that
decides write_index is eleven lines and fail-closed:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/decode.rs:263
/// Reserve the physical row for `position` and advance explicit ring
/// metadata. No physical placement is derived from the absolute token ID.
fn append(&mut self, layer: usize, position: usize) -> Result<usize, MetalModelError> {
let expected = self.origin_logical + self.len;
if position != expected {
return Err(MetalModelError::CacheDiscontinuity {
layer,
expected,
got: position,
});
}
if self.len < self.capacity {
let write = (self.origin_physical + self.len) % self.capacity;
self.len += 1;
Ok(write)
} else {
let write = self.origin_physical;
self.origin_logical += 1;
self.origin_physical = (self.origin_physical + 1) % self.capacity;
Ok(write)
}
}
}
Walk it at the ring boundary, capacity 2,048:
positions 0..2047 (fill): write = origin_physical + len, len → 2048
origin_logical = 0, origin_physical = 0
position 2048 (first wrap):
len == capacity → write = origin_physical = 0 (overwrite slot 0)
origin_logical → 1 (the window now starts at token 1)
origin_physical → 1 (slot 1 is the oldest live row)
position 2049: write = 1; origins → (2, 2). And so on, forever.
Figure 15.2: the ring wrap. The write pointer, the logical origin, and the physical origin advance together; the plane’s physical layout is a rotation of the logical tail.
Three properties fall out of the wrap arithmetic of Figure 15.2:
- Fail-closed continuity.
position != origin_logical + lenis an error, not a modulo — a skipped or replayed position can never silently alias a live row (decode.rs:117-121defines the error; prefill’s module doc states the invariant: “physical placement is never derived from absolute position”,prefill.rs:15-17). - NoPE degenerates to append-only. A NoPE plane’s capacity is
max_context;len < capacityalways holds in a valid session, so theelsebranch never runs,origin_logicalstays 0, and the modulo vanishes — the “ring” is a growing array. append_batch(decode.rs:286-314) is the chunked form: it advances the origins by the overflow when a chunk crosses capacity and hands back which source rows are still live — the arithmetic behind prefill’s ring wrap.
The write_physical this returns is exactly the write_index the store
kernel of §15.4 receives (decode.rs:5643, 5661-5667).
15.6 Why restore must preserve rotation
The plane’s rotation (where origin_physical points) looks like
implementation detail. It is not — it is numerics. Attention scans rows
in physical order and floating-point accumulation is order-sensitive, so
two planes holding identical rows in different rotations produce
different last-bit logits.
Put it plainly, because this is the idea in the chapter most worth re-reading: the same keys, stored starting at a different slot, are summed in a different order, and a different order of floating-point additions is a different number. The rotation is not where the data happens to sit. The rotation is part of the data. The restore path documents exactly this, with a test name in the comment:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/decode.rs:376
// Install at the rotation a sequentially-built live ring holds at this
// logical origin. Attention scans rows in physical order and float
// accumulation is order-sensitive, so a restore packed at origin 0
// can never replay a wrapped live session's logits bitwise (caught by
// real_model_wrap_boundaries_and_detached_restore_replay_exactly).
// NoPE planes never wrap (origin_logical is always 0), so their
// rotation is 0 and this reduces to the previous layout.
let rotation = snapshot.origin_logical % snapshot.capacity;
}
detached_from then scatters the snapshot’s logical rows back at
(rotation + logical_offset) % capacity (decode.rs:384-407) — head-major
per KV head, token-major as a split head/tail copy. Without this, a saved
session restored on another process would produce bit-different logits
from the same bytes, and the exactness gates of
Ch 38 (and kvpack’s bit-identical
warm hits, Ch 25) would fail for a reason no amount of
weight-side care could fix. Bitwise replay requires the rotation to
travel with the cache.
15.7 Footprint per token, derived by hand
Now the bill. Two questions a reader actually has — what does a session cost in memory, and where does that cost concentrate — are answerable with nothing but multiplication, so we would rather you re-derive them than take our word.
One K row per layer = n_kv_heads × head_dim × 2 B = 2 × 128 × 2 = 512 B;
K and V together = 1,024 B per layer per token — topology-derived
arithmetic, not a measured RSS [docs/memory-footprint.md §KV formula].
Derive the three numbers that matter:
per token, whole model: 52 layers × 1,024 B = 53,248 B ≈ 52 KiB
SWA steady state (bounded): 39 × 2,048 × 1,024 = 81,788,928 B ≈ 78 MiB
— constant, whatever the context depth
NoPE at max context: 13 × 131,072 × 1,024 = 1,744,830,464 B ≈ 1.66 GiB
— grows linearly, unbounded to the 131,072 model limit
one slot at 131,072: 81,788,928 + 1,744,830,464 = 1,826,619,392 B
≈ 1.827 GB (decimal)
which is the memory-footprint table’s one-slot figure (1.827 GB; four
slots 7.306 GB) [docs/memory-footprint.md, via the measured-numbers ledger §1k]. Note the asymmetry: at full depth, ~96 % of the KV footprint
is the 13 NoPE layers. The same split shows up on the wire: the deep
130,815-token handoff’s 1,823,184,896 B payload decomposes as
130,814 × 13,312 B of NoPE (13 layers × 1,024 B per token) plus three
pipe-safe SWA groups of 2,048 × 13,312 B — i.e. ≈95.5 % NoPE / ≈4.5 %
SWA by these terms [receipt phase4-disagg-20260820/130815-g900091/; docs/kvpack-merge-handoff §6]. Ch 22 owns
the full footprint treatment (2k/32k/131k tables, slot arithmetic); keep
this chapter’s derivation as the per-layer-class split it is.
15.8 Tradeoffs
The store could have been built differently at four points — how the cells are compared, how the planes are laid out, what precision they hold, and whether the store is its own dispatch at all. Each alternative deserves a price rather than a dismissal. Start with the frame the rest of them are argued in.
A 2×2 that splits the confounds. The ancestor book’s sharpest KV
device was a 2×2 layout × precision matrix whose ratios multiplied out to
the combined effect, exposing that a flag named “addressing” was mostly a
precision swap [ferrite-book Ch 14]. Muser’s KV matrix has the same
shape with different axes — layout (token-major vs head-major) × layer
class (SWA-RoPE vs NoPE) — and the decomposition that matters is the
payload split of §15.7: at depth, the NoPE cells carry ≈95.5 % of the
bytes and the SWA rings ≈4.5 %.
That split, not any kernel choice, is what shaped the disaggregated
lane’s send schedule: stream the “SWA groups (~82 MB) early” and hold
“the NoPE bulk (95.7 % of payload)” back until prefill finishes layer 51.
The reasoning felt free — the small planes are ready first, so send them
first and buy overlap for nothing. On the wire it was not free. A
burst-then-wait shape is exactly the traffic pattern that lets an
EEE-capable link drop into an idle state between bursts, and the pacing
section of the merge handoff is where that collision is written down
([docs/kvpack-merge-handoff §6 "Pacing reality"]);
Ch 31 tells the story properly. The lesson
outlives the wire: the payload split is an input to schedules made three
layers of abstraction away, so when you change one cell of this 2×2,
re-derive the split before predicting anything downstream of it.
Ring + growing plane vs one layout, vs paging. Why not one layout for
all 52 layers? A token-major growing NoPE plane would make each head’s
history strided, defeating the head-major ns10=128 addressing of the
pinned attention kernel; a head-major ring would rotate each head
independently — same rotation, but the store/restore walk gets no
contiguous tail and the llama vec path cannot read a wrapped ring as one
span. The hybrid pairs each class with the layout its reader wants. Why
not paging (16-token blocks behind a block table), the ancestor’s
design? Because Muse Glimmer’s SWA layers bound themselves — a full ring
is the whole live set, nothing to evict — and the NoPE layers’ prefix
sharing is handled one level up, by kvpack’s content-addressed chunks
(Ch 24), not by an in-GPU page table. The
OS-paging analogy survives only as contrast [ferrite-book Ch 14].
f16, not quantized, not f32. The planes are f16 on every Metal lane
(the GpuHalfBuffer field type, decode.rs:183-184). The ancestor
quantized its cache to Q8_0 and measured an 80/15 precision/addressing
split when it stopped [ferrite-book Ch 14] — Ferrite-lineage context,
not a Muser decision point: Muser’s parity anchor (pinned llama.cpp) runs
F16 KV, so Muser does too, and the lane table’s decode rows all say
“FP16 KV” [docs/muser-architecture.md]. The kvpack interchange still
carries both encodings (PlaneEncoding::{F16Le, F32Le},
crates/muser-engine/src/cache.rs:11-16) because the wire format serves
producers and archives beyond the live planes. A quantized live cache
would halve §15.7’s numbers and is exactly the kind of change the
exactness contract makes a research lane, not a default.
Store-then-barrier vs fused store-in-attention. Muser’s decode stores
K/V in its own dispatch and puts an explicit
memory_barrier_with_resources between store and attention
(decode.rs:5669-5670) when the pinned vec kernel follows. The ancestor
fused the quantizing store into its attention kernel to save a dispatch
[ferrite-book Ch 15]. Muser’s store is a 512-byte-per-plane copy with
nothing to amortize, and the split keeps the pinned llama kernels
untouched — the same pin-the-boundary reasoning as
Ch 13. The ferrite interleaved
fallback kernel does fuse its store (flash_attn_decode_vec_contiguous_ f16.metal:534-543) — on that route only, one simdgroup writes the current
K/V as a side effect; see Ch 16.
15.9 Where the gap lives
The book’s running question about the decode gap is which dispatches are waste and which are structure. This chapter is where that question gets its least comfortable answer, twice.
Two of the four +196 families live here. 52 KV-publication splits
(production’s separate kv_store + attention closures vs legacy’s
combined kv_store_attention) are classed “session/publication structure
— Keep; combining closures alone does not remove their kernel math”
[docs/decode-dispatch-gap-20260815.md §Corrected closure-count diff].
And 39 SWA wrapped-ring staging groups are the batch graph’s
multi-row ring feature — the staging shadow of
Ch 36 — kept “until a bit-exact
ring-aware replacement exists.” Neither is waste in the ordinary sense.
Both are dispatches that exist because removing them would change a
number the exactness contract does not allow to change — which is a
harder thing to argue away than a merely inefficient loop. The
note’s own ranked list still hopes for “a one-row ring-aware attention
path that avoids SWA staging, gated by bitwise KV and full-logit equality
at positions 1, 31, 32, 33, 2,047, 2,048, and 2,049.” This chapter is
where those two rows of the table become concrete: the splits are the
store dispatches you just read; the staging groups are what wrapping a
2,048-ring under a 512-token prefill chunk costs.
15.10 What comes next
The planes hold every visible token’s K and V — a ring for the sliding layers, a growing span for the full ones — and the current token’s Q is rotated and QK-normalized. Everything attention needs is in place. There is no single “attention kernel” to read next: there is a route ladder, selected per layer per token by alignment predicates. That ladder — and the Q·Kᵀ softmax V math it implements — is Ch 16.
References
crates/muser-engine/src/decode.rs:182-190—MetalKvPlane.crates/muser-engine/src/decode.rs:228-261— zero-filled and uninitialized constructors.crates/muser-engine/src/decode.rs:263-314—append/append_batch(the ring arithmetic; quoted).crates/muser-engine/src/decode.rs:374-417—detached_fromand the rotation-preserving restore (quoted).crates/muser-engine/src/decode.rs:1344-1358— per-layer-kind capacity and layout allocation (quoted).crates/muser-engine/src/decode.rs:5643-5745— the store + route call sites inencode_token;:117-121CacheDiscontinuity.crates/muser-engine/src/shaders/muse_reference.metal:979-992—muser_kv_store_f16(quoted).crates/muser-engine/src/shaders/muse_reference.metal:1203-1234—muser_kv_store_batch_f16, both layouts (quoted).crates/muser-engine/src/metal/encode/attn.rs:635-655, 786-827— the two store wrappers.crates/muser-engine/src/prefill.rs:15-17— the no-placement-from-absolute-position invariant.crates/muser-engine/src/cache.rs:11-16—PlaneEncoding(interchange encodings).crates/muser-engine/src/config.rs:13-21, 84-93— layer counts, window, the partition rule.crates/muser-engine/src/lib.rs:7-15— the position-free-NoPE summary.[docs/memory-footprint.md]— the 1,024 B/row formula and the 1.827/7.306 GB slot table (§15.7’s tags).[receipt phase4-disagg-20260820/130815-g900091/]— the 1,823,184,896 B deep payload (payload_bytesverified).[docs/kvpack-merge-handoff §6]— the ~82 MB SWA / 95.7 % NoPE pacing decomposition.[docs/decode-dispatch-gap-20260815.md]— the 52 publication splits and 39 SWA staging rows (§15.9).- Ch 16 — the read side.
- Ch 22, Ch 23, Ch 24 — footprint, server policy, the portable format.
[ferrite-book Ch 14]— the ancestor’s paged-Q8 cache (the 2×2 device and the paging contrast, ported as contrast only).
Chapter 16 — Attention: the decode kernel ladder
status: polished · path: Muse Glimmer, pinned Muser tree
Prerequisites: Ch 2 (SIMD groups,
simd_sum, barriers), Ch 9 (GQA 32:2, the two layer classes), Ch 14 (rotated Q/K), Ch 15 (the ring, the growing plane, and the explicit-origin arithmetic this chapter’s kernels index with). Attention math is built from zero here; no transformer background is assumed.
16.0 First: which kernel actually runs — the route ladder
The previous chapter left the keys and values sitting in the cache: appended, addressable through explicit origins, and so far unread. This chapter is where something finally reads them back. The natural way to write it would be to open the engine, find the attention kernel, and walk you through it line by line.
That is the one thing we cannot honestly do. There is no single attention
kernel on Muser’s decode path. There are four routes, selected per
layer, per token by predicates computed after the ring append of
Ch 15. Presenting any one of them as the
decode attention kernel would be this book’s fastest way to lie to you,
and it is a lie with precedent: the ancestor book made exactly that
mistake once and had to correct it in public [ferrite-book Ch 15]. So
we start where the engine starts — with the code that chooses. Here is
the selection, verbatim, from the middle of encode_token:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/decode.rs:5643
let write_physical = self.cache[layer_index].append(layer_index, position)?;
let plane = &self.cache[layer_index];
let strict_attention = std::env::var_os("MUSER_CROSS_VENDOR_QK").is_some();
let llama_vec_rows = (strict_attention || self.kernels.has_llama_flash_attn_vec())
&& plane.len > 0
// The pinned vec kernel rounds KV reads to a 32-row block.
// A deliberately tiny raw session can have a smaller backing
// allocation, so taking the vec path would read past it and
// poison the full distribution with NaNs.
&& plane.capacity >= 32
&& (plane.origin_physical == 0 || plane.len == plane.capacity);
// Token-major SWA cannot use llama's pad kernel (nb11 is a full
// token row, not one head). Only take that path when the window
// is a multiple of 32 so the vec kernel never pads.
let llama_swa = llama_vec_rows && plane.len.is_multiple_of(32);
}
And the four routes those predicates pick between:
layer class vec-eligible? route
────────────── ───────────────────── ─────────────────────────────────────────────
SWA (39) llama_swa kv_store_f16 → barrier → llama vec (pinned)
SWA (39) not llama_swa kv_store_f16 → splitk producer → splitk reduce
NoPE (13) llama_vec_rows kv_store_batch_f16 → barrier → llama vec (pinned)
NoPE (13) not llama_vec_rows ferrite interleaved producer → reduce_v2
Figure 16.0: the decode-attention route ladder. “Pinned” = a kernel from the llama.cpp metallib of Ch 4; the others are Muser-owned (splitk) or ferrite-lineage (interleaved). Decode the predicates, because each clause is a lesson:
has_llama_flash_attn_vec()— is the pinned metallib loaded? No library, no vec route; the ladder falls to Muser’s own kernels.plane.len > 0— attention needs at least one visible row.plane.capacity >= 32— the pinned vec kernel rounds its KV reads to a 32-row block; a tiny diagnostic session with a smaller backing allocation would be read past its end, and the comment names the failure: “poison the full distribution with NaNs” (decode.rs:5648-5651). A fail-closed predicate against a silent OOB.origin_physical == 0 || len == capacity— the vec kernel reads a contiguous span of cache rows. An unwrapped plane (origin_physical == 0) is contiguous by construction; a full wrapped ring is also usable, because every slot is in-window and softmax is permutation-invariant (metal/encode/attn.rs:431-435). A partially-wrapped ring is not contiguous, so it never takes the vec path.llama_swa = … && plane.len % 32 == 0— one more SWA-only clause: the token-major SWA plane’s row is a full token row (nb11is 256 halves, not one head’s 128), so llama’s padding kernel cannot patch a ragged 32-row block; the vec path is taken only when the window length never pads (decode.rs:5654-5657).
One subtlety about that last-but-one clause, before we trust it too far:
reading a full wrapped ring through the vec kernel is mathematically
valid, but it is not bit-identical to llama’s own SWA addressing — which
is why the serving batch graph, where bit-parity is the contract, stages
wrapped SWA rows into llama’s absolute, 256-row-padded indices first, “so
the pinned vec kernel sees the same reduction lanes rather than a
mathematically equivalent compact permutation”
(metal/encode/attn.rs:140-143). The teacher-forced graph this chapter
narrates reads the ring directly. Two graphs, two answers to the same
question — the recurring Part IV pattern.
It is worth flagging that distinction now, before a single kernel: the gap between the same answer and the same floating-point answer is the hinge every route decision in this chapter turns on. Keep it in view; it comes back as the sharpest tradeoff at the end.
16.1 What attention computes — from zero
We have just watched four routes argue with each other without saying what any of them computes. They all compute the same thing, so it is worth building that thing from nothing before we look at a kernel again.
Everything else in the transformer operates on one token’s vector.
Attention is the one operation that reaches
sideways: while generating token t, it is the moment token t may look
at tokens 0..t and mix information out of them. The folklore naming is
information retrieval: the Query (Q) is what this token seeks; each
past token’s Key (K) is what it offers for matching; its Value (V)
is the payload returned on a match.
Step 1 — the score is a dot product. For query head h, current query
Q_h ∈ ℝ¹²⁸, and the key of past token t in the same head’s group
K_t[h] ∈ ℝ¹²⁸:
score(t) = Q_h · K_t[h] = Σ_{d=0..127} Q_h[d] · K_t[h][d]
Step 2 — scale by 1/√head_dim. A sum of 128 random products has
standard deviation growing like √128; unscaled, it would push
softmax into saturation where one token takes all
the weight. Muse Glimmer’s scale is 1/√128 ≈ 0.0883883
(config.rs:277-281; the companion test at config.rs:426-430 also
checks it against the folded QK-norm factor). Note this is in addition
to the qk_scale_factor ≈ 3.87 baked into the Q-norm weights
(Ch 14) — two scales, two places, asserted
apart.
Step 3 — softmax over tokens. With scaled scores s_t, the attention
weights are a probability distribution:
w(t) = exp(s_t) / Σ_{t'} exp(s_{t'})
Step 4 — weighted sum of Values. The head’s output is
out_h = Σ_t w(t) · V_t[h]. All weight on token 3 → copy token 3’s value;
uniform weight → the mean. In one line:
out_h = softmax_t( (Q_h · K_t[h]) / √128 ) · V_t[h]
A fully worked example: head_dim = 2, three tokens
Smallest non-trivial case, every number by hand:
head_dim = 2, √head_dim ≈ 1.4142
Query: Q = [1, 0]
Keys: K0 = [1, 0] K1 = [0, 1] K2 = [1, 1]
Values: V0 = [2] V1 = [3] V2 = [4] (1-D values for readability)
1) raw scores: s0 = 1·1+0·0 = 1 ; s1 = 1·0+0·1 = 0 ; s2 = 1·1+0·1 = 1
2) scaled ÷√2: [0.7071, 0.0000, 0.7071]
3) softmax:
row max m = 0.7071
s_t − m = [ 0.0000, −0.7071, 0.0000]
exp(s_t − m) = [ 1.0000, 0.4931, 1.0000]
Z = Σ = 2.4931
weights = /Z = [ 0.4011, 0.1978, 0.4011] sums to 1 ✓
4) output:
out = 0.4011·2 + 0.1978·3 + 0.4011·4
= 0.8022 + 0.5934 + 1.6044 = 3.0000
Figure 16.1: attention end to end at head_dim 2. Q=[1,0] sees only each
key’s first dimension, so K0 and K2 tie at the top; the output sits
between V0=2 and V2=4, pulled toward V1 by the middle weight. Step 3’s
− m line is the max-subtraction trick: subtracting the row maximum
before exp is algebraically exact (the exp(−m) cancels between
numerator and denominator) and numerically mandatory — exp(200) is
+inf in f32 and inf/inf is NaN. Every kernel below carries it.
16.2 Online softmax — the running-max table
The formula we just wrote has an inconvenient property: its denominator
sums over every score, so no weight can be finalized until the last
token has been seen. What does a kernel do about that? The literal answer
is to keep everything. Materializing all visible scores and then doing
softmax in a second pass (the ancestor’s score-buffer design) costs a
buffer sized by context — comfortable at short depth, and a cost that
grows with exactly the thing you want to grow. The
flash/online formulation instead keeps three running quantities per worker
— max M, denominator S, accumulator O — and folds each new score in:
new_M = max(M, s)
corr = exp(M − new_M) ← rescale: old stats were normalized
against the wrong (smaller) max
S = S·corr + exp(s − new_M)
O = O·corr + exp(s − new_M)·V
M = new_M
Run it on Figure 16.1’s three scores in arrival order
[0.7071, 0.0000, 0.7071]:
start: M = −∞, S = 0, O = 0
token 0: new_M = 0.7071; corr = exp(−∞−0.7071) = 0
S = 0·0 + 1.0000 = 1.0000
O = 0·0 + 1.0000·2 = 2.0000
token 1: new_M = 0.7071 (unchanged); corr = exp(0) = 1
S = 1.0000 + 0.4931 = 1.4931
O = 2.0000 + 0.4931·3 = 3.4793
token 2: new_M = 0.7071 (unchanged); corr = 1
S = 1.4931 + 1.0000 = 2.4931 ← matches Figure 16.1's Z
O = 3.4793 + 1.0000·4 = 7.4793
output: O / S = 7.4793 / 2.4931 = 3.0000 ✓
Figure 16.2: online softmax as a running table. The corr rescale fired
only on the first row (max moved from −∞); had a later token beaten 0.7071,
every earlier statistic would have been scaled down by exp(old − new) in
one multiply. Two workers each running this over half the tokens merge
the same way — each is a “token” whose (M, S, O) combines with the
other’s — which is how the split across SIMD groups and workgroups below
stays exact.
Say that the other way round, because it is the load-bearing idea of the
whole chapter. The triple (M, S, O) is a complete summary of every
token a worker has looked at; nothing else about those tokens is ever
needed again. So attention can be cut into arbitrary pieces, chewed in
any order, on any number of workers, and reassembled with no
approximation anywhere. That single property is what lets three different
kernels share one mathematics while disagreeing about nearly everything
else — and, as we will see, it is also what makes them disagree in the
last bits of the result.
16.3 GQA 32:2 — sixteen query heads per KV head
Muse Glimmer has 32 query heads and 2 KV heads — grouped-query
attention at 32:2, i.e. heads_per_kv = 16 (config.rs:274-276; geometry
from muse_golden.rs:97-99). Queries fan in; the cache does not:
query heads: Q0 … Q15 Q16 … Q31
└── KV head 0 ┘└── KV head 1 ┘
K/V planes: K0,V0 K1,V1 (2 planes, not 32)
Figure 16.3: the 16:1 fan-in. Every kernel maps a query head to its KV
head by kv_head = head / heads_per_kv (muse_reference.metal:1077-1078,
flash_attn_decode_vec_contiguous_f16.metal:519). The bandwidth win is
the point: the KV cache — and every attention read of it — is 16× smaller
than full multi-head attention at the same head count
[arxiv:2305.13245]. It is also why the store kernels of
Ch 15 write 256-element rows (two heads’
worth), not 4,096.
16.4 SWA masking against the ring position
Which past tokens are visible? For a sliding layer at absolute position
position with window W = 2,048:
visible = min(position + 1, W) (this token included)
logical_start = position + 1 − visible (the window's first token)
and each visible logical token maps to its ring slot through Ch 15’s explicit origins:
physical = (origin_physical + logical − origin_logical) % capacity
These three lines — not position % capacity — are how every Muser-owned
attention kernel addresses the cache; they appear verbatim inside the
splitk kernel below (muse_reference.metal:1079-1102). For a NoPE layer
the same code with window = 0 degenerates to visible = position + 1,
logical_start = 0 — the growing plane. Masking here is addressing:
tokens outside the window are never read, so they need no explicit
−inf mask. (Explicit masks do exist on Muser’s paths — for the pinned
llama prefill kernel’s causal blocks and the staged SWA decode route —
metal/encode/attn.rs:266-317; the decode ladder itself masks by
address.)
16.5 The kernels — three rungs, one math
The rungs come in teaching order here, not in the order the predicates test them. Muser’s own splitk pair goes first, because its source is in the tree and it wears the online-softmax table on its sleeve. Then the pinned llama kernel that outranks it wherever bit-parity is the contract. Then the ferrite-lineage pair that catches NoPE layers when the metallib is missing. Read the first rung as the reference implementation and the other two as departures from it: everything that differs between them — who owns the reduction order, who reads the current token, who can address a wrapped ring — falls out of the situation each was built for.
16.5.1 The Muser splitk producer + reducer (SWA fallback)
This is the default SWA route whenever the pinned vec path is not
eligible, and the clearest exhibit of §16.1–16.2 in code. The producer’s
grid is (n_heads, n_workgroups) and its threadgroup is
(32, n_simdgroups):
// crates/muser-engine/src/shaders/muse_reference.metal:1052
kernel void muser_attention_decode_splitk_f16(
device const float *query [[buffer(0)]],
device const half *key_cache [[buffer(1)]],
device const half *value_cache [[buffer(2)]],
device float *partials [[buffer(3)]],
constant uint &n_heads [[buffer(4)]],
constant uint &n_kv_heads [[buffer(5)]],
constant uint &position [[buffer(6)]],
constant uint &capacity [[buffer(7)]],
constant uint &origin_logical [[buffer(8)]],
constant uint &origin_physical [[buffer(9)]],
constant uint &window [[buffer(10)]],
constant uint &n_workgroups [[buffer(11)]],
constant uint &n_simdgroups [[buffer(12)]],
constant float &attention_scale [[buffer(13)]],
threadgroup float *shared [[threadgroup(0)]],
uint2 group [[threadgroup_position_in_grid]],
uint lane [[thread_index_in_simdgroup]],
uint simdgroup [[simdgroup_index_in_threadgroup]]) {
const uint head = group.x;
const uint workgroup = group.y;
if (head >= n_heads || simdgroup >= n_simdgroups) {
return;
}
const uint head_dim = 128;
const uint heads_per_kv = n_heads / n_kv_heads;
const uint kv_head = head / heads_per_kv;
const uint visible = window > 0 ? min(position + 1, window) : position + 1;
const uint logical_start = position + 1 - visible;
const uint block_count = (visible + 31) / 32;
const uint vector_offset = lane * 4;
if (simdgroup == 0) {
*((threadgroup float4 *)(shared + vector_offset)) =
*((device const float4 *)(query + head * head_dim + vector_offset));
}
threadgroup_barrier(mem_flags::mem_threadgroup);
const float4 q = *((threadgroup float4 *)(shared + vector_offset));
float running_max = -3.402823466e+38f;
float running_sum = 0.0f;
float4 accumulator = 0.0f;
for (uint block = workgroup * n_simdgroups + simdgroup;
block < block_count;
block += n_workgroups * n_simdgroups) {
const uint first_offset = block * 32;
const uint count = min(32u, visible - first_offset);
float scores[32];
for (uint item = 0; item < count; ++item) {
const uint logical = logical_start + first_offset + item;
const uint physical =
(origin_physical + logical - origin_logical) % capacity;
const uint base = (physical * n_kv_heads + kv_head) * head_dim;
const float4 key =
float4(*((device const half4 *)(key_cache + base + vector_offset)));
scores[item] = simd_sum(dot(q, key)) * attention_scale;
}
float block_max = scores[0];
for (uint item = 1; item < count; ++item) {
block_max = max(block_max, scores[item]);
}
const float next_max = max(running_max, block_max);
const float old_factor = exp(running_max - next_max);
accumulator *= old_factor;
running_sum *= old_factor;
running_max = next_max;
for (uint item = 0; item < count; ++item) {
const float weight = exp(scores[item] - running_max);
running_sum += weight;
// …(V pass: same logical→physical walk, accumulator += weight * V;
// elided — muse_reference.metal:1117-1127)…
}
}
// …(threadgroup merge of the n_simdgroups partials and the per-workgroup
// [max, sum, weighted-V] write to `partials`; elided —
// muse_reference.metal:1130-1166)…
}
(V-pass and merge elided as marked; the elided lines are the same online-softmax steps against the value plane and the Figure 16.2 merge.) The anatomy:
- Q staged once — SIMD group 0 copies the head’s 128-wide Q into
threadgroup memory as
float4s; every lane then holds onefloat4slice (lane * 4), reused for all 32-token blocks. - The score —
dot(q, key)is a 4-lane partial;simd_sumfolds the 32 lanes’ partials into one score. 32 lanes × 4 = the whole 128-dim dot product per token. - Logical 32-token blocks, distributed round-robin — block index
workgroup * n_simdgroups + simdgroup, stepping byn_workgroups * n_simdgroups: each SIMD group owns every(n_workgroups × n_simdgroups)-th block, so every block is owned exactly once (asserted by test,metal/encode/attn.rs:1001-1019). - Online softmax per block — block max, then the
old_factorrescale of Figure 16.2, then weights and the V-accumulate. - Partials
[max, sum, weighted-V]— one per (head, workgroup), stride2 + 128floats (attn.rs:742-743).
Where do the workgroup and SIMD-group counts come from, and why cap them
at all? splitk_geometry (attn.rs:888-896) answers the first half:
blocks of 32 visible tokens, workgroups capped at
MAX_DECODE_SPLIT_WORKGROUPS = 32, SIMD groups growing 1→4 as visibility
demands. The second half is a fork we lost. The geometry inherited from
Ferrite put occupancy first and capped workgroups far above llama’s fixed
launch, on the reasonable theory that more resident work hides more
latency. On the deep, growing planes it did the opposite: the extra
workgroups arrived with too little to chew on. The comment on the
constant records the result in the codebase’s own words:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/decode.rs:41
// llama.cpp's Metal `flash_attn_ext_vec` always launches `nwg = 32` and only
// grows simdgroups (1→4) once `2 * nwg * nsg * 32 < visible`. Ferrite's
// occupancy-first cap of 96 oversubscribed the 13 full/NoPE planes and is
// the depth-rent we lose to llama as context grows. Keep short-context
// `nwg = min(blocks, 32)` so TG512 does not pay empty workgroups.
pub(crate) const MAX_DECODE_SPLIT_WORKGROUPS: usize = 32;
}
Read that comment twice. One half is the setting we shipped and the llama launch rule it now matches. The other half is a confession: the earlier cap oversubscribed the deep planes, and what we still lose to llama as context grows is named out loud, in the source, as rent. Nobody had to write that second half down — a tidier codebase would have left it in a commit message nobody reads. Muser’s house style is that the deficit lives next to the constant that causes it.
The reducer then finishes the job the producer deliberately left open.
Each workgroup handed up a partial, and the reducer merges those partials
with exactly the combine the producer used inside its own blocks:
correction = exp(part[0] − global_max), then
global_sum += part[1] * correction, then accumulate the weighted
values, then divide — once, at the very end. That final single divide is
why the producer normalized nothing along the way: an early
normalization would only have to be undone. The code is
muser_attention_decode_splitk_reduce_f32, sibling of
muser_attention_decode_splitk_reduce_f16
(muse_reference.metal:1169-1201), dispatched at attn.rs:776-783. One
detail there is worth stealing for your own encoders: the barrier before
it is scoped to the partials buffer alone, “instead of stalling every
buffer used by the 52-layer command buffer” (attn.rs:771-773).
16.5.2 The pinned llama vec kernel — kernel_flash_attn_ext_vec_f16_dk128_dv128
Why would an engine that has a perfectly good attention kernel of its own hand two of its four routes to somebody else’s binary? Park the question through the bullets; the answer arrives at the end of the section, and it is not about speed.
The vec-eligible routes dispatch llama.cpp’s own flash-attention decode
kernel from the pinned metallib. Like Ch 13’s
matvec, the body is pinned binary provenance — not in the Muser tree, not
quoted here — and what Muser owns is a meticulously shaped dispatch
(metal/encode/attn.rs:437-633):
- Strides describe the plane. Head-major NoPE planes bind as
ns10 = 128(one head row per KV row); token-major SWA rings asns10 = 256(a full token row per KV row) — the twoGgmlMetalKargsFlashAttnExtVeclayouts atattn.rs:503-511. - Split-K by construction. llama’s kernel launches
nwg = 32workgroups per head (LLAMA_FA_NWG,encode.rs:1066) and grows SIMD groups 1→4 the same way Muser’s splitk does (attn.rs:496-499); each writes a partial, and llama’s ownkernel_flash_attn_ext_vec_reducemerges them (attn.rs:616-630). - Ragged visibility handled llama’s way. If
visible % 32 ≠ 0, akernel_flash_attn_ext_paddispatch first rounds the tail (attn.rs:525-559) — which is exactly what the SWAlen % 32predicate avoids needing on the token-major plane, where llama’s pad kernel “would read past” the wrong-shaped row (decode.rs:5654-5657).
Now the parked question. The pinned-vs-own distinction is not performance
vanity — nobody clocked llama’s kernel as faster and surrendered. It is
arithmetic identity. The parity ledger’s Stage A close-out records two
findings that close the door together: llama’s vec kernel “uses an
intentionally different reduction DAG” than any Muser/Ferrite kernel, and
“no untried llama scheduling transplant [was] compatible with the fixed
production hash” [ledger, Stage A close-out]. Read them side by side —
you cannot reschedule your way to llama’s bits, and the hash that defines
production will not move to meet you. Matching llama’s bits meant
adopting llama’s kernels on the routes where llama runs them.
16.5.3 The ferrite interleaved fallback (NoPE)
The last rung catches the case neither of the others can: a NoPE layer
with no eligible vec route. Here the ladder falls to a ferrite-lineage
pair kept deliberately unmodified — the producer
flash_attn_decode_vec_f16_gqa_interleaved
(shaders/ferrite/flash_attn_decode_vec_contiguous_f16.metal:494) and
the reducer flash_attn_decode_reduce_v2
(shaders/ferrite/flash_attn_decode_reduce_v2.metal:4). The wrapper
states the terms of that inheritance in one line: “Exact Ferrite
a85048a90 cache-interleaved producer + LSE reducer for the growing NoPE
planes. These planes are head-major and never wrap; SWA rings remain on
Muser’s explicit-origin kernel” (attn.rs:185-187). That is a division
of labour, not a preference: the ferrite pair is trusted exactly where
the plane’s shape matches the assumption it was written under, and
nowhere else.
Its signature is the prelude ABI — function constants bake
head_dim = 128 and the SIMD-group count into the pipeline at PSO build
(encode.rs:798-835). Its grid is (n_heads, n_workgroups), launched so
that sibling Q heads land adjacent; its own header calls this a
“schedule-only interleaved sibling” (…contiguous_f16.metal:487-493),
and “schedule-only” is the load-bearing half of that phrase — the
interleaving changes which head runs beside which, never what any of them
computes. Each workgroup then merges its SIMD groups into one legacy
[M, S, O] partial, and reduce_v2 combines those with the same
precise::exp(p[0] − global_max) correction of §16.2. (The
“LSE” in that quoted header is log-sum-exp — the (M, S, O) statistics of
§16.2 under their textbook name.)
This route also owns the current-token KV bypass: the producer takes
k_cur/v_cur as buffers 4–5, one simdgroup stores them into the plane
as a side effect (…contiguous_f16.metal:534-543), and — per the code’s
own comment — “Every workgroup still reads the current token from
k_cur/v_cur”: the freshest row is consumed as f32 from the activation
buffer, never round-tripped through the f16 plane. The pinned-vec and
splitk routes do not bypass: they store first and read the plane back
(the vec route with an explicit barrier between, decode.rs:5669-5670).
So the answer to “does Muser bypass the current token?” is yes on the
ferrite rung only — a property of which ladder step you stand on, not of
the engine.
16.6 The Rust dispatch — the ladder in one table
Everything above lands in four call sites in encode_token, one per
rung. Read the table as the ladder seen from the encoder — and read the
last column first, because that is where the previous chapter’s store and
this chapter’s read have to be ordered against each other, and each route
answers that differently:
| route | wrappers (file:line) | kernels | barrier between store and attention? |
|---|---|---|---|
| SWA vec | encode_kv_store_f16 attn.rs:635 + encode_llama_flash_attn_decode_vec_f16 attn.rs:437 | muser_kv_store_f16 → pinned vec + pad + reduce | yes (decode.rs:5669-5670) |
| SWA fallback | store + encode_attention_decode_splitk_f16 attn.rs:708 | store → muser_attention_decode_splitk_f16 → …_reduce_f32 | barrier on partials (attn.rs:774) |
| NoPE vec | encode_kv_store_batch_f16 attn.rs:787 + vec wrapper | muser_kv_store_batch_f16 → pinned vec + reduce | yes (decode.rs:5746-5747) |
| NoPE fallback | encode_ferrite_attention_decode_interleaved_f16 attn.rs:189 | ferrite interleaved (fused store) → reduce_v2 | fused (…contiguous_f16.metal:534-543) |
That last column is the fused-versus-staged argument compressed into four cells. Three routes store the row and then read it back, so they need an ordering guarantee — a full barrier, or the narrow one scoped to the partials. The ferrite rung needs neither, because the value never leaves the kernel that produced it.
The vec wrapper call itself carries the whole story in its argument list —
visible, capacity, origin_physical, head_major, the pad and
partials scratch — decode.rs:5671-5692 (SWA, head_major = false) and
decode.rs:5748-5769 (NoPE, head_major = true).
16.7 The access pattern — when attention starts to matter
Now the question this chapter owes the bandwidth story: at what context length does attention stop being a rounding error and become the thing that costs? The derivation is short enough to do in full, so do it in full rather than trusting anyone’s intuition about it.
Per token per layer, the attention read is the visible window’s KV:
visible × n_kv_heads × 2 planes × head_dim × 2 B = visible × 1,024 B
(the per-layer row cost of Ch 15). Derive
the shape across depth:
SWA layers (window caps at 2,048): 39 × 2,048 × 1,024 ≈ 81.8 MB (constant)
NoPE layers at depth D: 13 × D × 1,024
weights per token: 16,756,681,056 B ≈ 16.76 GB (constant)
at D = 2,048: KV ≈ 0.17 GB ≈ 1 % of the weight stream
at D = 32,768: KV ≈ 0.44 GB + 0.08 GB ≈ 2.7 %
at D = 131,072: KV ≈ 1.74 GB + 0.08 GB ≈ 1.83 GB ≈ 10.9 %
crossover with weights: 13 × D × 1,024 = 16.76 GB ⇒ D ≈ 1.26 M tokens
— beyond the model's 131,072 limit
So unlike smaller models (the ancestor’s 1.5 B crossed over near 65 K
context [ferrite-book Ch 14] — Ferrite-lineage arithmetic), weights
dominate Muse Glimmer’s decode at every context the model can hold;
attention’s KV read grows linearly but never overtakes within the limit.
That is derived arithmetic from the geometry, not a measurement — and it
is also why the splitk workgroup cap of §16.5.1 (“the depth-rent we lose
to llama as context grows”, decode.rs:41-46) is about latency and
occupancy at depth, not about bytes: 13 NoPE layers × 131 K rows of
strided f16 is plenty to expose an under-parallel kernel even at 10 % of
the byte budget.
16.8 Tradeoffs
A ladder, not a kernel — and the evidence for each rung. The ancestor
faced this same question and got it wrong first: its position ladder was
ground-truthed by route logging and then corrected once
[ferrite-book Ch 15]. Muser’s ladder is at least legible, since the
predicates are in source and quoted at the top of this chapter. Legible
is not the same as justified, though, so here is how the rungs earned
their places.
The fork came while the route question was still open and attention was
the prime suspect for the decode gap. The attractive move was to
specialize. Decode issues exactly one query, so a one-query GQA FA2
kernel ought to beat a general flash-attention kernel that is still
carrying prefill’s machinery around with it. We wrote that kernel
and ran it on the streamed diagnostic, fully expecting the specialist to
win. It came in at 28.290 tok/s median against llama’s 33.428 — a ratio
of 0.8463×, the specialist losing to the generalist it was built to
beat. We kept the measurement: it sits in the landed-and-rejected table
[docs/decode-dispatch-gap-20260815.md §Landed and rejected reductions],
banked as evidence rather than shipped as a claim.
What the loss taught is that the kernel’s shape was never the lever.
The lever was the reduction order — and the only way to get llama’s
reduction order is to run llama’s kernel. Transplanting the attention DAG
bit-exactly is what moved decode from 0.781× (single-sample, 2026-08-14)
toward the six-depth matrix above parity [ledger, Arc 1]. So the ladder
is neither a hedge nor an accident of history. It exists because each
rung is the exact kernel for its situation: llama’s for parity-critical
contiguous reads, Muser’s splitk for wrapped rings the pinned kernel
cannot address, ferrite’s for the growing head-major planes without the
metallib.
Wrapped-ring vec read vs staging — mathematical validity vs bit-parity.
§16.0’s full-ring clause is sound mathematics (permutation-invariant
softmax, attn.rs:431-435) and still not what serving does: the batch
graph stages wrapped SWA into llama’s padded indices so “the pinned vec
kernel sees the same reduction lanes rather than a mathematically
equivalent compact permutation” (attn.rs:140-143). The 39 staging
groups that decision costs live in the gap accounting (§16.9). This is
the book’s exactness-vs-equivalence distinction at its purest: two
routes with identical softmax outputs in exact arithmetic, one of which
reproduces llama’s floating-point reduction order and one of which does
not. Put plainly, serving pays dispatch groups for an answer it already
had — because it needs that answer to arrive in llama’s order, not
merely to be correct.
Splitk’s own kernel vs pinned-everything. Why keep a Muser-owned
attention kernel at all when the metallib is loaded? Because the pinned
vec kernel cannot read a partially-wrapped token-major ring
contiguously — the predicate excludes it — and re-staging every decode
token (rather than every wrapped prefill chunk) would add a per-token
copy dispatch. The splitk producer walks the ring’s explicit origins
natively (muse_reference.metal:1100-1102). The price is a different
reduction DAG from llama’s on that rung — acceptable on the
teacher-forced/diagnostic graph, and one reason the serving route prefers
eligibility for the pinned kernel whenever the predicates allow.
32-token blocks, 32-workgroup cap. The block size matches the pinned
kernel’s read granularity (ncpsg = 32, encode.rs:1067) and the
workgroup cap matches llama’s fixed nwg = 32 — with the in-source
admission that an earlier occupancy-first cap “oversubscribed the 13
full/NoPE planes and is the depth-rent we lose to llama as context grows”
(decode.rs:41-46). Owning the tradeoff in a constant’s comment is this
codebase’s house style; the rent itself is the kind of measured deficit
Ch 40 catalogs.
16.9 Where the gap lives
Which of this chapter’s dispatches actually show up in the decode-gap
accounting, and could any of them be removed? Two families again, plus
one experiment. The 52 KV-publication splits — store dispatch +
attention dispatch as separate closures in production — are
“session/publication structure; Keep” [docs/decode-dispatch-gap-20260815.md]:
combining the closures would not remove either kernel’s math, and the
splits are what make the pinned-kernel-per-route discipline auditable.
The 39 SWA staging groups are this chapter’s wrapped-ring story —
staging exists because bit-parity with llama’s reduction lanes outranks
the cheaper direct read (§16.8). And the attention-shaped row in the
landed-and-rejected table — the one-query GQA FA2 at 0.8463× — is the
measured trace of the route hunt that J0/J1 eventually resolved by
changing the anchor. Attention is not the gap’s largest family; it is the
gap’s most instructive one.
16.10 What comes next
activations.attention now holds 32 heads × 128 floats of weighted-past
mixture. Muse Glimmer does something unusual with it before the output
projection: multiplies it element-wise by sigmoid(gate), where gate is
the fourth matvec of Ch 13 that has
been waiting in activations.gate all along. That sigmoid gate — and the
o_proj that follows it — is Ch 17.
References
crates/muser-engine/src/decode.rs:5643-5792— the route predicates (quoted) and all four dispatch branches.crates/muser-engine/src/shaders/muse_reference.metal:1052-1167—muser_attention_decode_splitk_f16(quoted in §16.5.1; V-pass and merge elided as marked).crates/muser-engine/src/shaders/muse_reference.metal:1169-1201— the splitk reducer.crates/muser-engine/src/metal/encode/attn.rs:437-633— the pinned vec wrapper (strides, ns10 split, pad, reduce).crates/muser-engine/src/metal/encode/attn.rs:707-784— the splitk wrapper and its scoped barriers;:888-896splitk_geometry(with the pinned-schedule test at:976-983).crates/muser-engine/src/metal/encode/attn.rs:185-257— the ferrite interleaved wrapper;:102-183the SWA staging kernels and their llama-lanes comment.crates/muser-engine/src/metal/encode.rs:149-167, 1066-1072, 1079-1249—LlamaFlashAttnPipelines,LLAMA_FA_NWG/NCPSG, and the vec/pad/reduce pipeline construction.crates/muser-engine/src/shaders/ferrite/flash_attn_decode_vec_contiguous_f16.metal:487-554— the interleaved producer: header, fused store, and the k_cur/v_cur bypass comment.crates/muser-engine/src/shaders/ferrite/flash_attn_decode_reduce_v2.metal:4-48— the LSE reducer.crates/muser-engine/src/decode.rs:41-46—MAX_DECODE_SPLIT_WORKGROUPSand the depth-rent comment (quoted).crates/muser-engine/src/config.rs:274-281—heads_per_kv,attn_scale;:426-430the scale test.[docs/decode-dispatch-gap-20260815.md]— the closure families of §16.9 and the 0.8463× GQA-FA2 diagnostic row.[ledger](docs/goal-parity-ledger-2026-08.md) — Arc 1 (0.781× → the six-depth matrix) and the Stage A close-out’s pinned-kernel audit.[docs/memory-footprint.md]— the 1,024 B/row KV constant §16.7 derives from.[arxiv:1706.03762]— Vaswani et al., Attention Is All You Need (the scaled dot-product formula).[arxiv:2305.13245]— Ainslie et al., GQA (the 16:1 bandwidth lever).- Ch 15 — the planes and origins this chapter indexes; Ch 17 — the gate that consumes this chapter’s output; Ch 36 — the prefill-side attention routes.
[ferrite-book Ch 15]— the ancestor’s attention chapter (the worked-example and online-softmax devices ported; its position-ladder correction is the reason §16.0 leads this one).
Chapter 17 — The sigmoid gate and the output projection
status: polished · path: Muse Glimmer, pinned Muser tree
Prerequisites: Ch 2 (threads, SIMD groups,
dispatch_threads), Ch 9 (the attention-output gate in the architecture), Ch 12 (the sandwich norms and the fused dual-eps tail), Ch 13 (the pinned ggml matvec family — this chapter reuses it verbatim), Ch 16 (what attention writes intoactivations.attention). Sigmoid is defined from zero here; no prior exposure is assumed.
17.1 What it computes
Ch 16 ended with attention writing one
vector: activations.attention, the concatenation of 32 head outputs of 128
elements each — attn_dim = 32 × 128 = 4,096 floats
(config.rs:268-270). In most transformer families that vector goes straight
to the output projection. Muse Glimmer does something extra first: before the
attention result is allowed to rejoin the residual stream, the layer asks a
second, learned question about every one of those channels — should this one
be heard at all?
The answer arrives as another vector. A fourth attention projection — the gate, sibling of Q, K, and V from Ch 13 — produced a second 4,096-vector, and this chapter’s kernel multiplies the two element-wise:
attn_out[i] ← attn_out[i] · σ(gate[i]) for i in [0, 4096)
where σ is the logistic sigmoid, defined from zero:
σ(x) = 1 / (1 + e^(−x))
The sigmoid maps any real number into the open interval (0, 1):
x | e^(−x) | σ(x) | meaning |
|---|---|---|---|
−4 | 54.6 | 0.018 | almost fully closed |
−1 | 2.72 | 0.269 | mostly closed |
0 | 1.0 | 0.500 | half open |
+1 | 0.368 | 0.731 | mostly open |
+4 | 0.018 | 0.982 | almost fully open |
Table 17.1: the sigmoid at five points. Positive gate values pass the attention output through nearly unchanged; negative values suppress it; the transition is smooth, never a hard zero.
So the gate is a learned, per-channel volume knob on the attention result,
and it lives in attention-output space — MuseConfig::attn_dim’s doc comment
calls 4,096 “the space the attention-output gate lives in”
(crates/muser-engine/src/config.rs:268-270). After the gate, the
output projection (o_proj, the tensor attn_output.weight) maps the
gated 4,096-vector back into the 6,656-wide residual
stream:
delta = W_o · (attention ⊙ σ(gate)) W_o : [6656 × 4096], Q4_K
residual ← residual + post_norm(delta) (the fused tail — §17.7)
Two operations, one chapter: a pointwise gate and a matvec you already know. Keep an eye on the asymmetry between them, because it is the chapter’s point. The cheap operation is the one that turned out to need a careful safety argument; the expensive one is the one with nothing to confess.
17.2 Why it exists — a gated attention output
There are two questions hiding in this section’s title, and they have very different evidence behind them. What is the gate, mechanically? is settled by reading the tree. Why did somebody train a model to want one? is not in the tree at all. Separating them is the whole discipline of this book, so we do it out loud: the verifiable facts first, the honest shrug second.
The gate is part of the model contract, asserted
in the engine’s own header — “Parameterless QK-RMSNorm, sigmoid
attention-output gate, Gemma-2-style sandwich norms”
(crates/muser-engine/src/lib.rs:12). It is a real learned projection with
its own tensor blk.{l}.attn_gate.weight of shape [6656, 4096]
(config.rs:307), and it rides in the same concurrent four-projection set as
Q, K, and V (Ch 13;
decode.rs:5569-5598). The CPU oracle applies it strictly between attention
and o_proj:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/reference.rs:446
// ── sigmoid gate, then o_proj ─────────────────────────────────
for g in gate.iter_mut() {
*g = 1.0 / (1.0 + (-*g).exp());
}
// … (capture elided) …
for (a, g) in attn_out.iter_mut().zip(gate.iter()) {
*a *= *g;
}
matmul(
&self.w(&format!("blk.{il}.attn_output.weight")),
&attn_out,
t,
&mut proj,
);
}
Now the second question. Why gate the attention output at all? The
structural reading — the same
decoupling you will meet again in the FFN of Ch 18 — is
that the gate separates whether a channel’s attention result flows onward
from what that channel carries. A sigmoid-squashed multiplier in (0, 1) can
near-zero a head-output channel that the layer has decided is noise for this
token, while passing useful channels through. But the model’s own training
rationale is not in the Muser tree, and this book does not invent psychology:
[unverified] why the authors chose a sigmoid (rather than tanh or ReLU)
gate for Muse Glimmer specifically. What the code proves is the wiring, the
shape, and that skipping it is not an option — remove the gate and every
downstream bit changes, because the oracle order above is the parity
specification the Metal graph must reproduce (reference.rs is “the
correctness gate”, lib.rs:99-109).
A second-order effect worth naming: because σ(x) > 0 always, the gate can
damp a channel but never invert it — and because the gate is applied before
o_proj, it scales each attention-head channel while they are still separate,
before the heads are mixed by W_o.
17.3 The operation, explained — a worked gate by hand
Before trusting a kernel, it is worth doing its job once by hand, at a size small enough to check on paper. The gate makes that easy: it is as simple as GPU work gets — element-wise, no reduction, no mixing, every output element depending on exactly one attention value and one gate value. A hand example with a 4-element slice of the two vectors:
i : 0 1 2 3
gate: +2.0 −1.0 0.0 −4.0
σ(g): 0.881 0.269 0.500 0.018
attn: 1.50 2.00 0.25 8.00
out: 1.32 0.54 0.125 0.14 ← attn[i] · σ(gate[i])
Every element is independent — element 3’s attention value of 8.0 is large, but its gate is nearly closed (σ(−4) = 0.018), so 98.2 % of it is suppressed. That is the whole gate.
The operation that follows is where all the bytes are, and it asks nothing new
of you. The o_proj after the gate is the matvec of
Ch 13 at a new shape — 6,656 output rows,
each a dot product over 4,096 inputs, which is 4096/256 = 16 Q4_K
super-blocks of 144 bytes = 2,304 bytes of weight per row. No new math; the
shapes, in Figure 17.1:
gated attn [4096] W_o [6656 × 4096] (Q4_K) projected [6656]
┌ ┐ ┌──────────────────────────────┐ ┌ ┐
│ 4,096 f32│ × │ 6,656 rows × 2,304 B/row │ = │ 6,656 f32 │
└ ┘ └──────────────────────────────┘ └ ┘
16 KiB read 15,335,136 B ≈ 15.34 MB 26 KiB write
(read once per layer)
Figure 17.1: The gate (pointwise, 4096 wide) feeding the o_proj matvec (4,096 → 6,656, Q4_K). The weight stream dominates: 15.34 MB against 42 KiB of activation traffic.
17.4 The Metal kernel — sigmoid_gate_inplace
How much Metal does a per-channel volume knob need? Less than the paragraph that describes it. Here is the whole kernel, verbatim — it is nine lines and worth reading as one piece before we take it apart:
// crates/muser-engine/src/shaders/ferrite/sigmoid_gate.metal:4
// Element-wise sigmoid gating: attn_out[i] *= sigmoid(gate[i])
// dispatch: (ceil(n/1024), 1, 1) × (1024, 1, 1)
kernel void sigmoid_gate_inplace(
device float* attn_out [[ buffer(0) ]],
device const float* gate [[ buffer(1) ]],
constant uint& n [[ buffer(2) ]],
uint gid [[ thread_position_in_grid ]])
{
if (gid < n) {
attn_out[gid] *= 1.0f / (1.0f + exp(-gate[gid]));
}
}
Line by line:
- Buffer 0,
attn_out— the attention result, and note it isdevice float*, notconst. This kernel mutates its input in place: the gated value overwrites the ungated one. There is no third buffer. - Buffer 1,
gate— the gate projection’s output, read-only. - Buffer 2,
n— the element count (4,096 on this model), bound as a 4-byte inline constant. gid— the global thread index; each thread owns exactly one elementgid, and theif (gid < n)guard covers the ragged tail when the grid rounds up pastn.- The body — literally the formula of §17.1: one
exp, one add, one divide, one multiply-into-place. Per element: two global reads (attn_out,gate), one global write (attn_out).
That *= is the only subtlety, and §17.7 is about why it is safe.
Now back to the top of that listing, because its header comment is a small
trap and it is worth walking into deliberately once. We read
dispatch: (ceil(n/1024), 1, 1) × (1024, 1, 1) as a specification and went to
the Rust wrapper expecting to find threadgroups of that width being launched.
They are not there. dispatch_1d calls dispatch_threads with threadgroup
width min(n, 256) (crates/muser-engine/src/metal/encode.rs:1337-1342), so
the comment describes a dispatch shape the engine no longer uses. The detour
taught more than the discrepancy is worth on its own: the half of the comment
that constrains correctness — one thread per element, ragged-tail guarded —
holds either way, and the half that drifted is the half nothing checks. So we
flag it code-wins style and move on. A kernel header is a hypothesis about the
code, dated the day it was typed; we kept the pointer to both sides of this one
[crates/muser-engine/src/shaders/ferrite/sigmoid_gate.metal:5 versus
crates/muser-engine/src/metal/encode.rs:1337].
One digression before the wrapper, and it announces its own relevance: the
pattern it shows up in recurs in nearly every kernel chapter after this one.
This kernel has a twin. A strict-arithmetic sibling exists for the
cross-vendor comparison lane: muser_cross_vendor_sigmoid_gate
(shaders/muse_reference.metal:680) computes the same thing through the
no-fast-math library’s controlled expf so a CUDA producer’s bytes can be
matched bit for bit — same formula, different compile flags
(gate.rs:14-15 selects it under MUSER_CROSS_VENDOR_QK). That is what an
exactness lane looks like throughout Muser: never a different algorithm,
always the same algorithm with the fast paths refused.
17.5 The Rust dispatch
The kernel is trivial, so the interesting question moves to the encoder: what
must the wrapper guarantee before it is allowed to launch anything? Two things.
The two vectors have to be the same length, or the element-wise product is
quietly meaningless; and the launch has to land between attention and o_proj
and nowhere else, because that is the order the CPU oracle fixed. The wrapper
is four statements:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/metal/encode/gate.rs:7
pub fn encode_sigmoid_gate(
&self,
encoder: &ComputeCommandEncoderRef,
values: &GpuBuffer,
gate: &GpuBuffer,
) {
debug_assert_eq!(values.len(), gate.len());
if std::env::var_os("MUSER_CROSS_VENDOR_QK").is_some() {
encoder.set_compute_pipeline_state(&self.cross_vendor_sigmoid_gate);
} else {
self.bind(encoder, "sigmoid_gate_inplace");
}
encoder.set_buffer(0, Some(values.metal()), 0);
encoder.set_buffer(1, Some(gate.metal()), 0);
set_value(encoder, 2, &(values.len() as u32));
dispatch_1d(encoder, values.len());
}
}
In prose: bind the PSO (the strict twin only under the cross-vendor flag),
bind values = activations.attention and gate = activations.gate (both
4,096 floats — the debug_assert enforces the equal lengths the element-wise
product requires), push n as an inline constant, and launch one thread per
element via dispatch_threads, i.e. grid (4,096, 1, 1) with 256-wide
threadgroups and a guarded ragged tail (encode.rs:1337-1342).
Its call site in the token graph sits exactly where the oracle puts it — one
dispatch closure after the attention route, before o_proj:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/decode.rs:5793
dispatch(command, |encoder| {
self.kernels.encode_sigmoid_gate(
encoder,
&self.activations.attention,
&self.activations.gate,
);
});
self.project(
command,
&layer.output,
&self.activations.attention,
&self.activations.projected,
);
}
self.project (decode.rs:5909-5917) is the same wrapper every projection
uses: it routes by the loaded tensor’s dtype to encode_projection
(decode.rs:6044), which for a Q4_K attn_output.weight and one token
dispatches the pinned llama.cpp metallib kernel
kernel_mul_mv_q4_K_f32 — the exact kernel family of
Ch 13:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/metal/encode/qkv.rs:429
if tokens == 1 {
if let Some(pipeline) = self.ggml_matvec(dtype) {
let (block_bytes, rows_per_group) = match dtype {
GgmlType::Q4_K => (144, 2),
// … (Q5_K (176, 1), Q6_K (210, 2) elided) …
};
let args =
GgmlKargsMulMv::for_matmul(n_out, n_in, block_bytes, rows_per_group as i32);
encoder.set_compute_pipeline_state(pipeline);
set_value(encoder, 0, &args);
encoder.set_buffer(1, Some(weights.metal()), weights.offset() as u64);
encoder.set_buffer(2, Some(input.metal()), 0);
encoder.set_buffer(3, Some(output.metal()), 0);
let simdgroups = 2usize;
encoder.dispatch_thread_groups(
MTLSize::new(n_out.div_ceil(rows_per_group * simdgroups) as u64, 1, 1),
MTLSize::new(32, simdgroups as u64, 1),
);
return;
}
// … (fallback to Muser's own muser_matvec_q4k_4r2s elided;
// reached only when the metallib is absent) …
}
}
The PSO comes from the pinned metallib (encode.rs:278-280 registers
ggml_q4k: kernel_mul_mv_q4_K_f32 and siblings). For o_proj,
n_out = 6,656, rows_per_group = 2, simdgroups = 2, so the launch is
6,656 ÷ 4 = 1,664 threadgroups of 64 threads (two SIMD groups, two rows
per group — every output row covered exactly once). The per-dtype table
Q4_K → (144 B, 2 rows), Q5_K → (176 B, 1), Q6_K → (210 B, 2) is the
same one Ch 13 introduced; the o_proj is
Q4_K on the release artifact (attn_output 4096->6656 q4k,
crates/muser-bench/src/m16.rs:163-166).
Put that another way, because it matters for everything the rest of the chapter
argues: the expensive half of this stage is not a Muser kernel. Muser computes
the shape arguments, binds three buffers, picks a grid — and then hands the
actual arithmetic to a pipeline state compiled from someone else’s pinned
metallib. Whatever the engine can be blamed for here happens before
set_compute_pipeline_state, never inside the loop.
17.6 The access pattern — where the bytes go
Where does the time go in this stage? For every decode kernel in this book the honest first answer is bandwidth rather than arithmetic, so the way to read a stage is to count what it drags across the bus. Start with the cheap half. The gate kernel, per layer per token:
read gate[4096] 16,384 B (16 KiB)
read attn[4096] 16,384 B (16 KiB)
write attn[4096] 16,384 B (16 KiB)
─────────────────────
49,152 B (48 KiB)
The o_proj matvec after it:
read W_o (Q4_K) 6,656 rows × 16 blocks × 144 B = 15,335,136 B ≈ 15.34 MB
read gated attn 16,384 B (16 KiB)
write projected[6656] 26,624 B (26 KiB) (f32; 6,656 × 4)
The arithmetic for the weight figure, shown so you can re-derive it: a row of
W_o spans cols = 4,096 inputs; Q4_K packs 256 elements per 144-byte
super-block (Ch 6), so a row is
4096/256 × 144 = 2,304 B; there are rows = 6,656 of them;
6,656 × 2,304 = 15,335,136 B. Across all 52 layers that is
52 × 15,335,136 = 797,427,072 B ≈ 797 MB of the pinned
16,756,681,056-byte artifact — 4.76 % (arithmetic against the artifact
size asserted at lib.rs:14). For scale, the whole five-projection attention
block (Q + gate + K + V + O) is ≈ 48.4 MB per layer, while the FFN of
Ch 18 is 224–259 MB per layer — Figure 17.2 itemizes
the layer:
per-layer weight read (kquant lane, derived arithmetic):
attn_q 15.34 MB (Q4_K) ffn_gate 74.76 MB (Q4_K)
attn_gate 15.34 MB (Q4_K) ffn_up 74.76 MB (Q4_K)
attn_k 0.96 MB (Q4_K) ffn_down 74.76 MB (Q4_K; 109.03 MB on
attn_v 1.40 MB (Q6_K) the Q6_K-down layers, Ch 19)
attn_o 15.34 MB (Q4_K)
────────────────────── ─────────────────────────────
attention ≈ 48.4 MB FFN ≈ 224–259 MB
Figure 17.2: The weight budget of one Muse Glimmer layer, from the
shape/dtype table of crates/muser-bench/src/m16.rs:139-198. The o_proj this
chapter covers is a mid-size slice; the FFN is ~4.6–5.3× the whole attention
block. The gate’s own projection (attn_gate) costs as much as the Q
projection — gating is not free, it is another 15.34 MB stream.
The pattern to internalize: the gate’s 48 KiB is rounding error against the 15.34 MB that follows it. Like every decode kernel in this book, this stage lives or dies on weight bandwidth, and the gate barely moves bytes.
17.7 Why the in-place *= is safe — ownership and ordering
The attn_out[gid] *= … is a read-modify-write on a shared buffer. Two
questions: can two threads collide inside the dispatch, and can another
dispatch read attention while the gate is still writing it? Both are worth
answering carefully, because a wrong answer does not crash — it produces a
plausible token, sometimes, on some runs, which is the most expensive kind of
bug this engine can have.
In-dispatch: no collision by construction. Each global thread index gid
touches exactly one element, attn_out[gid], and no two threads share a
gid. The write set is partitioned as finely as it can be — one element per
thread. There is no reduction, no simd_sum, no threadgroup_barrier, and
none is needed. (Contrast the attention kernels of Ch 16,
where many threads cooperate per head and partials must be combined.)
Across dispatches: ordering is explicit and tracked. The token graph runs all 52 layers inside one command buffer on one concurrent compute encoder — the source records the contract at the top of the token route:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/decode.rs:5449
// One concurrent encoder owns the complete token. Graph dependencies
// are explicit barriers; independent projection groups share a barrier
// interval and may overlap, matching the accepted Ferrite/llama route.
}
Muser’s buffers are allocated StorageModeShared with Metal’s automatic
hazard tracking. That is a deliberate default, and we know it is deliberate
because the other branch of the fork was taken first. Untracked buffers promise
less driver bookkeeping between dispatches, and this graph already declares its
dependencies explicitly, so we expected the tracking to be redundant work that
could simply be switched off — free latency, no behaviour change. It was not
redundant. With tracking off, the engine “empirically changed DFlash
conditioning” — no kernel had been edited, and the observable behaviour moved
anyway — so the experiment was reverted. The allocator still carries the name
of the mode that survived
[crates/muser-engine/src/metal/buffer.rs, shared_tracked]. The lesson
generalizes well past this kernel, which is why it belongs in a chapter about
a nine-line gate: in a fail-closed engine, an optimization that changes bits
is not an optimization.
So the tracking stays, and a dispatch that reads attention after the gate’s
write is ordered by the driver’s
tracked-resource dependencies; where the graph needs finer control it issues
targeted memory_barrier_with_resources calls (you saw them around the KV
store in Ch 16, decode.rs:5669-5670).
The o_proj that consumes the gated attention is therefore sequenced after
the gate, and the next layer’s attention (which overwrites attention) after
that — a producer→consumer chain, which is the dominant hazard shape of the
whole decode graph (Ch 35
formalizes the taxonomy).
What Muser deliberately does not do here is fold the residual add into
the o_proj matvec itself. This is a real fork, and the ancestor took the
other branch of it: the Ferrite book taught exactly that
device — a y[row] += dot write-back in the matvec kernel
[ferrite-book Ch 16] — and it is a good device, cheaper by a dispatch and a
buffer. Muser’s gate fuses nothing into the pinned
matvec. The o_proj writes to a scratch buffer, activations.projected, and
the residual add happens one dispatch later inside the fused dual-eps tail
(encode_fused_norm_residual_rms_norm_32sg, decode.rs:5806-5818),
where the in-place mutation actually lives: that kernel reads
activations.normed (the running residual), adds the post-normed projection
into it, and writes the result back in place.
Why give up the cheaper shape? The reason is the book’s recurring one, and the
source states it outright: the legacy one-token graph
with its Ferrite-lineage fused kernels “diverges from the source-pinned
llama Metal graph enough to breach public logprob tolerance”, so serving
routes one-token work through the batch graph that “dispatches the exact
pinned kernels” (decode.rs:2085-2091). Keeping the o_proj a stock
pinned-metallib matvec — unmodified write-back, = not += — is what makes
its bytes match llama.cpp’s. Turn that around and it becomes the sentence to
carry into the rest of the book: Muser buys exactness with dispatches. Every
fusion the ancestor took for free, this engine pays for at the encoder,
because it does not own the kernel it has to agree with. The fusion temptation
is paid for elsewhere
(Ch 19 prices it).
17.8 Tradeoffs
Three forks meet at this stage. Two of them were the engine’s to decide; the third was decided by whoever trained the checkpoint, and knowing which is which saves an afternoon of re-litigating a choice you cannot touch.
In-place gate vs materialized sigmoid buffer. The alternative — write
σ(gate) to a fresh buffer, then a separate multiply — would traffic
16 + 16 + 16 + 16 + 16 = 80 KiB per layer instead of 48 KiB, and add one
dispatch. The in-place form saves 32 KiB/layer ≈ 1.66 MB/token across 52
layers — real but tiny against the ~15.34 MB/layer o_proj weight stream, and
tiny again against the ~800 MB/token whole-model read. This is a “no
downside, take it” fusion of the pointwise kind: no reduction is moved, no
rounding changes (each element’s arithmetic is identical; only the buffer
destination differs), so it carries none of the exactness risk the norm
fusions of Ch 19 do. [unverified] whether the
32 KiB/layer saving is individually measurable in end-to-end tok/s — no
retained A/B isolates this kernel, and at 0.002 % of per-layer bytes it would
be below noise by construction.
A separate gate closure vs fusing the gate into attention or o_proj. The
gate could in principle be folded into the attention kernel’s epilogue
(compute σ(gate[h·128+d]) while writing the head) or into the o_proj’s
input load. The intuition pulls hard toward doing it: this book’s whole gap
story is told in dispatch counts, and here is a dispatch that moves rounding
error and computes one exponential per channel. Muser keeps it a standalone
closure on every route anyway — teacher
forced (decode.rs:5793-5799), batched serving, and the packed decode group
(decode.rs:5112-5116, where per-row attention/gate buffers are gathered
into shared batch buffers and one gate serves all rows).
We went to the closure-count accounting expecting to find the gate implicated somewhere, and it is not there. The measured consequence is structural rather than a timing number: in the one-token dispatch-gap accounting, the sigmoid-gate closures fall in the “common math closures” family that is identical in both the production and legacy graphs — 406 closures on each side, delta 0 [docs/decode-dispatch-gap-20260815.md, closure-count table]. So fusing the gate would trade a bit-exact match against the pinned graph for no reduction in the +196-closure gap at all, on the route that actually serves traffic. It stays separate because separate is exact.
Gate before o_proj vs gate after o_proj. The third fork was never ours to
take, and it is worth saying so before anyone spends a week on it. The model
applies the gate in
attention space (4,096 channels, pre-mixing) rather than in residual space
(6,656 channels). This is the checkpoint’s choice, mirrored by the oracle at
reference.rs:446-464; an engine has no say in it. The consequence for the
engine is only that the gate kernel is 4,096 wide rather than 6,656 wide —
and that the gate projection [6656 → 4096] is one of the four concurrent
matvecs of Ch 13, not a fifth sequential
one.
17.9 Where the gap lives
Every kernel chapter in this part owes the same answer, and the question behind it is one of suspicion: does this stage help explain the extra dispatch closures the campaign is hunting? Here the answer is short, and it is the boring one.
This kernel is not the gap. Both of this chapter’s operations appear in the “Common math closures (including LM head/softcap)” row of the corrected closure-count diff — 406 production versus 406 legacy, delta 0 [docs/decode-dispatch-gap-20260815.md]. The one-token graphs differ in norm boundaries, SWA staging, KV publication, and one copy — none of them here. The o_proj streams 15.34 MB/layer through the same pinned llama.cpp matvec the comparator itself runs (Ch 13), so there is no engine-specific bandwidth story to tell about it either. The gate’s 48 KiB is four orders of magnitude below the o_proj’s weights. When the dispatch-gap chapters (Ch 35, Ch 40) hunt the +196, this stage is already exonerated.
The attention half of the layer is closed: context was gathered, gated, and mixed back toward the residual stream. What remains of the layer is the part with most of the bytes — the feed-forward block, two 74.76 MB Q4_K streams and a learned gate of its own. Ch 18 teaches the FFN from zero.
References
crates/muser-engine/src/shaders/ferrite/sigmoid_gate.metal:7-16—sigmoid_gate_inplace, the whole kernel (this chapter’s primary source; the header dispatch comment at:5is stale versus the wrapper —dispatch_1dusesdispatch_threads,encode.rs:1337-1342).crates/muser-engine/src/metal/encode/gate.rs:7-23—encode_sigmoid_gate, the Rust dispatch (cross-vendor twin selected at:14-15).crates/muser-engine/src/shaders/muse_reference.metal:680-688—muser_cross_vendor_sigmoid_gate, the strict no-fast-math sibling.crates/muser-engine/src/decode.rs:5793-5805— the gate dispatch ando_projcall inencode_token;:5449-5451the one-encoder contract;:2085-2091the serving-versus-legacy exactness comment;:5112-5116the packed decode group’s shared gate.crates/muser-engine/src/decode.rs:6044-6091—encode_projection, the dtype router;crates/muser-engine/src/metal/encode/qkv.rs:429-450the one-token pinned-metallib matvec path;:451-459themuser_matvec_q4k_4r2sfallback.crates/muser-engine/src/metal/encode.rs:278-280—ggml_q4k/q5k/q6kPSO registration against the pinned llama.cpp metallib;:1337-1342dispatch_1d.crates/muser-engine/src/config.rs:266-273—attn_dim/kv_dim(“the space the attention-output gate lives in”);:300-314the per-layer tensor shape contract includingattn_gate.weight [h, attn]andattn_output.weight [attn, h].crates/muser-engine/src/lib.rs:7-15— the model-facts header asserting the sigmoid attention-output gate.crates/muser-engine/src/reference.rs:446-464— the CPU oracle’s gate-then-o_proj order (the parity specification).crates/muser-bench/src/m16.rs:139-198— the per-projection shape/dtype table (attn_q/gate 6656->4096 q4k,attn_v-q6k 6656->256,attn_output 4096->6656 q4k) backing §17.6’s budget.- [docs/decode-dispatch-gap-20260815.md] — the closure-count reconciliation; this stage is common math (406 = 406).
- Ch 13 — the pinned ggml matvec family
this chapter reuses; Ch 16 — what
produced
activations.attention. - Ch 12 — the fused dual-eps tail
that consumes this chapter’s
projectedoutput. - Ch 6 — the Q4_K 144-byte super-block behind the 15.34 MB arithmetic.
- Ch 35 — the hazard taxonomy behind §17.7’s ordering argument.
- [ferrite-book Ch 16] — the ancestor’s residual-fused o_proj matvec; the device Muser deliberately does not port onto the pinned kernel (§17.7).
Chapter 18 — The SwiGLU feed-forward block
status: polished · path: Muse Glimmer, pinned Muser tree
Prerequisites: Ch 2 (SIMD groups,
simd_sum, threadgroup memory), Ch 5 and Ch 6 (blocks, scales, the Q4_K super-block), Ch 12 (the dual-eps tail that produces this chapter’s input), Ch 13 (the matvec family and the pinned ggml kernels), Ch 17 (attention just closed into the residual stream). The FFN and its gated activation are taught from zero here.
18.1 What it computes
Attention has just folded its result back into the residual stream and handed the layer a vector. What does the layer do with it next? Half the layer’s work is still ahead, and it is the half that owns most of the weights on disk — so before any kernel, the question to settle is what shape that computation has and what it does to the vector it is given.
Each Muse Glimmer layer does two things to the residual stream: attention (Ch 13–Ch 17) mixes information across tokens; the feed-forward network (FFN) transforms the vector within a token — one position at a time, no cross-token mixing at all. Concretely it is two matvecs and a nonlinearity between them:
x : [6656] (hidden_dim — the ffn_norm'ed stream, Ch 12)
│ gate projection W_gate : [6656 → 19968] (Q4_K)
g : [19968] (intermediate_dim)
│ SiLU·Hadamard with the up branch
h : [19968] ("ffn_mid")
│ down projection W_down : [19968 → 6656] (Ch 19)
out : [6656]
The vector is blown up 6,656 → 19,968 — a 3× wider “thinking space” (the
checkpoint declares muse-glimmer.feed_forward_length,
config.rs:180) — and squeezed back down in Ch 19.
The widening is where most of the model lives: the two projections this
chapter covers are 74.76 MB each of Q4_K per layer (§18.7), against
48.4 MB for the entire attention block (Ch 17
Figure 17.2).
The combination rule between the two branches is SwiGLU — Figure 18.1 shows its wiring:
h = silu(W_gate · x) ⊙ (W_up · x)
Read the symbols: W_gate · x and W_up · x are two independent
[6656 → 19968] matvecs over the same input x; silu(·) is the
activation of §18.4 applied only to the gate branch; ⊙ is the
element-wise (Hadamard) product, (a ⊙ b)[j] = a[j] × b[j], no mixing
across the 19,968 coordinates.
18.2 Why it exists — the “thinking” half of the layer
Attention is the block’s mechanism for gathering context: it decides which earlier tokens this token looks at. The FFN is the mechanism for reasoning over what was gathered: a learned, position-independent transformation applied to the vector attention assembled. Every token at every layer walks the same FFN weights; what differs is the vector it walks in with. That is the “thinking” framing — and the size framing is starker: at ~30 B parameters, the FFN pair-plus-down is roughly 224–259 MB of the ~273–307 MB per-layer weight read (§18.7), so when Ch 1 said decode is ~99 % reading weights, this is where most of the weights are.
Why gated? A plain FFN (out = down(act(up(x))), the original
transformer’s shape [arxiv:1706.03762]) applies its activation
unconditionally per element — for feature j, “should this feature fire?”
and “what does it carry?” are the same number. A gated FFN splits them into
two learned projections: the gate branch answers should it fire, the up
branch answers what does it carry. The silu turns the gate into a smooth
on/off ramp. This structural claim is standard SwiGLU motivation
[arxiv:2002.05202]; the quality delta for Muse Glimmer specifically is
[unverified] here — this book does not retrain the model to A/B its own
architecture. The cost side, however, is exact arithmetic: gating means
two widening matrices instead of one, ~50 % more FFN parameters than the
ungated shape — and it is why W_gate and W_up both stream past every
token.
flowchart LR
x["x [6656]<br/>ffn_norm'ed stream"]
wg["W_gate<br/>[6656→19968] Q4_K"]
wu["W_up<br/>[6656→19968] Q4_K"]
g["g [19968]"]
u["u [19968]"]
sg["silu(g)"]
had["⊙ Hadamard"]
out["ffn_mid [19968]"]
x --> wg --> g --> sg --> had
x --> wu --> u --> had
had --> out
Figure 18.1: The SwiGLU dataflow. The fusion this chapter covers collapses
the two projections, the silu, and the Hadamard into one kernel; the down
projection that consumes ffn_mid is Ch 19.
18.3 The matrix operation, by hand
Trusting a fused kernel gets easier once you have done its arithmetic by hand. So: what exactly is computed for a single output coordinate, and where is the redundancy that a fusion could take away?
For output element j (0 ≤ j < 19,968):
g_j = Σ_{i=0..6655} W_gate[j, i] · x[i] one dot product over hidden_dim
u_j = Σ_{i=0..6655} W_up [j, i] · x[i] another, independent, same x
h_j = silu(g_j) · u_j the gated combination
Two dot products over the same x, then a pointwise combine. A toy
worked example with hidden = 2, one output element, invented numbers:
x = [1.0, −2.0]
W_gate row = [0.5, 0.25] → g = 0.5·1 + 0.25·(−2) = 0.0
W_up row = [2.0, −1.0] → u = 2.0·1 + (−1.0)·(−2) = 4.0
silu(0.0) = 0.0 · σ(0.0) = 0.0 · 0.5 = 0.0
h = 0.0 · 4.0 = 0.0 ← gate fully closed: nothing flows
Had the gate row been [1.0, 0.0] instead, g = 1.0, silu(1.0) ≈ 0.731,
and h ≈ 0.731 · 4.0 ≈ 2.92 — the same up-value flows, scaled by how open
the gate is. That decoupling is the whole idea.
The observation that motivates a fused kernel: both dot products read the
same x[k]. In an unfused pair of matvecs, x is fetched twice and both
19,968-wide intermediates (g and u) make a round trip through memory.
One kernel that loads x[k] once and updates two accumulators in
lockstep eliminates both — Figure 18.2 shows the loop shape.
18.4 SiLU — the sigmoid linear unit
Two branches go in and one comes out, and the function on the gate branch is the only nonlinearity in the whole block. It decides how much of the up branch survives. So it is worth a section of its own: what is it, and how does it behave at the edges where it will actually be asked to decide?
The activation on the gate branch is SiLU (a.k.a. Swish, [arxiv:1710.05941]):
silu(x) = x · σ(x) = x / (1 + e^(−x)) (σ from Ch 17 §17.1)
Behavior at the extremes — like ReLU at the ends, unlike it in the middle:
x | silu(x) | relu(x) | note |
|---|---|---|---|
0 | 0.000 | 0.000 | both zero |
1 | 0.731 | 1.000 | silu passes ~73 % |
−1 | −0.269 | 0.000 | silu dips negative |
2 | 1.762 | 2.000 | converging to identity |
−4 | −0.072 | 0.000 | the dip bottoms near x ≈ −1.28 at ≈ −0.278 |
Table 18.1: SiLU vs ReLU at five points (computed by hand from the
formula). Large positive x passes through nearly unchanged; large negative
x is suppressed — but small negative x lets the gate subtract a little,
not merely go silent.
Two properties matter downstream. First, SiLU is smooth and its gradient is
nonzero everywhere (ReLU has a dead zone for x < 0) — whether that is the
reason gated FFNs ship SiLU is [unverified] here; we inherit the
checkpoint’s choice. Second, it is cheap but not free: one exp, one add,
one divide per element. At 19,968 elements per layer it runs 19,968 times —
fully parallel, no reduction, and in every kernel this chapter quotes it is
folded into the final write.
The CPU oracle states the whole combination in two loops
(crates/muser-engine/src/reference.rs:511-513):
#![allow(unused)]
fn main() {
for (a, b) in ffn_a.iter_mut().zip(ffn_b.iter()) {
*a = silu_fast(*a) * *b;
}
}
The helper it calls is silu_fast(x) = x / (1.0 + (−x).exp()) — this
section’s formula written as a one-liner, inherited from Ferrite along with
the rest of the shader lineage. We kept the provenance:
crates/muser-engine/src/quant/helpers.rs:70-74, tracked through the
extraction manifest [docs/extraction-manifest.md].
18.5 The Metal kernel — ffn_q4k_gate_up_silu_4r2s
There are two gate+up routes in the tree, and which one runs is the
chapter’s real story (§18.6). The fused kernel first — it is the cleanest
expression of the SwiGLU fusion, a wholesale port of Ferrite’s accepted
897a6256b kernel, and both the call site and the wrapper say so in their
own comments (decode.rs:5823-5825, ffn.rs:7-8). Below: the signature,
the x-load, and the lockstep MAC, verbatim; the Q4_K decode helper it calls
is summarized after.
// crates/muser-engine/src/shaders/ferrite/ffn_fused_tail.metal:485
// ── ffn_q4k_gate_up_silu_4r2s ─────────────────────────────────────────────
//
// 4-row-per-TG fused gate+up variant using the V4 x-load pattern.
// 64 threads (2 SIMDs × 32), 4 output rows per TG (2 per SIMD).
// Each thread loads x into local registers yl[16]/yh[16] via stride-4
// block sub-groups (same as matvec_q4k_f32_v4), reusing x for both
// gate and up weight rows.
//
// Activation selected at PSO build time via FC_FFN_ACTIVATION.
// dispatch_thread_groups( (ceil(i_dim/4), 1, 1), (64, 1, 1) )
//
kernel void ffn_q4k_gate_up_silu_4r2s(
device const uchar* W_gate [[ buffer(0) ]],
device const uchar* W_up [[ buffer(1) ]],
device const float* x [[ buffer(2) ]],
device float* out [[ buffer(3) ]],
constant uint& rows [[ buffer(4) ]],
constant uint& cols [[ buffer(5) ]],
uint tgid [[ threadgroup_position_in_grid ]],
uint lid [[ thread_index_in_simdgroup ]],
uint sgid [[ simdgroup_index_in_threadgroup ]])
{
const uint n_blocks = cols / 256u;
const uint block_bytes = 144u;
const uint row_bytes = n_blocks * block_bytes;
// 2 rows per SIMD, 2 SIMDs = 4 rows per TG
const uint base_row = tgid * 4u + sgid * 2u;
if (base_row >= rows) return;
// V4 thread partitioning: 4 sub-groups of 8 threads for block stride
const uint ix = lid / 8u; // 0..3 — block stride index
const uint it = lid % 8u; // 0..7 — position within block
const uint iq = it / 4u; // 0 or 1 — half-block selector
const uint ir = it % 4u; // 0..3 — quarter within half
// x-vector pointer: each thread reads 8 positions per block (stride-4 blocks)
device const float* xp = x + ix * 256u + 64u * iq + 8u * ir;
float yl[16], yh[16];
float gate_sumf[2] = {0.f, 0.f};
float up_sumf[2] = {0.f, 0.f};
for (uint ib = ix; ib < n_blocks; ib += 4u) {
// Load x slice into registers (8 elements × 4 positions)
float4 sumy = {0.f, 0.f, 0.f, 0.f};
for (uint i = 0u; i < 8u; i++) {
yl[i] = xp[i]; sumy[0] += yl[i];
yl[i + 8] = xp[i + 32]; sumy[1] += yl[i + 8];
yh[i] = xp[i + 128]; sumy[2] += yh[i];
yh[i + 8] = xp[i + 160]; sumy[3] += yh[i + 8];
}
// Gate weight: 2 rows starting at base_row + sgid*2
device const uchar* gate_blk = W_gate + (ulong)base_row * (ulong)row_bytes
+ (ulong)ib * block_bytes;
q4k_v4_dual_row_mac(gate_blk, row_bytes, yl, yh, sumy, iq, ir, gate_sumf);
// Up weight: same 2 rows
device const uchar* up_blk = W_up + (ulong)base_row * (ulong)row_bytes
+ (ulong)ib * block_bytes;
q4k_v4_dual_row_mac(up_blk, row_bytes, yl, yh, sumy, iq, ir, up_sumf);
xp += 4u * 256u; // advance by 4 blocks (stride)
}
// Reduction across all 32 threads in each SIMD
const float gr0 = simd_sum(gate_sumf[0]);
const float gr1 = simd_sum(gate_sumf[1]);
const float ur0 = simd_sum(up_sumf[0]);
const float ur1 = simd_sum(up_sumf[1]);
if (lid == 0u) {
out[base_row] = apply_activation(gr0, ur0);
if (base_row + 1u < rows) out[base_row + 1u] = apply_activation(gr1, ur1);
}
}
Line by line:
- Bindings (496–505). Two weight buffers (
W_gateslot 0,W_upslot 1), the inputx(slot 2), the outputout(slot 3), androws/colsas inline constants (19,968/6,656on this model). - Row ownership (511–513).
base_row = tgid·4 + sgid·2: one threadgroup of 64 threads = 2 SIMD groups, each SIMD group owns two whole output rows. Four rows per threadgroup; the grid of §18.6 covers 19,968 rows. - The V4 lane decomposition (515–522). The 32 lanes split into 4
sub-groups of 8 (
ix), each striding a different Q4_K super-block (ib += 4); within a sub-block,iq/irselect which of the eight 32-element runs this lane dots. This is the same x-in-registers pattern as the QKV matvec family of Ch 13 — the header says so (“same as matvec_q4k_f32_v4”). - The register x-cache (524–536). Each thread pulls its 32 x-slice
elements (
yl[16],yh[16]) and their quarter-sumssumyinto registers once per block iteration. The foursumypartial sums exist for the Q4_K min-term subtraction (w = d·sc·nib − dmin·m, Ch 6). - The lockstep MAC (538–548). This is the fusion. The same register-held
yl/yh/sumyfeed two calls toq4k_v4_dual_row_mac— once against the gate rows, once against the up rows at the samebase_row:
┌─► gate_sumf[r] += dequant(W_gate[base_row+r, k]) · x[k]
x[k] (registers) ───┤
└─► up_sumf[r] += dequant(W_up [base_row+r, k]) · x[k]
Figure 18.2: The lockstep MAC. Every x element is loaded from device memory
once (into yl/yh) and consumed by both the gate and the up accumulator
for two rows each — one load, four dot-product contributions.
q4k_v4_dual_row_mac itself (shaders/ferrite/_q4k_helpers.metal:34-88)
decodes two consecutive Q4_K rows’ block: it unpacks the 6-bit scale strip
with the 0x3F3F/0x0F0F/0xC0C0 masks, accumulates nibble·x products into
four float4 lanes per row, and folds them into the
d·(Σ…) − dmin·(Σ…) super-block epilogue — the deferred-scaling schedule
of Ch 13, here applied to two rows and
two matrices at once.
- Reduction and activation (551–560).
simd_sumcollapses each accumulator across the 32 lanes; lane 0 of each SIMD group writes the two finished rows viaapply_activation(g, u). There is no cross-SIMD combine and nothreadgroup_barrier— whole-row ownership per SIMD group means each group’ssimd_sumis the final answer for its rows. The ownership implies synchronization freedom, the same argument as Ch 17 §17.7.
apply_activation is compiled, not branched. The helper is selected at
PSO build time through a Metal function constant:
// crates/muser-engine/src/shaders/ferrite/ffn_fused.metal:15
// 0 = SiLU (default), 1 = GELU
constant uint FC_FFN_ACTIVATION [[function_constant(32)]];
constant bool HAS_FC_FFN_ACTIVATION = is_function_constant_defined(FC_FFN_ACTIVATION);
// Activation helper — compiled away at pipeline creation time (zero runtime cost)
inline float apply_activation(float g, float up) {
if (HAS_FC_FFN_ACTIVATION && FC_FFN_ACTIVATION == 1u) {
// … (GELU branch elided) …
} else {
// SiLU (default): g * sigmoid(g) * up
return (g / (1.0f + exp(-g))) * up;
}
}
Build the PSO without slot 32 and the dead-code eliminator removes the GELU
path entirely; the shipped Muse kernel contains only the SiLU line —
silu(g) · u, the chapter’s formula, as the kernel’s last instruction
(ffn_fused.metal:14-30, the Ch 4
function-constant mechanism).
18.6 The Rust dispatch — and the opt-in flag story
Two questions decide what this section owes you. How does the host launch that kernel? And — since the tree carries both routes — which one actually runs when the engine serves a token? The first has a short answer. The second is this chapter’s war story.
The wrapper first:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/metal/encode/ffn.rs:7
/// Ferrite 897a6256b Q4_K SiLU gate+up route: four rows per threadgroup,
/// two SIMD groups, with the input vector shared by both projections.
pub fn encode_ffn_q4k_gate_up_silu_4r2s(
&self,
encoder: &ComputeCommandEncoderRef,
gate_weights: GpuByteView<'_>,
up_weights: GpuByteView<'_>,
input: &GpuBuffer,
output: &GpuBuffer,
intermediate_dim: usize,
hidden_dim: usize,
) {
let row_bytes = hidden_dim / 256 * 144;
debug_assert_eq!(gate_weights.len(), intermediate_dim * row_bytes);
// … (up/input/output length asserts elided) …
self.bind(encoder, "ffn_q4k_gate_up_silu_4r2s");
encoder.set_buffer(0, Some(gate_weights.metal()), gate_weights.offset() as u64);
encoder.set_buffer(1, Some(up_weights.metal()), up_weights.offset() as u64);
encoder.set_buffer(2, Some(input.metal()), 0);
encoder.set_buffer(3, Some(output.metal()), 0);
set_value(encoder, 4, &(intermediate_dim as u32));
set_value(encoder, 5, &(hidden_dim as u32));
encoder.dispatch_thread_groups(
MTLSize::new(intermediate_dim.div_ceil(4) as u64, 1, 1),
MTLSize::new(64, 1, 1),
);
}
}
In prose: grid (19,968 ÷ 4, 1, 1) = (4,992, 1, 1) threadgroups of
(64, 1, 1) threads — 4,992 × 4 = 19,968 rows covered exactly. The two
weight views are bound at their offsets into the mmap’d GGUF
(Ch 3); rows/cols ride as inline
constants.
Now the fork. We had a kernel that was better on paper in every dimension we knew how to count — fewer dispatches, fewer bytes moved, the same algebra — and it arrived as a port of something Ferrite had already accepted and run. The obvious move was to make it the FFN path and move on. We expected to do exactly that. Instead: the fused kernel is opt-in, and the default is the unfused control. The gate at the call site shows both branches side by side, which is why it is worth reading whole:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/decode.rs:5819
if self.ferrite_ffn_gate_up
&& layer.ffn_gate.layout.dtype == GgmlType::Q4_K
&& layer.ffn_up.layout.dtype == GgmlType::Q4_K
{
// Port Ferrite 897a6256b wholesale: the four-row/two-SIMD-
// group kernel reads the normalized input once for both Q4_K
// projections and writes the final SiLU(gate) * up row.
dispatch(command, |encoder| {
self.kernels.encode_ffn_q4k_gate_up_silu_4r2s(
encoder,
layer.ffn_gate.view(&self.mapped_weights),
layer.ffn_up.view(&self.mapped_weights),
&self.activations.post_norm,
&self.activations.ffn_gate,
cfg.intermediate_dim,
cfg.hidden_dim,
);
});
} else {
// Exact upstream-matvec control and non-Q4_K fallback.
dispatch(command, |encoder| {
self.encode_projection(
encoder,
&layer.ffn_gate,
&self.activations.post_norm,
&self.activations.ffn_gate,
1,
);
self.encode_projection(
encoder,
&layer.ffn_up,
&self.activations.post_norm,
&self.activations.ffn_up,
1,
);
});
dispatch(command, |encoder| {
self.kernels.encode_silu_mul(
encoder,
&self.activations.ffn_gate,
&self.activations.ffn_up,
);
});
}
}
ferrite_ffn_gate_up is set from the environment —
MUSER_FERRITE_FFN_GATE_UP (decode.rs:1334) — so by default the engine
takes the else branch: two pinned ggml matvecs (W_gate, W_up, the
exact kernel_mul_mv_q4_K_f32 of Ch 13)
plus one pointwise activation kernel. That third kernel is
muser_silu_mul_inplace, which does gate[i] = silu(gate[i]) · up[i] — the
same formula as the fused tail, materialized:
// crates/muser-engine/src/shaders/muse_reference.metal:4
kernel void muser_silu_mul_inplace(
device float *gate [[buffer(0)]],
device const float *up [[buffer(1)]],
constant uint &count [[buffer(2)]],
uint index [[thread_position_in_grid]]) {
if (index < count) {
float value = gate[index];
gate[index] = (value / (1.0f + exp(-value))) * up[index];
}
}
dispatched one-thread-per-element over 19,968 (ffn.rs:38-60).
So why is the better kernel the one that does not run? Because when the fused tail was put on the serving route, the numbers coming out of the model stopped matching the numbers the pinned comparator produced. Not wrong — different, and different by more than the public tolerance allows. That result is written down where it belongs, in the routing comment that governs serving decode:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/decode.rs:2085
// The legacy one-token graph uses Ferrite fused residual/norm and
// gate-up kernels whose rounding diverges from the source-pinned
// llama Metal graph enough to breach public logprob tolerance.
// The one-row batch graph dispatches the exact pinned kernels and
// has the same KV transition, so it is the serving correctness
// path until each fused kernel independently passes full-logit
// parity.
}
That is the contract discipline in one comment. The fused kernel’s reduction
order — two interleaved accumulators folded per super-block — is
mathematically SwiGLU, but it is not bit- the same as llama.cpp’s graph
of independent mul_mv nodes plus a pointwise silu-mul, and Muser’s public
commitment is logprob parity with the pinned comparator.
Say it the other way round, because this is the sentence the rest of the book keeps coming back to. Floating-point addition is not associative, so changing when you add changes what you get; a contract written against a specific graph is therefore a contract against a specific order of additions, not against the algebra those additions approximate. A kernel that computes the same function by a different schedule is, by that measure, a different kernel — and the measure is the one the public promise is written in.
The lesson we kept is the uncomfortable one: fusion is never free, even when
it is free, because what it spends is exactness, and here exactness is the
product. The fused kernel therefore lives on as a qualified-off fast path,
runnable under the flag for the teacher-forced/diagnostic route
(encode_token) and never on the serving default. The dtype guards in the
if are a smaller version of the same care — any non-Q4_K FFN tensor falls
back automatically, and on the release artifact ffn_gate/ffn_up are
Q4_K. We kept that evidence too: ffn_gate/up 6656->19968 q4k,
crates/muser-bench/src/m16.rs:171-175.
18.7 The access pattern — the largest weight read in the layer
Where does the time go in this block? Not into the silu, and not into the Hadamard: both are pointwise, and both vanish into the write. It goes into dragging two very large matrices past the arithmetic units, once for every token the model emits. It is worth knowing exactly how large, and worth knowing what the fusion actually buys against that background — because the answer decides whether the flag we just described gave up something expensive or something cheap.
All arithmetic derived from the verified shapes, shown step by step:
W_gate: 19,968 rows × (6,656 / 256 = 26 blocks) × 144 B = 19,968 × 3,744
= 74,760,192 B ≈ 74.76 MB (Q4_K: 0.5625 B/element)
W_up : same shape, same dtype = 74,760,192 B ≈ 74.76 MB
────────────────────────
gate + up pair per layer ≈ 149.5 MB ← read once per token
(for scale: the whole attention block ≈ 48.4 MB; o_proj alone 15.34 MB,
Ch 17 Figure 17.2; the down projection is Ch 19: 74.76 MB Q4_K /
109.03 MB Q6_K)
Activation traffic, fused vs unfused (per layer):
FUSED (one kernel):
read x [6656] f32 26,624 B (registers thereafter)
read W_gate + W_up 149,520,384 B
write ffn_mid [19968] f32 79,872 B
UNFUSED (two matvecs + silu_mul — the default):
read x twice 53,248 B
read W_gate + W_up 149,520,384 B
write g [19968], u [19968] 159,744 B ← intermediates born
read g + u 159,744 B ← …and read back
write ffn_mid 79,872 B
──────────
extra activation traffic vs fused: ≈ 345,856 B ≈ 338 KiB/layer
Hold the two ledgers side by side and the same lesson falls out that the
ancestor’s FFN chapter [ferrite-book Ch 17] drew, re-derived here for this
geometry. Reading x once is mostly a cache effect — x is 26 KiB and
would have been sitting in cache for the second matvec anyway. The hard
saving is the one that never shows up as a load at all: in the unfused
route the two 19,968-wide intermediates are born, written, and read back;
in the fused route they simply never exist.
Then scale it up before deciding how much to care. 338 KiB/layer × 52 layers ≈ 18.0 MB/token of avoided activation traffic — about 12 % of one layer’s FFN weight read. Real, then, but second-order; and on the serving route it is deliberately not taken (§18.6). What is neither second-order nor optional is the weight stream underneath it: 149.5 MB per layer, 7.78 GB across 52 layers, irreducible at Q4_K bitrate no matter which route dispatches it. That is the number Ch 1’s bandwidth argument leans on.
18.8 Tradeoffs
Fused 4r2s vs the unfused control — bytes versus bits. The fusion saves
~338 KiB/layer of activation round-trips and one dispatch: three closures
become one. The control gives up both of those and buys back llama.cpp’s
exact per-node arithmetic, and with it the public logprob contract. Muser
ships the control as the default and gates the fusion behind
MUSER_FERRITE_FFN_GATE_UP, and the reason lives in the source, where the
fused kernels’ rounding divergence “breache[s] public logprob tolerance”
against the source-pinned llama Metal graph (decode.rs:5819-5836 and
:1334 for the flag, decode.rs:2085-2091 for the reason).
That verdict was not invented here. The dispatch-gap investigation had already made the discipline explicit for a whole family of these fusions: they were “removed, not hidden behind a tolerance” [docs/decode-dispatch-gap-20260815.md, Rejected hybrid postmortem]. A flag is the gentlest form of that same judgement — the code survives, the default does not move. What we still cannot tell you is what the flag would be worth end to end: no retained A/B quotes a tok/s delta for this specific flag [unverified]. The burden the fused route has to clear is full-logit parity first, per the comment, and at the pin that gate is not recorded as passed.
Two accumulators in lockstep vs two kernels. The 4r2s design doubles
down on the V4 pattern of Ch 13: the
register x-cache is shared across two matrices and two rows
simultaneously — one x load feeds gate_sumf[0..1] and up_sumf[0..1].
The cost is register pressure (yl[16], yh[16], sumy, four running
accumulators) and a kernel that only exists for Q4_K (the q4k_v4_… helper
is Q4_K-specific; Q5_K/Q6_K tensors would need their own decoders — hence
the dtype guard at decode.rs:5820-5821). The payoff is the traffic ledger
of §18.7.
The design did not arrive in that shape, either. An older 4-SIMD-group
variant — ffn_q4k_gate_up_silu_4sg, one row per threadgroup, 128 threads —
sits in the same shader file at ffn_fused_tail.metal:295, with a
threadgroup-x-cache sibling at :392. Both are the same idea at a coarser
grain of x-reuse, and both were kept rather than deleted; the 4r2s port at
:496 is the one Muser wired.
SiLU vs GELU vs ReLU. The choice is the checkpoint’s, not the engine’s;
the function-constant mechanism (§18.5) exists so one .metal source could
serve either at zero runtime cost. Muse Glimmer is SiLU — every route
(fused apply_activation, control muser_silu_mul_inplace, CPU oracle
silu_fast) implements x·σ(x).
The normed-quant tail variants — present but unwired. The shader library
carries a family that goes one step further than 4r2s: fuse the norm into
the gate+up read, so that the FFN would consume the raw residual and
normalize in-kernel. The kernel is ffn_q4k_gate_up_silu_normed, at
shaders/ferrite/ffn_fused_normed_quant.metal:296, with Q5_K siblings at
:1 and :190. We went looking for whoever calls it, and found nobody: at
the pinned commit no Rust wrapper binds any kernel from that file
(verified: no reference in crates/muser-engine/src). It is not on a live
path — retained as Ferrite-lineage research material.
The reason that belongs in a tradeoffs section rather than a footnote is that it names the ceiling of the whole fusion strategy. Its fate is the story of §18.6 taken one step further: the more arithmetic you fold across a norm boundary, the harder bit-exactness becomes (Ch 19 §19.9 makes that tradeoff precise).
18.9 Where the gap lives
The gate+up stage is not the gap — but its fusion is one of the
casualties of the exactness contract. In the one-token closure accounting,
the FFN gate-up and swiglu closures are “common math” — identical counts in
the production and legacy graphs (the 406 = 406 row of
[docs/decode-dispatch-gap-20260815.md]); the +196-closure gap lives in norm
boundaries, SWA staging, KV publication, and one copy, not here. The FFN’s
connection to that story is the reverse direction: the fused kernel this
chapter teaches is part of the legacy route’s fusion set, and the serving
graph pays extra closures (two matvecs + silu_mul instead of one) precisely
to keep llama.cpp’s node-for-node arithmetic (decode.rs:2085-2091). When
Ch 35 and
Ch 40 audit what was measured and
rejected, this is a standing example: a structurally sound bandwidth win,
held out of serving by a logprob tolerance, exactly as the campaign’s
fail-closed culture requires.
The FFN is half closed: ffn_mid [19968] holds the gated, activated
intermediate. One projection remains — the squeeze back to 6,656, the
residual add, and the fused tail that hands the next layer its normed input.
That tail is also where this book’s central tradeoff — dispatch count versus
the logprob contract — gets priced to the last ULP. It is
Ch 19.
References
crates/muser-engine/src/shaders/ferrite/ffn_fused_tail.metal:485-561—ffn_q4k_gate_up_silu_4r2s, the fused Q4_K SwiGLU kernel (primary source; the_4sgancestor variant at:295,_4sg_tgcacheat:392).crates/muser-engine/src/shaders/ferrite/ffn_fused.metal:14-30—FC_FFN_ACTIVATIONfunction constant +apply_activation(PSO-build-time SiLU/GELU selection).crates/muser-engine/src/shaders/ferrite/_q4k_helpers.metal:34-88—q4k_v4_dual_row_mac, the dual-row Q4_K MAC both projections share.crates/muser-engine/src/metal/encode/ffn.rs:7-36—encode_ffn_q4k_gate_up_silu_4r2s(grid(i_dim/4, 1, 1)× 64 threads);:38-60encode_silu_mul(control-route activation).crates/muser-engine/src/shaders/muse_reference.metal:4-13—muser_silu_mul_inplace, the pointwise control kernel.crates/muser-engine/src/decode.rs:5819-5862— the flag/dtype gate, the fused dispatch, and the unfused control;:1334theMUSER_FERRITE_FFN_GATE_UPenv read;:2085-2091the serving-exactness comment;:5165-5185the packed decode group’s unfused FFN.crates/muser-engine/src/reference.rs:493-516— the CPU oracle’s gate/up/silu·mul order;quant/helpers.rs:70-74silu_fast.crates/muser-engine/src/config.rs:180—intermediate_dimfrommuse-glimmer.feed_forward_length.crates/muser-bench/src/m16.rs:171-175—ffn_gate/up 6656->19968 q4k(release-artifact dtype evidence).crates/muser-engine/src/shaders/ferrite/ffn_fused_normed_quant.metal:1,190,296— the normed-input fused FFN family; no Rust binder at the pin (§18.8).- [docs/decode-dispatch-gap-20260815.md] — closure accounting (common math) and the rejected-hybrid postmortem’s remove-don’t-tolerate discipline.
- [docs/extraction-manifest.md] —
silu_fastand the shader lineage from Ferrite83cfd55…/a85048a9…. - Ch 13 — the V4 lane decomposition and the pinned ggml matvec the control route dispatches.
- Ch 6 — the Q4_K super-block and dequant formula behind the MAC helper.
- Ch 1 — the per-token weight-read arithmetic this block dominates.
- [ferrite-book Ch 17] — the ancestor’s SwiGLU chapter; the fused-vs-control honesty pattern and the intermediate-buffer byte ledger ported here.
- [arxiv:1706.03762] — Vaswani et al., Attention Is All You Need (the ungated ReLU FFN).
- [arxiv:1710.05941] — Ramachandran et al., Searching for Activation Functions (Swish/SiLU).
- [arxiv:2002.05202] — Shazeer, GLU Variants Improve Transformer (SwiGLU).
Chapter 19 — The down projection + residual
status: polished · path: Muse Glimmer, pinned Muser tree
Prerequisites: Ch 6 (Q4_K and Q6_K block layouts), Ch 12 (the dual-eps sandwich and
muser_fused_norm_residual_rms_norm_32sg), Ch 13 (the pinned ggml matvec family), Ch 17–Ch 18 (the layer so far;ffn_midis waiting). This chapter closes the layer — and then prices what closing it costs in dispatch groups, which is where this book’s central tradeoff becomes concrete.
19.1 What it computes
Ch 18 left a [19,968] vector in
activations.ffn_gate — the gated, activated FFN intermediate. It is the
widest thing a layer ever holds, and it is the wrong shape to hand back to
the loop. So the closing question of every layer is a plumbing question:
how does that wide intermediate get folded back into the narrow residual
stream, and who prepares the stream for the layer that comes next? Two
operations answer it:
1. projected = W_down · ffn_mid W_down : [6656 × 19968]
2. residual += post_norm(projected) (eps 1e-8)
next_input = rms_norm(residual, next_norm) (eps 1e-5)
Operation 1 is a matvec — the last one of the layer, and on some layers the most expensive single weight read in it (§19.7). Operation 2 is the second fused dual-eps tail: the sandwich of Ch 12 paying off, where the same kernel that adds the FFN delta into the residual also computes the next layer’s normalized input (or, after layer 51, the final norm that feeds the LM head of Ch 20).
After operation 2, activations.normed holds the residual stream plus
layer l’s attention delta and FFN delta, normalized for layer l+1 — and
the 52-layer loop takes its next turn.
19.2 Why it exists — closing the block and opening the next
The down projection is the FFN’s exit: without it the layer would emit a
19,968-wide vector into a 6,656-wide stream — wrong shape, and the next
layer’s attention would have nothing to read. The residual add is what
makes deep transformers deep: each layer contributes into a running sum
rather than replacing it, so the gradient path to early layers stays
near-identity (∂(x + f(x))/∂x = 1 + f'(x)). And the tail’s second norm
exists because Muse Glimmer sandwiches every sub-block between a pre-norm
and a post-norm (Ch 12) — the
post-FFN norm (1e-8) scales the delta before it lands in the residual, and
the next pre-norm (1e-5) prepares the stream for the next attention.
Figure 19.1 lays out the whole layer with both tails.
┌────────────── layer l ────────────────────────────────────────────┐
│ [Ch 17] attention ─► gate ─► o_proj ─► TAIL#1: residual += │
│ post_norm(o) (1e-8); │
│ ffn_in = norm (1e-5) │
│ [Ch 18] ffn_in ─► gate·x, up·x ─► silu⊙ ─► ffn_mid [19968] │
│ [Ch 19] ffn_mid ─► W_down ─► TAIL#2: residual += │
│ post_ffn_norm(·) (1e-8); │
│ next_in = norm(residual, 1e-5) │
└───────────────────────────────────────────────────────────────────┘
│ after layer 51: next_in feeds
▼ the final norm → LM head (Ch 20)
Figure 19.1: One layer, two tails. This chapter is the W_down matvec and
TAIL#2.
19.3 The matrix operation — and the Q6_K wrinkle that is real here
Start with the question we actually wanted answered here: how many bytes does this matvec read? The mechanism is the family you already know — 6,656 output rows, each a dot product over 19,968 inputs — so we expected to multiply one row size by one row count and be done in a paragraph. The artifact would not give a single answer. What distinguishes this projection is its dtype mix: it is the one weight in the layer that is not stored in a single format.
On the kquant release artifact, ffn_down tensors come in both Q4_K and
Q6_K — the verify-shape table lists ffn_down-q4k 19968->6656 q4k and
ffn_down-q6k 19968->6656 q6k side by side
(crates/muser-bench/src/m16.rs:179-194), and the quickstart warns that a
build without the pinned llama.cpp metallib “fails closed … because Q6_K
tensors route through” it (docs/quickstart.md:16). The per-layer split —
which of the 52 layers carry which variant — lives in the GGUF tensor
headers and is not recorded in the repo docs this book cites
[unverified]; what is verifiable is that both variants exist on live
paths and both dispatch through the pinned metallib.
The two formats in bytes, step by step (layouts from Ch 6):
W_down row = 19,968 inputs = 19,968/256 = 78 super-blocks
Q4_K: 78 × 144 B = 11,232 B/row → 6,656 × 11,232 = 74,760,192 B ≈ 74.76 MB
Q6_K: 78 × 210 B = 16,380 B/row → 6,656 × 16,380 = 109,025,280 B ≈ 109.03 MB
─────────────────────────────────────
Q6_K / Q4_K = 210/144 = 1.4583 → +45.8 % bytes for the 6-bit format
So a Q6_K-down layer reads ~258.6 MB of FFN weights (gate 74.76 + up 74.76 +
down 109.03) against a Q4_K-down layer’s 224.3 MB. The ancestor book’s
Q4_K_M mix table [ferrite-book Ch 18] taught exactly this device — spend
extra bits on the projection whose output lands directly in the residual
stream — and Muse Glimmer’s artifact realizes the same idea with its own
(per-layer, GGUF-internal) split. The intuition is worth saying twice,
because it is the entire reason a mixed-format checkpoint exists at all:
an error made anywhere else in the layer is consumed and largely forgotten
inside that layer, but an error made in ffn_down is added into the
residual stream, and every remaining layer carries it forward. Bits spent
on this projection buy quiet downstream. Whether the quality payoff
justifies the +45.8 % on the layers that take it is the checkpoint
author’s call, inherited not measured [unverified].
The tail is this chapter’s kernel, so operation 2, the layer’s real exit,
deserves a worked example small enough to check by hand. Watch what the two
epsilons actually do to a vector: the first normalization shrinks the
delta before it lands, the second renormalizes the sum after it has
landed. Take n = 4, residual = [1, 2, 3, 4],
projected = [4, 0, 0, 0],
post_weight = [1,1,1,1], next_weight = [1,1,1,1], both eps tiny:
post_norm(projected, 1e-8): rms = √(16/4) = 2 → [2, 0, 0, 0]
residual += → [3, 2, 3, 4]
next_norm(residual, 1e-5): rms = √((9+4+9+16)/4) = √9.5 ≈ 3.082
next_input → [0.973, 0.649, 0.973, 1.297]
One kernel, two normalizations, one add — with a device-memory publication between them that turns out to be load-bearing (§19.9).
19.4 The Metal kernel — muser_fused_norm_residual_rms_norm_32sg
What does one kernel have to do to be simultaneously a layer’s exit and the next layer’s entrance? It has to normalize with one epsilon, add, and normalize again with a different epsilon — and it has to do all of that without ever letting the two reductions see each other’s rounding. Here is the tail kernel, verbatim. It is the decode-only member of the sandwich family Ch 12 introduced; read it here as the layer-exit machine:
// crates/muser-engine/src/shaders/ferrite/rmsnorm_batch_tail.metal:142
// Decode-only dual-epsilon tail fusion:
// hidden += rms_norm(src, eps1) * weight1
// output = rms_norm(hidden, eps2) * weight2
// Muse uses eps1=1e-8 for sandwich post-norms and eps2=1e-5 for the
// following pre-norm, so the older single-epsilon batch kernel is not valid.
kernel void muser_fused_norm_residual_rms_norm_32sg(
device float* hidden [[buffer(0)]],
device const float* src [[buffer(1)]],
device float* output [[buffer(2)]],
device const float* weight1 [[buffer(3)]],
device const float* weight2 [[buffer(4)]],
constant uint& n [[buffer(5)]],
constant float& eps1 [[buffer(6)]],
constant float& eps2 [[buffer(7)]],
uint row [[threadgroup_position_in_grid]],
uint tid [[thread_index_in_threadgroup]],
uint sgitg [[simdgroup_index_in_threadgroup]],
uint lid [[thread_index_in_simdgroup]],
threadgroup float* shared [[threadgroup(0)]]) {
const uint n4 = n >> 2u;
device float4* hidden4 = (device float4*)(hidden + row * n);
device const float4* src4 = (device const float4*)(src + row * n);
device float4* output4 = (device float4*)(output + row * n);
device const float4* weight14 = (device const float4*)weight1;
device const float4* weight24 = (device const float4*)weight2;
float sum_src = 0.0f;
for (uint i = tid; i < n4; i += 1024u)
sum_src += dot(src4[i], src4[i]);
sum_src = simd_sum(sum_src);
if (lid == 0u) shared[sgitg] = sum_src;
threadgroup_barrier(mem_flags::mem_threadgroup);
if (tid == 0u) {
float total = 0.0f;
for (uint group = 0u; group < 32u; ++group) total += shared[group];
shared[32] = rsqrt(total / float(n) + eps1);
}
threadgroup_barrier(mem_flags::mem_threadgroup);
const float inv_src = shared[32];
float sum_hidden = 0.0f;
for (uint i = tid; i < n4; i += 1024u) {
const float4 value = hidden4[i] + src4[i] * inv_src * weight14[i];
hidden4[i] = value;
sum_hidden += dot(value, value);
}
sum_hidden = simd_sum(sum_hidden);
if (lid == 0u) shared[sgitg] = sum_hidden;
threadgroup_barrier(mem_flags::mem_threadgroup);
if (tid == 0u) {
float total = 0.0f;
for (uint group = 0u; group < 32u; ++group) total += shared[group];
shared[32] = rsqrt(total / float(n) + eps2);
}
threadgroup_barrier(mem_flags::mem_threadgroup);
const float inv_hidden = shared[32];
for (uint i = tid; i < n4; i += 1024u)
output4[i] = hidden4[i] * inv_hidden * weight24[i];
}
Three passes, two barriers apiece:
- Pass 1 (sum of squares of
src). Every thread strides the row’sfloat4s (i += 1024); each of the 32 SIMD groups reduces withsimd_sum, writes one partial toshared[sgitg]; thread 0 sums the 32 partials in order and postsrsqrt(mean + eps1)toshared[32]. This 32-partial, fixed-order reduction is the pinned llama.cpp shape. - Pass 2 (the residual add — the in-place mutation). Each thread
computes
value = hidden + src·inv_src·weight1, writes it back intohidden, and accumulatesdot(value, value)for the second norm. This is wherehidden += …physically happens: the same buffer is read, added into, and rewritten,float4by disjointfloat4. - Pass 3 (the next norm). With
inv_hiddenfrom the second reduction,output[i] = hidden[i] · inv_hidden · weight2.weight1is the layer’spost_ffw_norm;weight2is the next layer’sattn_norm(or the finaloutput_normafter layer 51) — chosen by the caller (§19.5).
That “in order” in the first pass looks like pedantry, and it is the part
of the kernel we got wrong first. The fork was how to fold a threadgroup’s
partial sums: an earlier version of the tail reduced across four SIMD
groups rather than the full complement, gathering fewer partials in
whatever order they arrived. Same summands, same mean, same rsqrt — we
expected the same bits out. We did not get them. The dispatch-gap
investigation found that earlier four-SIMD-group rsqrt variant “not
exact” and replaced it with precisely the shape quoted above
[docs/decode-dispatch-gap-20260815.md, “The corrected fusion”]. The lesson
is one this chapter keeps circling back to: when a kernel’s job is to match
another engine bit for bit, the order of a floating-point reduction is
part of the interface, not an implementation detail. Add the same numbers
in a different sequence and you get a different number.
Safety of the in-place add, in full: one threadgroup owns one row
(grid = rows), and within the threadgroup each thread owns a disjoint
strided set of float4 slots — no two threads touch the same i in any
pass, so no in-dispatch race on hidden. The barriers order the shared
rsqrt handoffs, not the data writes (each thread’s pass-3 reads the same
slots it wrote in pass 2). Put it plainly: the kernel is allowed to
scribble on the residual stream in place because, for the duration of the
dispatch, nothing else is looking at it — no other thread, no other
threadgroup. Across dispatches, the consumer of hidden/
output is the next layer’s first kernel, sequenced by the tracked-buffer
ordering of the single-encoder token graph that
Ch 17 §17.7 introduced;
Ch 35 formalizes that
taxonomy.
One geometry note from the wrapper, because it explains the kernel’s odd
proportions: 32 SIMD groups keep “the 6,656-wide Muse tail resident” —
1,024 threads, one row per threadgroup
(crates/muser-engine/src/metal/encode/norm.rs:236-240), with 33 floats of
threadgroup memory padded to 144 bytes for alignment.
19.5 The Rust dispatch — the layer exit in source
Two dispatches close the layer, and only one of them is interesting. The
down projection is the stock project wrapper — the same pinned ggml
matvec the rest of the layer uses, with the dtype routed automatically.
Q4_K or Q6_K ffn_down both land on kernel_mul_mv_q{4,6}_K_f32, and
the rows-per-group table that picks the launch geometry — the
(144, 2)/(210, 2) rows Ch 13 walked
through — is read out of qkv.rs:429-450. The mixed dtype we made so much
of above costs the dispatch code nothing at all; it is a table lookup.
The interesting dispatch is what follows — the tail and its next_norm
selection, which is where the layer decides who it is handing off to:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/decode.rs:5863
self.project(
command,
&layer.ffn_down,
&self.activations.ffn_gate,
&self.activations.projected,
);
let (next_norm, next_output) = if layer_index + 1 < cfg.n_layers {
(
&self.layers[layer_index + 1].attn_norm,
&self.activations.post_norm,
)
} else {
(&self.output_norm, &self.activations.hidden)
};
dispatch(command, |encoder| {
self.kernels.encode_fused_norm_residual_rms_norm_32sg(
encoder,
&self.activations.normed,
&self.activations.projected,
next_output,
&layer.post_ffn_norm,
next_norm,
cfg.hidden_dim,
cfg.post_norm_eps,
cfg.rms_eps,
);
});
}
Read the buffer wiring carefully — it is the residual-stream bookkeeping of the whole graph in one call:
hidden←activations.normed: the running residual, updated in place.src←activations.projected: the down-proj output (scratch, like o_proj’s output in Ch 17).next_output←activations.post_normfor layers 0..50 (the next layer’s normalized input), butactivations.hiddenfor layer 51 — after the last layer the tail’s second norm is the final norm, writing the vector the LM head consumes (Ch 20 picks it up there). That is what “fused into last tail (decode.rs:5875)” means: there is no separate final-norm dispatch on this route.weight1←layer.post_ffn_norm(1e-8),weight2← the next layer’sattn_norm(1e-5) — the sandwich hand-off.
The wrapper itself (norm.rs:163-241) binds the six buffers, pushes n,
eps1, eps2 inline, sets 144 bytes of threadgroup memory, and dispatches
(rows, 1, 1) × (1024, 1, 1) — one row per threadgroup, 32 SIMD groups.
The first tail of the layer (post-attention, decode.rs:5806-5818) is the
identical call with post_attn_norm/ffn_norm — same kernel, both
boundaries.
19.6 The access pattern
Where does the time go in this pair of dispatches? The answer is lopsided enough to be worth stating before the arithmetic: one of the two operations moves essentially all of the bytes, and the other is free. Down projection per layer:
Q4_K: read W_down 74,760,192 B read ffn_mid 79,872 B write projected 26,624 B
Q6_K: read W_down 109,025,280 B (same activation traffic)
weight : activation ratio ≈ 74.8 MB : 106 KiB ≈ 700:1 — pure weight stream
Tail per layer:
read src (projected) 26,624 B
read+write hidden (normed) 26,624 B read + 26,624 B written (+ reread in pass 3)
write output (post_norm) 26,624 B
read two weight vectors 2 × 6,656 f32 = 53,248 B
≈ 181 KiB total — rounding error next to W_down
The layer’s total weight read: 224.3 MB (Q4_K-down) or 258.6 MB (Q6_K-down), of which the down projection is a third to 42 %. Across 52 layers the FFN family is the plurality of the 16.76 GB artifact — the arithmetic of Ch 18 §18.7 plus this chapter’s down numbers.
Turn that around and it says something uncomfortable about the shape of this chapter. The tail kernel — three passes, two reductions, an in-place mutation, the longest section here — moves less traffic than a rounding error on the matvec that precedes it. Expensive and interesting are not the same property. The matvec is where the bytes are; the tail is where the boundary is, and boundaries are what the rest of the chapter is about.
19.7 Tradeoffs
Q6_K vs Q4_K on the down projection — +45.8 % bytes on the layers that take it. The arithmetic of §19.3: 109.03 MB vs 74.76 MB per layer, a deliberate precision spend on the last projection before the residual — the same reasoning the ancestor’s Q4_K_M mix table documented for Qwen [ferrite-book Ch 18], realized differently here. Both engines pay it equally (both read the same GGUF through equivalent pinned kernels), so it is a quality-vs-bytes decision, not a parity hazard.
What we did not know going in was whether it was also a speed hazard. A wider quant means a fatter block to decode — more shifts, more scale unpacking per weight — so we expected the six-bit path to be slower per dispatch by something more than its byte ratio, and we went looking for that penalty. The M16 verify-shape bench measured the two side by side in its candidate sweep, and the penalty was not there: Q4_K ffn_down 0.891 → 0.533 ms and Q6_K ffn_down 0.897 → 0.539 ms per dispatch under the winning n32 tile. The ledger keeps that sweep [ledger Stage B close-out, L0 “Winner (n32)”]. Read the two rows next to each other and the conclusion is hard to miss: the formats cost nearly the same time per byte on this GPU, so the +45.8 % is a byte bill, not a kernel-efficiency bill. That is a comfortable result — it means the checkpoint author’s precision choice can be argued about purely in terms of bandwidth, with no hidden decode tax to price in.
One fused tail vs three separate kernels. Unfused, the layer exit is:
norm the projection (1e-8), add into the residual, norm the residual
(1e-5) — three dispatches and a materialized post-norm intermediate. The
32sg tail is one dispatch that never materializes the intermediate. But
note precisely what it preserves: the source comment at
decode.rs:1328-1330 says the fused kernel “reproduces the two pinned ggml
f32x4 norm reductions and their intervening f32 device-memory
boundary” — pass 2 writes hidden to device memory and pass 3 reads it
back, exactly where llama.cpp’s graph publishes between nodes. It is a
fusion of dispatches, not of arithmetic boundaries; that restraint is
why it can be exact at all, and the diagnostic split route survives behind
MUSER_NO_FUSED_PREFILL_DUAL_NORM (decode.rs:1331) as the control. State
it as the rule the next section tests to destruction: a fusion may delete
dispatches, but it may not delete rounding points. Wherever the
reference graph writes a float to memory and reads it back, a value gets
truncated to storage precision — and that truncation is part of the
answer, not an artifact standing in front of it.
The 104-group question — fusion rejected where it would matter most. This is the tradeoff this chapter exists for; §19.8 gives it its own section with the numbers.
19.8 Where the gap lives — the 104 norm-boundary groups
Every kernel chapter in this part ends by asking where its kernel sits in the decode gap. This one has a real answer, and it is the least comfortable answer in the book: the layer boundary — the thing the previous sections spent their length building — is the largest single identified block of extra dispatch work in the serving graph, and the obvious way to remove it is the one thing we are not allowed to do. Here is how we found that out, and what we took instead.
Start with the census. The one-token dispatch-gap investigation reconciled the production (serving) graph’s 760 profiling closures against the legacy fused route’s 564 — a difference of +196 — into four families plus one copy [docs/decode-dispatch-gap-20260815.md, “Corrected closure-count diff at position 2,048”]:
| family | production | legacy | delta | disposition |
|---|---|---|---|---|
| Entry/attention norm boundary | 53 | 2 | +51 | fusion not exact; reject |
| SWA wrapped-ring staging | 39 | 0 | +39 | keep until bit-exact replacement |
| KV publication and attention | 104 | 52 | +52 | session structure, keep |
| Post-attention residual + FFN norm | 104 | 52 | +52 | fusion not exact; reject |
| Post-FFN residual + next/output norm | 53 | 52 | +1 | fusion not exact; reject |
| Last-row copy | 1 | 0 | +1 | removed, bit-exactly |
The three norm-boundary rows are the 104 separated norm-boundary groups
(+51 +52 +1): they are this chapter’s boundaries — the layer exits and
entries that TAIL#2 fuses on the teacher-forced route but that the serving
graph keeps separated, publishing each norm as its own node through the
pinned ggml kernels. The instrument’s own label diff names them: production
carries layer.*.post_attn_norm + layer.*.ffn_norm where the legacy
route has one post_attn_ffn_norm, and layer.*.post_ffn_norm +
output_norm where legacy has post_ffn_next_norm
[docs/decode-dispatch-gap-20260815.md, label table].
So we tried to take them. The fork is the one any reader of that table would take: the tail kernel we just read is proof that a dual-eps boundary can be fused exactly, the serving graph keeps those same boundaries separated, so fuse them there and the largest rejectable family in the diff simply goes away. We built the hybrid and we expected it to come out bit-identical — the corrected fusion had already come out bit-identical, on the same arithmetic, in the same kernel family.
It did not. The fusion that would remove those 104 groups exists and was
rejected because it changes logprobs beyond contract. The seductive part
is that the greedy token survived: reusing a retained-activation schedule
with fast fused boundaries picked the same word, so a casual sample of the
model looks identical to the baseline. The logits did not survive, and the
hybrid postmortem is exact about how far beyond contract they went — a
full-logit maximum absolute error of 4.6300888e-4, a normalized-logprob
maximum error of 3.197146176834309e-4 against the 1e-4 contract, with
201,970 of 202,048 logits differing and the first KV divergence in layer
1, value plane element 524,115, one f16 ULP apart (bits 39,892 vs 39,893).
The runs that proved it are retained
[docs/decode-dispatch-gap-20260815.md, “Rejected hybrid postmortem”;
receipts muser-receipt://pinned-token-parity-20260814-v{3,4}/].
Follow that divergence back and it is a single rounding difference in the
first layer’s residual chain — one bit, in exactly the structure this
chapter has been describing — amplified through 51 more layers until it
breaks a public numerical commitment. That
is the lesson, and it is why the fusion rule of the previous section is
stated as a rule rather than a preference: the boundary you may fuse is the
one that keeps its rounding points, and this hybrid quietly dropped one.
The decision followed from the lesson without much argument. The attempt
“was removed rather than hidden behind a tolerance or shipped as an
alternate route” — and the routing comment of Ch 18
§18.6 (decode.rs:2085-2091) is the standing consequence: serving decode
takes the batch graph with the exact pinned kernels because the fused
boundaries breach tolerance.
So what survived the rejection? Exactly one removal, and it is deliberately small: the last-row copy — one closure and one 6,656-element f32 copy, worth −0.136 ms GPU (−0.34 %) in a single-run diagnostic, with no wall-time claim (the +4.380 ms wall sample was submit/wait noise) [docs/decode-dispatch-gap-20260815.md, “Landed and rejected reductions”]. The corrected exact dual-norm fusion (the 32sg kernel’s pinned-reduction form) matched the baseline’s full-logit SHA-256 but was retained as “historical self-consistency only” — useful, not sufficient. The distinction is worth holding onto, because it is easy to mistake one for the other: matching your own previous bytes proves you have not broken yourself, and proves nothing about whether you agree with the engine you are being measured against. By that point the agreement was the whole game. The five-sample streamed serving number after it was 28.290 tok/s against llama’s 33.428 (ratio 0.8463), Stage A still open at that point [same doc]. The gap closed later only when the anchor itself changed — J0 made llama’s own bytes the gate and J1 transplanted llama’s attention DAG [ledger Stage A close-out, Arc 1 of the campaign; the parity outcome is the six-depth matrix at or above 1.0 of Ch 38].
So the honest accounting for this chapter: the layer boundary is where the +196 lives, and the fix that looks obvious is the one the contract forbids. Every closure in those 104 groups does required math; the investigation’s own conclusion is that “no repeated closure performing provably identical arithmetic was found” — the boundary cost is real but the only cheap removals change bits, and changing bits is the one thing Muser’s public logprob contract cannot buy. Ch 35 carries the full hazard framing; Ch 40 files this as the canonical measured rejection.
The loop is closed: 52 times you have watched a layer normalize, attend,
gate, project, feed forward, and fold its delta into the stream — and the
last tail wrote activations.hidden through the final norm. One question
is left in Part IV: what does the model actually say? The 6,656-vector
now sitting in hidden is about to meet the largest matvec in the engine —
202,048 output rows — and the soft cap that bounds what it may score.
Ch 20.
References
crates/muser-engine/src/shaders/ferrite/rmsnorm_batch_tail.metal:142-201—muser_fused_norm_residual_rms_norm_32sg, the dual-eps tail kernel (primary source; the comment block states the eps split and why the single-epsilon kernel is invalid).crates/muser-engine/src/metal/encode/norm.rs:163-241— the 32sg wrapper and its batch form (1,024 threads, 144 B threadgroup memory, “6,656-wide Muse tail” geometry note);:97-160the batch dual-eps sibling; the strict decomposed route underMUSER_CROSS_VENDOR_QK.crates/muser-engine/src/decode.rs:5863-5889— down projection + tail +next_normselection inencode_token;:5806-5818the first (post-attention) tail;:1328-1334the fusion-control flags;:2085-2091the serving-exactness routing comment.crates/muser-engine/src/metal/encode/qkv.rs:429-450— the one-token pinned-metallib matvec path (Q4_K/Q6_K rows-per-group table);crates/muser-engine/src/metal/encode.rs:278-280PSO registration.crates/muser-bench/src/m16.rs:179-198—ffn_down-q4k/ffn_down-q6kshape-dtype evidence for the mix;docs/quickstart.md:16the Q6_K fail-closed routing note.crates/muser-engine/src/reference.rs:526-541— the oracle’s post-FFN norm, residual add, andl_out-{il}capture point.- [docs/decode-dispatch-gap-20260815.md] — the 760/564 reconciliation, the 104 norm-boundary groups, the rejected hybrid postmortem (3.197e-4 vs the 1e-4 contract), the −0.136 ms exact copy removal, and the 0.8463× Stage-A-open snapshot.
- [receipt
muser-receipt://pinned-token-parity-20260814-v3/], [receiptmuser-receipt://pinned-token-parity-20260814-v4/] — retained evidence for the rejected hybrid. - [ledger Stage B close-out] —
docs/goal-parity-ledger-2026-08.md, L0 “Winner (n32)”: the Q4_K/Q6_K ffn_down per-dispatch timings. - Ch 12 — the sandwich and the tail family’s first appearance; Ch 13 the pinned matvec; Ch 18 the FFN budget this chapter completes.
- Ch 35, Ch 38, Ch 40 — the downstream chapters that build on §19.8.
- [ferrite-book Ch 18] — the ancestor’s Q4_K_M mix table and the “where the bytes are, not where the gap is” device, ported with Muse’s real mix.
Chapter 20 — Final norm, LM head, and the soft cap
status: polished · path: Muse Glimmer, pinned Muser tree
Prerequisites: Ch 6 (the Q5_K block), Ch 12 (RMSNorm; the final norm is one of those), Ch 13 (the pinned ggml matvec family — this chapter runs it at its largest shape), Ch 19 (the last tail wrote
activations.hiddenthrough the final norm). This chapter leaves the per-layer loop and produces the model’s raw scores — 202,048 of them.
20.1 What it computes
Every chapter of the decode walk so far has handed the residual stream back to itself, one layer richer. This one spends it. The question we are finally answering is easy to ask and expensive to compute: given the model’s final thought, what score does it assign to every word it knows?
After layer 51’s tail, the residual stream is one 6,656-vector: the model’s final thought. Three operations turn it into a prediction:
- The final norm — an RMSNorm with the
output_norm.weightγ vector and the ordinaryrms_eps = 1e-5. On the teacher-forced route there is no separate dispatch for it at all: layer 51’s tail is the final norm, because itsnext_outputselection writesactivations.hiddenthroughoutput_norm(decode.rs:5869-5876, Ch 19 §19.5). The serving batch graph can also run it as its ownencode_rms_norm_mulnode (decode.rs:5229-5241) — node shape follows the pinned graph, the math does not change. - The LM head — one giant matvec projecting the normalized 6,656-vector up to a logit per vocabulary entry:
logits = W_lm · normed W_lm : [202048 × 6656], Q5_K
logits[i] = Σ_{j=0..6655} W_lm[i, j] · normed[j]
A logit is a raw, unnormalized score — one f32 per token in the vocabulary, no probability yet, no bound yet. 3. Scale, then soft cap — the chapter’s titular kernel:
value = logits[i] × logit_scale logit_scale = 0.196116 (GGUF metadata)
logits[i] = 20 × tanh(value / 20) final_logit_softcap = 20 (GGUF metadata)
Every logit ends up in the open interval (−20, 20).
20.2 Why it exists — the unprojection, and why bound it
The LM head (output.weight) is the unembedding: the inverse role of
the token embedding of Ch 11. The embedding
mapped one hot token id to a learned 6,656-vector; the head scores every
vocabulary row against the final hidden state — 202,048 independent dot
products, one per token, each of length 6,656. The largest of them wins and
becomes the next token (Ch 21). It is
the only per-token projection that is not repeated per layer: a one-shot
bandwidth hit, not a 52× multiplier.
Why a soft cap? Without it, logits are unbounded — a very confident
dot product can score 50, 100, or more, and training with such outliers is
unstable (huge gradients through the softmax). A soft cap squashes the
tails smoothly: 20·tanh(x/20) is ≈ x for small x and approaches ±20
asymptotically, so ordinary scores pass nearly unchanged while outliers are
bent into the bound. Said another way: near the origin the cap is invisible,
the identity map to within a rounding error, and it only becomes a real
transformation once a score is large enough that its exact magnitude was
never trustworthy anyway.
Where the idea came from is a question we can only half-answer. The mechanism is
Gemma-2-lineage; that it is why Muse Glimmer’s authors adopted it is not in
the Muser tree — [unverified], and we would rather say so than invent a
motive for a checkpoint we did not train. What we can verify is the wiring
and the constants. The engine reads both from the checkpoint
(config.rs:190-197); llama.cpp defaults the softcap to 30.0 when the key is
absent; this checkpoint carries final_logit_softcapping = 20 with
logit_scale = 0.196116 [docs/release-provenance.md:822-823].
One property matters enormously downstream and costs one line to prove:
the transform is strictly increasing. x ↦ 20·tanh((x·s)/20) with
s > 0 preserves order — if logits[a] < logits[b] before, then after.
Greedy token selection (Ch 21) is
therefore invariant to the soft cap: the argmax index cannot move. What
the cap changes is the gaps (§20.7), and gaps are exactly what logprob
comparisons consume.
20.3 The matrix operation — the largest matvec, by hand
Where does the time in this stage go? Almost all of it goes into dragging one matrix off memory, so before anything else we want that matrix’s size in bytes — derived rather than asserted, so you can re-derive it yourself and catch us if we are wrong.
The shapes, verified from the artifact’s verify-shape table
(lm_head 6656->202048 q5k, crates/muser-bench/src/m16.rs:195-197):
rows = 202,048 (vocab), cols = 6,656 (hidden), Q5_K.
Derive the byte size step by step — Q5_K packs 256 elements per 176-byte block (Ch 6):
blocks per row = 6,656 / 256 = 26
bytes per row = 26 × 176 = 4,576 B
total weight bytes = 202,048 × 4,576 = 924,571,648 B
≈ 924.6 MB (SI)
share of artifact = 924,571,648 / 16,756,681,056 ≈ 5.52 %
bits per element = 176/256 × 8 = 5.5 bits
924.6 MB in one matrix — larger than any single layer’s entire FFN (224–259 MB, Ch 18), read once per token. The “17× more rows than the next-largest projection” framing of the ancestor book [ferrite-book Ch 19] recurs here with new numbers: the next-largest output width is the FFN’s 19,968; the head’s 202,048 is 10.1× that, and 30× the attention block’s 6,656-output projections.
The output is also the largest activation the model materializes:
logits buffer = 202,048 × 4 B = 808,192 B ≈ 789 KiB of f32
Still three orders of magnitude smaller than the weights that produced it. Figure 20.1 shows the stage end to end.
normed [6656] W_lm [202048 × 6656] Q5_K logits [202048]
┌ ┐ ┌──────────────────────────────────┐ ┌───────────┐
│ 26,624 B │ × │ 202,048 rows × 4,576 B/row │ → │ 808,192 B │
└ ┘ │ = 924,571,648 B read once/token │ └───────────┘
fits in cache └──────────────────────────────────┘ one score per
vocab token
Figure 20.1: The LM-head matvec. The row count is the vocabulary; the 26 KiB input is re-read by every threadgroup and lives in cache; the 924.6 MB weight stream is the work.
20.4 The Metal kernel — muser_scale_softcap_inplace, and what actually runs
Two kernels in this tree compute the soft cap, and the one that reads best is not the one that runs. Start with the readable one, because it is also the definition.
The fused kernel, verbatim — it is the whole soft-cap formula in eight lines:
// crates/muser-engine/src/shaders/muse_reference.metal:15
kernel void muser_scale_softcap_inplace(
device float *logits [[buffer(0)]],
constant uint &count [[buffer(1)]],
constant float &scale [[buffer(2)]],
constant float &softcap [[buffer(3)]],
uint index [[thread_position_in_grid]]) {
if (index < count) {
float value = logits[index] * scale;
logits[index] = softcap > 0.0f ? softcap * tanh(value / softcap) : value;
}
}
One thread per logit; multiply by scale, divide by the cap, tanh,
multiply by the cap, overwrite in place. The softcap > 0 guard means the
same kernel serves uncapped models (scale only).
But here is this chapter’s twist, in the same spirit as Ch 18’s flag story: on the serving route this fused kernel is not what runs. When the pinned llama.cpp metallib is loaded, the wrapper deliberately decomposes the soft cap into four separately published unary nodes — the source comment states why:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/metal/encode/lmhead.rs:242
// Match pinned llama.cpp's graph literally: the LM head is
// followed by four independently published unary nodes. The
// previous combined kernel used a different tanh implementation
// and expression tree, so equal pre-softcap logits did not yield
// equal public bytes.
self.encode_ggml_unary_inplace(encoder, scale_pipeline, logits, count, scale);
if softcap > 0.0 {
for (pipeline, factor) in [
(scale_pipeline.as_ref(), 1.0f32 / softcap),
(tanh_pipeline.as_ref(), 0.0),
(scale_pipeline.as_ref(), softcap),
] {
let barrier: [&metal::ResourceRef; 1] = [logits.metal()];
encoder.memory_barrier_with_resources(&barrier);
self.encode_ggml_unary_inplace(encoder, pipeline, logits, count, factor);
}
}
}
Read that comment for what it is: a retracted attempt, kept in the source
where it can still teach. The fused kernel above was the obvious engineering
answer — one dispatch, one pass over the buffer, algebraically identical to
the step-by-step form. We shipped it, expecting byte-identical logits out of
it, because the arithmetic is the same arithmetic. The bytes disagreed.
Metal’s tanh and the fused expression tree round differently from llama’s
node-per-op graph, and the failure surfaced exactly where it costs the most:
“equal pre-softcap logits did not yield equal public bytes.” The lesson is
one this book keeps relearning in new costumes — algebraic identity is not
floating-point identity, and a comparator that gates on published bytes will
find every place the two come apart.
So the serving route decomposes instead. Four dispatches — ×scale,
×(1/20), tanh, ×20 — with a memory barrier between each, using the
comparator’s own kernel_…_unary PSOs: not kernels that behave like
llama.cpp’s, but llama.cpp’s own, registered as ggml unary ops 10 = scale
and 100 = tanh (encode.rs:288-289). The fused kernel is not deleted, it is
demoted: it still runs when the metallib is absent or the count is not a
multiple of four (lmhead.rs:230-241, :261-266), and a strict cross-vendor
split — scale / barrier / scale / barrier / tanh / barrier / scale — exists
for CUDA-parity lanes (lmhead.rs:197-228).
The CPU oracle states the reference semantics — same formula, scalar
order (crates/muser-engine/src/reference.rs:559-569):
#![allow(unused)]
fn main() {
let mut logits = vec![0.0f32; t * cfg.vocab_size];
matmul(&self.w("output.weight"), &hidden, t, &mut logits);
for l in logits.iter_mut() {
*l *= cfg.logit_scale;
}
if cfg.final_logit_softcap > 0.0 {
let cap_v = cfg.final_logit_softcap;
let inv = 1.0 / cap_v;
for l in logits.iter_mut() {
*l = cap_v * (*l * inv).tanh();
}
}
}
20.5 The Rust dispatch
What does it take to launch the largest matvec in the model? Less than you would guess, and that is the point worth carrying away: the head goes through the same wrapper as the smallest projection in the layer loop, with nothing special-cased for its size.
The head itself is the stock projection wrapper —
self.project(command, &self.output, &self.activations.hidden, &self.activations.logits) (decode.rs:5892-5897) — routing through
encode_projection to the one-token pinned path of
Ch 13. output.weight is Q5_K, so
the dtype table gives (block_bytes, rows_per_group) = (176, 1) and the
launch is:
grid = 202,048 ÷ (1 row/group × 2 simdgroups) = 101,024 threadgroups
threadgroup = (32, 2, 1) = 64 threads (two SIMD groups, one row each)
kernel = kernel_mul_mv_q5_K_f32 (pinned llama.cpp metallib)
(qkv.rs:429-450; the Muser fallback family has no Q5_K one-token
specialization — muser_matvec_q5k_4sg exists in muse_reference.metal
but the pinned path is taken whenever the metallib is present,
docs/quickstart.md:16’s fail-closed note again.)
The soft-cap entry point is encode_scale_softcap
(lmhead.rs:163-171), which delegates to encode_scale_softcap_count
with the full vocab count; the constants arrive from the config:
#![allow(unused)]
fn main() {
dispatch(command, |encoder| {
self.kernels.encode_scale_softcap(
encoder,
&self.activations.logits,
cfg.logit_scale,
cfg.final_logit_softcap,
);
});
}
(decode.rs:5898-5905 — the last dispatch of the token graph.)
Where logit_scale comes from — metadata, not code. This is worth a
paragraph of its own because it is easy to get wrong: 0.196116 is not
a constant in the engine. It is read from the GGUF key
muse-glimmer.logit_scale at load, fail-closed on absence
(config.rs:190-192), and the pinned artifact’s value is 0.196116
[docs/release-provenance.md:822-823]. Numerically that is 1/√26 ≈ 0.1961161…; the only place a 1/√26 expression appears in the tree is a
fixed Metal unit test — kernels.encode_scale_softcap(encoder, &logits, 1.0 / 26.0f32.sqrt(), 20.0) (crates/muser-engine/src/metal.rs:68) —
which pins the formula’s behavior, not the model’s constant. Whether the
checkpoint’s authors derived the value from 1/√26 (26 is, coincidentally
or not, the number of Q-blocks per LM-head row, §20.3) is
[unverified]. The chapter’s rule: the engine reads the number; the
book cites the metadata; nothing else is claimed.
20.6 The access pattern
Two questions to hold while reading the byte bill below. What does this stage cost in traffic? And what did the parity decision of the previous section actually charge us for it?
LM head:
read W_lm (Q5_K) 202,048 × 4,576 B = 924,571,648 B ≈ 924.6 MB
read normed 6,656 × 4 B = 26,624 B (cache-resident)
write logits 202,048 × 4 B = 808,192 B ≈ 789 KiB
Soft cap (serving route, four unary nodes):
read + write logits, four times over = 4 × 1,616,384 B ≈ 6.16 MiB
(fused fallback: one read + one write = 1,616,384 B ≈ 1.54 MiB)
The four-node decomposition re-touches the logits buffer four times — about 4.6 MiB of extra traffic per token versus the fused form. Against the 924.6 MB weight stream that produced those logits, that is ~0.5 % of the stage’s traffic: the byte cost of byte-exactness, paid deliberately (§20.4’s comment). This is the same trade as Ch 19’s 104 rejected groups, in miniature and in the other direction — here Muser adds dispatches to preserve bits rather than refusing to add them.
For the per-token budget: the head’s 924.6 MB is 5.52 % of the artifact, paid once per token — comparable to the whole 52-layer attention-block share and second only to the FFN family. Its arithmetic intensity is the same one-MAC-per-weight-byte decode shape as every other matvec (Ch 1).
20.7 Tradeoffs
Soft cap on vs off — what it does to comparisons. Order-preserving, so greedy decoding is unchanged (§20.2). But the cap is a compressive map: gaps between large logits shrink. Worked by hand at the 20 cap:
raw scaled logits a = 12.0, b = 6.0 gap = 6.00
capped: 20·tanh(0.6) = 10.741
20·tanh(0.3) = 5.826 gap = 4.915 (−18 %)
raw scaled logits a = 40.0, b = 20.0 gap = 20.0
capped: 20·tanh(2.0) = 19.281
20·tanh(1.0) = 15.232 gap = 4.049 (−80 %)
Two consequences follow, one local and one that reaches across the book.
The local one is about probabilities. For softmax over capped logits, and for
logprobs, the transform is emphatically not a no-op: probabilities come out
flatter than an uncapped engine would produce from the same hidden state, and
any cross-engine comparison of logprobs or bounded-logit deltas is only
meaningful if both engines apply the same scale-and-cap in the same order.
That is why Muser’s parity work pins the post-softcap public bytes rather
than anything upstream of the cap — the reference.rs result_output
capture and the four-node decomposition of §20.4 both aim at that same pinned
surface.
The far-reaching one is about units, and the reason it matters for the handoff is this: a tolerance is meaningless until you know which numbers it was measured on. The disaggregated lane of Ch 32 accepts or rejects a remote engine’s work through “declared bounded-logit policies” — acceptance rules of the form max delta < 11, mean < 1.25. Those thresholds were measured on exactly these capped logits, and we kept the run that produced them: the wizard’s native-lane rule [nvfp4-fast-lane-evidence-20260817 §Determinism; ledger wizard attempt 9]. So the cap is part of the contract’s units, not a cosmetic tail step. Without it the same deltas would be much larger and the tolerance would have to be re-derived.
Q5_K for the head — 5.5 bits on the score-setter. The head is the one tensor that decides, by small margins, which token wins; the artifact spends 5.5 bits/element here against 4.5 on the Q4_K bulk. A Q4_K head at the same shape would be 202,048 × 26 × 144 = 756,467,712 B ≈ 756.5 MB — the Q5_K choice costs +168.1 MB (+22.2 %) per token of extra read (arithmetic from §20.3’s blocks-per-row). That the precision lands here because small score differences decide the token is the recipe’s rationale, inherited from the quantized artifact, not re-measured here [unverified] — the same honesty note as the ancestor’s Q6_K LM head [ferrite-book Ch 19].
Four unary nodes vs one fused kernel. The war story is told above; here
is only its price tag, from §20.6: ~4.6 MiB/token plus three extra dispatches
and barriers, bought to make the public bytes match llama.cpp’s. The
receipt for the rejected direction stays where a future maintainer will trip
over it, in the source comment itself (lmhead.rs:243-246). Under the J0
anchor — llama’s own bytes as the gate
[ledger Stage A; see Ch 38] — the
ranking is not close: parity outranks dispatch count.
Why not vocab-block or resident layouts? The ancestor book explored vocab-blocked LM-head layouts [ferrite-book Ch 19]; Muser’s engine keeps the plain row-major head and the pinned matvec, and spends its layout ingenuity elsewhere (KV planes, Ch 15). No retained Muser measurement compares LM-head layouts [unverified]; the pinned-kernel parity argument (same kernel as the comparator) is the documented reason the simple route stays.
20.8 Where the gap lives
Not the gap — with one instrumentation lesson attached. In the one-token closure accounting, the LM head and softcap sit in the “common math closures” family: 406 production vs 406 legacy, delta 0 [docs/decode-dispatch-gap-20260815.md]. The serving route’s four-node softcap even adds work relative to the fused legacy form and stays: exactness is the constraint, dispatch count is not.
The lesson is in how that verdict was reached. We went into the gap
investigation expecting to read the head’s cost straight off the retained
baseline, and the first thing the baseline told us was false: “production
labels omitted lm_head, so its time was attributed to the following
softcap label” [same doc, Instrumentation correction]. The head’s time was
never missing — it was wearing the next stage’s name, which is the worst way
for a measurement to be wrong, because the total still adds up. A 924.6 MB
stage is exactly the kind of thing a label defect hides in plain sight. So
the investigation’s first act was fixing its own instrument, and only then
did it draw a conclusion about the engine: measure the instrument before the
engine (Ch 38 formalizes this culture).
The logits exist — 202,048 capped scores in a buffer on the GPU. The model has an opinion about the next token; nothing has chosen one yet. Choosing is a different kind of computation — a reduction, a policy, and in Muser’s case a deliberate trip back to the CPU — and it is the final chapter of the decode walk, Ch 21.
References
crates/muser-engine/src/shaders/muse_reference.metal:15-25—muser_scale_softcap_inplace, the fused scale+tanh kernel (fallback route).crates/muser-engine/src/metal/encode/lmhead.rs:163-267—encode_scale_softcap/encode_scale_softcap_count/encode_scale_softcap_legacy, including the four-unary-node serving route and the “equal pre-softcap logits did not yield equal public bytes” comment (:242-247);:36-80encode_ggml_unary_inplace.crates/muser-engine/src/metal/encode.rs:288-289— ggml unary scale (op 10) and tanh (op 100) PSO registration from the pinned metallib.crates/muser-engine/src/decode.rs:5869-5876— final norm fused into layer 51’s tail;:5892-5905LM head + softcap dispatches;:5229-5241the batch route’s separate final norm.crates/muser-engine/src/config.rs:190-197—logit_scale(required GGUF keymuse-glimmer.logit_scale) andfinal_logit_softcap(muse-glimmer.final_logit_softcapping, llama.cpp 30.0 default, this checkpoint 20.0).crates/muser-engine/src/metal.rs:68— the fixed test’s1.0 / 26.0f32.sqrt()(formula pin, not the model constant).crates/muser-engine/src/reference.rs:549-574— the CPU oracle tail: final norm, head, scale, cap,result_outputcapture.crates/muser-bench/src/m16.rs:195-197—lm_head 6656->202048 q5kshape/dtype evidence.- [docs/release-provenance.md:822-823] —
logit_scale=0.196116withfinal_logit_softcapping=20on the qualified dumps. - [docs/decode-dispatch-gap-20260815.md] — common-math closure accounting and the lm_head label-defect correction.
- [ledger wizard attempt 9 / nvfp4-fast-lane-evidence §Determinism] — the bounded-logit native-lane rule (max < 11, mean < 1.25) measured on softcapped logits; see Ch 32.
- Ch 6 — the Q5_K 176-byte block behind §20.3’s arithmetic; Ch 11 the embedding the head inverts; Ch 13 the pinned matvec at its smaller shapes.
- Ch 32 — bounded-logit policies over the handoff; Ch 38 the J0 anchor and instrument-first culture.
- [ferrite-book Ch 19] — the ancestor’s LM-head chapter: “17× more rows”, the one-shot-bandwidth framing, and the Q6_K-head precision note, all re-derived here with Muse’s Q5_K geometry.
Chapter 21 — Sampling, argmax, and grammar
status: polished · path: Muse Glimmer, pinned Muser tree
Prerequisites: Ch 2 (threadgroups,
threadgroup_barrier, the 1,024-thread limit), Ch 16 (softmax, defined there for attention), Ch 20 (the capped[202048]logits this chapter consumes). This chapter adapts the kernel skeleton: two GPU reduction kernels, then the CPU-side policy layers — sampler state, grammar, and exact speculative acceptance — that decide why the read-back is what it is.
21.1 What it computes
Ch 20 left 202,048 capped logits in a
GPU buffer. This chapter picks one token id — a single integer in
[0, 202048) — and in doing so closes the decode walk. Two families of
selection exist:
Greedy (argmax). Emit the highest-scoring token every step. No randomness; the same prompt + weights + precision always produce the same sequence. Deterministic by construction.
Sampling. Convert logits to a probability distribution (a softmax — the attention chapter’s normalization, applied to the vocab — usually temperature-scaled), then draw from it, possibly restricted to top-k / top-p / typical-p candidates. Different sequence every run.
Muser implements both, plus a third consumer of the same distribution: exact speculative acceptance — the CPU-side step that decides how many DFlash draft tokens to keep (Ch 8, Ch 33). All three need different amounts of the logits vector, and that is the question this chapter is really about. Finding a maximum is the easy part; the hardware does that in its sleep. The hard part is deciding how much of the distribution has to be standing in front of the policy at the moment it chooses — because that, and not the reduction, is what sets the traffic between GPU and CPU. Hence the architecture: the reduction (argmax) can run on the GPU in two phases and cost a 4-byte read-back, but the policy layers need the whole distribution on the CPU — so the serving path reads the full vocab row back every token, and the GPU argmax lives on the no-readback routes.
21.2 Why greedy is the reference — determinism is the gate
Everything that follows turns on a question that is easy to walk straight past: which policy is allowed to be the reference — the one the product ships, or the one a test can check?
The book’s measured spine is exact-token parity against pinned llama.cpp
(Ch 38): five-repetition cells whose
verdict is token equality, plus full-logit SHA comparisons. Only a
deterministic policy can be gated that way — you cannot diff two random
sequences. So greedy (--temp 0 on the comparator side) is the
qualification policy for every throughput number this book cites, and the
sampler exists for serving, not for measurement. The discipline cuts both
ways, and Muser honors the second direction too: even the sampled path is
pinned bit-for-bit to the comparator’s RNG (§21.7), so a seeded request
replays identically across engines — determinism as a cross-engine
contract, not just a bench convenience.
21.3 The reduction problem — two phases under the 1,024-thread limit
Where does the difficulty in picking a maximum actually live? Not in the comparison — that is one instruction. It lives in the geometry of who is allowed to talk to whom.
Argmax is a reduction: collapse 202,048 floats to one (value, index) pair.
The natural GPU pattern is the tree reduction — at each step half the
threads fold in their neighbor, the active stride halves, log₂(n) steps
finish. The catch is exactly that geometry: a tree lives in
threadgroup memory and Apple Silicon caps a
threadgroup at 1,024 threads (maxThreadsPerThreadgroup,
[Metal-PG]). The vocab is ~197× that. So: split the input into chunks,
reduce each chunk independently, then reduce the partials — the
two-phase reduction (Figure 21.1). Put the other way round: the
hardware will not let you hold one conversation among two hundred thousand
threads, so you hold a small conversation per chunk and then one more among
the chunk winners.
logits [202048] one token id
┌──────────────────────────────────────────────────────────┐ ┌───┐
│ chunk 0 (1024) │ chunk 1 │ … │ chunk 196 │ chunk 197(320)│ │ │
└──────────────────────────────────────────────────────────┘ └─┬─┘
│ phase 1: one threadgroup per chunk │ │ │
▼ ▼ ▼ ▼ │
[val,idx]₀ [val,idx]₁ … [val,idx]₁₉₆ [val,idx]₁₉₇ │ phase 2
└───────────────────┬───────────────────────────┘ │
│ 198 partials → 1 winner ▼
▼ result[0]
(value, index) of the global max = u32
Figure 21.1: The two-phase reduction at Muse’s vocab.
⌈202,048 / 1,024⌉ = 198 chunks — 197 × 1,024 = 201,728, so the last
chunk holds 202,048 − 201,728 = 320 elements (the ragged tail the
gid < n guard absorbs).
The comparison is strictly greater, so ties keep the lower index —
“matching the scalar first-maximum convention” of the reference sampler
(argmax_f32.metal:4-6) and of the CPU helper (api.rs:1842-1849). That
deterministic tiebreak is load-bearing for byte-identical diffs. It is worth
sitting with why: if two logits landed exactly equal and the tiebreak
wobbled between them — a different chunk winning on a different run — a
parity cell would fail for a reason that has nothing to do with arithmetic,
and the failure would be intermittent, which is the worst kind to chase.
The strict > is what makes ties boring.
21.4 The Metal kernels — the two-phase tree, and the greedy variant
Two things are worth watching for as you read the kernel below, because they are the questions a tree reduction always has to answer. What does a thread do when there is no element for it to load? And why does a barrier appear inside the loop rather than once before it?
Phase 1, verbatim — one threadgroup of 1,024 threads per chunk:
// crates/muser-engine/src/shaders/ferrite/argmax_f32.metal:7
kernel void argmax_f32_phase1(
device const float* x [[ buffer(0) ]],
device float* partial_val [[ buffer(1) ]],
device uint* partial_idx [[ buffer(2) ]],
constant uint& n [[ buffer(3) ]],
uint tgid [[ threadgroup_position_in_grid ]],
uint lid [[ thread_index_in_threadgroup ]],
uint tg_size [[ threads_per_threadgroup ]])
{
threadgroup float tg_val[1024];
threadgroup uint tg_idx[1024];
uint gid = tgid * 1024u + lid;
float best_val = -INFINITY;
uint best_idx = 0u;
if (gid < n) {
best_val = x[gid];
best_idx = gid;
}
tg_val[lid] = best_val;
tg_idx[lid] = best_idx;
threadgroup_barrier(mem_flags::mem_threadgroup);
for (uint stride = 512u; stride > 0u; stride >>= 1u) {
if (lid < stride && lid + stride < tg_size && tg_val[lid + stride] > tg_val[lid]) {
tg_val[lid] = tg_val[lid + stride];
tg_idx[lid] = tg_idx[lid + stride];
}
threadgroup_barrier(mem_flags::mem_threadgroup);
}
if (lid == 0u) {
partial_val[tgid] = tg_val[0];
partial_idx[tgid] = tg_idx[0];
}
}
Line by line: each thread loads one element (or −INFINITY if past n —
the 320-element tail and the idle lanes of a short phase 2 can never win a
max); one initial barrier makes all slots visible; then ten
stride-halving passes (512 → 256 → … → 1), a barrier per pass because
thread lid reads what thread lid+stride wrote last pass; lane 0
publishes the chunk winner. argmax_f32_phase2 (:41-70) is the same
body over the 198 partials with result[0] = tg_idx[0] — the output is a
single u32, the token id. The file’s own header notes the lineage:
“Exact extraction of Ferrite’s two-phase GPU greedy reduction”
(argmax_f32.metal:4-5) — the ancestor’s device [ferrite-book Ch 20],
ported with Muse’s vocab arithmetic.
That is the reduction as a textbook would leave it. Serving asks two more questions of it. What should a maximum-finder do when one of the numbers it is comparing is not a number at all? And how do you honour a request that says “never stop” without lying to everyone else about what the model actually scored? The greedy serving variant answers both, and its header comment is the specification:
// crates/muser-engine/src/shaders/ferrite/argmax_f32.metal:72
// Greedy serving variant. The high bit of every partial index carries a
// fail-closed nonfinite flag; vocabulary indices are required to fit in the
// remaining 31 bits. `excluded` is the request's EOG set for ignore-eos
// generation. Masking happens only inside the reduction, so the retained
// target logits remain byte-for-byte unchanged for logprob/session uses.
kernel void greedy_argmax_f32_phase1(
// … (same buffers plus:
// device const uint* excluded [[ buffer(4) ]],
// constant uint& n_excluded [[ buffer(5) ]],) …
First, nonfinite latching: any NaN/Inf logit sets the high bit of
its partial index, the bit propagates through the tree, and phase 2 returns
0xffffffff (:156-157) — an error, not a silently wrong token; the
caller converts it into a hard failure (§21.6). Second, EOG exclusion:
the request’s end-of-generation tokens are masked to −INFINITY inside
the reduction only, leaving the stored logits untouched — “the retained
target logits remain byte-for-byte unchanged for logprob/session uses.”
That is the ignore-eos feature done without corrupting the distribution
other consumers see.
21.5 The Rust dispatch
A two-kernel reduction has a hazard the one-kernel version does not: the second kernel reads a buffer the first one wrote, and nothing in Metal volunteers to order that for you. Getting it wrong does not crash — it silently reads stale partials, which is how you end up debugging a sampler that is occasionally, unreproducibly wrong. So the ordering is stated by hand.
Two wrappers record the pairs. encode_greedy_argmax_f32 (the serving
variant) — phase 1 over ⌈202,048/1,024⌉ = 198 threadgroups, an explicit
memory_barrier_with_resources between the phases (phase 2 reads what
phase 1 wrote — a RAW hazard, ordered by hand here), phase 2 as one
threadgroup:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/metal/encode/lmhead.rs:123 (abridged to the two dispatches)
self.bind(encoder, "greedy_argmax_f32_phase1");
encoder.set_buffer(0, Some(values.metal()), 0);
// … (partials, n, excluded set + count) …
encoder.dispatch_thread_groups(MTLSize::new(blocks as u64, 1, 1), MTLSize::new(1024, 1, 1));
let partial_barrier: [&metal::ResourceRef; 2] =
[partial_values.metal(), partial_indices.metal()];
encoder.memory_barrier_with_resources(&partial_barrier);
self.bind(encoder, "greedy_argmax_f32_phase2");
// … (partials in, result at result_offset) …
encoder.dispatch_thread_groups(MTLSize::new(1, 1, 1), MTLSize::new(1024, 1, 1));
}
Its sibling encode_argmax_f32_rows (lmhead.rs:83-120) loops the plain
pair over rows of a logits matrix — one winner per row — for the DFlash
verify lane. In prose: phase 1 grid (198, 1, 1) × (1024, 1, 1); one
inter-phase barrier; phase 2 grid (1, 1, 1) × (1024, 1, 1).
21.6 Reading the result back — two routes, two sizes
Everything so far argues for a tiny read-back, and a tiny read-back is exactly what we expected to ship. The ancestor had already settled the question: the GPU already found the maximum, so copy the maximum and nothing else — “4 bytes and done”. We carried that expectation into the serving path, and it did not survive there: every consumer serving cares about turned out to want the numbers the argmax throws away.
This is the point where Muser’s design departs from the ancestor’s story, and the departure is the chapter’s core lesson: the size of the read-back is not decided by the reduction. It is decided by the policy standing behind it. Muser therefore has two routes, and which one a request takes is a statement about what that request intends to do with the numbers.
Route A — serving: the full distribution comes back. Session::decode
holds a retained, vocab-sized CPU buffer and refills it every token:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/api.rs:696
pub fn decode(&mut self, input: DecodeInput) -> Result<DecodeResult, EngineError> {
self.validate_tokens(&[input.token_id])?;
self.ensure_capacity(1)?;
// The decode path refills the retained distribution in place, so a
// token costs one vocabulary-sized copy for the result instead of two
// fresh allocations.
let mut logits = self.last_logits.take().unwrap_or_default();
if let Err(error) = self.forward_into(&[input.token_id], &mut logits) {
// … (failure leaves the previous distribution installed) …
}
// Fail closed: a broken row installs no distribution at all.
// … (finite scan, diagnostics elided) …
let next_token = argmax(&logits) as u32;
// … (result clone, retain) …
}
}
The GPU→CPU hand-off itself is one line at the end of the batch graph:
batch_logits.as_slice()[..token_count * self.cfg.vocab_size].to_vec()
(decode.rs:3659). Read it in two halves. The as_slice() is a
StorageModeShared zero-copy view — the CPU is looking straight at the
buffer the GPU wrote, and nothing has moved yet
(Ch 3: unified memory makes this a
memcpy, not a device transfer). The to_vec() is the half that costs:
202,048 f32 = 808,192 B ≈ 789 KiB per token, copied so the caller owns
a row that will not change under it.
What happens to that row next is deliberately dull. The CPU’s own
five-line first-maximum scan picks the greedy token, and
ensure_finite_logits refuses the row outright if any entry is nonfinite —
the same fail-closed instinct as the kernel’s high-bit latch, arriving by a
different road. Both helpers sit a few lines apart in the same file
(api.rs:1842-1849 and api.rs:1851-1856).
Why pay 789 KiB when 4 bytes would answer the greedy question? Because
serving’s consumers need the distribution, not the winner: the sampled
chain of §21.7 (temperature, top-k, top-p, typical-p over 202,048
entries), grammar re-rolls (§21.8), logprob responses, session snapshots —
and above all exact speculative acceptance (§21.9), whose contract is
“acceptance against full target distributions”
(verify_full_speculative_mt_ordered, sampling.rs:1033). The one
vocabulary-sized copy is the price of every downstream policy being exact.
Said the other way round, because this is the sentence the rest of the chapter hangs on: the read-back is not sized by what the winner costs to report, it is sized by what the hungriest consumer on the route needs to see. Greedy alone would be cheap. Greedy plus a grammar plus a speculator is not, and you do not get to find out which one you are until the request arrives.
Route B — the GPU-resident greedy chain: 4 bytes per token. When (and
only when) the policy is pure greedy, Muser keeps the whole loop on the
GPU: forward_greedy_streaming (decode.rs:1626) pre-encodes a pipeline
of complete token graphs, each embedding the previous step’s argmax
result directly from GPU memory
(encode_embedding_q4k_from_u32_buffer against
dflash_argmax_results, decode.rs:1756-1764), and reads back exactly
one 4-byte slot per completed token:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/decode.rs:1688
let produced = self.activations.dflash_argmax_results.as_slice()[completed].to_bits();
if produced == u32::MAX || produced as usize >= self.cfg.vocab_size {
return Err(MetalModelError::InvalidSnapshot(
"pipelined greedy argmax observed nonfinite logits or an invalid token".into(),
));
}
}
The .to_bits() reinterprets the stored f32 slot as the u32 the kernel
wrote — four bytes of meaningful data cross per token — and the
u32::MAX check is the greedy kernel’s fail-closed flag from §21.4
becoming a hard error. This route serves the DFlash block-decode lane and
the no-readback benchmark policy (“no-per-token-host-readback” is part of
the teacher-forced comparator contract, decode.rs:2118-2123). It is the
ancestor’s 4-byte device [ferrite-book Ch 20], alive on exactly the routes
where the policy permits it.
21.7 The sampler — pinned RNG, per-request state
What breaks if the random number generator is wrong? Nothing you can see in a single response — and everything you can see in a diff. Two engines given the same seed and the same prompt have to walk the same sequence of draws, or the cross-engine determinism contract from the top of this chapter is a slogan.
So the sampled path’s first commitment is not a sampler at all; it is an
engine. Muser carries a bit-for-bit reimplementation of libc++’s
std::mt19937 — “the std::mt19937 engine used by the source-pinned
llama.cpp sampler”. It is kept in-tree deliberately, “rather than using
StdRng, whose algorithm is deliberately unspecified … makes seeded API
results stable across Rust and rand releases”. The same commitment runs
downward: libc++’s exact uniform_f32/f64 conversions rather than
Rust’s, and snapshot/restore of the engine state, so a durable session
reopened later resumes mid-stream instead of quietly reseeding. The
evidence trail is short and worth keeping in one place — the engine and
its rationale at sampling.rs:53-56, the conversions at :107-120,
snapshot and restore at :88-105, and the test that pins known vectors
against libc++ at mt19937_matches_libcxx_engine_and_uniform_distributions
(:1105-1133).
On top of the engine sits the distribution chain: a scalar, ordered
pipeline, distribution_ordered (sampling.rs:399). The order is not
ours to choose. It applies, in source order, top_n_sigma masking against
the max (llama’s newer filter), top_k truncation, typical_p, then the
top_p nucleus cutoff, each over the full candidate list. What makes it
delicate is that the tie and ordering conventions must match upstream even
where upstream looks careless — and the source comments say so out loud:
“Upstream masks in place and intentionally leaves candidate order
untouched” (:432-434), “Locally-typical order is part of the source
contract” (:477-479). Those two comments are the difference between a
filter that agrees with llama.cpp and one that merely resembles it.
The state lives per request in the server, as four separated RNG streams plus sampler scalars — snapshottable for session persistence:
#![allow(unused)]
fn main() {
// crates/muser-server/src/openai.rs:4331
struct RequestSamplerState {
distribution_rng: Mt19937,
xtc_rng: Mt19937,
mirostat_rng: Mt19937,
mirostat_mu: f32,
adaptive: AdaptiveSamplerState,
}
}
The separation is deliberate: each stochastic feature burns its own stream, so enabling XTC cannot shift the draws the distribution sampler sees. Per-slot independence in serving is the scheduler’s business (Ch 34); this struct is the per-request half of that story, and its snapshot/restore is what lets a migrated session resume its exact draw sequence.
21.8 Grammar-constrained sampling — GBNF on the CPU
Now the awkward case. A request demands JSON, and the sampler — which knows nothing about JSON — draws a token that would break it. What should happen to that draw?
Structured-output requests constrain the token stream to a grammar — JSON schemas, quoted literals. The engine surface is a pinned llama-style GBNF matcher, and its module header is the specification:
#![allow(unused)]
fn main() {
// crates/muser-server/src/grammar.rs:1
//! Pinned llama-style GBNF parsing and incremental UTF-8 matching.
//!
//! The matcher is an Earley recognizer over Unicode code points. It keeps all
//! ambiguous stacks alive, accepts token byte fragments that end in a partial
//! UTF-8 sequence, and exposes acceptance separately so EOS is legal only at
//! a completed root rule.
}
An Earley recognizer is a chart parser that keeps every viable parse stack alive simultaneously — necessary because a tokenizer can split one grammar-legal string several ways. The matcher consumes bytes, so a token ending mid-UTF-8-sequence is accepted as a partial and the grammar state advances only when the code point completes; EOS is admitted only at the root, so the grammar can never strand an incomplete literal.
Selection uses llama-server’s rejection sampling discipline, with the reason in source:
#![allow(unused)]
fn main() {
// crates/muser-server/src/openai.rs:4469
// Pinned llama-server uses grammar rejection sampling: run the
// ordinary chain first, accept an eligible result immediately,
// and only rerun with a grammar mask after a rejected result. The
// rejected draw deliberately advances every stochastic sampler.
}
So the sampler of §21.7 runs unmolested; the winner is checked with
grammar_allows(grammar, model, first, eos) (openai.rs:4982); only a
rejection triggers a masked rerun. And the rejected draw still advances
every RNG — replicating llama-server’s draw-stream semantics exactly,
which is what keeps seeded, grammar-constrained generations comparable
across engines. This is another reason the full distribution rides to the
CPU: the mask-and-rerun needs the whole candidate vector.
21.9 Exact speculative acceptance — the CPU contract
The third consumer is the hungriest, and the one with the most to lose. Speculation is supposed to be free: a small model guesses ahead, the big model checks the guesses, and the output is the same text you would have got anyway, only sooner. Get the check subtly wrong and it stops being a speed-up and becomes a quality change nobody asked for and nobody can see in a benchmark.
That closes the loop with Ch 8’s draft model. When DFlash proposes tokens, acceptance is computed on the CPU against the full target distribution — the function the campaign calls the exactness anchor for speculation:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/sampling.rs:1033
pub fn verify_full_speculative_mt_ordered(
draft_tokens: &[u32],
draft_probabilities: &[Vec<f32>],
target_probabilities: &[Vec<f32>],
target_orders: &[Vec<u32>],
rng: &mut Mt19937,
) -> Result<SpeculativeDecision, SamplingError> {
// … (geometry validation elided) …
for (index, (&token, (draft, target))) in draft_tokens
.iter()
.zip(draft_probabilities.iter().zip(target_probabilities))
.enumerate()
{
let token = token as usize;
// … (bounds check elided) …
let q = draft[token];
let p = target[token];
let acceptance = if q <= 0.0 { 1.0 } else { (p / q).min(1.0) };
if rng.uniform_f32() <= acceptance {
continue;
}
let mut residual = target
.iter()
.zip(draft)
.map(|(&p, &q)| (p - q).max(0.0))
.collect::<Vec<_>>();
let total = residual.iter().sum::<f32>();
if total <= 0.0 {
residual.clone_from(target);
} else {
for probability in &mut residual {
*probability /= total;
}
}
// … (lines elided: the filter that builds `order` from
// `target_orders[index]`, keeping tokens with positive
// residual; see file) …
return Ok(SpeculativeDecision {
accepted: index,
next_token: sample_distribution_mt_ordered(&residual, &order, rng)?,
});
}
// … (all-accepted path: sample from the last target row) …
}
}
Read the math: each draft token is accepted with probability
min(p/q, 1) — the standard speculative-decoding rejection rule, which
makes the combined draft+verify process sample exactly from the target
distribution. On the first rejection, the residual
max(p − q, 0) is renormalized and the replacement token is drawn from it
through the same pinned Mt19937 stream (uniform_real_distribution<float>
per attempt, the libc++ double draw for selection — sampling.rs:1001-1007).
In plainer words: the draft model is allowed to be wrong as often as it
likes, and the residual step is what makes that harmless. It is not
allowed to change what the target model would have said. The accept rule
and the residual draw are two halves of one guarantee, and dropping either
half turns speculation from a lossless optimization into an approximation.
“Exact” is not a hope here; the qualifier compares every full target-logit
row in its gates (256 greedy tokens plus all rows,
[crates/muser-bench/src/remote.rs:8-10]). The engine-level driver
(Session::verify_batch, api.rs:913; the Metal mirror-SD split at
decode.rs:3298) gets the full story in
Ch 33 — including the
measured fate of moving this acceptance off the Mac.
21.10 Tradeoffs
Full-distribution read-back vs GPU-only selection. Route A costs ~789 KiB of unified-memory copy per token. Set that beside the traffic the same token already generates — a weight read of ~16.76 GB at the artifact scale (Ch 1) — and the copy lands near 0.005 % of it, which sounds like the end of the argument. It is not, and this is the part that trips people up. The copy is not a bandwidth event competing with the weights; it is a serial addition on the critical path, and cheap bytes on a critical path are still time somebody waits for. How much time, we cannot say: no retained measurement isolates its wall cost [unverified].
What the copy buys is exactness for three consumers at once: the sampler chain, grammar re-rolls, and speculative acceptance against full target distributions. Route B (4 bytes/token) exists precisely for the policy that needs none of them — pure greedy — and is the no-readback comparator policy. The ancestor’s framing — the GPU already found the max; copying the vector buys nothing for greedy — survives intact, scoped to the routes where it is true.
Two-phase tree vs one big kernel. A single tree over 202,048 logits
would need one threadgroup of 202,048 threads — impossible under the
1,024-thread cap, and the required threadgroup memory
(202,048 × 8 B ≈ 1.54 MiB) would blow the per-threadgroup budget on its
own. Two phases cost one extra dispatch and one barrier (§21.5) — noise
against the 924.6 MB LM head that precedes them.
Rejection-sampled grammar vs masked-first sampling. Masking the
distribution before sampling would guarantee grammar legality in one
draw, but it changes the draw stream and the probabilities the sampler
consumes; llama-server’s rejection form (ordinary chain, check, re-roll
with mask, rejected draw still advances every RNG — openai.rs:4469-4472)
preserves the comparator’s semantics at the cost of an occasional second
sample. Muser pins the rejection form for the same reason it pins
mt19937: cross-engine reproducibility is the product feature.
CPU acceptance vs GPU acceptance for speculation. The acceptance rule is trivially parallelizable, and for a while that looked like an invitation. Verification is the expensive half of speculation; the Mac is the busy machine; there was other hardware sitting on the network. Why not move the verifier off the Mac and let the Mac get on with decoding?
We built that lane and measured it, expecting the Mac’s returned time to pay for the wire. It did not come close. Take the wire and the draft model out of the accounting entirely and ask only how fast the remote verifier could go on its own: the verifier-only ceilings came in at 20.15/40.04/55.96 tok/s against the 107.9 tok/s kquant bar — the best imaginable case for the lane was already far under the number it had to beat, so no amount of tuning downstream could rescue it. That run is retained [nvfp4-distributed-speculative-frontier-20260818; Ch 33]. The native W4A4 variant went further in the wrong direction: 6.805 tok/s, with verification alone consuming 35.915 s of a 37.619 s span [nvfp4-fast-lane-evidence; ledger F-series]. At that point the verifier was not a step inside the decode loop; it was the loop.
What the failure taught is the sentence to take away from the whole section: exactness was never the casualty, time was. Nothing about moving acceptance would have made it less exact — the rule is the rule wherever it runs. What moving it does change is everything around it: the rule’s inputs are two full vocab-sized distributions per draft token, and its outputs feed the pinned RNG. So keeping acceptance on the CPU beside the sampler is not a purity argument. It is the arrangement that leaves one authoritative draw stream, and no network between the two halves of a decision that has to be made for every token.
21.11 Where the gap lives
Sampling is not the Metal gap — it is not on the Metal graph at all.
The dispatch-gap accounting covers closures from embedding to softcap;
selection runs host-side on the read-back (this chapter, §21.6), and the
GPU argmax pair exists for routes that remove host round-trips. The
+196-closure story of Ch 19 §19.8 is
untouched by any of this. If anything, selection is where the other
currency is spent: the read-back’s serialization and the sampler’s scalar
CPU loops are the kind of cost Ch 34’s
rendezvous budget has to absorb — measured in phase timings like
grammar and argmax_ns (state.rs:1566, api.rs:719-727), not in
dispatch groups.
And with that, the kernel walk is done: eleven chapters from an embedding lookup to a chosen token, every dispatch accounted. But look back at the path — every attention chapter from Ch 14 onward quietly depended on a structure this Part never costed: the KV that Q·Kᵀ read and the store kernel wrote, the ring that wrapped at 2,048, the planes that grew without bound. Each layer’s attention was borrowing memory at one thousand twenty-four bytes per token per layer (Ch 15) and we never once asked what the loan costs. That debt is about to become the whole story — what context weighs, why it — not the weights — decides how many slots a 96 GB Mac can serve, and how a cache becomes an asset you can seal, move, and trust. Part V begins with the bill: Ch 22.
References
crates/muser-engine/src/shaders/ferrite/argmax_f32.metal:7-39—argmax_f32_phase1(the chunk tree);:41-70argmax_f32_phase2;:72-158the greedy pair with the nonfinite latch and EOG exclusion (headers quote the contract).crates/muser-engine/src/metal/encode/lmhead.rs:83-120—encode_argmax_f32_rows;:123-161encode_greedy_argmax_f32(the inter-phase barrier).crates/muser-engine/src/api.rs:696-737—Session::decode: the retained distribution, CPU argmax, fail-closed finite scan;:1842-1849the CPUargmax;:1851-1856ensure_finite_logits.crates/muser-engine/src/decode.rs:1626-1728—forward_greedy_streaming, the GPU-resident chain;:1688-1693the 4-byte read and theu32::MAXfail-closed check;:1730-1765the embedding-from-argmax-buffer link;:3659the batch graph’s full-vocab read-back;:2118-2123the teacher-forced no-readback contract.crates/muser-engine/src/sampling.rs:53-120—Mt19937and its libc++ conversions;:399-509distribution_ordered(the sampler chain);:1008-1097verify_full_speculative_mt/_mt_ordered(exact acceptance);:1105-1133the libc++ vector test.crates/muser-server/src/openai.rs:4266-4379—AdaptiveSamplerState/RequestSamplerState(four RNG streams, snapshot/restore);:4413-4509sample_or_argmax(logit bias, repeat penalties, grammar rejection sampling);:4982grammar_allows.crates/muser-server/src/grammar.rs:1-71— the GBNF Earley matcher’s specification and types.crates/muser-bench/src/remote.rs:8-10,36-37— the exact-verification comparator policy (256 tokens + all logit rows; 0.95 acceptance floor).- [nvfp4-distributed-speculative-frontier-20260818] — the rejected distributed-verifier lane (verifier-only ceilings vs the 107.9 bar).
- [nvfp4-fast-lane-evidence-20260817] / [ledger F-series] — the native W4A4 verification no-go (6.805 tok/s).
- Ch 8, Ch 33 — the draft model and the full speculative story this chapter previews.
- Ch 34 — slots, rendezvous, and where the CPU-side selection cost lands; Ch 38 — the exact-token gate that makes greedy the reference policy.
- [ferrite-book Ch 20] — the ancestor’s argmax chapter: the two-phase tree, the deterministic tiebreak, and the 4-byte read, all ported and re-scoped to Muser’s two read-back routes.
- [Metal-PG] —
maxThreadsPerThreadgroupand the threadgroup-memory budget behind §21.3’s limits.
Chapter 22 — The price of context
status: polished · path: Muse Glimmer, pinned Muser tree
Prerequisites: Ch 15 (the KV planes, the ring, the two layouts — this chapter costed nothing there on purpose), Ch 9 (the 39/13 layer split, GQA 32:2), Ch 1 (bytes-per-token as the organizing number). This is a systems chapter: no new kernels, one bill.
22.1 What this chapter computes
Ch 21 closed the kernel walk and then pointed at a debt: every attention chapter from Ch 14 onward borrowed memory at “one thousand twenty-four bytes per token per layer” (Ch 15) and we never once asked what the loan costs. This part of the book is the repayment schedule. The next five chapters cover what the KV cache costs (this chapter), how the two storage regimes actually work (Ch 23), how the cache becomes a portable, sealed artifact (Ch 24), what a hit is worth (Ch 25), and how to move only the part you don’t already have (Ch 26).
This chapter derives every number by hand. The numbers are short enough to
derive, and a memory table you cannot re-derive is exactly how a wrong figure
survives into product copy. We have already caught one of those. Early in the
campaign the deep handoff was budgeted at a “~7 GB payload” — a figure that
felt right, got repeated, and was wrong by ~4×. The measured wire payload is
1,823,184,896 B, and the run that proved it is retained
[receipt phase4-disagg-20260820/130815-g900091/]. Where that estimate came
from is a story worth telling, and we tell it later in the chapter, once the
arithmetic is on the table to tell it against. Derive, then measure, then
reconcile.
22.2 The per-token bill, one layer at a time
Where does the memory actually go, and what breaks if the answer is wrong? Every figure in this chapter — a slot’s footprint, a machine’s serving capacity, the size of a handoff on the wire — falls out of a handful of integers, so a slip in any one of them is a slip in all of them downstream. Start from the model’s geometry, which is not a convention but a pinned constant of this engine:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/config.rs:13
pub const MUSE_LAYER_COUNT: usize = 52;
pub const MUSE_SWA_LAYER_COUNT: usize = 39;
pub const MUSE_NOPE_LAYER_COUNT: usize = 13;
pub const MUSE_SWA_WINDOW: usize = 2_048;
pub const MUSE_MAX_CONTEXT: usize = 131_072;
pub const MUSE_HEAD_COUNT: usize = 32;
pub const MUSE_KV_HEAD_COUNT: usize = 2;
pub const MUSE_HEAD_DIM: usize = 128;
pub const MUSE_KV_ROW_ELEMENTS: usize = MUSE_KV_HEAD_COUNT * MUSE_HEAD_DIM;
}
Ch 13 projected 32 query heads but only 2 KV heads — that is GQA, the grouping that makes the cache cheap before any cleverness is applied. One cached row of one layer holds, per token:
K row, one layer, one token:
n_kv_heads × head_dim × bytes_per_element
= 2 × 128 × 2 (f16, every Metal lane)
= 512 bytes
K + V together (two separate f16 buffers — decode.rs:182-190):
= 2 × 512 = 1,024 bytes per layer per token
That is the doc’s own formula, 2 KV heads * 128 values * 2 bytes * (K + V) = 1,024 bytes [docs/memory-footprint.md §KV formula], rebuilt from the
constants. The element type is f16 on every Metal lane —
the GpuHalfBuffer field type is the constraint
(crates/muser-engine/src/decode.rs:182-184) — because the parity anchor,
pinned llama.cpp, runs F16 KV [docs/muser-architecture.md]. Whole model:
per token, all 52 layers: 52 × 1,024 = 53,248 B (≈ 52 KiB)
Here is the part that trips people up: both layer classes pay the same per-token price. A sliding layer’s row costs exactly what a full layer’s row costs — 1,024 B. The 39/13 split does not change the price of a token. It changes how many tokens keep paying.
22.3 Two regimes: one curve flattens, one doesn’t
If every token costs the same, where does the expense of a long conversation
come from? From the stopping points, not the price tag. Same price per token,
different stopping points — so total footprint is a function of how far each
layer class keeps paying. For a slot configured with context C, the
footprint formula is
[docs/memory-footprint.md §KV formula]:
swa_rows = min(C, 2,048)
nope_rows = C
slot_kv_bytes = (39 * swa_rows + 13 * nope_rows) * 1,024
Why per class:
- The 39 SWA layers attend only to the trailing 2,048 tokens
(
layer % 4 != 3,config.rs:84-93), so their planes are allocated once atmin(max_context, sliding_window)capacity (decode.rs:1346-1347) and never grow. Every token past 2,048 overwrites a row (Ch 15’s ring) instead of adding one. - The 13 NoPE layers attend to the entire history, so their planes are
allocated at
max_contextcapacity (decode.rs:1348) and fill one row per token until the model limit.
Said in plainer words, because this is the sentence the rest of Part V leans on: the sliding layers rent one fixed room and keep re-using it, while the full layers buy a new shelf for every token and never sell one back. Draw the two components as functions of context:
KV bytes
1.83 GB ┤ NoPE: 13×C×1,024
│ ▄▄
│ ▄▄▄▄
│ ▄▄▄▄ ← grows linearly,
│ ▄▄▄▄ never flattens
│ ▄▄▄▄
81.8 MB ┤▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ SWA: 39×2,048×1,024
│ ↑ flat from C = 2,048 onward (constant after wrap)
└──┬──────┬──────────┬──────────┬──────→ context C
2,048 32,768 65,536 131,072
Figure 22.1: the two growth regimes. At full depth the NoPE term is 1,744,830,464 B and the SWA term is 81,788,928 B — 95.5 % of a slot’s KV bytes live in the 13 full layers. The curve’s shape has a direct consequence for cost accounting, and it is the amortization insight that makes long context survivable at all:
marginal KV cost of one more token:
below 2,048: 52 × 1,024 = 53,248 B/token
above 2,048: 13 × 1,024 = 13,312 B/token (exactly 13/52 = 25 %)
kv(131,072) / kv(2,048)
= 1,826,619,392 / 109,051,904
= 16.75× — not the naive 131,072/2,048 = 64×
Thirty-nine of fifty-two layers stop billing at token 2,048. A 64× longer context costs 16.75× more KV — the architecture quietly discounted three-quarters of the cache. Keep that 13,312 B/token figure; it returns as the exact NoPE-per-token payload unit in Ch 26.
One measurement-grade cross-check that the two derivations agree. Below the window, the per-class formula and the whole-model formula must coincide:
(39 + 13) × 2,048 × 1,024 = 109,051,904 B
52 × 2,048 × 1,024 = 109,051,904 B ✓
Two routes, one number — the habit the ancestor book taught with its
block-form versus element-form footprint derivations [ferrite-book Ch 14],
kept here because it catches transcription errors no single formula can.
22.4 The footprint table, derived and cross-checked
A formula is cheap. What decides a release is the number a machine has to
hold, so the formula has to become a table: one slot and the four-slot release
configuration, at three depths [docs/memory-footprint.md]:
C = 8,192:
slot = (39×2,048 + 13×8,192) × 1,024
= (79,872 + 106,496) × 1,024 = 190,840,832 B = 0.191 GB
×4 slots = 763,363,328 B = 0.763 GB
C = 32,768:
slot = (79,872 + 425,984) × 1,024 = 517,996,544 B = 0.518 GB
×4 slots = 2,071,986,176 B = 2.072 GB
C = 131,072:
slot = (79,872 + 1,703,936) × 1,024 = 1,826,619,392 B = 1.827 GB
×4 slots = 7,306,477,568 B = 7.306 GB
| Context per slot | One-slot KV | Four-slot KV |
|---|---|---|
| 8,192 | 0.191 GB | 0.763 GB |
| 32,768 | 0.518 GB | 2.072 GB |
| 131,072 | 1.827 GB | 7.306 GB |
Table 22.1: decimal GB, KV planes only — my arithmetic lands on the
document’s values to the byte [docs/memory-footprint.md]. These are
topology-derived allocation numbers, not a measured peak RSS, and the doc
says so on its first line: it is “a topology-derived allocation estimate,
not a measured peak-RSS result and not launch guidance for unqualified
Macs” [docs/memory-footprint.md].
Two honesty notes the same document insists on, and this book inherits:
- KV planes are not the process. The target GGUF is 16,756,681,056 B on
disk (
crates/muser-engine/src/lib.rs:13-14), the DFlash draft GGUF 1,631,205,312 B and the vision projector 1,400,328,928 B — the latter two load only when configured[docs/memory-footprint.md §Other material allocations]. Add shared Metal pipelines and workspaces, per-slot logits and sampler state, prefill chunk buffers (~0.99 GB of f32 batch-activation widths, reused, chunked at 512 positions — “must not be labeled peak RSS”), network buffers, and temporary restore/migration material. “Summing artifact sizes with the KV formula is therefore only a lower bound, not a safe RAM recommendation”[docs/memory-footprint.md]. - A staging generation exists. Restore and context shift build a
replacement state before swapping it in, so an operation can temporarily
require additional state — but staging “is not a fifth concurrently
serving slot”
[docs/muser-architecture.md §Slots and scheduling](state.rs:240-243; the full mechanism is Ch 23).
22.5 The 131,072-position limit
Why does the chart stop where it stops? Not because measurement ran out of
patience — because a rule says so, and the rule is worth knowing before you
plan capacity around the last column of the table.
The horizontal axis of Figure 22.1 stops at 131,072 because the model stops
there: MUSE_MAX_CONTEXT: usize = 131_072 (config.rs:17), and the
architecture doc states the serving rule — “The model limit is 131,072
positions per slot” [docs/muser-architecture.md §Context and sessions].
The NoPE planes are allocated at exactly that capacity
(decode.rs:1348), which is why the one-slot figure above is a ceiling, not
a trajectory that keeps going.
Two practical footnotes. First, the deep campaign cells you will meet in
Ch 25 and Ch 26 use
fixtures at 130,815 and 131,008 tokens — inside the limit, not at it; depth
labels on one claim are never the depths of another (the measured-numbers
ledger’s rule 4). Second, the limit is per slot, so four concurrent slots
each get their own 131,072 positions — the table’s 7.306 GB row is that
configuration’s KV bill [docs/memory-footprint.md].
22.6 Why KV — not weights — decides how many slots a 96 GB Mac serves
With the per-slot bill and the 131,072 ceiling on the table, we can finally
ask the question an operator actually cares about: how many concurrent request
slots can one machine serve? The decode host is one Apple Silicon Mac, an
M3 Ultra with 96 GB of
unified memory [docs/memory-footprint.md §intro; docs/release-provenance.md].
The shape of the answer surprises people who arrive from a weights-first
intuition — the model is the big thing on disk, so surely the model is the
constraint — so we build it in two steps and let the two terms argue.
Weights are paid once and shared. All slots run the same pinned model.
The 16,756,681,056 B GGUF is mmap’d once; immutable weights, Metal
pipelines, and the DFlash executor are explicitly shared across slots
[docs/muser-architecture.md §Slots and scheduling] (state.rs:221-236).
Going from one slot to four does not buy four copies of the model.
KV is paid per slot, forever, at the depth the conversation reaches.
Each slot owns “independent target KV, DFlash state, logits, RNG, sampler
and grammar state” [docs/muser-architecture.md §Slots and scheduling].
Table 22.1 is that cost: the only term that scales with context depth is
the slot’s KV. At full depth, four slots add 7.306 GB on top of the shared
artifacts — and the deeper each conversation runs, the larger that term
grows, with the NoPE planes doing essentially all of the growing (Figure
22.1). Serving capacity at depth is therefore KV-bound: the weights fit or
they don’t (a question answered once, at load), while the number of
long-context conversations you can host is a question about 1,024 B per
layer per token per slot.
Two boundaries keep this honest rather than purely arithmetic:
- The slot count is also a design constant, not just a memory outcome.
The packed decode graph accepts 1..=4 sequences
(
forward_decode_group,decode.rs:4874); the server enforces--parallelin 1..=4 (state.rs:1054-1056). Four is the release configuration, and the memory contract is stated as such: “The v0.1 release contract is four full-context slots on the 96 GB M3 Ultra. No smaller-memory configuration may be advertised as supported until a retained hardware qualification measures it”[docs/memory-footprint.md §Release requirement]. - The final serving benchmark must retain process and system memory
evidence for the exact four-slot binary, artifacts, context cell, and
concurrency before any of this becomes a product claim
[docs/memory-footprint.md §Release requirement]. Until that matrix passes, this chapter supports engineering capacity checks only.
Put as a ratio, the two currencies this book tracks: one decode token reads ≈16.76 GB of weights (Ch 1, artifact size above) and writes 53,248 B of KV below the window, 13,312 B above it — a factor of ~315,000 between the per-token weight read and the per-token KV write. Weights dominate the speed of a token (Ch 1); KV dominates the capacity of the machine. The whole of Part V is about spending the second currency well.
22.7 Where the bytes go at depth: the 95.5/4.5 split
Does any of this arithmetic survive contact with a real transfer? It should:
the same per-class arithmetic, applied to the wire instead of the slot,
predicts what a deep handoff must carry, and a prediction that misses is a
sign the model of the cache is wrong somewhere. The measured 130,815-token
disaggregated payload is 1,823,184,896 B (payload_bytes verified in
the client receipt [receipt phase4-disagg-20260820/130815-g900091/]), and
it reconciles to the byte against this chapter’s formula:
NoPE: 130,814 rows × 13 layers × 1,024 B = 130,814 × 13,312 = 1,741,395,968 B
SWA: 3 groups × 2,048 rows × 13,312 B = 81,788,928 B
total = 1,823,184,896 B ✓
(Two details you will meet again: the NoPE row count is 130,814, not
130,815, because the receiver deliberately holds back the boundary token and
decodes it locally; and the SWA rings travel as three 13-layer groups — the
transfer schedule of Ch 24.) By these terms,
95.5 % of a deep payload is the 13 NoPE planes and 4.5 % is the 39 SWA
rings [docs/kvpack-merge-handoff §3 D1, §6]. This is not incidental: it
is the load-bearing fact that the NoPE layers are position-free
(Ch 14 — no RoPE, so their rows are relocatable
bytes: “relocate = memcpy — the whole kvpack free lunch”,
crates/muser-engine/src/lib.rs:8-10), and it is why the entire Part VI
wire economy is really a NoPE-plane economy.
That reconciliation is the tidy ending. The first attempt was not tidy, and
this is the war story promised at the top of the chapter. We needed a payload
estimate before the deep cell had ever run, and the fastest number to hand sat
in the producer’s own configuration: --kv-cache-memory-bytes, the knob that
tells the vLLM side how much memory to reserve for cache. It read 7–8 GB, so
we wrote “~7 GB” for this same payload and expected the receipt to land near
it. The receipt landed nowhere near it. What the knob describes is how much
memory the producer allocates to hold cache for whatever sessions it happens
to be serving; what crosses the wire is one session’s rows, and the two
quantities are not even the same kind of thing. The lesson is small enough to
carry: allocation is not traffic; traffic is not allocation. It is now written
down twice, in the merge-handoff audit [docs/kvpack-merge-handoff §3 D1] and
in the campaign ledger’s landmine list, so the next person to reach for a
convenient configuration value finds the warning first.
22.8 Tradeoffs
Why f16 KV and not quantized, at 4 bits or 8. Halving (or quartering)
Table 22.1 looks tempting at 7.306 GB per four slots — it is the first idea
anyone has after reading that table, and we had it too. It is not taken, and
the reason is the exactness contract rather than conservatism-as-a-virtue. The
parity anchor is pinned llama.cpp running F16 KV, so the live planes are
GpuHalfBuffer f16 on every Metal lane (decode.rs:182-184); the lane
table’s decode rows all say “FP16 KV” [docs/muser-architecture.md]. A
quantized live cache would change attention inputs, and changed attention
inputs change logits. We know how little of that the contract tolerates,
because the dispatch-gap campaign already ran the experiment on a different
structure: a norm fusion moved logprobs 3.197e-4 against a 1e-4 contract, and
it was rejected on that number alone
[docs/decode-dispatch-gap-20260815.md]. Nothing about a quantized cache
would miss more gently than a fused norm did.
The compressed encodings are not banned everywhere, only from the hot path.
The interchange format carries both — PlaneEncoding::{F16Le, F32Le},
crates/muser-engine/src/cache.rs:13-16 — because the wire serves producers
and archives beyond the live planes, and a KIVI-style 4-bit store lives inside
kvpack as a CPU reference codec with honest error bounds, “not the hot
path” [docs/kvpack-merge-handoff §5]. The measured consequence of staying
f16 is everything in Ch 25: bit-identical warm hits.
Ring-plus-growing vs paging. At the fork, the default was to copy the
ancestor. Its cache was paged — 16-token blocks behind a block table, a whole
allocator chapter [ferrite-book Ch 14] — and inheriting a working allocator
is cheaper than arguing with one. Muser has no page table, and the arithmetic
of this chapter explains why the design didn’t need one: the 39 SWA layers bound
themselves (a full ring is the live set; nothing to evict — 81.8 MB,
Figure 22.1’s flat line), and the 13 NoPE layers’ prefix sharing is handled
one level above the GPU, by kvpack’s content-addressed chunks
(Ch 24), not by in-GPU indirection. Paging bought
the ancestor fragmentation control it needed at 8 GB; at 96 GB with a
bounded 75 % of layers, the simpler structure wins on review surface —
which is a bet, not a measurement, and it is labeled as one. What was
measured is the per-class byte split this chapter derives
([receipt phase4-disagg-20260820/130815-g900091/]), and any future format
change must re-derive §22.7’s payload decomposition before predicting wire
behavior.
The “grows with context” correction. The ancestor chapter’s motivating
sentence — the KV cache is the structure that grows with context — is
true for 13 of Muser’s 52 layers and false for the other 39
[ferrite-book Ch 14] (the port audit’s flagged hazard #1). Porting that
sentence uncorrected would misprice everything: capacity planning
(§22.6), wire payloads (§22.7), and the warm-up economics of
Ch 25 all turn on which layers grow. This chapter’s
standing instruction: any KV statement must name its layer class.
Where the gap lives. This chapter is not the Metal gap — nothing here is a dispatch. If anything, it is the gap’s opposite: the KV-publication splits and SWA staging groups that Ch 15 §15.9 counted inside the +196-closure accounting exist because the store is structured for exactness-preserving publication, and this chapter is the bill that explains why nobody cheapened them.
22.9 What comes next
The bill is derived, cross-checked, and split by layer class: 1,024 B per layer per token; 39 layers stop at 2,048 rows, 13 grow to the model limit; a full-depth slot costs 1.827 GB and four of them 7.306 GB on a 96 GB machine whose weights are paid once. But the arithmetic treated the two regimes as curves on a chart. The chart is implemented — as a ring whose write pointer, logical origin, and physical origin advance together across a wrap, next to a growing plane whose per-head spans must stay contiguous, plus a server policy that decides what to do when context exceeds the limit. That machinery — and the reason a restored ring must keep its rotation to replay bitwise — is Ch 23.
References
crates/muser-engine/src/config.rs:13-21— the pinned geometry constants (quoted);:84-93thelayer % 4 == 3partition rule.crates/muser-engine/src/decode.rs:182-190—MetalKvPlane(f16 fields);:1338-1358per-layer-kind capacity allocation (min(max_context, sliding_window)vsmax_context).crates/muser-engine/src/lib.rs:7-14— model facts: 39/13 split, GQA 32:2, head_dim 128, the 16,756,681,056 B artifact, NoPE relocate-as-memcpy.crates/muser-engine/src/cache.rs:13-16—PlaneEncoding::{F16Le, F32Le}(interchange encodings, not live planes).crates/muser-server/src/state.rs:221-243— slot independence vs shared weights; the out-of-poolstaginggeneration.crates/muser-server/src/state.rs:1054-1056,decode.rs:4874— the 1..=4 slot bound.[docs/memory-footprint.md]— the 1,024 B row formula, the one-slot / four-slot table (§22.4’s cross-check), other material allocations, the 96 GB M3 Ultra release contract, and the lower-bound honesty rule.[docs/muser-architecture.md]— §Slots and scheduling (shared weights, staging), §Context and sessions (the 131,072 per-slot limit), the FP16 KV lane table.[receipt phase4-disagg-20260820/130815-g900091/]— the 1,823,184,896 B deep payload (payload_bytesverified); decomposition per[docs/kvpack-merge-handoff §3 D1, §6].[docs/decode-dispatch-gap-20260815.md]— the exactness-contract precedent cited in §22.8.- Ch 15 — the per-layer-class split this chapter costed; owns the store kernels and layouts.
- Ch 23 — the ring machinery, the growing plane, context-shift policy.
[ferrite-book Ch 14]— the ancestor’s paged-Q8 cache: the dual-derivation device (ported in §22.3) and the “grows with context” framing (corrected in §22.8).
Chapter 23 — The SWA ring and the growing cache
status: polished · path: Muse Glimmer, pinned Muser tree
Prerequisites: Ch 22 (the two cost curves), Ch 15 (the planes, the layouts, the store kernels — this chapter goes deeper, not around), Ch 16 (the route ladder that reads these planes), Ch 8 (speculative blocks that must be able to roll back).
23.1 What this chapter is about
Ch 22 ended with two curves: one flat at 81,788,928 B (the 39 sliding layers, bounded at 2,048 rows), one climbing to 1,744,830,464 B (the 13 full layers, growing to 131,072). This chapter is the machinery under the curves — not whether the memory exists, but how a token gets into it, how a chunk of tokens crosses the ring boundary without destroying the attention inputs, how the same bytes come back out as a snapshot, and what the server does when the context outgrows the model’s limit.
Each of those is a junction where the obvious implementation is available, cheap, and wrong, and where the shipped answer only makes sense once you have watched the obvious one fail. So we walk them in that order: the fork first, then what breaks, then the code that survived. The receipts are kept where they were earned.
The recurring lesson of this chapter is that the ring is not a storage detail. Its rotation is numerically observable (bitwise replay depends on it), its wrapped shape constrains which pinned kernels may read it, and its overwrite-in-place semantics force a stage-then-commit protocol in prefill and a row-retention protocol in speculative decoding. The growing NoPE plane has none of these problems — which is exactly why it, and not the ring, is the part of the cache that becomes a portable asset in Ch 24.
One piece of background from Ch 15 §15.2,
kept to one paragraph: each layer’s cache is a MetalKvPlane, two f16
buffers plus five fields of explicit bookkeeping — capacity, len,
origin_logical, origin_physical, head_major, declared together at
decode.rs:182-190. Sliding layers get capacity
min(max_context, 2,048) token-major; full layers get max_context
head-major (decode.rs:1346-1348). Keep one invariant in view above all
the others, because most of this chapter is a consequence of it: physical
placement is never derived from an absolute token position. Prefill’s module
doc says it in a line — “physical placement is never derived from absolute
position”, prefill.rs:15-17.
23.2 Reserving rows: append for one token, append_batch for a chunk
Start with the smallest question the cache has to answer. Where does the next token’s key and value actually go — and what stops that write from landing on a row that some query still needs? Get this wrong and nothing downstream can save you: a corrupted row is not detected by attention, it is silently attended to.
Ch 15 §15.5 walked the single-token
append, which lives at decode.rs:263-284. It checks continuity against
origin_logical + len and fails closed, then either writes at
(origin_physical + len) % capacity (still filling) or overwrites
origin_physical and advances both origins (wrapping). Decode calls it once
per layer per token (decode.rs:5643). Prefill chunks need the batched
form, and its arithmetic is where the ring’s edge cases live:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/decode.rs:286
fn append_batch(
&mut self,
layer: usize,
start_position: usize,
token_count: usize,
) -> Result<(usize, usize), MetalModelError> {
let expected = self.origin_logical + self.len;
if start_position != expected {
return Err(MetalModelError::CacheDiscontinuity {
layer,
expected,
got: start_position,
});
}
let total = self
.len
.checked_add(token_count)
.ok_or_else(|| MetalModelError::InvalidSnapshot("cache length overflow".into()))?;
if total <= self.capacity {
self.len = total;
} else {
let overflow = total - self.capacity;
self.origin_logical += overflow;
self.origin_physical = (self.origin_physical + overflow) % self.capacity;
self.len = self.capacity;
}
let source_first = self.origin_logical.saturating_sub(start_position);
Ok((source_first, token_count - source_first))
}
}
Walk the two regimes with numbers. They look symmetric on the page; they are not, and the asymmetry is the whole chapter in miniature.
Wrapping (SWA, from the first chunk that crosses 2,048). len = 1,900,
chunk of 512, capacity 2,048: total = 2,412 > 2,048 — this chunk does
overflow a SWA ring, so take it as the wrap case directly. overflow = 364; the window’s
logical start advances 364 rows (origin_logical: 0 → 364), the physical
origin advances 364 rows, len saturates at 2,048. The 364 evicted rows
were the oldest ring rows, so every row of the new chunk is still live:
source_first = 364 − 1,900 saturates to 0, and the function returns
(0, 512) — all 512 source rows survive. In steady state (len already
== capacity == 2,048, chunk 512), overflow = 512, again all from old
rows, again (0, 512). The returned pair exists for the asymmetric case
where the chunk itself is wider than the whole window (token_count > capacity, possible only in tiny test geometries): then part of the chunk’s
own head has already scrolled out before the chunk ends, and
source_first > 0 names the first surviving source row. The
saturating_sub is the guard that keeps that case a number, not a panic.
Say the returned pair as a question and its answer, because it is the one
part of append_batch that reads like bookkeeping and is not: of the rows I
just handed you, which are still inside the window now that the reservation
is done? In every geometry the production model can reach, the answer is all
of them and the pair is a formality. It is written as arithmetic anyway, so
that the geometry where the answer is not all of them returns a fact
instead of crashing.
NoPE degenerates to pure append. A growing plane’s capacity is
max_context, total <= capacity always holds inside valid context bounds,
the else branch never runs, origin_logical stays 0, origin_physical
stays 0 — the modulo never fires and the “ring” is an array. That is not an
accident; it is the design’s way of making one code path serve two regimes:
the ring machinery costs a NoPE plane nothing.
Both append forms fail closed before any GPU write happens:
CacheDiscontinuity (“Metal KV cache for layer {layer} expected logical
position {expected}, got {got}”, decode.rs:117-122) means a skipped or
replayed position can never silently alias a live row. When it trips, the
operator sees the layer index and both positions — enough to find the caller
that broke continuity. That is the answer to the question this section opened
with, and it is deliberately boring: a write cannot land on a row it does not
own, because a request to do so is refused before the encoder is touched.
23.3 Crossing the wrap in prefill: the staging shadow
Reserving a row was the easy question. Here is the hard one: when a prefill chunk wraps the ring, what exactly is it allowed to write, and when?
The obvious implementation is the one anybody writes first. Store the chunk’s
keys and values into the ring, then run attention over the ring, the way
decode does it a token at a time. Follow that through a wrap and it comes
apart. A wrapped ring has a property that a contiguous buffer never has:
the live window is split across the physical array’s seam. Rows
[origin .. capacity) hold the older half, rows [0 .. origin) hold the
newer half. The attention kernels of
Ch 16 want one linear span of rows, and the
seam is not one. Worse, a chunk that wraps will overwrite the oldest live
rows — the very rows this chunk’s own queries must still attend to. Storing
first is a WAR hazard committed against your own
inputs: the chunk eats its own context, and nothing in the output announces
it. The lesson is a sequencing law, not a bug fix — on a ring, attend before
you overwrite is a precondition, not a preference.
So a wrapped SWA prefill does not write the ring at all until attention is done. It stages:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/metal/encode/attn.rs:103
pub fn encode_stage_swa_prefill_f16(
&self,
encoder: &ComputeCommandEncoderRef,
current_key: &GpuBuffer, // this chunk's fresh K (f32, from projections)
current_value: &GpuBuffer,
ring_key: &GpuHalfBuffer, // the live (rotated) ring
ring_value: &GpuHalfBuffer,
staged_key: &GpuHalfBuffer, // detached shadow: old rows ‖ new rows
staged_value: &GpuHalfBuffer,
kv_dim: usize,
old_len: usize,
old_origin_physical: usize,
ring_capacity: usize,
token_count: usize,
) {
}
The kernel muser_stage_swa_prefill_f16 (shaders/muse_reference.metal:1240)
un-rotates the old ring rows into logical order in the shadow, appends the
chunk’s rows after them, and attention runs over the shadow as one
contiguous span — the FA2 route (encode_flash_attention_v2,
decode.rs:4329-4346) or the llama vec route with its padded-index
materialization (§23.7). Only after attention does the CPU commit the
reservation:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/decode.rs:4348
let (_source_first, _source_count) = self.cache[layer_index].append_batch(
layer_index,
start_position,
token_count,
)?;
}
and the next chunk sees a consistently rotated ring. The non-wrapping
fallback branch keeps the ordering explicit with a comment — “Attend before
overwriting any still-visible old rows” (decode.rs:4356) — the same law,
stated for the route where the overwrite is partial.
A shadow copy per sliding layer is not free, and rather than hide the cost we
counted it. The batch graph’s wrapped-ring work shows up in the dispatch-gap
accounting as 39 SWA wrapped-ring staging groups, one per sliding layer.
The obvious saving is to teach attention to read a wrapped ring directly and
delete the shadow; the reason that saving is still on the table and not in
the tree is written into the accounting note itself, which keeps the groups
“until a bit-exact ring-aware replacement exists”
[docs/decode-dispatch-gap-20260815.md §Corrected closure-count diff]. A cheaper path exists. A cheaper path that
reproduces the same reduction order bit for bit does not, yet — and where
those two compete, exactness wins and we pay the groups. It is the same
verdict, for the same reason, as the 52 KV-publication splits of
Ch 15 §15.9.
23.4 Snapshots: logical order going out, rotation preserved coming back
What has to be true for a cache to leave the process and come back without changing a single logit? The going-out half of that question is plumbing. The coming-back half is where the ring taught us something we did not expect, and it is the one idea in this chapter worth slowing down for.
To hand a plane to anything outside the engine — a durable pack (Ch 24), a migration (Ch 26) — the rotated physical layout must become logical: ascending token order, no rotation, no head interleave assumptions about the consumer. The snapshot walk does exactly that, per layout:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/decode.rs:327
for logical_offset in 0..self.len {
let physical = (self.origin_physical + logical_offset) % self.capacity;
if self.head_major {
for kv_head in 0..kv_dim / head_dim {
let start = (kv_head * self.capacity + physical) * head_dim;
key_logical.extend_from_slice(&key[start..start + head_dim]);
value_logical.extend_from_slice(&value[start..start + head_dim]);
}
} else {
let start = physical * kv_dim;
key_logical.extend_from_slice(&key[start..start + kv_dim]);
value_logical.extend_from_slice(&value[start..start + kv_dim]);
}
}
}
Now the reverse, detached_from (decode.rs:351-417), and the fork we
walked into. The tidy way to install a snapshot is to take the rows in
ascending logical order and lay them down in ascending physical order
starting at the front of the buffer. Every row is present, every row sits in
the right sequence relative to its neighbours, and the plane’s contents are
by any structural test identical to the session you captured. We would have
called that a correct restore. The bitwise replay test disagreed.
Ch 15 §15.6 quoted the load-bearing comment,
and it is the explanation: attention scans rows in physical order, float
accumulation is order-sensitive, so a restore “packed at origin 0 can
never replay a wrapped live session’s logits bitwise”
(decode.rs:376-380). Say it the other way round, because this is the part
that trips people up: floating-point addition is not associative, the
attention reduction sums over rows in the order they physically sit, and
therefore where a row sits is part of the answer. A neatly packed restore
is arithmetically tidy and numerically a different session. The fix is one
line of arithmetic plus a layout-aware scatter:
rotation = origin_logical % capacity (decode.rs:383)
token-major: copy the logical rows as a head at [rotation .. capacity)
and a tail at [0 .. rotation) (decode.rs:397-406)
head-major: for each logical row, for each KV head:
destination = (head * capacity + (rotation + logical) % capacity)
(decode.rs:384-395)
Note what the rotation formula says about the two regimes: a NoPE plane’s
origin_logical is always 0, so its rotation is always 0 and the head-major
scatter reduces to a plain copy at physical = logical — the ring logic
switches itself off. A restored SWA ring comes back rotated exactly as a
sequentially-built live ring would sit at that logical origin, because
origin_logical % capacity is where sequential appends would have left the
physical origin. The property is test-enforced, not aspirational — the
comment names the test: real_model_wrap_boundaries_and_detached_restore_ replay_exactly (decode.rs:380).
The interchange contract sits one level up, in SessionCacheSnapshot —
“A complete restorable cut. The 39 SWA layers contain the complete logical
tail and the 13 NoPE layers contain [0, position)”
(crates/muser-engine/src/cache.rs:39-41). Its shape gate is fail-closed
per layer: an SWA plane must carry exactly min(position, window) rows
starting at position − count, a NoPE plane exactly position rows from 0,
with byte lengths checked to the element (cache.rs:62-118). Two
consequences worth naming. First, the interchange never carries rotation —
rotation is reconstructed on install by the formula above, so a pack’s
bytes are layout-stable while replay stays bitwise. That is worth restating,
because it is the trick: the rotation is not data, it is a function of the
logical origin, so it can be thrown away on the wire and rebuilt on arrival.
Second, the CPU oracle is deliberately not a ring. It allocates f32
full-history planes and applies the window as a mask, which means its
snapshots and Metal’s are mutually uninstallable — one side is F32Le, the
other demands ProductionF16Required, and each refuses the other’s bytes at
the gate. We kept the receipts for that refusal: cache.rs:14-17,
muser-kvpack/src/session.rs:164-166, and
[docs/kvpack-merge-handoff §4]. Exactness by incompatibility: the two
backends cannot accidentally share state that only one of them defined.
23.5 The growing plane: why head-major suits append and relocation
The ring has now spent three sections earning its complications. Put the same questions to the other plane — where does a row go, what happens at the boundary, what does it cost to move it somewhere else — and the answers come back suspiciously short. Why does the growing plane get the easier life? The answer reaches past this chapter: the plane whose rows can be moved without changing what attention computes is the plane that becomes a portable asset later in the book, and the one that cannot is the plane that stays home.
The NoPE plane’s layout — [kv_head][capacity][head_dim] — pairs with its
job in three ways, each anchored in code you have already met:
- The reader wants per-head spans. The pinned llama.cpp
flash_attn_extvec kernel addresses KV head-major withns10 = 128strides (metal/encode/attn.rs:503-511, Ch 15 Figure 15.1). A head’s whole history is one linear span; the kernel never crosses a seam because a growing plane has no seam. - Append never wraps. §23.2’s NoPE degeneration means physical order
is logical order,
origin_logical = 0forever, and the batch store kernel’s head-major index —(kv_head * capacity + physical) * head_dim + dim(muse_reference.metal:1228) — is a plain row append per head. - Relocation is memcpy. The 13 NoPE layers apply no rotation
(Ch 14: no RoPE at all), so a row’s bytes do
not encode its absolute position. Moving row 5,000 to a different
machine, or installing it at a different physical offset, changes
nothing about what attention computes with it. The engine’s own module
doc names this “the whole kvpack free lunch” (
lib.rs:8-10), and the interchange’s install math is the proof by construction — the head-major tile scatter inwrite_f16_tile(cache.rs:205-222) is byte movement plus index arithmetic, no numeric transformation anywhere.
The ring cannot make claim 3: an SWA key row was rotated by RoPE at store time into its absolute position, so its bytes are position-bound. This single asymmetry — position-free growing planes versus position-bound bounded rings — drives everything from the transfer schedule (NoPE tiles stream during CUDA prefill; SWA groups ride along as window snapshots, Ch 22 §22.7) to delta admission rules (Ch 26).
23.6 The third interaction: speculative blocks and the checkpoint
Speculation puts a question to the cache that nothing else in the engine asks: can you undo? Everything so far has been an append discipline, and an append is a commitment — but a rejected draft block needs its rows to have never happened. The naive answer is to copy the cache before each round and put it back on rejection, and the two curves this chapter opened with price that immediately: it is a multi-gigabyte copy per speculative round, which is to say it is not an answer. What the engine does instead splits along the same seam as everything else here.
DFlash speculative decoding (Ch 8, Ch 33) proposes a block of up to 16 tokens, the target verifies them, and on rejection the cache must roll back to the block’s start. The two regimes pay differently, and the checkpoint type says so in its own doc comment:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/decode.rs:208
/// Lightweight transactional checkpoint for one speculative verification
/// block. Growing NoPE planes only need their logical metadata rewound. SWA
/// planes may overwrite live ring rows, so the small set of destinations
/// touched by the candidate block is retained here instead of copying the
/// complete multi-gigabyte cache on every DFlash round.
pub(crate) struct MetalSpeculativeCheckpoint {
start_position: usize,
token_count: usize,
planes: Vec<MetalSpeculativePlaneCheckpoint>,
}
}
A NoPE plane’s speculative writes land in unused rows past len —
rewinding is restoring three integers (origin_logical,
origin_physical, len; decode.rs:419-421: “A NoPE plane grows into
unused storage, so only its logical metadata is kept”). An SWA plane at
steady state has no unused rows: the block overwrites up to token_count
live ring rows, so the checkpoint retains exactly those destinations’
physical indices and key/value bits (decode.rs:428-434) — at most 16 rows
× 1,024 B per plane, ~16 KiB per sliding layer, never a copy of the
multi-gigabyte cache. Commit discards the checkpoint; rollback restores the
rows and the metadata. The two regimes of this chapter, priced as rollback
protocols: metadata-only versus row-retention. (The commit/rollback driver
is decode.rs:1386-1496.)
23.7 Context shift: the engine has no shift, the server has a policy
What happens when the conversation outgrows 131,072 positions? Not an
engine operation. There is no shift, truncate, or evict op anywhere in
MetalKvPlane or the encode paths — the map’s audit is blunt: “there is no
engine-level ‘shift’ op; the server owns the policy” [code-map §6, per source]. The policy is two variants:
#![allow(unused)]
fn main() {
// crates/muser-server/src/state.rs:215
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ContextPolicy {
Shift,
Error,
}
}
shift is the default; error refuses the request
[docs/muser-architecture.md §Context and sessions]. The architecture doc
states what a shift preserves — and it is semantic units, not token counts:
Chat shifting preserves system content and whole newest turns/tool/image units. Raw shifting preserves the configured prefix plus the newest suffix. A request is rejected if the minimum retained unit plus output reserve cannot fit.
[docs/muser-architecture.md §Context and sessions]
The chat rule is built by splitting the message list into a system prologue and turns, where a turn begins at each user message and everything after it — assistant replies, tool calls, tool results, images — stays attached:
#![allow(unused)]
fn main() {
// crates/muser-server/src/openai.rs:5356
/// A shift may remove only complete units beginning at a user boundary.
/// Assistant calls, tool results, and image-bearing messages remain attached
/// to their turn and therefore move or disappear as one replay unit.
fn complete_chat_turns(messages: &[Message]) -> Vec<Vec<Message>> {
}
shift_chat_units (openai.rs:5374-5391) enforces the precondition (all
system messages precede the turns) and prepare_with_context_policy
(openai.rs:5292-5317) drops oldest turns in a loop until the retained
conversation fits max_context − output_reserve, rejecting when “system
content, newest complete turn, and output reserve cannot fit”
(openai.rs:5311-5315). The raw path keeps a configured prefix plus the
newest suffix (compact_raw_prompt, openai.rs:5338-5354).
So the server knows what to keep. The open question is how the retained conversation becomes a live cache again — and the cheap-looking move, editing the dropped turns out of the cache in place and sliding the survivors down, is the one that cannot work here. Hold that thought for a few paragraphs; the reason is worth earning rather than asserting.
The rebuild is a staging production with atomic publication. The
retained context is re-prefilled into the runtime’s one hidden
full-capacity session — staging, “deliberately outside slots, so it can
never admit or decode a fifth serving request” (state.rs:240-243) — and
only a successful prefill promotes it:
#![allow(unused)]
fn main() {
// crates/muser-server/src/openai.rs:1563 (abridged to the spine; DFlash
// pair-swap branch elided, see file)
if shifted {
// Do not start a potentially long staging prefill for a client that
// disconnected while waiting for its serving-slot lease.
measured_emit("", None, None)?;
let batch = prepared_prefill.materialize(runtime)?;
let mut staging = match runtime.staging.try_lock() { ... };
staging.reset();
let prepared = staging
.prefill(batch)
.map(|_| ())
.map_err(|_| accelerator_failure(runtime));
swap_staging_on_success(session, &mut staging, prepared)?;
// The old serving generation is now the hidden owner. Empty it only
// after the infallible ownership swap; no failure path can have touched
// the live session that was committed before this rebuild.
staging.reset();
}
}
swap_staging_on_success is four lines — prepared?; std::mem::swap(live, staging) (openai.rs:2790-2798) — and the pair variant swaps target and
DFlash states together (:2800-2811). Publication is a pointer swap, so it
cannot fail halfway; the comment above states the invariant. A busy staging
lock is Overloaded, a poisoned one latches the pool unhealthy and returns
Unavailable (openai.rs:1569-1576). Every shift advances a committed
context_epoch (openai.rs:1547-1552), and later continuation requests
must validate their lineage against the stored replay plan — the retained
turns must appear “as one exact ordered run under identical leading system
content” (openai.rs:5408-5413).
Now the promised reason. Why rebuild instead of truncating the live cache in place? Because of the invariant we put in view at the start of the chapter, seen now from its other side. The retained set is a prefix (system) plus a suffix (newest turns) with a hole in the middle, and closing that hole moves every token after it to a new logical position. On the 39 RoPE layers a changed position means changed key bytes (Ch 14): those rows were rotated into their old positions at store time, so sliding the survivors down would leave a cache that is structurally plausible and numerically fiction. The middle cannot simply be deleted. A fresh generation computes the retained context at its true positions instead, and the atomic swap makes the replacement all-or-nothing. The staging prefill is real work at real depth — which is precisely the cost Ch 25’s reuse ladder exists to skip when the prefix is not holed.
23.8 Tradeoffs
Explicit origins vs position % capacity. The tempting design is the one
the ancestor shipped: hold no origin state at all, index the cache by
absolute token position, and let position % capacity find the row whenever
the window has wrapped. It is one expression, there is nothing to keep in
sync, and it looks like the modulus is doing the bookkeeping for free. The
ancestor’s own extraction manifest records where it ended up — the modulus
arrived “unwired/stubbed — a named OOB hazard muser fixed from day one”
(docs/extraction-manifest.md, per Ch 15).
The lesson we took from that record is not “compute the modulus properly.”
It is that a rotation derived on demand is a rotation nobody can inspect,
hand to a snapshot, or roll back — and this chapter needed all three. So
Muser keeps the rotation as state: two origin fields make it an explicit,
checkpointable fact, restore can reproduce it (§23.4), speculative rollback
can retain it (§23.6), and the route ladder can test it (below). The measured
consequence of not having it is the ancestor’s hazard record; the measured
consequence of having it is the bitwise replay test named at
decode.rs:380.
The compact ring vs the pinned kernel’s addressing. This is the fork we keep re-walking, because it is where “store the fewest bytes” and “reproduce the comparator’s arithmetic exactly” pull in opposite directions. The route predicate for llama’s pinned SWA vec kernel accepts only rings the kernel can read safely:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/decode.rs:5646
let llama_vec_rows = (strict_attention || self.kernels.has_llama_flash_attn_vec())
&& plane.len > 0
// The pinned vec kernel rounds KV reads to a 32-row block.
// A deliberately tiny raw session can have a smaller backing
// allocation, so taking the vec path would read past it and
// poison the full distribution with NaNs.
&& plane.capacity >= 32
&& (plane.origin_physical == 0 || plane.len == plane.capacity);
// Token-major SWA cannot use llama's pad kernel (nb11 is a full
// token row, not one head). Only take that path when the window
// is a multiple of 32 so the vec kernel never pads.
let llama_swa = llama_vec_rows && plane.len.is_multiple_of(32);
}
A ring is vec-eligible while filling (origin_physical == 0) and again at
steady state (len == capacity) — every wrapped steady-state decode token
qualifies. The odd states (a restored ring sitting mid-rotation below
capacity) fall back to the split-K or ferrite interleaved kernels of
Ch 16. And when the one-row decode path
does use the llama kernel against compact ring rows, Muser stages the
ring into llama’s absolute 256-row-padded indices first — “so the pinned
vec kernel sees the same reduction lanes rather than a mathematically
equivalent compact permutation” (metal/encode/attn.rs:140-144): the
staging copy buys the comparator’s exact reduction order. Exactness beats
the compact layout, again at a counted cost (§23.3).
Server-owned shift vs engine-owned eviction. Pushing the policy up means the engine’s cache code has exactly one writer discipline (append continuity) and the semantic decisions — what a turn is, what the system prologue means, how much output reserve to protect — live where the request model lives. The price is that a shift is a full re-prefill of the retained context through staging rather than a surgical cache edit; the mitigation is not cleverer eviction but the reuse ladder of Ch 25, which makes the un-holed case free. No retained measurement isolates the staging re-prefill’s wall cost from the request it serves [unverified] — it is bounded above by the cold prefill numbers of Ch 25 at the retained depth.
Where the gap lives. Two rows of the +196 accounting live in this
chapter’s machinery: the 39 SWA wrapped-ring staging groups (§23.3) and the
52 KV-publication splits (Ch 15 §15.9). Both
are classed “session/publication structure — Keep” by the gap note itself
[docs/decode-dispatch-gap-20260815.md]; this chapter is the concrete
machinery those labels were pasted onto.
23.9 What comes next
The ring and the growing plane are now complete stories: reserved fail-closed, staged across wraps, snapshotted to logical order, restored at the exact rotation, rolled back by metadata or by retained rows, and rebuilt wholesale by a server policy that swaps generations atomically. But everything so far keeps the cache inside one process on one machine. The interchange snapshot of §23.4 already hinted at the next move — a fail-closed, shape-checked, layout-stable byte contract that something outside the engine can hold. That something is kvpack: a vendored, provenance-pinned format that turns a prefill into a durable, portable, authenticated asset. The format is Ch 24.
References
crates/muser-engine/src/decode.rs:117-122—CacheDiscontinuity.crates/muser-engine/src/decode.rs:263-314—append/append_batch(append_batchquoted; §23.2’s walk).crates/muser-engine/src/decode.rs:208-226, 373-434, 1386-1496— the speculative checkpoint: metadata rewind vs retained rows.crates/muser-engine/src/decode.rs:322-349— the snapshot walk (quoted);:351-417detached_fromand the rotation-preserving install;:376-383the order-sensitivity comment and the named replay test.crates/muser-engine/src/decode.rs:4290-4352— the wrapped SWA staging route and itsappend_batchcommit;:4356the attend-before-overwrite comment.crates/muser-engine/src/decode.rs:5643-5657— the route predicates that test ring state (quoted).crates/muser-engine/src/metal/encode/attn.rs:103-138—encode_stage_swa_prefill_f16(signature quoted);:140-144the llama-padded-index staging and its exactness rationale.crates/muser-engine/src/shaders/muse_reference.metal:1224-1228, 1240— the store/scatter indices and the staging kernel.crates/muser-engine/src/cache.rs:13-17, 39-47, 62-118, 205-222— the interchange: encodings, the SWA-tail/NoPE-full cut contract, the fail-closed shape gate,write_f16_tile’s head-major scatter.crates/muser-engine/src/prefill.rs:15-17— placement-never-from-position.crates/muser-server/src/state.rs:215-219—ContextPolicy(quoted);:240-243the out-of-pool staging generation.crates/muser-server/src/openai.rs:5241-5317—prepare_with_context_policy(the turn-dropping loop);:5338-5354compact_raw_prompt;:5356-5391complete_chat_turns/shift_chat_units;:5408-5413lineage validation;:1547-1552the context epoch;:1563-1637the staging rebuild (spine quoted);:2790-2811the swap helpers.crates/muser-kvpack/src/session.rs:164-166—ProductionF16Required(CPU/Metal snapshot mutual uninstallability, with[docs/kvpack-merge-handoff §4]).[docs/muser-architecture.md]— §Context and sessions (shift semantics quoted), §Slots and scheduling (staging is not a fifth slot).[docs/decode-dispatch-gap-20260815.md]— the 39 staging groups and 52 publication splits rows.[docs/extraction-manifest.md]— the ancestor’s ring-modulus hazard and Muser’s fix (via Ch 15).- Ch 15 — planes, layouts, store kernels,
single-token
append(this chapter’s ancestors). - Ch 16 — the read-side route ladder.
- Ch 24, Ch 25 — the portable format and the reuse ladder that staging re-prefill motivates.
[ferrite-book Ch 14]— the ancestor’s paged cache, kept as contrast (Ch 22 §22.8).
Chapter 24 — kvpack: the format
status: polished · path: Muse Glimmer, pinned Muser tree
Prerequisites: Ch 23 (the interchange snapshot and its fail-closed shape gate), Ch 22 (what a cache weighs), Ch 14 (why NoPE rows are relocatable bytes). Some exposure to [HMAC]/[SHA-256] as “keyed and unkeyed digests” is assumed; both get their one-sentence definitions at the top of Ch 30 §30.1.
24.1 What kvpack is
Ch 23 ended inside one process:
the interchange snapshot was a byte contract, but nothing outside the engine
held it. kvpack is the thing that holds it. It is the format and protocol
family that moves a prefilled KV cache between producer and consumer —
between processes, between disks, and in Part VI
between a CUDA producer on a GX10 and the Metal decoder on a Mac. The
engineering stance is stated in one line of its overview doc: “exactness
is the product; speed is the consequence” [docs/kvpack.md]. Restored
state is proven byte-identical to what was saved and proven to carry the
exact computation identity it was produced under; anything short of both
proofs is a loud refusal, never a best-effort restore.
Three questions define the format’s job, and kvpack answers each
mechanically rather than by convention [docs/kvpack.md §Why it exists]:
- Is this cache the cache I asked for? — keyed, fail-closed identity.
- Have I seen this request before? — replay protection.
- Did anyone touch it in flight? — layered integrity and authenticated sealing.
The scale motivating all three is already derived: a 65k prompt’s cache is
hundreds of megabytes; a 131k prompt’s is nearly a gigabyte
(Ch 22, Table 22.1); the measured deep wire
payload is 1,823,184,896 B [receipt phase4-disagg-20260820/130815-g900091/].
State that size cannot be re-checked by eyeball; it needs proof objects.
One definition before the bytes. Replay, in kvpack’s vocabulary, means restoring inference state — not replaying a request log, not semantic caching:
Here, replay means restoring inference state. kvpack is not an inference engine, a semantic prompt cache, or a request/response log. It stores and verifies the exact bytes an engine adapter gives it; the engine remains responsible for what those bytes mean.
[third_party/kvpack/README.md]
That sentence is the format’s integration boundary, and it is worth
pausing on, because every refusal in the rest of this chapter follows from
it. The line the README draws is a line between provable claims. kvpack
owns everything it can prove from bytes alone: the pack format, the durable
write protocol, validation, indexing, restore I/O, exact-token and
compatibility-identity matching, failure ordering, byte-for-byte
preservation. The engine adapter owns everything only a running engine can
prove — synchronizing device work before export, serializing its state
objects, installing and committing restored state, deciding where prefix
checkpoints are valid, bumping engine_abi when semantics change, and
“proving that restore produces the same model behavior as uninterrupted
execution” [third_party/kvpack/README.md §The integration boundary]. Said
the other way round: kvpack can prove the bytes came back unchanged and
were selected under the expected identity; only the engine can prove those
bytes are correct KV for its runtime.
24.2 The vendored tree and its provenance discipline
A format is only as trustworthy as the copy of it you are actually running. So before any bytes: where does kvpack live in Muser’s tree, who is allowed to change it, and how would we find out if someone had?
In Muser’s workspace, kvpack is not a crates.io dependency. It is vendored
— a hash-pinned snapshot living at third_party/kvpack, excluded from the
workspace’s own crates, with every file’s SHA-256 recorded. The adapter
crate’s module doc explains why this one dependency gets special treatment:
#![allow(unused)]
fn main() {
// crates/muser-kvpack/src/lib.rs:3
//! kvpack is the **one** shared external dependency in `muser`
//! (docs/muser-architecture.md §1), pinned to a path-pinned release source —
//! the `release/muser-alpha2` branch's `kvpack-core`/`kvpack`/`kvpack-handoff`
//! crates at `0.1.0-alpha.2` (`docs/release-provenance.md`), not a live git
//! dependency. It defines the sealed V2 wire format (HMAC over a canonical
//! manifest) that the CUDA producer on GX10 and the Metal consumer on Mac
//! must agree on: the producer reimplements that format itself
//! (`scripts/gx10/llamacpp/spark_kv_export.cpp` + `muser_v2_send.py`) rather
//! than linking this crate, so agreement isn't "one format authority linked
//! on both sides" but cross-verification — the authenticated Mac-side
//! receiver rejects anything the producer's reimplementation gets wrong,
//! which is why HMAC verification on restore is load-bearing, not a
//! formality.
}
Read that twice, because it is unusual: the producer does not link the
crate. The CUDA side reimplements the format in C++/Python, and the two
implementations agree because the receiver verifies an HMAC over
canonical bytes and rejects anything that diverges — agreement by
cross-verification, not by shared code [docs/kvpack-merge-handoff §6].
The seal is the format authority.
The vendored tree is three crates (provenance.json workspace_members):
- kvpack-core — the pure in-memory pack codec, no file I/O:
canonical.rs(canonical JSON),chunk.rs(content-addressed chunks),manifest.rs,pack.rs,identity.rs,keys.rs(longest-committed-prefix lookup),quant.rs,rotation.rs,validator.rs[code-map §8.2]. - kvpack — the engine-facing store:
store/,restore/,writer/,export/,gguf_layout/, the adapter contract, the pack bridge. - kvpack-handoff — the sealed V2 wire format this chapter quotes
(
handoff_v2.rs,manifest.rs,receiver/,mac.rs).
provenance.json pins the snapshot: schema muser.vendored-source.v1,
upstream https://github.com/High-Performance-AI-Lab/kvpack at commit
70c34c7d790dbfc9c1271727dd34ea0e863404d2, tag
kvpack-v0.1.0-alpha.2-rc1, upstream tree 7d56417c…, and a per-file
SHA-256 map covering every source file in the tree — from
crates/kvpack-core/src/canonical.rs to the conformance tests
(third_party/kvpack/provenance.json). python3 scripts/audit_vendored_kvpack.py re-verifies the hashes against the tree,
so a silent local edit to the vendored format is a detectable event, not a
drift.
Two carried patches are recorded in the same file, each with its reason. The first is the whole argument of this chapter compressed into a bug we did not see coming, so it is worth walking through the way we met it.
Here is the fork. Both ends of a transfer serialize a descriptor to JSON
and hash the result; both were written against the same canonical-encoding
rule; so we expected the two digests to agree, and for a long while they
did. Then the workspace pinned serde_json’s preserve_order feature — a
change made for unrelated reasons, by someone thinking about map iteration,
not about wire formats. What we expected from that change was nothing at
all. What we got was live transfers dying:
// third_party/kvpack/provenance.json (patches[0], abridged)
"id": "canonical-json-sorted-keys-feature-independent",
"reason": "canonical_json passes through serde_json::Value, whose map
order follows the preserve_order Cargo feature; with preserve_order
pinned workspace-wide the receiver emitted insertion-ordered descriptor
bytes while the Spark producer emits sorted-key bytes, so Handoff V2
terminal seals failed (descriptor_sha256 mismatch) and live transfers
were dropped."
Read that reason field as a chain, because every link of it is ordinary.
canonical_json handed its map through serde_json::Value; with
preserve_order on, a Value remembers insertion order instead of sorting
its keys; the receiver therefore emitted descriptor bytes in one order
while the Spark producer emitted them sorted; the two descriptor_sha256
values diverged; and the terminal seal refused to match. Nothing was
corrupted. Nothing was silently accepted. The transfer simply died at the
seal, loudly, on a live link.
That is the lesson, and it cuts both ways. Two “identical” implementations disagreed over something no code review would have flagged, because the disagreement did not live in either implementation — it lived in a feature flag. And the seal caught it anyway, because a seal does not care what either side meant to encode. This is exactly the failure the cross-verification design exists to catch, caught in the wild; the decision it produced was to make the canonical encoding (recursive sorted keys) feature-independent, so that no dependency flag can quietly redefine what “canonical” means.
The second patch needs no story: it adds a domain-separated protocol
HMAC on the MAC key (mac.rs, domain tag b"kvpack-domain-mac-v1\0"),
consumed by muser-cluster’s verifier lane [third_party/kvpack/provenance.json patches[1]]. Both patches live as recorded, receipted facts rather than
local edits — and the merge ruling
of 2026-08-20 makes muser’s vendored copy the canonical kvpack, with merge
direction vendor → upstream [docs/kvpack-merge-handoff §1].
24.3 The durable container: pack v1
Start with the easy case — producer and consumer on the same machine, talking through a file — and ask what has to be true of that file before a restore from it can be called proven. Two things, and they are the two things filesystems are worst at. It must be impossible to read a half-written file as if it were whole. And it must be impossible to change a written file without the change announcing itself.
The on-disk unit is a pack: an append-only immutable file whose shape is header ‖ body ‖ footer with fixed-size bookends:
┌──────────────┬─────────────────────────────┬──────────────┐
│ 4 KiB header │ canonical manifest │ 4 KiB footer │
│ │ (optional ChaCha20Poly1305 │ │
│ │ envelope, AAD = header) │ │
└──────────────┴─────────────────────────────┴──────────────┘
Figure 24.1: pack v1 layout, per the architecture map — “4 KiB header ‖
canonical manifest (optional ChaCha20Poly1305, AAD = full header) ‖ 4 KiB
footer; footer HMAC binds body length and file size (truncation fails
closed)” [docs/kvpack-merge-handoff §5].
The integrity model is layered, and the doc lists the layers in a single
breath: “record headers and payloads are hashed, object IDs are
content-derived, the terminal commit carries an ordered inventory, a
canonical Merkle root binds it, and the footer seals the header digest plus
every byte of the file” so that “truncation, single-bit flips, reordering,
substitution, and length games are rejected” [docs/kvpack.md].
That is dense, so unpack it as a ladder in which each rung catches a
different lie. Hashing records catches a flipped bit. Deriving object IDs
from content catches an object substituted under an honest-looking name.
The ordered inventory catches a reordering. The Merkle root over that
inventory catches an addition or a deletion. And the footer, by binding
body length and file size, catches the truncation that would otherwise look
like a perfectly valid shorter file. No rung on that ladder is aspirational;
every one of them is verified by exhaustive truncation and bit-flip
conformance corpora across Rust, Python, and C99 reference implementations
that produce byte-identical packs [third_party/kvpack/README.md §Conformance].
Three properties matter for everything downstream in this Part:
- Crash-safe publication. Packs are append-only; the commit is written
last; pack sets publish through an exclusive atomic rename. “A torn
write or SIGKILL mid-write (injected in tests) can never replace the
last known-good generation”
[docs/kvpack.md]. - Content-addressed chunks. Payloads split into chunks of at most 4 MiB
plaintext on token-aligned boundaries, each chunk addressed by its
content
[docs/kvpack-merge-handoff §5]. Chunk IDs bind token offset — no cross-position dedup — which is what makes prefix lookup exact rather than fuzzy. - Keyed prefix identity. The cache key is a keyed HMAC prefix chain,
one node per 256-token block, “context-bound to tenant ‖ semantic-model
‖ family ‖ aux root; no token witness retained; trailing partial block
reusable: false”[docs/kvpack-merge-handoff §5]. Raw prompt text never appears in paths or telemetry — privacy by construction, since the restorer must prove the prefix bytes to even look them up[docs/kvpack.md §Privacy by construction].
An honest threat-model line completes the picture: “pack hashes prove
integrity, not authenticity — a whole-file rewrite attacker needs the
kvenc envelope or the transport-layer authentication above”
[docs/kvpack.md]. Unencrypted packs detect accidents; the ChaCha20-Poly1305
envelope (kvenc) or the mTLS channel of §24.6 handles adversaries.
24.4 The Handoff V2 wire objects
The durable pack moves a cache between processes on trustable storage.
The disaggregated lane needs something harder: a wire protocol for state
that crosses a network between different engines. The difference is not
cosmetic. A file can be re-read from the top as many times as you like, so
a pack can afford to put its proof in a footer; a stream arrives once, in
order, and whatever the receiver intends to check it must be able to check
as the bytes go past — and it must be able to refuse before it has
installed anything into a live engine. That protocol is Handoff V2, defined
in kvpack-handoff/src/handoff_v2.rs.
Its model is components × segments: a transfer declares components (target KV required, DFlash context optional, vision refused at Muser admission), and each component arrives as an ordered stream of segments — one segment per plane tile, each with its own descriptor and payload digest. The descriptor is the atom of the format:
#![allow(unused)]
fn main() {
// third_party/kvpack/crates/kvpack-handoff/src/handoff_v2.rs:59
pub struct SegmentDescriptorV2 {
pub sequence: u32,
pub component_id: String,
pub role: SegmentRoleV2,
pub layer: Option<u32>,
pub logical_start: u64,
pub logical_count: u64,
pub element_type: String,
pub elements_per_token: u32,
pub byte_len: u64,
pub sha256: String,
}
}
SegmentRoleV2 is a closed nine-variant set — NopeKey/NopeValue,
SwaKey/SwaValue, NopeTile/SwaTile (packed multi-plane tiles),
DflashKey/DflashValue, Auxiliary (handoff_v2.rs:41-53) — Ch 22’s
two-regime byte economics, made wire vocabulary: the roles exist because
NoPE and SWA bytes travel differently (tiles stream during prefill; window
groups ride the schedule [docs/kvpack-merge-handoff §6]).
A transfer opens with a begin manifest that binds everything the receiver must agree to before any payload byte arrives:
#![allow(unused)]
fn main() {
// third_party/kvpack/crates/kvpack-handoff/src/handoff_v2.rs:73
pub struct BeginManifestV2 {
pub protocol: String,
pub transfer_id: String,
pub generation: u64,
pub created_unix_ms: u64,
pub expires_unix_ms: u64,
pub identity: ExactIdentityV1,
pub prompt_token_ids: Vec<u32>,
pub multimodal: Option<MultimodalIdentityV2>,
pub hmac: HmacIdentityV2,
pub components: Vec<ComponentV2>,
/// Streaming producers cannot know hashes for future KV tiles at begin
/// time. In deferred mode each ordered segment frame carries its complete
/// descriptor; the terminal seal still binds the canonical descriptor
/// stream and all payload bytes before commit.
#[serde(default, skip_serializing_if = "is_false")]
pub deferred_segments: bool,
pub segments: Vec<SegmentDescriptorV2>,
}
}
Validation of the begin (ValidatedBeginV2::validate,
handoff_v2.rs:100-160) is fail-closed on each field, and the list is best
read as the questions the receiver settles before it has spent a byte of
memory on this transfer. Is this conversation the one we agreed to have —
exact protocol string, bounded transfer id, non-expired lifetime? Is the
sender who it claims to be, and not an echo — hmac.key_id equal to
the enrolled key, hmac.epoch at or above the minimum (the replay
floor), hex-shaped identity digests? And is the payload shaped like
something installable — exactly one of declared/deferred segment mode,
nonempty prompt tokens, unique component ids with a required TargetKv?
Any one of them failing ends the transfer there, at the cheapest
possible moment. A transfer closes with the seal:
#![allow(unused)]
fn main() {
// third_party/kvpack/crates/kvpack-handoff/src/handoff_v2.rs:233
pub struct SealCoreV2 {
pub transfer_id: String,
pub generation: u64,
pub begin_sha256: String,
pub descriptor_sha256: String,
pub payload_sha256: String,
pub segment_count: u32,
pub total_bytes: u64,
}
// … :242
pub struct SealManifestV2 {
pub core: SealCoreV2,
pub hmac_sha256: String,
}
}
SealManifestV2::sign hashes the canonical JSON of every descriptor in
sequence order and every payload byte, refuses to sign any descriptor whose
byte_len or sha256 disagrees with its payload (“cannot sign mismatched
segment material”, :265), and then tags the whole core with the shared
key: hmac_sha256 = key.tag_hex(&canonical_json(&core)?) (:285). That
tag is the terminal seal — no unkeyed mode exists — and the receiver
re-verifies it before anything installs (verify at :447-449 per the
merge map). This is the exact digest that the canonical-JSON patch of
§24.2 made feature-independent; a single reordered key anywhere in the
descriptor stream would have broken it.
Two supporting objects from the v1/v2 manifest complete the identity and
layout story. ExactIdentityV1 is the compatibility namespace —
#![allow(unused)]
fn main() {
// third_party/kvpack/crates/kvpack-handoff/src/manifest.rs:58
pub struct ExactIdentityV1 {
pub adapter_sha256: String,
pub chat_template_sha256: String,
pub context_policy_sha256: String,
pub model_revision: String,
pub model_sha256: String,
pub tokenizer_revision: String,
pub tokenizer_sha256: String,
}
}
— model, tokenizer, chat template, context policy, adapter, each pinned
either by content digest or by revision string. The doc counts eight
runtime inputs in all, once quantization and engine ABI join the set at the
descriptor level [docs/kvpack.md]. The point of enumerating them is that
a cache is only meaningful relative to the machine that produced it: change
any one of the eight and the stored bytes stop being an answer to the same
question.
LayoutClassV2 describes
a layer class compactly — from..until stepped by step, minus except,
with kv_heads, head_dim, dtype, roles, and window_tokens:
#![allow(unused)]
fn main() {
// third_party/kvpack/crates/kvpack-handoff/src/manifest.rs:92
pub struct LayoutClassV2 {
pub class: String,
pub dtype: String,
pub except: Vec<u32>,
pub from: u32,
pub head_dim: u32,
pub kv_heads: u32,
pub roles: Vec<TensorRoleV1>,
pub step: u32,
pub until: u32,
pub window_tokens: u32,
}
}
“A class with window_tokens > 0 ships only the trailing in-window tokens
of each plane” (manifest.rs:88-89) — the SWA declaration, in format form.
Muse needs exactly two classes: the 39 SWA layers are every layer with
layer % 4 != 3, and NoPE is expressed the other way — from 3 until 52 step 4 — with the rest SWA (the partition rule of
Ch 15 §15.1);
§24.5 shows the adapter deriving and cross-checking that table.
24.5 The Muse adapter: muser-kvpack
Everything so far is model-agnostic: kvpack does not know what Muse is, and that is on purpose. Somebody has to tell it — how many layers there are, which of them are windowed, what scalar constants the math ran under — and that somebody is the adapter. So the questions for this section are where Muse’s shape enters the format, and what happens when the shape the adapter believes disagrees with the model the engine actually loaded.
The Muse-specific half lives in
crates/muser-kvpack, which “re-exports the pinned-release API and adds
three things that are muser’s own product surface” — the Muse K1/K3 layout
glue (layout), session save/restore plus relocation-as-memcpy
(session), and the dashboard’s cache-economics accounting (economics)
(crates/muser-kvpack/src/lib.rs:28-32). The keys landed upstream are
recorded in the same doc, and each one guards a different way a cache
could lie. Muse layout table K1 (NoPE theta = 0, fail-closed) pins the
fact that the NoPE layers never rotate — the memcpy free lunch the later
chapters lean on exists because K1 refuses any layout that would touch
those bytes. K3 (two-class 39-SWA/13-NoPE) makes the split between the
two cache layouts a checked identity instead of a convention. Scalar-math
identity K4 (qk_scale, output_mult, softcap, eps as f64 bits — “caught
2 real GGUF-vs-config regressions”) binds the arithmetic itself. And
session artifact K5 (13 NoPE planes as-is + 39 SWA windowed planes,
fail-closed resume) is the shape a restorable session must present — or
the resume is a miss (lib.rs:19-26).
The parenthetical inside K4 is the one not to skim past. Binding the scalar constants of the math — the attention scale, the output multiplier, the softcap, the norm epsilon — as raw f64 bit patterns reads as paranoia, since surely a model file and its config agree about its own constants. They did not, twice, and the identity caught both before anyone would have noticed by staring at output quality.
The identity object scopes everything the resident tier serves:
#![allow(unused)]
fn main() {
// crates/muser-kvpack/src/layout.rs:21
pub struct MuseIdentity {
pub model_sha256: [u8; 32],
pub adapter_sha256: [u8; 32],
pub tokenizer_sha256: [u8; 32],
pub chat_template_sha256: [u8; 32],
pub context_policy_sha256: [u8; 32],
pub model_revision: String,
pub tokenizer_revision: String,
pub weight_precision: String,
}
}
Its digest() hashes a domain tag, the five 32-byte fields
length-prefixed, and the three strings — “One digest covering every
identity field. The resident radix scopes its keys by this digest, so an
entry written under another identity — or under none — is structurally
unreachable from a lookup, never a best-effort hit” (layout.rs:32-38;
the test at :182-206 shows a one-bit model change, a precision swap, and
even a field-boundary shift each producing a different digest). The
descriptor builder then derives the qualified layout and validates the
geometry against the live config before any state is described:
#![allow(unused)]
fn main() {
// crates/muser-kvpack/src/layout.rs:138
fn validate_geometry(cfg: &MuseConfig, cached: u32) -> Result<(), LayoutError> {
if cfg.n_layers != MUSE_LAYER_COUNT {
return Err(LayoutError::Geometry("layer count"));
}
if cfg.n_kv_heads != MUSE_KV_HEAD_COUNT || cfg.head_dim != MUSE_HEAD_DIM {
return Err(LayoutError::Geometry("KV head geometry"));
}
if cfg.sliding_window != MUSE_SWA_WINDOW {
return Err(LayoutError::Geometry("SWA window"));
}
if cfg.context_length != MUSE_MAX_CONTEXT {
return Err(LayoutError::Geometry("maximum context"));
}
// … (cached-token bound and the 39-SWA/13-NoPE partition check,
// :151-161 — any layer violating `is_swa() == (layer % 4 != 3)`
// refuses with "39-SWA/13-NoPE partition") …
}
}
Duplication is normally a smell, so it is worth saying why this one is
deliberate. The NoPE layer table exists in three places — engine
config, the Mac sink, the transfer schedule — because those three live in
different processes, in different languages, on different machines, and no
single one of them can be made authoritative without a network round trip
in the middle of a hot path. The reason it matters for the handoff is that
a silent disagreement between the copies would not look like an error: it
would produce a structurally perfect transfer of the wrong planes. So the
copies are checked against each other instead of trusted. This cross-check
(plus a fail-closed GGUF cross-check, layout.rs:154-161 per the merge
map) is what keeps them honest [docs/kvpack-merge-handoff §4].
descriptor also binds the K4 scalar math (qk_scale_factor_bits,
output_multiplier_bits, final_logit_softcapping_bits,
post_norm_eps_bits as f64 bit patterns, layout.rs:96-104) and appends
the exact-logits state — a full vocab-width f32 plane at the synthetic
layer 52 (MUSE_EXACT_LOGITS_LAYER, layout.rs:107-125) — which is what
lets Ch 25’s warm hit resume generation, not just
attention.
Durable sessions go through session.rs: save exports the interchange
snapshot (refusing any cut without final logits, session.rs:150-153),
save_snapshot writes every plane under its descriptor key and the
logits plane last, and lookup is deepest-prefix by construction —
#![allow(unused)]
fn main() {
// crates/muser-kvpack/src/session.rs:222
pub fn find_deepest(&self, tokens: &[u32]) -> Result<Option<DurableHit>, SessionCacheError> {
// … (derive the requested descriptor; resolve the 256-token-block
// prefix chain; `resolve_prefix` returns the deepest committed
// cut, :228-243) …
}
}
— and the whole ladder is ordered by one module line: “Ordered exact-prefix
reuse: current session, resident, durable, then remote”
(crates/muser-kvpack/src/reuse.rs:1). The resident tier is a
content-interned, identity-scoped token radix (resident.rs); the durable
tier caps it — “the durable tier is the sole authentication authority …
an unauthenticated resident entry can never serve deeper than the
authenticated chain” (reuse.rs:322-328). Ch 25 walks
the ladder end to end.
24.6 The sealed manifest in the cluster: identities bound at enrollment
Who does the receiver think it is talking to, and when was that decided? The answer is the organizing idea of this section: it was decided at enrollment, before any transfer existed, and it is recorded on disk rather than negotiated on the wire. A protocol that negotiates identity can be talked out of it; a protocol that reads identity from a file it was configured with cannot.
On the disaggregated lane, the same sealed-manifest discipline is wired
into muser-cluster’s receiver configuration, and the architecture doc
states the security model in one paragraph: “GX10 Handoff V2 uses mutually
authenticated TLS plus an HMAC-sealed manifest. Enrollment generates each
TLS private key on the machine where it remains; the HMAC is a shared
secret transferred over known-host-verified SSH. Replay admission durably
reserves the generation with file and directory fsync before
target+DFlash publication and ACK. Any durability failure degrades the
route until repair and restart” [docs/muser-architecture.md §Durable and remote KV].
The receiver’s config is the enrollment artifact made concrete — every identity the seal will be checked against, held by path, never inlined:
#![allow(unused)]
fn main() {
// crates/muser-cluster/src/config.rs:20
pub struct ReceiverConfigV2 {
pub listen: SocketAddr,
pub certificate_chain: PathBuf, // mTLS: this node's chain
pub private_key: PathBuf,
pub peer_ca: PathBuf,
pub peer_leaf_sha256: BTreeSet<String>, // leaf-pin set for the producer
pub hmac_key_file: PathBuf, // the shared seal key, by path
pub hmac_key_id: String,
pub minimum_hmac_epoch: u64, // replay floor
pub replay_ledger: PathBuf,
// … (timeouts, producer control, producer mode) …
pub identity: ExactIdentityV1,
pub target_cache_identity_sha256: String,
#[serde(default)]
pub dflash_identity_sha256: Option<String>,
/// Context shape stamped during enrollment from the digest-verified
/// DFlash sidecar. It is paired with the component digest so a receiver
/// can never silently substitute its own window.
#[serde(default)]
pub dflash_context_geometry: Option<DFlashContextGeometry>,
}
}
Note what is not here: no secret material inline — keys are paths
(secrets hygiene is repo law, AGENTS.md). The struct is a list of things
the receiver refuses to be talked into: which producer leaf it will accept,
which seal key, which floor for the epoch, which model identity, which
context window. Each field is a door that enrollment nailed shut.
The wire framing itself is
Muser’s reimplementation beside the vendored crate — frames
Begin/Segment/Seal/Ack/Abort over the TLS stream with magic
KVPKV2\0\0 and a 20-byte preamble (crates/muser-cluster/src/transport.rs:15-16),
plus one deliberate extension: the delta prefix_cut travels beside the
typed manifest, lifted from raw JSON at the frame boundary, because the
typed BeginManifestV2 drops unknown keys (transport.rs:35-46) — a
live illustration of §24.2’s “two implementations, one seal” reality, and
the hook Ch 26 pulls on.
None of that is worth much until somebody has watched it refuse. So we made it refuse, deliberately, in both directions. We replayed a request one generation below the watermark, expecting the ledger to notice, and got the explicit stale/replayed refusal rather than a quiet re-serve of stale state. Then we handed the receiver a config that was well-formed in every respect except a flipped adapter digest — the case that is dangerous precisely because everything else about it looks right — and got an identity-mismatch error rather than a restore that would have seemed fine until the output drifted.
Both runs are retained, and the receipts discipline around them is stricter
than it first sounds: “receipts bind every attempt — command, exit status,
retained log — including the
refused ones. A producer timeout is recorded as invalid evidence, never
counted as a refusal” [docs/kvpack.md §Proven live — refusal receipts, not claims]. That last clause is the one to hold on to. A refusal only
counts as evidence when the thing that refused was healthy enough to have
said yes.
24.7 The receiver refuses slow volumes before any transfer
Before a receiver even binds its listener, it probes the volume its replay ledger will live on — and refuses to serve from a slow one. That sentence contains two terms this book has not yet defined, and the refusal only makes sense once both are in hand.
The replay ledger is the receiver’s durable record of the highest generation ever committed per HMAC key: the watermark that makes an old, validly-signed handoff refuse as a replay; Ch 30 §30.6 tells its full story. fsync is the operating-system call that forces buffered writes out of volatile cache onto the storage device itself — it is what turns “we wrote the watermark” into “the watermark survives a power cut”, and Ch 30 §30.6 walks why the directory variant is the load-bearing one here.
Put those together and the constraint appears: the watermark must be durable before the ACK goes out, so the speed of a disk sits directly in the latency path of every transfer. The code carries its own incident report about what that cost us:
#![allow(unused)]
fn main() {
// crates/muser-cluster/src/receiver.rs:108
/// The commit path durably reserves every generation with
/// write+fsync+rename+directory-fsync before the ACK leaves. On a volume with
/// a slow directory-fsync tail this stalls the ACK and first decode by
/// hundreds of milliseconds at random (the 2026-08-18 p4 seal stall), so a
/// receiver whose replay ledger sits on such a volume is refused at bind
/// time. `scripts/gx10/durable_fsync_probe.py` is the standalone operator
/// check for the same pattern.
const LEDGER_RESERVE_PROBE_ITERATIONS: usize = 20;
const LEDGER_RESERVE_PROBE_MAX_TAIL: Duration = Duration::from_millis(100);
fn check_ledger_volume(ledger: &Path) -> Result<(), String> {
}
probe_ledger_reserve (receiver.rs:150-178) runs the exact reserve
pattern twenty times — create temp file, write 4 KiB, sync_all, rename,
directory sync_all — and check_ledger_volume refuses the bind if the
worst sample exceeds 100 ms, with an error that tells the operator what to
do: “point replay_ledger at the internal disk (see
scripts/gx10/durable_fsync_probe.py)” (receiver.rs:139-146).
The backstory is worth telling in the order we lived it, because we spent a while chasing the wrong suspect. What showed up on 2026-08-18 was a bimodal stall in the commit paths: most transfers were fine, and then one would sit for ~1 s for no reason visible anywhere in the transfer itself. A random stall on a network path looks like a network problem, and that is where we looked first. It was not the network. Root cause was the directory-fsync tail of the evidence volume — the volume we had, quite reasonably, pointed the replay ledger at, because that is where receipts go and the ledger felt like a receipt.
The lesson went straight into the repo’s working agreements, and it is a
placement rule rather than a tuning knob: operational state (replay
ledger, sockets, locks) belongs on the internal disk, evidence on the
append-only volume [AGENTS.md; ledger Arc 2]. The bind-time gate is that
lesson turned into a fail-closed preflight — the receiver refuses before
any transfer rather than stalling during one, which is the difference
between an operator reading an error message that names the fix and an
operator staring at an intermittent mystery.
One separation is worth making explicit, because the two failures are easy to blur. This fsync tail is not the other deep-payload stall of the same campaign (EEE link-idle retransmission blackouts, Ch 31); both punished the deep burst schedule, by different mechanisms, and both got operationalized — one as this bind-time probe, one as the EEE-off link invariant.
24.8 Tradeoffs
Each of the decisions above had a cheaper alternative, and in most cases the cheaper alternative is the one you would reach for first. This section is the accounting: what we gave up, and what the measurements say we bought with it.
One format authority, twice implemented, verified by a seal. The
obvious architecture is a shared crate on both ends; Muser deliberately
does not have one (the producer reimplements the format in C++/Python,
lib.rs:8-14). The measured consequence of divergence is not corruption
but refusal: the sorted-keys incident of §24.2 dropped live transfers on
descriptor_sha256 mismatch — the seal doing its job — and produced a
patch with a regression test. A shared crate would have made both ends
silently consistent and silently coupled to one implementation’s bugs;
cross-verification makes disagreement loud. The cost is real and known:
format fixes must land twice (F1 in the merge audit tracks that prefix_cut
still has no home in the typed protocol, so “delta handoff … has a
Python-only producer” [docs/kvpack-merge-handoff §3 F1]).
Fail-closed identity vs best-effort reuse. Every identity dimension of
§24.4–§24.5 could, in principle, be advisory — restore anyway, let quality
absorb it. The format refuses, and the campaign’s receipts show what the
refusal buys: bit-identical warm hits at depth
(Ch 25) are only claimable because a wrong
tokenizer, template, layout, or scalar-math constant is a miss, not a
restore. The cost is the opposite of convenience — “a different model,
tokenizer, template, quantization, layout, or engine ABI is a miss, not a
best-effort restore” [third_party/kvpack/README.md §What kvpack provides]
means upgrading any of them orphans every pack, by design.
Integrity hashes vs authenticated sealing. Unencrypted pack hashes
detect accidents cheaply and nothing more; the honest line is written down
(“pack hashes prove integrity, not authenticity” [docs/kvpack.md]). The
defense-in-depth ordering — kvenc envelope at rest, mTLS + HMAC seal in
transit — lets each layer carry only the threat it can actually prove.
Skipping the distinction would be the security version of the “~7 GB”
payload mistake: reading an allocation as if it were traffic.
Vendoring vs a live dependency. The vendored, per-file-hashed snapshot
trades upstream motion for reviewability: every byte the engine relies on
is in-tree, auditable by one script, and — the merge ruling’s direction —
canonical for upstream [docs/kvpack-merge-handoff §1]. The alternative, a
git dependency, would re-introduce exactly the silent-drift class the
canonical-JSON patch closed.
Where the gap lives. None of this is on the Metal decode graph; kvpack
is not the dispatch gap. Its costs live elsewhere and are measured
elsewhere: receiver-side verify/install/seal/commit phases (~0.2 s constant
in the N2 diagnostic [ledger N2]), and the wire amortization schedule of
Ch 30.
24.9 What comes next
The format is now fully assembled: a provenance-pinned vendored tree, a crash-safe layered-integrity container, a component × segment wire protocol with a mandatory keyed seal, a Muse adapter that scopes every entry by a digest over eight identity dimensions, and a receiver that refuses slow volumes before accepting a single byte. But a format is only as valuable as the hits it serves. The whole apparatus — identities, seals, ledgers — exists so that one question can be answered fast and provably: “have I already computed this prefix?” What a hit is actually worth, at two very different depths, with controls that prove it is reuse and not cache-forever, is Ch 25.
References
[docs/kvpack.md]— the stance (“exactness is the product”), the three questions, the security model in full, refusal receipts, the honest threat-model line.third_party/kvpack/README.md— replay semantics (quoted), the integration boundary, layered integrity, concurrency model, conformance.third_party/kvpack/provenance.json— schema, upstream commit/tag/tree, per-file SHA-256 map, the two recorded patches (canonical-JSON sorted-keys quoted abridged).third_party/kvpack/crates/kvpack-handoff/src/handoff_v2.rs:28-91—HmacIdentityV2,SegmentRoleV2,SegmentDescriptorV2,BeginManifestV2(quoted);:100-160begin validation;:233-246SealCoreV2/SealManifestV2(quoted);:248-287signing;:307-314theHandoffSinkV2shadow contract.third_party/kvpack/crates/kvpack-handoff/src/manifest.rs:56-66—ExactIdentityV1(quoted);:85-129LayoutClassV2(quoted) with the windowed-class rule;:347-368LayerHeaderV1;:397-410the pre-RoPE canary contract.crates/muser-kvpack/src/lib.rs:3-32— the one-shared-dependency ruling (quoted), K1/K3/K4/K5, the adapter’s three additions.crates/muser-kvpack/src/layout.rs:20-58—MuseIdentityand its digest (quoted);:69-136the descriptor builder incl. K4 binding and the exact-logits plane;:138-163validate_geometry(quoted).crates/muser-kvpack/src/session.rs:144-244— durable save/find_deepest;crates/muser-kvpack/src/reuse.rs:1-7, 322-328— ladder order and the durable-caps-resident rule.crates/muser-cluster/src/config.rs:20-50—ReceiverConfigV2(quoted): pins, key paths, replay floor, identity fields.crates/muser-cluster/src/transport.rs:15-16, 35-46—KVPKV2framing; the raw-JSONprefix_cutlift.crates/muser-cluster/src/receiver.rs:108-178— the slow-volume refusal (comment and thresholds quoted),probe_ledger_reserve.[docs/kvpack-merge-handoff-20260820]— §1 the merge ruling, §3 F1 (prefix_cuthas no typed home), §4 the muser cache/reuse map, §5 pack internals, §6 the handoff architecture and pacing reality.[docs/muser-architecture.md §Durable and remote KV]— enrollment key hygiene, durable replay reservation (quoted in §24.6).[AGENTS.md]— the 2026-08-18 durability lesson (operational state on the internal disk) andscripts/gx10/durable_fsync_probe.py.[ledger N2]— receiver phases constant ~0.2 s (the non-wire cost).[receipt phase4-disagg-20260820/130815-g900091/]— the 1,823,184,896 B deep payload (§24.1’s scale anchor).- Ch 22, Ch 23 — the byte economics and the interchange snapshot this format carries.
- Ch 25, Ch 26, Ch 30, Ch 31 — the hits, the deltas, the transport, and the wire discipline this chapter foreshadows.
Chapter 25 — Warm reuse: the cache as an asset
status: polished · path: Muse Glimmer, pinned Muser tree
Prerequisites: Ch 24 (the format, the identity digest, the seal), Ch 23 (the interchange cut: 39 SWA tails + 13 NoPE fulls), Ch 22 (what a cache weighs). This chapter’s numbers are the most tempting to over-quote in the book; every one carries its scope, and §25.5 is about a time the scope discipline failed in public.
25.1 The question this chapter answers
Ch 24 built the vault: identities, seals, content-addressed packs, a fail-closed receiver. A vault is only worth what its contents return on withdrawal. This chapter is the withdrawal record — what happens when a request arrives whose prefix the engine has already computed, under what identity, with what controls, and what the hit is worth against recomputing. Put as the question we kept asking of every number below: when the engine says hit, what exactly did we not pay for?
kvpack’s overview doc frames the whole ladder in three rungs, each working
on more machines and costing less [docs/kvpack.md §The reuse ladder]:
| Rung | What happens | Result |
|---|---|---|
| Warm prefix, one Mac | Local resident/durable cache answers from installed state | ~65 ms to resume a shallow warm prefix |
| Warm prefix at depth, remote producer | Producer holds the exact prefix; no compute, no transfer | 0.613 s at 65,536 tokens (vs 68.6 s cold), 1.057 s at 130,815 (vs 147.8 s), bit-identical output |
| Delta handoff | Only the missing suffix crosses the wire | 54.2851 % of full payload bytes; output SHA-256 exactly equal |
Table 25.1: the reuse ladder [docs/kvpack.md]. This chapter cashes the
first two rungs; Ch 26 the third.
The doc’s own warning is what turned that ladder into a method for us:
“the miss controls matter as much as the hits.” A fast path is only genuine
reuse if a different prompt through the same path is not fast. Otherwise
you have not measured a cache; you have measured a shortcut that happens to
answer everything quickly, which is a bug wearing a benchmark’s clothes.
Every rung below therefore arrives with its negative control attached.
25.2 The ladder as code, and what counts as a hit
Two questions have to be settled before a single millisecond is quoted.
Where may the state come from? And what is the engine allowed to call a
hit? They sound like bookkeeping and they are not: the first decides
correctness, the second decides whether the numbers in the rest of this
chapter mean anything at all. The runtime ladder answers the first as one
ordered walk, muser-kvpack/src/reuse.rs:1: “Ordered exact-prefix reuse:
current session, resident, durable, then remote.” Four tiers, each stricter
than the last:
- Current session — the live session’s own token history is a raw
prefix of the request: no restore at all, just continue
(
reuse.rs:330-335). - Resident — an in-process, identity-scoped token radix holding
content-interned Muse plane chunks (
resident.rs); fast, but it has no cryptographic authentication of its own. Its internals are shaped entirely by Ch 22’s two regimes: cuts are kept at 256-token alignment “for ancestor reuse (non-aligned kept exact-hit-only),” plane chunks are SHA-256 content-interned in aChunkPoolwith weak-reference dedup, and a global byte-budget LRU (default 8 GiB) bounds the tier[docs/kvpack-merge-handoff §4]. - Durable — the kvpack
LocalStore: packs on disk, manifests, MACs — the authentication authority. Publication is cadence-driven, not request-driven: packs publish “at 2,048-token multiples + prompt/turn boundaries,” and each one carries the exact-final-logits plane at the synthetic layer 52 — theMUSE_EXACT_LOGITS_LAYERof[docs/kvpack-merge-handoff §4]. The writer side is worth following because of its order:session.rs:183-219lays the planes down, and the logits plane — the one that makes a cut exact, below — is written last, at:189-200. - Remote — an authenticated remote import, quarantined on failure,
then re-resolved locally (“a transport response must not become an
alternate authority”,
[docs/kvpack-merge-handoff §4]).
Publication itself is two calls on the ladder object, and their shapes say
the design: publish_resident exports the live session’s interchange
snapshot plus its cached logits into the radix, and publish_durable
hashes the token history under a domain tag
(b"muser-durable-prompt-v1\0") into an idempotency key so the same
session saved twice is one pack, not two
(reuse.rs:136-162).
The ordering embeds one non-obvious rule worth quoting in full, because it is the anti-self-dealing core of the design:
The durable tier is the sole authentication authority: when it is configured, its deepest authenticated cut caps what the unauthenticated resident tier may serve, so a resident entry can never serve a deeper cut than the durable chain has authenticated for the same identity.
crates/muser-kvpack/src/reuse.rs:322-328
Nothing resident — not even an entry with witnessed final logits — stands
beyond the deepest durable cut; the tests pin exactly this
(witnessed_exact_hit_does_not_stand_beyond_the_authenticated_cut,
reuse.rs:536-558).
Read that rule the other way round, because it is the hinge the whole design hangs on. The fast tier is allowed to be fast because it is never allowed to be deep on its own authority. Speed can be unauthenticated; depth cannot. A cache that could vouch for its own depth would be a cache grading its own homework, and the ladder simply removes the opportunity.
Which leaves the second question, and it has to be settled before any
number is quoted, because “cache hit” is the easiest metric in a serving
system to inflate. A hit is recorded only after authenticated state has
been restored and committed into an engine slot; a lookup that fails
verification or installation “contributes no saved value.” Two things that
feel like hits are deliberately excluded. Continuing the same live session
is not a hit — nothing was evicted, nothing was restored — and it lands in
session_continuation_hits. Installing a fresh GX10 prefill is not a hit
either, because a remote prefill is real compute someone paid for, not a
cache read; it lands in disagg_prefills. Both counters, in the doc’s own
words, “never inflate the cache hit rate”
[docs/kvpack-economics.md §What counts as a hit].
25.3 The shallow warm hit, at its original scope
Start with the easiest case there is, the one a single Mac can hold on its own: what does it cost to answer a prompt we have already answered? That is the first measured rung — a 2,048-token prompt whose exact prefix is already resident. The P4 cell of the campaign ledger is a five-sample measurement with one unmeasured warmup request priming the serve path:
| P4 cell | Five-rep samples | Median | CV |
|---|---|---|---|
| Warm resident prefix hit | 65.263, 64.502, 64.631, 64.621, 64.921 ms | 64.631 ms | 0.4239 % |
Table 25.2: the shallow warm hit [ledger P4 table]. CV here is the
coefficient of variation — standard deviation over mean, the campaign’s
stability metric (Ch 1’s
convention). Against the same packet’s official five-repetition cold disagg
median of 46.787 s, the ledger computes “a 723.90x cold-to-warm TTFT ratio”
[ledger P4] — TTFT being time to first token, the quantity
Ch 27 makes the whole disaggregation argument
turn on.
A ratio that large is exactly the kind of result that ought to make you
suspicious, so the first thing we did with it was go look at the counters
rather than the clock. The hit accounting of §25.2 is what made that check
possible, and the cell’s evidence trail is that accounting made visible:
“the cache cell itself recorded exactly one Spark prefill, one declared
unmeasured cache-path warmup, and five measured resident hits: cache hits
advanced 0 → 6, disaggregated prefills remained 1, and 654,311,424 bytes
were served from cache” [ledger P4]. Read that as an alibi. One prefill
means the state was computed once and only once. Six advances across five
measured requests plus one declared warmup means nothing was quietly served
twice or counted twice. A flat prefill counter means no request smuggled in
fresh remote compute and called it reuse. The counters moved exactly as a
real hit should move them, and nothing else moved at all.
Then the scope, carried verbatim in substance from the claims register
because it is the load-bearing part: this is the shallow figure —
2,048-token, resident, one Mac — it “remains valid at its original scope,”
and the register’s proposed wording for it stays conditional on the final
identity [claims #11]. It is never the number to quote at depth. That
number belongs to the next section, and the two must never be conflated;
the campaign keeps a landmine list precisely because “~64 ms warm hits at
depth” is exactly the sentence someone will eventually write.
25.4 Warm reuse at depth: stage 5 of the kvpack ladder
A shallow hit on one machine is a nice result and a weak argument. The
claim the whole appliance rests on is the deep one: does reuse still
deliver at 65,536 and 130,815 tokens, with controls, on live hardware?
That is the question the kvpack ladder’s stage 5, run as ordered
fail-closed stages [ledger Arc 3], was built to answer.
The apparatus is designed so that a hit cannot hide. For each depth, an
isolated leased server runs three legs: a cold leg (remote prefill from
the producer, full handoff), a warm leg (the same prompt again, after
the cold leg installed the state), and a miss control (an unrelated
8,192-token prompt through the same path). Cold gives us the price of not
having the cache. Warm gives us the price of having it. The miss control is
the negative — the leg that must stay slow, or the other two prove nothing.
The verdict [ledger "Kvpack ladder stage-5 isolated-depth verdict"]:
| depth | leg | generation | first-token s | total s | producer exit |
|---|---|---|---|---|---|
| 65,536 | cold | 960209 | 68.6166 | 74.5217 | 0 |
| 65,536 | warm | n/a (no producer) | 0.6132 | 6.5360 | n/a |
| 65,536 | miss control (8,192) | 960210 | 10.5567 | 12.8994 | 0 |
| 130,815 | cold | 960211 | 147.8321 | 157.7974 | 0 |
| 130,815 | warm | n/a (no producer) | 1.0566 | 11.0625 | n/a |
| 130,815 | miss control (8,192) | 960212 | 10.5088 | 12.8513 | 0 |
Table 25.3: stage-5 isolated-depth results [ledger stage-5 verdict; receipts kvpack-ladder-20260820/attempt-9-20260822T074100Z-stage5-warmhit/ stage5-warm-hit/{65536,130815}/warmhit-*.json]. The receipts record
legs_valid: true, outputs_match: true, and producer_driven: false
on both warm legs — I re-read the JSONs; the numbers above are the
receipts’ own. Three findings, in the register’s own scope language:
- Cold and warm text was bit-identical at both depths
[claims #11]. Not similar — the same bytes, which is only claimable because the restored planes install at the exact ring rotation (Ch 23 §23.4) under the exact identity (Ch 24 §24.5). - No producer drive on either warm hit
[claims #11]. The warm leg’s “n/a (no producer)” row is the receipt’sproducer_driven: false: zero remote compute, zero wire. The Mac answers from its own installed state. - The miss controls stayed slow — 10.5–12.9 s for an unrelated
8,192-token prompt through the same path, producer-driven as expected.
“The fast path is genuine reuse, not a cache that answers everything”
[docs/kvpack.md].
Then the discipline half of the claim, because this row is under operator
review and the book does not get to outrun it. The table above is
seductive, and the seduction has a precise shape: two bolded latencies look
like a benchmark result, and a benchmark result is something you are
allowed to generalise from. These are two depth-specific samples, not a
distribution — one warm leg per depth, not five repetitions. The
register’s rule says so in as many words: “never call decode faster or
claim these two latency samples as a distribution.” Even the proposed
public wording — “At 65k and 130,815-token prompts, resident kvpack reuse
skipped the producer, preserved bit-identical output, and returned the
first token in about 0.6 s and 1.1 s” — carries the stamp OPERATOR
REVIEW REQUIRED [claims #11]. The release lock is authoritative. This
chapter reports the measurement and its scope; it does not write product
copy on the register’s behalf.
What the warm leg mechanically does. The reason this matters here is that “no producer drive” is an absence, and an absence is a poor thing to build confidence on — the reader deserves the corresponding presence, the work that actually happens in those six tenths of a second.
The state restores “into an owned detached shadow, two-phase KV+logits swap
with checkpoint rollback” [docs/kvpack-merge-handoff §4]. Unpacked: the
tier’s bytes land in a MetalMuseModel generation nobody is decoding from,
the shape gate of Ch 23 §23.4
validates the cut, the install reproduces ring rotation, and only then does
an infallible swap publish KV and logits into the serving session. It is
the same shadow-then-commit contract as the handoff sink
(Ch 24) and the context-shift staging
(Ch 23 §23.7) — build the new
state somewhere nobody can observe it, and make the visible step the one
step that cannot fail. A failed restore therefore aborts and resets engine
state rather than leaving a partial cache live
[third_party/kvpack/README.md §How it works].
So the warm leg’s clock is not idling. The 0.6132 s / 1.0566 s first tokens
are what it costs on this Mac to restore and verify roughly 0.87 GB /
1.74 GB of sealed state and locally decode one boundary token — the NoPE
arithmetic being 65,535 × 13,312 ≈ 0.87 GB and 130,814 × 13,312 ≈ 1.74 GB
[Ch 22 §22.7] — with zero wire and zero producer compute. How that time
splits between verify, install, and commit we cannot tell you: no phase
decomposition of the warm leg is retained in the receipt beyond the wall
clocks [unverified].
Which sets up the correction we most want the reader to leave with. Coming off the shallow rung, the intuition is that a deep warm hit is the same event at a larger size — still a hit, still in the tens of milliseconds. It is not. The deep warm hit is a second-class sibling of the shallow one: 0.6132 s and 1.0566 s, not 64.631 ms. Restoring and re-verifying roughly a gigabyte of sealed state (Ch 22: a 130,815-token NoPE span is ~1.74 GB) costs real time even with zero wire, where the shallow rung had a prefix sitting resident in the process already. And the warm totals — 6.54 s, 11.06 s — are a different quantity again, because they include the streamed decode that follows the first token. Different rung, different price, different number; the moment two of these are quoted as one, the measurement has been lost.
25.5 The retracted “failure” — and the three apparatus failures before it
The verdict above was the fifth attempt. The four that failed before it are the best evidence-culture exhibit in this Part, so this section walks them one at a time instead of listing them — beginning with the one that briefly looked like a real finding.
The first isolated run at 65,536 came back reporting
outputs_match: false. Taken at face value that is the worst sentence
anywhere in this Part: the cache returning different text from the cold
path would make every number in the previous section worthless, and we
read it that way first. So we went looking for the correctness bug.
There wasn’t one. The probe’s producer-timeout default was 240 s, and a
65,536-token prefill takes longer than that, so the harness had killed the
producer mid-handoff. The “cold” leg consequently had nothing to say —
“the Mac returned an empty completion in 3.7 s because no KV ever
arrived” — and the warm leg timed out entirely. What the comparison had
actually done was this: “outputs_match: false was comparing an empty
string against a leg that had errored … No cache-correctness conclusion
can be drawn from this cell in either direction”
[ledger e426ec0 postmortem].
The lesson we took was not “the cache is fine after all,” which would have
been the comfortable reading. It was that the harness had been able to
publish an infrastructure failure using the vocabulary of a correctness
failure — a defect in the instrument, not in the thing measured. So two
fixes landed alongside the retraction, not just the retraction: a
depth-scaled producer timeout, and legs_valid/leg_errors gating so that
“a timeout can no longer be published as a correctness failure” [ledger].
The measured-numbers ledger then carries the landmine entry that keeps the
retracted headline from walking again: the retracted cell was an
infrastructure timeout, not a cache-correctness failure; the valid cell is
bit-identical [measured-numbers §7].
Three more attempts failed before one passed, and here is the part worth sitting with — none of them failed at the thing we were trying to measure. Each time, the ladder’s stop rule refused to hand us a number, which is a far better outcome than handing us a wrong one.
Attempt 6 was supposed to be the clean sweep. Instead the receiver rejected the handoff outright as a “replayed or stale generation.” Our first instinct was that the check was too strict, since the run was obviously fresh; the generation formula turned out to wrap every 16 min 40 s, and a deep run is long enough to hand the receiver a generation number it had already seen. From the receiver’s side that is indistinguishable from a replay, and a receiver that guesses in that situation is a receiver that can install stale state. We fixed the formula and left the check alone.
Attempt 7 took an obvious economy: lease one long-lived server and run both depths through it, rather than paying setup twice. It produced a full set of numbers, and the numbers were worthless. The second depth’s “cold” leg had run on a server whose radix already held the first depth’s state, so the control that was supposed to establish the price of not having the cache had itself been warmed. A cold leg is only cold on a machine that has never seen the prompt. Fresh independently leased servers per depth became part of the apparatus from then on, cost and all.
Attempt 8 is the one that stings, because it got everything right and
failed anyway: after a successful handoff, an O_EXCL collision on a
node-side operational file made the run refuse to publish. Irritating in
the moment, and correct on reflection — exclusive-create is what stops two
concurrent runs from sharing one file and letting the later one silently
overwrite the earlier. The answer was to namespace the file by attempt
identity and generation [ledger stage-5 verdict preamble], never to relax
the flag.
The pattern to internalize is in the shape of those four stories rather
than in any one of them. The fail-closed machinery refused every bad
attempt; the verdict waited for a valid one; and the retracted headline
never hardened into a “cache returns wrong text” legend, because the
retraction was published as loudly as the original cell. Evidence wins over
wording [measured-numbers §6 rule 10].
25.6 Admission identity discipline — the exact-hit contract
Everything so far has assumed the engine can tell your prefix from a prefix that merely resembles yours. Suppose it could not. A hit under the wrong identity would not announce itself as an error; it would return fluent text computed against the wrong tokenizer, template, or adapter, and the only symptom would be output that is subtly, unaccountably wrong. That is why what makes a warm hit admissible is the same discipline that makes it bit-identical. As code, three rules from the ladder:
- Exact held identity, structurally unreachable otherwise. Resident
entries are keyed under the
MuseIdentitydigest (Ch 24 §24.5); “a wrong identity is structurally unreachable from a lookup, never a best-effort hit” (layout.rs:33-36), and swapping the durable tier’s identity re-scopes every lookup so stale entries miss (reuse.rs:560-590). - Exact hits need witnessed final logits. A cut that would end a
generation is only exact if the final target distribution was captured
with the KV: “Exact-final state is useful only when its target
distribution was captured with the KV cut. Older/generic entries remain
eligible as aligned ancestors but cannot masquerade as exact”
(
reuse.rs:352-360). A full-depth restore without logits is an error, not a silent downgrade (reuse.rs:203-217). - Durable caps resident (§25.2) — depth served is authenticated depth.
The identity chain binds everything Ch 24
enumerated — model, tokenizer, template, context policy, adapter, layout,
scalar math — and the receipt-side proof that the binding has teeth is in
the refusal records: the well-formed config with one flipped adapter digest
was rejected with an explicit identity-mismatch error
[docs/kvpack.md §Proven live]. Admission is fail-closed on identity the
way append is fail-closed on continuity
(Ch 23 §23.2): the engine would
rather recompute than trust state it cannot prove.
25.7 The economics of a hit, honestly
What is a hit worth? The dashboard’s economics module answers with
formulas and refuses to answer without inputs
(crates/muser-kvpack/src/economics.rs, specified by
[docs/kvpack-economics.md]):
restore_speedup = local_prefill_seconds / restore_seconds
seconds_saved = max(0, local_prefill_seconds - restore_seconds)
The rule running through that module is that a derived value is either
earned or labelled, never quietly assumed. restore_speedup “becomes
measured only after a caller records positive, paired wall-clock durations
for a restore and the identical local-prefill cut. Before that it is zero
and … mock” [docs/kvpack-economics.md §Timing]. gflops_avoided
reports only the conservative linear weight-matmul floor
(2 * 30e9 * restored_tokens) and omits attention FLOPs, so that “the
field undercounts, not overclaims” (economics.rs:53-57) — a savings
figure that errs toward less saving is one you can quote without first
auditing it. And joules_saved has no calibrated power source at all, so
it stays permanently tagged mock rather than becoming a plausible
fiction.
The same rule governs bytes, where it has a Muse-specific bite. Durable and
remote restores must bill the authenticated manifest’s byte count, never a
tokens × per_token_bytes estimate, because “Muse’s 39 SWA layers don’t
grow linearly past 2048 tokens, so a naive per-token estimate overstates
value on long contexts” [docs/kvpack-economics.md §Byte accounting]. Note
which direction that error runs: the shortcut would flatter precisely the
deep-context case the appliance exists to sell. The resident tier, being an
in-process copy with no manifest, is allowed the estimate — and has to be
labeled as having used it.
With those guardrails in place, two measured anchors bound the answer — and they point in usefully different directions, which is why the honest answer is “it depends on what you are comparing against.”
Against local Mac re-prefill, deep reuse wins by orders of magnitude:
warm first-token 1.0566 s at 130,815 tokens versus a local 131,008-class
prefill mean of 570.122 s [ledger "EEE A/B at 130815"], and the ladder
doc states the general form of it — “at deep prompts, reuse in any form
beats recompute by orders of magnitude” [docs/kvpack.md §Economics].
Against an overnight GX10 producer at 5–20k tok/s, the picture is much
less flattering, and the research frontier analysis is where that gets
said out loud: the honest fleet crossover sits at ~20–40× reuse counts, and
per-request TTFT on composed contexts is “dominated by the SWA warm-up
(~5–9 s at W = 2048 …), not by the restore (~0.6 s for a full 131k NoPE
image)”. Which is worth pausing on — at fleet scale the restore is not the
expensive part of serving a composed context. That whole analysis is
labeled as analysis, carrying evidence tags [EXACT arithmetic]/[HYP] rather
than a Muser measurement [docs/kv-reuse-frontier §1].
Either way, the subject of the transaction is not the thing a first glance
suggests. The cache is ~3,000× larger than the text it memoizes, so bytes
cannot possibly be the product: “the appliance sells time (and joules), not
bytes” [docs/kv-reuse-frontier §1]. The economic subject of a cache is
time and joules; the bytes are just the receipt.
25.8 Tradeoffs
Every choice below had a plausible alternative, and in one case the alternative is still a live research lane rather than a settled question. What follows is what the ladder gave up, and what the measurement says it bought in exchange.
Exact-prefix reuse vs semantic or fuzzy reuse. The ladder serves exact
token prefixes under exact identities, full stop. The alternative —
similarity-matched or spliced caches — is a research lane the frontier doc
maps in full, and its headline accounting is why the product stays exact:
“exact composition of B onto A costs a full prefill of B in A’s context …
Every non-prefix composition of representations is approximate by
construction” [docs/kv-reuse-frontier §2]. Muser keeps the approximate
lane out of the admission path (composed caches are “RECONCILED, never
EXACT,” a provenance distinction the research inherits from the same
culture [docs/kv-reuse-frontier §4]). The measured consequence of
exactness is Table 25.3’s outputs_match: true at two depths; the cost is
that near-misses serve nothing.
Two rungs, two numbers, never blended. 64.631 ms (shallow, five-rep
median, CV 0.4239 %) and 0.6132 s / 1.0566 s (deep, one sample each) are
different scopes on different apparatus — the campaign’s landmine list
exists because conflating them manufactures a claim nobody measured
[measured-numbers §7]. The honest comparison for the deep rung is its
own cold leg (111.9× at 65,536; 139.9× at 130,815 by first-token
arithmetic on Table 25.3) — single-sample ratios, labeled as such.
Fast resident tier vs authenticated durable tier. The resident radix
is the only tier fast enough for the ~65 ms rung, but it authenticates
nothing; the durable tier is authoritative but reads disk and verifies
MACs. The design neither trusts the fast tier nor slows every lookup to
the strict one — it caps the fast tier by the strict one’s authenticated
depth (§25.2). The unmeasured cost is a durable catalog read on every plan
(a find_deepest walk, session.rs:222-244) even when the resident hit
would serve; no retained measurement isolates that lookup’s wall cost
[unverified].
Where the gap lives. Warm reuse is the anti-gap: the whole point is
that no Metal dispatch runs for the restored prefix. The costs it does
carry are the receiver/restore phases (verify, install, commit — the
~0.2 s-class constants of [ledger N2]) and the pack reads of §25.7,
which live in the economics panel, not the dispatch-gap table.
25.9 What comes next
A warm hit is the best case: the cache holds exactly what you need, and nothing moves. One rung down is the case that dominates real traffic — the cache holds a prefix of what you need: the system prompt and the first 32k of a document, say, with a fresh suffix appended. Exactness does not have to be sacrificed to save the wire: the handoff can be armed as a delta, admission can verify the held prefix to the token, and only the missing suffix crosses — with a measured 54.2851 % cell to show for it. How the cut is aligned, what gets re-sent and why, and how whole sessions move between decode nodes without ever losing either end — that is Ch 26, the last chapter of this Part.
References
[docs/kvpack.md]— the reuse ladder (Table 25.1), miss-control framing, refusal receipts, economics summary.[ledger P4]— the shallow warm-hit five-sample cell, the 723.90× ratio, and the counter trail (0 → 6 hits, 654,311,424 B served).[ledger "Kvpack ladder stage-5 isolated-depth verdict"]— Table 25.3’s source, the attempt-6/7/8 apparatus failures, the PASS verdict.[ledger e426ec0 postmortem]— the retracted 65,536outputs_match: falsecell: 240 s producer timeout, empty-vs-errored comparison, the two fixes.[receipts kvpack-ladder-20260820/attempt-9-20260822T074100Z-stage5-warmhit/ stage5-warm-hit/{65536,130815}/warmhit-{65536,130815}.json]— per-leg TTFT/totals,legs_valid,outputs_match,producer_driven(re-read for this chapter).[claims #11]— the scope language carried in §25.3–25.4 (original scopes, no-producer-drive wording, OPERATOR REVIEW REQUIRED).[measured-numbers §1d, §6, §7]— the warm/delta rows, claim-discipline rules, and the “different scopes” landmine.crates/muser-kvpack/src/reuse.rs:1-7, 136-162, 203-217, 322-368, 536-590— ladder order, resident/durable publication, exact-hit logits rule, durable-caps-resident, identity re-scoping tests.crates/muser-kvpack/src/session.rs:144-244— durable save andfind_deepest.crates/muser-kvpack/src/economics.rs+[docs/kvpack-economics.md]— hit definition, byte accounting, mock-tagged derived values.[docs/kv-reuse-frontier-20260820 §1-2, §4]— the crossover and SWA-warm-up analysis (labeled research), the exact-vs-reconciled provenance regime.[ledger "EEE A/B at 130815"]— the 570.122 s local deep-prefill mean (§25.7’s anchor).- Ch 22, Ch 23, Ch 24 — weight, interchange, and identity machinery the hits stand on.
- Ch 26 — the delta rung this chapter exits to.
Chapter 26 — Delta handoff and session migration
status: polished · path: Muse Glimmer, pinned Muser tree
Prerequisites: Ch 25 (the reuse ladder, the exact-hit
contract), Ch 24 (the seal, the receiver, the
prefix_cut lifted at the frame boundary), Ch 22
(the per-class byte arithmetic this chapter re-derives on the wire),
Ch 23 (why SWA rows are
position-bound and NoPE rows are not).
26.1 The case between “everything” and “nothing”
Ch 25 cashed the two clean rungs: a full hit, where the cache holds exactly the prompt and nothing moves at all. Real traffic is rarely that clean. The common shape is half-cached: the system prompt and the first chunk of a document are already resident — from an earlier request, an earlier session, an earlier handoff — and only the suffix is new. The two obvious responses are both wasteful: recompute everything (throw away proven state) or transfer everything (ship bytes you already hold, bit-identically, and pay the wire again).
The delta rung is the third response: leave the held prefix installed, admit the difference against it, and move only what is new. And once a cache can be moved partially on the wire, it can also be moved wholly between machines — a session migrating from one decode node to another, or into enrolled storage — which is this chapter’s second half. Both halves obey one rule stated once and enforced everywhere: the receiver must hold exactly what it claims to hold, provably, before anything is skipped.
26.2 The shallow cell: half-cached, half the bytes
Before trusting a delta anywhere deep, we wanted a cell small enough to check by hand — one where the arithmetic predicts the answer in advance, so that a surprise in the measurement would be a real surprise and not a mystery of scale. The first delta cell was therefore deliberately shallow: hold a 1,024-token prefix, request 2,048. The question put to it was blunt. How much of the wire does a half-cached prompt actually save, and does what comes out of the machine change?
The wire moved 49.98 % of the full-handoff bytes and the decode was
bit-exact. We kept the run that proved it [ledger T-series "Delta-only prefill (W3)"; receipt nvfp4-pacing8g-20260818/delta-wrapper7/],
and the claims register carries the sentence it became: “the original
half-cached 2,048-token cell moved 49.98% of full bytes and decoded
bit-exactly” [claims #12].
Why 49.98 and not the naive 50.0 %? Derive it with Ch 22’s
formula, remembering one convention from Ch 23:
the receiver holds back the boundary token and decodes it locally, so KV
ships for one token less than the prompt. At this geometry the suffix
(1,024 tokens) fits inside the 2,048 window, so the SWA span for the delta
is exactly the suffix’s own rows — nothing is double-paid (§26.4 is where
that changes) — and the payload is the suffix’s NoPE rows plus the suffix’s
SWA rows, one token short: 49.98 %, the suffix share at that geometry
[measured-numbers §1d]. The cell’s value is not the number, which
arithmetic predicts; it is the bit-exact decode — proof that skipping
1,024 tokens of prefill on the wire changes nothing downstream. Said the
other way round: a receiver handed only the suffix emitted, token for token,
what a receiver handed the whole cache would have emitted. Half the bytes
bought the same answer.
26.3 The deep cell: 32,768 of 65,536, measured to the byte
The shallow cell proved the mechanism. It could not prove the mechanism survives depth, and there were two ways it might not. The saving could decay once the suffix grows longer than the sliding window, where a delta stops being a clean suffix and starts overlapping state the receiver already holds a version of. And bit-exactness could rot quietly over a context long enough that a single stale row would change a token far downstream. Both risks needed a witness deep enough to expose them, judged against a control.
Stage 6 of the kvpack ladder ran the deep witness: hold a 32,768-token
prefix, request 65,536, and compare a delta handoff against a full-handoff
reference on the same prompt [ledger "Kvpack ladder stage-6 delta-witness verdict"]. Three arms, one node, one night:
| arm | generation | prompt tokens | prefix cut | payload bytes | producer total s |
|---|---|---|---|---|---|
| prefix identity witness | 960213 | 32,769 | 0 | 517,996,544 | 30.7269 |
| delta handoff | 960214 | 65,536 | 32,768 | 517,983,232 | 63.4784 |
| full reference | 960215 | 65,536 | 0 | 954,190,848 | 64.5361 |
Table 26.1: the stage-6 arms [receipt kvpack-ladder-20260820/ attempt-10-20260822T074826Z-stage6-delta/stage6-delta-65536/stage6-verdict.json]
— delta_share_of_full: 0.5428507652… = 54.2851 %, output SHA-256
exactly equal to the full-handoff reference (2526a55d…19778,
exact_against_full_handoff: true), seal_eligible: false.
Every payload in that table reconciles to the byte against the per-class arithmetic — this is Ch 22 §22.7’s method applied three times, boundary token held back throughout:
full reference:
NoPE [0, 65,535) = 65,535 × 13 × 1,024 = 872,401,920 B
SWA window = 2,048 × 39 × 1,024 = 81,788,928 B
total 954,190,848 B ✓
prefix witness (32,769-token prompt, boundary held back → 32,768 rows):
NoPE [0, 32,768) = 32,768 × 13 × 1,024 = 436,207,616 B
SWA window = 81,788,928 B
total 517,996,544 B ✓
delta:
NoPE [32,768, 65,535) = 32,767 × 13 × 1,024 = 436,194,304 B
SWA window (re-sent) = 81,788,928 B
total 517,983,232 B ✓
Here is where the arithmetic we walked in with turned out to be wrong. If a
delta ships “the part the receiver does not already have,” then its payload
should weigh full − prefix, and that is what we expected the table to say.
It does not. The delta (517,983,232 B) is larger than that difference
(436,194,304 B) — by exactly one SWA window, 81,788,928 B, far too round a
discrepancy to be noise.
The reason is positional. The held prefix’s rings contain
the window at the prefix’s tail, positions [30,720, 32,768); the finished
context needs the window at [63,488, 65,536) — different tokens, and on
RoPE layers different bytes (Ch 23 §23.5:
an SWA key row is rotated by its absolute position at store time). The
schedule states the rule plainly: “delta span re-sends the whole window
when the suffix exceeds it” [docs/kvpack-merge-handoff §6]. So at this
geometry the delta ships a full new window plus the NoPE suffix, which
happens to land within 13,312 B (one NoPE token) of the prefix arm’s size —
a coincidence of the 50 %-cut geometry, not a law.
The lesson we took from the miss is worth stating on its own, because it is the idea in this chapter most likely to be mis-generalized: a delta is not a subtraction. It is a re-derivation of the span the finished context needs, and whenever the suffix outruns the sliding window, the window is part of that span again — freshly rotated, freshly shipped, no matter how much of it the receiver appears to hold already.
And the register’s caveat, carried verbatim in substance because it is the
difference between an engineering fact and a product claim: “Do not
claim producer-side compute savings (suffix-only wire, not proven
suffix-only compute)” [claims #12]. The wire provably carried
54.2851 % of the bytes; nobody has proven what the producer recomputed
behind the seal. The cell is also explicitly seal_eligible: false —
unsealed engineering evidence, like everything in this book
[measured-numbers §6 rule 8].
26.4 Arming a delta: the ladder decides, admission enforces
Bytes on a wire were the easy half. The harder question is asked at request time, in the moment before anything is skipped: is this delta legal? What breaks if the answer is wrong is not a slow request — it is a fast and plausible one, a session decoding against a prefix that is not the prefix it believes it holds, with nothing anywhere raising a hand about it.
Muser answers the question twice, deliberately. The runtime path from “a prompt arrives on the remote lane” to “delta” has two halves: the reuse ladder classifies the hit, then the handoff admission verifies the cut. The ladder’s classifier is small enough to read whole — note the boundary-token convention and the alignment rule:
#![allow(unused)]
fn main() {
// crates/muser-kvpack/src/reuse.rs:25
/// What the reuse ladder can do for a prompt a remote producer would
/// otherwise prefill.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RemoteReuseAction {
/// The ladder holds the prompt minus at most the boundary token the
/// receiver decodes locally: skip the remote transfer entirely.
ServeLocal,
/// The ladder holds a cut-aligned strict prefix: leaving it installed in
/// the session arms the handoff as a delta (the producer's `prefix_cut`
/// is validated against the session's held tokens at admission, and a
/// full producer answer atomically replaces them, so arming never
/// grafts unverified state).
ArmDelta,
/// Nothing the remote handoff could build on: run a full transfer.
FullTransfer,
}
// crates/muser-kvpack/src/reuse.rs:55
fn remote_action(matched: usize, prompt: usize, cut_align: usize) -> RemoteReuseAction {
if prompt < 2 {
return RemoteReuseAction::FullTransfer;
}
if matched >= prompt - 1 {
RemoteReuseAction::ServeLocal
} else if matched > 0 && cut_align > 0 && matched.is_multiple_of(cut_align) {
RemoteReuseAction::ArmDelta
} else {
RemoteReuseAction::FullTransfer
}
}
}
A hit reaching prompt − 1 is already full (the held-back token covers the
rest); a shorter prefix arms a delta only on the cut alignment; anything
else — including an unaligned partial, which the radix deliberately keeps
for exact-hit-only lookup — runs a full transfer, “left uninstalled, so
the full-transfer path can reset exactly as before” (reuse.rs:226-237).
The server adds one economic filter before arming: a live-session
continuation prefills its suffix locally for less than a handoff costs, so
only fetched tiers (resident, durable, remote) arm
(arm_remote_delta, openai.rs:2907-2918, with matched + 1 < prompt
guarding a nonempty transferable suffix).
The alignment constant is the format’s, not the ladder’s:
PREFIX_CUT_ALIGN: u64 = 256 (crates/muser-cluster/src/schedule.rs:26) —
“Delta handoffs may begin only on a radix-friendly 256-token boundary,”
matching kvpack’s 256-token prefix-key blocks (Ch 24 §24.3).
Admission then verifies the cut against everything it must, fail-closed:
#![allow(unused)]
fn main() {
// crates/muser-cluster/src/identity.rs:140
/// transfer over `[cut, position)`. Fail closed unless the cut is
/// 256-aligned, leaves a nonempty suffix, names a prefix the receiving
/// session holds exactly, and — for declared schedules — the declared
/// target segments equal the span schedule for the cut.
fn validate_prefix_cut(&self, manifest: &BeginManifestV2) -> kvpack_handoff::Result<()> {
// … ("delta prefix cut is not a 256-aligned cut inside the prompt";
// "delta prefix cut names a prefix the receiving session does not
// hold" when held_token_ids[..cut] != prompt_token_ids[..cut]) …
}
}
“Admission remains fail-closed on exact held identity, aligned nonempty
suffix, and span schedule” [claims #12] — the held tokens are compared
element-by-element against the manifest’s prompt; a one-token disagreement
refuses.
That two-step is the point, so it is worth saying in the other vocabulary as well. Arming is a proposal: the ladder looks at what the session appears to hold and says this could be a delta. Admission is the proof: it makes the proposal earn the skip against the actual token ids. Because the check lives at admission rather than at arming, a wrong guess upstream costs a full transfer — the expensive outcome — instead of a wrong answer, the unrecoverable one.
The span schedule itself is derived, not negotiated:
muse_schedule_span_for (schedule.rs:91-130) computes the NoPE tiles
over [prefix_cut, position) in 512-token steps and places the SWA span at
position − 2,048 clamped to the cut — the exact decomposition §26.3
reconciled to the byte.
One more honest wart from the format audit, and it earns its place here
because it decides who is allowed to originate a delta at all: the typed
BeginManifestV2 still drops prefix_cut, so the field travels as raw JSON
lifted at the frame boundary (transport.rs:35-46), and “delta handoff
(ArmDelta, 256-aligned cuts) has a Python-only producer”
[docs/kvpack-merge-handoff §3 F1]. Keeping two implementations honest
about one format is what makes the format trustworthy; this is the
maintenance tax that cross-verification charges for it.
26.5 Session migration: moving the whole asset, two-phase
Delta handoff moves the new part of a cache. Migration moves the whole session — target KV, DFlash state, sampler/RNG, replay messages, vision rows, revision — between machines. Ask of it the question the last section asked of arming: what breaks if this goes wrong? Not throughput. The failure that matters is a session that ends up existing twice, or not at all, because a machine died in the window between “sent” and “kept”. Everything below is shaped by that one crash, and the architecture doc lays out the protocol in five sentences:
Migration is two phase. Decode-node copy/move uses authenticated HTTPS between identically qualified Muser decoders; storage-tier copy/move uses enrolled kvpack storage. The destination durably commits before a move can delete the source, and transfer status is idempotently queryable after ambiguous failures. GX10 is not a decode destination.
[docs/muser-architecture.md §Context and sessions]
The public surface is one route — POST /v1/sessions/{id}/migrate
(axum_httpd.rs:527, handler at :3742) taking mode (copy|move),
tier (decode|storage), destination, and an optional transfer_id
whose reuse is bound to the same session/destination/mode — replaying a
different migration under a known id is a 409 (:3812-3830).
Decode tier. The destination must be “an absolute HTTPS origin without
path, query, or fragment” (validate_decode_destination,
axum_httpd.rs:4265-4277) — a Muser decoder, authenticated by the source
server’s API key, optionally pinned to a private CA
(MUSER_DECODE_MIGRATION_CA, :4345-4359). run_decode_transfer
(:4394-4504) is the two-phase spine:
#![allow(unused)]
fn main() {
// crates/muser-server/src/axum_httpd.rs:4428 (abridged to the spine)
let prepare = InternalTransferPrepare {
transfer_id: transfer_id.into(),
// bytes, sha256, transport_key,
model_sha256: export.model_sha256,
tokenizer_sha256: encode_hex32(&export.tokenizer_sha256),
template_sha256: encode_hex32(&export.template_sha256),
layout_abi: export.layout_abi,
dflash_identity_sha256: export.dflash_identity_sha256,
// (vision projector/preprocessing digests)
};
// 1. prepare at the destination → 2. PUT the payload → 3. commit
// … then:
if !committed {
return Err("destination did not durably commit the transfer".into());
}
server.logical_sessions.update_transfer(transfer_id, "destination_committed", None, false)?;
}
“Identically qualified” is not a hope — the prepare record carries the
model, tokenizer, template, layout-ABI, DFlash, and vision digests
(InternalTransferPrepare, :3979-3997; session_identity at :4318-4343),
the same identity family Ch 24 sealed into every
pack. A destination that cannot match them refuses.
Now the crash the whole design is built around. Suppose the payload uploads
and the commit reply never comes back. From the source’s side, “it never
landed” and “it landed and the acknowledgement was lost” look identical, and
each of the two guesses destroys something: assume failure and you may keep
a session that now lives on two machines, assume success and you may delete
the only copy. So the source is not allowed to guess. The ambiguity rule is
explicit in code: if the upload or commit errors, the source reconciles by
querying the destination’s transfer status and accepts a
"committed" verdict from there (:4480-4491, via
GET {destination}/v1/session-transfers/{id}, :4380-4392); failures
record status "ambiguous" unless the record already shows
destination_committed or source_restored (record_transfer_failure,
:3942-3958). Transfer status is queryable any number of times, on either
end, idempotently (session_transfer_get, :3960-3977) — after an
ambiguous failure, the answer to “did it land?” is a lookup, not a guess.
Storage tier. The same crash, against a weaker partner: the destination
here is not a peer running the transfer protocol but a filesystem on an
enrolled node, reached through pinned shell fragments. Everything therefore
has to be recoverable by re-running it. The destination is an enrolled
node, and enrollment is verified before anything moves: the registry entry must be healthy under
enrollment v2 with a live HMAC epoch (enrolled_storage_node,
:4540-4559). The remote side runs pinned shell fragments — prepare
(mkdir, chmod 700, sync), commit (verify byte count and SHA-256, then
mv temporary → final, sync file and directory), delete — quoted in
source at :4506-4538. The commit script is idempotent by construction:
if the final file already exists it re-verifies size and digest and exits
clean, so a retried transfer after an ambiguous failure cannot corrupt
either end. run_storage_transfer (:4568-4640) marks
destination_committed only after the remote commit script succeeds, and
the local payload is removed only when the record says the source side is
deleted (:4630-4638). A storage restore that is a move deletes the
remote bundle only after the local adoption succeeded — and if that delete
fails, the record degrades to source_restored_remote_retained rather
than pretending completion (:4657-4685).
The move invariant. Read across both tiers: no failure ordering deletes
the source before the destination’s durable commit is on record. A move
is a copy plus a deletion that only ever runs after "destination_ committed" — which is why the status vocabulary (starting,
transferring, destination_committed, source_restored,
source_restored_remote_retained, completed, ambiguous) has a shape:
every prefix of a crash is recoverable, and every recovery is a status
query away. And the GX10 line is a topology fact, not a slight: the GX10
is a prefill producer with no Muser decode runtime; there is nothing on it
to receive a decode session [docs/muser-architecture.md].
26.6 Tradeoffs
Delta vs full handoff. Measured: 54.2851 % of full bytes at the deep
cell with exactly-equal output SHA, 49.98 % at the shallow cell
[claims #12; ledger stage 6]. Unmeasured and unbought: resumability.
Ask what happens when a deep transfer drops most of the way through, and the
format has no answer to give — it carries “no offset/resume/retry vocabulary
… the only partial-work mechanism is prefix_cut at BEGIN,” so a dropped
1.82 GB transfer restarts from zero [docs/kvpack-merge-handoff §3 F3]. The
cut is a place to begin, never a place to resume. The register’s caveat stands
guard on the interpretation: wire savings are proven, producer-compute
savings are not [claims #12].
The SWA window tax. The delta pays 81,788,928 B to re-send a window it
“already has” a version of, because the version it has is position-bound
(Ch 23). The alternative —
re-anchoring cached rotated keys by one rotation — is exact mathematics
and a research lane this program has deliberately not shipped
([docs/kv-reuse-frontier §2]: the rotation group action is solved;
contextualization is not). At this geometry the tax is 15.8 % of the delta;
at deeper held prefixes it amortizes toward the NoPE-share floor of
Ch 22 §22.7.
Arming from the live session vs fetched tiers only. arm_remote_delta
refuses current-session prefixes (openai.rs:2907-2918) — locally
prefilling a suffix you are already positioned on costs less than any
handoff. The cost of the rule is that the one case it blocks (a live
session exactly on an aligned cut with a huge suffix) pays a local prefill;
the benefit is that arming always corresponds to state a tier vouched for.
Copy-then-delete vs in-place move. The two-phase design spends an extra copy’s storage and one round of status traffic to buy the invariant that no crash window loses the session. The alternative — in-place move with compensation — would need exactly the distributed transaction machinery the replay ledger/seal architecture already refuses to improvise (Ch 24 §24.6). The retained receipts for this lane are the wizard and ladder sessions; a dedicated migration failure-mode matrix is not among them [unverified] — the design’s guarantees are code-and-doc anchored here, with the same status-reconciliation paths exercised only incidentally.
Where the gap lives. Nothing here touches the decode graph; the costs are wire and storage, and they are booked where Ch 31 books wire costs. The one decode-graph interaction is the good kind: a delta’s installed prefix means fewer prefill chunks through the Metal graph, which is the entire point.
26.7 What comes next — and the end of Part V
Part V has followed one asset through its whole life: what the cache costs per token and per layer class (Ch 22), how the ring and the growing plane implement it (Ch 23), how kvpack seals it into a portable, provenance-pinned format (Ch 24), what a warm hit is worth with controls (Ch 25), and now how to move only the new part — or the whole session — without ever trusting an unverified byte. The discipline underneath every chapter has been the same: move KV, don’t recompute it, and prove what moved.
The next question is forced by the numbers already on the table. The cold
deep legs of Ch 25 measured 68.6 s and 147.8 s of
first-token latency on this lane — and the local alternative at 131k-class
depth was 570 s [ledger "EEE A/B at 130815"]. Someone computed that KV
fast, over a wire, under a seal, and it was not the Mac. If KV is an asset
that can move, then the machine that computes prefill and the machine
that uses it need not be the same machine — and the economics of splitting
them is exactly why Muser puts a Mac and a GB10 on the same fabric. That
argument — the TTFT cliff at depth, the roofline split, and what it costs
to trust someone else’s prefill — opens Part VI:
Ch 27.
References
[claims #12]— both delta cells, the admission rule, and the “suffix-only wire, not proven suffix-only compute” caveat (§26.2–26.4).[ledger T-series "Delta-only prefill (W3)"]+[receipt nvfp4-pacing8g-20260818/delta-wrapper7/]— the shallow cell.[ledger "Kvpack ladder stage-6 delta-witness verdict"]+[receipt kvpack-ladder-20260820/attempt-10-20260822T074826Z-stage6-delta/ stage6-delta-65536/stage6-verdict.json]— Table 26.1’s arms, the equal output SHA,seal_eligible: false(re-read for this chapter).crates/muser-cluster/src/schedule.rs:20-26, 91-130—PREFIX_CUT_ALIGN,muse_schedule_span_for(tiles, window clamp, layer-major streaming order).crates/muser-cluster/src/identity.rs:143-160—validate_prefix_cut(quoted abridged): alignment, nonempty suffix, exact held prefix, span schedule.crates/muser-kvpack/src/reuse.rs:27-66, 226-237—RemoteReuseActionandremote_action(quoted); unaligned partials left uninstalled.crates/muser-server/src/openai.rs:2907-2918—arm_remote_delta(fetched-tiers-only rule).crates/muser-cluster/src/transport.rs:35-46— the raw-JSONprefix_cutlift;[docs/kvpack-merge-handoff §3 F1, F3]— the Python-only producer and the missing resumability.[docs/muser-architecture.md §Context and sessions]— the five-sentence migration protocol (quoted in §26.5); §Durable and remote KV (the GX10’s producer role).crates/muser-server/src/axum_httpd.rs:527, 3742-3977— the migrate route and handler;:3942-3958ambiguous-failure recording;:3960-3977idempotent status.crates/muser-server/src/axum_httpd.rs:4265-4277, 4318-4343, 4394-4504— HTTPS-origin validation, the identity set,run_decode_transfer(prepare/upload/commit spine quoted), destination reconciliation.crates/muser-server/src/axum_httpd.rs:4506-4538, 4540-4659— storage prepare/commit/delete scripts, enrollment-v2 gate,run_storage_transfer’s commit-before-delete;:4642-4710run_storage_restore’s move semantics andsource_restored_remote_retained.[ledger "EEE A/B at 130815"]— the 570.122 s local deep-prefill mean (§26.7’s cliff).- Ch 22 — the per-class arithmetic reconciled in §26.3; Ch 23 — position-bound SWA rows behind the window tax; Ch 24 — the seal and receiver; Ch 25 — the ladder.
- Ch 27 — Part VI’s opening argument.
Chapter 27 — Why disaggregate prefill and decode
status: polished · path: Muse Glimmer, pinned Muser tree
Prerequisites: Ch 1 (the bandwidth wall and the four levers), Ch 7 (NVFP4), Ch 22 (KV bytes per token), Ch 26 (delta handoff). This is the first chapter of Part VI.
27.1 Where we are: KV as a movable asset
Ch 26 ended with the delta handoff: with
32,768 of 65,536 prompt tokens already held, the wire moved 54.2851 % of the
full payload and the decoded output was exactly the full-handoff reference
[claims #12]. The lesson generalized quietly: the KV cache is not a
private data structure of the machine that computed it — it is an asset that
can be moved, stored, and resumed, bit-exactly, somewhere else.
This part of the book takes that lesson to its logical end. If KV can move, then the machine that computes prefill and the machine that uses the result need not be the same machine. That is the disaggregated lane. This chapter is the argument for why you would bother — an argument that returns, at systems scale, to the memory-bound case Ch 1 built for one token.
The book’s standing question — what does one token cost, where does the time go, and what may be moved without breaking the exactness contract? — now has a fourth clause in play: moved across the wire. Everything in Part VI is about paying for that clause honestly.
27.2 Two regimes, stated precisely
Before the wire, before the second machine, one question has to be settled precisely, because every argument in this Part leans on it: why should a machine be good at one half of inference and bad at the other, when both halves push the same weights through the same kernels? You have met the two regimes before, briefly (Ch 1 §1.2); here they are as the engineering facts the whole lane rests on.
Decode — generating tokens one at a time. Each token does one matvec against every weight matrix (Ch 13 is the hero example). One multiply-add per weight byte read; ~53 GFLOP of arithmetic against ~16.76 GB of reads; arithmetic intensity ~3.2 FLOPs per byte (derived in Ch 1 §1.3). Decode is bandwidth-bound, serial, and proportional to the number of generated tokens. You cannot make it faster by having more arithmetic units sitting idle.
Prefill — processing the whole prompt before the first token can be
emitted. Here the same weight matrices are multiplied by many rows at
once: the engine chunks prompts into 512-position batches
(PREFILL_BATCH_TOKENS = 512,
[crates/muser-engine/src/decode.rs:53]), and each weight byte read from
DRAM is reused across the whole chunk — the module doc says it plainly:
“prefill of T tokens ≈ one token’s DRAM traffic”
[crates/muser-engine/src/prefill.rs:6-8]. The arithmetic per byte
therefore scales with the batch. Prefill is compute-bound, parallel, and
proportional to the prompt length.
Same weights, same kernels’ worth of math, opposite bottleneck. That asymmetry is the entire subject of this chapter.
The roofline, with both workload points
Ch 1 put decode on one side of
the machine’s roofline — the “balance point” where a workload’s FLOPs-per-
byte exactly matches the machine’s FLOPs-per-byte of bandwidth. With the
~800 GB/s memory class [ledger L0] and the ~2.6 TFLOP/s of FP32 that keeps
ALUs busy at decode intensity (both derivations from
Ch 1 §1.3), the balance point
sits at ≈ 3.2 FLOPs/byte. Now put both regimes on the same chart:
arithmetic intensity (FLOPs per byte read), log scale
10^4 ┤ ● prefill, 512-chunk
│ │ ≈ 1,619 F/B
│ │ (derived below)
10^3 ┤
│
10^2 ┤
│ roofline: compute ceiling
│ ╱ (need ~1.3 PFLOP/s FP32 to feed a
│ ╱ 512-chunk at 800 GB/s — no Mac has that)
10 ┤ ╱
│ ╱ ← machine balance point ≈ 3.2 F/B
4 ┤ ─ ─ ─ ─ ─ ─ ─╱─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
│ ╱
1 ┤ ● decode ≈ 3.2 F/B
│ │ bandwidth-bound compute-bound
│ └ the entire token time is the weight read (intensity → right)
└─┴────────┴──────────────────────────────────────────────
1 B/token ~5 B/token ~1,000+ B/token
Figure 27.1: The roofline flip, ported from the Ferrite book’s Ch 23 device
[ferrite-book Ch 23] and recomputed for Muse Glimmer on the M3 Ultra. The
ancestor’s marked points were decode ~3.6 vs prefill ~455 FLOPs/byte at
batch 128 on an A18 Pro — Ferrite-lineage numbers that do not transfer; the
method does.
Derive the prefill point yourself, the way Ch 1 derived the decode one. Per token, the matmul parameter count is 26.5 × 10⁹ and per-token FLOPs ≈ 2 × 26.5e9 ≈ 53 GFLOP (the arithmetic in Ch 1 §1.3 step 4). A 512-token chunk therefore costs:
FLOPs per chunk ≈ 53.0e9 × 512 ≈ 2.714e13 FLOP
bytes read ≈ 16.757e9 (each weight byte read once, reused)
intensity ≈ 2.714e13 / 16.757e9 ≈ 1,619 FLOPs / byte
Roughly 500× further right than decode. On the compute side of the roofline, the only way to go faster is more arithmetic per second — or cheaper arithmetic per FLOP. A Mac’s FP32 units are what they are; a GPU with FP4 tensor cores (matrix-multiply units that consume 4-bit operands natively; see Ch 7 for the NVFP4 format) offers both. Hold that thought for two more sections.
The measured shape of the two regimes on one Mac
The roofline predicts the measured behavior. Local decode is 35.440 tok/s
(kquant, CV 0.037 %, its full scope in
Ch 1 §1.3) — 28.22 ms per token,
dominated by the weight stream. Local prefill per prompt token is much
cheaper — weight reads amortize across the chunk — but it is pinned against
the machine’s compute ceiling instead. Do the arithmetic with the deep
cell: 570.122 s mean for a 131,008-token prompt
[claims #6, local baseline] is
570.122 s / 131,008 tokens ≈ 4.35 ms per prompt token
53.0e9 FLOP/token / 4.35e-3 s ≈ 12.2 TFLOP/s sustained
Twelve TFLOP/s of sustained arithmetic on this machine’s FP32-class throughput — the compute-bound regime, hit at depth, on one Mac.
Be careful about what that number is, though, because we were. Nobody instrumented the machine’s arithmetic here. The 12.2 figure is what falls out of dividing the measured 570.122 s wall-clock cell by its token count and multiplying by a per-token FLOP estimate, so it is a derivation and we label it one rather than dressing it up as a compute measurement. We keep it because it does not need to be tight to carry the argument: the derivation would have to be wrong by two orders of magnitude before prefill stopped landing on the far side of the roofline from decode.
Now look at what the user experiences in that regime. Nothing. For nine and a half minutes, nothing. That is the TTFT cliff.
27.3 The TTFT cliff at depth
So put a number on that silence — and name it first, because the silence is the quantity this whole Part exists to shorten.
TTFT — time to first token — is the latency from “prompt submitted” to “first generated token visible.” At shallow depth on one Mac it is fine. At depth it is catastrophic, and it is catastrophic precisely because prefill is proportional to prompt length and compute-bound on silicon shaped for bandwidth-heavy decode.
We walked the cliff ourselves rather than model it: the local lane, five exact-token repetitions at every depth, the prompt growing by powers of two as we climbed. There is nothing subtle about the shape that came back.
| Depth | Local TTFT (mean) |
|---|---|
| 2,048 | 6.48 s |
| 8,192 | 26.77 s |
| 16,384 | 54.79 s |
| 32,768 | 114.31 s |
| 65,536 | 247.88 s |
| 131,008-class | 570.12 s |
Table 27.1: Local prefill TTFT by depth — linear growth in the prompt,
compute-bound on one Mac [docs/benchmarks.md §3].
Every row there is a retained run [docs/benchmarks.md §3]
[ledger "Phase 4 disaggregated GX10→Mac context matrix", 2026-08-20]. Read the bottom row as a person waiting rather than
as a table cell, and the engineering problem states itself. An agent
workload that refreshes a 100k-token context is not an edge case
of this table; it is the table’s whole reason to exist. This is the demand
side. The supply side is the next section’s question: what would it cost to
get that KV from somewhere else?
27.4 What the wire would have to carry
Here is the quiet fact that makes disaggregation plausible for this model specifically: Muse Glimmer’s KV is small. Ch 22 derived 1,024 bytes per token per layer (2 KV heads × head_dim 128 × 2 bytes × K+V). If all 52 layers shipped in full, that would be 52 KiB per token. But only the 13 NoPE full-attention layers (Ch 14) grow with context; the 39 SWA layers (Ch 23) live in a 2,048-token ring. What crosses the wire at depth is therefore:
NoPE: 13 layers × 1,024 B/token × position
SWA: 39 layers × 1,024 B/token × 2,048 tokens (the window, not the past)
At the 130,815-token cell that product is 1,823,184,896 B (≈ 1.82 GB).
That is not an estimate we are asking you to take on faith. It is what the
wire actually carried, and the per-class arithmetic above reconciles to it
byte for byte — a reconciliation Ch 22 §22.7
walks through in full. Getting the two sides to agree took two conventions
that are easy to trip over. The receiver holds back the boundary token, so
NoPE rows ship for prompt − 1, and the last of them is decoded locally; the
SWA rings travel as three 13-layer groups rather than as one block. Both
conventions are written down, and the client-side record of the run that
moved those bytes is retained:
[docs/kvpack-merge-handoff-20260820.md §3 D1], [receipt phase4-disagg-20260820/130815-g900091/out-p4/ f-p4-text-g900091-client.json].
Now hold the two costs of one token side by side, because that comparison
is the argument for this whole Part: ≈ 13.9 kB (decimal; 13.6 KiB) shipped
per token versus ~53 GFLOP recomputed per token. Shipping the answer is
absurdly cheaper than computing it again. Nor is that special pleading for
our model. The sealing plan’s external-research
section makes the same point against the literature: ~52 KiB/token effective
full-model footprint versus ~2.2 MB/token for DistServe’s OPT-66B — a 42×
smaller artifact class — and DéjàVu’s viability rule, that a transfer must
cost less than the recompute it replaces, holds here with room to spare
[docs/disaggregated-prefill-sealing-plan-20260818.md §4].
And the wire is fast relative to that payload. Our raw reference ceiling
was taken on the direct 10GbE link the lab ran before the migration:
~9.4 Gbps single-stream [ledger T0]
[docs/disaggregated-prefill-sealing-plan-20260818.md §W0]. Then, on
2026-08-23, we moved the fabric behind a switch — an improvement for
everything except our confidence in that ceiling, which was now a
measurement of a topology that no longer existed. So we re-probed instead of
assuming the number travelled with us. The product direction came back at
9.256 Gbps, close enough to call the class intact. The reverse direction
came back at 6.161 Gbps, which was not what we wanted and is retained as a
deviation anyway
[ledger GX10 return 2026-08-23, attempts 3–4 + readiness entries]. That
reflex — when the ground moves, re-measure, and keep the half of the result
you did not like — is the one habit that makes the rest of this Part’s
numbers worth anything.
Even at the release floor, the shape holds. That floor is a
3.0 Gbps installed-payload median
([crates/muser-cluster/src/lib.rs:14-15]), where installed payload means
the handoff’s payload bytes divided by the kernel’s measured send
busy-time — the wire clock Ch 31 defends.
Held to that rate, the transfer floors are ~224 ms
at 2k, ~1.06 s at 32k, ~3.75 s at 131k [docs/disaggregated-prefill-sealing-plan-20260818.md §4].
Chapter 31 owns the full wire-discipline story (pacing, EEE, why the
product rate sits below raw); the point here is the shape: the wire
cost is seconds; the local recompute cost is minutes.
So the economics write themselves — if you have a prefill machine whose compute is up to the job, and if moving the KV does not break the exactness contract. Everything after this section is about those two ifs.
27.5 The measured payoff
So much for the argument on paper. Paper is free; what decides this Part is whether the machines agreed with it.
Here is the lane we actually built and measured, as qualified: a resident vLLM NVFP4 producer on one GX10 node prefills the prompt and hands the KV to Mac Metal decode over authenticated Handoff V2. The next two chapters are the producer and the transport; the measurement is here.
The shallow, final-image cell — the one the claims register scopes carefully — at 2,048 prompt / 256 output tokens on the final image, with one uncounted warmup handoff then five counted reps:
1.493 s median TTFT, 0.22 % counted CV, ≥ 6.23 Gbps installed payload, deterministic output —
[claims #6], receiptnvfp4-pacing8g-20260818/p4-wrapper23/.
The counted-warmup convention is part of the claim (rep 0 is ~8 % hot from
CUDA warmup; the ruling that made it uncounted is
[docs/disaggregated-prefill-sealing-plan-20260818.md §7.3]). A post-router
re-qualification on the switched fabric reproduced the class: 1.535889499 s
median, CV 0.322 %, payload 6.4592–7.2065 Gbps [ledger "Post-router GX10 lane requalification"].
The deep cell — the headline — at 130,815 tokens, EEE-off arm, same night, same producer, same fixture, one warmup + five counted:
137.405 s median remote TTFT, CV 0.576 %, ≥ 6.995 Gbps per-rep payload floor, deterministic output, versus 570.122 s local 131,008-token mean: 4.149× —
[claims #6], receipts underkvpack-ladder-20260820/stage2-130815-rerun/.
Scope discipline, both sides, because the number is useless without it: the
remote side is a median over five counted reps at 130,815 tokens
with EEE disabled (the enrolled link invariant,
Ch 31); the local side is a mean at
131,008 tokens from the same claims row. The local baseline is 0.15 %
deeper than the remote cell, so the payoff is, if anything, understated
[ledger "EEE A/B at 130815"]. The earlier Phase-4 matrix — five reps per
depth, an earlier packet lineage — put the whole band at 3.75–4.26×
across 2,048 → 130,815 [docs/benchmarks.md §3]:
| Depth | Local TTFT | Remote TTFT | Payoff |
|---|---|---|---|
| 2,048 | 6.48 s | 1.520 s | 4.26× |
| 32,768 | 114.31 s | 30.489 s | 3.75× |
| 130,815 | 570.12 s | 137.405 s | 4.149× (EEE-off cell) |
Table 27.2: The disaggregated payoff band [docs/benchmarks.md §3]
[ledger "Phase 4 disaggregated GX10→Mac context matrix"]. Payoff here is
local ÷ remote TTFT — note this is the one ratio family in the book that is
not the llama ÷ muser convention.
Those are the cells that survived. Two earlier numbers did not, and the claims register requires that they travel with the survivors — so here they are, in the order we lost them.
The first we lost as a claim rather than as a fact. Running the integrated
lane cold, we got a 3.881 s disaggregated TTFT for a 2,048-token
prompt — 1.87 s of it native producer compute — against ~6.5 s of local
serving prefill, on a wire paced at 3.925 Gbps. We wanted that to be the
headline for the lane. Then we tried to move it into the claims register,
which asks of every number the one question this cell could not answer: what
was the spread across repetitions? There had been a single run. Nothing
about the packet is false, and it is retained; what it is not is a stability
claim, and the register files it as exactly that — a dated single-cell
packet from 2026-08-17, “operator-accepted engineering headline, not a
five-repetition stability claim”
[docs/nvfp4-fast-lane-evidence-20260817.md §Measured product numbers]. The
lesson cost us only a headline: one cold cell is an anecdote until it
repeats.
The second we lost outright. An early comparison set the exact Spark
producer against a 275 s Mac exact mirror and came out at 5.83× — a much
prettier ratio than the band above, and a different denominator than the one
the claims row scopes. It is retired, and must never be cited
[docs/nvfp4-fast-lane-evidence-20260817.md §Measured product numbers]
[claims #6]. We leave the retraction visible instead of quietly deleting
the figure, because a reader who meets that ratio on an old slide deserves
to know it was ours and that we withdrew it. Both packets remain
non-notarial; the release lock governs
what may be said publicly [docs/launch-claims.md §Ground rules].
And reuse — the Part V machinery — collapses the bill further on repeat
traffic: warm first token 0.6132 s at 65,536 and 1.0566 s at 130,815,
bit-identical text, no producer drive at all [claims #11]; delta handoff
54.2851 % of bytes [claims #12]. Disaggregation and reuse compose.
27.6 Why v0.1 is honestly ONE Mac + ONE producer
A payoff table invites a natural question: how big is the thing that produced it? Readers who have met other disaggregated systems will be picturing a pool of prefill workers and a scheduler in front of them. That is not what we have. Here is a place where the architecture document is blunt, and the book will not soften it:
“The v0.1 topology is one Mac decoder and one Spark/GX10 producer. … Multi-producer scheduling and node discovery are not implemented.”
[docs/muser-architecture.md §Durable and remote KV]
The claims register puts the launch-side form: “1× Mac + 1× GX10 today”;
scale-out is roadmap, and no wording may imply a multi-GX10 cluster is
running [claims #8]. The receiver admits one producer at a time — one
control endpoint, one HMAC key id, and a replay ledger keyed per key id
([crates/muser-cluster/src/lib.rs:9-12]). Onboarding a second node
registers it; it does not create a second concurrent producer
[docs/one-button-onboarding.md §v1 limits].
Why this is the right v0.1, not a cop-out: the roles are the architecture;
the placement is a technicality. The producer/consumer split is defined
between processes, not vendors or hosts — the handoff protocol, identity
binding, and kvpack state format hold for a colocated producer over loopback
just as for a remote one [docs/disaggregated-prefill.md §Roles, not machines]. What the single-remote-producer placement adds is exactly the
one thing this part of the book has to teach: tensor-core NVFP4 prefill and
unified-memory decode each win on different silicon
[docs/disaggregated-prefill.md §The idea]. Colocated producers remain
unqualified, not unarchitected [docs/release-todo-20260823.md §10].
The failure model follows from the topology and is worth stating as a design principle, because it decides what the producer is allowed to be:
The producer is a single point of failure for TTFT, never for correctness.
If the GX10 is down, the lane falls back to local Mac prefill — the engine
keeps a complete local path for exactly this reason
([crates/muser-engine/src/prefill.rs:10-12]: prefill is “the Mac-local
fallback path when GX10 disaggregated prefill (muser-cluster) isn’t
available”). Output tokens stay exact; TTFT degrades to Table 27.1’s column.
The claims register’s own boundary language: “one producer is a TTFT SPOF
with local-prefill fallback, never a correctness SPOF” [claims #13]. A
disaggregated system whose correctness depends on a remote node’s uptime
has failed at system design; Muser’s contract keeps every correctness gate
on the Mac side of the wire (that is Ch 32’s
subject in full).
27.7 The decision, as a tradeoff
Every chapter in this book owes a tradeoff with measured consequences. This one has three:
Why split at the prefill/decode boundary and not somewhere else?
Because that is where the roofline flips (Figure 27.1) — the two regimes
want opposite machines, and the artifact that crosses the boundary (KV, ~14
KiB/token at depth) is three orders of magnitude cheaper to move than the
work it saves (~53 GFLOP/token). The measured consequence is the 3.75–4.26×
band [docs/benchmarks.md §3]; the counterfactual is the 570.122 s local
cell [claims #6].
Why not just make local prefill faster? The obvious alternative — better
local batch kernels — attacks the compute-bound side on silicon whose
computing budget is already spent elsewhere. The measured consequence of
not having that option is visible in the six-depth plain matrix: local
prefill means 1.0139–1.0397× versus llama [claims #2] — competitive, not
transformative; no local lane turns 570 s into 137 s. The FP4-tensor-core
route exists on the GB10, not on the Mac
([docs/disaggregated-prefill-sealing-plan-20260818.md §4], the W4A4 /
FlashInfer CUTLASS notes — Ch 28
gets the details).
What does the split cost? A wire, with everything a wire brings: a
pacing story (Ch 31 — pacing is the
sender-side rate cap on its own sockets), a durability story (the
replay ledger’s fsync dance, Ch 30), a
security story (mTLS + HMAC, Ch 30), and a
precision story (trusting someone else’s prefill, Ch 32).
Every one of those costs is a chapter in this Part, and every one of them
earned its chapter by biting us during the campaign. We paced the sender
to be kind to the link, then spent effort explaining a rate we had imposed
ourselves: 3.9 of 9.4 Gbps was our own pin [ledger T1]. A latency tail we
would gladly have blamed on the network lived in our own commit path
instead, in the replay ledger’s fsync
([docs/disaggregated-prefill-sealing-plan-20260818.md §W1]). And
energy-efficient Ethernet’s retransmission blackouts proved to be a property
of our own burst schedule rather than of a sick link [ledger "EEE link ruling — operator decision (2026-08-20)"]. Read those three together and a
pattern falls out that is worth carrying into the next five chapters: the
wire was hardly ever the villain — our own defaults were. The lane survived
them because it fails closed, not because it was lucky.
27.8 What comes next
The argument is done: prefill is a throughput job that wants tensor cores and FP4; decode is a latency job that wants unified memory close to the user; Muse Glimmer’s KV is small enough to move; and the measured payoff at depth is a 4.149× TTFT reduction with deterministic output — under scopes this book will keep restating. To disaggregate you need a prefill machine. Ours is one ASUS GX10 — an NVIDIA GB10 — running a resident vLLM NVFP4 producer in a docker container, with a fail-closed culture all its own. That machine, its producer process, and its exit code 75 are the next chapter.
References
[crates/muser-engine/src/decode.rs:53]—PREFILL_BATCH_TOKENS = 512, the chunk size behind the prefill intensity arithmetic.[crates/muser-engine/src/prefill.rs:6-12]— “prefill of T tokens ≈ one token’s DRAM traffic”; the local-fallback role of the same driver.[crates/muser-cluster/src/lib.rs:9-22]— 1× Mac + 1× GX10 launch config, one-producer-at-a-time admission, 3.0 Gbps release floor.[docs/benchmarks.md]— §Methodology (repetition and floor conventions), §3 (the disaggregated payoff table and the EEE-off 130,815 row).[docs/muser-architecture.md §Durable and remote KV]— v0.1 topology, lane matrix, multi-producer-not-implemented.[docs/disaggregated-prefill.md]— roles-not-machines; the two-jobs argument; honest limitations (single producer, link dependence).[docs/disaggregated-prefill-sealing-plan-20260818.md]— §4 (KV-size math vs DistServe/DéjàVu, transfer floors, GB10 tensor-core notes), §W0 (raw 9.4 Gbps), §W1 (pacing and the fsync-tail lesson), §7.3 (the counted-warmup ruling).[docs/nvfp4-fast-lane-evidence-20260817.md §Measured product numbers]— the dated 3.881 s / 1.87 s / ~6.5 s integrated cell and the retired 5.83×.[docs/launch-claims.md]— #2 (local matrix), #6 (the disaggregated claims and their scope language), #8 (topology wording), #11/#12 (reuse and delta), #13 (SPOF boundary), §Ground rules.[ledger …]— “Phase 4 disaggregated GX10→Mac context matrix” (the payoff band), “EEE A/B at 130815” (the 137.405 s / 4.149× cell), T0/T1 (raw ceiling, pacing ladder), “Post-router GX10 lane requalification”.[receipt phase4-disagg-20260820/130815-g900091/out-p4/f-p4-text-g900091-client.json]—payload_bytes = 1,823,184,896, the deep wire payload.[docs/kvpack-merge-handoff-20260820.md §3 D1]— the payload reconciliation (NoPE + SWA arithmetic).[ferrite-book Ch 23]— the roofline-flip device this chapter ports; its A18 Pro points (~3.6 vs ~455 F/B at batch 128) are Ferrite-lineage and do not transfer to Muser measurements.- glossary — terms introduced this chapter: TTFT, disaggregated prefill, producer, consumer (receiver), tensor core, TTFT cliff, local-prefill fallback.
Chapter 28 — The GX10 node and vLLM NVFP4 prefill
status: polished · path: Muse Glimmer, pinned Muser tree
Prerequisites: Ch 7 (the NVFP4 format), Ch 27 (why a prefill machine at all). This chapter is about the machine at the other end of the wire: what it is, what runs on it, and how it fails.
28.1 Where we are
Ch 27 ended on the supply-side question: the payoff band is real, the KV payload is small, but only if you actually have a prefill machine whose arithmetic is up to a 130k-token prompt. This chapter is that machine. It is one node, it runs one pinned producer process inside one docker container, and it has a stricter operational culture than most servers you have owned — because Ch 27 §27.6 made it a TTFT single point of failure, and because everything it computes crosses a trust boundary (Ch 30, Ch 32).
Three questions organise everything that follows. What is the machine, and what wire does it hang on? What exactly runs on it, and how do we know it is still the same thing tomorrow? And — the long one — what does it do when something goes wrong?
28.2 The node and the wire it hangs on
Start with the physical facts, because two of them decide whether any number in this chapter is admissible at all: what the machine is, and what path a measurement travels over. Get the second one wrong and every throughput figure you collect is fiction, however honestly you collected it.
The producer is one ASUS GX10 — a DGX Spark-class machine built around
the NVIDIA GB10 package, with an aarch64 host and an NVIDIA driver
[docs/disaggregated-prefill.md §What you need]
[docs/one-button-onboarding.md §What "Add node" requires up front]. The
canonical SSH alias is producer-1. Its exact GPU microarchitecture details
beyond “GB10, Blackwell-generation FP4 tensor cores” are not recorded in
any Muser document this book quotes [unverified] — and the book does not
need them; every claim that matters is a measured property of the
producer process, not a spec-sheet property of the chip.
The topology, which the operator cheat sheet fixes exactly and Figure 28.1
draws ([AGENTS.md §The GX10 lane], mirrored in
[docs/gx10-return-runbook-2026-08.md §Constants]):
Mac (decode) GX10 (prefill)
┌───────────────────────────┐ ┌───────────────────────────┐
│ en0 Ethernet │ wired MikroTik │ enp1s0f0np0 │
│ 192.0.2.10 │◄────10GbE───────►│ 192.0.2.20 │
│ 10Gbase-T full duplex │ switched fabric │ (200 GbE port; link runs │
│ │ │ at the fabric's 10GbE) │
│ en1 Wi-Fi — NEVER a │ │ ssh alias: producer-1 │
│ measurement path │ │ docker: resident producer │
└───────────────────────────┘ └───────────────────────────┘
Figure 28.1: The enrolled transfer path since the 2026-08-23 topology
migration [AGENTS.md §The GX10 lane] [docs/gx10-return-runbook-2026-08.md §Correction]. Historical (pre-migration) numbers used a retired direct
retired /30 link [docs/disaggregated-prefill-sealing-plan-20260818.md §Operational topology amendment].
Two disciplines are welded to this picture, and they will recur all through Ch 31:
- Wi-Fi is not a measurement path. Mac Wi-Fi is
en1; “a measurement is invalid if it routes there”[scripts/gx10/README.md]. Verify direct same-subnet routes in both directions before probing anything. - Re-prove the raw ceiling before trusting it. A topology that was characterised yesterday is not a measured ceiling today.
The second rule earned itself during the migration. Before it, the direct
link had been characterised at ~9.4 Gbps single-stream each way
[ledger T0], and we expected the switched fabric to land just under that
in both directions — a switch is nominally direction-agnostic, so a
symmetric result was the boring prediction. It was not what came back. The
product direction measured 9.256 Gbps, close enough to the old reference to
be unremarkable; the reverse direction measured 6.161 Gbps. We had no root
cause, and the two honest options were to keep hunting or to write it down,
so we wrote it down: the asymmetry is retained as a deviation, not promoted
to a pass [ledger GX10 return 2026-08-23, attempts 3–4 + readiness entries]
[docs/gx10-return-runbook-2026-08.md §Execution annotation, attempt 4].
The lesson outlives this particular link. A fabric change invalidates the
ceiling in both directions until both directions have been re-measured,
which is why the discipline is written as a rule and not as a number.
The node also holds the lab’s accelerator lease file — the same path the
Mac side uses, /tmp/ferrite.gpu.lock
(NODE_GPU_LOCK = "/tmp/ferrite.gpu.lock",
[scripts/gx10/vllm/resident_producer.py:43]). The producer takes this
flock for its lifetime [scripts/gx10/vllm/resident_producer.py:39-57],
which is how a lab full of GPU-hungry processes avoids stampeding one
Spark. When you see “the resident holds the lease” in a health receipt,
this is the lock being described
[docs/gx10-return-runbook-2026-08.md §1.3].
28.3 The resident producer: one pinned identity chain
The question here sounds boring — what, exactly, is running over there? — and it is the question the whole lane rests on. The Mac will later refuse KV bytes that arrive from an identity it does not recognise, and for that refusal to mean anything, “the producer” has to name something far more precise than a hostname.
What runs on the GX10 is the resident pinned Muse Glimmer NVFP4 prefill
producer — a Python process living inside a docker container, running
vLLM with the NVFP4 checkpoint [scripts/gx10/vllm/resident_producer.py:2].
“Pinned” is doing real work in that sentence. The producer refuses to start
against anything but the qualified vLLM commit:
# scripts/gx10/vllm/resident_producer.py:21
PINNED_VLLM_COMMIT = "6adad08767583f52eb4d2122111af0bf638ed5e6"
# scripts/gx10/vllm/resident_producer.py:68-69
if config.get("vllm_commit") != PINNED_VLLM_COMMIT:
raise ValueError("producer config does not pin the qualified vLLM commit")
The full identity chain is frozen in the onboarding identity document
[scripts/gx10/vllm/native_onboarding_identity_v1.json]: the NVFP4
checkpoint (RedHatAI/Muse-Glimmer-30B-NVFP4, revision d5109a1…,
23,409,256,035 bytes, per-file SHA-256 map), the producer image
(muser/gx10-vllm-native:593b96a, image id
sha256:578888b2…), and the vLLM overlay adapter digest. The enrolled
resident container at the pinned tree is muser-redhat-native-f1-593b96a
with host work directory /home/<user>/.muser/lane/gx10/work/<deployment>
[docs/gx10-return-runbook-2026-08.md §Constants and evidence discipline].
One fact in that chain ties both ends of the lane together, and it is the
one worth carrying with you. The checkpoint’s chat_template.jinja is
exactly 7,167 bytes, with SHA-256 114f55eb…07965e. That is the same
template hash the Mac-side release test asserts against the kquant GGUF:
two artifacts, produced by two vendors’ toolchains through two unrelated
quantisation pipelines, agreeing on one template identity down to the byte.
Both halves of that assertion live in the tree, and we kept them there
deliberately — the Mac-side test at
[crates/muser-server/src/chat_template.rs:237-261], the frozen producer
identity at [scripts/gx10/vllm/native_onboarding_identity_v1.json]. The
receiver will not accept a handoff whose identities do not match to the
byte (Ch 30).
The process is just as unwilling to be vague about the model it serves. It
asserts the shape from its own constants — 39 RoPE modules, head size 128,
context length 131,072 [scripts/gx10/vllm/resident_producer.py:25-27] —
so a checkpoint that quietly changed geometry fails at startup rather than
at handoff time. Every generate call runs under a default watchdog of
900 s [scripts/gx10/vllm/resident_producer.py:24], and its vLLM KV-cache
allocation is bounded to 1–8 GiB by argument validation
(1 << 30 <= args.kv_cache_memory_bytes <= 8 << 30,
[scripts/gx10/vllm/resident_producer.py:582]).
Hold on to that last bound, because it is the easiest number in the lane to misread. The next time you meet a “~7 GB payload” figure for the deep cell, remember what it is describing: 7–8 GiB is the producer’s KV-cache allocation on its own GPU, not the wire payload. What actually crosses the wire for that cell is 1.82 GB, and Ch 27 §27.4 derived it.
One more process belongs in the answer to “what runs over there,” and the
line between the two is drawn on purpose. A small host-side daemon,
muser_native_prefilld.py, owns lifecycle and the authenticated control
channel, while “the vLLM image owns the GPU and Handoff V2 data plane” —
and the daemon’s own docstring states the boundary as a prohibition: “No
cache bytes cross the control connection”
[scripts/gx10/vllm/muser_native_prefilld.py:2-9]. Control authority in one
process, cache bytes in the other, and no path by which a control message
can quietly become a data path.
28.4 NVFP4 prefill on tensor cores
Ch 7 introduced NVFP4 as a weight format: E2M1
values, one E4M3FN scale per 16, a per-tensor f32 scale2. On the Mac it
feeds SIMD-group matvecs — memory-bound decode. The same numeric format on
the GB10 plays a different role: it is the working precision of batch
prefill on tensor cores, the matrix-multiply units that consume FP4
operands directly. The producer runs vLLM W4A4 (4-bit weights, 4-bit
activations) via the FlashInfer/CUTLASS kernels of the pinned stack
[docs/disaggregated-prefill-sealing-plan-20260818.md §4].
That is the Ch 27 roofline made silicon: the
512-chunk’s ~1,619 FLOPs/byte intensity is unreachable in FP32 on a Mac,
and trivially fed by dense FP4 matrix units. The measured consequence is
the entire payoff band of Table 27.2: the deep cell’s producer compute
finishes far inside the 137.405 s remote median [claims #6]. If you want
a feel for how far inside, one integrated cell at 2,048 tokens put native
producer compute at 1.87 s — a dated packet and a single cell, and it is
scoped exactly that narrowly where it lives
[docs/nvfp4-fast-lane-evidence-20260817.md §Measured product numbers].
One hardware honesty note the sealing plan carries, because Ch 32
needs it: on this GB10 generation the FP4 conversion path is emulated in
software (E2M1 round-to-nearest-even in code, not a hardware convert
instruction) — “a systematic producer-side rounding our drift band must
absorb” [docs/disaggregated-prefill-sealing-plan-20260818.md §4]. The
same format, on two machines, is not the same arithmetic. That is why the
lane’s trust chapter exists.
28.5 Exact and native: one process, two modes
Why would one process need two personalities? Because the fast lane and the trustworthy lane are not the same lane, and the engine needs both: one to ship the product numbers, one to check the shipping lane against something that cannot drift. The producer therefore has exactly two modes, selected by an environment variable at start:
# scripts/gx10/vllm/resident_producer.py:31-36
def producer_mode() -> str:
"""Return the closed producer lane selected for this process."""
value = os.environ.get("MUSER_NVFP4_EXACT", "0")
if value not in {"0", "1"}:
raise RuntimeError("MUSER_NVFP4_EXACT must be exactly 0 or 1")
return "exact" if value == "1" else "native"
native(the default,MUSER_NVFP4_EXACT=0): the tensor-core NVFP4 producer of §28.4 — the fast product lane.exact(MUSER_NVFP4_EXACT=1): the integer-dot verification producer, built on the llamacpp runtime (spark_kv_export[scripts/gx10/llamacpp/spark_kv_export.cpp:1-3]), which computes in integer/scalar arithmetic pinned to the CUDA compatibility graph. It is the deterministic anchor the native lane is checked against — Ch 32 is that story.
A subtlety the code map insists on and this book will not let you misread:
MUSER_NVFP4_EXACT does not exist in Rust. It is a producer-side
Python environment variable. What exists on the Mac is the receiver-side
record of which mode produced the KV — the enum in the cluster config:
#![allow(unused)]
fn main() {
// crates/muser-cluster/src/config.rs:13-18
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Nvfp4ProducerMode {
Exact,
Native,
}
}
It would be convenient if the two modes were interchangeable — take
whichever producer happens to be warm, take its KV, decode. They are not,
and the receiver is built to know it. Exact and native producers derive
different target-cache identities, so a cache filled by one is not even
addressable by the other
[docs/muser-architecture.md §Durable and remote KV]; neither mode may mix
KV with a differently-modeled decode lane; and a native-mode enrollment
cannot carry a DFlash identity at all. That last one
is not a warning in a doc, it is a hard config-validation error —
native producer mode cannot enroll DFlash context geometry
[crates/muser-cluster/src/config.rs:128-132].
The split polices itself on the producer side too. Run the native
benchmark with the exact flag set and it refuses to start, on the grounds
that setting it “would invalidate the native-path claim”
[scripts/gx10/vllm/benchmark_native_prefill.py:99-102]. That is a
benchmark guarding the meaning of its own result rather than the
convenience of whoever is running it — the same instinct as the fail-closed
exit in the next section, one layer up.
28.6 Fail-closed by construction: exit 75
Now the culture. The producer never serves degraded state. Any
engine-touched error — including something as mundane as the receiver not
listening yet — kills the process with exit status 75
[scripts/gx10/restart_resident_producer.py:5-8]. There are exactly three
death sites in the request loop:
# scripts/gx10/vllm/resident_producer.py:747-749
if worker.is_alive():
print("[muser-nvfp4-producer] watchdog fired", flush=True)
os._exit(75)
The first is the watchdog: the 900 s ceiling elapsed on a generate that never returned. The second is where the interesting decision lives:
# scripts/gx10/vllm/resident_producer.py:786-795
try:
connection.sendall((_canonical(response) + b"\n"))
finally:
# A connector/send failure can leave vLLM's synchronous V1
# engine request registered even though generate() raised.
# Reusing that engine produced a host-side busy loop with no
# GPU work. Fail closed after returning the error so an
# orchestrator can restart from the persistent compile cache.
if engine_touched and response["status"] != "ok":
os._exit(75)
That second site is the one with a story behind it, and the comment is the
post-mortem. The instinct when a send fails is to log it, drop the request,
and keep serving — the process is alive, the model is loaded, why throw
away a warmup that took minutes? Because the engine does not come back
clean. A connector or send failure can leave vLLM’s synchronous V1 engine
request registered even though generate() raised, and reusing that engine
produced “a host-side busy loop with no GPU work”: a producer that looked
healthy from the outside, held its port, burned a core, and computed
nothing. That is the worst possible state for a machine whose whole job is
to be trusted. The resolution is not a cleverer retry — it is to hand the
error back to the caller and then die, so an orchestrator can restart from
the persistent compile cache.
The third death site is quieter than the other two: three consecutive
non-engine errors also trip the exit
[scripts/gx10/vllm/resident_producer.py:796-797].
Why 75? It is the EX_TEMPFAIL convention — “temporary failure, distinguish me from a normal exit” — so a supervisor can tell “this producer died on purpose” apart from other process deaths. The design bet: a dead producer is a known, recoverable state; a live producer serving wrong bytes is not. The cost of the bet is that restarts are routine; the next two sections are the machinery that pays that cost.
28.7 Why docker restart is not enough — the ritual
Because the producer dies by design, bringing it back is a ritual, not a command. The tool’s own docstring is the specification:
# scripts/gx10/restart_resident_producer.py:2-12 (module docstring, excerpt)
"""Restart a resident GX10 vLLM producer and wait for real readiness.
The resident producer exits fail-closed (status 75) after an engine-touched
error — including a receiver that is not listening yet — and a bare
`docker restart` is NOT enough to bring it back: the startup receipt and the
RoPE cache are created with O_EXCL, and a stale producer.sock blocks the new
bind. This script performs the full restart ritual and then waits for the
fresh startup receipt, which only appears after the model is loaded and the
warmup has run.
"""
Three pieces of stale state, three different failure modes if you skip
them: an O_EXCL-created startup receipt that the new process cannot
recreate over the old one; an O_EXCL RoPE cache with the same problem; a
stale Unix socket file blocking the bind. The ritual, as code:
# scripts/gx10/restart_resident_producer.py:101-106
rows: list[tuple[str, Path, Path | None]] = [
("move-aside", rope_cache, rope_cache.with_name(f"{rope_cache.name}.stale-{stamp}")),
("move-aside", receipt, receipt.with_name(f"{receipt.name}.stale-{stamp}")),
("remove", sock, None),
]
Note what it does not do: nothing is deleted — stale artifacts are moved
aside with a timestamp (evidence discipline even in recovery). And before
any of it runs, the tool checks the accelerator lease and refuses to
restart a producer whose lease is still held, printing a fuser holder
hint rather than weakening the refusal
[scripts/gx10/restart_resident_producer.py:154-162]. After docker restart, it polls for the fresh startup receipt — “real readiness, not
process liveness” — because a running container that has not finished model
load (~2–3 min) is not a producer [scripts/gx10/restart_resident_producer.py:21-23].
The runbook makes this the only legal path: “For an exited resident with a
free lease, use the node copy of restart_resident_producer.py; never use
bare docker restart” [docs/gx10-return-runbook-2026-08.md §1.3]. The
operator cheat sheet agrees ([AGENTS.md §The GX10 lane]), and adds a
second discipline that comes from the same pinned-identity thinking — this
one learned the expensive way. Patching a file inside the container looks
like a solved problem: the repo has the corrected version, docker cp it
in, restart, done. We did exactly that with resident_producer.py,
expecting the container to behave like a checkout of the tree. It does not.
The image was built from an older commit, the muser_vllm package inside it
had drifted from HEAD in the meantime, and the copied file failed on
muser_vllm.native_capture. The lesson is the one this chapter keeps
teaching from new angles: a container here is not a working copy, it is a
pinned identity that happens to contain files, and editing it as if it were
a checkout silently unpins it. Hence the rule:
extract the file from the container, modify, verify, docker cp
back — never copy a file from repo HEAD wholesale into a container built
from an older commit — and both the rule and the drift that taught it to
us are written down where an operator will meet them
[AGENTS.md §The GX10 lane] [docs/disaggregated-prefill-sealing-plan-20260818.md §W1 finding 4].
28.8 The supervisor: recovery without flapping
A ritual needs a caller at 3 a.m. supervise_resident_producer.py is that
caller — “Keep it back: restart ritual + readiness wait in a loop, with a
failure latch” [scripts/gx10/README.md]:
# scripts/gx10/vllm/supervise_resident_producer.py:65-67
def decide(consecutive_failures: int, max_failures: int) -> str:
"""Latch off at the failure ceiling; otherwise restart."""
return "latch" if consecutive_failures >= max_failures else "restart"
The default ceiling is three consecutive failed starts
(--max-consecutive-failures N default 3,
[scripts/gx10/vllm/supervise_resident_producer.py:124]), with backoff
doubling per failure (time.sleep(backoff * (1 << (consecutive - 1))),
[scripts/gx10/vllm/supervise_resident_producer.py:113]). The latch is the
interesting part: a supervisor that restarts forever converts “producer is
broken” into “producer is flapping,” which is strictly worse — you get
half-loaded models racing watchdogs instead of one clear down state. When
the latch trips, the supervisor prints the container’s last log lines and
exits 1, leaving the failure legible to an operator
[scripts/gx10/vllm/supervise_resident_producer.py:110-112]. A successful
restart resets the counter [scripts/gx10/vllm/supervise_resident_producer.py:98-101].
What the operator sees when this machinery trips is precisely specified in
the runbook’s health checklist: the container Up, producer.sock present
as a Unix socket, LEASE HELD (exit 1 from the probe is expected — it
means the resident owns the lease), and exactly the intended supervisor
active and not latched [docs/gx10-return-runbook-2026-08.md §1.3].
28.9 What the evidence says about recovery
So the producer dies on purpose and a supervisor brings it back. Does that
actually work — and how far can the lane be pushed before it stops working?
The claims register keeps both answers, with their boundaries drawn
[claims #13].
Take the cheapest test first: kill the producer outright and watch. It “was
detected and recovered with no operator action in testing, resuming
bit-identical payloads” [docs/disaggregated-prefill.md §Operating it] —
recovery that restores not merely service but the same bytes, which is the
only kind of recovery this lane can use. A harder case came out of the
kvpack ladder, whose stage 4 swapped the resident producer under the lane
and then restored a healthy supervised resident; we kept the node state
captured after that stage
[receipt kvpack-ladder-20260820/attempt-13-…-stage4-naive/node-state-after-stage4.log].
Then we went after duration, because a lane that survives one kill can still
be terrible at staying up. The bounded deep soak ran eight consecutive
130,815-token handoffs, back to back. It passed on every axis we had
pre-registered: zero producer deaths, deterministic output, payload
throughput drifting 6.87→3.47 Gbps with every rep still at or above the
3.0 floor [ledger "eight-handoff deep soak", 2026-08-23]
[receipt final-campaign-20260823/attempt-4/soak/run-attempt-3/SOAK_VERDICT.json].
Reading that verdict, the temptation is obvious — the machinery works, call
sustained load solved.
Then, during the EEE-off sequence, a producer died on the ninth consecutive deep handoff.
We do not know what the ninth handoff hit. What we do know is that eight was
not the ceiling we had quietly credited ourselves with: the passing soak and
the death sit on either side of one boundary, and only the death told us the
boundary was there. So the register
says “Sustained-deep-load stability remains open” [claims #13] and forbids
claiming otherwise, and the answer is not to re-run the eight-handoff soak
until it looks better — it is to design a run that would have caught this
one. The return runbook pre-registers exactly that: a bounded soak at N=12,
chosen to cover the observed ninth-handoff boundary
[docs/gx10-return-runbook-2026-08.md §4].
That pairing is the fail-closed culture in miniature: a pass at N=8 and a death at N=9 are reported together, and neither is smoothed into “stable” or “unstable.”
28.10 Tradeoffs
Three decisions in this chapter had a live alternative on the table, and in each case the alternative was the more comfortable engineering choice. Here is what taking it would have cost.
- Fail-closed exit 75 vs serve-degraded. The alternative — catch the
error, keep the process, keep the port open — would hide the
half-registered-engine busy loop the comment at
[scripts/gx10/vllm/resident_producer.py:789-794]documents. Measured consequence: one tooling mistake (a refused receiver connection) is enough to kill the producer[docs/disaggregated-prefill-sealing-plan-20260818.md §W1 finding 3]— the lane accepted that fragility on the death side and bought back recovery with the supervisor. The economics work only because the producer is a TTFT SPOF, never a correctness SPOF (Ch 27 §27.6). - Supervised restart with a latch vs no supervision. The sealing plan
originally listed “no supervisor” as producer gap G3
[docs/disaggregated-prefill-sealing-plan-20260818.md §3]; the latch design answers its other half (flapping). The kill/recovery evidence above is the measured outcome. - A second producer as failover? Not in v0.1: one producer at a time is
a receiver-side admission property
(
[crates/muser-cluster/src/lib.rs:9-12]), and the claims register bars multi-node failover wording[claims #13]. The local-prefill fallback is the qualified answer to producer outage.
28.11 What comes next
You now have both machines: a Mac that decodes from unified memory with SIMD-group Metal kernels, and a GB10 that prefills NVFP4 on CUDA tensor cores and dies loudly on exit 75. They must agree, bit for bit at the seams, on what the KV means — and they were never designed to agree. The next chapter is the CUDA-versus-Metal divide: the differences that actually mattered to this engine, each one tied to a decision you can read in the Muser tree.
References
[AGENTS.md §The GX10 lane]— the operator cheat sheet:producer-1(192.0.2.20), Macen0192.0.2.10, Wi-Fien1never a measurement path, status-75 fail-closed, restart tooling, container-file edit discipline.[docs/gx10-return-runbook-2026-08.md]— enrolled lane constants (container, work dir, socket, lease), preflight §1.2–1.3, the bounded soak §4, and the 2026-08-23 topology correction.[scripts/gx10/README.md]— the five diagnostic tools and the bottom-up diagnostic flow.[scripts/gx10/restart_resident_producer.py]— the restart ritual: docstring (lines 2–38), the plan rows (101–106), the lease guard (154–162), readiness wait (170–182).[scripts/gx10/vllm/supervise_resident_producer.py]— the supervisor: docstring (2–32),decidelatch (65–67), backoff (113), defaults (124).[scripts/gx10/vllm/resident_producer.py]— pinned vLLM commit (21), model expectations (25–27), lease (39–57),producer_mode()(31–36), config pin check (68–69), KV-cache bounds (582), the three exit-75 sites (747–749, 786–795, 796–797).[scripts/gx10/vllm/muser_native_prefilld.py:2-9]— control-plane daemon scope (“no cache bytes cross the control connection”).[scripts/gx10/vllm/benchmark_native_prefill.py:99-102]— the native benchmark’s refusal ofMUSER_NVFP4_EXACT=1.[scripts/gx10/llamacpp/spark_kv_export.cpp:1-30]— the integer-exact llama.cpp KV export producer.[crates/muser-cluster/src/config.rs:13-18, 128-132]—Nvfp4ProducerModeand the native-mode/DFlash enrollment refusal.[scripts/gx10/vllm/native_onboarding_identity_v1.json]— the frozen producer identity chain (checkpointd5109a1…, image593b96a, the shared 7,167-byte chat template).[crates/muser-server/src/chat_template.rs:237-261]— the Mac-side assertion of the same template identity.[docs/disaggregated-prefill-sealing-plan-20260818.md]— §2 producer split, §3 gap G3, §4 (W4A4/CUTLASS, sm_121 software E2M1 RNE), §W1 findings 3–4.[docs/disaggregated-prefill.md]— operating characteristics and the kill/recovery statement.[docs/launch-claims.md]— #8 (topology), #13 (producer self-recovery and its boundaries).[ledger …]— “eight-handoff deep soak” (2026-08-23); attempt-4 readiness entries (post-rebuild TCP asymmetry).[receipt final-campaign-20260823/attempt-4/soak/run-attempt-3/SOAK_VERDICT.json]and[receipt kvpack-ladder-20260820/attempt-13-…-stage4-naive/node-state-after-stage4.log]— the soak and producer-swap evidence.- glossary — terms introduced this chapter: GB10/GX10, resident producer, producer mode (exact/native), exit 75, restart ritual, supervisor latch, O_EXCL startup receipt, accelerator lease.
Chapter 29 — CUDA versus Metal: the differences that mattered
status: polished · path: Muse Glimmer, pinned Muser tree
Prerequisites: Ch 2 (the Metal compute model), Ch 4 (the three kernel sources), Ch 7, Ch 27, Ch 28.
29.1 Where we are
Ch 28 left us with two machines
that must agree about bytes: a Mac that decodes with SIMD-group Metal
kernels out of unified memory, and a GB10 that prefills NVFP4 with CUDA
tensor cores inside vLLM. This chapter is about the divide between their
GPU programming models — but it is not a spec-sheet tour. The question
we kept asking, machine to machine, was never “which model is better.” It
was narrower and more useful: where does agreement actually have to
happen, and what does it cost us to get it? Every contrast below earned
its place by forcing a decision you can read in the Muser tree.
Where a fact is about the vendors’ models rather than Muser’s code,
it carries a vendor tag ([CUDA §…], [Metal-SS §…]) or [unverified];
where it is about Muser, it carries a file:line.
The one-line summary, up front: CUDA and Metal disagree most usefully at the seams this lane crosses — batch matrix math, the host/device memory boundary, execution-graph control, and floating-point compilation — and Muser answered each disagreement by pinning an interface rather than bridging a semantics gap in code. Table 29.1 lists all six disagreements and the decision each forced; the sections that follow take them in order.
29.2 The contrast table
Before the arguments, the map. Each row below is a place where the two programming models genuinely disagree, paired with the decision that disagreement forced on us. Read the right-hand column first if you read nothing else: in every row, without exception, the decision was to pin an interface rather than to write code that bridges one model to the other.
| # | CUDA (GB10 producer) | Metal (Mac consumer) | The Muser decision it forced |
|---|---|---|---|
| 1 | Warp: 32 threads in lockstep; branch divergence serializes [CUDA §thread-hierarchy] | SIMD group: 32 lanes; simd_* group ops [Metal-SS §simd-group-functions] | Ferrite-lineage kernels ported byte-for-byte; both models center a 32-lane lockstep unit (§29.3) |
| 2 | Tensor-core FP4 batch matmul (W4A4, producer prefill) [docs/disaggregated-prefill-sealing-plan-20260818.md §4] | SIMD-group matvec (decode) + an M16 batch route used only at 16-row shapes | Ask each silicon for what it is shaped to do — the Ch 27 roofline, enforced by lane (§29.4) |
| 3 | Host/device boundary: explicit D2H copy to pinned host memory (connector.py) | One shared address space (StorageModeShared, Ch 3) | The wire becomes the memory bus: pacing, streaming, and two chapters of wire discipline (§29.5) |
| 4 | Streams + events: side streams, async copies, event fences (connector.py) | One queue, record-then-commit command buffers Ch 2 | One accelerator owner (AcceleratorScheduler); concurrent-vs-serial dispatch is an explicit flag (§29.6) |
| 5 | (ggml kernels compiled for both) CUDA build of llama.cpp is the comparator’s home | Pinned llama.cpp metallib as Muser’s third kernel source Ch 4 | Bit-parity with the comparator beats re-expressing ggml kernels in native Metal (§29.7) |
| 6 | Determinism knobs (e.g. VLLM_BATCH_INVARIANT=1, producer self-consistency only) [docs/disaggregated-prefill-sealing-plan-20260818.md §4] | Two compiled libraries: fast-math serving + strict-f32 cross-vendor | Logit parity made Muser run both Metal build modes and pin the seam arithmetic (§29.8) |
Table 29.1: Six contrasts that survived contact with this codebase.
29.3 Contrast 1 — warps and SIMD groups: the shape that ported
Start with the contrast that turned out not to be much of a contrast at all. If the two shader languages were as alien to each other as their toolchains are, nothing in this codebase could have crossed between them without a rewrite. So ask the question the port had to answer first: what is the unit a kernel is organized around, and do the two vendors agree about it?
First definitions, vendor-side, once. A CUDA warp
is the unit of 32 consecutive threads that an NVIDIA SM executes in
lockstep; when threads of a warp take different branches, the hardware
serializes the paths — the divergence penalty [CUDA §thread-hierarchy].
A Metal SIMD group is Apple’s counterpart:
32 threads of a threadgroup acting in lockstep, with simd_sum,
simd_shuffle and friends as explicit group-wide operations
[Metal-SS §simd-group-functions] — Ch 2
introduced it as “the unit that actually matters on Apple Silicon.” The
microarchitectures beneath differ in ways this book does not need
[unverified]; what matters is that both models expose the same abstraction
shape: a 32-lane lockstep unit with cheap group reductions.
That shape is why Muser’s ferrite-lineage kernels ported cleanly. Look at the Q4_K matvec — four rows per threadgroup, two rows per SIMD group, one output element per lane:
// crates/muser-engine/src/shaders/muse_reference.metal:741-743
uint group [[threadgroup_position_in_grid]],
uint lane [[thread_index_in_simdgroup]],
uint simd [[simdgroup_index_in_threadgroup]]) {
and the closing reduction — one simd_sum per row, lane 0 writes:
// crates/muser-engine/src/shaders/muse_reference.metal:780-786
for (uint row_index = 0; row_index < active_rows; ++row_index) {
accumulator[row_index] = simd_sum(accumulator[row_index]);
}
if (lane == 0) {
for (uint row_index = 0; row_index < active_rows; ++row_index) {
output[base_row + row_index] = accumulator[row_index];
}
}
Nothing in this structure is Apple-specific as a structure: “one 32-lane
group owns one output row, reduce with a group op, lane 0 writes back” is
exactly how a warp-organized matvec is written on CUDA (__shfl_down_sync
reductions are the counterpart idiom [CUDA §thread-hierarchy]). The
provenance record makes the same point at file granularity: fifteen shader
files were pulled byte-for-byte from the Ferrite tree at a85048a90, and
three more with small adaptations [docs/extraction-manifest.md §Stage 2]
— a port this mechanical is only possible because the organizing unit
matched. The Mac’s own batch kernels go one step further and use the
SIMD-group matrix types — simdgroup_float8x8 mc[2] accumulating
matrix-multiply tiles in m16_q4k_n32
([crates/muser-engine/src/shaders/ferrite/batch_m16_n32.metal:86-88]) —
hardware matrix operations inside a SIMD-group programming model, which
foreshadows contrast 2.
29.4 Contrast 2 — tensor cores versus SIMD groups: the right silicon per regime
The producer’s prefill arithmetic is W4A4 dense matmul on tensor cores —
vLLM’s FP4 path (FlashInfer/CUTLASS kernels of the pinned stack)
[docs/disaggregated-prefill-sealing-plan-20260818.md §4]. The Mac’s
decode arithmetic is a SIMD-group matvec — muser_nvfp4_matvec_c{1..16}
for the native lane ([crates/muser-engine/src/shaders/nvfp4.metal:226]
onward), the pinned ggml matvecs for kquant (Ch 13).
Same numeric format on both sides (NVFP4, Ch 7);
radically different machines and purposes.
This is Ch 27’s roofline enforced as a lane policy rather than as a benchmark observation — and the honest way to tell it is to admit that we tried the other thing first.
Here is the fork. The Mac is not helpless at batch work: it has a batch
route, the m16_q4k_n32/m16_n32-family kernels, dispatched when a
16-token batch, the NVFP4 lane, and a 64-aligned input width all coincide.
For 16-row speculative-verify shapes that route is exactly the right tool,
and its existence is what made the tempting question tempting. If the Mac
can already multiply a matrix by a small block of rows, why ship prefill to
a second machine at all? Drop the wire, drop the box, keep the whole
inference in one address space. We expected to pay a penalty for that
convenience. We did not expect the shape of the bill.
The measurement closed the question. Native NVFP4 speculative decode — the
lane that lives on batched W4A4 target execution, and so the closest thing
to Mac-native batch GEMM we could put on a stopwatch — ran at 6.805
tok/s against the 107.9 tok/s kquant bar. Worse than the headline ratio
is where the time sat: the verify step alone consumed 35.915 s of a
37.619 s decode span. The batch work was not merely slower than the rest;
it was the clock. We kept the run that proved it
[docs/nvfp4-fast-lane-evidence-20260817.md] [ledger F-series remediation].
The lesson is narrower than “Metal is bad at GEMM,” which would be an
intuition rather than a finding. What the run actually says is that a
matvec-shaped machine, asked to do prefill-shaped arithmetic, spends nearly
all of its time in precisely the part of the workload the disaggregated
lane was invented to move elsewhere. So the engine does not try to make the
Mac a tensor-core machine. It sends batch work to the machine whose silicon
is that shape, and keeps the Mac on the matvec workload its wide SIMD
groups and unified memory are built for. The disaggregated lane is that
policy, wired — and the policy is code, not advice: the batch entry point
is encode_nvfp4_w4a4_prequant_m16
([crates/muser-engine/src/metal/encode/qkv.rs:13]), and the predicate
that decides when the Mac is allowed to take that route lives at
[crates/muser-engine/src/decode.rs:5946-5980].
29.5 Contrast 3 — the memory boundary: where the wire starts
The deepest difference between the two models is not in the kernels at all.
It is in what the word pointer is allowed to mean on each side, and if
you follow that single question far enough you arrive at the physical
picture the rest of this Part is built on. On the Mac, CPU and
GPU share one physical memory and one pointer space —
Ch 3; a StorageModeShared buffer is
visible to both without copies. CUDA’s programming model, whatever the
underlying packaging (the GB10’s own memory topology is not recorded in any
Muser document this book quotes [unverified]), retains an explicit
host/device boundary — and the producer’s connector code lives on that
boundary. Every KV layer the GPU computes must be gathered to pinned host
memory before TLS can ship it:
# scripts/gx10/vllm/muser_vllm/connector.py:249-261 (excerpt)
if self._copy_stream is None:
self._copy_stream = torch.cuda.Stream(device=pair.device)
current = torch.cuda.current_stream(device=pair.device)
self._copy_stream.wait_stream(current)
with torch.cuda.stream(self._copy_stream):
canonical_pair = pair.contiguous()
host_pair = torch.empty(
canonical_pair.shape, dtype=torch.float16, device="cpu", pin_memory=True
)
copied_ns = time.perf_counter_ns()
host_pair.copy_(canonical_pair, non_blocking=True)
ready = torch.cuda.Event()
ready.record(self._copy_stream)
That is the CUDA idiom in one quote: a dedicated stream for the copy, a
pinned-memory host tensor, a non-blocking device-to-host DMA, and an
event to fence it — plus the allocator subtlety documented right below
(canonical_pair.record_stream(self._copy_stream) keeps the device
allocation alive until the DMA has finished reading it
[scripts/gx10/vllm/muser_vllm/connector.py:262-266]).
On the Mac side, the mirror image: there is no gather, because there is no
boundary — the receiver’s KV planes are Metal buffers in shared memory
(Ch 15), and the sealing plan’s receive-side
design wraps the network destination in a Metal buffer directly
(makeBuffer(bytesNoCopy:…, .storageModeShared) — zero copies, zero GPU
work) [docs/disaggregated-prefill-sealing-plan-20260818.md §4 "Apple-side install"].
Put the two together and you get this Part’s central physical picture:
Between a CUDA address space and a Metal address space, the network is the memory bus.
Everything that follows from that — the pacing pin, the streaming schedule
that overlaps CUDA prefill with TLS sends, the EEE blackouts, the fsync tail
— is the cost of doing a “memory copy” across a wire. That is why
Ch 31 exists, and why the connector’s
streaming seam (enqueue each intent whose layers exist, mid-prefill;
[scripts/gx10/vllm/muser_vllm/connector.py:274-281]) is designed exactly
like a DMA engine hiding behind compute.
29.6 Contrast 4 — streams and graphs versus one queue and one owner
Both models have to answer the same question — how do you keep an accelerator busy when the work has dependencies? — and they answer it at very different altitudes. Knowing which altitude you are standing on decides who owns the ordering, and therefore who is to blame when the ordering is wrong.
CUDA exposes concurrency as a first-class graph: multiple streams, each
an ordered queue; events to cross-synchronize; and CUDA graphs to
capture and replay whole dependency DAGs [CUDA §streams]. The connector
uses the small version of this — a forward thread, a sender thread, a copy
stream, and events (§29.5) — because the producer must overlap three
resources at once: tensor cores, PCIe-class D2H, and the NIC.
Metal’s model is sparser and stricter: you record work into command
buffers and commit them; one MTLCommandQueue serializes
(Ch 2). Muser’s answer to “how do I get
concurrency?” is not more queues — it is one scheduler owning one
accelerator:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/decode.rs:1020-1026
/// One owner for the shared Metal queue. Decode work is selected first and
/// resident sequence IDs rotate in ascending cyclic order, preventing a hot
/// slot from repeatedly reacquiring the accelerator ahead of its peers.
struct AcceleratorScheduler {
state: Mutex<AcceleratorSchedulerState>,
ready: Condvar,
}
}
Concurrency inside a submitted graph is expressed with a concurrent
compute encoder plus explicit resource barriers — the packed decode group
encodes 1..=4 sequences with “one concurrent encoder + one commit + one
wait” ([crates/muser-engine/src/decode.rs:4920-4937], walk in
Ch 34) — and the encoder type itself is the
one place Muser kept an escape hatch back toward CUDA-style serial
ordering: MUSER_SERIAL_PREFILL_DISPATCH selects a serial (non-concurrent)
prefill encoder (concurrent_prefill_dispatch defaults to true;
[crates/muser-engine/src/decode.rs:1331-1333]).
Why no CUDA-graph analogue on the Mac? Because the problem CUDA graphs solve
— cheap re-submission of a fixed DAG — is solved differently here: the
52-layer graph is encoded directly by hand-written Rust (encode_token,
encode_decode_group) rather than captured, and ordering hazards are
managed by the single-queue owner plus tracked buffers and targeted barriers
(the full hazard story is Ch 35).
Worth one detour, because it is the kind of absence a reader can mistake
for an omission: there is a third design in this space, the ancestor
Ferrite book’s compiled-VM replay, and Muser kept neither it nor graph
capture. That matters for the handoff story because it means the consumer’s
execution order is authored in plain Rust that anyone auditing the seam can
read top to bottom — no captured graph, no replayed bytecode standing
between the source and what the GPU does. The divergence is documented
lineage, not an oversight
[ferrite-book Ch 21] (KEEP-AS-LINEAGE in the port audit).
29.7 Contrast 5 — when NOT to re-express a kernel: the pinned metallib
When is the right amount of kernel code to write none? Here, and the reasoning is worth slowing down for, because it inverts the instinct that owning the source of everything you dispatch is always the safer engineering.
The lane needs the Mac’s Q/K/V/gate/o projections to match what the comparator (llama.cpp) computes — and later, what the producer computed (Ch 32). Muser could have re-expressed llama.cpp’s ggml Metal kernels natively. It did the opposite: it pins llama.cpp’s own prebuilt metallib as the engine’s third kernel source (Ch 4):
#![allow(unused)]
fn main() {
// crates/muser-engine/src/metal/context.rs:122-131 (excerpt)
let ggml_library_path = std::env::var_os("MUSER_GGML_METALLIB").map(PathBuf::from);
let ggml_library = match ggml_library_path.as_ref() {
Some(path) => Some(device.new_library_with_file(path).map_err(|message| {
MetalError::GgmlLibrary { path: path.clone(), message }
})?),
None => None,
};
}
The bet: bit-parity with the comparator beats any performance you could
buy by rewriting. The serving graph dispatches the pinned
kernel_mul_mv_q*_K_f32 matvecs and the pinned flash_attn_ext family
(the route tables in Ch 13 and
Ch 16); a native re-expression would
re-litigate every accumulation order in those kernels for a gain the
dispatch-gap accounting says is not there (Ch 35).
A pin you cannot verify at runtime is not a pin; it is a wish. The
discipline that makes this one honest is fingerprinting, and we
inherited it as a scar rather than as a principle: in the ancestor project,
a metallib that simply failed to load fell back silently, and a benchmark
spent an afternoon carefully timing the wrong kernel family
([ferrite-book Ch 23 §23.7], lineage). Nothing errored. The numbers
looked plausible. That is the failure mode a pin invites — it moves the
identity of your kernels out of your build and into your environment, where
a missing file becomes a quiet substitution instead of a crash. So Muser
bakes the check into every route identity: route_identity reads the
metallib bytes and hashes them into the record —
#![allow(unused)]
fn main() {
// crates/muser-bench/src/main.rs:329-333, 341-346 (excerpt)
let bytes = std::fs::read(&path).map_err(|error| {
format!(
"cannot fingerprint GGML metallib {}: {error}",
path.display()
)
})?;
let digest = Sha256::digest(bytes);
// …
matvec_route: "llama-ggml-metallib",
ggml_metallib_sha256: Some(format!("sha256:{digest:x}")),
}
— so no measurement can silently report a kernel family it did not run
(compare the same discipline for the server-side receipt at
MUSER_GGML_METALLIB_RECEIPT,
[crates/muser-server/src/node/mod.rs:83]). The same thinking governs the
producer side of the lane: pinned vLLM commit, digest-pinned image
(Ch 28 §28.3). When the two
vendors’ stacks must agree, you do not bridge them; you pin both ends
and hash the bridge.
29.8 Contrast 6 — compiler discipline: fast math, strict f32, and running BOTH
The last contrast is the quietest and the most expensive. CUDA compilers
offer determinism knobs (and vLLM a VLLM_BATCH_INVARIANT=1 mode) that
give the producer self-consistency only — “cross-vendor parity remains
our own pinned-op-order route plus calibrated drift bands”
[docs/disaggregated-prefill-sealing-plan-20260818.md §4]. Metal’s
compiler has its own switch that matters just as much: fast math.
Muser compiles its kernels twice, from the same sources, under different flags (Ch 4):
#![allow(unused)]
fn main() {
// crates/muser-engine/src/metal/context.rs:36-39
/// Strict-f32 copy of the standalone Muse kernels. The cross-vendor
/// Q8 projection and integer NVFP4 routes must match CUDA's explicit
/// scalar boundaries, while the ordinary serving kernels retain fast math.
pub cross_vendor_library: Library,
}
The main library compiles with set_fast_math_enabled(true), and the tree
records the reason in place: disabling it “materially slows attention, FFN,
and the norm/tiny-op stack without changing the imported GGML PSOs”
([crates/muser-engine/src/metal/context.rs:49-53]). The strict-f32
cross_vendor_library then recompiles exactly the same two shader files,
muse_reference.metal and nvfp4.metal, with fast math off. Say that
again slowly, because it is the whole trick: the source does not change at
all. What changes is the license the compiler has to reassociate
floating-point arithmetic — and that license is the difference between a
result that matches CUDA and one that merely rounds to it.
A single flag, MUSER_CROSS_VENDOR_QK, routes QK-norm and attention
through the strict build, so that the Mac “must derive Q/K exactly the way
the producer did, or the KV is foreign by construction”
[docs/disaggregated-prefill.md §What you need]. The flag is not advisory:
serving refuses to start on the remote lane without it. We kept the trail —
the strict recompile at
[crates/muser-engine/src/metal/context.rs:111-121], the routing sites at
[crates/muser-engine/src/decode.rs:5645] and
[crates/muser-engine/src/metal/encode/norm.rs:115-116], and the
onboarding rule at
[docs/one-button-onboarding.md §Starting the production consumer].
Why pay for two builds of the same source? Because logit parity across the handoff demanded it — and we learned that over the campaign we came to call the wizard’s arithmetic-ABI chase. It is worth telling slowly, because it is the story that turned a compiler flag into a protocol.
The setup: the consumer has to derive Q and K exactly as the producer did, or the KV planes arriving over the wire describe a subtly different model than the one the Mac is decoding. Our plan was the obvious one — compile the seam kernels strictly, check the logits, declare victory. Attempts 10–30 of the combined-lane onboarding said otherwise. They kept failing on single-bit seam divergences: not garbage, not a crash, one bit in one element. That is the most exhausting failure mode there is, because everything looks right.
So we stopped guessing and built a ladder. Comparing element by element
from layer-0 forward, the first mismatch appeared at attn_norm-0 element
4 and propagated into K RoPE element 256 before surfacing downstream at
attn_out-0 element 4,096. Two arithmetic-ABI splits fell out of that
trace: “CUDA’s serial 128-dim attention reduction vs Metal’s 32-lane tree,
and F32 vs F16 residual materialization” [ledger §2b, 2026-08-24]. Neither is a bug in anybody’s compiler. Both are legal,
defensible choices that two teams made independently — which is exactly why
no tolerance band would have found them for us; a tolerance would have
hidden them.
The fix was to stop treating the arithmetic as an implementation detail and
write it down as an interface: a versioned cross-vendor arithmetic ABI,
commits 27b5790/80f294f, at a cost of 4–7 accelerator-hours of rework
[ledger §2b, 2026-08-24]. Attempt 31 then passed 7/7 with exact
full logits and payload rates 9.812/8.887/8.690 Gbps [claims #9].
The lesson deserves saying twice, in two different registers. Put mechanically: the compiler is part of the ABI, so a build flag that reorders a reduction is as much a protocol change as renaming a field on the wire. Put another way: two machines do not agree because they were handed the same source. They agree because somebody pinned the order in which the source is permitted to add things up, and then refused to paper over the remainder with a tolerance.
Notice what this contrast did not become: a search for bitwise CUDA↔Metal
equality everywhere. Nobody achieves that — “that matches the state of
practice (nobody achieves bitwise CUDA↔Metal; llama.cpp uses tolerance-based
backend diffs)” [docs/disaggregated-prefill-sealing-plan-20260818.md §4].
The discipline is surgical: pin the seam arithmetic exactly (strict f32,
pinned op order), bound the interior drift with calibrated bands, and let
Ch 32 carry the boundary between
them.
29.9 Tradeoffs
Three roads we did not take. Each was a genuine option rather than a straw man, and none of them died against taste — each died against something measured, and the measurement is what the reader should walk away with.
- Re-express ggml kernels natively (rejected). The case for it was
respectable: owning the source of every kernel you dispatch is a sane
default, and a native rewrite could in principle be tuned to Muser’s own
dispatch shapes rather than to llama.cpp’s. What stopped us was not a
benchmark but a correctness precedent from inside our own tree. When
Muser fused a pair of adjacent norm ops — its own kernels, its own
accumulation order, a change we were confident about — the fused path
breached the 1e-4 logprob contract, coming in at 3.197e-4, and was
rejected
[docs/decode-dispatch-gap-20260815.md §Rejected hybrid postmortem]. Generalize from that, and the argument makes itself: if rearranging kernels we wrote can move the last digits, re-expressing kernels a foreign project wrote multiplies exactly that risk class, once per kernel, across the whole projection stack. So the pin stayed, and the measured consequence was a good one — llama’s own bytes became the parity gate (the J0 anchor flip, Ch 38), and when we wanted llama.cpp’s one-reduction attention DAG (flash_attn_ext_vec) we adopted it as a pinned kernel rather than imitating it[ledger Stage A close-out]. - One strict seam library vs strict everywhere (chosen: seam only).
The tidy answer to the chase above would be to switch fast math off across
the whole engine and never think about associativity again. The tree
records why we did not: strict-f32 everywhere means materially slower
attention/FFN/norm “without changing the imported GGML PSOs”
[crates/muser-engine/src/metal/context.rs:49-53]— you would pay over the entire serving graph and buy nothing on the imported kernels that dominate it. The win is confined to the routes that must match CUDA scalar boundaries, so that is the only place strictness lives (context.rs:36-39). Drawing that line has a price when you draw it wrong, and we have the invoice: the wizard chase (§29.8) is what one f32-vs-f16 materialization on the wrong side of the seam cost. - Mac-native batch GEMM for prefill-scale work (rejected). Told as a
story earlier in the chapter; here is the receipt on its own line. Native
spec decode reached 6.805 tok/s against the 107.9 bar, with 35.915 s of
verify inside a 37.619 s span
[docs/nvfp4-fast-lane-evidence-20260817.md]. That is the measured no-go that keeps batch work on the producer and matvec work on the Mac — a roofline argument that ended up encoded in a dispatch predicate rather than in a paragraph of advice.
29.10 What comes next
Strip the vendors away and this chapter was about software contracts: pinned kernels with hashed identities, pinned commits, pinned op order at the seam, two compiled libraries where one language would do if parity did not matter. But contracts need a channel that cannot be forged or replayed. The two machines in this lane talk to each other over a network, and everything you just read about pinning identity is only as strong as the transport that carries it. The next chapter is Handoff V2: mutually authenticated TLS, an HMAC-sealed manifest, a durable replay ledger, and the one-button wizard that proves a stranger’s GX10 in three handoffs.
References
[crates/muser-engine/src/shaders/muse_reference.metal:735-788]—muser_matvec_q4k_4r2s: the SIMD-group matvec withsimd_sumreduction.[crates/muser-engine/src/shaders/ferrite/batch_m16_n32.metal:59-88]—m16_q4k_n32: SIMD-group matrix types (simdgroup_float8x8) in the 16-row batch kernel.[crates/muser-engine/src/shaders/nvfp4.metal:226+]— themuser_nvfp4_matvec_c*native-lane decode family.[crates/muser-engine/src/metal/encode/qkv.rs:13],[crates/muser-engine/src/decode.rs:5946-5980]— the M16 NVFP4 batch route and its dispatch predicate.[crates/muser-engine/src/metal/context.rs:36-39, 49-53, 111-131]— the two compiled libraries, the fast-math justification comment, the pinned metallib load.[crates/muser-engine/src/decode.rs:1020-1026, 1331-1333, 4920-4937, 5645]— the one-queue owner, the serial-dispatch flag, the packed decode group’s single commit, the cross-vendor route.[crates/muser-engine/src/metal/encode/norm.rs:115-116],[crates/muser-server/src/node/mod.rs:83]— cross-vendor norm routing; the metallib provenance receipt.[crates/muser-bench/src/main.rs:305-346]—route_identity: the metallib SHA-256 fingerprint baked into every route identity.[scripts/gx10/vllm/muser_vllm/connector.py:184-285]— the CUDA side of contrasts 3–4: sender thread (214), copy stream and pinned-host D2H (249–261),record_stream(262–266), streaming seam (274–281).[docs/disaggregated-prefill-sealing-plan-20260818.md]— §4 (W4A4 FlashInfer/CUTLASS,VLLM_BATCH_INVARIANTlimits, “nobody achieves bitwise CUDA↔Metal”, Apple-side install design), §2 (connector streaming status).[docs/disaggregated-prefill.md §What you need]— theMUSER_CROSS_VENDOR_QK=1requirement in plain words.[docs/one-button-onboarding.md §Starting the production consumer]— serving refuses the remote lane without the cross-vendor flag.[docs/nvfp4-fast-lane-evidence-20260817.md]— the 6.805 tok/s native spec no-go.[docs/decode-dispatch-gap-20260815.md §Rejected hybrid postmortem]— the 3.197e-4 breach that prices re-expression risk.[docs/launch-claims.md]— #9 (attempt-31 exactness and wire rates).[ledger §2b, 2026-08-24]— the arithmetic-ABI chase, attempts 10–31, fixes27b5790/80f294f.[docs/extraction-manifest.md §Stage 2]— the fifteen byte-for-byte shader pulls behind contrast 1.[CUDA §thread-hierarchy],[CUDA §streams]— NVIDIA CUDA C++ Programming Guide (SIMT/warps; streams and events), vendor context only.[Metal-SS §simd-group-functions]— Metal Shading Language Specification (simd-group operations), vendor context only.[ferrite-book Ch 21],[ferrite-book Ch 23 §23.7]— lineage: the compiled-VM replay Muser did not keep; the silent-metallib-fallback lesson behind fingerprinting (ancestor context).- glossary — terms introduced this chapter: warp, divergence penalty, tensor core (cross-ref Ch 7), metallib pin, fast-math library, strict-f32 cross-vendor library, arithmetic ABI, D2H gather, record-then-commit.
Chapter 30 — Handoff V2: the authenticated transport
status: polished · path: Muse Glimmer, pinned Muser tree
Prerequisites: Ch 24 (the kvpack container and manifest), Ch 27, Ch 28, Ch 29.
30.1 Where we are
Ch 29 ended with the observation that pinning identity — kernels, commits, op order — is only as strong as the channel that carries the result. This chapter is that channel. Handoff V2 is the protocol that moves a prefilled KV cache from the GX10 producer to the Mac receiver: mutually authenticated TLS, an HMAC-sealed manifest, a durable replay ledger, and an atomic engine-side install. Its one-sentence specification lives at the top of the cluster crate:
“CUDA prefill -> authenticated Handoff V2 tiles -> Metal scatter-on-arrival -> atomic commit -> Metal decode.”
[crates/muser-cluster/src/lib.rs:5-7]
Two terms before they are used. mTLS (mutual TLS) is Transport Layer Security with both sides presenting certificates — each machine proves its identity to the other, not just the client to the server. HMAC (keyed hash-based message authentication code) is a cryptographic tag computed over a message with a shared secret key — anyone holding the key can compute it, nobody without the key can forge it.
30.2 The threat model: why two keys and not one
What must be true when 1.8 GB of KV lands on the Mac (Ch 27 §27.4 derived the payload)?
- It came from the enrolled producer, not an impostor with a valid certificate from some other CA. → mTLS with pinned leaves.
- The bytes are exactly what the producer sealed — not truncated, spliced, or tampered in flight. → HMAC over the canonical manifest core, plus per-segment SHA-256.
- It is not a replay — a captured, perfectly valid, correctly signed handoff being re-sent to roll the engine’s KV back to stale state. → the durable replay ledger.
Note the ordering discipline in that list: HMAC and mTLS prove who and
what; only the ledger proves when — “HMAC and mTLS prove the message
came from a key/cert this receiver trusts, but only the ledger proves the
message isn’t a valid, correctly-signed replay of one already installed”
[docs/one-button-onboarding.md §Why the generation ledger must never reset]. The architecture document summarizes the whole design in three
sentences:
“GX10 Handoff V2 uses mutually authenticated TLS plus an HMAC-sealed manifest. Enrollment generates each TLS private key on the machine where it remains; the HMAC is a shared secret transferred over known-host-verified SSH. Replay admission durably reserves the generation with file and directory fsync before target+DFlash publication and ACK.”
[docs/muser-architecture.md §Durable and remote KV]
The whole transaction, end to end (Figure 30.1):
flowchart TD
M[Mac receiver: slot misses locally] -->|1. control request<br/>ALPN muser-prefill-control-v1| P[muser_native_prefilld on GX10]
P -->|2. one closed token request| R[resident vLLM producer]
R -->|3. connect back over mTLS<br/>ALPN muser-kvpack-v2| M
R -->|4. Begin manifest| M
R -->|5. Segment tiles, streamed during prefill| M
R -->|6. Seal manifest HMAC| M
M -->|7. verify seal, prepare install<br/>into detached Metal generation| M2[(detached generation)]
M -->|8. durable ledger reserve<br/>write+fsync+rename+dir-fsync| L[(replay ledger, internal disk)]
M -->|9. atomic commit of live KV| M3[(live decode state)]
M -->|10. ACK| R
Figure 30.1: The Handoff V2 transaction. Cache bytes never ride the control channel (step 1); they flow only on the mutually authenticated data connection (steps 4–6). Nothing live is touched before the seal verifies (step 7) and the generation is durably reserved (step 8).
30.3 The mTLS layer: TLS 1.3, exact ALPN, pinned leaves
Take the threat-model questions in order. The first one is who: when a socket opens, how does the receiver know the machine on the far end is the producer it enrolled, and not something else that happens to hold a certificate? The security module answers with three non-negotiable properties, built symmetrically into both sides of the connection.
First, TLS 1.3 only. The client config is built
with_protocol_versions(&[&rustls::version::TLS13]), and the server side
likewise, so there is no older version on offer for a downgrade to fall
back to ([crates/muser-cluster/src/security.rs:122-124],
[crates/muser-cluster/src/security.rs:172-174]). Second, an
exact ALPN string — Application-Layer Protocol Negotiation, the
protocol name agreed inside the TLS handshake:
#![allow(unused)]
fn main() {
// crates/muser-cluster/src/security.rs:21
pub const MUSER_HANDOFF_ALPN: &[u8] = b"muser-kvpack-v2";
}
Third — and this is the load-bearing one — leaf pinning on top of chain validation. A certificate that chains to your CA is not enough; the receiver additionally demands the SHA-256 of the exact leaf certificate it was enrolled with:
#![allow(unused)]
fn main() {
// crates/muser-cluster/src/security.rs:196-213
fn verify_connection(
alpn: Option<&[u8]>,
peer: Option<&[CertificateDer<'_>]>,
pins: &BTreeSet<String>,
expected_alpn: &[u8],
) -> Result<(), SecurityError> {
if alpn != Some(expected_alpn) {
return Err(SecurityError::Alpn);
}
let leaf = peer
.and_then(|chain| chain.first())
.ok_or_else(|| SecurityError::Config("peer certificate chain is empty".into()))?;
let digest = format!("{:x}", Sha256::digest(leaf.as_ref()));
if !pins.contains(&digest) {
return Err(SecurityError::LeafPin);
}
Ok(())
}
}
Why pins, when CA trust already worked? Because “enroll records the pin for
the specific certificate it just issued, not a CA that could later sign a
different one” [docs/one-button-onboarding.md §Security model] — a
valid-but-unpinned certificate is rejected exactly like an invalid one.
The key-management rule is the quiet half of the design: TLS private keys
never leave the machine that generated them. Enrollment generates the
node’s key on the node (inside a versioned 0700 staging directory),
retrieves and verifies only its CSR, and returns the signed public
certificate; “the node TLS private key never leaves GX10”
[docs/one-button-onboarding.md §4 enroll]. Even the file contract is
fail-closed: the key loader rejects symlinks, group/other-readable modes,
and files that change while being opened, closing the
validate-then-reopen race [crates/muser-cluster/src/security.rs:289-335].
One more channel, and it is worth a paragraph here rather than later,
because it is what keeps the data path the only path bytes travel: the
control plane. The Mac asks the resident producer daemon to prefill one
exact token sequence over its own mTLS connection, with its own ALPN
muser-prefill-control-v1 ([crates/muser-cluster/src/control.rs:10]).
The rule attached to that channel is absolute — “cache bytes never ride
this channel; the daemon connects back over Handoff V2”
[crates/muser-cluster/src/control.rs:1-3]. Say it the other way round:
control is small canonical JSON with closed bounds, and every byte of cache
arrives on a connection the producer opened back to the receiver, after
proving itself again. Ask for the work on one wire; receive the artifact on
another. That second wire is the subject of the next section.
30.4 The HMAC-sealed manifest
mTLS answers who is on the far end of this socket, and that answer dies with the socket. The KV cache does not: it gets installed into an engine, re-checked against a delta witness, quoted in receipts, kept as evidence. So the second threat-model question is about the artifact rather than the channel — what proves these bytes are the ones the producer sealed, long after the connection that carried them is gone?
Inside the mTLS stream, then, the payload itself is framed as a
transaction (the wire format is §30.5). The transaction’s anchor is the
begin manifest:
a typed structure carrying the transfer id, a generation number, the
exact model identity digests, the full prompt token ids, the component
list, and the segment descriptors
(BeginManifestV2, [third_party/kvpack/crates/kvpack-handoff/src/handoff_v2.rs:72-91]).
The terminal frame is the seal: a core of digests plus an HMAC tag over
that core:
#![allow(unused)]
fn main() {
// third_party/kvpack/crates/kvpack-handoff/src/handoff_v2.rs:273-286
let core = SealCoreV2 {
transfer_id: begin.manifest.transfer_id.clone(),
generation: begin.manifest.generation,
begin_sha256: sha256_hex(begin.canonical_bytes()),
descriptor_sha256: hex::encode(descriptor_hash.finalize()),
payload_sha256: hex::encode(payload_hash.finalize()),
segment_count: descriptors
.len()
.try_into()
.map_err(|_| validation("segment count exceeds u32"))?,
total_bytes: total,
};
let hmac_sha256 = key.tag_hex(&canonical_json(&core)?)?;
Ok(Self { core, hmac_sha256 })
}
Read what the seal binds: the begin manifest’s canonical bytes (so the declared identity and prompt cannot change after the fact), the concatenated canonical descriptors, and the entire payload stream — every KV byte — hashed in order as segments arrived. One tag covers all of it.
The word canonical is doing cryptographic work, and this is the part
that trips people up. An HMAC is computed over bytes, not over meaning. Two
JSON documents that any parser would call equal — same fields, same values,
different key order or different spacing — are two different messages to
the tag. So the seal only works if the producer and the receiver serialize
the core to the same bytes, and here they are not even the same language:
the producer is Python, the receiver is Rust. canonical_json is what makes
the two agree. It sorts object keys recursively and emits compact JSON, “so
Rust and Python (sort_keys=True, compact separators) emit identical
bytes”; the recursive sort is “load-bearing” because a Cargo feature flag
(preserve_order) would otherwise silently change the wire form
([third_party/kvpack/crates/kvpack-handoff/src/canonical.rs:8-17]).
Notice what kind of failure that flag would produce, because it is the
reason the next detail matters. A dependency change nobody reviewed as a
protocol change would reorder some keys, the tag would stop matching, and a
perfectly honest handoff would be refused as tampering — a security
mechanism firing on a build-configuration bug. Byte-layout contracts across
ecosystems do not happen by accident, so the vendoring record carries a
deliberate one-file patch for exactly this concern: canonical-json sorted
keys, independent of serde’s preserve_order feature
(third_party/kvpack/provenance.json, audited by
scripts/audit_vendored_kvpack.py).
Verification is streaming, not retrospective. The receiver’s
AtomicReceiverV2 hashes descriptors and payloads as each segment
arrives, and at the seal it re-derives everything and checks the tag in
constant time:
#![allow(unused)]
fn main() {
// third_party/kvpack/crates/kvpack-handoff/src/handoff_v2.rs:435-452 (excerpt)
if core.transfer_id != expected.transfer_id
|| core.generation != expected.generation
|| core.segment_count as usize != self.next
|| core.total_bytes != self.total
|| core.begin_sha256 != sha256_hex(self.begin.canonical_bytes())
|| core.descriptor_sha256 != hex::encode(self.descriptor_hash.clone().finalize())
|| core.payload_sha256 != hex::encode(self.payload_hash.clone().finalize())
{
return self.fail("seal identity or digest mismatch");
}
validate_hex("seal HMAC", &seal.hmac_sha256)?;
let stream = canonical_json(core)?;
if let Err(error) = self.key.verify_hex(&stream, &seal.hmac_sha256) {
self.abort();
return Err(error);
}
}
The MacKey behind verify_hex is a 256-bit key that zeroizes on drop,
with constant-time tag comparison
[third_party/kvpack/crates/kvpack-handoff/src/mac.rs:28-32, 79-80]. And
because this is fail-closed culture, the refusals are tested as passes:
“stale replay generations, identity-mismatched configs, and tampered
manifests are refused end-to-end on live hardware — the refusal itself is
the passing test” [docs/disaggregated-prefill.md §Correctness]. The
return-runbook’s secd1 cell exists purely to keep one of those refusals
proven live (the exact replayed or stale generation message in a
retained command log; [docs/gx10-return-runbook-2026-08.md §2]).
30.5 The wire format
The seal is checked at the end of the transaction. That leaves an uncomfortable stretch of time in between, and the question the wire format has to answer is: what can a peer make the receiver do before the seal arrives to vindicate or condemn it? The design’s answer is to keep the pre-seal surface as small and as bounded as it can be made.
On the TLS stream, frames are length-prefixed JSON headers with binary payloads. The constants and the frame set:
#![allow(unused)]
fn main() {
// crates/muser-cluster/src/transport.rs:15-16
const MAGIC: &[u8; 8] = b"KVPKV2\0\0";
const PREAMBLE_BYTES: usize = 20;
}
#![allow(unused)]
fn main() {
// crates/muser-cluster/src/transport.rs:19-38 (excerpt)
pub enum WireFrameV2 {
Begin(BeginAdmissionV2),
Segment { sequence: u32, payload: Vec<u8> },
DeferredSegment { descriptor: SegmentDescriptorV2, payload: Vec<u8> },
Seal(SealManifestV2),
Ack { transfer_id: String, generation: u64 },
Abort { reason: String },
}
}
The header carries a magic plus a 20-byte preamble and the frame kind;
payloads are bounded by explicit limits — 8 MiB header, 512 MiB payload
(FrameLimitsV2, [crates/muser-cluster/src/transport.rs:83-95]) — so a
hostile or corrupted stream cannot ask for unbounded memory. One frame
shape deserves its own note: DeferredSegment exists because a
streaming producer “cannot know hashes for future KV tiles at begin
time. In deferred mode each ordered segment frame carries its complete
descriptor; the terminal seal still binds the canonical descriptor stream
and all payload bytes before commit”
(the deferred_segments field doc,
[third_party/kvpack/crates/kvpack-handoff/src/handoff_v2.rs:84-90]).
The sender-side mirror of that schedule is Ch 29 §29.5’s
streaming seam: segments leave during prefill, each with its own
just-in-time descriptor, and the seal closes the transaction.
30.6 The replay ledger and the durable reservation
Now the third leg of the threat model: replay. Every handoff carries a
generation number, a monotonically increasing counter scoped to one
HMAC key id and epoch. The receiver’s ReplayLedger admits a
generation only if it is strictly above the highest ever committed for
that key_id:epoch; generation 0 is refused unconditionally
([crates/muser-cluster/src/security.rs:387-406]). The dangerous window
is between “engine prepared” and “engine published”: a crash there must
not allow the same generation to install twice. So the reservation is
made durable before anything live changes, with the full
write-fsync-rename-dir-fsync dance.
Two words in that dance need unpacking, because the whole guarantee rests on them. fsync is the operating-system call that forces buffered writes out of volatile caches onto the storage device itself. Put the other way round: without it, “written” means only that the kernel intends to write it soon, and a power cut turns that intention into nothing. The directory variant is the one people forget. A rename is a modification of the directory, not of the file, so a renamed file whose directory entry never landed is a file that never got renamed. Together the two give the property the ledger actually needs — the new high-water mark either exists after a crash or it does not, and there is no state in between.
The sequence:
#![allow(unused)]
fn main() {
// crates/muser-cluster/src/security.rs:472-486
let result = (|| -> std::io::Result<()> {
let mut options = OpenOptions::new();
options.write(true).create_new(true);
#[cfg(unix)]
{
use std::os::unix::fs::OpenOptionsExt as _;
options.mode(0o600);
}
let mut output = options.open(&temporary)?;
output.write_all(&bytes)?;
output.write_all(b"\n")?;
output.sync_all()?;
std::fs::rename(&temporary, path)?;
File::open(parent)?.sync_all()
})();
}
Walk it slowly, because every step answers a specific crash:
create_new(O_EXCL) + mode 0600 — the temp file cannot clobber or alias anything;write_allthensync_all()— the new high-water mark is on the platter before it becomes authoritative;rename— one atomic step replaces the ledger;File::open(parent)?.sync_all()— the directory is fsynced, so the rename itself survives power loss.
If any step fails, the ledger latches degraded: “A failed reservation
latches this ledger degraded; the receiver refuses all later traffic until
the operator repairs storage and restarts the process”
[crates/muser-cluster/src/security.rs:407-411]. A durability layer that
silently continued after failing to be durable would be a worse liar than
an outage.
The epoch rule closes the last loop: if a node’s PKI is ever regenerated,
its hmac_epoch must be bumped “so the ledger starts a fresh, disjoint
counter space rather than silently reusing one whose history was just
discarded” [docs/one-button-onboarding.md §Why the generation ledger must never reset] — deleting or truncating the ledger file resets the
high-water mark and makes every previously captured, validly-signed
handoff replayable again.
30.7 What lives where: the durability lesson
That directory-fsync in step 4 is where a whole failure class was discovered, and the fork is worth walking slowly, because the wrong turn was the reasonable one.
Look at the replay ledger and it presents itself as evidence. It is an
append-only record of which generations were admitted — precisely the sort
of file this campaign keeps forever. And the fleet has a disk for exactly
that: the evidence volume, muser-receipt://, an external disk
optimized for append-only writes. So on 2026-08-18 the fast lane’s
qualification config pointed the replay ledger there. The premise was
sound; only the conclusion was wrong. This file is not evidence, it is
operational state, and what a receiver needs from it is not cheap appends
but a fast, boring, predictable rename.
The symptom was time-to-first-token that would not sit still. Not uniformly slow — bimodal. Most handoffs were fine; some stalled by most of a second, apparently at random, on identical work. When a decode arrives late, the expensive machinery is the natural suspect: the network transfer of a multi-gigabyte cache, or the Metal install on the far side. Neither explains a stall that comes and goes on runs that are otherwise the same.
The cheap step was the culprit. Measured directly, the evidence volume’s directory-fsync had a bimodal tail of its own — median 0.22 ms, observed ~0.7 s — and that tail sat exactly where it could do maximum damage, “sit[ting] between prepare_commit and commit — delaying ACK and first decode.” A sub-millisecond call in the common case was, in its tail, the single longest step in the critical path.
Moving the ledger to the internal disk settled it: “TTFT median 1.596 s,
CV 0.56% (was 2.699 s / 21.40%)” [docs/disaggregated-prefill-sealing-plan-20260818.md §W1 finding 1].
Read the coefficient of variation before the median. The median improved,
but the real result is that the run stopped being erratic — which is the
lesson worth carrying out of this section. What a latency budget spends on
a durability primitive is not its average cost; it is its tail.
The lesson is now codified in two places, and the pairing is the point: one
rule for the humans, one gate in the code that would otherwise suffer.
In the operator rules: “operational state (replay ledger, sockets, locks)
belongs on the internal disk — the
evidence volume’s directory-fsync tail produces bimodal ~1 s stalls in
commit paths” [AGENTS.md §Hard rules]. And in the receiver itself — the
ledger-volume gate: at bind time, the receiver probes its own ledger
directory with the exact reserve pattern (20 iterations) and refuses to
start if the worst sample exceeds 100 ms:
#![allow(unused)]
fn main() {
// crates/muser-cluster/src/receiver.rs:108-116 (doc comment, excerpt)
/// The commit path durably reserves every generation with
/// write+fsync+rename+directory-fsync before the ACK leaves. On a volume with
/// a slow directory-fsync tail this stalls the ACK and first decode by
/// hundreds of milliseconds at random (the 2026-08-18 p4 seal stall), so a
/// receiver whose replay ledger sits on such a volume is refused at bind
/// time. `scripts/gx10/durable_fsync_probe.py` is the standalone operator
/// check for the same pattern.
const LEDGER_RESERVE_PROBE_ITERATIONS: usize = 20;
const LEDGER_RESERVE_PROBE_MAX_TAIL: Duration = Duration::from_millis(100);
}
So the placement rule is not tribal knowledge; it is enforced by the code
that would otherwise suffer (check_ledger_volume,
[crates/muser-cluster/src/receiver.rs:118-148]) and re-checkable by the
operator tool (scripts/gx10/durable_fsync_probe.py, exit 1 past
--max-tail-ms). Ch 31 generalizes this into
the wire discipline: every stall in this campaign was self-inflicted
infrastructure until proven otherwise.
30.8 Enrollment and the one-button wizard
Everything above needs material: certificates, pins, an HMAC key, a
deployed producer, a qualified identity. The one-button wizard —
muser node add user@host on the CLI, Add node on the dashboard —
does the whole pipeline, and the operator reference is careful to say
“every step below is a real action against a real remote host, over real
SSH, with real files on disk on both ends” [docs/one-button-onboarding.md].
Six executable stages, seven progress labels (smoke emits two):
| # | Stage | What it does |
|---|---|---|
| 1 | preflight | SSH with BatchMode (fails closed rather than prompting), aarch64, NVIDIA driver, docker — nothing copied |
| 2 | deploy | Pushes the pinned runtime container (a sha256:… id, never a mutable tag); records the container receipt |
| 3 | model | Places the pinned GGUFs under lane_dir, verifying byte sizes and SHA-256 against the release artifact manifest |
| 4 | enroll | Node-local TLS key generation (CSR out, certificate in); pinned leaf exchange; mints the HMAC key and transfers it over the known-host-verified SSH channel; bumps hmac_epoch |
| 5 | daemon | Starts the producer daemon in the container, pointed at the enrolled PKI and model |
| 6 | smoke | netqual (median TCP RTT; installed-payload throughput derived from committed bytes — median across three handoffs ≥ 3.0 Gbps) then the qualification recipe |
Table 30.1: The onboarding pipeline [docs/one-button-onboarding.md §Six executable stages, seven progress labels]. The MAC-side receiver config
lands at ~/.muser/nodes/<name>/cluster.json (§Where things live) —
secrets referenced by path, never inlined.
The qualification recipe is declared by the enrolled producer identity and
is always exactly three ordered Handoff V2 exchanges at a 2,048-position
prompt and 256 output tokens [docs/muser-architecture.md §Durable and remote KV]:
native/text: exact target tokens under the identity’s bounded full-logit drift policy; “no DFlash identity or token trace is admitted” — the exact-vs-bounded distinction is Ch 32’s core subject;kquant/target-plus-dflash: exact target tokens, exact required full logits, and exact DFlash tokens and trace.
“An identity with no known recipe is refused at enrollment” — the same
fail-closed stance as everything else in this chapter. Only after the
three passes and the 3.0 Gbps median is state=healthy durably written
[docs/one-button-onboarding.md §6b smoke].
Does any of this actually run, end to end, against real hardware? Two
retained wizard packets say yes, and they are worth reading side by side,
because the second one holds itself to a strictly harder contract than the
first [claims #9]:
- Attempt 9 (native/text): all seven labels; three 2,048/256 handoffs
with exact tokens (digest
42f09900…) under the bounded-logit rule — max/mean logit deltas 10.884401 / 1.233788776 against the <11 / <1.25 policy — at payload rates 6.866 / 6.976 / 6.708 Gbps;state=healthy[receipt wizard-validation-20260823/attempt-9-native-live-20260824T051305Z/validation-summary.json]. - Attempt 31 (combined): 7/7 stages; three handoffs with exact full
logits (max/mean delta 0) and exact DFlash tokens/trace; payload rates
9.811736 / 8.886919 / 8.689889 Gbps;
state=healthy, canonical resident restored afterward[receipt wizard-validation-20260823/attempt-31-combined-full-20260824T132639Z/validation-summary.json].
Two clean packets. That is what the record shows, and it is not what the work looked like — the attempt numbers give that away before anything else does.
Behind attempt 9 stood eight earlier attempts, and the useful thing
about them is how they ended. Not one hung, and not one produced quietly
plausible output that someone had to catch by eye later. Each failed
closed, on a real defect the gates were built to find: stale geometry
residuals; a wrong-route flash_attn_ext override; a stale RoPE-cache
manifest; and a 3.0 Gbps gate computed from the wrong clock
[ledger Arc "The wizard: attempts 9 and 31"]. Sit with that last one for
a moment, because it is the most instructive of the four. The gate did not
wrongly admit a bad node — it wrongly rejected a good one, by measuring
the sender’s own optimism instead of the wire. We expected a failing gate
to mean “the node is not ready.” What it meant that time was “the
instrument is not ready,” and nothing but going and looking will tell those
two apart. The next chapter turns that particular scar into a standing rule
about which clock is allowed to time a transfer.
Getting from attempt 9 to attempt 31 then took attempts 10–30 — the arithmetic-ABI chase of Ch 29 §29.8, a long stretch of runs spent making two vendors’ arithmetic agree when both believed they already did.
So read the two passing packets the right way. They are not evidence that the pipeline worked on the first try; they are the runs that came out the far side of a gate that had been refusing everything before them. That is the pattern of this whole Part: the channel is trustworthy exactly because its refusals kept firing.
One operational requirement survives the wizard and lands on the operator,
and it matters because it is the seam where all of this could still be
undone by a default: a cache sealed under strict cross-vendor arithmetic is
only meaningful if the machine that decodes from it does the same
arithmetic. So the enrolled consumer must run the matching strict Metal
graph — MUSER_CROSS_VENDOR_QK=1
— and “both modes refuse startup when MUSER_CROSS_VENDOR_QK is absent or
not exactly 1; they never install a strict CUDA cache into the ordinary
Metal math route” [docs/one-button-onboarding.md §Starting the production consumer].
30.9 Scatter-on-arrival: the engine side of the transaction
The last piece is what the receiver does while bytes stream in — and there is a real tension to resolve there. Waiting for the seal before touching anything is the safe order, but a receiver that waits idly until the last frame has thrown away the whole prefill window it could have spent installing. The resolution is to do the work early and make it unobservable until the seal clears.
The MuseCacheShadow sink “writes authenticated target tiles into a detached
Metal generation as they arrive. Live decode state is replaced only in
commit, after the HMAC seal has been verified”
([crates/muser-cluster/src/muse_sink.rs:41-43]). A detached generation is
a staging area with the shape of the real thing: bytes land in it during
the transfer, and if the seal fails they are discarded having never been
visible to a decode.
If the sink can install as tiles land, the next question belongs to the
producer: what can it send before prefill has finished? The wire order that
makes streaming productive is fixed by the transfer schedule — the 13 NoPE
layers [3, 7, …, 51] as 512-token tiles (~6.5 MiB each), three SWA
groups of 13 layers, layer-major so “every SWA group is sendable as
soon as its 13 layers exist mid-prefill and only the NoPE tiles trail”
([crates/muser-cluster/src/schedule.rs:19-26, 124-129]).
The same schedule carries the delta-handoff discipline from
Ch 26: a prefix cut is legal only on
a 256-token boundary (PREFIX_CUT_ALIGN = 256,
[crates/muser-cluster/src/schedule.rs:25-26]), NoPE tiles cover only
[cut, position), and SWA tiles cover [max(cut, position−2048), position)
— mirrored exactly by the producer’s Python muse_intents
[crates/muser-cluster/src/schedule.rs:75-90]. The sink even records a
delta witness — the observed (role, layer, start, count) stream — and
re-checks it against the span schedule at prepare time, because a
deferred stream declared no schedule at begin
([crates/muser-cluster/src/muse_sink.rs:19-21, 68-76]).
Prepare-then-publish is itself two-phase and component-scoped: “a combined
remote install prepares and verifies both [target and DFlash] before an
infallible publication step” [docs/muser-architecture.md §Model and engine] — and qualification evidence must prove both components prepared
and installed separately, because “aggregate byte counts cannot establish
that on their own” (ComponentInstallEvidence,
[crates/muser-cluster/src/muse_sink.rs:23-28]).
30.10 Tradeoffs
- HMAC on top of mTLS (chosen) vs TLS alone. TLS authenticates the
channel; the seal authenticates the artifact. The split pays off the
moment the sealed bytes outlive one connection — durable stores,
delta witnesses, receipts — where a channel-only design would have no
artifact-level tag to verify
[third_party/kvpack/crates/kvpack-handoff/src/mac.rs:1-18]. The cost is the key-provisioning channel: a shared secret needs the known-host-verified SSH leg of enrollment, accepted deliberately[docs/one-button-onboarding.md §Security model]. - Reserve-before-ACK (chosen) vs record-after-install. The durable
reservation costs a write+fsync+rename+dir-fsync in the TTFT path —
the measured downside is the entire 2026-08-18 stall story (§30.7). The
alternative (ack first, persist later) reopens the replay window after
every crash; the campaign chose the stall and then engineered it away
by placement (internal disk) plus the bind-time gate. Both the failure
and the fix are retained evidence
[docs/disaggregated-prefill-sealing-plan-20260818.md §W1][crates/muser-cluster/src/receiver.rs:108-148]. - Three handoffs at 2,048/256 as the qualification recipe (chosen) vs
deeper/longer qualification at enrollment. The recipe is deliberately
shallow and fast; depth and sustained load live in the campaign packets
(the eight-handoff soak, the 130,815 cells — Ch 27 §27.5).
The wizard’s scope is “can this enrolled identity be trusted to serve
this lane’s contract,” not “prove all performance claims” — the
claims register keeps the boundary explicit
[claims #9].
30.11 What comes next
Handoff V2 answers who and what and when-not-before: the producer is pinned, the bytes are sealed, the replay is refused, the ledger survives power loss. But authentication says nothing about throughput. The deep cell still has to put 1,823,184,896 bytes across a 10GbE link inside a 137-second budget — at the qualified floor that is ~2.1 s of pure wire time (1.823e9 B × 8 ÷ 6.995e9 b/s), and the campaign learned, painfully, that the difference between 9.4 Gbps of raw TCP and 3.9 Gbps of installed payload was its own pacing pin; that a power-saving Ethernet feature could black out exactly the burst pattern this schedule produces; and that the honest wire clock is the kernel’s busy-time, not anyone’s send-loop. That discipline — pacing, EEE, clocks, and where state lives — is the next chapter.
References
[crates/muser-cluster/src/lib.rs:5-22]— the crate’s one-sentence pipeline and the 3.0 Gbps release floor.[crates/muser-cluster/src/security.rs]— ALPN constant (21), TLS 1.3 (122–124, 172–174),verify_connectionleaf pins (196–213),MacKeyloading contract (55–95), private-key file contract (289–335),ReplayLedgeradmit/reserve/latch (356–439),persist_replay_state(460–491).[third_party/kvpack/crates/kvpack-handoff/src/handoff_v2.rs]—BeginManifestV2(73–91), deferred-segments doc (84–89),SealManifestV2::sign(247–287), streaming verify inprepare_commit(419–460).[third_party/kvpack/crates/kvpack-handoff/src/canonical.rs:8-36]—canonical_json, the load-bearing recursive sort.[third_party/kvpack/crates/kvpack-handoff/src/mac.rs]—MacKey(28–32), constant-timeverify_hex(79–80), the F1 design note (1–18).[crates/muser-cluster/src/transport.rs:15-16, 19-38, 83-95]— magic, preamble, the frame enum, frame limits.[crates/muser-cluster/src/control.rs:1-13]— control-plane scope and ALPN.[crates/muser-cluster/src/receiver.rs:108-148]— the ledger-volume gate and its probe constants.[crates/muser-cluster/src/muse_sink.rs]— detached-generation contract (41–43),ComponentInstallEvidence(23–28), delta witness (19–21, 68–76).[crates/muser-cluster/src/schedule.rs:19-26, 75-90, 124-129]— NoPE layers, tile/group sizes,PREFIX_CUT_ALIGN, layer-major streaming order.[docs/muser-architecture.md §Durable and remote KV]— the three-line security summary and the recipe contracts.[docs/one-button-onboarding.md]— the six stages, the security model, the generation-ledger essay, v1 limits.[docs/disaggregated-prefill.md §Correctness]— refusals as passing tests.[docs/disaggregated-prefill-sealing-plan-20260818.md §W1]— the 2026-08-18 fsync-tail root cause and fix.[AGENTS.md §Hard rules]— operational-state-on-internal-disk as a hard rule.[docs/gx10-return-runbook-2026-08.md §2]— the live stale-generation refusal proof.[docs/launch-claims.md]— #9 (wizard attempts 9 and 31, their rates and scopes).[receipt wizard-validation-20260823/attempt-9-native-live-20260824T051305Z/validation-summary.json]and[receipt wizard-validation-20260823/attempt-31-combined-full-20260824T132639Z/validation-summary.json]— the two passing wizard packets.- glossary — terms introduced this chapter: Handoff V2, mTLS, ALPN, leaf pin, HMAC-sealed manifest, canonical JSON, generation number, hmac_epoch, replay ledger, durable reservation, ledger-volume gate, one-button wizard, qualification recipe, detached generation, delta witness.
Chapter 31 — The wire discipline
status: polished · path: Muse Glimmer, pinned Muser tree
Prerequisites: Chapters 22–26 (you know what the KV cache costs and how kvpack moves it), Chapters 27–30 (you know why prefill is disaggregated, what the GX10 producer is, and how Handoff V2 authenticates every byte). This chapter is about the unglamorous layer under all of that: making 1.8 GB cross a 10GbE link in about two seconds — and knowing, with receipts, why it took exactly that long.
Chapter 30 ended with a sealed manifest: every segment HMAC-verified, the replay ledger consulted, the ACK meaningful. That is the correctness story of the wire — the bytes you received are the bytes that were sent. This chapter asks the question that comes next, and it is the one that actually hurt: why did those bytes take as long as they did, and how would we know?
Between 2026-08-16 and 2026-08-23 we asked it four times, because four times the transfer looked sick. Each time we went in half expecting to find a bad link. Each time the culprit turned out to be something we had built ourselves — a pacing pin, a filesystem, a power-saving Ethernet feature, and a stale reference number — never the network the symptoms were blaming. Being wrong in the same direction four times running is how a rule gets earned, and this is the one we earned: the link is never the constraint until it suddenly is, and every stall is self-inflicted until proven otherwise.
We did not invent that rule for the book; by the time we wrote it down it
was already load-bearing in the repo. “Every stall in this campaign was
self-inflicted infrastructure until proven otherwise” is how the research
ledger frames the wire question [measured-numbers §3, question 3]. And
the operator cheat sheet at the Muser root carries the scar tissue as
standing policy: operational state — replay ledger, sockets, locks —
belongs on the internal disk, because “the evidence volume’s
directory-fsync tail produces bimodal ~1 s stalls in commit paths”
[AGENTS.md, muser root]. That sentence has a date and a price, and you
will meet both.
31.1 The bill: what actually crosses the wire
Every argument in this chapter is an argument about seconds, and a second on a wire is only ever bytes divided by a rate. So before we are allowed to accuse the link of anything, we owe the reader — as we owed ourselves — an exact count of what we are handing it. Get the numerator wrong and every rate in the chapter is fiction.
Start with the invoice, derived the way Chapter 22 taught you. One
layer-token of KV is 2 KV heads × 128 head-dim × 2 bytes (f16) × 2 planes
(K and V) = 1,024 B [docs/memory-footprint.md §KV formula]. The 52
layers split 39 SWA (window 2,048) + 13 NoPE (full history)
[docs/muser-architecture.md], so a handoff carries two kinds of bytes:
- NoPE tiles — absolute, position-free planes: 13 layers × 1,024 B =
13,312 B per prompt token, all of
[0, position)minus the boundary token the receiver holds back for local first logits. - SWA groups — the trailing window only: 39 layers × 2,048 rows ×
1,024 B = 3 groups of 13 layers ≈ 81.8 MB total, re-sent whole
whenever the suffix outgrows the window
(
muse_schedule_span,[crates/muser-cluster/src/schedule.rs:84-98]).
At the shallow end, a 2,048-token prompt moves ≈104 MiB of target KV plus
≈42.5 MiB of DFlash context in 36 segments — ≈146 MiB combined
[docs/disaggregated-prefill-sealing-plan-20260818.md §2]. At the deep
end, the 130,815-token cell moves exactly 1,823,184,896 B. The word
exactly is doing real work there: the per-class decomposition above
reconciles to the byte, and we kept the run that proves it
[docs/kvpack-merge-handoff-20260820.md §3 D1; receipt phase4-disagg-20260820/130815-g900091/out-p4/f-p4-text-g900091-client.json].
The derivation is walked in full in
Ch 22 §22.7.
We are this pedantic about the invoice because we once got it wrong, and
badly. An early doc read the deep payload as “~7 GB” — it had picked up
the producer’s --kv-cache-memory-bytes allocation instead, which is
the size of the box, not the weight of what is in it. A wrong numerator
never announces itself; it simply makes a healthy link look sick, and
sends you hunting for a fault that is not there. That kind of mistake is
one of this book’s standing landmines, and it is told as its own story at
Ch 22 §22.7.
Now the physics. A 10GbE link at its measured ~9.4 Gbps raw ceiling moves
1.823 GB in 1.823e9 × 8 / 9.4e9 ≈ 1.55 s; at the 3.9 Gbps the lane
originally achieved it takes ≈ 3.73 s; at the 6.995 Gbps deep-cell
floor it takes ≈ 2.09 s. That last number is the chapter’s title claim:
about two seconds, honestly. The sealing plan’s own floor table says
the same thing in campaign units — at 3.9 Gbps the lossless transfer
floors are 2k ≈ 224 ms, 32k ≈ 1.06 s, 131k ≈ 3.75 s, and “at 131k the
floor exceeds the entire current 2k handoff — delta transfer (W3) is what
keeps long context viable, not a faster NIC”
[docs/disaggregated-prefill-sealing-plan-20260818.md §4]. Everything
else in this chapter is the gap between 1.55 s of physics and whatever
the lane actually delivered on a given night. Figure 31.1 draws the
whole path now, so you can place each villain as it appears.
The wire path, with every discipline that touches it (2026-08-23 topology):
┌──────────────────────── GX10 producer (producer-1) ────────────────────────┐
│ vLLM NVFP4 prefill (CUDA, layer-major) │
│ │
│ SWA groups (~82 MB) stream during the last CUDA ubatches │
│ NoPE bulk (1.74 GB, 95.5% of payload) waits for layer 51 │
│ │ │
│ sender thread: SO_MAX_PACING_RATE = 8 Gbps pin (fail-closed readback) │
│ wire clock: TCP_INFO.busy_time (the only honest denominator) │
└────────────────────┼─────────────────────────────────────────────────────┘
▼
[ enp1s0f0np0 · 192.0.2.20 ]══════════[ MikroTik 10GbE fabric ]
│ EEE off — enrolled link invariant
▼ (6.42 s retransmission ladders
[ Mac en0 · 192.0.2.10 ] otherwise; §31.4)
│ per-segment: drain → HMAC verify → install into a DETACHED Metal gen │
│ terminal seal → ReplayLedger.reserve (write+fsync+rename+dir-fsync │
│ on the INTERNAL disk — never the evidence volume) │
│ atomic swap → ACK Wi-Fi en1 exists and never carries a │
└──────────────────────────── measurement [scripts/gx10/README.md:7-11] ──┘
Figure 31.1: The full wire path with its annotations. Every box on this diagram is a chapter villain at least once: the pacing pin (§31.2), the ledger volume (§31.3), EEE on the switch link (§31.4), and the reference numbers after the topology change (§31.5).
31.2 The pacing ladder: 3.9 of 9.4 Gbps was our own pin
So: where did the other half of the link go? For the better part of a week we would have told you the network ate it, and we would have been wrong. What follows is the ladder we climbed to find that out, told the way the campaign climbed it — one dated rung at a time. The order the rungs arrived in is the whole lesson, so resist reading ahead to the answer.
Rung 0 — the symptom (2026-08-17). The F-series engineering packet
measured installed payload at 3.910 Gbps median, CV 0.401% on the
2,048-class cell — stable, but less than half of what 10GbE should do
[docs/nvfp4-fast-lane-evidence-20260817.md §Measured product numbers].
The production sender had just acquired a 4.0 Gbps kernel pacing
ceiling in the N5 transport fix, and five fresh 109 MB serving handoffs
passed at median 3.893 Gbps, CV 0.6074% [ledger §N5]. Note what the CV
is telling you: the sender was rock-steady. A stable wrong number is
still a wrong number.
Rung 1 — the raw ceiling (2026-08-18, T0). A 5-second TCP probe in
both directions answered the only question that matters first: is the
wire healthy? The point-to-point 10GbE path sustained 9.40 Gbps
single-stream, MTU 1500, zero tuning, zero retransmits over 30 s
[ledger §T-series T0]. Conclusion, verbatim from the ledger: “The wire
was never the constraint.”
Rung 2 — the culprit (W0). The 3.9 Gbps was the sender’s own
SO_MAX_PACING_RATE = 500 MB/s pin — a Linux socket option that caps the
kernel’s transmit pacing — set as “the N-series floor guard” to protect
the 3 Gbps product floor on a then-unhealthy link
[docs/disaggregated-prefill-sealing-plan-20260818.md §5 W0]. Read that
again slowly, because it is the whole chapter in one sentence: a guard we
had written earlier in the campaign, to stop a sick link from dipping
under the product floor, was now the only reason we could not go faster
on a healthy one. Both producers inherited it, because the vLLM connector
imports the sender from the llamacpp path — one line of old caution, two
lanes throttled.
Rung 3 — the raise (W1). The pin went 4 → 8 Gbps, env-configurable.
Installed payload moved 3.91 → 5.89 Gbps median [ledger §T-series T1]. Note what that arithmetic does not say: doubling a ceiling did
not double a rate. The payload landed well short of the new pin, which
was our first hint that the pin had stopped being the only thing standing
in the way. The code that owns this today reads:
# scripts/gx10/llamacpp/muser_v2_send.py:55
HANDOFF_PACING_BYTES_PER_SECOND = 1_000_000_000 # 8.0 Gbps; the direct link measures 9.4
# scripts/gx10/llamacpp/muser_v2_send.py:58
def handoff_pacing_bytes_per_second() -> int:
"""Configured payload pacing ceiling (default 8.0 Gbps).
The N-series pin of 500 MB/s protected the 3 Gbps product floor on an
unhealthy link. The direct 10GbE path measures 9.4 Gbps single-stream, so
the default now sits ~15% under line rate. MUSER_GX10_PACING_BYTES_PER_SECOND
overrides for experiments without a redeploy; a value the kernel refuses
still fails closed in `configure_linux_pacing`.
"""
Two disciplines live in that one constant. First, the pin stays ~15%
under line rate on purpose — it exists so the kernel smooths bursts
rather than letting a 1.74 GB NoPE flood collide with TCP’s own
congestion logic (§31.4 explains why bursts are this lane’s resting
state). Second, the pin is fail-closed in both directions: the
resident producer’s receipt validator refuses any handoff whose
payload_pacing_bps is below 4 Gbps — handoff["payload_pacing_bps"] < 4_000_000_000 is a hard rejection in the daemon’s receipt check
[scripts/gx10/vllm/muser_native_prefilld.py:569-570] — and a kernel
refusal of the socket option aborts the send rather than silently
unpacing.
Rung 4 — the honest clock. Then the fix broke the thermometer. We expected a faster lane to look like the old lane with a bigger number; what we got was a lane that suddenly failed its own stability gate. At pins above the achievable rate, the producer’s TCP busy-time metric tracks real jitter (CV ~5%) instead of the pacer (CV 0.4%), and the old ≤2% link-CV gate had been calibrated on the flattened version.
Say that the other way round, because it is the part that trips people up. While the pin was low, the pacer — not the link — was setting the rate, so the rate came out beautifully steady. The gate was never measuring the wire; it was measuring our own metronome, and grading it excellent. Unpin the sender and the gate starts seeing the world for the first time, and the world is noisy. The stability we had been shipping was, in part, a property of the instrument.
So the campaign repaired the instrument before trusting it again. The
wire clock ruling is that Linux TCP_INFO.busy_time is the only honest
link denominator; userspace send-time and receiver first-read clocks
were both tried and both rejected, each one contaminated by something
that is not the wire — buffering in one case, compute in the other
[ledger §P4; §N5]. The link gate was then re-specified from a rate-CV
to a per-counted-rep floor: every counted repetition ≥ 3.0 Gbps
installed payload, with the CV retained in receipts for audit only
[docs/disaggregated-prefill- sealing-plan-20260818.md §7.4]. And the row that died in the transition
was kept rather than deleted: five reps of 5.581 / 4.550 / 5.309 /
5.769 / 4.765 Gbps at CV 8.9985% survive in the record as failed-metric
evidence [measured-numbers §1e]. Nothing about the link changed when
that row was retired. Only our right to quote it did.
Rung 5 — the landed numbers. With the ladder climbed, three results
landed, and they are worth reading as a set rather than as a list: one
shallow, one deep, and one that quietly embarrasses the pin. The shallow
headline is the final-image 2,048/256 packet: TTFT median
1.493 s, counted CV 0.22%, ≥6.23 Gbps installed payload (receipt minimum
6.228), deterministic, exit 0 — stable: true
[claims #6; ledger §T-series "Final packet on the final image"; receipt nvfp4-pacing8g-20260818/p4-wrapper23/]. The deep 130,815 cell under the
EEE-off invariant holds a ≥6.995 Gbps floor with TTFT median 137.405
s at CV 0.576% [claims #6; ledger §EEE A/B at 130815]. And the one
place the lane exceeds the pin is the wizard’s onboarding recipe, where
three enrollment handoffs measured 9.812 / 8.887 / 8.690 Gbps
installed payload — onboarding-recipe scope, explicitly not a
serving-throughput claim [claims #9]. The count of bytes never changed
on this ladder; only the honesty of the clocks and the height of a pin
we set ourselves.
31.3 Our own fsync tail: the durability lesson of 2026-08-18
Raising the pin bought real throughput and settled nothing about the
question that mattered more: could we promise a time-to-first-token at
all? The pacing fix did not kill the 2,048-class TTFT variance. What
remained was a bimodal ~1 s stall on random repetitions — the packet
whose five-rep TTFT median was 2.699 s at CV 21.40%
[docs/nvfp4-fast-lane- evidence-20260817.md §Measured product numbers]. Bimodal is the tell,
and it is worth pausing on it. A slow lane is a lane that is slow every
time. This one was quick on most repetitions and roughly a second slower
on the others, with nothing in the inputs to tell the two groups apart.
That shape does not come from a gradual cost. Something was either
happening or not happening.
The hunt that followed was pure elimination, and every
suspect on the list was one we had reason to fear. Per-frame arrival
stamps and absolute seal clocks cleared the raw link (0 retransmits
under saturation), then CPython GC (sub-ms collections), then producer
compute (flat CPU), then the Mac-side engine phases (constant), then
session creation (constant ~650 ms) [ledger §T-series T2]. That exhausted everything we had instrumented, and the
stall was still there — which meant it lived in the one seam nobody had
thought to time, between prepare_commit and commit. There it was:
the replay ledger’s directory fsync on the evidence volume, reserve
median 0.22 ms, observed tail 691 ms [ledger §T-series T2]. A
durability primitive, sitting on the critical path of a network
benchmark.
Here is why a directory fsync is on the critical path at all. Replay
admission (Chapter 30’s defense against a replayed generation) must be
durable before the ACK leaves and before any live engine pointer is
swapped — the timeline in Figure 31.2 shows where the tail lands.
ReplayLedger::reserve therefore persists the new high-water mark with
the classic crash-safe sequence — write a temp file, fsync the file,
rename over the target, then fsync the directory so the rename
itself survives power loss. The function is persist_replay_state
(security.rs:460-491), walked step by step in
Ch 30 §30.6; the expensive step is the last
one, and on the big external evidence disk — a volume tuned for throughput,
under load from everything else the campaign writes to it — it has a
bimodal ~0.7–1.0 s tail [scripts/gx10/durable_fsync_probe.py:5-12].
one handoff's commit path, time going right (log scale in spirit):
segments drain ── verify+install ── seal ── RESERVE ─────────────► swap → ACK
(paced by sender) (~0.2 s total) │
│ write tmp
│ fsync file ~0.2 ms median
│ rename
│ fsync DIRECTORY ← 0.22 ms median,
│ 691 ms tail on the
│ evidence volume
▼
TTFT absorbs the tail whole
Figure 31.2: Where the ~1 s went. The receiver phases the N-series
instrumentation isolated (verify+install+seal+commit ≈ 0.2 s, constant
[ledger §N2]) are not the stall; the stall is one fsync of one
directory on the wrong volume [ledger §T-series T2].
The fix was one line of configuration with a permanent lesson attached:
move replay_ledger to the internal disk. TTFT median went 2.699 s /
CV 21.40% → 1.596 s / CV 0.56% [ledger §T-series T2]. The lesson,
now enshrined in the repo’s working agreements: operational state
(replay ledger, sockets, locks) belongs on the internal disk; the
evidence volume is append-only storage, not a commit path [AGENTS.md, muser root]. And the guard became code, not advice — the receiver now
refuses to bind on a slow ledger volume, before any transfer can be hurt
by it. The gate is the bind-time probe that
Ch 30 §30.7 introduced (twenty iterations of
the exact reserve pattern against the ledger’s parent directory, refusal
past a 100 ms worst sample); its error message teaches the fix:
#![allow(unused)]
fn main() {
// crates/muser-cluster/src/receiver.rs:139 (inside check_ledger_volume_with)
if tail > LEDGER_RESERVE_PROBE_MAX_TAIL {
return Err(format!(
"replay ledger volume {} has a {tail:?} durable-reserve tail; \
the handoff commit path would stall on it — point replay_ledger \
at the internal disk (see scripts/gx10/durable_fsync_probe.py)",
directory.display()
));
}
}
Note when that check runs. RemoteReceiver::bind performs
check_ledger_volume — twenty real write+fsync+rename+dir-fsync cycles
against the ledger’s parent directory — before the listener even opens,
and refuses the config outright if the worst sample exceeds 100 ms
[crates/muser-cluster/src/receiver.rs:189-206]. A misconfigured node
therefore never gets as far as accepting a handoff it would go on to
stall. And the check is only worth anything because it lies about
nothing: probe_ledger_reserve is a faithful in-process twin of
persist_replay_state’s sequence, the same four steps in the same order
[crates/muser-cluster/src/receiver.rs:150-178]. A cheaper probe would
have measured a cheaper thing.
The operator-side twin is scripts/gx10/durable_fsync_probe.py, whose
docstring is the pre-flight ritual in miniature: run it “against the
directory that will host replay_ledger in the receiver cluster config”;
“median ~0.1-0.3 ms and max < ~5 ms: healthy; the ledger may live here”;
“a max (or p99) of hundreds of ms: do NOT put operational durability
state on this volume” — and it exits 1 if the worst reserve exceeds
--max-tail-ms, so it can gate automation, not just inform humans
[scripts/gx10/durable_fsync_probe.py:16-33]. The docstring ends with
the scar: “This exact failure cost the fast lane its stability gate on
2026-08-18.”
31.4 EEE: when the power saver eats the burst
The third stall was the strangest, because by now the shallow cells were healthy and it was the deep cells that collapsed. Depth-dependent failure has an obvious shortlist, and ours was two names long: either the receiver had started pushing back once the payload got big, or the producer had simply gotten slower per byte on a long prompt. Both are our own software. Both would have been consistent with the rule. Both were wrong. Two investigations, two payoffs.
The controlled diagnosis (N2, 2026-08-16 era). The first investigation was built to kill those two hypotheses cleanly: six repetitions of the identical strict 1×2048×256 cell, with the receiver’s phases instrumented so that “the receiver is slow” would have to show up as an actual number (Figure 31.3):
| Condition | Rep | Producer wire s | installed Gbps | verify+install+seal+commit |
|---|---|---|---|---|
| as-is | 1 | 13.25 | 0.093 | 0.20 s |
| as-is | 2 | 6.58 | 0.187 | 0.16 s |
| as-is | 3 | 19.75 | 0.062 | 0.20 s |
| serial quiesce | 1 | 19.54 | 0.063 | 0.20 s |
| serial quiesce | 2 | 0.22 | 5.526 | 0.20 s |
| serial quiesce | 3 | 6.57 | 0.187 | 0.19 s |
| EEE off (probe) | 1 | 0.20 | 6.215 | 0.20 s |
| EEE off (probe) | 2 | 0.24 | 5.208 | 0.19 s |
Figure 31.3: The N2 table. Installed payload swings 0.062–5.526 Gbps —
roughly 90× — across identical conditions, while the receiver’s entire
post-wire cost sits still at ~0.2 s [ledger §N2].
Read the columns the way we had to read them. Our first hypothesis,
receiver backpressure: refuted, because the last column never moves
(constant ~0.2 s). Our second, producer compute: refuted the same way.
Both shortlist names were gone, and the swing had to live in the only
column left — the wire span itself, which is precisely the thing the
chapter’s rule says to suspect last. The last row of the table is the
answer, and it is a row about a power-saving feature.
EEE —
Energy-Efficient Ethernet, the link’s low-power idle mode — was
enabled-and-active on the GX10 side (Tx LPI 19 µs), and with EEE disabled
on the Spark side both probe reps ran the fast case [ledger §N2]. One
wrinkle that matters operationally, and that will shape every remedy
below: macOS exposes no EEE control on en0; the toggle exists only on
the Spark side [ledger §N2].
Why this lane, specifically. A power saver that harms a link is not a
general law — offices run on EEE happily. So the question we owed
ourselves was why our traffic provoked it, and the answer is that EEE
punishes bursty senders while this lane’s wire schedule is bursty by
architecture. The SWA groups (~82 MB)
stream early, during the last CUDA ubatches; but the NoPE bulk — 1.74 GB,
95.5% of the payload by the §31.1 arithmetic — cannot start until CUDA
finishes layer 51, because NoPE planes are absolute full-history planes
built in layer order [docs/kvpack-merge-handoff-20260820.md §6 "Pacing reality"]. The deep cell therefore manufactures 41–47 s of forced link
idle and then dumps a 1.74 GB burst onto a link that has gone to sleep.
Said without the units: for the better part of a minute we tell the link there is nothing to do, the link takes us at our word and powers its lanes down, and then we hand it the single largest burst of the entire run and expect it to be awake. The deep cell is not an unlucky workload for EEE. It is close to the worst one you could design.
The blackouts, quantized. On the 130,815-token payload the failure
mode is not vague jitter but discrete retransmission ladders quantized
at 6.42 ± 0.03 s, after the LPI idle, and counted reps split into a
0.68–1.73 Gbps regime versus a 7.20 Gbps regime — same fixture, same
producer, same night [ledger §"EEE link ruling — operator decision (2026-08-20)"; receipts phase4-disagg-20260820/130815-g900091/].
The ruling (2026-08-20). The operator decision, recorded in the
ledger: “EEE-off is authorized for deep-payload p4 packets and
diagnostics; EEE-off is enrolled as the link invariant for the
disaggregated lane; ‘disable EEE on the point-to-point 10GbE link’
ships as production guidance” [ledger §EEE link ruling].
The confirmation by intervention (2026-08-21). A ruling deserves an
experiment, so the ladder session ran a same-night A/B in which only
the EEE state differed — 1 warmup + 5 counted reps per arm
[ledger §"EEE A/B at 130815"]:
- Arm A, EEE-active: TTFT median 138.886 s, CV 2.0013%, per-rep
payload [7.213, 7.445, 7.315, 1.728, 7.275] Gbps — exactly one rep
lost one ~6.4 s retransmission ladder;
stable:false. - Arm B, EEE-off: TTFT median 137.405 s, CV 0.576%, payload [7.043, 7.063, 7.084, 6.995, 7.084] Gbps (minimum 6.995), deterministic.
Attribution “by intervention, not correlation” — flip the link state, the
blackout follows it. Note also what the two medians say: at this depth
EEE’s damage is variance, floor violations, and occasional +6.4 s reps —
not the median [ledger §EEE A/B]. The payoff number the claims
register carries — 4.149× median TTFT versus the 570.122 s local
131,008-token mean baseline — is explicitly the EEE-off arm’s
[claims #6].
31.5 After the rebuild: re-prove the ceiling, and never trust en1
Rewiring a lab bench is not usually a chapter event. This one was, because the day it happened it silently invalidated every wire number printed above — and nothing in any tool would have told us. That is the fourth villain, and it is the quietest of the four: not a pin, not a volume, not a power saver, just a figure that used to be true.
On 2026-08-23 the lab moved from the retired direct retired /30 link
to the wired MikroTik fabric: Mac Ethernet en0 at 192.0.2.10 to
the producer’s enp1s0f0np0 at 192.0.2.20 [AGENTS.md, muser root]. Every historical wire number in this chapter — including the 9.40
Gbps T0 ceiling that gave us the confidence to raise the pin at all — was
measured on the old path. The topology
amendment to the sealing plan says what to do about that: “qualification
must first re-anchor raw TCP on the new topology and must never use Mac
Wi-Fi en1” [docs/disaggregated-prefill-sealing-plan-20260818.md, topology amendment]. Re-anchoring costs an afternoon. Carrying a stale
ceiling forward costs you the ability to tell a healthy lane from a sick
one, which is the only thing this chapter has been doing.
The re-anchor found something the direct link had hidden: asymmetry.
In the product payload direction (GX10 → Mac) single-stream TCP measured
9.256 Gbps (adjacent probes 9.218 and 9.291); the reverse direction
measured only 6.161 / 6.501 / 5.410 Gbps — retained as a deviation
and deliberately not promoted to a pass [ledger §"Final GX10 campaign attempt 4"; receipts final-campaign-20260823/attempt-4/phase0/tcp-*.json].
The rule this installs is the chapter’s last and most portable one: a
reference number is a property of a topology, not of hardware —
re-prove the raw ceiling before relying on any inherited figure, and
treat a deviation in the non-product direction as data, not as a
boundary to quietly round up. The product direction being the healthy
one is what let the campaign proceed; the reverse number stays in the
record as an open deviation [measured-numbers §1e].
With the ceiling re-anchored and EEE off, the lane re-qualified on the
switched fabric: the 2,048/256 P4 packet measured TTFT median
1.535889499 s at CV 0.322%, installed payload 6.4592–7.2065 Gbps, all
six outputs sharing the same token and full-logit digests, stable: true
[ledger §"Post-router GX10 lane requalification"; receipt final-campaign-20260823/attempt-4/p4/P4_VERDICT.json]. And the Wi-Fi
rule is written where operators will trip over it: “Mac Wi-Fi is en1;
a measurement is invalid if it routes there” [scripts/gx10/README.md:7-11].
31.6 The tools: a diagnostic ladder, bottom-up
The point of a war story is that the next person should not have to fight
the war. Each villain above therefore has a script attached, so that the
question “is it the wire?” can be answered in a minute instead of a week.
Every lesson in this chapter is productized under scripts/gx10/,
documented in scripts/gx10/README.md and tested by
scripts/tests/test_gx10_diagnostics.py. The flow is deliberately
bottom-up — fix the layer under suspicion before debugging the layer
above it [scripts/gx10/README.md:21-41]. Read the ladder as the
chapter’s own history, rung for rung:
tcp_probe.py— “Answer exactly one question, fast: is the wire healthy?” Interpretation bands are in the docstring: ~9+ Gbps single-stream means look at the product path (pacing pin, ledger placement, producer health); ~3–6 Gbps means check MTU, socket buffers, and on the GB10 the ConnectX-7 driver (pre-580.142 drivers throttle the 200GbE ports to ~13 Gbps); and — the sentence that saves the next agent an afternoon — “Bimodal stalls of ~1 s under load are NOT a raw-link symptom; that pattern in the product lane points at the receiver’s durable reserve”[scripts/gx10/tcp_probe.py:2-31].durable_fsync_probe.py— reproduces the exact write+fsync+rename+dir-fsync reserve pattern against a candidate ledger directory; exits 1 past--max-tail-ms(§31.3)[scripts/gx10/durable_fsync_probe.py].handoff_report.py— turns one qualification packet’s retained receipts into the per-repetition phase table (producersched/first/ d2h/hash/pack/seal/wire/gbpsbeside receiverdrain/verify/install/ commit/seal_off) and maps patterns to causes: “wire stable + seal bimodal by ~1 s … the replay ledger’s directory fsync on a slow volume”; “d2horfirst_layergrowing across reps: producer-side slowdown”; “wire far below what tcp_probe.py measures raw: the sender’s pacing pin or a genuinely sick link, in that order”[scripts/gx10/handoff_report.py:2-33].restart_resident_producer.py/supervise_resident_producer.py— the fail-closed producer’s restart ritual (staleO_EXCLstartup receipt, RoPE cache, and socket must be moved aside; a baredocker restartis not enough) and its unattended supervisor that latches off after three consecutive failed starts[AGENTS.md, muser root; scripts/gx10/README.md:42-48].
One more measurement belongs in this section rather than in a results
table, because it is the one that let us stop suspecting ourselves: the
deep-payload soak that closed the arc. Eight
consecutive 130,815-token handoffs ran with zero producer deaths and
deterministic output, payload declining 6.87 → 3.47 Gbps across the soak
while every rep held ≥ the 3.0 floor. That decline is not nothing, and we
have not dressed it up — the soak is bounded, and is labelled in the
ledger as explicitly “not a 20-rep
W4 stability packet” [ledger §"bounded eight-handoff deep-load soak"; receipts final-campaign-20260823/attempt-4/soak/run-attempt-3/SOAK_VERDICT.json].
31.7 Tradeoffs
Four villains, four decisions — and none of the decisions was simply “remove the thing that hurt us.” Each had an obvious lever beside it that we did not pull, or pulled only within a stated scope. Here is what each choice bought and what it cost, in measured terms.
Pin vs no pin. The obvious alternative — remove SO_MAX_PACING_RATE
and let TCP self-clock at line rate — was never taken, and the measured
record explains why the pin survives at 8 Gbps: the sender’s own floor
guard origin (“the N-series pin of 500 MB/s protected the 3 Gbps product
floor on an unhealthy link” [scripts/gx10/llamacpp/muser_v2_send.py:58-66]),
the fail-closed readback discipline, and a burst schedule that is
architecturally coerced (§31.4). The measured cost of the wrong pin was
3.91 vs an available ~9.4 Gbps — a 2.4× self-cap; the measured benefit
of keeping a pin is a sender whose receipts can be validated against
payload_pacing_bps ≥ 4 Gbps at the daemon
[scripts/gx10/vllm/muser_native_prefilld.py:569-570]. Multi-stream
slicing, the other obvious lever, was “evaluated and rejected by the W0
measurement (a single stream already saturates)”
[docs/disaggregated-prefill-sealing-plan-20260818.md §5 W1].
Evidence volume vs internal disk. The append-only evidence volume is
the campaign’s integrity substrate — and the worst possible home for a
0.22-ms-median, 691-ms-tail directory fsync on the ACK path. The
measured consequence of getting this wrong was CV 21.40% on a five-rep
TTFT packet; the measured consequence of fixing it was CV 0.56% at
median 1.596 s [ledger §T-series T2]. The design resolution keeps both
virtues: evidence stays append-only on the big disk, operational state
moves inside, and the boundary is enforced by the bind-time probe
rather than remembered by operators [crates/muser-cluster/src/receiver.rs:108-148].
EEE on vs EEE off. Energy-efficient idle is a fine default for a
mostly-quiet office link and a measurable liability for a link whose
workload is 41–47 s of silence followed by 1.74 GB. The campaign’s
disposition is scoped, not global: EEE-off is the enrolled link
invariant for the disaggregated lane, ships “as production guidance,”
and shallow 2,048-class packets had passed repeatedly with EEE
enabled-active earlier in the campaign (the F-series and N5 packets)
[ledger §EEE link ruling; docs/nvfp4-fast-lane-evidence-20260817.md].
What EEE-off buys: the 6.42 s ladders vanish and arm B’s floor holds at
6.995 Gbps [ledger §EEE A/B]. What it costs: a standing operator
instruction, and one producer death during the EEE-off sequence’s ninth
consecutive deep handoff that remains an open
sustained-deep-load follow-up [claims #13].
Clocks. Every wire-rate number in this chapter is
TCP_INFO.busy_time-based or receiver-drain-based because the two
convenient alternatives — userspace send time and receiver first-read —
were measured to be wrong (buffer- and compute-dependent), and an
entire five-rep row at CV 8.9985% had to be retired as failed-metric
evidence before the gate was re-specified to a per-rep floor
[ledger §P4, §N5; docs/disaggregated-prefill-sealing-plan-20260818.md §7.4].
31.8 Where the gap lives
This chapter’s “gap” is the spread between the raw ceiling and installed
payload. Where does it live, post-ladder? At the shallow end, the
wrapper23-class cell sits at ≥6.23 Gbps against a 9.4-class raw path:
the residue is TLS+framing+per-segment verify overhead and the pacer’s
deliberate ~15% margin under line rate, with a measured ~133 ms
pacer-drain tail on the 2,048 cell [docs/disaggregated-prefill-sealing- plan-20260818.md §5 W2]. At the deep end, the floor is wire-dominated
by construction — 1.82 GB cannot arrive faster than physics — which is
precisely why the reuse machinery of Chapters 25–26 (warm hits, delta
handoffs) attacks the bytes, not the link. And the variance component
of the gap, the part that looked like mystery for a week, lived
entirely in our own footprint: a pin, an fsync, a power saver. None of
it was the network.
31.9 What comes next
Bytes now arrive intact, paced honestly, on a link whose invariants are written down. But intact is not the same as correct. The producer on the far side of Figure 31.1 computes your prefill KV with CUDA kernels, NVFP4 tensor cores, and reduction orders no Metal kernel reproduces — and the Mac then decodes from those bytes as if they were its own. What does “the producer’s KV is good enough” have to mean before that is not faith but engineering? That is Ch 32 — the trust chapter: exact-token policies, declared bounded-logit rules, the integer-dot anchor, and the drift record that keeps every one of those words honest.
References
[AGENTS.md]— Muser root working agreements: the 2026-08-18 durability lesson (operational state internal, evidence append-only), the ~9.4 Gbps healthy reference,en1prohibition, the gx10 tool cheat sheet.[ledger §T-series]—docs/goal-parity-ledger-2026-08.md: T0 raw ceiling (9.40 Gbps), T1 pacing pin (3.91 → 5.89 Gbps), T2 seal-stall root cause (ledger dir-fsync tail 691 ms; TTFT 1.596 s / CV 0.56% after the move), T3 clean-image validation, the final wrapper23 packet (1.493 s / CV 0.22% / ≥6.23 Gbps).[ledger §N2],[ledger §N5],[ledger §P4]— the EEE collapse table (0.062–5.526 Gbps, ~90×), the 4 Gbps pacing-ceiling origin, and the wire-clock ruling (TCP_INFO.busy_time).[ledger §"EEE link ruling — operator decision (2026-08-20)"],[ledger §"EEE A/B at 130815"]— the 6.42 ± 0.03 s retransmission ladders, the enrolled EEE-off invariant, the intervention A/B (138.886 s / CV 2.0013% vs 137.405 s / CV 0.576%, floor 6.995 Gbps).[ledger §"Final GX10 campaign attempt 4"],[ledger §"Post-router GX10 lane requalification"],[ledger §"bounded eight-handoff deep-load soak"]— post-rebuild asymmetry (9.256 vs 6.161 Gbps), the re-anchored P4 packet, the soak.[claims #6],[claims #9],[claims #13]—docs/launch-claims.md: the 1.493 s / ≥6.23 Gbps and 137.405 s / 4.149× EEE-off scopes; wizard rates 9.812/8.887/8.690 Gbps; producer self-recovery boundary.[docs/disaggregated-prefill-sealing-plan-20260818.md]— §2 payload (≈146 MiB at 2k), §4 link floors (224 ms / 1.06 s / 3.75 s at 3.9 Gbps), §5 W0/W1 (the 500 MB/s pin, the 4→8 Gbps raise), §7.4 the link-gate re-spec, the 2026-08-23 topology amendment.[docs/kvpack-merge-handoff-20260820.md]— §3 D1 (payload 1,823,184,896 B, reconciled; the “~7 GB” correction), §6 “Pacing reality” (SWA ~82 MB early, NoPE bulk behind layer 51).[docs/nvfp4-fast-lane-evidence-20260817.md]— installed payload 3.910 Gbps / CV 0.401% and the 2.699 s / CV 21.40% packet.[crates/muser-cluster/src/security.rs:355-491]—ReplayLedgeradmission/reserve semantics (reserve before publication, latched degradation) and the write+fsync+rename+dir-fsyncpersist_replay_state.[crates/muser-cluster/src/receiver.rs:108-206]—check_ledger_volume/probe_ledger_reserveand the bind-time refusal; the module doc’s “cannot accidentally bypass” guarantee.[crates/muser-cluster/src/schedule.rs:84-157]— the span schedule: NoPE tiles over[cut, position), SWA over the trailing window, layer-major order.[scripts/gx10/llamacpp/muser_v2_send.py:54-76]— the 8 GbpsHANDOFF_PACING_BYTES_PER_SECOND, its rationale docstring, theMUSER_GX10_PACING_BYTES_PER_SECONDoverride, fail-closed readback.[scripts/gx10/vllm/muser_native_prefilld.py:514-577]— producer receipt validation incl.payload_pacing_bps >= 4_000_000_000and thelinux-tcp-info-busy-time-v1wire source.[scripts/gx10/tcp_probe.py],[scripts/gx10/durable_fsync_probe.py],[scripts/gx10/handoff_report.py],[scripts/gx10/README.md]— the diagnostic ladder and its interpretation bands.[measured-numbers §1e]— the book’s wire-rate table incl. the retired CV 8.9985% row and the asymmetry caveat.- Ch 22, Ch 24, Ch 26 — the byte arithmetic and the reuse machinery that shrinks this chapter’s invoice.
- Ch 30 — the authentication machinery under everything here; Ch 32 — what “intact but correct” must mean next.
Chapter 32 — Precision across the handoff
status: polished · path: Muse Glimmer, pinned Muser tree
Prerequisites: Chapters 5–7 (the two weight lanes and their arithmetic), Chapter 20 (the soft cap and why it changes logit gaps), Chapters 28–31 (the producer, the transport, and the wire it rides). This chapter assumes you believe the bytes arrive intact — Chapter 31 bought you that — and asks the harder question: are they right?
Chapter 31 ended on a deliberately sharp distinction: intact is not the same as correct. The Handoff V2 machinery of Chapter 30 proves the bytes on the Mac are the bytes the producer signed; nothing in a MAC tells you the producer’s arithmetic was worth signing. This chapter is the one this book was explicitly demanded to contain — the user’s own words were that what they wanted read out of this codebase was “the special care we had for precision while bringing prefill from vLLM” — and it is where that care lives: in declared policies, sealed bounds, mode-separated identities, an integer-dot anchor, and a drift record that is published even when one row of it is unflattering.
The demand is not sentimental. Ask first what breaks if this is wrong. When the GX10 prefills, a different machine — CUDA kernels, NVFP4 tensor cores, cuBLAS reduction orders, Blackwell software E2M1 rounding — computes the KV cache that the Mac’s decoder will treat as ground truth for every subsequent token. Not for one token: for all of them. A producer that is slightly wrong does not announce itself with a crash. It hands over a plausible cache, and the Mac then generates fluent, confident text on a foundation nobody checked.
We went in expecting the disagreements to be rounding noise. They were
not even the same kind of arithmetic. The two engines do not agree on
how to sum: the wizard campaign traced one mismatch to “CUDA’s serial
128-dim attention reduction vs Metal’s 32-lane tree, and F32 vs F16
residual materialization,” and that only surfaced after a layer-0 ladder
chase (attn_norm-0 element 4 → K RoPE 256 → attn_out-0 element
4,096) isolated two arithmetic-ABI splits. We kept the trail:
[measured-numbers §2 Arc 4; ledger §2b, 2026-08-24].
Nor is this a Muser embarrassment that trying harder would fix. Nobody
in the field achieves bitwise CUDA↔Metal equivalence — llama.cpp itself
uses tolerance-based backend diffs [docs/disaggregated-prefill-sealing- plan-20260818.md §4]. So “good enough” has to be a contract, and the
contract has to be checkable. This chapter is about the three contracts
Muser actually runs.
32.1 The trust ladder
Before the details, the shape. The question to carry through the rest of the chapter is not “is the handoff correct” — that is unanswerable as posed — but how much agreement did we buy, and what did each level of agreement cost? Muser’s disaggregated lanes answer it as a three-rung ladder, and every rung names its price and what it rules out (Figure 32.1):
THE TRUST LADDER (strictest at the top)
┌─────────────────────────────────────────────────────────────────────┐
│ RUNG 3 — BIT-EXACT FULL HANDOFF (combined kquant lane) │
│ exact target tokens AND exact FULL logits (max/mean delta = 0) │
│ AND exact DFlash tokens/trace │
│ cost: a versioned cross-vendor arithmetic ABI — wizard │
│ attempts 10–31 chasing one f16 ULP in layer-1 V into │
│ 51.7 M differing logits (4–7 accelerator-hours) │
│ rules out: any logit-level doubt at all; what remains is │
│ scheduling and speed │
├─────────────────────────────────────────────────────────────────────┤
│ RUNG 2 — EXACT TOKENS + DECLARED BOUNDED LOGITS (native/text) │
│ greedy tokens bit-exact; full-logit drift must fit a rule that │
│ is WRITTEN IN THE FROZEN IDENTITY: max |Δ| < 11, mean |Δ| < 1.25│
│ cost: "zero drift" wording is prohibited; the envelope must │
│ be re-measured per identity; logits are never claimed │
│ equal │
│ rules out: token divergence, nondeterminism, unbounded or │
│ undeclared drift │
├─────────────────────────────────────────────────────────────────────┤
│ RUNG 1 — PARITY-WITHIN-NOISE (plain decode, both lanes) │
│ NVFP4 35.491 tok/s vs adjacent kquant 35.440 — a 0.14% split │
│ inside 0.03–0.13% CVs; "never call decode faster" │
│ cost: no throughput claim may lean on the split │
│ rules out: the idea that either lane is the other's inferior │
└─────────────────────────────────────────────────────────────────────┘
Figure 32.1: The trust ladder. Rung citations: rung 3 [claims #9; measured-numbers §1l]; rung 2 [scripts/gx10/vllm/native_onboarding_ identity_v1.json:57-71; crates/muser-server/src/node/smoke.rs:451-469];
rung 1 [ledger §P1.3; claims #11]. A lane picks its rung by declaring
it at enrollment — it cannot drift between rungs unnoticed, because the
rung is part of the identity (§32.3).
The rest of the chapter climbs the ladder from the middle out: rung 2 is the product lane and the most instructive (§32.2–32.3), rung 3 is what the wizard bought with its ABI chase (§32.4), rung 1 is where decode lands (§32.5). Then the apparatus underneath all three: the soft cap’s role in the contract’s units (§32.6), the integer-dot anchor (§32.7), mode-separated cache identities (§32.8), the measured drift record and deep content controls (§32.9–32.10), the reference lock (§32.11), and the reason the Mac never trusts when it can re-check (§32.12).
32.2 Declaring the policy: the rule is written before the run
Start with the question this section answers: when two engines cannot agree exactly, who decides how much disagreement is acceptable — and when do they decide it? The second half is the part with teeth. A rule invented once the outputs are in hand is not a rule; it is a description of what happened, wearing a rule’s clothes. So Muser’s rule is written down first, and written somewhere it cannot be quietly edited later.
The native/text lane’s contract is therefore not a wiki page or a convention; it is a field in the frozen onboarding identity that both peers pin:
// scripts/gx10/vllm/native_onboarding_identity_v1.json:57
"onboarding_qualification": {
"schema": "muser.native-text-onboarding.v1",
"prompt_tokens": 2048,
"output_tokens": 256,
"repetitions": 3,
"prompt_sha256": "149ac0d9c37c957823e53c0637b52a38f2ac601089dbda9f98eec4bc5f369030",
"require_exact_tokens": true,
"require_deterministic_remote_output": true,
"full_logits": {
"mode": "bounded-drift",
"maximum_absolute": 11.0,
"maximum_mean_absolute": 1.25
},
"basis": "muser-receipt://nvfp4-f4-native-text-p4-accelerator-20260817/20260817T074427Z-44c761545afa47d78f8407734f8d2145.command.log"
}
Deconstruct it. Tokens are exact — require_exact_tokens: true, no
tolerance, no exceptions. The remote output must be deterministic
across repetitions — a producer whose own output wobbles cannot be
gated at all. And full logits are bounded, not equal: the mode is
literally named bounded-drift, with two numbers — a maximum absolute
delta (11.0) and a maximum mean absolute delta (1.25) — plus a
basis receipt tying the numbers to the measurement they came from.
This is what “the special care” means in practice: the tolerance is
declared before any qualification run, in a file whose SHA-256 is part
of the lane’s identity, not chosen after seeing the outputs.
And the Mac side does not merely read that file — it re-derives it against a sealed constant and refuses to proceed if they disagree:
#![allow(unused)]
fn main() {
// crates/muser-server/src/node/artifacts.rs:188
const NATIVE_SEALED: SealedNativeIdentity = SealedNativeIdentity {
// … checkpoint, image, adapter, consumer, tokenizer, template,
// context-policy, RoPE-cache digests …
target_cache_identity_sha256:
"a3bbd72fc16116322b5c9dc701f155d35349146b2af3e3b8465732b7df1eabd0",
prompt_sha256: "149ac0d9c37c957823e53c0637b52a38f2ac601089dbda9f98eec4bc5f369030",
// …
maximum_logit_absolute: 11.0,
maximum_logit_mean_absolute: 1.25,
};
// crates/muser-server/src/node/artifacts.rs:472
if qualification.schema != "muser.native-text-onboarding.v1"
|| qualification.prompt_tokens != 2_048
|| qualification.output_tokens != 256
|| qualification.repetitions != 3
|| !qualification.require_exact_tokens
|| !qualification.require_deterministic_remote_output
|| qualification.full_logits.mode != "bounded-drift"
// … finite, positive, and exactly the sealed values …
|| qualification.full_logits.maximum_absolute != NATIVE_SEALED.maximum_logit_absolute
|| qualification.full_logits.maximum_mean_absolute
!= NATIVE_SEALED.maximum_logit_mean_absolute
|| qualification.basis.is_empty()
{
return Err("native onboarding qualification rule is invalid".into());
}
}
(lines 188–210 and 472–488 elided in the middle: the digest pins; see file). Notice what that closes off. An operator who “relaxes” the rule to 12.0 in the JSON now fails onboarding against the compiled constant — the tolerance can only change through a code change that says so, in a commit, in front of a reviewer. Widening a bound stops being a configuration decision and becomes an edit to the product.
Where did the two ceilings come from, then? Not from taste. We measured
the lane first and set the rule a hair above what we saw. The
fast-vs-exact envelope on the 2,048/256 five-repetition comparator was
max/mean 10.884401 / 1.233789 with 100% token agreement in all five
runs, and 7.270581 / 1.040619 on the 32-token standard fixture
[docs/nvfp4-fast-lane-evidence-20260817.md §Determinism]. Sitting the ceiling that close to the observed envelope
is a trap we set for ourselves on purpose: a lane that drifts even
slightly worse than the lane we measured does not squeak through, it
fails onboarding. It is also why the claims register prohibits the
flattering framing — the fixture “may be described as deterministic and
token-identical only with its scope; zero drift … remain[s] prohibited”
[claims #10]. We are allowed to say the drift is bounded. We are not
allowed to say there is none.
32.3 Checking the policy: three ordered handoffs, per sample and per summary
A declared rule is only as good as its enforcement, so the next question is: who reads the rule at run time, and what happens to a run that misses it?
The wizard’s smoke step runs the qualifier three times. What the
operator watches scroll past is the contract restated in English —
“three ordered 2,048/256 exact-token handoffs with bounded full-logit
drift” [crates/muser-server/src/node/smoke.rs:43-52] — driven by the
native recipe’s own flags,
--onboarding-native --drift-graded --reference-once
[crates/muser-server/src/node/smoke.rs:644-650]. The middle flag is
the interesting one. Left alone, the qualifier refuses on any logit
digest mismatch at all [crates/muser-bench/src/remote.rs:690-694],
which is the correct default for a lane claiming equality and useless
for a lane claiming a bound. --drift-graded trades refusal for
measurement: retain the comparison, compute the deltas, then judge them.
And the wizard judges twice — once per sample, once over the summary
arrays:
#![allow(unused)]
fn main() {
// crates/muser-server/src/node/smoke.rs:451
if let Some(rule) = native {
let maximum = value
.get("remote_local_logit_max_abs")
.and_then(serde_json::Value::as_f64)
.ok_or_else(|| "native sample omits maximum full-logit drift".to_string())?;
let mean = value
.get("remote_local_logit_mean_abs")
.and_then(serde_json::Value::as_f64)
.ok_or_else(|| "native sample omits mean full-logit drift".to_string())?;
if !maximum.is_finite()
|| !mean.is_finite()
|| maximum > rule.full_logits.maximum_absolute
|| mean > rule.full_logits.maximum_mean_absolute
{
return Err(format!(
"native full-logit drift exceeds its identity rule: max={maximum}, mean={mean}"
));
}
}
}
The summary-level check then re-verifies that all three repetitions
agree on token rate exactly 1.0, that every per-repetition maximum and
mean sits inside the rule, and that the fast generated-token digest is
present [crates/muser-server/src/node/smoke.rs:538-569]. A missing
field is a refusal, not a zero (“native sample omits maximum full-logit
drift”). Put the same idea the other way round, because it is the one
readers hand-wave past: a sample that forgot to report its drift is not
a sample with no drift. Silence is not a measurement. Absent evidence is
failed evidence — the same fail-closed posture as everywhere else in
this book.
All of which is machinery until it meets a real node. The measured
result of running exactly this gate live: native/text attempt 9 passed
all seven progress labels with three exact-token handoffs under the
bounded-logit rule (deltas inside 10.884401 / 1.233788776) at payload
rates 6.866 / 6.976 / 6.708 Gbps, and the node reached healthy. The
run that proved it is retained: [claims #9; measured-numbers §1l; receipt wizard-validation-20260823/attempt-9-native-live-20260824T051305Z/validation-summary.json].
32.4 Rung 3: what bit-exact costs
One rung up sits the combined kquant lane, and it asks for the whole top box of Figure 32.1: exact target tokens, exact full logits — the digests equal, deltas zero — and exact DFlash tokens and trace. This is the rung we assumed would be a scheduling problem. It was not, and the story of finding that out is the most useful thing in the chapter.
With the transport already trustworthy, we expected the remaining work
on the combined lane to be plumbing: line the sequence up, pin the
fixtures, watch the digests agree. The digests did not agree, and the
wizard spent attempts 10 through 31 finding out why. The trigger was
as small as a divergence can be — a single F16 ULP in layer-1 V, the
last place anyone wants to go hunting — and by the time it had
propagated through the stack it had become 51.7 M differing logits
[ledger §2b, 2026-08-24]. That escalation is the point: at the top of
the ladder there is no such thing as a small disagreement, because
nothing downstream damps it.
What the chase found underneath were the two reduction-order splits
named in this chapter’s opening, and no amount of retrying was going to
dissolve them. They had to be specified. So the fix was a versioned
cross-vendor arithmetic ABI, and we know what it cost: 4–7
accelerator-hours [measured-numbers §1l]. Attempt 31 then passed 7/7
with bit-exact logits and payload rates 9.812 / 8.887 / 8.690 Gbps
[claims #9; receipt wizard-validation-20260823/attempt-31-combined-full-20260824T132639Z/validation-summary.json].
The lesson generalizes the integer-exact philosophy of Chapter 7 §7.4.2: when you cannot reproduce another engine’s reduction topology, you either (a) pin an ABI that makes the controllable parts associativity-free — the a16-q8 kernel’s trick — or (b) chase and pin each divergence site until the digests match, which is what the wizard did. What you may not do is (c) declare a tolerance and quietly widen it each time a cell fails. The 4–7 accelerator-hours are the price of (b), and the receipt chain is what makes the spend auditable.
32.5 Rung 1: parity-within-noise, never “faster”
At the bottom of the ladder, the plain decode lanes — where the question
is not whether the two lanes agree, but whether either one is entitled
to brag. The paired P1.3 cells measured native NVFP4 decode at
35.490711722 tok/s (CV 0.130%) against an adjacent kquant control at
35.439527527 tok/s (CV 0.037%) — a +0.1444% split inside the noise
of cells whose CVs bracket it
[ledger §P1.3]. The claims register’s standing instruction is three
words long: “Never call decode faster” [claims #11]. The trust-relevant
content of rung 1 is that neither lane is entitled to be called the
other’s quality fallback by speed arguments — the native lane’s
differences from kquant are quality-shaped, not speed-shaped, and
§32.10 is where they live.
32.6 The soft cap is part of the contract’s units
A tolerance is a number and a unit, and the unit is the part everyone
forgets. So before trusting a bound, ask what it is measured in.
Chapter 20 §20.7 proved the soft cap is order-preserving (greedy tokens
cannot move) but gap-compressing — by hand, a 6.00 raw gap becomes 4.915
capped, a 20.0 gap becomes 4.049. Carry that into this chapter: every
number in the bounded-drift rule is a delta of capped logits, because
both engines apply 1/√26 then tanh@20 as the last step before these
bytes are compared [Ch 20 §20.4, §20.7]. That has two consequences you
must hold simultaneously. First, the bounds 11.0/1.25 are units of the
contract — recalibrate either engine’s scale-and-cap order and the
tolerance is meaningless, which is why the whole transform is pinned in
both engines. Second, the cap compresses exactly the large-logit regime
where the two lanes’ numerics disagree most, so the measured drift
envelope is smaller than an uncapped comparison would produce — the
bound is doing quiet work, and “without the cap the same deltas would be
much larger and the tolerance would have to be re-derived” [Ch 20 §20.7]. A reader comparing Muser’s 11.0 against some other engine’s
uncapped logit tolerance is comparing different units — do not.
32.7 The integer-dot anchor: a producer that exists to be compared against
Every ladder needs a plumb line. “Bounded drift” is a claim of the form no farther than this from something — and the something has to be an engine we can re-run on demand, or the bound is unfalsifiable. Muser’s plumb line is the exact producer mode: the same GX10 node, the same transport, but a producer whose NVFP4 arithmetic is integer-dot deterministic — built to be compared against, not served. The shipped lane matrix gives it its own row:
| Lane | Prefill | Decode | Speculative | Intended use |
|---|---|---|---|---|
| Native NVFP4 | Spark tensor-core FP4 | Mac NVFP4, 35.491 tok/s | Rejected (fail-closed) | Fast product lane |
| kquant/reference | Reference path | kquant, 35.440 tok/s | 107.9 tok/s | Speculative + reference lock |
| Exact NVFP4 flag | Integer-dot verification producer | Mac NVFP4 | Verification only | Deterministic anchor |
[docs/muser-architecture.md, lane matrix]
The mode is selected producer-side only, by the Python environment
flag MUSER_NVFP4_EXACT=1. It does not exist anywhere in the Rust tree
— a Mac-side reviewer will find nothing to set — and the native
benchmark refuses to run with it set, because mixing modes would
invalidate the claim being measured:
# scripts/gx10/vllm/benchmark_native_prefill.py:98
# The benchmark is intentionally stock: importing muser_vllm exact modules
# or setting MUSER_NVFP4_EXACT would invalidate the native-path claim.
if os.environ.get("MUSER_NVFP4_EXACT") == "1":
parser.error("native benchmark refuses MUSER_NVFP4_EXACT=1")
os.environ["MUSER_NVFP4_EXACT"] = "0"
The resident native daemon likewise pins MUSER_NVFP4_EXACT=0 into the
container’s environment [scripts/gx10/vllm/muser_native_prefilld.py:446].
(Chapter 7 §7.7 introduced this split; the point to add here is what the
anchor is for.) The exact lane’s value is that its outputs are stable
enough to be a reference: the G3 live recheck re-ran the retained strict
cell after routing changes and reproduced every retained digest
bit-for-bit — generated-token SHA, full-logit digests, payload SHA, all
52 seam digests, KV max deltas 9.625/18.4580078125, logit errors
7.270581/1.040619, 32/32 tokens [docs/nvfp4-fast-lane-evidence-20260817.md §G3]. When you ask “how far has native drifted?”, you are asking it
against this lane’s numbers.
32.8 Mode-separated cache identities, and refusing the unknown
An anchor is only an anchor if you can tell it apart from the thing it
anchors. Bounded drift is safe only when native KV and exact KV can
never be mistaken for each other — a cache entry produced by tensor
cores must not be served to a session whose contract says “integer-dot
anchor,” because such a session would then be measuring drift against
drift and reporting the answer as ground truth. The fast-lane evidence
note states the rule in one line: exact and native
producers “use mode-separated target-cache identities, so exact and
native KV entries cannot alias” [docs/nvfp4-fast-lane-evidence-20260817.md §Product route]. Concretely, the receiver configuration carries a
target_cache_identity_sha256 field that is validated as a digest at
load [crates/muser-cluster/src/config.rs:41-42, 103-115], and the
sealed native identity pins the exact value (a3bbd72f…,
[crates/muser-server/src/node/artifacts.rs:206-207]). A cache identity
is a property of the recipe, not of the model file alone.
The same fail-closed logic covers recipes Muser has never heard of. Enrollment maps a producer lane to exactly one qualification contract, and an unknown lane dies before any key is minted:
#![allow(unused)]
fn main() {
// crates/muser-server/src/node/registry.rs:39
/// Exact qualification contract selected by the enrolled producer lane.
/// Keeping this exhaustive beside `ProducerKind` means adding a lane without
/// choosing a recipe is a compile error; an unknown serialized lane is
/// refused while loading the registry, before enrollment can mint keys.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum QualificationRecipe {
KquantTargetPlusDflash,
NativeText,
}
}
Two recipes exist — KquantTargetPlusDflash (the rung-3 combined lane)
and NativeText (the rung-2 bounded-drift lane)
[crates/muser-server/src/node/registry.rs:49-76] — and the mapping is
total: Llamacpp → KquantTargetPlusDflash, Native → NativeText.
Because the compiler forces the match to be exhaustive, a future lane
must choose a recipe to compile; because loading refuses unknown
serialized values, an old registry file cannot silently re-interpret
itself. The architecture document’s summary is the sentence to quote
whole: “Exact and native producers derive different target-cache
identities; an unknown recipe is refused at enrollment”
[docs/muser-architecture.md §Durable and remote KV]. (The native lane
additionally cannot enroll a DFlash identity at all
[crates/muser-cluster/src/config.rs:128-131] — that refusal is Chapter
33’s opening subject.)
32.9 The measured drift record: what bounded actually means
A bound with no published measurement behind it is empty theater: it
tells the reader what we promised, never what actually happened. So here
is the drift the rule bounds. The fast-lane evidence note’s determinism
section is that record (Figure 32.2), for the standard 2,048-token
prompt with 32 greedy tokens [docs/nvfp4-fast-lane-evidence-20260817.md §Determinism]:
| Quantity | Measured envelope (fast vs exact) |
|---|---|
| Greedy token agreement | 32/32, 100% |
| First divergent token | none |
| Full-logit maximum absolute error | 7.270581 |
| Full-logit mean absolute error | 1.040619 |
| KV key maximum absolute delta across 52 layers | 9.625 |
| KV value maximum absolute delta across 52 layers | 18.458008 |
| Per-layer KV key mean-absolute range | 0.015648–0.431876 |
| Per-layer KV value mean-absolute range | 0.044231–0.387556 |
Figure 32.2: The standard-fixture drift envelope. The KV deltas are the
raw material of everything downstream: 9.625 on keys and 18.458 on
values, per layer, feeding attention for every future token — and the
greedy tokens still agree 32/32. The 2,048/256 five-repetition
comparator extended the same result — 100% token agreement in all five
runs, max/mean 10.884401/1.233789 — and immediately disclaims its own
scope: “This demonstrates deterministic, coherent output on the standard
fixture; it does not prove a general zero-divergence contract”
[docs/nvfp4-fast-lane-evidence-20260817.md §Determinism]. And the seam
itself is reproducible: repeated A requests, A/B/A queue interleaving,
and a full engine restart reproduce the same 52 layer hashes, the same
seam SHA-256, and the same payload SHA-256 [docs/nvfp4-fast-lane- evidence-20260817.md §Determinism]. Drift, yes — but deterministic
drift, which is the difference between a risk you can bound and one you
can only fear.
32.10 Deep content controls: the sensitivity that was published, not hidden
A 2,048-token fixture is a shallow probe of a 131,072-position model. So the honest next question was the uncomfortable one: what is happening further out, where we had not yet looked? The deep ladder went looking, and this is the stretch of the record that contains a row that fails. We walked into it in three stages, and the order we walked them in matters as much as the outcome:
- E1 calibrated the yardstick first. Before judging native against
kquant, the campaign measured an accepted alternate quant (Q6) against
kquant on the same rows and set each cell’s disagreement gate from that
two-sided 95% Wilson bound plus two points — calibrated gates of
8.796–15.299% across the long-context cells. Native exceeded the band
by 1.746–1.996 points at 8k/16k/32k
[docs/nvfp4-fast-lane-evidence- 20260817.md §E1]. A tolerance derived from a second real quantization cannot be accused of being tuned for convenience. - E2 asked whether length was the culprit, and the answer was no.
The natural hypothesis — written down before the run, which is what
makes the answer worth anything — was that native drift grows with
context: past some length the lane goes bad, and you route around it
with a number. E2 held content fixed across three nested documents
(rust, python/shell, docs) and varied length. Rust and python passed
every disagreement row at every length 2k–32k; only the docs
document exceeded its gate at 8k/16k/32k, and the 512-token position
profiles kept the sensitive regions localized rather than showing a
length transition — so the preregistered replicated-length criterion
was false
[docs/nvfp4-fast-lane-evidence-20260817.md §E2/E3]. The tidy story we expected was not on offer. What we had instead was a content effect, which is far harder to route around than a length threshold, because content is not a knob on the request. - The stage-3 yardstick published the exceedance. At 65,536 tokens
on the docs corpus, native-vs-kquant top-token disagreement was
15.134% against a calibrated gate of 13.339% (relative PPL +4.227%)
— recorded, published, and explicitly scoped: a content-local
sensitivity that “did not replicate cross-document”; persistence at
131,008 could not be tested because the corpus is too short
[claims #10; receipts kvpack-ladder-20260820/attempt-5-…-stage3-compact/stage3-e2-quality/].
So what do you do with a row like that? Before publishing it, we tried
to make it go away. The route-exhaustion matrix put eight native vLLM
runtime variants (chunked prefill, BF16, Triton, batch-invariant
CUTLASS, FlashInfer B12X/cuDNN, engine ceiling) against the docs 65,536
row, on the expectation that one of them was the real culprit and the
sensitivity was a configuration mistake wearing a numerics costume. All
of them failed identically or worse, and no runtime change was promoted
[ledger §"native-route exhaustion"; measured-numbers §1i]. There was
nothing left to out-configure.
That left two cheap ways out, and the record rejected both. Hiding the
row would violate the publish-the-sensitivity rule
[measured-numbers §6 rule 9]. Capping context — the “native up to N,
kquant beyond N” policy — sounds principled right up until you look at
which cells actually fail: a cap would have silently included the
failing 8k cell while failing to cover the passing deeper cells, the
non-monotonicity that killed exactly that policy back in the D1 routing
ladder [docs/nvfp4-fast-lane-evidence-20260817.md §D1]. So no context
cap was imposed through the measured 32k range, and the sensitivity was
published instead.
32.11 The reference lock: an explicit lane, not an automatic proxy
What is a user who does care about the docs-class sensitivity supposed
to do? The answer is deliberately manual: “Users who need a reference
lock select the existing kquant lane explicitly”
[docs/nvfp4-fast-lane-evidence-20260817.md §E2/E3]; the architecture
document repeats it as a first-class property — “the kquant lane is the
explicit reference lock for that class” of high-entropy
numeric/digest/tabular documentation content [docs/muser-architecture.md, lane-matrix paragraph]. Why explicit? Because no automatic
disagreement proxy is claimable — building one “would require a second
reference computation” [docs/nvfp4-fast-lane-evidence-20260817.md §E2/E3], i.e., the proxy would itself need the very reference it is
pretending to replace. The same logic explains the lane matrix’s
“kquant/reference” row: a reference lock is a lane you can route to,
whose own parity is maintained against the pinned llama.cpp comparator
(the parity ledger is Ch 38’s
subject), not a background oracle.
32.12 Why the Mac re-checks rather than trusts
Everything above describes contracts the producer satisfies. The final
layer of care is that the Mac never takes even a satisfied contract on
faith at qualification time. The remote qualifier’s module doc states
the design in four lines — it uses “the same RemoteReceiver as
serving” (so a benchmark cannot bypass the machinery), and “every sample
performs cold local recomputation and an authenticated remote install,
compares 256 greedy tokens plus every full target-logit row, and retains
producer/transport phase times needed to prove real overlap”
[crates/muser-bench/src/remote.rs:3-8]. The trust direction matters:
the remote-installed KV is decoded and compared against a local cold
recompute, token by token and logit row by logit row, before a lane is
called qualified. The producer is not asked to certify itself; it is
checked against a computation the Mac performed on its own.
Serving then inherits the disposition, not the measurement: once
enrolled, the lane’s correctness lives in its identity and gates —
and the one machinery that must never trust a draft in flight,
speculative decoding, is under an absolute rule: “DFlash drafts are
always verified by target distributions” [docs/muser-architecture.md §Model and engine], with the remote qualifier enforcing an acceptance
floor of DFLASH_ACCEPTANCE_MINIMUM = 0.95
[crates/muser-bench/src/remote.rs:33]. The all-accept diagnostic that
proved the native target+DFlash path correct (240/240 drafted tokens,
canonical digest retained) is carefully recorded as establishing
“correctness of the diagnostic, not competitive speculative throughput”
[docs/nvfp4-fast-lane-evidence-20260817.md §G1 tail] — correctness and
speed claims are never allowed to borrow each other’s evidence.
32.13 Tradeoffs
Bounded drift vs bit-exactness. Rung 2 gives up logit equality and in
exchange gets a lane the tensor cores can actually serve at speed; rung 3
gets equality and pays an arithmetic ABI plus per-divergence-site pinning
(4–7 accelerator-hours for the wizard’s fix [measured-numbers §1l]).
The measured consequence of choosing rung 2 honestly: a drift envelope
of 10.884401/1.233789 published next to the tokens-exact claim, and the
prohibition on “zero drift” wording [claims #10]. The measured
consequence of rung 3: digests equal, deltas zero, at 9.812/8.887/8.690
Gbps [claims #9] — but only after the ULP chase.
Publishing the sensitivity vs capping the lane. The docs@65,536
exceedance (15.134% vs 13.339%) could have been “fixed” by a context
cap; the D1 ladder measured why that fails (non-monotonic, no honest N
exists) and E2’s preregistered criterion formally rejected the
length-effect interpretation [docs/nvfp4-fast-lane-evidence-20260817.md §D1, §E2/E3]. What publishing costs: one uncomfortable row in the
record, carried forever. What it rules out: a user unknowingly serving
the sensitive class on the native lane with no way to know
[measured-numbers §6 rule 9].
Explicit reference lock vs automatic proxy. An automatic
disagreement-triggered fallback was rejected because the proxy needs a
second reference computation to be trustworthy
[docs/nvfp4-fast-lane-evidence-20260817.md §E2/E3]. The cost of the
explicit lock is that routing is a human decision; the benefit is that
no silent heuristic ever re-ranks the lanes on unaudited numbers.
Integer-dot anchor vs serving it. The exact producer is qualified
enough to reproduce every retained digest bit-for-bit
[docs/nvfp4-fast-lane-evidence-20260817.md §G3] and slow enough that
an attempted 33,024-token exact-lane scoring pass was stopped during
warmup when “the measured approximately 50x compute ratio” showed it
would spend the experiment box just warming the graph [docs/nvfp4- fast-lane-evidence-20260817.md §G1 intro]. The anchor is a plumb line,
not a product — which is why its matrix row says “Verification only”
[docs/muser-architecture.md].
32.14 What comes next
The trust ladder now stands complete: bytes intact (Chapter 31), tokens exact, logits either equal or bounded-by-declared-rule, identities mode-separated, sensitivities published, and a reference lane one switch away. One precision machinery in this system remains unexamined — the one that makes a guess participate in exact inference: speculative decoding, where a five-layer draft proposes and the 52-layer target disposes, and where the same evidence culture had to pass a verdict on its own distributed variant. That is Ch 33: the local win, the fail-closed refusal, and the measured rejection — with receipts.
References
[scripts/gx10/vllm/native_onboarding_identity_v1.json:57-71]— the declaredbounded-driftrule (11.0 / 1.25), exact-token and determinism requirements, and its basis receipt.[crates/muser-server/src/node/artifacts.rs:188-210, 472-488]—NATIVE_SEALED(incl.maximum_logit_absolute: 11.0,maximum_logit_mean_absolute: 1.25, thea3bbd72f…cache identity) and the validation that refuses any other qualification rule.[crates/muser-server/src/node/smoke.rs:43-52, 451-469, 538-569, 644-650]— the recipe’s progress label, per-sample and summary bounded-drift checks, and the--onboarding-native --drift-graded --reference-oncewiring.[crates/muser-bench/src/remote.rs:3-8, 32-33, 690-694]— the qualifier’s cold-recompute + 256-token + full-logit-row comparison,LINK_GBPS_MINIMUM/DFLASH_ACCEPTANCE_MINIMUM = 0.95, and the non-graded refusal on logit digest mismatch.[crates/muser-cluster/src/config.rs:41-49, 103-115, 128-131]—target_cache_identity_sha256validation; native mode cannot enroll DFlash geometry.[crates/muser-server/src/node/registry.rs:29-76]—ProducerKind/QualificationRecipe, the exhaustive mapping, and the unknown-lane refusal before key minting.[scripts/gx10/vllm/benchmark_native_prefill.py:98-102],[scripts/gx10/vllm/muser_native_prefilld.py:446]—MUSER_NVFP4_EXACTis producer-side Python only; the native benchmark refuses it set; the daemon pins=0.[docs/nvfp4-fast-lane-evidence-20260817.md]— §Product route (mode-separated identities, Fallback B), §Determinism (the drift envelope and its disclaimer), §G1/§D1/§E1/§E2-E3 (widened fixtures, yardstick calibration, content controls, routing disposition), §G3 (bit-for-bit anchor recheck).[docs/muser-architecture.md]— the lane matrix (exact-flag row: “Verification only”), “unknown recipe is refused at enrollment,” “DFlash drafts are always verified by target distributions,” the kquant reference-lock paragraph.[claims #9],[claims #10],[claims #11]—docs/launch-claims.md: wizard attempts 9/31 with their exactness policies and rates; the docs@65,536 sensitivity scope and prohibited “zero drift” wording; 35.491/35.440 parity scope (“Never call decode faster”).[ledger §P1.3],[ledger §2b 2026-08-24],[ledger §"native-route exhaustion"]— the paired decode cells; the one-ULP wizard chase; the eight-variant exhaustion matrix.[measured-numbers §1i, §1l, §2 Arc 4, §6]— the quality-gate table, wizard validation cells, the arithmetic-ABI arc, and the claim- discipline crib sheet (publish the sensitivity; evidence wins).- Ch 7 §7.6–7.7 — Fallback B and the producer-mode preview this chapter builds on.
- Ch 20 §20.7 — the soft cap’s monotonicity and gap compression; why bounded-logit deltas are capped- logit units.
- Ch 30, Ch 31 — the authentication and wire machinery underneath; Ch 33 — the verification machinery that never trusts a draft.
Chapter 33 — Speculation: the local win and the distributed verdict
status: polished · path: Muse Glimmer, pinned Muser tree
Prerequisites: Chapter 8 (the DFlash draft — its context ABI, its window bug, and the four guarantees that make exact verification possible) and Chapter 7 (Fallback B’s refusal). This chapter completes the loop the draft opened: the accept/verify algorithm as code, the local lane’s measured scope, the rejections told with receipts, and the one experiment still alive.
Chapter 32 ended with a system that re-checks rather than trusts — every lane qualified by cold recompute, every draft verified against full target distributions. This chapter is about that last clause taken to its conclusion. Speculative decoding is the book’s most demanding precision machinery, because it deliberately introduces an approximate participant — a five-layer draft guessing what the 52-layer target will say — and then has to prove the approximation changed nothing. And it is the machinery Muser took furthest: to a local lane that wins on synthetic fixtures, to a fail-closed refusal on the native lane, and to a distributed variant that was built, measured, and rejected in a single day, with the rejection recorded so carefully that the receipts are still worth reading.
One framing before any number. In this chapter, ratios are llama ÷ muser (above 1.0 means muser wins), synthetic-versus-natural scope is load-bearing, and one famous number — 107.9 tok/s — exists in this record only as a bar, never as a result. Hold that; §33.1 explains why.
33.1 The local win, stated with its scope language
Start with the question the whole lane has to answer: what does guessing ahead actually buy, and under exactly which conditions? Decode spends its life waiting on memory, so the only way to go faster without changing the answer is to get more decisions out of each pass over the weights. That is the entire bet. The rest of this section is about what the bet paid — on which fixtures, at which lengths, and with which caveat riding on each number, because in this lane the caveats are not decoration. Two of the figures below mean the opposite of what they look like.
The mechanism in one paragraph, since Ch 8 §8.1 built it: decode is bandwidth-bound because each token reads the whole 16.76 GB artifact; speculation has a cheap draft propose k tokens so the target can verify them in one batched forward pass, k+1 rows of decision per weight read. The draft is pure overhead that pays only when its guesses are good; the target still makes every decision, which is what keeps the output exact.
The bar, and why it is a landmine. The campaign’s Stage B verdict
measured kquant DFlash speculative decode at 107.9136 tok/s median,
CV 0.200%, against llama’s 81.3047 — ratio 1.3273 (2,048+256
streamed, verify length 15, five reps) [ledger §L2 Stage B verdict].
For a while that was the headline of the whole campaign. Then the fix
described in §33.3 landed and moved the ground under it: the run had
been measured before the 2026-08-21 draft-window fix, on a draft
that was quietly attending half the context it had been trained on.
We did not delete the number, because every later lane in this chapter
was judged against it. We demoted it. It survives in the record as a
bar and nothing else, and the pre-fix ratios 1.3273/1.3012 are
superseded [measured-numbers §1b, §7].
What the lane wins now. After the fix, in retained fixed-window
synthetic packets, exact-token decode ratios are 1.23692× at 2,048,
1.20323× at 16,384, and 1.19616× at 32,768, with 5/5 exact reps per
depth [claims #15]. The claims row’s own instruction is part of the
claim: never generalize to natural text, native NVFP4, or untested
depths; the 8,192 and 65,536 cells of that family are single-rep
diagnostics (1.214†, 1.188†) [measured-numbers §1b].
Natural text is a different regime. On real corpora the two engines’
outputs diverge cross-engine, so speed stands without an exactness gate,
and the picture splits: spec decode wins python-like content (16,384:
1.186; 8,192 suffix: 1.321) and loses high-acceptance shallow text
(rust at 2,048: 0.931, improving only to 0.945 at verify length 7) —
llama’s lighter draft wins where acceptance is nearly free
[docs/benchmarks.md §2]. Sit with that asymmetry for a moment,
because it is the counterintuitive result of the section: the harder
the text is to predict, the better our speculation looks, and the
easier the text is, the more our heavier draft is just a tax. It is
also why serving froze verify-length 7 while the comparison harness
pins 15 [docs/benchmarks.md §2].
And at the deepest tested scope, the funded 131,008/48 packet crossed
end-to-end wall parity for the first time at 1.02536×
[claims #16]. The word wall is carrying weight there. The same run
offers a far more flattering 1.64960× decode figure, and that one
is barred — we may not quote it as a result, because its
first-round split is not an accounting-neutral cross-engine per-round
metric, per the 2026-08-23 ledger amendment. Both readings are
retained, the barred one included, which is the point of retaining
them [claims #16; measured-numbers §7].
So the local lane’s honest headline is: about 1.20–1.24× on three fixed synthetic fixtures with exact tokens, a wall-parity crossing at 131k, wins and losses on natural text by content class — and every clause of that sentence has a receipt.
33.2 The loop as code: propose, verify, accept — exactly
How can an approximate guesser touch a model’s output and leave that output provably unchanged? That is the question this section answers, and the answer is stranger than “we check the guesses.” The draft never decides anything. It proposes; the target judges; and the judging rule is arranged so that the tokens leaving the loop are distributed exactly as if the draft had never run at all. Get this rule wrong in a subtle way and nothing crashes — you simply ship a different model than the one you qualified. So it is worth reading the code slowly.
Chapter 8 pinned what the draft guarantees; here is the algorithm that consumes those guarantees. Acceptance happens on the CPU, against full target distributions — the target’s complete probability row per position, not a top-k sketch:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/sampling.rs:1033
pub fn verify_full_speculative_mt_ordered(
draft_tokens: &[u32],
draft_probabilities: &[Vec<f32>],
target_probabilities: &[Vec<f32>],
target_orders: &[Vec<u32>],
rng: &mut Mt19937,
) -> Result<SpeculativeDecision, SamplingError> {
// … geometry validation elided (row counts, widths, token bounds) …
for (index, (&token, (draft, target))) in draft_tokens
.iter()
.zip(draft_probabilities.iter().zip(target_probabilities))
.enumerate()
{
let token = token as usize;
let q = draft[token];
let p = target[token];
let acceptance = if q <= 0.0 { 1.0 } else { (p / q).min(1.0) };
if rng.uniform_f32() <= acceptance {
continue;
}
let mut residual = target
.iter()
.zip(draft)
.map(|(&p, &q)| (p - q).max(0.0))
.collect::<Vec<_>>();
let total = residual.iter().sum::<f32>();
if total <= 0.0 {
residual.clone_from(target);
} else {
for probability in &mut residual {
*probability /= total;
}
}
let order = target_orders[index]
.iter()
.copied()
.filter(|token| residual[*token as usize] > 0.0)
.collect::<Vec<_>>();
return Ok(SpeculativeDecision {
accepted: index,
next_token: sample_distribution_mt_ordered(&residual, &order, rng)?,
});
}
Ok(SpeculativeDecision {
accepted: draft_tokens.len(),
next_token: sample_distribution_mt_ordered(
target_probabilities.last().ok_or(SamplingError::Geometry)?,
target_orders.last().ok_or(SamplingError::Geometry)?,
rng,
)?,
})
}
}
(lines 1040–1059 elided: the geometry guard that rejects any shape or token-bound mismatch before a single RNG draw — see file.) Walk it once, slowly:
- For each proposed token, look up the draft’s probability
qand the target’spat the same token. Accept with probabilitymin(p/q, 1)— the maximal-coupling rule behind Leviathan et al. and the independent DeepMind formulation[arxiv:2302.01318; frontier §"The target identity invariant"]. Oneuniform_f32()per attempted draft, always consumed in the same order. - On the first rejection, stop. Build the residual distribution
max(p − q, 0), renormalize, and draw the replacement token from it. This correction is what makes the marginal output distribution equal the target’s exactly — rejection is not an error, it is part of the sampler. - Every draw comes from a source-pinned MT19937 stream deliberately
isolated from the generic RNG so “a
randalgorithm or conversion change” cannot alter tokens or a persisted session frontier[crates/muser-engine/src/sampling.rs:1001-1007], and the ordered variant (_mt_ordered) walks each row in a precomputed token order so the cumulative sum is deterministic.
If one sentence from this section survives the reading, make it the middle bullet, said again in different words. A rejection is not the loop failing; it is the loop’s correction step doing its job. A round that accepts nothing still emits a token, and that token comes from a distribution built precisely so that the whole accept-or-correct procedure, averaged over its own coin flips, reproduces what the target would have produced alone. Which means the draft’s quality is a performance parameter and never a correctness one. A brilliant draft and a broken draft give you the same text. Only one of them gives it to you quickly. Hold that thought; the next section is the story of what it cost us to learn it in production.
The engine side overlaps draft work with the target’s suffix (Figure 33.1). The Metal mirror-SD route splits the target graph at a capture layer:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/decode.rs:3293
/// Execute through `capture_end` synchronously, then submit the remaining
/// target layers and LM head without waiting. The returned hidden rows are
/// exact target activations and are stable before ANE sees them. This is
/// the narrow public-Metal half of Mirror-SD; no target result is accepted
/// until [`Self::finish_dflash_verify_suffix`] succeeds.
pub(crate) fn begin_dflash_verify_suffix(
}
begin_dflash_verify_suffix (decode.rs:3298) encodes embedding through
capture_end in one command buffer, submits the remainder without
waiting, and returns a PendingMetalDFlashVerify handle;
finish_dflash_verify_suffix (decode.rs:3635) then waits, reads back
the full token_count × vocab logit block, advances n_past, and
returns the distributions that verify_full_speculative_mt_ordered will
judge [crates/muser-engine/src/decode.rs:3635-3666]. The acceptance
decision itself never touches the GPU — by design, the exactness-critical
step runs where every rounding decision is visible.
one speculative round (verify length 3 shown; real lanes use 3/7/15):
CPU/GPU time ──────────────────────────────────────────────────────►
DFlash draft │███ block forward + argmax → proposals t1 t2 t3
Target (Metal) │ │──── begin: layers 0..capture_end ────┐
│ │ (hidden rows captured here) │ async submit
│ │◄── exact activations ────────────────┤ layers ..51
│ │ │ + LM head
CPU accept │ │ wait ──►│██│ ──────┘
│ │ logits rows 0..3
│ │ verify_full_speculative_
│ │ mt_ordered (CPU, MT draws)
output │ │ ├── accepted prefix ──►
│ │ └── residual sample ──► next round
Figure 33.1: The round structure. The draft proposes; the target’s graph
is split so its suffix overlaps; acceptance is CPU-side against full
distributions on the pinned MT19937 stream; a rejected proposal becomes
the next round’s seed via the residual sample. The checkpoint machinery
that rolls back target KV on rejection is MetalSpeculativeCheckpoint
(decode.rs:213-226, Ch 8 §8.6).
The qualification gate for any of this is numeric and cold: the remote
qualifier “compares 256 greedy tokens plus every full target-logit row”
per sample, with a speculative-acceptance floor
DFLASH_ACCEPTANCE_MINIMUM = 0.95
[crates/muser-bench/src/remote.rs:3-8, :33] — exactness as a gate, and
acceptance itself as a qualified quantity.
33.3 The bug that rewrote every number: a precision-culture lesson
Ch 8 §8.4 told the draft-side story: Muser
never read dflash.attention.sliding_window (2,048) from the sidecar and
hardcoded sink 64 + window 1,024, so the draft ran on half its trained
window for an entire campaign. This chapter owns the consequences,
because they are a lesson about precision instruments, not about drafts.
The ledger’s geometry sweep on the collapsed cell is the cleanest
falsification table in the whole campaign — same cell, only the
conditioning changed (Figure 33.2) [ledger §"ROOT CAUSE FOUND AND FIXED"]:
| sink | window | muser acceptance | decode |
|---|---|---|---|
| 64 | 1024 (shipped) | 2.2% | 0.535 |
| 1 | 1024 | 2.2% | — |
| 1 | 32768 | 2.2% | 0.534 |
| 1 | 2048 | 72.5% | 1.315 |
| 64 | 2048 | 72.5% | 1.315 |
Figure 33.2: Half the trained window collapses acceptance; sixteen
times too much collapses it equally; exactly the declared window works;
the sink is immaterial [ledger §"ROOT CAUSE FOUND AND FIXED"]. The fix
(commit a7a4d11) took python-suffix-8,192 acceptance from 1.1% to
72.7% (decode 0.535 → 1.322) and made every natural-text cell
token-exact — while the synthetic fixture’s acceptance moved only 100% →
99.6% and its ratio fell 1.3012 → 1.2368, “the draft now attends 2048
context rows instead of 1024, which is real work the under-sized window
was skipping” [ledger §"ROOT CAUSE FOUND AND FIXED"].
Now the precision-culture part. The defect was invisible to the
exactness gate for the entire campaign, because the period-8 synthetic
stream “is predictable with no context at all, scored 100% acceptance
throughout the defect’s lifetime, and therefore certified a broken draft
lane” [ledger §"ROOT CAUSE FOUND AND FIXED", consequence 2]. The
verifier was exact; the tokens were exact; the draft was broken — and
nothing that compared outputs could see it, because the outputs are the
target’s outputs no matter what the draft does. That is the theorem of
§33.2 read as a warning: losslessness means a bad draft costs speed, not
correctness, so no exactness gate can detect a bad draft. Detecting it
requires fixtures whose acceptance depends on conditioning — natural
text — which is why natural-text cells became a standing part of the
matrix despite carrying no exactness gate [ledger §"ROOT CAUSE FOUND AND FIXED", consequence 2].
Getting to that table took two wrong turns, and the ledger keeps both
with the counters that killed them. Our first suspect was the governor.
Acceptance behaved like something being throttled, so we went after the
throttle, expecting to find it clamping the draft too early — and the
counters said the governor was “correctly protecting throughput,”
doing exactly the job it was written to do. Our second suspect was the
window, and we thought we had cleared it: a sweep row with the window
eliminated changed nothing, which seemed to rule the geometry out
entirely and sent us looking elsewhere. That row was invalid.
reset() rebuilt the cache at the hardcoded geometry, so the override
never took effect and we had carefully measured the same broken
configuration twice [ledger §"ROOT CAUSE FOUND AND FIXED", consequence 3]. The habit that came
out of it is cheap to state and expensive to learn: an override you
have not watched take effect has not been tested, and a negative
result from an inert knob is not a negative result. Every retroactive
restatement in §33.1 exists because of the table that finally replaced
those two guesses.
33.4 The first rejection: native NVFP4 speculation, fail-closed
Once the local lane worked, the obvious next move was to point it at the fast lane. The reasoning felt airtight: speculation multiplies the number of decisions you get per pass over the weights, and NVFP4 is the format that makes each pass cheaper, so the two should compound. We expected a win, or at worst a wash. What we got was a native NVFP4 W4A4 batched-verification diagnostic running at 6.805 tok/s against the 107.9 bar — one diagnostic, explicitly unqualified, and not close to anything.
The autopsy is in the split. Verification consumed 35.915 s of a
37.619 s decode span, which means the lane was not decoding with a
verify step attached; it was verifying, with a little decoding around
the edges [docs/nvfp4-fast-lane-evidence-20260817.md §Measured product numbers; ledger §F-series remediation]. So the
draft is kquant-only, and the reason is the target’s verify
arithmetic, not the draft at all. The lesson generalizes past this
lane, and §33.5 shows us paying to learn it a second time at cluster
scale: speculation is a multiplier, and a multiplier amplifies a slow
verifier exactly as faithfully as it amplifies a fast one.
Ch 7 §7.6 gives the full treatment. The
claims register supplies the wording discipline: the lane’s verifier
diagnostics “missed the qualified bar,” and native NVFP4 speculative
decode “has no launch claim and remains fail-closed” [claims #4].
Fail-closed here means code, in two layers. A receiver configuration
declaring producer_mode: "native" cannot even enroll a DFlash identity
— "native producer mode cannot enroll DFlash context geometry"
[crates/muser-cluster/src/config.rs:128-131] — and the server refuses
the combination at startup with an operator-facing message
[crates/muser-server/src/state.rs:1667-1678], quoted in
Ch 7 §7.6: “native NVFP4 fast-lane
speculative decode is unqualified; omit –dflash and use plain NVFP4
decode, or route speculative serving to the kquant lane.” The design
choice worth meditating on: the alternative to refusing was serving
6.805 tok/s silently — more than five times slower than the lane’s own
plain decode (35.491 tok/s) and nearly sixteen times below the 107.9
bar [claims #4; ledger §F-series remediation]. Fallback B is
fail-closed beats silently-slow, instantiated.
33.5 The distributed verdict: measured, with receipts
The largest question this machinery ever faced: if the Mac drafts, could
the GX10 verify — putting the authoritative target transition on the
node whose tensor cores are idle after prefill? The frontier doc of
2026-08-18 opens by overturning its own prior assumption: lossless
speculative decoding does not require drafter and target to share a
checkpoint; it “requires one endpoint to execute the authoritative target
transition” — the other may be any approximation — so the real question
is target identity, and a Dudeman verifier on the GX10 consuming the
same RedHat-produced prefix KV is admissible [docs/nvfp4-distributed- speculative-frontier-20260818.md §Decision].
The screen that set the bar. Thirty-one warm prefix-cached GX
Dudeman M16 runs, with the five f32 DFlash target layers pinned in host
memory, measured 107.152 ms median target wall (p95 107.947 ms).
Charging the already-measured 26.9 ms Mac draft, 0.78 ms RTT, ~4.37 ms
to move 2,129,920 capture bytes, and ~0.01 ms for sparse q, full
acceptance projects to 114.93 tok/s — which established the
preregistered requirement: at least 99.151% IID per-edge acceptance
(99.229% at p95) to beat 107.9 [docs/nvfp4-distributed-speculative- frontier-20260818.md §Decision]. Read that requirement twice, because
it is the whole experiment in one line: the lane was only interesting
if the draft was right 99 times out of 100. Every round where it was
not, the GX would stream a model’s weights and emit nothing for them.
What the organic runs said. We ran it anyway, and that was the
right call — a preregistered bar is only worth writing down if you
intend to run at it and let it decide. The runs rejected the lane
(Figure 33.3) [docs/nvfp4-distributed-speculative-frontier-20260818.md §End-to-end linear-lane verdict]:
| Trace | Output | Rounds | Accepted / drafted | Acceptance | Measured tok/s | Verifier-only ceiling |
|---|---|---|---|---|---|---|
| Standard | 512 | 35 | 477 / 477 | 100.00% | 110.59 | 125.61 |
| Documentation | 256 | 109 | 147 / 1,592 | 9.23% | 15.53 | 20.15 |
| Python | 256 | 55 | 201 / 764 | 26.31% | 11.17 | 40.04 |
| Rust | 256 | 39 | 217 / 570 | 38.07% | 15.41 | 55.96 |
Figure 33.3: The linear-lane verdict table. The standard trace is the all-accept control; the three organic strata are real content. Every cell has retained Mac and GX service receipts (SHA-256 pairs in the frontier doc’s receipt table).
One column in that table decides everything, and it is not the column
of measured throughput. The verifier-only ceiling is the decisive
bound, and its definition is the whole trick:
output_tokens / sum(GX verifier wall) — it grants zero time to
DFlash drafting, feature decode, transport,
installation, and scheduling, i.e., “physically impossible zero-Mac-cost”
assumptions [docs/nvfp4-distributed-speculative-frontier-20260818.md §End-to-end linear-lane verdict; claims #14]. Said another way: we
started a stopwatch that runs only while the GX is doing arithmetic and
kept it paused for every other part of the system — the drafting, the
wire, the feature install, the scheduler — and then asked whether the
lane could win under that fantasy accounting. All three organic strata
stay below the 107.9 bar even under that impossible grant (20.15 /
40.04 / 55.96 tok/s); documentation stays below even the 35.5 tok/s
plain-decode floor.
This is why the softness in the measured column does not rescue
anything. The measured end-to-end numbers (15.532 / 11.172 /
15.412 tok/s) really are point estimates only — the python and rust
walls overlapped unrelated local validation work, and we say so rather
than quietly presenting them as clean. But a soft number sitting
underneath a hard bound does not move the bound, and the bound was
already below the bar
[frontier §End-to-end linear-lane verdict; measured-numbers §1g].
Two cells in that table invite misreading, so both carry labels. The
110.59 tok/s standard-trace cell is a positive control under forced
acceptance — it proved the machinery worked end to end (477/477
proposals committed, 34/34 Mirror-SD speculative transactions
retained), and it is never a serving result
[claims #14; measured-numbers §7]. And llama’s own draft-dflash
accepted 65–81% on the same fixture families, which reads like a
damning contrast until you place it in time: the collapse was
muser-side draft conditioning at the time, and this session ran
pre-window-fix, §33.3. We keep the comparison in the record anyway,
because the frontier’s conclusion never rested on it — the organic
ceilings sit below the bar regardless [measured-numbers §1g].
Then we went looking for a way out. A lane is only honestly rejected once you have tried the obvious rescues, so we tried three, and measured each one instead of arguing about it.
The first rescue was adaptive width: when acceptance collapses, stop
drafting wide and verify a single token at a time. It is safe, it
degrades gracefully, and it is not an escape. Singleton verification —
width reduced to the carried [frontier] token alone — still costs one
Dudeman weight stream per emitted token, and the measurement puts it at
“a roughly 9–10 tok/s safety mode” [frontier §End-to-end linear-lane verdict]. The safety net hangs an order of magnitude below the thing
it was catching.
The second was to bail out locally: if the remote lane stalls, finish
the request on Mac plain decode. This one fails on exactness before it
ever gets to speed. Mac/Metal and GX/vLLM are distinct target-engine
epochs, so a silent switch mid-stream swaps the target out from under
the exactness guarantee itself — a legitimate switch would need a
signed epoch seam plus replay of the accepted suffix, machinery nobody
has built. And even granting that unbuilt handoff zero cost, the
arithmetic refuses to save us: one failed gamma-14 probe followed by
the measured 35.491 tok/s Mac lane projects only ~35.16 tok/s over 512
tokens, below the documented >35.5 fallback gate
[frontier §End-to-end linear-lane verdict].
The third was to admit the lane per request rather than per
deployment — run one probe round, and if it accepts, trust the
stratum for the rest of the request. Mirror-SD missing on documentation
and python’s first attempts was survivable. Rust killed the idea: “the
adversarial classifier case” passed its first 14 proposals and then
failed the second Mirror attempt. A probe that passes and then fails
is worse than no probe, because it buys confidence it cannot honor, so
“a one-round admission probe is therefore unsafe” [frontier §End-to-end linear-lane verdict].
The verdict, in the register’s own words. “We measured remote
speculation across the wire and rejected it for general serving — the
verifier cost eats the gain. The shipped disaggregated lane is fast
remote prefill plus plain parity decode” [claims #14]. The economics
are structural, not tuning: a remote verifier is weight-stream-bound —
it must read a model’s weights per round trip — while the local kquant
verifier was engineered until its 16-row batch forward cost 128.4 ms
against the draft’s 26.9 ms [ledger §Stage B L1].
33.6 The falsification ledger: a device worth porting
What should a rejected experiment leave behind for whoever tries it
next? A verdict is nearly useless on its own — “we rejected the
distributed lane” invites exactly one response, which is to try it
again from scratch. What actually transfers is the shape of the search:
which branches were entered, what killed each one, and which are merely
untested rather than disproved. The frontier doc’s most durable
artifact may therefore be its form rather than its finding: every
hypothesis carries its evidence class and its verdict, so the next
experimenter inherits a map of the dead ends. A selection, verbatim in
structure [docs/nvfp4-distributed-speculative-frontier-20260818.md §Architecture attempts and disposition]:
| # | Hypothesis | Receipt | Verdict |
|---|---|---|---|
| 1 | Optimize the Mac Dudeman verifier | Measured: best 227.864 ms GPU / 239.564 ms wall — 13.9% over the 200 ms gate, 1.77× the 128.400 ms kquant reference; hard ceiling 70.2 verified rows/s [ledger §"Fallback A follow-up"] | Rejected as primary (Fallback A) |
| 2 | Second resident Dudeman verifier on GX10, linear chain | Authenticated composite import, end-to-end traces (§33.5) | Rejected for general serving |
| 3 | Existing RedHat session as target, Mac DFlash proposes | Measured mismatch screen: 190,449 teacher-forced rows, 8.116% RedHat/Dudeman top-1 disagreement; docs 15.428% → projected 42.82 tok/s | Algorithmically valid, fails the speed gate on docs |
| 4 | Shared-Gumbel coupling | Literature + reference implementation | Retained experimental; changes sampler semantics |
| 6 | Stateless GX verifier, authenticated Mac log, prefix-cache soft state | V2 transcript/journal implemented; service unimplemented | Preferred service architecture, if a lane ever returns |
| 10 | Uncertainty-aware token tree on GX | Measured batch curve; rank/coverage unmeasured | Only remaining performance experiment |
| 14 | Cross-checkpoint KV translator / CRDT branch union / wrong target certifying | Invariant rejection + published approximate systems | Rejected as unsafe |
Figure 33.4: The falsification ledger (selected rows). The frontier’s
own table runs to fourteen attempts, each with evidence class and
disposition [frontier §Architecture attempts and disposition].
This device is not new to this book — Figure 33.4’s rows 1 and 2 are
the Fallback A no-go and the linear-lane rejection this chapter has
already met with their receipts. It is the same discipline the
decode-dispatch-gap note used when it reconciled the +196 closure gap
into named families and then rejected the numerically inexact fusion
(logprob error 3.197e-4 over the 1e-4 contract) rather than shipping it
[docs/decode-dispatch-gap-20260815.md].
The discipline has a second face, which shows when the evidence simply
runs out. The offline GX trace analysis refused to invent a fix from
insufficient evidence: the retained hashes “cannot identify the first
proposal token,” so “inventing a rollback or serialization fix from
these data would be guesswork” [docs/gx-speculative-trace-offline- 20260815.md §What the evidence does and does not prove]. Reconcile
exactly, reject what breaks the contract, name what you do not know.
The distributed-speculative campaign simply ran the same culture at
cluster scale, and its rejection row in the claims register is the
output format: what was measured, what it cost, what ships instead
[claims #14].
33.7 What survives: the V2 machinery, unwired
A rejected lane leaves usable parts, and the reason this matters for
the handoff is specific: the lane died on economics, not on design. The
protocol was never the thing that failed. So the research record
retains a V2 typed protocol whose pieces are individually tested
even though “nothing is wired into the serving route” [frontier §What was implemented]. Read the list below as a description of a machine
that works and is switched off — each entry is there because it solves
a problem the next attempt would otherwise have to rediscover:
- The carried-frontier state machine. A target-selected token that
is not yet evaluated or emitted; a round evaluates
[frontier_in, draft_0, …, draft_(D-1)]over D+2 target witnesses, and “this explicit geometry prevents a common off-by-one: publishing a replacement whose target KV row and five DFlash features do not yet exist”[frontier §Carried-frontier state machine]. - A replayable request. The V2 request carries the complete
evaluated-token transcript and the actual MT19937 state; the GX side
reconstructs the f64 normalizer, replays one
uniform_f64draw per row, checks every proposed token, and rejects a mismatched post-draft snapshot — consistency with the authenticated Mac’s q bytes, with the honest caveat that it “does not prove that those q bytes came from the declared neural model”[frontier §Coupling policy decision]. - A durable transaction.
PREPARED reservation → immutable staged render + fragment closure → durable result → fenced head CAS + render activation → emit → ACK[frontier §Distributed state and reconciliation]— an fsynced commit WAL before idempotent activation, content-addressed fragments that absorb duplicates and reordering, Ed25519 target-only result signatures so a proposer holding the request key cannot forge verifier output[frontier §Fragment closure and security limits]. - A promotion-gate list that any future revival must pass:
composite-genesis exactness against a non-speculative oracle, a fused
verifier service (“do not infer service latency from the Python
screen”), real acceptance including the 24-task agentic set,
end-to-end economics against a paired 107.9-lane control with a
preregistered lower confidence bound, semantic requalification, and
authority hardening
[frontier §Product promotion gates].
The architecture document’s one-paragraph summary carries the boundary
that matters for readers of the shipped tree: the linear policy is
rejected, and “the production authority, renderer, executor, and stream
boundary remain unwired, so this research does not change the fail-closed
lane” [docs/muser-architecture.md, distributed-verifier paragraph].
One experiment is still alive, and it is worth seeing why it survived
the same document that rejected everything around it. The linear policy
pays a full weight stream every round, whatever that round returns; a
tree spends the GX’s otherwise-idle batch arithmetic on covering
near-miss branches instead, so a round that would have been a total
loss can still emit. That is the hardware-aware token tree, the
only remaining performance experiment — and even it enters through a
preregistered admission screen: at
24/32/48/64 nodes the measured capture curves require mean emitted
tokens per call of 15.77/16.18/17.01/17.84 (48 nodes is the first
sensible target; 64 only if its extra nodes add ≥0.83 token/call), and
“each organic stratum must exceed 107.9 tok/s with a preregistered lower
confidence bound; otherwise reject trees too” [frontier §Frontier attempt: uncertainty-aware token trees].
33.8 Tradeoffs
Local speculation vs distributed verification, measured. The local
lane’s verify is a 16-row batch on shared weights (128.4 ms forward
against a 26.9 ms draft, [ledger §Stage B L1]); the distributed lane
pays a weight-stream per round plus transport, and its organic
acceptance (9.23–38.07%) put even its zero-cost ceilings at 20.15–55.96
tok/s [frontier; claims #14]. The crossover the tree experiment chases
is real but unproven; nothing in the shipped product depends on it.
Draft cheapness vs draft quality. Chapter 8 §8.8 measured it: the
draft is the small side of its own loop, so effort went to the verify
kernels and — after 2026-08-21 — to conditioning. The window bug’s
arithmetic is the sharpest statement of the tradeoff in this book:
fixing the draft cost ~5% of synthetic speed and bought 1.1% → 72.7%
natural-text acceptance [ledger §"ROOT CAUSE FOUND AND FIXED"].
Fail-closed vs silently-slow. Fallback B’s refusal (§33.4) and the unwired V2 (§33.7) are the same decision twice: a route that measured 6.805 tok/s (native spec) or 15.53 tok/s (distributed docs stratum) is not served while awaiting a better design — it is refused, with the evidence retained. The measured alternative was always available and always rejected.
The all-accept control as instrument, not result. 110.59 tok/s
proved the pipeline’s plumbing end-to-end; the same session’s organic
strata killed the lane. Controls that cannot fail teach nothing;
controls that can fail — and this one could only pass by construction —
calibrate the instrument. Citing it as performance inverts its purpose
[claims #14; measured-numbers §6 rule 6].
33.9 Where the gap lives
For the speculative lane, the dispatch-gap question gets a twist: the
gap is mostly the point. A speculative round deliberately runs more
kernels than plain decode (draft block, split target, wider verify) —
work that only pays if acceptance converts it into emitted tokens. The
measured acceptance collapse of §33.5 is what “the gap ate the win”
looks like when the extra work is remote: every rejected proposal paid
a full GX weight stream for zero emitted tokens. Locally, the same
arithmetic is why the engine distrusts its own draft (the windowed
disable gate of Ch 8 §8.7) and why the
natural-text losses on high-acceptance shallow text are real: when
llama’s lighter draft verifies nearly everything, extra conditioning
work is pure overhead [docs/benchmarks.md §2].
33.10 What comes next
Everything in this book so far has been one accelerator’s story — one Mac’s kernels, one producer’s prefill, one pair’s wire — plus the evidence culture that keeps their numbers honest. Part VII assembles the machine that owns it all: one scheduler that owns one accelerator, slots the requests, favors decode over prefill, and decides — per token, under load — which of the lanes this Part built actually runs. That is Ch 34: the scheduler and the slots.
References
[crates/muser-engine/src/sampling.rs:1001-1097]— the source-pinned MT19937 stream,verify_full_speculative_mt/_mt_ordered, themin(p/q, 1)acceptance rule and residual-corrected resample.[crates/muser-engine/src/decode.rs:3293-3369, 3635-3666]—begin_dflash_verify_suffix/finish_dflash_verify_suffix(the Mirror-SD split-graph overlap);:213-226the speculative checkpoint.[crates/muser-bench/src/remote.rs:3-8, :33]— the 256-token + full-logit-row comparison andDFLASH_ACCEPTANCE_MINIMUM = 0.95.[crates/muser-cluster/src/config.rs:128-131],[crates/muser-server/src/state.rs:1667-1678]— Fallback B’s two refusal layers.[docs/nvfp4-distributed-speculative-frontier-20260818.md]— the decision and target-identity invariant, the 107.152 ms screen and the 99.151% preregistered bar, the end-to-end verdict table and receipt SHA pairs, the carried-frontier and coupling-policy sections, the architecture-attempts disposition table, the tree admission screen, and the product promotion gates.[docs/nvfp4-fast-lane-evidence-20260817.md]— the 6.805 tok/s native spec no-go and its qualification boundary; the 240/240 correctness diagnostic.[ledger §L2 Stage B verdict],[ledger §Stage B L1],[ledger §"ROOT CAUSE FOUND AND FIXED"],[ledger §F-series remediation],[ledger §"Fallback A follow-up — weight-only verifier final no-go"]—docs/goal-parity-ledger-2026-08.md: the pre-fix 107.9136/1.3273 bar, the L1 cycle decomposition, the window-bug sweep and consequences, the native no-go, the Mac verifier no-go.[claims #3],[claims #4],[claims #14],[claims #15],[claims #16]—docs/launch-claims.md: exact-token depths; native spec fail-closed; the distributed rejection and its wording discipline; the fixed-window synthetic ratios; the 131,008 wall parity and the barred 1.64960× figure.[docs/benchmarks.md §2]— natural-text wins/losses and the verify-length conventions.[docs/decode-dispatch-gap-20260815.md],[docs/gx-speculative-trace-offline-20260815.md]— the mirror disciplines: exact reconciliation with inexact fusions rejected; refusal to invent fixes from insufficient evidence.[docs/muser-architecture.md]— the lane matrix; “DFlash drafts are always verified by target distributions”; the distributed-verifier paragraph (V2 unwired).[measured-numbers §1b, §1g, §2 Arc 5, Arc 7, §6, §7]— the spec scopes and landmines; the distributed-speculative table; the window arc; the claim crib; the mis-cited-numbers list.[arxiv:2302.01318]— Chen et al., the independent lossless speculative decoding formulation; the frontier doc’s research basis additionally links Leviathan, Kalman, and Matias (PMLR 2023) for the same core result.- Ch 7 §7.6, Ch 8 — Fallback B’s full treatment; the draft’s contracts and gates this chapter consumes.
- Ch 34 — the scheduler that owns everything this Part built.
Chapter 34 — The scheduler and the slots
status: polished · path: Muse Glimmer, pinned Muser tree
Prerequisites: Ch 2 (command buffers, encoders, the “record a tape, press play” model), Ch 10 (the one-token graph and its two routes), Ch 22 (KV footprint per slot), Ch 33 (the DFlash loop that shares this machinery). No OS-scheduling background is assumed; the chapter builds the concurrency story from one Mutex and one Condvar.
Ch 33 closed Part VI with a measured verdict: speculative decoding stays local and kquant-only, because a remote verifier’s ceiling sat below the 107.9 tok/s bar even under physically impossible assumptions. Part VII now turns from what one token computes to how many tokens share the machine. The same question the book has asked since Ch 1 — what does one token cost, and what may be moved without breaking the exactness contract? — acquires a concurrency dimension: what may be shared without one generation ever observing another’s state?
The answer in Muser is a discipline, stated in one line of the architecture
document: “One scheduler owns one accelerator and between one and four resident
slots” [docs/muser-architecture.md §Slots and scheduling]. This chapter takes
that sentence apart, clause by clause. The first thing to say about it is that
the singular is a simplification: there are two schedulers, an engine-side owner
of the Metal queue and a server-side pool that admits requests into slots, and
the design keeps them deliberately separate. Walking that separation is most of
the chapter, because it is where the interesting decisions live. By the end you
will know why four is the number, what exactly a slot owns, what is shared and
why sharing is safe, and how a 250 µs rendezvous turns four independent request
threads into one packed Metal submission without any of them giving up its
slot.
34.1 The problem a scheduler solves
Where does the time go when several people talk to one machine at once? Not into any single kernel — those are the same kernels Part III measured, and they do not get slower because a second user showed up. It goes into the waiting: whose token runs next, and who is stuck behind whom. That is the question a scheduler exists to answer, and answering it badly does not show up as a uniform slowdown you can average away. It shows up as one user’s stream visibly freezing while somebody else’s document is processed.
Start from the hardware. One Mac, one GPU, one MTLCommandQueue
(Ch 2). Multiple HTTP requests arrive; each wants
a generation; each generation is a serial loop of one-token forward passes
through the same 52-layer graph. The obvious design is no design at all: let
every request open its own session, submit its own command buffers onto the
shared queue, and let Metal sort out the ordering. It is worth walking that
picture to its failures, because each failure is a requirement in disguise, and
the scheduler that follows is shaped by exactly these three:
- Interleaving is uncontrolled. A 512-row prefill chunk from request B can land in front of request A’s next decode token, and A’s user stares at a frozen stream for the whole chunk. Decode latency — the per-token time a streaming user feels — must not queue behind prefill work.
- Weight residency multiplies. The mmap’d 16,756,681,056-byte GGUF [crates/muser-engine/src/lib.rs:14] plus the pipeline set plus the RoPE tables are one immutable arena. Loading “the 16+ GiB target once per serving slot” is exactly what the code refuses to do [crates/muser-engine/src/decode.rs:954-957].
- Starvation. If one hot slot re-acquires the accelerator ahead of its peers in a tight loop, the other users’ tokens stall indefinitely.
A scheduler is the piece of code that turns “everyone submits whenever” into “one owner decides who runs next.” Muser’s answer is two levels with different jobs, and it is worth naming them precisely because the word “scheduler” is overloaded:
- Engine level —
AcceleratorScheduler(this chapter, §34.2): a Mutex and a Condvar insidemuser-enginethat serialize command submission onto the Metal queue, with decode preferred over prefill and round-robin fairness across sequences. - Server level —
SlotPool+DecodeBatcher(§34.4–34.5): admission and rendezvous insidemuser-server. The pool bounds how many generations may become resident (and how many may wait); the batcher packs ready decode rows into one submission.
The split is not accidental. The engine knows about Metal but not about HTTP; the server knows about requests but never touches a command buffer. Each level can be tested, and reused, alone — the engine scheduler is exercised by Metal-only tests with no server in sight [crates/muser-engine/src/decode.rs:6295-6306].
34.2 The engine scheduler: one owner for one queue
Take the engine level first, and take it on its own terms: given one Metal queue and several host threads that all want to encode onto it, who goes next? That is the entire brief. It is a narrow question, and the answer is small enough to read in one sitting — no work-stealing deques, no priority heaps, no timer wheel. Here is the whole type, the “one scheduler” of the architecture sentence, and it is 25 lines including comments:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/decode.rs:1013-1030
#[derive(Default)]
struct AcceleratorSchedulerState {
active: bool,
decode_waiting: BTreeSet<usize>,
last_decode: Option<usize>,
}
/// One owner for the shared Metal queue. Decode work is selected first and
/// resident sequence IDs rotate in ascending cyclic order, preventing a hot
/// slot from repeatedly reacquiring the accelerator ahead of its peers.
struct AcceleratorScheduler {
state: Mutex<AcceleratorSchedulerState>,
ready: Condvar,
}
struct AcceleratorPermit {
scheduler: Arc<AcceleratorScheduler>,
}
}
Read the state fields as a sentence: either someone holds the accelerator
(active), or they do not; the set of sequence IDs waiting for decode is kept
sorted (decode_waiting: BTreeSet<usize> — sorted by sequence ID, which is
what makes the rotation below cheap); and last_decode remembers who ran last
so fairness can resume after them. The permit is a
RAII guard — acquiring returns it, dropping it
releases the accelerator and wakes the next waiter
(Drop for AcceleratorPermit, decode.rs:1154-1159). There is no “release”
call you can forget to make.
Acquire: decode first, prefill only into silence
What breaks if this next part is wrong? A streaming user’s next word arrives
behind somebody else’s document, and no amount of kernel tuning downstream will
give it back. Every graph — every token, every prefill chunk — acquires the
scheduler before encoding, which makes this one loop the place where the felt
responsiveness of the whole server is decided. The policy lives in acquire:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/decode.rs:1053-1069
loop {
let selected_decode = next_decode_sequence(&state);
let eligible = !state.active
&& match work {
AcceleratorWork::Decode => selected_decode == Some(sequence_id),
AcceleratorWork::Prefill => selected_decode.is_none(),
};
if eligible {
state.active = true;
if work == AcceleratorWork::Decode {
state.decode_waiting.remove(&sequence_id);
state.last_decode = Some(sequence_id);
}
return Ok(AcceleratorPermit {
scheduler: Arc::clone(self),
});
}
state = self.ready.wait(state).map_err(|_| {
MetalModelError::InvalidSnapshot("accelerator scheduler is poisoned".into())
})?;
}
}
Decode the eligibility line, because both clauses are the design:
!state.active— the accelerator is exclusive. One command-submission critical section at a time; the GPU itself may still be draining earlier buffers asynchronously, but no second encoder walks the queue concurrently from the host side.AcceleratorWork::Prefill => selected_decode.is_none()— prefill may proceed only when no decode is waiting anywhere. This is the decode-favored rule as code: a single queued decode token outranks any amount of prefill work, because a decode token is somebody’s next streamed word and a prefill chunk is nobody’s.AcceleratorWork::Decode => selected_decode == Some(sequence_id)— a waiting decoder is not served merely because it woke up; it is served when the rotation selects it.
Fairness: ascending cyclic order
Exclusivity on its own is not fairness. A fast slot can drop its permit and ask
again immediately, and nothing in !state.active stops it from winning that
race every time; the loser would simply wait, correctly and forever. Fairness
has to be something the code asserts, not something that emerges. So
next_decode_sequence implements the rotation promised by the doc comment:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/decode.rs:1142-1152
fn next_decode_sequence(state: &AcceleratorSchedulerState) -> Option<usize> {
let Some(last) = state.last_decode else {
return state.decode_waiting.first().copied();
};
state
.decode_waiting
.range((std::ops::Bound::Excluded(last), std::ops::Bound::Unbounded))
.next()
.copied()
.or_else(|| state.decode_waiting.first().copied())
}
}
With sequences {1, 3, 4} waiting and last_decode = 3, the next selection is
4, then wraps to 1, then 3 — strictly cyclic. No sequence can be skipped twice
in a row by a peer. Put it the other way round: the waiting set is a circle and
last_decode is a finger resting on it, so selection never looks for the
best candidate, only for the next one. That is why asking more often buys a
hot slot nothing — it only returns to its own place on the circle sooner, and
the place is where the turn comes from.
This is the “preventing a hot slot from repeatedly
reacquiring the accelerator ahead of its peers” clause made algorithmic, and
the same rotation reappears at the server level (§34.5), where the batch sorts
its candidates by a matching decode_rotation_key
[crates/muser-server/src/state.rs:444-450].
Chunk shrinking: keeping decode’s escape hatch open
“Prefill only into silence” sounds like it settles the matter. It does not, and
the gap is worth seeing before the fix, because the fix looks arbitrary
otherwise. The rule is checked at acquisition only, and a permit once granted
is not preemptible: once a prefill chunk has started encoding, every decode
token that queues behind it waits for the whole chunk. Tightening the rule is
not available as a move — you cannot abandon a half-encoded graph, and you
cannot ask the GPU to put a command buffer down. What is available is the size
of the thing decode has to wait for. So the chunk boundary becomes adaptive,
in forward_into:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/decode.rs:2097-2107
while offset < tokens.len() {
// Long idle prefills retain the accepted 512-row physical batch.
// Once a decoder is queued, the next prefill boundary shrinks to
// 64 rows so decode can take ownership without another long
// accelerator interval in front of it.
let scheduler = Arc::clone(&self.shared.scheduler);
let chunk_tokens = if scheduler.has_waiting_decode() {
MAX_TEACHER_FORCED_TOKENS
} else {
PREFILL_BATCH_TOKENS
};
}
PREFILL_BATCH_TOKENS = 512 and MAX_TEACHER_FORCED_TOKENS = 64
[crates/muser-engine/src/decode.rs:53-54]. An idle machine prefills in 512-row
chunks; the moment any decoder queues, the next boundary drops to 64 rows, so
the worst-case wait for the accelerator is one 64-row interval rather than one
512-row interval. The prefill still finishes — just in smaller bites. This is
the interplay Ch 36 revisits from the prefill
side.
34.3 What a slot is: the state inventory
“One scheduler owns one accelerator and between one and four resident slots.” The word “slot” has been carrying a lot of weight for two sections now, and it is time to open it. It is the unit the whole design is bounded in, which means an error in either direction is expensive. Put too much into a slot and four of them will not fit in memory. Put too little in, and two users end up quietly sharing a buffer that nobody intended them to share — the kind of bug that looks like a rare sampling glitch and is really a correctness breach. So: what, precisely, is a slot? The architecture document inventories it:
Each slot owns independent target KV, DFlash state, logits, RNG, sampler and grammar state, detokenizer/stop state, and cancellation state. Immutable weights, Metal pipelines, and the DFlash executor are shared.
[docs/muser-architecture.md §Slots and scheduling]
That paragraph is a summary of real types spread across two crates. Walk it bottom-up.
The engine’s per-sequence handle is MetalMuseModel — its doc comment
states the isolation contract directly:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/decode.rs:986-998
/// Sequence-local Metal state. Immutable execution resources are shared;
/// cache, activations, speculative workspaces, and logical position remain
/// isolated for this one resident sequence.
pub struct MetalMuseModel {
pub cfg: MuseConfig,
shared: Arc<MetalShared>,
cache: Vec<MetalKvPlane>,
activations: Activations,
batch_workspaces: BTreeMap<usize, BatchWorkspace>,
n_past: usize,
sequence_id: usize,
verify_route_banner_printed: bool,
}
}
Every field after shared is per-sequence: the 52 KV planes (the ring and
growing cache of Ch 15), the ~15 MB activation
pool of Ch 10 §10.8, the prefill
workspaces, the logical position n_past, and the sequence_id the scheduler
rotates on.
The engine’s session wrapper adds the retained distribution and token history — the state that makes a decode error non-destructive:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/api.rs:602-613
/// Mutable inference state for one sequence.
///
/// A session owns its KV cache, token history, retained next-token logits,
/// and context limit. Call [`Session::prefill`] once or more, then pass each
/// selected token to [`Session::decode`] to advance the sequence.
pub struct Session {
backend: SessionBackend,
tokenizer: Arc<BpeTokenizer>,
max_context: usize,
token_history: Vec<u32>,
last_logits: Option<Vec<f32>>,
}
}
The server’s per-request state adds everything the architecture sentence lists after “logits”: RNG, sampler, grammar, detokenizer/stop, cancellation. The sampler state is one concrete struct worth quoting, because it is what a durable session bundle later snapshots (§34.6 and Ch 37):
#![allow(unused)]
fn main() {
// crates/muser-server/src/openai.rs:4331-4337
struct RequestSamplerState {
distribution_rng: Mt19937,
xtc_rng: Mt19937,
mirostat_rng: Mt19937,
mirostat_mu: f32,
adaptive: AdaptiveSamplerState,
}
}
One Mt19937 stream per stochastic sampler feature — the deterministic
Mersenne Twister of Ch 21, snapshot-able
and restore-able so the same RNG stream survives across local/remote lanes and
across a session save/restore. Grammar state, the streaming detokenizer, and
the stop filter are built fresh for every request, inside the generation loop
[crates/muser-server/src/openai.rs:2266-2270]; the grammar matcher there is a
GBNF Earley recognizer, GrammarMatcher [crates/muser-server/src/grammar.rs:1-9].
Cancellation is a flag checked between tokens — §34.6.
What is shared is the counter-list, MetalShared:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/decode.rs:954-970
/// Immutable Metal execution resources shared by every resident sequence.
/// Metal command submission is scheduler-serialized; retaining one context,
/// pipeline set, mapped weight arena, and GPU vector set avoids loading the
/// 16+ GiB target once per serving slot.
pub struct MetalShared {
context: MetalContext,
kernels: MetalKernels,
_residency_set: Option<crate::metal::residency::ResidencySet>,
mapped_weights: GpuBytes,
// …(embedding/output projections, entry-norm ones, RoPE tables,
// per-layer weights, scheduler, shared batch workspaces; elided —
// decode.rs:963-971)…
scheduler: Arc<AcceleratorScheduler>,
decode_batch_workspaces: Mutex<BTreeMap<usize, DecodeBatchWorkspace>>,
}
Every field is either immutable after load or already synchronized (scheduler
is the Mutex+Condvar; the batch workspaces are keyed by row-count behind a
Mutex, allocated once per width and reused). That is the whole sharing-safety
argument: there is no shared mutable state on the token path. Two sequences
never write the same buffer; they borrow the same read-only weights
(Ch 3’s zero-copy mmap views) and take
turns at the queue.
Notice what that argument does not need. There is no lock guarding the
weights, because nothing writes them; the sequence-local handle reaches them
through an Arc, which hands out shared references and nothing else. Isolation
here is a shape in the type layout rather than a convention somebody has to
remember at every call site — which is why the sharing story can be stated in
one sentence and then trusted for the rest of the chapter.
Which brings us to the number the architecture sentence asserts without
arguing. Why four, and not eight, or simply as many as will fit? The bound has
two halves, and they were set by different arguments. The
memory half is Ch 22’s arithmetic: one
slot’s KV at the 131,072-position limit is ≈1.827 GB and four slots ≈7.306 GB
on the 96 GB M3 Ultra [docs/memory-footprint.md]. The throughput half is
the packed-decode graph: the engine’s group runner rejects anything outside
its supported width, “decode group must contain 1..=4 sequences”
[crates/muser-engine/src/decode.rs:4874-4877]. Four is a designed width, not
an accident of memory.
34.4 The server level I: SlotPool — bounded admission
The engine level knows how to take turns. What it does not know is how many
players there should be: the type you just read holds a BTreeSet of waiting
sequence IDs and has no capacity at all, so nothing in it would refuse a fifth
resident sequence, or a fiftieth. Bounding the population is the server’s job,
and the question it answers is the unglamorous one every serving system has to
answer somewhere — what happens to the request that arrives when every slot is
already taken? Above the engine sits the server’s InferenceRuntime, and its
doc-commented fields state the second level’s job:
#![allow(unused)]
fn main() {
// crates/muser-server/src/state.rs:221-243
pub struct InferenceRuntime {
pub(crate) model: Model,
// …(vision, identities; elided — state.rs:223-227)…
/// Independent serving slots. The pool owns admission and makes it
/// impossible for more than `--parallel` generations to become resident.
pub(crate) slots: SlotPool,
/// Decode-step rendezvous. Request threads retain their independent slot
/// ownership while one elected runner packs up to four ready Metal rows.
pub(crate) decode_batcher: DecodeBatcher,
/// DFlash state is sequence-local for exactly the same reason as target
/// KV/RNG state. Indexes correspond one-for-one with `slots`.
pub(crate) dflash: Option<Vec<Mutex<DFlashRuntime>>>,
/// One assistant context paired with `staging`; it is never indexed by a
/// serving slot and cannot participate in decode before an atomic swap.
pub(crate) dflash_staging: Option<Mutex<DFlashRuntime>>,
/// The one full-capacity generation reserved for atomic context rebuilds.
/// It is deliberately outside `slots`, so it can never admit or decode a
/// fifth serving request.
pub(crate) staging: Mutex<Session>,
}
Three bounds live near it:
#![allow(unused)]
fn main() {
// crates/muser-server/src/state.rs:253-254
const MAX_QUEUED_REQUESTS: usize = 64;
const DECODE_COALESCE: Duration = Duration::from_micros(250);
}
plus the engine-side width checks: --parallel must lie in 1..=4
[crates/muser-server/src/state.rs:1054-1056] and an OpenAI-style request’s n
(the number of parallel completions) must too — "n must be in 1..=4"
[crates/muser-server/src/openai.rs:3480-3482]. So the admission pyramid is:
4 slots, 64 waiters, 256 concurrent HTTP connections
(ConcurrencyLimitLayer::new(256), [crates/muser-server/src/axum_httpd.rs:546]).
Every layer is bounded; nothing anywhere waits forever.
The pool itself is a classic condition-variable resource pool — but read its doc comment, because the third sentence is a whole philosophy:
#![allow(unused)]
fn main() {
// crates/muser-server/src/state.rs:474-483
/// Bounded admission for the resident target sessions.
///
/// A poisoned accelerator/session lease is not recovered in place. The
/// process is latched unhealthy so an operator restart is required before
/// any further inference, which avoids serving from uncertain GPU state.
pub(crate) struct SlotPool {
state: Mutex<SlotPoolState>,
available: Condvar,
unhealthy: AtomicBool,
}
}
Acquire pops a free slot if one exists; otherwise the caller counts itself
among the waiters, and if 64 waiters are already queued it fails immediately
with SlotAcquireError::Overloaded — an HTTP-level rejection, not a hang
[crates/muser-server/src/state.rs:545-590]. That is the ordinary path, and it is
the boring half.
The interesting half is what happens when the pool cannot trust itself. A
poisoned mutex or a missing session does something stronger than fail one
request: it latches the pool unhealthy permanently, so every later acquire
returns Unhealthy and surfaces as HTTP 503
[crates/muser-server/src/axum_httpd.rs:1066-1070]. Only an operator restart
clears it. This is the same fail-closed reflex as the producer’s exit-75 in
Ch 28, and the reason it is worth
naming twice is that both are refusals to guess: a lock poisoned mid-decode
means some sequence’s GPU state was abandoned in an unknown condition, and
nothing available at that moment can tell you which sequence or how badly.
Ch 37 finishes the story.
34.5 The server level II: DecodeBatcher — the 250 µs rendezvous
Four independent decode loops, one weight pass. Ch 10
showed the engine side: forward_decode_group packs 1..=4 rows that share one
MetalShared executor into a single concurrent encoder, one commit, one wait
[crates/muser-engine/src/decode.rs:4869-4937]. The economics are the whole
point — its doc comment says it in one line: “Pack one ready decode row from
each resident sequence into a single weight pass”
[crates/muser-engine/src/decode.rs:4866-4868]. Four sequences reading the same
16.76 GB of weights amortize the dominant cost of
Ch 1 across four users.
But four request threads arrive at four unaligned moments. Who calls
forward_decode_group? The DecodeBatcher is the rendezvous, and its own
comment is the contract: “Request threads retain their independent slot
ownership while one elected runner packs up to four ready Metal rows”
[crates/muser-server/src/state.rs:231-233]. The mechanics, in decode and
run_one_batch:
- Each request thread enqueues a
DecodeJob— slot, input, and a shared result cell — then loops waiting for its cell to fill [crates/muser-server/src/state.rs:314-340]. - One thread is elected (the first to find
running == falsesets it and becomes the runner; the others go back to sleep) [crates/muser-server/src/state.rs:341-360]. - The runner waits a coalesce window of 250 µs if fewer than four rows are queued, then drains the queue, sorts candidates by the same cyclic rotation the engine uses, takes up to four, and leaves the rest:
#![allow(unused)]
fn main() {
// crates/muser-server/src/state.rs:370-385
if state.queue.len() < 4 {
let (next, _) = match self.ready.wait_timeout(state, DECODE_COALESCE) {
Ok(next) => next,
Err(_) => return,
};
state = next;
}
let mut candidates = state.queue.drain(..).collect::<Vec<_>>();
candidates.sort_by_key(|job| decode_rotation_key(state.last_slot, job.slot));
let split = candidates.len().min(4);
let remainder = candidates.split_off(split);
state.queue.extend(remainder);
}
- One row runs
session.decode; two-to-four rows runSession::decode_group— the engine entry that frontsforward_decode_groupand then fans results back out, one per session [crates/muser-engine/src/api.rs:796-826; crates/muser-server/src/state.rs:398-435]. - The runner installs each job’s result, clears
running, and notifies; the blocked threads wake with their token computed.
The election is the part that trips people up, so here it is once more from a
different angle. Nobody hands over a slot. Every request thread still owns its
own Session for the whole generation, start to finish; what the batcher
borrows is only the turn — the right to be the thread that encodes this one
step. Several threads go to sleep, one of them does the encoding for all of
them, and each wakes with its own row’s result installed in its own cell. The
batch is an implementation detail of a single step, not a transfer of
ownership, which is exactly why nothing above this layer has to know that a
batch happened at all.
The 250 µs window is a latency budget spent to buy bandwidth amortization: if a second and third row are 100 µs behind the first, waiting for them costs each early row a fraction of a millisecond and saves up to three full weight passes. And the window is conditional on deployment shape — a single-slot server disables batching outright, with a comment that shows the measured instinct behind the constant:
#![allow(unused)]
fn main() {
// crates/muser-server/src/state.rs:280-284
// A single resident slot can never form a multi-row batch. The
// 250 us coalescing window only delays every token in the
// release-relevant parallel-1 latency cell.
enabled: metal && resident_slots > 1,
}
That comment is the closest the source comes to citing evidence, and notice what it cites: a latency cell, a lane, not a width. So we went looking for the payoff on the other side of the trade — the throughput the packing is supposed to buy. What we expected to find was a width sweep, one resident row, then two, then four, tokens per second for each — because that is the obvious way to settle a packing argument, and packing arguments are easy to believe and easy to get wrong. It is not in the evidence. The qualification cells were run per-lane, not per-width, so what the four-way packing buys in absolute throughput on this hardware is not claimed as a measurement anywhere in the campaign docs [unverified]. The absence is worth stating plainly instead of papering over: the case for packing, here, is a design argument read out of the source — the weight-pass amortization above — and not a measured one. The lesson we took away from the search is that a campaign measures what it was asked to release, and concurrency width was not on that list.
34.6 Keeping the owner clean, and the staging generation
A scheduler is only as good as what it refuses to admit into the critical section, and as good as what it does when the far end of a socket stops listening. Two disciplines answer those two questions. A third rule then guards the slot count against a tenant that would otherwise walk in through a side door.
Nothing slow happens on the accelerator owner. The architecture document lists what stays off: “Tokenization, sampling, grammar/tool parsing, disk, TLS, and socket writes stay outside the accelerator owner” [docs/muser-architecture.md §Slots and scheduling]. Concretely: the request thread tokenizes, then acquires a slot, then decodes; sampling and speculative acceptance run on the CPU against the read-back row (Ch 10 §10.9); the SSE/WebSocket writer is a separate async task fed through a bounded channel. While your token computes on the GPU, nothing about your TLS handshake can delay someone else’s token — the accelerator critical section contains encode, commit, wait, and nothing else.
Output is bounded, and blocked consumers are cancelled. The streaming
channel has depth 64 (STREAM_CHANNEL_DEPTH,
[crates/muser-server/src/axum_httpd.rs:54]) and writes go through
send_bounded, which never blocks the generator indefinitely:
#![allow(unused)]
fn main() {
// crates/muser-server/src/axum_httpd.rs:2277-2287
match sender.try_send(item) {
Ok(()) => return Ok(()),
Err(mpsc::error::TrySendError::Full(returned))
if started.elapsed() < SLOW_CLIENT_GRACE =>
{
item = returned;
std::thread::sleep(Duration::from_millis(5));
}
Err(_) => return Err(openai::ChatError::Cancelled),
}
}
Read that match arm by arm, because it encodes a policy rather than plumbing. A
client that has merely fallen behind is given room: the generator retries,
sleeping briefly between attempts, instead of failing on the first full
channel. That is backpressure relief, and the budget for it is 5 s
(SLOW_CLIENT_GRACE, [crates/muser-server/src/axum_httpd.rs:55]). When the
budget runs out the request is cancelled, not parked — relief, not a hostage
situation — and the error type maps to HTTP 499
“Client Closed Request” [crates/muser-server/src/openai.rs:649, 665]. A socket
that has already gone away does not even get the grace: the closed-channel arm
of the same match cancels it immediately (axum_httpd.rs:2319).
The resumable-stream variant keeps the same rule on purpose — its comment is the isolation contract in miniature: “A connected client that remains backpressured for the full grace period still cancels this request, as required by the serving isolation contract” [crates/muser-server/src/axum_httpd.rs:2310-2313]. A slow reader can waste its own request; it cannot hold the accelerator or a slot.
The staging generation is not a fifth slot. Context shift
(Ch 23) is server policy: to shift
context, the server rebuilds the truncated context in a separate
full-capacity Session — staging — and swaps it into the slot only when the
replacement state is complete [crates/muser-server/src/state.rs:240-243]. The
field’s doc comment carries the warning you now have the context to read:
“It is deliberately outside slots, so it can never admit or decode a fifth
serving request.” Admission counting and rebuild scratch are different
lifecycles; conflating them is how a context shift becomes an accidental
fifth tenant that the 1..=4 invariants (and the batcher’s four-row packs)
never learned about. The same pattern guards the DFlash assistant’s rebuild
context (dflash_staging, “never indexed by a serving slot and cannot
participate in decode before an atomic swap”, state.rs:237-239).
34.7 The two levels, one picture
Every piece of this chapter now has a place on one path — Figure 34.1 traces a request from the HTTP admission gate through the rendezvous, the engine scheduler, the read-back, and the bounded output channel, with the staging generation off to the side where it belongs:
flowchart TD
REQ([HTTP request arrives]) --> ADM{SlotPool admission<br/>state.rs:479}
ADM -- "4 slots free / ≤64 waiting" --> LEASE["SlotPermit: exclusive Session lease<br/>+ per-request sampler/grammar/detok state"]
ADM -- "64 waiters already" --> REJ([HTTP rejection: overloaded])
ADM -- "pool latched unhealthy" --> FIVE03([503 until restart])
LEASE --> TOK["CPU: tokenize, template, validate<br/>(never on the accelerator owner)"]
TOK --> PREFILL["Prefill: 512-row chunks,<br/>shrinking to 64 when a decode waits"]
TOK --> DECODEQ["Decode step: enqueue DecodeJob,<br/>one thread elected runner"]
subgraph RENDEZVOUS["DecodeBatcher rendezvous — state.rs:269"]
direction TB
W["wait ≤ 250 µs for up to 4 ready rows<br/>(disabled at parallel=1)"]
SORT["sort by cyclic slot rotation<br/>take ≤ 4, leave the rest queued"]
W --> SORT
end
DECODEQ --> RENDEZVOUS
subgraph ENGINE["AcceleratorScheduler — decode.rs:1023 (engine level)"]
direction TB
ACQ{"acquire:<br/>decode selected? prefill: no decode waiting?"}
GRP["forward_decode_group: one encoder,<br/>one commit, one wait for 1..=4 rows"]
REL["permit drops: active=false, notify"]
ACQ --> GRP --> REL
end
SORT -->|"1 row"| ACQ
SORT -->|"2..4 rows"| ACQ
GRP --> READBACK["per-row logits read back"]
READBACK --> CPU2["CPU: sample / argmax,<br/>grammar mask, detokenize, stop check"]
CPU2 --> OUT["bounded channel (depth 64)<br/>send_bounded: 5 s grace → cancel"]
OUT --> STREAM([SSE / WebSocket frames])
CPU2 --> LOOP{next token?}
LOOP -- yes --> DECODEQ
LOOP -- "stop / length / cancel" --> RELEASE(["slot returns to pool"])
SHIFT["Context shift: rebuild in `staging`<br/>(outside the pool), atomic swap"] -.-> LEASE
Figure 34.1: The request lifecycle across both scheduler levels. The server
level (SlotPool admission, DecodeBatcher rendezvous) owns requests and
boundedness; the engine level (AcceleratorScheduler) owns the Metal queue and
decode-over-prefill priority. The staging generation sits outside the pool
entirely.
34.8 Tradeoffs
Each of these was a real fork, and each was settled by giving something up. The honest way to read the four below is as prices paid, not as features.
Decode-absolute vs prefill-throughput. Every scheduling policy is a
decision about whose time is cheap, and this one says the prefiller’s is.
Prefill runs only when no decode waits (decode.rs:1058), and chunk boundaries
collapse 512→64 under decode pressure
(decode.rs:2103-2107). The cost side is explicit: under concurrent
load, prefill throughput drops (smaller chunks, deferred acquisition). The
benefit side is structural, not a measured serving cell: decode latency is
bounded by one 64-row interval plus the running decode queue, which is the
quantity a streaming user perceives as responsiveness. The campaign’s
throughput matrices (e.g. the six-depth plain matrix
[ledger, "Phase 2 non-spec context matrix"]) were measured without
concurrent decode pressure, so they do not adjudicate this trade — the code’s
own comments are the design record.
The 250 µs window as a spent latency budget. Coalescing is a bet: you delay
work you already have in hand in order to collect work that has not arrived
yet. Where the bet cannot pay — one resident slot, no second row physically
possible — the delay is pure loss, and the code declines to take it. With
--parallel 1 the batcher disables itself because the window
“only delays every token in the
release-relevant parallel-1 latency cell” [crates/muser-server/src/state.rs:281-284];
at --parallel > 1 the window trades ≤250 µs per packed row for up to
three avoided weight passes. No five-rep measurement of the packed-batch win
exists in the campaign evidence [unverified]; the justification in source is
the weight-pass amortization argument of [Ch 1], and the constant is small
enough to dominate in only one direction.
Two schedulers instead of one. A single admission-plus-dispatch monolith is
the design you reach for first, and it genuinely removes a level of
indirection. Follow it one step further, though, and the two vocabularies
collide: the engine would have to learn slots, waiters, and overload, which are
HTTP-shaped ideas, and the server would have to learn permits and encoders,
which are Metal-shaped ones. The split keeps muser-engine independently
testable — the scheduler tests run with no server anywhere in sight
[crates/muser-engine/src/decode.rs:6295-6306] — and it lets the batcher’s
unsafe row-packing (SAFETY: a DecodeJob exists only while its caller is blocked inside decode, state.rs:390-392) live in the one crate where its
invariants are local facts rather than cross-crate promises. The price is paid
in duplication, and it is the kind that rots quietly: fairness is now
maintained twice, once at each level, by two rotation keys that have to agree
(next_decode_sequence decode.rs:1142; decode_rotation_key state.rs:444) —
a duplication that must not drift.
Fail-closed admission over best-effort recovery. The unhealthy latch (state.rs:474-478) turns uncertain GPU state into a hard 503 wall until an operator restarts. The alternative — reset the poisoned session and keep serving — would serve from state whose integrity nobody can vouch for, which is precisely what the exactness contract forbids. This is the same ruling as the engine’s “a failed forward installs no distribution” gate Ch 10 §10.9, lifted to process lifetime; Ch 39 collects the pattern.
34.9 What comes next
You now have four slots, one queue owner, and a rendezvous that packs ready rows — but everything so far treats a submitted command buffer as if ordering were free. It is not. The moment one encoder holds a whole token’s graph, when each kernel’s writes become visible to the next kernel becomes a program you must write, with hazards to name and barriers to place — and the accounting of those placement decisions, the +196-closure dispatch gap, is the single most instructive measurement in the campaign. Ch 35 builds the hazard taxonomy from zero and then reads the gap diagnosis against it.
References
crates/muser-engine/src/decode.rs:41-54— workgroup cap; chunk constants (512 / 64).crates/muser-engine/src/decode.rs:954-998—MetalShared(the shared inventory) andMetalMuseModel(the sequence-local inventory), doc comments quoted.crates/muser-engine/src/decode.rs:1013-1030, 1040-1074, 1142-1152, 1154-1159— the scheduler state, acquire loop, cyclic rotation, permit drop.crates/muser-engine/src/decode.rs:2077-2113—forward_intoand the decode-aware chunk shrinking (quoted).crates/muser-engine/src/decode.rs:4866-4952—forward_decode_group: the 1..=4 packed graph, one encoder/commit/wait.crates/muser-engine/src/api.rs:602-613, 796-826—Sessioninventory;decode_groupfan-out.crates/muser-server/src/state.rs:221-254, 269-441, 444-483, 512-590, 1054-1056—InferenceRuntimefields (quoted),DecodeBatcherandrun_one_batch(quoted), rotation key,SlotPool(quoted), admission,--parallelbound.crates/muser-server/src/openai.rs:4331-4337, 649, 665, 2266-2270, 3480-3482—RequestSamplerState(quoted); 499 mapping; per-request grammar/detokenizer/stop construction;nin 1..=4.crates/muser-server/src/axum_httpd.rs:54-55, 544-546, 1066-1070, 2271-2289, 2310-2321— channel depth, slow-client grace, concurrency limit, 503 mapping,send_bounded(quoted), the resumable-stream isolation comment.crates/muser-engine/src/metal/buffer.rs— the tracked-buffer substrate behind “no shared mutable state” (Ch 35 opens here).[docs/muser-architecture.md §Slots and scheduling]— the one-scheduler contract and the state inventory (quoted).[docs/memory-footprint.md]— 1.827 GB/slot and 7.306 GB/four-slot KV at 131,072 positions.[ledger](docs/goal-parity-ledger-2026-08.md) — the campaign matrices whose scope excludes concurrent-load scheduling.- Ch 33 — the previous chapter; Ch 36 — the prefill side of the chunk-shrinking bargain.
Chapter 35 — Ordering, hazards, and the dispatch gap
status: polished · path: Muse Glimmer, pinned Muser tree
Prerequisites: Ch 2 (command buffers, encoders, dispatch — this chapter uses “closure,” “encoder,” and “commit” fluently), Ch 3 (unified memory and the buffer substrate), Ch 10 (the one-token graph whose dispatches we will count), Ch 34 (who submits). No GPU-synchronization background is assumed; the hazard taxonomy is built from zero.
Ch 34 ended with a submission: the batcher packs rows, a permit is acquired, one concurrent encoder records a whole token, one commit, one wait. That chapter treated the recorded tape as if pressing “record” and “play” were all there was to correctness. They are not. The moment more than one kernel writes GPU memory inside one command buffer — and Muser’s token graph runs hundreds of dispatch groups — you must answer a question the hardware will not answer for you on every path: when do this kernel’s writes become visible to that kernel? Get it wrong in one direction and you read stale bytes; get it wrong in the other and you stall the GPU for no reason.
This chapter does three things. It teaches the hazard taxonomy — RAW, WAW, WAR — with timelines small enough to check by eye. It walks Muser’s actual ordering tools, verified in source: one command buffer per token, tracked buffers by default, two barrier forms, a serial/concurrent encoder switch, and queue-ordered submissions instead of fences. Then it spends that vocabulary on the campaign’s most instructive measurement: the bounded one-token diagnosis that reconciled a +196 dispatch-closure gap into four named families and rejected the largest of them for changing logprobs beyond contract. That last part is a story about wanting something and not being allowed to have it, and we tell it that way, because the recurring question of this book — what may be moved without breaking the exactness contract? — has never had a sharper answer than “not these 104 groups.”
35.1 What one queue buys — and what it does not
Start with the question every ordering bug is an answer to: what has the platform already promised us, and what do we still owe it ourselves? Getting that boundary wrong in either direction is expensive, so it is worth drawing carefully before any Muser code appears.
Recall the Metal execution model of Ch 2. You
record kernel dispatches onto a MTLComputeCommandEncoder, end the encoding,
commit the command buffer, and the queue
executes it. Two guarantees come free:
- Within one serial encoder, dispatches run in recorded order. Whatever ordering problems exist, they are not scheduling problems: kernel B recorded after kernel A does not start before A on a serial encoder.
- Command buffers on one queue complete in commit order. The engine leans on this for its teacher-forced lane — the comment at the submission site: “The queue serializes GPU work so token i+1 cannot race token i’s residual/KV, while the host encodes i+1 during i’s GPU interval” [crates/muser-engine/src/decode.rs:2151-2155].
So where is the problem? In memory visibility. A dispatch “completing” does not mean its writes are visible to the next dispatch — caches, and the freedom a concurrent encoder grants its dispatch closures to overlap, sit between the two. Ch 10 already showed the serving token graph uses one concurrent encoder with explicit barriers precisely because the four independent Q/K/V/gate matvecs are allowed to overlap [crates/muser-engine/src/decode.rs:5448-5458]. Ordering within overlap is the subject of this chapter. And even on a serial encoder, the fine print matters: Metal orders dispatch execution, but Muser’s own diagnostic notes teach that relying on implicit visibility instead of saying what you mean is how Ch 34’s clean design acquires invisible state.
35.2 The three hazards, from zero
Before the definitions, the stake. What does it cost to classify one of these memory relationships wrongly? Not a crash, usually. The failure is a number that is merely different — different enough to fail a bit-exactness diff weeks later, on someone else’s machine, with nothing in the log to point at. That is why the taxonomy below is worth memorizing rather than looking up.
A hazard is a pair of memory operations whose order changes the result. Three kinds can hurt you; classify every buffer relationship into exactly one of four buckets (Figure 35.1). Two kernels A (first) and B (second), one buffer X:
RAW — Read After Write (true dependency: B reads what A wrote)
kernel A ████ write X
kernel B ▒▒▒▒ read X ← must see A's bytes
────────── time ──────────→
Rule: A's writes must be visible to B. THE decode hazard: the token
graph is a producer→consumer chain (norm → matvec → gate → …).
WAW — Write After Write (order of writers decides final bytes)
kernel A ████ write X = 1
kernel B ████ write X = 2 ← final value must be 2
────────── time ──────────→
Rule: writers serialize. Rare on the token path (buffers have one
writer per dispatch group) but lives in workspaces reused across
dispatch groups and in-place kernels.
WAR — Write After Read (B overwrites bytes A still needs)
kernel A ▒▒▒▒ read X (old)
kernel B ████ write X (new) ← must wait for A's read
────────── time ──────────→
Rule: B waits. Appears when a ring buffer's producer laps a slow
consumer — Ch 15's ring rotations are the in-engine near-miss.
RAR — Read After Read (both readers: order irrelevant)
NOT A HAZARD. No barrier, ever.
Figure 35.1: the hazard taxonomy as timelines. ████ = write, ▒▒▒▒ = read. The rule column is the whole discipline: name the bucket, then place (or omit) a barrier accordingly.
Worked micro-example, one buffer x, two dispatch closures on a concurrent
encoder. Recorded order A then B, but the encoder may overlap them:
A: x = x + 1 (read-modify-write — contains a RAW against itself)
B: y = x * 2 (reads x)
If B's read overlaps A's write: y may see the old or the new x.
This is a RAW hazard from A to B → a barrier between the closures is
REQUIRED. Without one, the graph is correct only by scheduling luck.
Now the same pair with B: x = x - 1 instead: both closures write x, a WAW
hazard — the final value of x depends on which write lands last, so the
closures must serialize even though neither reads the other. And if A
reads x while B writes it, you have WAR — on the decode path, think “the
KV ring’s next row is also a row someone is still attending from.” Ch 15’s
explicit origin_logical/origin_physical bookkeeping exists so that the
writer and the readers name different rows and the WAR never fires.
One more non-hazard worth naming because the engine exploits it: reads of immutable data never need ordering. All four packed decode rows of Ch 34 read the same weights concurrently — RAR against the weight arena, zero barriers, by construction.
35.3 Muser’s actual ordering tools
So what do we actually reach for when a dependency has to be stated? It is
worth asking suspiciously, because the ancestor answered the same question
with a great deal of machinery. The Ferrite engine solved this problem with a
compiled Vec<Op> program and a frozen per-model barrier plan (§35.8). Muser
has no such machinery — there is no Op enum, no compiler pass, no plan. What
it has instead is five explicit devices, each verifiable in source, and the
discipline to place them by hand.
Tool 1 — one command buffer per token, one queue
The serving token graph is one command buffer: encoder opened, whole 52-layer
graph recorded, end_encoding, commit, bounded wait
[crates/muser-engine/src/decode.rs:5448-5460]. The packed group graph of
Ch 34 is the same shape with four rows
[crates/muser-engine/src/decode.rs:4920-4937]. Cross-token ordering is the
queue’s commit-order guarantee (decode.rs:2151-2155); cross-graph
ordering is the scheduler’s single-owner rule of [Ch 34 §34.2]. There are no
MTLFence or MTLSharedEvent objects anywhere in the engine — a grep for
fence construction across crates/muser-engine/src/ finds the word only in
a comment [crates/muser-engine/src/metal/buffer.rs:10]. Where a boundary must
be crossed, Muser ends the command buffer and waits — and even the wait is
bounded: wait_for_completion parks the blocking call on a watcher thread
with a condvar so “a hang becomes a logged Deadline error, not a frozen
box” [crates/muser-engine/src/metal/context.rs:153-175].
Tool 2 — tracked buffers by default
Metal buffers can be created tracked (the driver inserts the visibility synchronization your dispatches imply) or untracked (it does not; you owe every dependency an explicit fence or barrier — see [Metal-PG]). Muser’s allocation path routes every buffer through one function:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/metal/buffer.rs:7-14
fn shared_tracked() -> MTLResourceOptions {
// Several accepted Muse paths still cross compute encoders (notably
// target-hidden prefill/capture). Untracked resources are only valid when
// every such dependency has an explicit fence/barrier. b9678d4 enabled
// untracked mode globally before that contract existed and empirically
// changed DFlash conditioning while leaving final greedy IDs unchanged.
MTLResourceOptions::StorageModeShared
}
}
That comment is a war story compressed into a code block, and it is worth
unpacking, because the bet it records is one we lost. The fork is a tempting
one: tracking is not free — the driver re-derives, on every dispatch, the
dependencies you already know — and switching it off is the standard way to
buy that cost back. So an earlier revision (b9678d4) flipped the engine to
untracked globally, before any explicit-dependency contract existed. What we
expected was a cheaper encode with identical output, on the reasoning that
the graph’s real dependencies were already written down as barriers anyway.
What we got was quieter than a crash and worse. The final greedy token IDs stayed identical — decode the fixture, read the text, and nothing at all looks wrong — but the DFlash draft’s conditioning, the hidden states it reads, changed. The lesson is the one this chapter keeps circling: a knob that sits nowhere near the arithmetic can still perturb numerics, here through scheduling overlap, and in an engine whose contract is reproducible bits “the tokens still match” is not a passing grade. So the decision was to revert and stay reverted: tracked is the default, and untracked mode is not re-openable until every crossing dependency carries an explicit fence or barrier.
One documented exception survives, and it is worth separating from the story
above so it is not misread as a crack in it: multi-gigabyte KV planes may
allocate uninitialized — and that is an initialization contract, not a
tracking one; debug builds poison the bytes (0xDEAD) so a premature read
is conspicuous, and the ring metadata guarantees every read row was written
first
[crates/muser-engine/src/metal/buffer.rs:246-277].
Tool 3 — two barrier forms
With tracking on, why barriers at all? Because the concurrent encoder still must be told where dispatch closures depend on each other, and because the engine wants dependencies to be auditable in source rather than implied. Form one, broad scope, is inserted automatically between closures:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/decode.rs:6256-6266
impl EncodeTarget for GraphEncoder<'_> {
fn before_dispatch(&self) {
if self.concurrent && self.has_dispatch.replace(true) {
// Broad buffer scope exactly matches llama.cpp's dependency reset.
// Independent kernels are deliberately grouped into one dispatch
// closure, so every closure boundary is a real graph dependency.
unsafe {
let _: () = objc::msg_send![self.encoder, memoryBarrierWithScope: 1u64];
}
}
}
}
Every dispatch(command, |encoder| { … }) call routes through
before_dispatch [crates/muser-engine/src/decode.rs:6273-6279]: on a
concurrent encoder, each closure after the first is preceded by a
whole-buffer-scope memory barrier — deliberately the same reset llama.cpp
performs between its own kernel groups [crates/muser-engine/src/decode.rs:6259].
The cost of the broad form is that it also orders unrelated reads; which is
why form two exists:
Form two is the targeted resource barrier, naming exactly the buffers a dependency flows through. The clearest example is on the decode attention route — KV store then attention, the RAW hazard made visible as two lines:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/decode.rs:5660-5671
dispatch(command, |encoder| {
self.kernels.encode_kv_store_f16(
encoder,
&self.activations.k,
&self.activations.v,
&plane.key,
&plane.value,
write_physical,
);
let kv: [&metal::ResourceRef; 2] = [plane.key.metal(), plane.value.metal()];
encoder.memory_barrier_with_resources(&kv);
self.kernels.encode_llama_flash_attn_decode_vec_f16(
}
Store writes K/V; barrier names K/V only; attention reads them. The scoped form has its own didactic comment on the splitk route, where the producer’s partials scratch (not the whole world) must reach the reducer: “Scope the dependency to that allocation instead of stalling every buffer used by the 52-layer command buffer” [crates/muser-engine/src/metal/encode/attn.rs:771-775]. Targeted barriers appear at each genuine RAW boundary — mask-before-attention and pad-before-vec on the llama routes, staged-shadow-before-attention on the SWA route, partials-before-reducer on the split routes [attn.rs:247, 256, 293, 316, 428, 558, 614, 632, 747, 783; decode.rs:4305].
Tool 4 — serial versus concurrent encoder, as a flag
Serial or concurrent — which should the prefill encoder be? The engine declines to answer permanently. It ships a default and keeps the loser reachable behind an environment variable, with the A/B history recorded in the comment beside it:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/decode.rs:975-979
// Prefill-only concurrent Q/K/V/gate and FFN gate+up. Decode already
// groups those projections; serial prefill paid a launch tax on PP128.
// `MUSER_SERIAL_PREFILL_DISPATCH` restores the previous encoder for A/B.
concurrent_prefill_dispatch: bool,
}
Default: concurrent grouping of the independent projection GEMMs (the
MUSER_SERIAL_PREFILL_DISPATCH flag at decode.rs:1331-1333 restores the
serial encoder for exact A/B comparisons). The retained flag matters for the
same reason the receipts do — a performance default you cannot switch off is a
default nobody can re-measure, and the comment names the measurement that
decided this one: the launch tax the serial encoder paid on the prefill
benchmark. The same select-by-graph structure appears in
new_prefill_graph_encoder, which also documents the command
buffer’s reference contract — unretained references, “This matches pinned
llama.cpp’s commandBufferWithUnretainedReferences contract”
[crates/muser-engine/src/decode.rs:6102-6122].
Tool 5 — discipline as documentation
Count the tools and you notice what is missing: no barrier planner, no
analysis pass, no proof type. The contract is maintained the way Ch 34’s
isolation contract is — in the structure of the code and its comments.
Independent kernels are grouped into one closure so that “every closure
boundary is a real graph dependency” (decode.rs:6260-6261); the barriers
you find are therefore exactly the hazard statements, no more. When a new
hazard class appeared — the SWA staging shadow needing its staged bytes
visible to the vec kernel — the fix was three named resources and a comment
[crates/muser-engine/src/decode.rs:4300-4305].
That is the whole ordering model. Now spend it on the measurement.
35.4 The instrument, corrected first
On 2026-08-15 the campaign asked: where does the one-token decode deficit
live? The instrument was MUSER_METAL_PHASE_PROFILE and its PhaseProfiler
— and the first finding of the investigation was not about decode at all. The
instrument itself was wrong in two ways. We went looking for the deficit and
found the ruler bent first, which is why the note corrects the ruler before it
presents any number [docs/decode-dispatch-gap-20260815.md].
What a “closure” is. The profiler counts calls to the Rust dispatch
closure, each submitted as its own command buffer with a synchronous wait —
it does not count raw dispatch_thread_groups calls. One qkvg closure
contains four kernel dispatches but contributes one count. Production serving
encodes the graph into one shared encoder with a single wait, so
“diagnostic wall time minus reported GPU time” mostly measures the
diagnostic’s own hundreds of submit/wait cycles. Any statement like “the gap
is N dispatches of overhead” is therefore a category error; the honest unit
is profiling closures. Put plainly, the profiler measures a graph that
production never runs, and its unit is a boundary in our own Rust code rather
than a launch on the GPU. Every count in the next two sections is in that
unit: read them as boundaries we can name, never as kernels the hardware
issued.
Two label defects had shifted timings. The tell was arithmetic that could
not be true: more labels printed than samples taken. Production labels omitted
lm_head (its time silently attributed to the following softcap label),
and the legacy schedule declared a separate SWA kv_store in all 39 SWA
layers although the implementation at position 2,048 combines KV publication
and attention in one closure — 603 labels printed for 564 samples, “every
legacy per-label timing after the first extra label was shifted.” The fix went
past the two defects to the class: the profiler now derives labels from
post-append ring state and aborts outright on a label/sample count mismatch,
so the next such bug stops the run instead of quietly re-attributing time
[docs/decode-dispatch-gap-20260815.md].
This is the book’s measurement culture in miniature, and it recurs in Ch 38: before a number can mean anything, the instrument’s own error model must be on the table.
35.5 The +196 reconciliation
With a trustworthy instrument we could finally ask the counting question and believe the answer: how much bigger is the production graph than the legacy schedule, and is the difference waste? The bounded one-token diagnostic counted 760 profiling closures for the production graph against 564 for the legacy schedule — a difference of +196. The fixture behind that count is worth stating once, because every number in this section and the next shares it: the pinned 16,756,681,056-byte target, a 2,048-token fixture, one teacher token.
The interesting part is not the size of the gap. It is that the gap reconciled exactly, with no unexplained remainder, into four families [docs/decode-dispatch-gap-20260815.md §Corrected closure-count diff]:
| Family | Δ closures | What it is | Disposition |
|---|---|---|---|
| Norm-boundary groups | +104 | 51 entry/attn-norm boundaries + 52 post-attn/FFN-norm pairs + 1 post-FFN/output boundary, separated instead of fused | Existing fusion not exact; reject |
| SWA wrapped-ring staging | +39 | one per SWA layer after ring wrap: stage old rows into the shadow (Ch 36) | Keep until a bit-exact ring-aware replacement exists |
| KV-publication/attention splits | +52 | store dispatch and attention dispatch as separate closures, once per layer | Session/publication structure; keep — combining closures alone removes no kernel math |
| Last-row copy | +1 | one bookkeeping copy of the final hidden row | No math; removed |
| Total | +196 |
Table 35.1: the +196-closure reconciliation at position 2,048 [docs/decode-dispatch-gap-20260815.md]. Closures are Rust profiling closures, not raw Metal dispatches (§35.4).
Two structural reads of this table matter more than the arithmetic. First, the 52 splits and 39 staging groups are the price of the ordering and exactness disciplines you just learned: the store-then-barrier-then-attend sequence of §35.3 is what the 52 splits are, and the staging groups are Ch 16’s decision to reproduce llama’s reduction lanes rather than read a wrapped ring “mathematically equivalently.” Second — and the note says this in bold — “No repeated closure performing provably identical arithmetic was found” [docs/decode-dispatch-gap-20260815.md]. The gap is not waste shaped like duplicated work. It is boundaries, each of which exists for a reason.
35.6 What was landed, what was rejected
Naming four families is a diagnosis, not a cure. The question we actually cared about is narrower and harsher: which of them can be removed without changing a single bit of the output? So the note ran the candidates as experiments rather than as a plan — each on the fixture just described (pinned target, 2,048-token fixture, one teacher token, bounded phase diagnostic), and each judged by one gate, the SHA-256 of the full logit row [docs/decode-dispatch-gap-20260815.md §Landed and rejected reductions]:
| Step | Groups | GPU ms | Full-logit SHA-256 changed? | Verdict |
|---|---|---|---|---|
| Baseline | 760 | 40.330 | — | historical pre-J0 reference |
| Direct one-row LM-head input (copy elision) | 759 | 40.194 | no | landed |
| Existing dual-norm fusion | 655 | 40.614 | yes | rejected: not exact |
| Pinned-reduction dual norm | 655 | 39.274 | no | historical only |
| Eight-head one-query GQA FA2 | 655 | 37.097 | no | historical only |
Table 35.2: the reduction table. GPU times are single-run diagnostics; the SHA-256 is over the full logit row — the exactness gate [docs/decode-dispatch-gap-20260815.md].
The one landed change is the +1 family: remove the last-row copy. It is bit-exact by construction — no arithmetic is touched, one 6,656-element f32 copy simply stops happening — so it never had to argue with the gate at all. Its single-run GPU delta is −0.136 ms (−0.34 %). Its wall sample went up 4.380 ms, and that number we do not believe: it is the diagnostic’s own submit/wait noise, which is precisely why the instrument had to be characterized before this table could be read, and no wall claim was made [docs/decode-dispatch-gap-20260815.md]. A lesson in miniature: the only guaranteed-free lunch in the whole table was a bookkeeping copy, and it was worth a third of a percent.
The 104-group fusion is rejected on exactness, not on speed. This is the family we wanted most — the largest one, and the one whose removal looks like pure structure rather than arithmetic — so we took the swing. The available dual-norm fusion does collapse the separated norm boundaries into 655 groups, exactly as advertised. It also changes the logit bytes (Table 35.2, row 3), which ends the argument before performance is even discussed. The gate is not “faster.” The gate is “identical.”
The second swing was the careful one. Replace the four-SIMD-group rsqrt
reduction with the pinned llama 32-SIMD-group reduction twice, preserve the
intervening f32 device-memory publication and reread, and the bits survive
(row 4). We expected that to be the answer: the same structural win as before,
merely performed correctly this time. It was not. Its five-sample streamed
serving result was 26.714 tok/s median (CV 0.079 %) versus llama’s 33.428, and
the note’s own verdict is the sentence to carry away: “the exact reduction is
useful but does not by itself close Stage A”
[docs/decode-dispatch-gap-20260815.md]. The one-query GQA specialization said
the same thing from another direction, measuring 28.290 tok/s median
(CV 0.193 %), ratio 0.8463×, prefill 1.0366× — Stage A still 13.37 points
below its 98 % decode bar at that moment
[docs/decode-dispatch-gap-20260815.md]. What the two swings taught together is
that closure count is not the currency the gap is denominated in: removing a
boundary buys less than counting boundaries suggests it should. (How Stage A
eventually closed — by changing the anchor itself, J0/J1 — is
Ch 38’s story; the gap families
survived it.)
The hybrid postmortem. Before either of those disciplined attempts there was a bolder one, and it is the one worth re-telling, because it failed in the most instructive way on offer. The idea was to take the whole prize at once: reuse the legacy retained-activation schedule and select the fast fused boundaries wherever they existed. The first look said it worked — the greedy token was preserved, so you could decode the fixture, print the text, and see nothing at all. Then we looked underneath the token, at the full distribution, and found that it had failed the public numerical contract. Every number in that postmortem is worth quoting [docs/decode-dispatch-gap-20260815.md §Rejected hybrid postmortem]:
- full-logit maximum absolute error: 4.6300888e-4; mean absolute error 1.5170774150033564e-4;
- normalized logprob maximum absolute error: 3.197146176834309e-4 — above the 1e-4 contract;
- 201,970 of 202,048 logits differed;
- the first KV difference: layer 1, value plane element 524,115, f16 bits 39,892 versus 39,893 — a single ULP flip in one value element, one layer in, propagating to a hundred thousand logits.
Read that last bullet slowly, because it is this book’s precision thesis in
one measurement. A one-ULP difference in one f16 value — the seventeenth-bit
wobble of a rounding-order change in the fused residual/norm chain — is
invisible in the sampled token and fatal to the contract. Put the other way
round: the argmax that picks a token is a lossy hash of the distribution, so a
check written against the token cannot see a change in the thing the token was
computed from. That is why the attempt “was removed rather than hidden behind
a tolerance or shipped as an alternate route”
[docs/decode-dispatch-gap-20260815.md], and why we kept the run that convicted
it — evidence retained under
muser-receipt://pinned-token-parity-20260814-v{3,4}/.
35.7 What the gap reveals about what an engine is for
Step back and state the tradeoff plainly. The available 104-group norm fusion is the single largest closure family. Fusing it changes normalized logprobs to 3.2e-4 against a 1e-4 contract. Therefore Muser keeps the boundaries. The ~3 % of GPU time that fusion-class changes might recover (40.330 → 39.274 ms for the exactable subset, Table 35.2) is not purchasable at that price — and the campaign’s own ranked list records the principle as its first item: “Implement an exact boundary fusion only if it reproduces the standalone reduction and store order bit for bit. The current 104-group fusion is a negative fixture, not a candidate” [docs/decode-dispatch-gap-20260815.md §Ranked remaining exact work].
This is the recurring question’s hardest answer. What does one token cost? Some of the cost is not removable — not because the techniques are unknown, but because the engine is for something: producing tokens whose full distributions can be diffed against a pinned reference, byte for byte, so that every other claim in this book (parity, handoffs, warm reuse, sessions) has a ground truth to stand on. An engine that quietly rounded differently under load could not host Ch 32’s bounded-drift gates — it would have already spent its credibility on its own scheduler. Bit-exactness beats throughput, and not sentimentally: it is the enabling asset for every measured claim Parts IV–VI made.
35.8 Ancestor contrast: the compiled barrier plan that was not ported
One question is still owed an answer, and it is the one a skeptical reader has been holding since the tools section: is hand-placing every barrier a choice, or just what happens when nobody gets around to building the tool? The ancestor is the control experiment, because it did build the tool. That is why this contrast belongs in the chapter rather than in an appendix — it decides how you should read every barrier you have just been shown, as deliberate design or as absence. The ancestor Ferrite engine ordered its decode differently, and the contrast is instructive enough to box.
The Ferrite design (lineage, not Muser). Ferrite compiled its decode graph into a
Vec<Op>program at load time — routing decisions resolved once, then a pipeline of passes fused, reordered, and emitted a frozen per-model barrier plan of byte-range hazards, replayed per token; a sealedOverlapProoftype made concurrency certification constructible only by the analyzer [ferrite-book Ch 21; ferrite-book Ch 22]. It ran its buffers untracked under that plan, buying a measured −2 to −4 % tracking cost back on the ancestor’s A18 Pro hardware [ferrite-book Ch 22]. Why none of it ported. The audit that planned this book verified the divergence in the Muser tree: there is noOpenum and no VM program — decode is hand-written encode methods, the shape of Ferrite’s legacy route, chosen for Muser’s fixed single-model, pinned-kernel discipline where route reasoning does not benefit from a program representation (weights and routes are constant after load). And the ancestor’s own corrections register later established that the comparison motivating its untracked mode was false — “llama.cpp uses untracked hazard tracking by default” is marked FALSE, the fabricated-claim finding that forced the ancestor book’s largest correction pass [ferrite-book CORRECTIONS-2026-07 §3a]. Muser keeps the lesson and not the machinery: hazard-tracking is default-on (§35.3, theb9678d4reversal), and barriers are hand-placed statements rather than compiler output. What survives as genuine lineage is the taxonomy (§35.2), the scope-versus-per-resource barrier insight, and the measurement instinct that barrier overhead must be counted, never assumed [ferrite-book Ch 22].
The gentle summary: Ferrite’s design was a bet that a compiler could own ordering so the runtime could be free with it. Muser’s design is a bet that a small, fixed, audited graph does not need a compiler — only a taxonomy, five tools, and the discipline to write every dependency down.
35.9 Tradeoffs
Four choices in this chapter were live enough to have an alternative someone could reasonably have picked. Here they are with the evidence attached, so that the next person to reopen one starts from what was measured rather than from what seems obvious.
Tracked-by-default vs untracked-plus-plan. Tracking costs driver-side
dependency analysis on every dispatch; the ancestor measured −2 to −4 % for
turning it off [A18-neo, ferrite-book Ch 22] — Ferrite-lineage hardware,
never measured on Muser’s M3 Ultra [unverified]. Muser pays the tracking
cost because the one attempt at untracked mode “empirically changed DFlash
conditioning while leaving final greedy IDs unchanged”
[crates/muser-engine/src/metal/buffer.rs:10-12] — a silent-numerics
perturbation, the exact failure class this engine exists to exclude.
The 104 groups: paid in boundaries, refunded in trust. The separated norm-boundary closures cost GPU time relative to a fused schedule (Table 35.2: 40.330 vs 39.274 ms for the bit-exact variant on the diagnostic fixture), and the hybrid that fused them aggressively breached contract at 3.197e-4 normalized-logprob error [docs/decode-dispatch-gap-20260815.md]. The refund is every exactness-gated claim downstream — most concretely, the parity matrices of Ch 38 that could not have been run against an engine with drifting boundaries.
Broad-scope closure barriers vs targeted resource barriers. The broad
form is simpler and matches llama’s own dependency reset (decode.rs:6259);
the targeted form exists where a broad stall would serialize unrelated work
— “instead of stalling every buffer used by the 52-layer command buffer”
[crates/muser-engine/src/metal/encode/attn.rs:771-773]. No measurement
separates the two forms’ costs [unverified]; the split is driven by the
shape of the dependencies (closure-granular on the token graph,
allocation-granular inside attention producer/reducer pairs).
One-CB-per-token vs fences. Ending the command buffer and waiting is
coarser than an MTLFence would be — the teacher-forced lane’s comment
shows the trade was considered and taken for host-encode overlap instead
(decode.rs:2151-2155). The compensating control is the bounded wait
(context.rs:153-175): the coarser primitive cannot wedge a serving thread
indefinitely.
35.10 What comes next
Ordering and hazards were the last invisible ingredient in the token’s journey — and the +196 reconciliation named one family, the 39 SWA staging groups, that belongs to a graph this book has only glimpsed: the prefill route, where whole prompts flow through batch-shaped kernels, wrapped rings force a staging shadow, and chunk boundaries yield to waiting decoders. Decode is a matvec story; prefill is a GEMM story; the same weights star in both. Ch 36 walks the second graph.
References
crates/muser-engine/src/metal/buffer.rs:7-14, 246-277—shared_tracked(quoted, with the b9678d4 incident) and the uninitialized-KV contract.crates/muser-engine/src/decode.rs:5448-5460— one command buffer per token, concurrent encoder, explicit barriers.crates/muser-engine/src/decode.rs:6102-6122, 6256-6279— unretained- references prefill command buffer; serial/concurrentGraphEncoder; the broad-scope closure barrier (quoted); thedispatchtrampoline.crates/muser-engine/src/decode.rs:975-983, 1331-1333— encoder-mode and fusion defaults with their A/B flags.crates/muser-engine/src/decode.rs:2151-2155, 5660-5671— queue serialization comment; the store→barrier→attend RAW (quoted).crates/muser-engine/src/decode.rs:4297-4305— the staged-shadow resource barrier on the SWA route.crates/muser-engine/src/metal/encode/attn.rs:247, 256, 293, 316, 428, 558, 614, 632, 745-783— the targeted-barrier inventory; the scoped- dependency comment (quoted).crates/muser-engine/src/metal/context.rs:142-184— bounded completion waits.[docs/decode-dispatch-gap-20260815.md]— read in full for this chapter: the instrumentation correction, the 760/564/+196 reconciliation (Table 35.1), the landed/rejected reduction table (Table 35.2), the hybrid postmortem numbers, and the ranked remaining work.crates/muser-engine/src/decode.rs:6143-6149, 6224-6236—PhaseProfileras the closure-counting instrument behind §35.4.[ferrite-book Ch 21],[ferrite-book Ch 22],[ferrite-book CORRECTIONS-2026-07 §3a]— the ancestor’s compiled-program/barrier-plan design and its corrections register (lineage/contrast only; A18 Pro numbers are ancestor context, never Muser results).- Ch 16 §16.9 — the staging and split families from the attention side; Ch 38 — how the anchor change (J0/J1) closed what the fusions could not; Ch 40 — the norm-boundary fusion as a catalogued rejection.
Chapter 36 — Prefill vs decode: the two graphs
status: polished · path: Muse Glimmer, pinned Muser tree
Prerequisites: Ch 1 (bytes per token), Ch 10 (the decode graph and the “prefill is a different graph” preview), Ch 13 (matvec math — this chapter generalizes it to GEMM), Ch 15 and Ch 16 (the ring the staging shadow protects), Ch 34 (chunk boundaries and decode-priority). GEMM is defined here from the matvec you already know.
Ch 35 closed with the dispatch-gap families, and the largest structural one — 39 SWA staging groups — lives on a route this book has visited only in passing: prefill. Time to walk it properly. Everything in Parts III–IV was the decode graph: one token, one row, matvecs, a bandwidth story. Prefill is the same 52 layers, the same weights, the same sandwich norms — run over a prompt of hundreds or thousands of tokens at once. The math per layer is identical; the shape of every operation changes; the bottleneck flips from memory to compute; and on the disaggregated lane of Part VI the whole graph may be skipped on the Mac entirely, replaced by a wire and a KV install. This chapter is the two-graph chapter: decode-matvec-serial versus prefill-GEMM-parallel, in code.
36.1 The same model, two regimes
Start with the question everything below answers: what makes a prompt a different kind of work from a token, when the weights, the layers and the math are identical? Fix the vocabulary once more (it was introduced in Ch 10 §10.1 and every performance claim in the book is scoped to one of these):
- Prefill — process the entire prompt as one batch
of query rows:
Ttokens through each weight matrix together. - Decode — one token in, one prediction out, repeat.
The engine’s routing is disarmingly small — token count picks the regime:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/decode.rs:2082-2094
if tokens.len() == 1 {
let scheduler = Arc::clone(&self.shared.scheduler);
let _permit = scheduler.acquire(self.sequence_id, AcceleratorWork::Decode)?;
// …(serving-route comment; elided — quoted in Ch 10 §10.2)…
*logits = self.forward_batch(tokens)?;
return Ok(());
}
}
One token takes the decode route (through the one-row batch graph, as
Ch 10 explained); more than one takes
the prefill loop of §36.7. From that single branch, everything differs
downstream: the projections, the attention kernels, the workspace sizes, the
scheduler work class (Decode vs Prefill permits,
Ch 34). Pause on how little machinery that is.
Two regimes, two kernel families, two classes of work for the scheduler to
arbitrate — and the thing that picks between them is the length of a slice.
36.2 The roofline flip, with the arithmetic shown
Why do the regimes deserve different kernels? Because of one ratio: arithmetic intensity — FLOPs performed per byte read from DRAM. Derive it for Muse Glimmer, both regimes, from facts you already have.
The weight stream is the same for both. Every forward pass — one row or
five hundred — reads the weight arena once per matrix touched. The pinned
artifact is 16,756,681,056 bytes [crates/muser-engine/src/lib.rs:14] for a
model this book counts by hand at 27,854,794,240 parameters total — the
“~30B” class of [docs/muser-architecture.md] — of which the matmul subset
that generates FLOPs is 26,508,558,312 ≈ 26.5e9 (both counts derived in
Ch 1 §1.3; the difference is the
embedding table, which is a gather, not multiplies).
Per row, each weight parameter participates in one multiply-accumulate — 2 FLOPs. So one decode row performs ≈ 2 × 26.5e9 FLOPs while moving 16.7566e9 bytes of weights, plus its (small) activations and KV:
decode intensity ≈ 2 × 26.5e9 FLOPs / 16,756,681,056 B ≈ 3.2 FLOPs/byte
prefill, B rows ≈ B × 3.2 FLOPs/byte (each weight byte feeds B MACs)
B = 64 → ~202 FLOPs/byte
B = 512 → ~1,619 FLOPs/byte
Figure 36.1: arithmetic intensity per weight byte, derived. The numerator is the canonical matmul parameter count 26,508,558,312 (Ch 1 §1.3); the denominator is the exact artifact size. Intensity scales linearly with batch width B — the whole roofline story in one line.
Now place the two points against the machine. The M3 Ultra’s memory system
is of the ~800 GB/s class [ledger L0]. The GPU’s compute ceiling on this
part is not published by Apple and was not measured by the campaign
[unverified] — so take the knee (the intensity where compute time equals
memory time) symbolically: knee = FLOPs_ceiling / 800 GB/s. Whatever the
true ceiling, decode at ~3.2 FLOPs/byte sits far below any plausible knee —
memory-bound: halve the bytes, halve the time — while a 512-row prefill
chunk at ~1,619 FLOPs/byte sits far above it — compute-bound: time tracks
FLOPs, and extra bytes are nearly free. This is the flip.
Say it once more without the ratio, because it is the load-bearing idea of the chapter. A decode token drags the entire weight arena across the memory bus in order to compute a single row, and then throws that traffic away; the bus sets the clock. A wide prefill chunk drags the same arena across the same bus, but every byte that arrives is put to work by every row in the batch before it is discarded; now the arithmetic sets the clock. Same weights, same bus, a different master. That is why Ch 1 could cost a decode token by bytes alone, and why prefill wants the opposite of everything the decode path optimizes for: wide tiles, arithmetic-dense kernels, batches as large as memory and latency allow.
One corollary to carry through Part VI: because prefill is compute-bound, a remote prefiller with more compute (the GB10’s tensor cores) plus a wire can beat local prefill even after paying the network — the economics Ch 27 quantifies.
36.3 prefill.rs is a signpost — by design
Where does the prefill code live? In a file that is only a module document, and the file itself explains why:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/prefill.rs:1-17
//! Batched GPU prefill driver — muse-fixed, no VM. macOS-only.
//!
//! **REIMPLEMENTED** (docs/muser-architecture.md §D), replacing Ferrite's
//! `forward_gpu/engine_prefill/*`. The implementation lives beside decode in
//! `decode.rs` so both routes share the exact layer graph. It is batched
//! over T query positions, exploiting the same weight-row-reuse
//! `weights.rs` documents ("prefill of T tokens ≈ one token's DRAM
//! traffic"). Release throughput remains gated on the paired campaign.
//!
//! Also the Mac-local fallback path when GX10 disaggregated prefill
//! (`muser-cluster`) isn't available or isn't worth the wire hop for a
//! short prompt.
//!
//! Chunks retain their activation/token arenas and encode the full 52-layer
//! graph into one serial command encoder. Cache placement remains explicit in
//! logical/physical ring metadata and never derives placement from absolute
//! positions.
}
Every sentence is load-bearing. Reimplemented, no VM — this is the same divergence from the ancestor’s compiled-program design that Ch 35 §35.8 documented for decode. Beside decode so both routes share the exact layer graph — the exactness argument again: one layer graph, two widths, not two implementations that could drift. “prefill of T tokens ≈ one token’s DRAM traffic” — Figure 36.1 restated as the reuse invariant. Fallback when disaggregated prefill isn’t worth the wire hop — the lane boundary of Part VI, resting on prompt length. And the closing paragraph names the two invariants this chapter will watch: explicit ring placement (no absolute-position addressing, Ch 15) and the chunk arena discipline.
The actual driver is forward_batch → forward_batch_hidden →
encode_batch_hidden_range [crates/muser-engine/src/decode.rs:2857, 3788,
3824-3855], and the last is the prefill twin of Ch 10’s
encode_token — the function a prompt chunk actually flows through:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/decode.rs:3857-3875
#[allow(clippy::too_many_arguments)]
fn encode_batch_hidden_range<T: EncodeTarget + ?Sized>(
&mut self,
batch: &BatchActivations,
swa_staged_key: &GpuHalfBuffer,
swa_staged_value: &GpuHalfBuffer,
fa_prefill: Option<(&GpuBytes, &GpuBytes)>,
token_count: usize,
start_position: usize,
command: &T,
capture_layers: &[usize],
capture_buffers: &[GpuBuffer],
layer_major_capture: Option<&GpuBuffer>,
batch_logits: Option<&GpuBuffer>,
tail_capture: Option<BatchTailCapture<'_>>,
layers: Range<usize>,
encode_entry: bool,
encode_output: bool,
) -> Result<(), MetalModelError> {
}
The parameter list is the chapter’s table of contents: a BatchActivations
workspace (T-scaled twins of the decode pool), the two SWA staging shadow
planes (§36.6), the llama prefill mask/block pair (§36.5), a layer range
(this same function also encodes partial layer spans for the DFlash split
graph of Ch 10 §10.6), and entry/output
switches. Inside, the loop is encode_token’s sequence — norm, projections,
QK-norm, RoPE on SWA layers, attention, gate, o_proj, fused tails — with
every dispatch carrying token_count rows instead of one
[crates/muser-engine/src/decode.rs:3922-4007].
36.4 Projections: from matvec to GEMM
What happens to a projection when the batch widens? Nothing, mathematically: the same weight matrix meets the same kind of input. What changes is the shape, and shape is the only thing a GPU kernel really has an opinion about.
A matvec computes y = W·x for one vector x: each
output element is a dot product of one weight row with x
(Ch 13). Prefill has T input vectors —
Y = W·X, a GEMM (general matrix-matrix
multiply): output row i, column t is W_i · X_t. The weight row W_i
is read once and used T times — Figure 36.1’s reuse, in kernel terms.
The dispatch site is encode_batch_projection, and its first branch is a
lane surprise worth slowing down for:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/decode.rs:5955-5961
if token_count == 16
&& projection.layout.dtype == GgmlType::NVFP4_E2M1
&& projection.layout.n_in.is_multiple_of(64)
&& std::env::var_os("MUSER_NO_M16_N32").is_none()
{
if let Some(input_scale_inv) = projection.layout.nvfp4_input_scale_inv {
self.kernels.encode_nvfp4_w4a4_prequant_m16(
}
On the native NVFP4 lane, 16-row chunks with 64-aligned input widths take a
quantized-activation GEMM — the W4A4 M16 route of
Ch 7, muser_nvfp4_w4a4_prequant_m16_n32
[crates/muser-engine/src/shaders/nvfp4.metal:504]. The surprise is how narrow
the keyhole is: one exact row count, one dtype, one alignment rule, and an
escape hatch in the environment. This is a shape-specific kernel that earns
its place only where a chunk happens to land on it — the flavour of the whole
prefill path, and the reason the attention story below is a tree rather than
a kernel. Everything else falls
through to the same projection stack decode uses, just with token_count
rows. The four independent Q/K/V/gate GEMMs share one dispatch closure so a
concurrent encoder can overlap them — with the comment: “Independent
projections share a read-only normalized input and write disjoint
activations. Group them so a concurrent prefill encoder can overlap the four
GEMMs” [crates/muser-engine/src/decode.rs:3953-3956]. That is
Ch 35’s closure-boundary
discipline, applied at batch width: independent work in one closure, real
dependencies at closure edges.
36.5 Prefill attention: the route tree
Which attention kernel should a chunk of prompt use? Decode answered that question per layer per token, climbing the four-rung ladder of Ch 16 §16.0. Prefill has to answer it per layer per chunk, and the answer is not one kernel: it is a three-route tree, plus a fail-safe. What sorts a chunk into the trunk is a single predicate:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/decode.rs:4104-4106
let flash_contiguous = old_origin_logical == 0
&& old_origin_physical == 0
&& old_len + token_count <= capacity;
}
A chunk whose cache span is contiguous from physical row zero — the common case for a fresh prompt before any ring wraps — takes one of three routes; otherwise an SWA layer jumps to the staging shadow (§36.6). The decision, as a picture (Figure 36.2):
flowchart TD
CHUNK["prefill chunk: token_count rows<br/>at start_position, layer L"] --> CONTIG{"flash_contiguous?<br/>decode.rs:4104"}
CONTIG -- "no, SWA layer" --> STAGE["staging shadow route (§36.6)"]
CONTIG -- "no, NoPE" --> F32F["encode_attention_prefill_f32<br/>(fail-safe; unreachable under valid bounds)"]
CONTIG -- "yes" --> STORE["append_batch + encode_kv_store_batch_f16<br/>(store first: match the live f16 cache)"]
STORE --> VEC{"token_count < 20<br/>and metallib loaded?"}
VEC -- "yes (decode.rs:65-70)" --> PVEC["one unmasked llama vec launch<br/>per query row — exact PSO + reduction"]
VEC -- no --> LLA{"NoPE layer, chunk-aligned?<br/>8 | token_count, 32 | visible"}
LLA -- "yes (decode.rs:56-63)" --> MASKBLK["mask/blk once per chunk<br/>+ kernel_flash_attn_ext_f16_dk128_dv128"]
LLA -- no --> FA2["encode_flash_attention_v2<br/>(local FA2)"]
Figure 36.2: the prefill attention route tree
[crates/muser-engine/src/decode.rs:4090-4374]. “llama” = pinned metallib
kernels (MUSER_GGML_METALLIB); SWA = the 39 sliding layers; NoPE = the 13
full layers.
Route (a) — short chunks: the pinned vec kernel, per query. The pinned
Metal backend selects its vec flash-attention kernel for batches below 20
queries (llama_vec_prefill_route_available: `token_count < 20 && capacity
= 32 && has_llama_flash_attention && !cross_vendor`, [crates/muser-engine/src/decode.rs:65-70]). Muser then runs one unmasked vec launch per query row with that row’s exact visible-prefix length — and the comment explains why something so brute-shaped survives: it is “equivalent to its causal mask (NQPSG=1 for DK128), while reusing the exact upstream PSO and reduction order. This matters for public embedding/logprob parity: the older local FA2 path was mathematically close but diverged sharply after four positions across 52 layers” [crates/muser-engine/src/decode.rs:4117-4127]. The recurring exactness theme again: mathematically close is not a compatibility contract.
Route (b) — NoPE layers at chunk bounds: the pinned non-vec kernel.
Full-attention layers with token_count a multiple of 8 and the visible
prefix a multiple of 32 take llama’s own masked causal prefill kernel
(llama_fa_prefill_route_available, decode.rs:56-63). The comment: “same
kernel, tiling, and reduction order the comparator measures. SWA layers and
unaligned shapes keep the local FA2 route” [crates/muser-engine/src/decode.rs:4181-4184].
The dispatch is two encodes — a per-chunk mask/block preparation shared by
every eligible layer, then the attention itself:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/decode.rs:4195-4198, 4207-4209
if !llama_fa_prefill_mask_ready {
dispatch(command, |encoder| {
self.kernels.encode_llama_fa_prefill_mask_blk(
// …(mask/blk dispatch once; elided)…
dispatch(command, |encoder| {
self.kernels.encode_llama_flash_attn_prefill_f16(
}
The two wrappers [crates/muser-engine/src/metal/encode/attn.rs:266-317,
328-369] carry the contract. The first is preparation:
encode_llama_fa_prefill_mask_blk fills a
causal f16 mask and runs llama’s flash_attn_ext_blk block classifier, which
emits bytes marking each 32×8 tile skip/partial/dense — “what make the pinned
kernel’s causal prefill cheap on the fully-masked upper triangle”
[attn.rs:259-265]. That classification is why the preparation earns a
dispatch of its own. A causal chunk’s mask is mostly holes, and a kernel told
in advance which tiles are entirely masked simply never reads them. It is
also why the dispatch is per chunk rather than per layer: a barrier follows
each stage, and the wrapper notes that “one
dispatch per prefill chunk is shared by every full-attention layer in that
chunk” [attn.rs:262-263] — prepared once, amortised across every eligible
layer in the chunk.
The second wrapper is the attention itself.
encode_llama_flash_attn_prefill_f16 is
kernel_flash_attn_ext_f16_dk128_dv128 from the pinned metallib, run at the
shape constants LLAMA_FA_PREFILL_NQPTG = 8, LLAMA_FA_PREFILL_NCPSG = 32,
LLAMA_FA_PREFILL_NSG = 4 [crates/muser-engine/src/metal/encode.rs:1069-1072].
The strides are where Muser’s layout meets ggml’s expectations —
“Q and the output stay in Muser’s token-major [token, head, dim] f32
layout via explicit strides” [attn.rs:325-326]. No transpose pass, no copy:
just an honest description of the memory the pinned kernel is about to read.
Route (c) — everything else: local FA2. flash_attn_v2
[crates/muser-engine/src/shaders/ferrite/flash_attn_v2.metal:59] over the
just-stored f16 cache — including every SWA layer whose chunk has not caused
a ring wrap. Note the store-first order shared by all contiguous routes:
“Ferrite’s production order is store-to-F16 then FA2. This also makes
prefill arithmetic match the live cache used by subsequent decode rather
than attending transient F32 K/V” [crates/muser-engine/src/decode.rs:4108-4110]
— prefill’s outputs must be the same bits a later decode token would have
attended from, so it reads through the same f16 planes.
36.6 The SWA staging shadow: prefill through a wrapped ring
Now the 39-group family from Ch 35,
and the awkward case it exists to survive. Once a sliding layer’s ring has
wrapped (Ch 15’s origin_logical > 0), the live cache is no longer a
contiguous span from row zero — flash_contiguous fails, and the pinned
kernels cannot walk an arbitrary physical rotation. Prefill still has to run
on that layer, and it still has to produce exactly the bits a later decode
token will attend from. The route out:
#![allow(unused)]
fn main() {
// crates/muser-engine/src/decode.rs:4247-4252
} else if cfg.layer_kinds[layer_index].is_swa() {
// Preserve Ferrite FA2 after the explicit SWA ring wraps.
// The staging arena is a detached logical tail: old ring rows
// in logical order followed by this chunk, all F16 exactly as
// the production cache stores them. The live ring is changed
// only after attention has consumed the complete shadow.
}
Three steps, per wrapped SWA layer, per chunk. Stage:
encode_stage_swa_prefill_f16 dispatches muser_stage_swa_prefill_f16, a
(old_len + token_count) × kv_dim gather that rewrites the ring’s retained
logical tail into a detached shadow plane, in logical order, followed by
the chunk’s own K/V rows. Attend: FA2 runs against the shadow, which is
contiguous by construction, so window masking against logical indices now
matches physical layout. Commit: only after attention has consumed the
whole shadow does append_batch update the live ring’s metadata. The
evidence trail, in step order: the stage kernel
[crates/muser-engine/src/shaders/muse_reference.metal:1240] and its wrapper
[crates/muser-engine/src/metal/encode/attn.rs:103-138]; the attend site
[crates/muser-engine/src/decode.rs:4328-4345]; the commit
[crates/muser-engine/src/decode.rs:4348-4352].
Read that ordering once more, because it is the entire trick. The live ring is never observed mid-rebuild — not because the rebuild is fast or carefully interleaved, but because the thing attention reads is not the ring at all. It is the same build-detached-then-swap shape as the remote KV install of Ch 10 §10.7 and the server’s staging generation of Ch 34 §34.6, here at kernel granularity.
There is also a single-row special case with an exactness twist. When a
one-row continuation lands on a wrapped ring (token_count = 1, metallib
present, full window), the stage kernel is muser_stage_swa_llama_decode_f16
[crates/muser-engine/src/shaders/muse_reference.metal:1281], and its wrapper
comment is the sharpest sentence in the file:
“Materialize Muser’s compact SWA ring at llama.cpp’s absolute, 256-row- padded KV indices for one-row decode. The staged mask retains llama’s masked cells, so the pinned vec kernel sees the same reduction lanes rather than a mathematically equivalent compact permutation.” [crates/muser-engine/src/metal/encode/attn.rs:140-143]
The shadow is not merely a contiguous copy — it reproduces llama’s padding and mask topology, so the pinned vec kernel reduces over the same lanes llama itself would. A resource barrier over the three staged buffers orders the RAW before attention [crates/muser-engine/src/decode.rs:4300-4305]. This is why the 39 staging closures of Ch 35 carry the disposition “Keep until a bit-exact ring-aware replacement exists” [docs/decode-dispatch-gap-20260815.md]: the copies are the price of pinned reduction lanes, and the ranked-work item gates any replacement on “bitwise KV and full-logit equality at positions 1, 31, 32, 33, 2,047, 2,048, and 2,049.”
36.7 Chunking, and yielding to decode
Prompts do not arrive pre-sliced, so something has to decide how much of one crosses to the GPU at a time. Throughput argues for the widest possible chunk; but the GPU is not private, and somewhere another sequence may already be waiting on its next token. That tension is what this section resolves.
forward_into’s prefill loop
(Ch 34 §34.2, quoted there) walks the prompt in
PREFILL_BATCH_TOKENS = 512-row chunks — and shrinks the next boundary to
MAX_TEACHER_FORCED_TOKENS = 64 rows the moment any decoder is queued
[crates/muser-engine/src/decode.rs:53-54, 2097-2113]. Each chunk acquires a
Prefill permit per Ch 34’s decode-first
scheduler, so between chunks a waiting decode always wins the accelerator.
The chunk width is also a memory decision, and the reason that matters here
rather than in the memory chapter is that it caps the first argument: you
cannot widen a chunk past what its workspace costs. The batch workspace’s
activation twins scale with rows, and ~0.99 GB of f32 batch-activation widths at the
512-position chunk are reused, with the explicit caveat that this “must
not be labeled peak RSS” [docs/memory-footprint.md].
36.8 The measured contrast — and the two latencies users feel
So does the two-graph story survive a comparator? The campaign measured both graphs against the pinned llama build, and the three results below are best read as one argument rather than as three scores: the graphs hold parity locally, the prefill graph is the one worth moving off the machine, and the two of them are what a user actually feels as two different waits.
Local parity, both regimes. The production six-depth plain matrix
(2,048 → 131,008 positions, five exact-token reps per depth, llama ÷ muser
ratios) measured prefill means 1.0139–1.0397× and decode means
1.0274–1.0504× across depths — 30/30 cells at or above parity
[ledger, "Phase 2 non-spec context matrix"; receipt root ctx-matrix-plain-b972b55-20260819/]. Both graphs hold parity against the
pinned comparator; read the tables’ scope language before quoting any single
cell Ch 38.
Disaggregation, the prefill-side lever. When prefill moves to the GX10,
TTFT — time to first token, almost pure prefill plus wire — improves
4.26× at 2,048 tokens, a figure the Phase-4 context matrix produced over
five reps per cell. The deep end of the band behaves the same way: 4.149×
at 130,815 tokens, where a remote 137.405 s median stands against a local
570.122 s mean on the EEE-off arm. Three records sit behind that pair of
numbers — the claim that fixes what the speedup does and does not cover, the
ledger matrix the shallow cell came from, and the A/B run that settled the
deep one — and we kept all three
[claims #6; ledger "Phase 4 disaggregated GX10→Mac context matrix"; ledger "EEE A/B at 130815"]. The full depth band and its caveats are
Ch 27’s subject — this chapter only claims the
regime split that makes the lever coherent.
TTFT and TPOT are the user-facing twins of this chapter’s two graphs.
Time-to-first-token is prefill-dominated; time-per-output-token is decode-
dominated. That one sentence sorts nearly every serving optimization in this
book into two piles: disaggregation and kvpack warm reuse
(Ch 25)
attack TTFT; DFlash speculation (Ch 33)
attacks TPOT. The speculation numbers belong to that lane’s own chapter, but
the scope they carry belongs here — the kquant spec bar 107.9 tok/s, and a
current synthetic restatement
decode ratio 1.23692 at 2,048, five of five exact reps [claims #15]. Even
the tuning traces back to this split: the
frozen serving verify-length 7 came from exactly this matrix’s natural-text
cells [ledger, "Spec re-measurement at the fixed window"]. And when both
graphs are measured together, what comes out is the
131,008-depth wall-parity cell — end-to-end 1.02536× with prefill 1.02460×,
the first 131k-class result above parity [claims #16].
36.9 Tradeoffs
Three attention routes instead of one. The obvious design is one route. Muser owns a perfectly good flash-attention kernel of its own; point every prefill layer at it and the tree collapses into a straight line. That is how the graph ran. The expectation was mild — the local kernel and the pinned one compute the same attention over the same cache, so their outputs should agree to within floating-point noise, and nothing a client can observe through a public embedding or logprob endpoint should be able to tell them apart. It could. The source still carries the note of what went wrong: the local FA2 path “was mathematically close but diverged sharply after four positions across 52 layers” [crates/muser-engine/src/decode.rs:4121-4124]. The lesson is the one this book keeps relearning from a different angle each time — agreeing on the algebra is not agreeing on the reduction order, and a parity contract is written in bits. So the tree stayed, and each route is a concession to that: pinned vec for short chunks and parity-critical reads, pinned non-vec for aligned NoPE chunks, local FA2 for everything the pinned kernels cannot legally walk. What it costs is complexity — predicates on chunk shape, alignment, layer class, lane. What justifies the cost is the divergence above, plus the parity matrices above, run with the pinned routes in place. What we never ran is an isolated A/B of route (b) versus route (c) on throughput alone, so no such number exists in the campaign evidence [unverified].
Stage-then-attend versus a ring-aware kernel. This is a fork we left standing rather than closed, and the price of leaving it open is visible in the diagnostics. The staging shadow pays 39 closure groups per wrapped token diagnostic [docs/decode-dispatch-gap-20260815.md] plus shadow-plane memory (two 131,072-row staging planes in the batch workspace, Ch 10 §10.8). The alternative — an attention kernel that addresses the rotated ring natively, with no copy at all — is not hypothetical: it already exists on the decode side, as the splitk rung of Ch 16. What keeps it out of the prefill path is not doubt about the kernel. It is the proof obligation attached to it, bit-exactness at seven named boundary positions before it could replace the pinned-lane staging route [docs/decode-dispatch-gap-20260815.md §Ranked remaining exact work]. Until someone does that work, copying rows is the cheaper mistake to be making.
512/64 adaptive chunks. This tradeoff is deliberately lopsided, and worth naming as such. Wide chunks maximize GEMM intensity (Figure 36.1); the shrink-to-64 rule throws that intensity away precisely when a decode is waiting, buying a cap on decode’s worst-case queue-behind interval [crates/muser-engine/src/decode.rs:2098-2101]. In other words, prefill is made slower on purpose so that somebody else’s stream does not stutter. What we cannot tell you is how much slower: the prefill-throughput cost under concurrent load was never isolated as a measurement [unverified]. The benefit side is the bounded decode-latency argument, and the constants are in source.
Local prefill retained at all. Even with a qualified GX10 lane, the Mac keeps this graph — short prompts “aren’t worth the wire hop” [crates/muser-engine/src/prefill.rs:10-12], and the lane must degrade when the producer is down (Ch 28’s fail-closed ritual). The crossover is an operator/economics question Ch 27, not a constant in this code.
36.10 What comes next
Both graphs now end the same way: with logits, samples, and tokens that exist because a person, somewhere, sent an HTTP request. Everything in Parts I–VII ultimately serves a wire protocol — llama-compatible routes, sessions that outlive connections, migration between decode nodes, and a security boundary that decides who may ask at all. The last chapter of this part assembles that surface, and the deliberately asymmetric auth model that guards it. Ch 37 is the server.
References
crates/muser-engine/src/prefill.rs:1-17— the module header (quoted in full): reimplementation note, reuse invariant, fallback role, chunk invariants.crates/muser-engine/src/decode.rs:53-54, 59-70, 2082-2113— chunk constants; prefill route predicates; the regime branch and adaptive chunking.crates/muser-engine/src/decode.rs:2857, 3788-3855—forward_batch→forward_batch_hidden→encode_batch_hiddenchain.crates/muser-engine/src/decode.rs:3857-4007—encode_batch_hidden_range(signature quoted) and the batch layer loop.crates/muser-engine/src/decode.rs:4090-4374— the prefill attention route walk:flash_contiguous, pinned-vec comment, llama non-vec comment, staging route (quoted), NoPE fail-safe.crates/muser-engine/src/decode.rs:5946-5980—encode_batch_projectionand the M16 NVFP4 predicate (quoted).crates/muser-engine/src/metal/encode/attn.rs:103-183— the two staging wrappers; the llama-lanes comment (quoted).crates/muser-engine/src/metal/encode/attn.rs:259-369— mask/blk andkernel_flash_attn_ext_f16_dk128_dv128wrappers with contracts.crates/muser-engine/src/metal/encode.rs:1066-1072— the pinned prefill shape constants.crates/muser-engine/src/shaders/muse_reference.metal:1240, 1281— the staging kernels.crates/muser-engine/src/shaders/ferrite/flash_attn_v2.metal:59— local FA2.[docs/memory-footprint.md]— batch workspace widths; chunk arithmetic.[docs/decode-dispatch-gap-20260815.md]— the 39 staging families and the ring-aware-replacement gate.[ledger, "Phase 2 non-spec context matrix"]/ctx-matrix-plain-b972b55-20260819/— prefill 1.0139–1.0397×, decode 1.0274–1.0504× across the six depths (five-rep exact-token means).[claims #6],[claims #15],[claims #16]— TTFT disaggregation scope; speculative restatement scope; 131,008 wall-parity scope.- Ch 27 — the disaggregation economics in full; Ch 35 — the ordering discipline this graph inherits; Ch 38 — how the parity matrices behind §36.8 were run.
Chapter 37 — The server surface: sessions, migration, and the security boundary
status: polished · path: Muse Glimmer, pinned Muser tree
Prerequisites: Ch 24 (identity binding and the sealed manifest), Ch 26 (migration’s KV-side story), Ch 34 (slots, admission, the unhealthy latch), Ch 21 (the sampler state a session carries). No web-security background is assumed; CSRF, cookies, and Origin are defined on first use.
Ch 36 ended at the point where the two
graphs produce tokens — for whom? For a client, over a wire, under an
identity, with state that must survive a connection closing and a process
restarting. This chapter assembles muser-server: the frozen
llama-compatible HTTP surface, the Ollama aliases, the logical-session and
migration machinery, and — the part most worth studying as a design — the
deliberately asymmetric security model that makes a loopback laptop
frictionless and a LAN exposure impossible to configure by accident. Two
threads from earlier chapters converge here: the identity discipline of
Ch 24 (a bundle that binds everything, refusing
to restore across any mismatch) and the fail-closed latch of
Ch 34 (uncertain state → 503, visible to the
client as ordinary HTTP).
37.1 The public serving surface
Before the route list, the question the route list answers: what can a client
ask this process to do, and which of those asks are dangerous? Muser keeps
that answer in one place. The active router is a single Axum Router
construction [crates/muser-server/src/axum_httpd.rs:487-559] — the entire
reachable surface written down in one span of one file, which is itself the
chapter’s first design decision. The architecture
document describes it as “the frozen llama-compatible completion, chat,
tokenizer/template, embedding, slot, model/property, and health routes; the
Ollama-compatible /api/generate and /generate aliases; logical session
and migration routes; /snapshot JSON; /metrics Prometheus text;
authenticated /stream WebSocket telemetry; and temporary /telemetry SSE
keyframes” [docs/muser-architecture.md §Public serving surface]. Table 37.1
enumerates it with one-line purposes, grouped by role, paths exactly as
routed:
| Routes | Purpose |
|---|---|
/, /dashboard | the same-origin dashboard (live process state only) |
/snapshot | JSON snapshot of live engine state (management auth) |
/metrics, /telemetry | Prometheus text; temporary SSE keyframes |
/health, /v1/health, /healthz | health — 503 when latched unhealthy (§37.8) |
/models, /v1/models, /props | model identity/properties surface |
/slots, /slots/{id} | resident-slot inspection and actions (erase) |
/tokenize, /detokenize, /apply-template | tokenizer/template endpoints |
/embedding, /embeddings, /v1/embeddings | embedding endpoints |
/completion, /completions, /v1/completions | llama-compatible completions |
/api/generate, /generate | Ollama-compatible aliases |
/v1/chat/completions (+/chat/completions, /control) | the chat route of §37.3; mid-stream reasoning control |
/v1/stream (GET/DELETE), /v1/streams/lookup | resumable SSE streams and lookup |
/v1/dashboard/login | API-key → cookie exchange (§37.6) |
/v1/ws-tickets | mint a 30 s single-use WebSocket ticket (§37.6) |
/v1/sessions (+/{id}, /save, /restore, /migrate) | logical-session CRUD, durability, migration |
/__muser/v1/session-transfers/* | migration two-phase control (§37.5) |
/stream | authenticated WebSocket telemetry |
/v1/nodes (+/{name}/progress) | GX10 node enrollment/progress (management auth) |
/__muser/benchmark/shutdown | benchmark-lane control |
Table 37.1: the serving surface
[crates/muser-server/src/axum_httpd.rs:488-543]. Compatibility claims are
bounded by the frozen contract: “security policy, identifiers, clocks,
paths, timings, build fingerprints, and documented Muser metrics may differ”
[docs/muser-architecture.md §Product boundary].
Two routers, not one, and the split is a bound: the main application layer
carries DefaultBodyLimit::max(MAX_BODY) (64 MiB), a 30 s request-body
timeout, and ConcurrencyLimitLayer::new(256); a separate transfer-payload
router alone gets DefaultBodyLimit::disable(), a one-hour body timeout, and
ConcurrencyLimitLayer::new(4) [crates/muser-server/src/axum_httpd.rs:544-554].
Unbounded bodies exist only on the migration lane, four at a time.
Why split the router rather than simply raise the limit? Because a session bundle can legitimately be enormous while a chat request never is, and a single ceiling generous enough for the first is an invitation on the second. Splitting the router lets the dangerous lane be dangerous in isolation: its own body policy, its own timeout, its own concurrency, and — because it is a different router — no way for an ordinary request to wander onto it.
37.2 Strict framing: unknown fields are errors
What should a server do with a field it does not recognize? The permissive
answer — ignore it, keep going — is the one most JSON APIs give, and it is
how a client ships a typo into production and runs it for a year without
anyone noticing. Muser gives the other answer. Every request DTO in the
server carries #[serde(deny_unknown_fields)], so a misspelled field is a
400, not a silently ignored value. The receipts are spread across the DTO
definitions themselves: SlotSnapshot
[crates/muser-server/src/state.rs:504-510], CreateSessionRequest
[crates/muser-server/src/axum_httpd.rs:3606-3610], SlotActionRequest
[axum_httpd.rs:1074-1080], OllamaOptions [axum_httpd.rs:2324-2326], and the
rest. Content types are likewise exact — the check is literal string
equality:
#![allow(unused)]
fn main() {
// crates/muser-server/src/axum_httpd.rs:4838-4843
fn exact_json_content_type(headers: &HeaderMap) -> bool {
headers
.get(CONTENT_TYPE)
.and_then(|value| value.to_str().ok())
== Some("application/json")
}
}
The architecture document summarizes the posture: “Request DTOs reject
unknown fields. Intentional rejections are listed in the compatibility
contract” [docs/muser-architecture.md §Public serving surface]. This is
fail-closed parsing — the same instinct as the engine refusing an unknown
producer recipe at enrollment — applied to JSON.
The trade is worth stating in the other direction too, because it is the whole argument for strictness anywhere. A rejected request is a bug report delivered instantly, to the person who can still fix it, while the request is still in their hands. An ignored field is the same bug report delivered months later by someone whose output was quietly wrong the entire time and who now has to work out which of the two systems lied.
37.3 Stateful generation: the 409 protocol
Now the harder question, and the one that decides whether a server can be trusted with a conversation at all: where does the conversation live? Chat completions can be stateless (the full conversation rides every request) or stateful (the server keeps the frontier). Statefulness buys you not resending the transcript on every turn. It costs you the entire problem of concurrent writers — two clients, or one client and its own retry, both believing they are the next turn — and the rest of this section is Muser paying that bill in full. Stateful generation is opt-in by supplying three things together, and the “together” is enforced:
#![allow(unused)]
fn main() {
// crates/muser-server/src/openai.rs:1413-1428
let stateful = match (
request.session_id.as_deref(),
request.expected_revision,
request.idempotency_key.as_deref(),
request.idempotency_request_sha256,
) {
(Some(id), Some(revision), Some(key), Some(request_sha256)) => {
Some((id, revision, key, request_sha256))
}
(None, None, None, _) => None,
_ => {
return Err(ChatError::BadRequest(
"session_id, expected_revision, Idempotency-Key, and a canonical request identity are all required for stateful generation"
.into(),
))
}
};
}
Three things: a session ID, an expected revision (a monotone
counter), and an Idempotency-Key. A fourth rides along that the client
never sends — a canonical SHA-256 of the request body, which the handler
computes for itself rather than trusting a client to describe its own request
[crates/muser-server/src/axum_httpd.rs:3179-3186]. Any one of them without
the others is a bad request; there is no half-stateful mode to slip into by
accident. Then the store’s begin runs the
admission logic, and every failure mode in it is a 409 Conflict
(ChatError::Conflict maps to 409 [crates/muser-server/src/openai.rs:656,
668]):
#![allow(unused)]
fn main() {
// crates/muser-server/src/session_store.rs:338-358
let record = records.get_mut(id).ok_or("session does not exist")?;
if let Some(cached) = record.idempotency.get(idempotency_key) {
if cached.expected_revision != expected_revision
|| cached.request_sha256 != request_sha256
{
return Err(
"Idempotency-Key is already bound to a different session mutation".into(),
);
}
return Ok(BeginMutation::Replay(cached.result.clone()));
}
if record.busy {
return Err("session is busy".into());
}
if record.revision != expected_revision {
return Err(format!(
"session revision conflict: expected {expected_revision}, current {}",
record.revision
));
}
record.busy = true;
}
Read it as three guards in order. Replay: the same key bound to the same
revision and the same request digest returns the cached result — a network
retry after a lost response is answered with the original completion, not a
second generation [crates/muser-server/src/openai.rs:1437-1465]. The same
key with a different request is refused; a key is a commitment.
Busy: one mutation per session at a time. Revision: an
optimistic-concurrency compare — the client must state which revision it
believes it is extending, and a mismatch is a conflict, never a merge.
Only after all three guards pass does the mutation happen: commit
atomically advances revision = expected_revision + 1 under the registry
lock and files the idempotency record
[crates/muser-server/src/session_store.rs:385-409].
Two ceilings sit on top of this machinery, and neither is an accident. The
per-session idempotency map is bounded at 64 entries and cleared wholesale
on overflow — a memory of retries, not a permanent log. And at most 64
logical sessions are tracked at once (MAX_LOGICAL_SESSIONS
[crates/muser-server/src/session_store.rs:14];
[docs/muser-architecture.md §Context and sessions]). Bounded everything,
again; the rest of the bounds are swept into one table later in the chapter.
37.4 The session bundle: identities welded to state
A session is only worth saving if what comes back is the same session —
not a plausible reconstruction of one, and not a conversation replayed
through a slightly different machine. That requirement drives the whole
design, so it is worth asking the concrete version first: what does a session
contain? The SessionBundle — and its field list is the chapter’s thesis
in one type:
#![allow(unused)]
fn main() {
// crates/muser-server/src/session_store.rs:18-31
pub(crate) struct SessionBundle {
pub schema: String,
pub session_id: String,
pub revision: u64,
pub context_epoch: u64,
pub model_sha256: String,
pub tokenizer_sha256: [u8; 32],
pub template_sha256: [u8; 32],
pub layout_abi: String,
pub dflash_identity_sha256: Option<String>,
pub vision_projector_sha256: Option<String>,
pub vision_preprocessing_sha256: Option<String>,
pub target: muser_engine::cache::SessionCacheSnapshot,
pub target_logits: Vec<f32>,
pub dflash: Option<muser_engine::dflash::DFlashContextSnapshot>,
}
— plus the RNG seed, the full sampler-state snapshot (the four Mt19937
streams of Ch 34 §34.3), sampler history, the
detokenizer’s and stop matcher’s pending fragments, grammar state with its
own digest, the canonical replay plan, and vision rows
[crates/muser-server/src/session_store.rs:32-47]. The architecture document’s
sentence: bundles “bind exact model, tokenizer, template, layout, and state
identities together with target/DFlash state, sampler state, replay
messages, vision rows, context epoch, and revision”
[docs/muser-architecture.md §Context and sessions].
This is Ch 24’s manifest discipline generalized to
a whole session: state is only restorable against the identities that
produced it. The chat path enforces it on every continuation — model,
tokenizer, template, the layout ABI string ("muse-kv-layout-v1"), the
DFlash draft identity, and the vision projector identities must all match
this server, or the request is a 409
[crates/muser-server/src/openai.rs:1516-1530]. Even the sampler stream is
welded in: “A restored logical session continues the exact sampler stream
that was committed with its KV/logit frontier. A fresh client seed only
applies when creating a new frontier, never midway through one”
[crates/muser-server/src/openai.rs:1489-1493]. A grammar_sha256 mismatch is
likewise a conflict [crates/muser-server/src/openai.rs:1494-1498]. There is
no “restore anyway.”
Say it once more in different words, because this is the idea the chapter turns on. A bundle does not store a conversation. It stores a conversation together with the exact machine that could have produced it, and on the way back in the two are checked separately. Half the fields in that struct are not state at all — they are digests, and every one of them is a way for the restore to say no.
On disk, the bundle is encrypted and authenticated: serialized with
Postcard, sealed with XChaCha20Poly1305 under a key held beside the
store, written with a magic MUSER-SESSION-V3 envelope, a 0700-mode
directory, and an atomic private write
[crates/muser-server/src/session_store.rs:9-10, 15, 428-434]. Restore refuses
anything that is not a private regular file or fails authentication
[crates/muser-server/src/session_store.rs:445-457]. The architecture
document calls these “authenticated encrypted bundles”
[docs/muser-architecture.md §Context and sessions] — the tamper-evidence
role the HMAC seal plays on the wire in Ch 30,
played locally for state at rest.
37.5 Migration: two-phase, destination first
Migration is where systems lose data, and almost always for one reason: something deleted the source before the destination was certain. So hold a narrow question while reading this section — what has to be true for a session to move without ever existing in two places, or in none?
Sessions move — between decode nodes (authenticated HTTPS between
“identically qualified Muser decoders”) or to storage tiers (enrolled kvpack
storage) [docs/muser-architecture.md §Context and sessions]. The wire-side
story (deltas, cut points, bit-exactness) is
Ch 26; here is the protocol shape:
POST /v1/sessions/{id}/migrateandPOST /__muser/v1/session-transfers/preparestart an export — modecopyormove, tier validated, a durable transfer journal created, and a transfer ID bound irreversibly to(session, destination, mode, tier)[crates/muser-server/src/session_store.rs:479-512; axum_httpd.rs:528-539].- The payload moves over the separate router of §37.1 —
PUT /__muser/v1/session-transfers/{transfer_id}/payload, the only unlimited body lane, capped instead byMAX_TRANSFER_BYTES = 32 GiB[crates/muser-server/src/session_store.rs:16]. POST /__muser/v1/session-transfers/{transfer_id}/commitcompletes the two-phase transaction, and status is idempotently queryable (GET /v1/session-transfers/{transfer_id}) “after ambiguous failures”[docs/muser-architecture.md §Context and sessions].
The ordering law is the one Ch 26 stated
for caches, holding here for whole sessions: the destination durably
commits before a move may delete the source. The journal’s own status
vocabulary records the near-miss — a failed post-commit delete leaves the
transfer in destination_committed_source_retained, never in a state that
implies the source is gone [crates/muser-server/src/session_store.rs:303-316].
That identifier is ugly on purpose. It names the exact awkward half-state the
world can be left in — copied, committed, and not cleaned up — so an operator
reading the journal after an ambiguous failure is told what actually happened
rather than what was intended. A vocabulary that cannot express the near-miss
is a vocabulary that will report it as success.
And one boundary is absolute: “GX10 is not a decode destination”
[docs/muser-architecture.md §Context and sessions] — the producer prefills;
it never holds sessions.
37.6 The security boundary, taught as a design
Here is the chapter’s design study, and it starts with a question every security design answers whether or not it admits to it: who is the attacker? Most systems answer once, globally, and then spend their lives being either too annoying on a laptop or too trusting on a network. Muser answers twice — one answer for a process on the same machine as you, another for anything arriving over a wire — and every rule below falls out of that single split. Its authorization is asymmetric on purpose, and the architecture document states the whole ladder at once:
Authorization is deliberately asymmetric:
- loopback inference is keyless;
- loopback management needs bearer auth or a same-origin dashboard session;
- dashboard login exchanges the API key for a Secure, HttpOnly, SameSite=Strict cookie, and cookie-authenticated mutations additionally need an exact Origin match and CSRF token;
- a nonloopback bind is refused before listening unless a certificate, mode-0600 private key, and mode-0600 API-key file are supplied;
- every LAN inference, telemetry, WebSocket, session, cancellation, and node management request needs authentication.
[docs/muser-architecture.md §HTTP and security boundary]
Each rung is visible in code. Loopback inference keyless: the chat
handler authenticates only when the bind is non-loopback — if state.lan && !valid_bearer(&state, &headers) [crates/muser-server/src/axum_httpd.rs:3159]
— so curl localhost just works. Management needs a key: every
snapshot/slots/sessions/nodes handler opens with
valid_management_auth(...) (bearer or dashboard cookie; e.g.
axum_httpd.rs:601-604, 1082-1085). The cookie is bound hard:
valid_dashboard_cookie re-derives the server’s own origin from Host,
compares it to the origin the session was minted for, requires an exact
Origin header match for browser WebSockets and all mutations, and compares
the CSRF token in constant time [crates/muser-server/src/axum_httpd.rs:4879-4916].
(Login itself demands TLS and an exact Origin match before it will mint
anything [axum_httpd.rs:3532-3563].) A quick definition for the reader new
to this: CSRF is the attack where some other
website’s page makes your browser send a request with your cookie to a
service you are logged into — an exact-Origin check plus a token the other
site cannot read is the standard counter, and both are present.
The nonloopback wall is config-time, not request-time. Before the server listens at all, bind validation runs:
#![allow(unused)]
fn main() {
// crates/muser-server/src/axum_httpd.rs:256-265
let lan = addresses.iter().any(|address| !address.ip().is_loopback());
let tls_pair = security.tls_cert.is_some() && security.tls_key.is_some();
if security.tls_cert.is_some() != security.tls_key.is_some() {
return Err("--tls-cert and --tls-key must be supplied together".into());
}
if lan && (!tls_pair || security.api_key_file.is_none()) {
return Err(
"nonloopback serving requires --tls-cert, --tls-key, and --api-key-file".into(),
);
}
}
And the key files must be private regular files — mode 0600 or stricter,
refusing symlinks [crates/muser-server/src/axum_httpd.rs:281-295]. You
cannot expose this server to the network by forgetting a flag: exposure
requires a certificate, a locked-down key, and a locked-down API-key file,
or the process refuses to listen.
WebSockets never carry long-lived keys in URLs. There is an obvious shortcut at this fork, and naming it is the fastest way to see why the rule exists: put the API key in the socket’s query string. It works on the first try, every client library supports it, and it writes a long-lived secret into every proxy log, browser history, and referrer header the connection passes through — places that keep things far longer than a session does. Muser refuses the shortcut. The telemetry socket accepts either a dashboard cookie (with Origin enforced) or a ticket, and the comment states the threat: “Browser WebSockets are not protected by fetch’s same-origin response rules. A dashboard cookie therefore requires an explicit Origin match; long-lived bearer auth must first be exchanged for a single-use ticket” [crates/muser-server/src/axum_httpd.rs:4734-4737]. The ticket mint is thirty seconds and single-use by construction:
#![allow(unused)]
fn main() {
// crates/muser-server/src/axum_httpd.rs:3594-3602
let ticket = random_secret();
let expires = Instant::now() + Duration::from_secs(30);
// …(insert into the ticket registry; elided)…
Json(serde_json::json!({"ticket": ticket, "expires_in": 30, "single_use": true}))
}
and consume_ticket removes it from the registry on first use
[crates/muser-server/src/axum_httpd.rs:4753-4771]. Default CORS is none, the
dashboard is same-origin with no override, and the local-CA operator
workflow (muser tls init / muser tls issue) is separate
[docs/muser-architecture.md §HTTP and security boundary].
Why call this a design rather than a checklist? Because the asymmetry encodes a model of who the attacker is. On loopback, the parties are the user’s own processes — so friction is the only cost of auth, and inference is keyless while mutations still require intent. Off loopback, the model inverts: nothing is trusted, so everything authenticates, exposure demands explicit cryptographic readiness, and even then the strongest secrets never appear in URLs where logs and history collect them.
37.7 Bounded everything
Every section so far has dropped a ceiling on the floor and walked on. Here they are swept up, because collected they answer a question no single one of them answers alone: what does this server do when the world sends it more than it expected? The answer is never “find out at run time.” Each bound was met earlier in its natural habitat; Table 37.2 is the inventory:
| Bound | Value | Where |
|---|---|---|
| Request body | 64 MiB (MAX_BODY) | axum_httpd.rs:53, 544 |
| Request-body timeout | 30 s | axum_httpd.rs:545 |
| Concurrent HTTP requests | 256 | axum_httpd.rs:546 |
| Migration payload lane | unlimited body / 60 min / 4 concurrent | axum_httpd.rs:547-554 |
| Single transfer payload | 32 GiB | session_store.rs:16 |
| Waiting admissions | 64 (MAX_QUEUED_REQUESTS) | state.rs:253 |
| Streaming channel depth | 64 (STREAM_CHANNEL_DEPTH) | axum_httpd.rs:54 |
| Backpressured writer | 5 s grace, then cancel (499) | axum_httpd.rs:55, 2271-2289 |
| Logical sessions | 64 | session_store.rs:14 |
| Idempotency records per session | 64 (then cleared) | session_store.rs:398-400 |
| Resident generations | 4 + staging (never a 5th server) | state.rs:240-243 |
Table 37.2: the bound inventory. Every queue in the process has a number; nothing waits forever, and each overflow is a defined HTTP status, not a hang.
The lesson to carry out of the table is not any individual ceiling — those are tuning, and tuning changes. It is that each one has a name, a home in the source, and a defined thing that happens when it is hit. An unbounded queue is a hang waiting for a bad afternoon; a bounded one is an error code the client already knows how to read.
37.8 The unhealthy latch: fail-closed as an HTTP status
Ch 34 §34.4 showed the SlotPool latching
itself unhealthy when a lease is poisoned. That was the engine’s half of the
decision. The half that decides whether anyone can operate the thing is
this one: when the latch trips, what does a client see, and what does a load
balancer in front of it do? Here is the client-visible half. /health
reports the latch directly:
#![allow(unused)]
fn main() {
// crates/muser-server/src/axum_httpd.rs:738-754
let healthy = state
.server
.inference
.as_ref()
.is_some_and(|runtime| runtime.slots.is_healthy());
if healthy {
Json(serde_json::json!({"status": "ok"})).into_response()
} else {
(
StatusCode::SERVICE_UNAVAILABLE,
// …(503 body; elided)…
}
The engine side of the same rule, from the architecture document: “If
rollback or accelerator state becomes uncertain, the engine latches
unhealthy and serving returns 503 until restart”
[docs/muser-architecture.md §Model and engine]. Note what this is not:
not a retry-after hint, not a self-healing reset — a wall, held until an
operator restarts the process. The rationale is the slot pool’s own comment:
recovery-in-place “avoids serving from uncertain GPU state” — nobody can vow
for the bits anymore, so nobody is allowed to serve them
[crates/muser-server/src/state.rs:474-478]. Status routes speak the same
language (“accelerator state is unhealthy”, axum_httpd.rs:1066-1070), and
/healthz exposes the latch for orchestrators alongside a degraded flag
[axum_httpd.rs:758-775]. The philosophy is Ch 39’s
subject; here it matters that fail-closed composes cleanly with HTTP — the
client sees an ordinary, cache-unfriendly 503, and load balancers do the
right thing without knowing why.
37.9 Tradeoffs
Each of the decisions below is a fork with a cheaper road visibly leading off it, and in every case the cheaper road is the one most servers take. They are collected here because the shape of the choice repeats: Muser takes the road that makes a whole class of wrong outcome impossible to represent, and pays for it in friction somewhere a human can feel.
Framing strictness vs compatibility reach. deny_unknown_fields plus
exact content types means a client sending an unknown field or
application/json; charset=utf-8 gets an error where llama.cpp might
tolerate it. The trade is recorded as deliberate — intentional rejections
are listed in the compatibility contract
[docs/muser-architecture.md §Public serving surface] — and it buys a
surface that cannot silently accept a request it half-understands, the same
discipline the engine applies to GGUF metadata
(Ch 9).
Optimistic revision CAS vs server-side merge. The 409 protocol refuses concurrent writers instead of reconciling them. Clients must retry with the new revision — extra round trips for a genuinely racing client, in exchange for a frontier that is never a blend of two histories. The idempotency replay path is what makes retry safe [crates/muser-server/src/session_store.rs:339-348]; without it, optimistic concurrency would punish exactly the flaky networks that cause retries.
Identity-welded bundles vs portable state. A bundle refuses to restore on any identity drift (model, tokenizer, template, layout, draft, vision — [crates/muser-server/src/openai.rs:1516-1530]). Portability loses: you cannot carry a session across a model update. Exactness wins everything: a restored session continues the same bits the committed frontier implies, which is the property Ch 25 and Ch 26 had to prove on the KV side with receipts.
Refuse-to-listen vs warn-and-serve. The nonloopback gate could have been a log line; it is a hard error before bind (axum_httpd.rs:261-265) with file-mode checks on the secrets (281-295). The cost is setup friction for every legitimate LAN deployment — TLS material must exist first. The benefit is that the insecure configuration is unreachable, not merely discouraged; combined with keyless-loopback, the security posture is chosen by where you bind, not by flags you forgot.
503-until-restart vs in-place recovery. The latch turns a poisoned mutex or an uncertain rollback into operator-visible downtime. The alternative — reset and continue — would manufacture confidence in state that may already be wrong; the campaign’s own hybrid-fusion postmortem (Ch 35) is the precedent for why Muser does not ship “probably fine” states. No serving-availability measurement of the latch exists [unverified]; it is a correctness decision priced in downtime.
37.10 What comes next
That is the whole engine, end to end: memory model, quantization, the model, the decode kernels, the KV cache and its portable format, the disaggregated lane, and now the orchestration and serving surface that expose it to humans. Parts I–VII have made hundreds of claims, and every one of them ended in a tag — a receipt path, a ledger row, a claims-register entry. Part VIII is about those tags: how the parity matrices were actually run, what a ratio may and may not say, why the J0 anchor flip mattered, and the evidence culture that keeps copy from outrunning receipts. Ch 38 begins with the comparator.
References
crates/muser-server/src/axum_httpd.rs:487-559— the router (Table 37.1) and the two-layer body/timeout/concurrency split.crates/muser-server/src/axum_httpd.rs:53-55—MAX_BODY,STREAM_CHANNEL_DEPTH,SLOW_CLIENT_GRACE.crates/muser-server/src/axum_httpd.rs:244-295— bind-security validation (quoted) and the 0600 regular-file checks.crates/muser-server/src/axum_httpd.rs:733-775—/healthand/healthz(quoted).crates/muser-server/src/axum_httpd.rs:3152-3186— the chat handler: loopback-keyless inference, idempotency-key capture, request digest.crates/muser-server/src/axum_httpd.rs:3532-3563, 3590-3604, 4727-4771— dashboard login; the 30 s single-use ticket (quoted); WebSocket auth andconsume_ticket.crates/muser-server/src/axum_httpd.rs:4845-4916—valid_bearer,valid_management_auth, origin-bound cookie + constant-time CSRF (§37.6).crates/muser-server/src/openai.rs:656, 668, 1413-1428, 1431-1465, 1489-1530— Conflict→409; the stateful tuple (quoted); replay; the sampler-stream and identity-binding rules.crates/muser-server/src/session_store.rs:9-47, 14-16—SessionBundle(quoted), sampler snapshot, session/transfer caps.crates/muser-server/src/session_store.rs:303-316, 325-410, 412-477, 479-512— migration disposition vocabulary;begin/commit(quoted); encrypted save/restore; export journal.crates/muser-server/src/state.rs:240-243, 253-254, 474-483, 504-510— staging isolation; admission constants; the unhealthy latch;deny_unknown_fieldsDTOs.[docs/muser-architecture.md §Product boundary, §Context and sessions, §HTTP and security boundary, §Public serving surface, §Model and engine]— the frozen surface, the 409/bundle contract, the asymmetric auth ladder (quoted), the latch sentence.- Ch 24 — the manifest/identity discipline this chapter’s bundles inherit; Ch 26 — migration’s KV-side exactness; Ch 34 — the latch’s scheduler side; Ch 38 — where the numbers come from; Ch 39 — fail-closed as philosophy.
Chapter 38 — Measuring against llama.cpp
status: polished · path: Muse Glimmer, pinned Muser tree
Prerequisites: Chapters 1 and 35 are the load-bearing ones. You need the book’s standing question (what does one token cost, where does the time go, and what may be moved without breaking exactness?) and the vocabulary of Ch 35: dispatches, closures, and why the decode graph is the shape it is.
38.1 Why this chapter exists
Chapter 37 closed the serving surface: sessions, migration, the security boundary — the last of the machinery. This part is not machinery. Part VIII is about how you know anything the previous thirty-seven chapters claimed, and this chapter is the instrument itself: how do you compare two inference engines — one you wrote, one pinned from upstream — and produce a number that survives being quoted?
The comparator is llama.cpp, pinned at commit
89e0aa6fd362617d9073e0dafc18e41241521572, running the same pinned Muse
Glimmer GGUF with its flash_attn_ext prefill route
[docs/benchmarks.md §Methodology]. Every throughput ratio in this book is
llama ÷ muser, so above 1.0 means Muser wins — a convention you must
state every time, because half the world writes it the other way
[docs/benchmarks.md §Methodology].
The ancestor Ferrite book had a chapter with this same title, and its core
lesson survives intact: absolute tok/s is noise; the same-session interleaved
ratio is the only stable cross-engine statistic [ferrite-book Ch 24]. The
Ferrite lab measured ~30 % session-to-session spread on identical builds —
75–99 tok/s bands with nothing changed [ferrite-book Ch 24]. That is
ancestor-lab context, not a Muser measurement, but the physics is universal:
thermal state, DVFS (the chip’s clock-boost governor), background load, and
memory pressure all move an absolute number between sessions. Muser rebuilds
the apparatus around that lesson — the parity ledger, the accelerator lease,
the exact-token gate — and this chapter walks all of it.
38.2 Why absolute tok/s lie
Start with the failure mode. Suppose you run Muser on Monday (35.1 tok/s) and llama.cpp on Tuesday (33.4 tok/s), and conclude Muser is 5 % faster. Three things can quietly invalidate that:
- Thermal state. A 96 GB M3 Ultra running a 16.76 GB weight stream heats up; the clock governor throttles. Monday’s cold machine and Tuesday’s warmed machine are different machines.
- Background load. A build, a backup, a browser tab compositing — anything that touches the unified memory pool steals bandwidth from the exact resource decode is starving for. Recall from Ch 1: decode time is weight-read time; anything that slows memory slows tokens exactly proportionally.
- Clock choice. Which clock did you divide by? This is the one that cost
us. On the wire side we reached for the obvious denominators first —
userspace send-time, then the receiver’s first read — expecting either to
be good enough to state a link rate. Both were rejected; only Linux
TCP_INFO.busy_timesurvived, as “the only honest link denominator” for wire rate[ledger P4, "The original installed-payload row…"]. The lesson arrived with a bill attached. The one-button wizard computed its 3.0 Gbps link gate from the wrong clock and reported 0.67 Gbps against a true 6.71 Gbps median, so a perfectly healthy link failed its own gate[ledger §2b wizard attempt 8, 2026-08-24]. A denominator is a measurement decision, not a formality.
The cure is not a better stopwatch. It is a design that makes the noise common-mode: measure both engines in the same session, interleaved, so that whatever the machine is doing to A it is also doing to B, and the ratio cancels it. Said the other way round, because this is the hinge the rest of the chapter swings on: you stop trying to measure two speeds accurately and start measuring one comparison accurately. The absolute numbers stay noisy — we publish them anyway — but their quotient holds still.
38.3 Same-session interleaved A/B — the only stable cross-engine statistic
So what does a measurement look like that the machine cannot spoil? Not a quieter machine — you will never get one. The move is to stop holding the machine still and instead measure both engines while it drifts, close enough together that every drift lands on both sides of the ratio at once.
The canonical protocol is the J3 five-pair streamed verdict, and it is
worth reading as a recipe — its five reps are Table 38.1
[ledger J3, "Stage A five-pair streamed verdict", 2026-08-15]:
- Each repetition starts a fresh Muser server and a fresh pinned llama server — no state carry-over.
- Each engine runs one uncached 2,048+1 warmup request first.
- Then one uncached streamed 2,048+256 request is measured, per engine, within the same rep window.
- Five repetitions. Tokens must match exactly in every pair.
Rep | Muser decode | llama decode | Muser prefill | llama prefill | Tokens
1 | 35.0973 | 33.3817 | 317.6623 | 306.5062 | exact
2 | 35.0850 | 33.3035 | 317.4944 | 306.3196 | exact
3 | 35.1586 | 33.4304 | 317.8869 | 306.3874 | exact
4 | 35.1939 | 33.4261 | 317.5583 | 306.2955 | exact
5 | 35.1910 | 33.4900 | 317.9913 | 305.9840 | exact
Table 38.1: The J3 five-pair table, verbatim from the ledger
[ledger J3]. Medians: Muser decode 35.1586 tok/s (CV 0.131 %) vs llama
33.4261 (CV 0.185 %) → 1.05183×; prefill 317.6623 (CV 0.060 %) vs 306.3196
(CV 0.057 %) → 1.03703×.
Three details in that table carry the whole methodology:
- The citable statistic is the ratio of per-engine medians, not the mean of per-pair ratios. Medians resist a single poisoned rep; the CV column tells you how much the five samples disagreed, and CVs under ~0.2 % here say the session was clean.
- Fresh servers per rep. Reusing a warm server would let allocator state, page-cache layout, and JIT’d pipeline caches leak between reps — real serving conditions, but not a controlled comparison.
- “Exact” is a column. Timing and correctness travel together, always.
The same discipline applies inside Muser, not only across engines — comparing
two of our own quantization lanes is the same problem wearing different
clothes. There the guard has a name: the adjacent lease window. Both
lanes are measured back-to-back under a single held accelerator lease, so the
drift between them is bounded by seconds rather than days, and everything
else is nailed down: “the same 66-token prefix, 32 teacher-forced tokens, F16
KV, flash attention, release binary, and adjacent lease window”
[ledger P1.3]. Under those conditions the kquant-vs-NVFP4 plain-decode
cells landed at 35.440 vs 35.491 tok/s — a gap you are entitled to read as
small, because the apparatus earned the right to resolve something that
narrow.
One term in that quoted setup does more work than it looks like it does. Teacher-forced means the harness feeds each engine the correct previous token rather than letting it run on its own prediction. That is what makes per-token timing comparable before you trust either engine’s sampler: both lanes walk the identical token path, so the clock is timing the same computation on both sides rather than two divergent ones.
38.4 Exact-token comparison — divergence poisons timing
Here is the rule that trips people up: if the two engines generate different tokens, you cannot compare their speed. Different tokens mean different attention patterns, different KV growth, different branch behavior in speculative rounds — different work. A 10 % “win” measured over divergent outputs is a measurement of two different computations, not of two implementations of one computation.
So every performance rep in the campaign is simultaneously an exactness check:
- Local matrices: “every cell exact-token vs llama.cpp” — “Cells that fail
exactness are not reported as passes”
[docs/benchmarks.md §Methodology]. - Remote lane: the qualifier “compares 256 greedy tokens plus every full
target-logit row”
[crates/muser-bench/src/remote.rs:3-10]— not just the chosen token, the entire 202,048-wide distribution, per position. Its gates are hard-coded:LINK_GBPS_MINIMUM = 3.0Gbps installed payload andDFLASH_ACCEPTANCE_MINIMUM = 0.95[crates/muser-bench/src/remote.rs:32,36].
Figure 38.1 shows the whole gate as a flow — correctness is checked before any throughput number exists:
flowchart TD
A[One measured cell] --> B{Tokens byte-identical?}
B -- no --> C[FAIL: no timing is reported]
B -- yes --> D[Timing recorded with CV]
D --> E{All reps exact?}
E -- no --> C
E -- yes --> F[Cell eligible for the matrix]
F --> G{Ratio ≥ 1.0 at every depth?}
G -- no --> H[Recorded verbatim as a miss]
G -- yes --> I[Verdict: PASS at stated scope]
Figure 38.1: The exactness-first gate. Correctness is checked before any throughput number exists; misses are recorded, never smoothed.
The J-series went further than tokens: after the J0 re-anchor (§38.6), the
single-row gate became equality of raw bytes — the complete
little-endian f32 logit row, 808,192 bytes, SHA-256 fc37487b… from llama’s
own llama_get_logits() [ledger J0]. And on natural text, where
cross-engine outputs do diverge, the honest move is to say so and drop the
exactness gate rather than fake it: “on real corpora, cross-engine outputs
diverge, so speed stands without an exactness gate” [docs/benchmarks.md §2].
38.5 Five-repetition means and the six-depth matrix
One ratio at one prompt depth answers almost nothing. Decode cost changes shape as the KV cache grows, and an engine can plausibly win where the cache is small and lose where reading it dominates the token. So the answerable question is never “is Muser faster?” but “faster at which depths, and does the answer survive as the context lengthens?” A single cell cannot say. A matrix can.
The campaign’s headline throughput artifact is the Phase 2 non-spec context
matrix: six prompt depths, five reps at each, every cell exact-token, zero
failures. Every timing in it came through one harness,
representative_target_smoke.py, held under the accelerator lease this
chapter unpacks later [scripts/representative_target_smoke.py], and the
whole run is retained
[ledger "Phase 2 non-spec context matrix", 2026-08-20]:
| Depth | Decode mean | Decode CV | Prefill mean | Prefill CV |
|---|---|---|---|---|
| 2,048 | 1.0504 | 0.47 % | 1.0397 | 0.12 % |
| 8,192 | 1.0429 | 0.32 % | 1.0208 | 0.06 % |
| 16,384 | 1.0414 | 0.50 % | 1.0185 | 0.06 % |
| 32,768 | 1.0479 | 0.92 % | 1.0171 | 0.05 % |
| 65,536 | 1.0274 | 1.39 % | 1.0163 | 0.11 % |
| 131,008 | 1.0277 | 0.43 % | 1.0139 | 0.23 % |
Table 38.2: The six-depth plain matrix, llama ÷ muser, five exact-token
reps per depth [ledger "Phase 2 non-spec context matrix"], receipt root
[receipt ctx-matrix-plain-b972b55-20260819/]. The 131,008 cells reserve 64
output tokens inside the 131,072 context; all other depths ran 256 outputs.
Two things to internalize. First, five reps after the stated warmup
convention is the campaign’s unit of evidence: one discarded warmup handoff
in disaggregated cells, 60 s cooldowns in spec matrices, means reported with
CV [docs/benchmarks.md §Methodology]. Single-rep diagnostics exist but are
marked † and never join a matrix (the 8,192 and 65,536 spec cells are
single-rep diagnostics at 1.214† and 1.188† [docs/benchmarks.md §2]).
Second, look at the 65,536 decode row: mean 1.0274, but “min 0.9990 rep2”
[ledger "Phase 2 non-spec context matrix"] — one rep dipped below parity and
the ledger says so in the same breath as the mean. That is the house style:
the mean is the statistic, the min is the disclosure.
The claim the matrix supports is precisely worded and precisely bounded —
this is [claims #2], one of the rows carrying OPERATOR REVIEW REQUIRED:
“Across a six-depth synthetic plain-generation matrix, Muser’s five-repetition mean prefill and decode matched or beat the pinned llama.cpp comparator at every tested depth.” — proposed wording,
[claims #2]; must retain “synthetic,” “mean,” and the exact tested range; no workload-general throughput wording.
Note what the row does not allow: it “does not establish performance on
natural workloads” [claims #2]. Which brings us to what synthetic fixtures
can and cannot see.
38.6 The J0 anchor flip — the chapter’s centerpiece
Every methodology chapter needs one story where the method itself was the problem. Here is Muser’s.
Day zero (2026-08-14). A single-sample server diagnostic — one warm
server, concurrency one, 2,048+256, exact tokens — reported Muser prefill
3.6 % faster and decode 0.7814× llama [ledger "Stage A entry gate"],
receipt [receipt human-test-target-only-run-20260814-v1/target-comparator.json]. A 22 % decode
deficit, single sample, non-notarial. The ledger recorded it as “observation,
not a parity claim” [ledger "Stage A entry gate"] — but it was now the
number to explain.
The wrong first assumption. The obvious reading was “Muser’s kernels are
slower.” The Ch 35 story is
what happened next: the +196-closure dispatch-gap diagnosis (760 vs 564
profiling closures), the reconciliation into 104 norm-boundary groups + 39 SWA
staging groups + 52 KV-publication splits + 1 copy, and the brutal finding
that every fusion which would remove the 104 groups changed bits — the
rejected hybrid’s normalized-logprob error was 3.197e-4 against a 1e-4
contract [docs/decode-dispatch-gap-20260815.md §Rejected hybrid postmortem].
The A/H-series chased the deficit through exact reductions — copy elision,
the one-query GQA FA2 kernel — and topped out at 0.8463×
[docs/decode-dispatch-gap-20260815.md §Landed and rejected reductions].
Thirteen points below the bar, with every exact lever spent.
The question that flipped everything. The engines use the same pinned
GGML Metal kernels for the quantized projection paths. If the kernels are
the same and the work is the same, whose schedule is “the” schedule? Or, as
the ledger put it when comparing submission topology: llama “commits an
initial prefix promptly, and parallel-encodes the remaining command buffers”
while Muser “encodes one retained command buffer in full before commit and
wait” — “this makes submission/encoding topology the leading hypothesis, not
a claim that llama has a fundamentally different matvec kernel”
[ledger "Stage A entry gate"].
And underneath that sat a deeper question: whose bytes gate the round?
Muser’s exactness gate until that day was a self-referential hash — Muser
had to reproduce Muser’s own historical production digest
(9cdf6323…). On 2026-08-15 the operator rewrote the contract, verbatim:
“llama.cpp at 89e0aa6fd362617d9073e0dafc18e41241521572 is THE reference. Muser’s output must be bit-identical to it, and decode performance must match it within measurement noise. The self-referential production hash 9cdf6323… is hereby retired as the exactness gate for the single-row decode path; it is replaced by equality with the pinned comparator’s own bytes.”
[ledger J0, "Stage A reference re-anchor"]
A fresh probe was built from a detached checkout of the exact pinned source;
it published llama’s complete 202,048-element f32 logit row — 808,192 bytes,
SHA-256 fc37487b8eb5… — as the one comparator golden [ledger J0].
Figure 38.2 contrasts the two anchors, before and after the flip:
flowchart LR
subgraph BEFORE[J0: self-referential anchor]
A[Muser must reproduce<br/>Muser's own digest] --> B[Muser's DAG is the truth]
B --> C[llama's different DAG<br/>= wrong by definition]
end
subgraph AFTER[J0: comparator-anchored]
D[llama's bytes are the truth] --> E[adopt llama's attention DAG]
E --> F[bit-equal full logit row<br/>SHA-256 fc37487b...]
end
BEFORE == operator contract revision ==> AFTER
Figure 38.2: The J0 anchor flip. The gate changed from self-consistency to
comparator equality [ledger J0].
J1 then transplanted llama’s attention DAG into Muser’s single-row graph
— staging the wrapped SWA ring into llama’s absolute masked layout,
dispatching the exact pinned masked-vec/pad/reduce pipelines, reproducing the
pinned LM-head scale/tanh chain literally — and landed byte-equality at
fc37487b… [ledger J1]. J3 ran the five-pair verdict of Table 38.1:
decode 1.05183×, prefill 1.03703×, every pair exact. Verdict, in the ledger’s
own voice: “Stage A met. The margin comes from retaining H3’s bounded
GPU-resident greedy chain while replacing the old self-referential attention
constraint with llama’s exact bytes and exact DAG. No tolerance, waiver,
finding-status change, readiness receipt, or seal was used.”
[ledger J3]
Sit with what happened. Muser did not get faster by optimizing. It got
honest about what the reference was, adopted the reference’s execution
shape where exactness demanded it, and the 22 % deficit — most of which had
been an artifact of comparing two valid-but-different execution graphs under
a self-chosen anchor — became a ~5 % win measured against the comparator’s
own bytes. The lesson generalizes far past inference engines: when you
cannot reach parity, before concluding you are slow, ask whose reference
frame the parity is defined in. The ancestor book’s version of this was
“the gate before perf” [ferrite-book Ch 24]; Muser’s version is sharper:
sometimes the gate is the perf story.
Stage B, briefly, because the spec lane replayed the same shape at
smaller scale. It opened badly, with a five-rep verdict of 0.8670×. We probed
six exact levers hunting for the deficit and rejected every one of them —
which is the point in an investigation where you either concede or change
instrument. The L-series changed instrument, going microbenchmark-first, and
the microbenchmark named the culprit: an n32 tile took the 16-row verify
matmul from ~148 to ~83 ms/cycle, and the verdict to 1.3273×
[ledger K0, L2]. Then watch what the campaign did to its own good news.
That 1.3273× is superseded — it predates the draft-window fix of §38.7 —
and it must not be cited as a current result
[ledger "Spec re-measurement at the fixed window"]. A real number, honestly earned, retired anyway.
38.7 Synthetic vs natural — the fixture that could not see the bug
Every measurement rests on a fixture, and a fixture is a decision about what you will be able to see. That decision deserves the question what can this prompt not show me? asked out loud and early — because if you do not ask it, the fixture will answer it for you later, at a time of its choosing.
The campaign’s synthetic fixture is a period-8 cycle of 9 token ids — a prompt whose next token is predictable from token identity alone. It exists so exactness and speed can be compared deterministically across engines. And for the entire campaign it certified a broken draft lane.
The story is Ch 33’s to
tell in full; the measurement lesson belongs here. Muser never read
dflash.attention.sliding_window (2,048) from the GGUF and hardcoded sink 64
- window 1,024 — the DFlash draft was conditioned on half its trained
window for every measurement to date
[ledger "ROOT CAUSE FOUND AND FIXED", 2026-08-21]. The consequences, measured, are Table 38.3:
| cell | acceptance before → after | decode before → after |
|---|---|---|
| python suffix 8192 | 1.1 % → 72.7 % | 0.535 → 1.322 |
| rust 2048 | 36.5 % → 59.1 % | 0.833 → 0.930 |
| synthetic 2048 (matrix fixture) | 100 % → 99.6 % | 1.3012 → 1.2368 |
Table 38.3: The window-fix deltas [ledger "ROOT CAUSE FOUND AND FIXED"],
commit a7a4d11, evidence [receipt gate-fix-20260821/].
Read the last row twice. The natural-text cells moved from catastrophic to
healthy; the synthetic cell barely moved — because a period-8 cycle is
predictable with no context at all, so it “scored 100 % acceptance
throughout the defect’s lifetime, and therefore certified a broken draft lane
for the entire campaign” [ledger "ROOT CAUSE FOUND AND FIXED", consequence 2]. The ledger’s own enrolled corollary: “natural-text cells must be a
standing part of the spec matrix” — and the current numbers carry both
regimes side by side:
- Synthetic (fixed window, current): decode means 1.23692 @2,048,
1.20323 @16,384, 1.19616 @32,768, 5/5 exact reps per depth
[claims #15](OPERATOR REVIEW REQUIRED wording; never generalize to natural text, native NVFP4, or untested depths). - Natural text: spec decode wins python-like content (16,384: 1.186;
8,192 suffix: 1.321) and loses high-acceptance shallow text — rust at
2,048: 0.931, improving to 0.945 at verify-length 7 — where llama’s lighter
draft wins
[docs/benchmarks.md §2][ledger "Spec re-measurement at the fixed window"]. That asymmetry is why serving froze verify-length 7.
The fixture question — what does the fixture hide? — is the book’s methodological chorus, and this is its proven case study: a fixture that certified a broken lane for a week of campaign time.
38.8 The apparatus — the lease, the lock, the labels, the receipts
You cannot measure fairly on a machine you do not control. That reads like a
platitude until you price it: one forgotten profiler still attached, or a
second engine that never released the GPU, and your parity verdict is a
measurement of contention instead — with nothing in the resulting number to
tell you so. Discipline you have to remember is discipline you will
eventually forget, so the campaign does not rely on remembering. Every
accelerator run — Metal, llama.cpp, Core ML — goes through one wrapper,
scripts/accelerator_safe.py:
# scripts/accelerator_safe.py:2
"""Dry-run-first, serialized wrapper for every accelerator invocation."""
Its contract, all of it load-bearing for benchmark integrity:
- Dry-run by default. Without
--executeit prints the plan and returns 0[scripts/accelerator_safe.py:35-38, 331-332]. You read what would run before anything touches the GPU. - One machine-wide lock. It holds
/tmp/ferrite.gpu.lockviaflock(exclusive, non-blocking; refusal is exit-code 75, the same fail-closed convention the GX10 producer uses)[scripts/accelerator_safe.py:21, 355-359, 393]. Before the child starts, the wrapper scans for other accelerator processes (llama-*,muser-*,ferrite-*, profiling tools) and refuses if any exist[scripts/accelerator_safe.py:24-29, 360-362], with mandatory quiet periods (≥ 10 s, default 10 s) on each side of the run[scripts/accelerator_safe.py:333-334, 364, 387]. This is why shared-machine GPU benchmarks need serialization: two engines racing on one accelerator measure contention, not code. - A forbidden list.
xctrace,gputrace,kill,killall,pkillcannot run under the lease except through the narrowly shaped--allow-profileradmission for a directgputrace headless-profile --attach-launchedcommand[scripts/accelerator_safe.py:23, 90-114]. - Receipts, not promises. Each executed run publishes an atomic,
immutable result receipt (it refuses to replace an existing receipt
file) plus an append-only
records.jsonlentry, both fsynced with directory durability[scripts/accelerator_safe.py:200-224, 427-429]. Every table in this chapter resolves to receipts like these undermuser-receipt://. - No silent retries. The plan prints
"automatic_retry": false[scripts/accelerator_safe.py:325]; a failed cell is retained evidence, and the red-team audit specifically verified that “discarded runs were kept and are statistically indistinguishable from counted ones — there is no fabrication and no result-shopping”[docs/redteam-review-campaign-brief- 20260820.md §Verdict].
The harnesses enforce the lease themselves — qualify_nvfp4_fast.py refuses
to run unless MUSER_ACCELERATOR_LEASE is set by the wrapper
[scripts/qualify_nvfp4_fast.py:307-308] — so the discipline is structural,
not habitual.
The [labels] discipline. There is a failure mode worse than a noisy
instrument, and the dispatch-gap investigation walked straight into it: an
instrument that answers confidently and wrongly. We were reading a per-stage
timing breakdown, chasing where the decode time went, and taking the stage
names on that breakdown at face value. They were not trustworthy. Production
labels omitted lm_head altogether, so its time was being charged silently
to softcap; worse, the legacy schedule still declared a separate SWA
kv_store stage that no longer existed, which shifted every legacy timing.
The profile we had been reasoning from was describing a schedule the engine
had stopped running. The repair was structural rather than clerical: labels
are now derived from post-append ring state, and the tool “aborts if label
and sample counts differ” instead of printing a plausible table
[docs/decode-dispatch-gap-20260815.md §Instrumentation correction].
The wizard side arrived at the same rule from the opposite direction. Node
onboarding reports its progress as seven versioned JSON labels relayed
verbatim over SSE [docs/one-button-onboarding.md §Six executable stages, seven progress labels], and a pass is defined by
those labels rather than by anyone’s impression that the thing worked —
attempt 9’s “native/text PASS” means precisely “all seven labels” green plus
three exact handoffs
[claims #9]. The rule is worth stating twice, because it governs the
profiler and the wizard alike: a measurement — or a pipeline stage — exists
only when its label exists and reconciles. An unlabelled number is not a
minor documentation debt. It is a number about nothing in particular.
The wire-side apparatus extends the same ideas to the GX10 lane, and it
reads best as three questions asked in order before any handoff is timed.
What can this link actually carry? tcp_probe.py re-establishes the raw
ceiling, and the answer is never inherited from last week: the ~9.4 Gbps
reference is pre-rebuild and must be re-proven after topology changes. When we
did re-prove it, the switched fabric turned out to be asymmetric — 9.256 Gbps
in the product direction, 6.161 in reverse — and rather than average that
away we retained the asymmetry as a deviation
[ledger, 2026-08-23 readiness entries]. Will the storage underneath spoil
the number? durable_fsync_probe.py fails (exit 1) any volume whose
reserve-pattern tail would poison TTFT. And where did the time actually go?
handoff_report.py turns a packet’s retained receipts into a per-rep phase
table [scripts/gx10/README.md].
One rule from that lane deserves promotion to general statute, because it is
the cheapest mistake to make and the hardest to detect after the fact:
Wi-Fi never carries a measurement. Mac en0, wired, or the run does not
count; en1 invalidates it [scripts/gx10/README.md:7-11].
Finally, the volume split that Ch 31 derived:
evidence is append-only on muser-receipt://; operational state
(replay ledger, sockets, locks) lives on the internal disk, because the
evidence volume’s directory-fsync tail produced the bimodal ~1 s TTFT stalls
that cost the fast lane its stability gate [AGENTS.md] [ledger W1 evening findings, 2026-08-18].
38.9 Best-of-N, median, mean — choosing the statistic
You have a handful of samples and one number to publish. Which one? The
choice is not cosmetic bookkeeping; it decides which question your number is
answering, and picking the wrong summary is how an honest run turns into a
dishonest headline. The ancestor’s rule ports cleanly [ferrite-book Ch 24]:
best-of-N for ceilings, median (or mean+CV) for ratios. Best-of-N answers
“how fast can this possibly go” — a kernel-occupancy question — but it
selects the luckiest sample, which is exactly what you must not do when
claiming your engine beats another. Muser’s campaign statistic choices, all
citable:
- Ratios of per-engine medians for the streamed A/B verdicts (J3, above).
- Means with CV for the matrices (Table 38.2) — five reps, every rep disclosed.
- Median TTFT with per-rep payload floors for disaggregated cells: the
130,815 EEE-off cell is 137.405 s median, CV 0.576 %, floor ≥ 6.995 Gbps
[claims #6]— the mean-based earlier Phase-4 cell (3.886×) was superseded precisely because stall-contaminated means mislead[ledger "EEE A/B at 130815"]. - Single samples labeled as such: the 3.881 s cold integrated headline is
an “operator-accepted engineering headline, not a five-repetition
stability claim”
[ledger F-series operating amendment]; the deep warm-hit latencies (0.6132 s / 1.0566 s) are “two depth-specific samples, not a distribution”[claims #11].
What is never allowed: picking the best rep of five and calling it the
result, or running until a number you like appears. The wrapper’s
no-automatic-retry flag exists to make that impossible
[scripts/accelerator_safe.py:325].
38.10 What a ratio may and may not claim
A measurement’s scope is part of the measurement. Strip the scope and the
number keeps all its digits while losing everything that made it true — which
is exactly how a careful lab ships a careless sentence. So the campaign
maintains a register of numbers that are real, retained, and nonetheless
unquotable. Each one below is a genuine measurement; each is barred for a
different reason, and the reasons are the interesting part. This list is
binding on this book too [docs/launch-claims.md]:
- 110.59 tok/s (distributed all-accept control): a positive control under
forced acceptance, never serving performance
[claims #14]. - 5.83× (exact-Spark vs exact-Mac mirror): retired baseline; the product
claim is 3.881 s vs ~6.5 s, or the 4.149× EEE-off median
[claims #6]. - 1.3273× / 1.3012× / 107.91 tok/s as current spec speed: pre-window-fix;
the current synthetic restatement is 1.23692 @2,048, and 107.9 survives
only as the kquant spec bar
[claims #15]. - 0.781× as a current deficit: single sample, superseded by the six-depth
matrix
[claims #2]. - 1.64960× decode @131,008: barred by the 2026-08-23 accounting
amendment. This one is the most instructive of the five, because nothing
about the run itself was wrong. At 48 output tokens the decode phase
boundary simply is not drawn in the same place on both sides — Muser’s
denominator excludes its first verified round, llama’s includes its first
eval round — so the ratio is comparing two differently-bounded phases and
flatters us accordingly. The amendment’s ruling: “no value in that series
should be presented as an accounting-neutral cross-engine per-round
speedup. Prefer wall time”
[ledger "AMENDMENT — 131008 decode accounting audit"]. Wall time admits no such ambiguity, which is why the surviving headline is the wall crossing parity: 0.9768 → 0.98400 → 1.02536×[claims #16].
And the positive statement of scope discipline: ratios carry their depth, lane, fixture class, rep count, and hardware in the same breath. “1.05× at 2,048 synthetic kquant five-rep mean on the M3 Ultra against pinned llama” is a claim; “Muser is 5 % faster than llama.cpp” is marketing.
38.11 Tradeoffs
- Why interleaved ratios and not blocked A-then-B runs? Blocking lets
machine state drift between the two blocks; interleaving makes drift
common-mode. The measured consequence of not interleaving appears as
apparatus artifacts the campaign then had to chase — the 2,048 spec-mode
prefill “miss” (0.9968) closed with no code change once
measurement-order carryover was controlled (rerun 1.0017, every rep
≥ 1.0007)
[ledger "Overnight matrix 2026-08-21"]. - Why five reps and not fifty? Five reps with disclosed CV caught every
real effect in this campaign (CVs of 0.1–1.4 % on clean cells); the cost of
a 131,008-token rep is minutes-to-hours, and the marginal statistics of
rep 40 do not pay for the machine time. Where five was impossible
(single-sample diagnostics) the label does the work instead
[claims #11]. - Why is exactness a gate rather than a reported column? Because a
timing number over divergent tokens is not merely noisy — it measures a
different computation (§38.4). Gating costs us cells (the python 8192
natural cell reads
exact: falseand its 0.499 decode is context, not a comparison[ledger "Spec re-measurement at the fixed window"]); what it buys is that every surviving ratio means what it says. - What the method still cannot see. Porting the ancestor’s blind-spot
honesty
[ferrite-book Ch 24]: an exact-token gate on a synthetic fixture cannot see conditioning bugs (§38.7 — proven, expensively); a five-rep mean cannot see effects that only appear across sessions; and a ratio against pinned llama says nothing about any other engine, quant, or model. The register’s “Explicitly post-launch” list exists to keep those silences from being read as answers[docs/launch-claims.md §Explicitly post-launch].
38.12 The ledger as an instrument
One apparatus remains: the campaign ledger
(docs/goal-parity-ledger-2026-08.md), 5,751 append-only lines that every
number in this chapter resolves into. Its entry format is the methodology —
Hypothesis, Change, Exactness gate, Performance delta, Verdict, Evidence
(receipt paths) [ledger A0] — and its ethos is append-only: corrections are
new entries (“CORRECTION”, “RETRACTION”, “AMENDMENT”, “SUPERSEDED” banners),
never edits of history [ledger §"CORRECTION to the 2026-08-21 acceptance root cause"]. When the wizard’s numbers were invalidated by the window fix,
the ledger did not tidy them away; it restated them and kept both.
That ledger is also where the evidence culture lives — release locks, findings registers, claim registers, and the rule that copy never outruns the receipt. That culture is the next chapter.
References
[docs/benchmarks.md]— §Methodology (ratio convention, five-rep/CV, exactness gate), §1 (six-depth matrix), §2 (spec + natural-text edges), §5 (measured-and-rejected summary).[ledger …]—docs/goal-parity-ledger-2026-08.md: “Stage A entry gate” (0.781× observation), J0/J1/J3 (the anchor flip and five-pair verdict), P1.3 (adjacent-lease decode cells), “Phase 2 non-spec context matrix” (Table 38.2), “ROOT CAUSE FOUND AND FIXED” (half-window draft), “Spec re-measurement at the fixed window”, “AMENDMENT — 131008 decode accounting audit”, “EEE A/B at 130815”, W1 evening findings, §2b wizard attempts.[claims #2],[claims #6],[claims #9],[claims #11],[claims #14],[claims #15],[claims #16]—docs/launch-claims.mdrows (OPERATOR REVIEW status quoted where present).[scripts/accelerator_safe.py]— dry-run default (:35-38), lock path (:21), forbidden list (:23), quiet periods (:333-334), no auto-retry (:325), immutable receipts (:200-224), exit 75 (:393).[crates/muser-bench/src/remote.rs:3-10],[:32],[:36]— the remote qualifier’s exactness scope and hard gates.[scripts/qualify_nvfp4_fast.py:307-308]— lease enforcement below the wrapper.[scripts/gx10/README.md]— diagnostic flow, en0/en1 rule, ~9.4 Gbps re-provenance.[docs/decode-dispatch-gap-20260815.md]— instrument label correction; rejected-hybrid postmortem; A/H-series top-out at 0.8463×.[docs/redteam-review-campaign-brief-20260820.md §Verdict]— no fabrication, no result-shopping; discarded runs retained.[receipt …]— undermuser-receipt://:human-test-target-only-run-20260814-v1/,ctx-matrix-plain-b972b55-20260819/,gate-fix-20260821/,spec-prefill-fix-20260822/…command.log,respec2-deep-20260822/…command.log.[ferrite-book Ch 24]— the ancestor’s measurement chapter: noise taxonomy, interleaved ratios, best-of-N vs median, gate blind spots (lineage only; its numbers are A18-class ancestor context).- glossary — terms introduced this chapter: parity ledger, comparator golden, adjacent lease window, teacher-forced, exact-token gate, five-rep mean, synthetic fixture, natural-text cell, anchor flip.
Chapter 39 — The evidence culture
status: polished · path: Muse Glimmer, pinned Muser tree
Prerequisites: Chapter 38 (the measurement protocol this culture wraps) and passing familiarity with Ch 30 and Ch 32, where exactness policies first appeared as contracts rather than hopes.
39.1 The chapter that makes the other chapters cheap to trust
Chapter 38 gave you the instrument: interleaved ratios, exact-token gates, five-rep means, receipts on an append-only volume. But an instrument only tells you what happened. The questions that come immediately after are the ones this chapter answers: when does a measurement earn the right to be called a claim, who is allowed to say it out loud, and what happens when the number and the sentence disagree?
Everything wrapped around the instrument exists to answer exactly that — the locks, registers, contracts, and tags. Muser calls the whole assembly the evidence culture, and its constitution is one sentence from the working agreements:
“Never weaken a fail-closed check to make a run pass. If a gate rejects your evidence, the evidence is wrong until proven otherwise.”
[AGENTS.md, "Hard rules"]
That sentence inverts the ordinary debugging instinct. The ordinary instinct, when a gate rejects your run, is that the gate is too strict. Here the default is the opposite: the gate is presumed right, your evidence is presumed wrong, and the burden sits exactly there until an audit moves it.
Read it a second way, because the first reading undersells it. The rule does not claim that gates are always correct — gates have bugs like everything else. It claims something narrower and more useful: the cost of being wrong is deliberately loaded onto the person holding the failing run, never onto the check. Loosening a threshold is cheap and quiet; producing an audit that moves the burden is expensive and loud. The culture makes the honest move the cheap one by making the dishonest move impossible to do silently. Every mechanism in the rest of this chapter is that sentence rendered in JSON, and we will walk them in the order a claim itself walks them: refusal, lock, register, release path, wording, live tag, evidence volume, audit.
39.2 Fail-closed, defined and mechanized
Start with the primitive everything else is built from, and start with the question it answers: what should a system do at the moment it cannot prove it is in a good state? There are only two families of answer, and the choice between them decides how much a whole program’s evidence is worth.
Fail-closed means: when a check cannot prove the good state, the system stops, and it stops before the unproven state can be mistaken for a proven one. A fail-open system degrades to permissive under uncertainty; a fail-closed system degrades to refusal. Put the difference in terms of what survives a bad day: a fail-open system’s worst outcome is a number that looks fine and is not, which you may never catch; a fail-closed system’s worst outcome is a stopped run, which you catch immediately by definition. You have already met a dozen instances without the word:
- The producer exits with status 75 on any engine-touched error, and a bare
docker restartis not enough — stale startup receipts, RoPE caches, and sockets must be cleared by the restart ritual[AGENTS.md, "The GX10 lane"](Ch 28). - Serving refuses to load
producer_mode: nativetogether with DFlash, with the error stating the remedy, at[crates/muser-server/src/state.rs:1666-1675]: “native NVFP4 fast-lane speculative decode is unqualified; omit –dflash and use plain NVFP4 decode, or route speculative serving to the kquant lane”. The qualifier wrapper carries the same refusal for its variant (target-plus-dflash) at[scripts/qualify_nvfp4_fast.py:333-336]. - Model identity at startup: configured vs verified SHA-256, and “mismatch
refuses”
[crates/muser-server/src/state.rs:1168-1175]. - The accelerator wrapper’s lease refusals, receipt-immutability refusals, and forbidden-command refusals ([Ch 38 §38.8]).
The design pattern in every case: the operator sees the failure — an exit code, an error string naming the remedy, a latch — and the system never silently proceeds on an unverified state. This is why the culture can be a subject of this book rather than an obstacle to it: each refusal is a documented, testable boundary, not a mysterious crash.
39.3 The release lock — one file that outruns everyone
Suppose every measurement in this book came back gold tomorrow: every lane
exact, every gate green, every receipt retained. What stops that from
becoming a release the same afternoon? The answer is deliberately
unimpressive. At the center of the culture sits a single small file,
release/release-lock.json, short enough to read in a minute and blunt
enough that nobody can misread it. As of the pinned tree its actual state is
(Figure 39.1):
{
"schema": "muser.release-lock.v1",
"state": "containment",
"sealing_enabled": false,
"candidate_creation_enabled": false,
"tagging_enabled": true,
"tagging_policy": {
"class": "non-release-marker",
"allowed_tags": ["v0.1.0-beta.1"],
"operator_go_required": true,
"creates_seal": false,
"creates_candidate": false,
"creates_publication": false
},
"publishing_enabled": false,
"blocked_commits": ["11119bd"],
"unlock_requires": "the exact beta marker requires a separate operator go; sealing, candidate creation, and publication require a new lock amendment"
}
Figure 39.1: release/release-lock.json at the pinned tree, quoted in full
— sealing_enabled: false, candidate_creation_enabled: false,
publishing_enabled: false; the only permitted tag is the non-release beta
marker, and only after an explicit operator go.
What “authoritative” means here is literal: while the lock is in
containment, no seals, tags, or release candidates may be created, no matter
how strong the evidence is [AGENTS.md]. Every ledger entry from the
2026-08 campaigns repeats the reminder in its own preamble — “no entry is a
readiness receipt, seal, tag, candidate, or publication” [ledger, preamble]
— and every chapter of this book inherits the constraint. The numbers you
have read are unsealed engineering evidence, every one of them, and the
ledger stamps the fact onto each restatement as seal_eligible: false
[ledger "Synthetic spec matrix deep-cell restatement"].
That is the distinction the campaign calls notarial versus
non-notarial evidence: notarial evidence is a sealed, independently
reproducible release artifact; non-notarial evidence is everything retained
so far. Say it once more in the negative, because the word invites the wrong
reading. Non-notarial does not mean sloppy, preliminary, or unreproducible —
a non-notarial number can be measured exactly and rerun on our bench all
afternoon. What it lacks is the seal that would let a stranger reproduce it
without us in the room. “Measured” carries a similarly narrow meaning here:
measured on Muser, on this hardware, under a retained receipt — never
“measured once, on any hardware, ever”
[docs/launch-claims.md §Ground rules].
The objection writes itself: the lock is a file in the repo, so what stops
anyone from editing it? Two things, and only the second one is durable. The
lock is tracked rather than advisory — commit 11119bd is listed in
blocked_commits, and the feature contract independently declares that same
commit the non-releasable source baseline
[release/feature-contract-v1.json, "source_baseline"], so a quiet edit to one file contradicts the other and
the contradiction shows up in review. More importantly, the unlock has a
prescribed shape. Deleting or relaxing the lock to make a release happen is
not a move anyone has; the only permitted unlock is “a narrowly scoped,
reviewed change setting sealing_enabled true for this exact
readiness-authorized campaign” [docs/private-release.md §3]. An escape
hatch that has to be argued for in the open is not much of an escape hatch,
which is the point.
39.4 Findings and the feature contract — the campaign’s identity
The lock answers when, and its answer is “not yet.” Two further files
answer what: what the release would consist of, and what still stands
between it and existing. Together with the lock they complete the
constitutional set, and the working agreements warn about them in the same
breath — “changes to them change the campaign identity” [AGENTS.md].
Editing one of these files is not maintenance. It is starting a different
campaign, under a different identity, whose earlier evidence no longer
applies.
release/findings-v1.json is the defect register, and its policy line
only has to close two doors: {"waivers_allowed": false, "release_requires_zero_open": true} [release/findings-v1.json]. Those are
the two escapes a defect register is normally asked for — ship with the
defect and note it (the waiver), and ship with it still open and fix it
next cycle (the deferral). Neither exists here, which means the register’s
open-row count is a gate rather than a status report.
Each finding has id, severity, area, title, status, and resolution; the register
spans 44 rows from REL-001 (the blocked commit) through security
(SEC-001–SEC-005: TLS, CORS, CSRF, WebSocket tickets, CA workflow),
enrollment, replay-ledger durability (REP-001: “generation reservation
ordering and durable fsync protocol incomplete”), scheduling (SCH-001:
the global session mutex replaced by the four-slot pool of
Ch 34), to the performance finding PERF-001.
PERF-001 is the instructive one: it was closed not by a wave of the hand but
by enumerating the retained verdict-grade evidence — the six-depth plain
matrix, the fixed-window spec ratios, the funded-fix 131,008 wall parity,
the disaggregated TTFT/link/determinism/soak gates — and its closure text
still bounds the claim: “Scope remains exactly the measured synthetic and
single-producer lanes” [release/findings-v1.json, PERF-001]. Notice the
shape of that closure. It does not say “fixed.” It is a list of retained runs
plus a fence drawn around what those runs cover — a closure in this register
is itself a piece of evidence, which is why closing PERF-001 took a campaign
rather than a commit.
release/feature-contract-v1.json fixes what the release is: the
hardware contract (one M3 Ultra 96 GB decode host, four slots at 131,072
context, GX10 as prefill/storage node and never a decode destination), the
in-scope list (single model, llama-pinned parity, vision, DFlash, GX10,
dashboard, sessions, migration), the out-of-scope list (LoRA, hot-swap,
infill, hosted-provider APIs, “public-CoreML ANE DFlash routing
(experimental post-release)”), and a release policy that reads like the
ledger’s ethics compressed into six booleans (Figure 39.2):
"release_policy": {
"waivers_allowed": false,
"open_findings_allowed": false,
"qualification_skips_allowed": false,
"owner_tags_or_publishes": true,
"seal_requires_release_readiness_receipt": true,
"post_seal_change_invalidates_campaign": true
}
Figure 39.2: [release/feature-contract-v1.json] — no waivers, no open
findings, no skipped lanes, owner-only publication, and any post-seal change
invalidates the campaign.
That last clause is the sharpest: after a seal exists, any change — source,
artifact, documentation — does not get patched in; it restarts the stage
[docs/private-release.md §3]. A sealed campaign is a photograph, not a
living document.
39.5 The release path — freeze, run, readiness, seal
Grant, for a moment, that the lock does open. What happens then is not “cut a
release.” It is a fixed sequence with a stop at every junction, and the
sequence is worth studying even though it has never run to completion,
because its shape is a list of the things the culture is afraid of. The one
permitted path from “lots of evidence” to “a release” runs like this
(Figure 39.3) [docs/private-release.md]:
flowchart TD
A[Freeze one clean identity:<br/>findings, contracts, provenance,<br/>matrix config, binaries] --> B[Run all 15 mandatory<br/>lanes UNSEALED]
B --> C{Zero open findings?<br/>All lanes exact identity?}
C -- no --> D[STOP: fix and re-freeze]
C -- yes --> E[One readiness receipt]
E --> F[Atomic final campaign:<br/>freshly rerun all 15 lanes<br/>into a hidden directory]
F --> G[One fsync-backed rename<br/>exposes the whole bundle]
G --> H[Candidate built only<br/>from that exact bundle]
H --> I[Two independent verifiers<br/>including a clean-room rebuild]
Figure 39.3: The freeze→run→readiness→seal flow [docs/private-release.md].
Failure at any stage exposes nothing; the seal bundle appears atomically or
not at all.
The fifteen mandatory lanes are enumerated by name — correctness,
sampled, greedy, kvpack, session, vision, baseline, dflash,
remote, serving, onboarding, api-parity, continuous-batching,
migration, security — and “a skipped, unstable, malformed, cross-lane,
wrong-identity, or unsealed=false report is a failure. There are no waivers”
[docs/private-release.md §2]. The final campaign reruns fresh — the
sealed matrix is measured after readiness, not assembled from remembered
numbers — into a hidden sibling directory, fsyncing everything, exposing the
bundle with one rename; “failure exposes nothing” [docs/private-release.md §4].
That last phrase is the atomic-seal idea in one image, and it is worth holding onto. Anyone reading the evidence directory sees either no bundle at all or a complete one; there is no window in which they can catch the campaign mid-sentence and mistake a partial run for a result. A crash halfway through leaves nothing but a hidden directory of garbage — which is the correct outcome of a failed release, and the reason the rename comes last.
Even the candidate verifiers are structural: a second clean-room
verifier “must extract the source archive, perform the offline locked build,
re-hash the resulting binary … and run the loopback smoke request on an
externally offline host” [docs/private-release.md §5], under the
accelerator lease.
None of this has run to completion: the lock is still in containment, and every number in this book is pre-seal. The machinery’s purpose is precisely that this fact is checkable from one file rather than folklore.
39.6 The launch-claims register — copy never outruns the receipt
Evidence decides what is true. Words decide what a reader ends up believing you said. Most technical dishonesty lives in the gap between the two — not in the numbers, which are usually fine, but in the sentence built on top of them, one adjective wider than the measurement supports. So the question this section answers is: where does that gap get closed, and by whom?
docs/launch-claims.md is the answer — the interface between measurements
and words. It is a table — seventeen numbered rows at the pin — where every
row carries its current evidence (with receipt paths), its conditionally approved
wording, and, where wording exists but the owner has not approved it, the
banner OPERATOR REVIEW REQUIRED. The register’s ground rules are the
culture’s most quotable sentences; four verbatim:
“The release lock (
release/release-lock.json) is authoritative: while the feature contract is in containment, no row above goes live regardless of how strong its evidence is. Conditional wordings activate only when the contract leaves containment and the row’s stated reproduction gate passes.”[docs/launch-claims.md §Ground rules]
“A number with no row above does not ship. Add a row (with its evidence citation) before using it.”
[docs/launch-claims.md §Ground rules]
“
[precedent-7B-ferrite]numbers (e.g. 34.9/308 t/s, 21.9–30.1x restore, 24.6 GB/s fabric, 1.42x ANE+GPU concurrency) describe the historical Ferrite research lineage on a 7B model, never this Muser program. They may appear in engineering docs as context but never as a Muser product claim.”[docs/launch-claims.md §Ground rules]
“When evidence and wording conflict, evidence wins and the wording row gets corrected — copy is never allowed to outrun the receipt.”
[docs/launch-claims.md §Ground rules]
Three of those rules govern what a number is allowed to become; the fourth governs what happens when a sentence has already got ahead of its number, and its direction is not negotiable — the wording moves, the evidence does not.
The OPERATOR REVIEW tier is the register’s subtlest device. Rows #2, #6,
#11, #12, #15, #16, and #17 carry evidence-backed proposed wording that
the owner has not approved; the register states the rule outright — such
rows “remain unavailable to launch copy even if its reproduction gate later
passes, until the operator approves it” [docs/launch-claims.md, preamble].
Evidence quality and wording approval are orthogonal axes: a perfect
five-rep matrix still cannot speak until a human owner signs the sentence.
The review package for those rows exists
(docs/launch-claims-review-20260824.md), states each row’s exact proposed
wording, receipts, and risk (“removing ‘synthetic,’ ‘mean,’ or the
tested-depth scope would turn a controlled fixture result into an
unsupported workload-general claim” [docs/launch-claims-review-20260824.md, claim #2]), and closes with the discipline that “this review package
remains the pre-decision record” — it prepares decisions, it does not make
them [docs/launch-claims-review-20260824.md, preamble].
The register also carries the negative space: an “Explicitly post-launch”
list of things that do not exist (node discovery, multi-node scheduling,
revocation, full-depth reuse coverage, remote multimodal, send-during-
prefill) with the instruction that “they simply do not exist yet and must
not be implied” [docs/launch-claims.md §Explicitly post-launch]. A claims
register that only lists what you have is half a register; the other half is
listing what a reader might reasonably assume you have.
39.7 Honesty tags — the metrics schema
The same discipline reaches into the live server payload, and it gets there
by way of a small design question with a load-bearing answer: what should a
dashboard show for a quantity nobody has measured? The tempting answer is
zero. Zero renders cleanly, keeps the layout intact, and is a lie shaped
exactly like data. Muser’s answer instead is that every field in the
telemetry snapshot carries an honesty tag, with the legend enforced in both
prose and code [docs/metrics-schema.md]:
measured— “a live counter, duration, or verified loaded-model fact”;target— “a threshold or modeled goal, never an observed result”;mock— “no backing measurement is available; the dashboard renders the value unavailable.”
The register’s copy legend extends the same idea with five tags for claims —
[measured] / [precedent-7B-ferrite] / [target] / [roadmap] /
[mock] [docs/launch-claims.md, preamble]. The two legends look redundant
until you notice they cover different surfaces: three tags for a live
telemetry payload, five for launch copy, which has to make one distinction
telemetry never faces. That extra distinction is [precedent-7B-ferrite],
and the reason it matters to every chapter of this book is that it keeps
ancestor-lab numbers quarantined where no reader can mistake them for Muser
measurements.
The mock rule has one canonical application. The dashboard’s nodes[]
array — M3/GX10 utilization, memory, power, temperature — “is currently
empty and tagged mock: … collection are not wired to this payload. The
separate node-management API and registry do not manufacture telemetry node
cards” [docs/metrics-schema.md §Cluster and nodes]. And the optimization
card list is empty on purpose: “tricks[] is intentionally empty. No
optimization card appears until its independent correctness and performance
qualification passes for the release identity. Historical Ferrite results
are provenance, not live Muser metrics” [docs/metrics-schema.md §DFlash and optimization claims]. That last sentence is the dashboard-mock-tagging rule
in its general form: fields without measurement render unavailable, and
historical Ferrite results are never inserted as live Muser measurements.
An idle counter may legitimately read a measured zero; a modeled threshold
may never dress up as an observation [docs/metrics-schema.md, preamble].
39.8 Evidence volume discipline — where truth is allowed to live
Two questions sound like one question here, and telling them apart took a measured failure. Where is a receipt allowed to live? And what else is allowed to live beside it? The first has a short answer; the second we got wrong first.
Retained evidence lives on muser-receipt:// and is
append-only [AGENTS.md]. The wrapper’s mechanics make the append-only
property physical: receipts are created through exclusive temp-file +
fsync + rename + directory-fsync, and the publish function’s first act is to
refuse if the target exists — “refusing to replace result receipt”
[scripts/accelerator_safe.py:202-203]; the run journal is opened
O_APPEND and fsynced per record [scripts/accelerator_safe.py:190-197].
That property invites an obvious generalization, and we took it. If the evidence volume is the durable place — exclusive create, fsync, rename, refuse-on-exists — why maintain two storage stories? Put the operational state there as well: replay ledgers, sockets, locks, all on the disk built to never lose a write. We expected the volume’s guarantees to carry over intact.
They did not carry over. The 2026-08-18 durability investigation (fully told
in Ch 31) found that the evidence volume’s
directory-fsync tail produced bimodal ~1 s stalls in the commit path
[AGENTS.md]: a ledger commit that should have been imperceptible would
instead, some of the time, freeze the lane. Nothing was wrong with the
durability. What we had never tested was its latency distribution — a
different property of the very same fsync. That is the lesson worth carrying
out of the episode: a volume tuned so that writes can never be lost is not
thereby a volume on which writes are always quick, and the two properties
have to be measured separately because only one of them was ever on trial.
So the decision went the other way. Operational state — replay ledgers,
sockets, locks — belongs on the internal disk, and because a lesson that
lives only in a document decays, the receiver now probes rather than
trusts: check_ledger_volume measures the reserve-pattern tail
latency and refuses a slow volume before any handoff
[crates/muser-cluster/src/receiver.rs:108-150], with
scripts/gx10/durable_fsync_probe.py as the standalone probe (exit 1 past
--max-tail-ms) [scripts/gx10/durable_fsync_probe.py:19-22]. Evidence and
operations are separated not by convention but by measured failure mode.
39.9 The documentation truth pass — auditing claims against receipts
Documents drift; code moves; receipts stay. Which raises the question this section exists to answer: how do you catch a claim that was true on the day it was written and quietly stopped being true while nobody was watching it? Nothing in the machinery so far catches that one, because nothing so far re-reads old sentences. You have to go looking, deliberately. A documentation truth pass is the genre of audit that re-reads every claim-bearing document against implementation and retained evidence.
Muser’s 2026-08-15 pass checked README
and CLI help against cli.rs, security text against the Axum authorization
policy, architecture against the slot pool and GGUF geometry, dashboard
copy against MetricsSnapshot, performance claims against the retained
representative artifact — result columns recorded surface by surface
[docs/documentation-truth-pass-20260815.md §Sources checked]. Its
performance wording ruling is a model of the form: the one-sample 3.6 %
prefill / 22 % decode figures are “engineering-only” and may appear only
“always with the single-run/non-notarial limitation”; the register
“expressly authorizes no product throughput wording” [docs/documentation- truth-pass-20260815.md §Performance evidence wording].
The same genre runs continuously in the ledger as CORRECTION / RETRACTION / AMENDMENT / SUPERSEDED entries, and in the claims register when evidence moves faster than wording. One worked example, dissected.
The evidence box: a stale claim, handled correctly
The claim. On 2026-08-20 the campaign close-out brief reported, for the external reviewer: “Spec decode vs llama spec: 107.91 vs 81.30 tok/s = 1.327×. PASS,” plus a full spec context matrix “decode means 1.305 / 1.278 / 1.282 / 1.250 / 1.232 at 2k–65k”
[docs/campaign-review-brief- 20260820.md §The campaign]. Every number recomputed exactly from receipts; the red-team review verified “there is no fabrication and no result-shopping”[docs/redteam-review-campaign-brief-20260820.md §Verdict].The staleness. On 2026-08-21 the half-window root cause landed ([Ch 38 §38.7]): every one of those figures was measured while the DFlash draft ran on half its trained sliding window. The measurements were real; the lane they measured was broken. Synthetic speed ~5 % optimistic, natural-text acceptance catastrophically pessimistic.
The handling. Nothing was deleted. The brief now opens with a supersession banner: “SUPERSEDED — 2026-08-21. Every speculative-decode figure below was measured while the DFlash draft was conditioned on half its trained sliding window … All spec claims here are pending re-measurement at the fixed sha. The non-spec content (Phase 2 plain matrix, Phase 4 disaggregated payoff …) is unaffected”
[docs/campaign-review-brief-20260820.md, banner]— the identical banner sits on the red-team brief[docs/redteam-review-campaign-brief- 20260820.md, banner]. The ledger restated the numbers in new entries (1.23692 @2,048 and sisters); the claims register rows #15/#16 now cite only the fixed-window packets; and the old 1.3273/1.3012 figures survive in exactly one role — as the superseded numbers this book tells you not to cite[docs/launch-claims.md #15].What the example teaches. A stale claim is not a scandal; leaving one standing is. The culture’s answer has three moves — the evidence is preserved, the supersession is written on the artifact itself where the next reader cannot miss it, and the replacement claim is scoped tighter than the one it replaces.
The register shows the same move in miniature: claim #6’s wording rule
still instructs “Do not cite the historical 5.83× exact-mirror comparison”
[docs/launch-claims.md #6] — a retired number whose tombstone is kept
inside the very row that replaced it.
39.10 Red-teaming the record
The truth pass audits documents against implementation. That leaves the
uncomfortable question one step further out — who audits the people doing the
auditing, before an outsider ever sees the file? The campaign’s answer was to
red-team itself before asking an external reviewer anything: a review
document produced by “seven independent auditors
(ledger forensics, raw-receipt recompute, Phase-4 packet forensics,
engine-code attribution audit, statistics, framing/honesty,
completeness), plus direct spot-verification of every load-bearing claim.
No measurements were run; nothing in the repo or evidence store was
modified” [docs/redteam-review-campaign-brief-20260820.md, header]. Its
verdict is the culture’s certificate: “The measurements are real. Every
headline number in the ledger recomputes exactly from the retained
receipts; the fail-closed machinery demonstrably worked; discarded runs
were kept and are statistically indistinguishable from counted ones —
there is no fabrication and no result-shopping” [docs/redteam-review- campaign-brief-20260820.md §Verdict].
Note what the red-team pass did not conclude: it did not say the
campaign’s decision was right — its ranked findings argue the decision
was mis-posed, the attribution wrong “four different ways,” and the
statistics inverted [docs/redteam-review-campaign-brief-20260820.md §Findings, ranked]. Honest evidence and a defensible decision are
separate claims; the culture’s machinery certifies the first so the
argument can be about the second. That separation is the whole point:
when the receipts are beyond suspicion, disagreeing well becomes
possible.
39.11 What the culture costs and what it buys
Every discipline sends a bill, and a culture whose costs go unnamed is the kind that gets quietly abandoned the first time it is inconvenient. So here is the ledger, both columns.
- Cost: latency on every claim. An OPERATOR REVIEW row cannot ship even
with perfect evidence; a release cannot seal while the lock says
containment; a finding closure needs enumerated receipts, not a
narrative. Measured consequence: the 2026-08-24 wizard PASS (exact
logits, 9.812/8.887/8.690 Gbps) still “remains the operator-review
draft” for public wording
[docs/launch-claims-review-20260824.md, "Draft new row"]. - Cost: negative space must be maintained. The post-launch list, the
mocktags, the “not measured at every depth” caveats — publishing the sensitivity is part of the claim[docs/launch-claims.md §Explicitly post-launch]. - Buys: auditability in one hop. Any number in this book resolves to a
receipt path; any word resolves to a claims row or is barred; any release
question resolves to one lock file. The red-team review could verify
“no fabrication and no result-shopping” because the evidence chain never
breaks
[docs/redteam-review-campaign-brief-20260820.md §Verdict].
The last entry in the buys column is a story rather than a line item, and it
is the episode we would point at if the whole apparatus had to justify itself
once. Fail-closed buys safety under error. A matrix cell came back with
outputs_match: false at the 65536 warm hit, which for a warm-cache result
is about the worst string the harness can print. The tempting response to a
single red square is to argue with the square rather than with the system:
call it noise, rerun it, move on. Fail-closed forbids exactly that move — the
gate is presumed right and the evidence presumed wrong — so the cell was
investigated instead of defended. The investigation found a mundane cause,
and the correction says so plainly: “the 65,536 warm-hit result was an
infrastructure timeout, not a cache-correctness failure”
[ledger "CORRECTION — the 65536 warm-hit result", 2026-08-21]. The cell was
retracted as an infrastructure timeout rather than explained away, and the
valid cell is the one that then passed its gate.
Notice that the discipline paid off in both directions at once. Had the red square been a real correctness bug, arguing with the gate would have shipped it. Because it was not, the retraction sits on the record where any reader can confirm which cell is being counted — the one that passed its gate on its own merits, not the one that happened to be convenient.
There is one more register the culture keeps, and it is the grimmest one: the list of things measured carefully and then rejected. That is the last chapter of this book.
References
[release/release-lock.json]— quoted in full at Figure 39.1; statecontainment, all release machinery disabled, beta-marker-only tagging.[release/feature-contract-v1.json]— hardware contract, scope lists, the six-boolean release policy (Figure 39.2).[release/findings-v1.json]— zero-waiver policy; the 44-row register; PERF-001’s evidence-enumerated closure.[docs/private-release.md]— the freeze→run→readiness→seal flow (Figure 39.3), the 15 mandatory lanes, atomic bundle semantics, clean-room verification.[docs/launch-claims.md]— the register; preamble (OPERATOR REVIEW semantics, five-tag legend); ground rules (four quoted verbatim in §39.6); rows #2, #6, #15, #16; §Explicitly post-launch.[docs/launch-claims-review-20260824.md]— the pre-decision review package with per-row risk statements.[docs/metrics-schema.md]— honesty-tag legend;nodes[]mock;tricks[]intentionally empty; measured-zero vs modeled-target rule.[docs/documentation-truth-pass-20260815.md]— the audit table; single-run performance wording limits.[docs/campaign-review-brief-20260820.md],[docs/redteam-review-campaign-brief-20260820.md]— the SUPERSEDED banner pair, the seven-auditor method (§39.10), and the no-fabrication verdict (the evidence box).[AGENTS.md]— hard rules (fail-closed sentence quoted §39.1), evidence volume rules, operational-state-on-internal-disk.[scripts/accelerator_safe.py:190-197, 202-203]— append-only journal, immutable receipts.[crates/muser-cluster/src/receiver.rs:108-150]— ledger-volume gate.[scripts/gx10/durable_fsync_probe.py:19-22]— the standalone tail probe and its exit contract.[crates/muser-server/src/state.rs:1666-1675]— the native+DFlash fail-closed serving refusal (quoted).[scripts/qualify_nvfp4_fast.py:333-336]— the qualifier’s matching variant refusal.[ledger …]— preamble; “Synthetic spec matrix deep-cell restatement” (seal_eligible); “CORRECTION — the 65536 warm-hit result”; the J0/J3 entries cited for the notarial/non-notarial distinction.- glossary — terms introduced this chapter: fail-closed, release lock, findings register, feature contract, readiness receipt, atomic seal bundle, launch-claims register, OPERATOR REVIEW, honesty tags, documentation truth pass, notarial evidence, append-only evidence volume.
Chapter 40 — What we measured and rejected
status: polished · path: Muse Glimmer, pinned Muser tree
Prerequisites: the whole book. This chapter is the ledger of its dead ends; each one assumes you know the machinery it tried to move. The load- bearing priors are Ch 33, Ch 35, and Ch 32.
40.1 The last chapter is a graveyard
Chapter 39 ended on the evidence culture: locks, registers, receipts — the
machinery that decides what may be said. This final chapter is about the
saying of “no.” A from-scratch engine is a pile of attractive hypotheses,
and the only reason the pile stands at the end is that most of them were
measured and killed. The public benchmarks file says it plainly, in a
section literally titled “What we measured and rejected” [docs/benchmarks.md §5].
So the question here is not “what shipped?” The preceding chapters answered that at length. The question is the harder one behind it: how did we find out where the exactness contract actually binds? Not by reasoning about it. We walked into it — repeatedly, at speed, with the instruments running — and each collision left a mark you can still read. This chapter is those marks.
The genre is inherited: the ancestor book closed with a falsification
ledger — hypotheses, verdicts, tombstones, and a survivor exposed as a
tautology [ferrite-book Ch 25]. Muser’s version is stricter, because
Muser’s campaign had receipts. Each rejection below runs in the same five
beats, and it is worth having them in mind before the first one starts:
- The hypothesis — what we hoped was true.
- The experiment — what was actually measured, under what scope.
- The receipt — where the evidence lives.
- The verdict — what was concluded, in the record’s own words where possible.
- What the rejection preserves — because a well-run rejection is not a loss. It buys a boundary, a guard, or an insight that ships.
Read the fifth beat carefully; it is the point of the chapter. A rejection that leaves nothing behind was a waste of a week. A rejection that leaves a guard, a bound, or a named lesson is the cheapest engineering there is: you paid once, in measurement, and the boundary holds for everyone who comes after.
40.2 Rejection 1 — the linear distributed-verifier lane
Start with the most tempting idea of the whole campaign, because it is the
one that took longest to kill. During Mac decode the GX10 sits mostly idle:
a tensor-core machine watching a laptop work. So make the remote node the
authoritative speculative verifier — Mac DFlash drafts, the GB10 runs the
target’s verification pass on tensor cores, and the pair beats the local
107.9 tok/s kquant spec bar [docs/nvfp4-distributed- speculative-frontier-20260818.md §Decision]. On paper that is free money:
idle silicon, a draft model we already have, a link we already trust.
We screened before we built. The composite M16 screen took 31 warm
prefix-cached GX Dudeman runs with all five f32 DFlash target layers copied
into pinned host memory and measured a 107.152 ms median target wall.
Charging the already measured Mac draft (26.9 ms), the RTT (0.78 ms), and
the capture transport against that median projected 114.93 tok/s at
median — a real opening. The same screen fixed the price of entry, written
down before any end-to-end run: the lane needed ≥ 99.151 % IID per-edge
acceptance to beat 107.9 tok/s [frontier §Decision]. Sit with that
figure a moment, because it is the whole story in advance. It demands that
the drafter be right on essentially every edge — and acceptance is a
property of the content being generated, not of the hardware. That is
exactly why the traces below are split by content type.
So we ran the real thing: four end-to-end traces through the authenticated
lane, one positive control plus three organic content strata (docs, python,
rust). We expected the control at the top, the organic strata somewhere
beneath it, and the interesting question to be whether “beneath” still
cleared the bar. Here is what came back, each row carrying its own
receipt SHA-256 pair [frontier §End-to-end linear-lane verdict]:
| Trace | Acceptance | Measured tok/s | Verifier-only ceiling |
|---|---|---|---|
| Standard (all-accept control) | 100.00 % | 110.59 | 125.61 |
| Documentation | 9.23 % | 15.53 | 20.15 |
| Python | 26.31 % | 11.17 | 40.04 |
| Rust | 38.07 % | 15.41 | 55.96 |
The control row is the all-accept case, and it is beautiful: 110.59 tok/s, past the bar with room to spare. Real text is nowhere near it. Acceptance on organic content did not miss the preregistered demand by a few points; it missed by a factor, and measured throughput went with it.
The record’s verdict is flat — “They reject the linear M16 candidate for
general product serving” [frontier §End-to-end linear-lane verdict] — but
the sentence is not what closed the lane. The last column did. The
verifier-only ceiling divides output tokens by GX verifier wall alone,
“granting zero time to DFlash, feature decode, transport, installation, or
scheduling”; it is the score the lane would post if everything except the
remote verifier were free and instantaneous. Even under those physically
impossible assumptions, all three organic traces stay below 107.9 tok/s.
That is what makes this a tombstone rather than a to-do. The lane does not
lose to overhead we could go and optimize. It loses to arithmetic.
One beautiful number therefore needed a leash, and the claims register put
one on it in wording: “We measured remote speculation across the wire and
rejected it for general serving — the verifier cost eats the gain. The
shipped disaggregated lane is fast remote prefill plus plain parity decode …
Never cite the all-accept control number as a serving result”
[claims #14].
What did the dead lane leave behind? Three things, and the first outlives
the lane by a distance. It is a theorem-shaped insight: lossless
speculative decoding does not require the drafter and target to share a
checkpoint — “It requires one endpoint to execute the authoritative target
transition. The other endpoint may use any approximation”
[frontier §Decision]. That is worth restating in flatter words, because it
contradicts what most people assume when they hear “speculative decoding”:
the two models do not have to match. Exactly one side must own the
authoritative step; the other may be any approximation you like, and the
output is still lossless. Second, the rejection left a bound: any future
distributed scheme must beat 20.15/40.04/55.96 tok/s before it even pays
for transport. Third, it left one live research thread, deliberately
unwired — a “hardware-aware token tree” that would turn otherwise idle GX
batch arithmetic into path coverage, admitted only on a standing condition:
“That experiment must beat the ceilings above with measured emitted tokens
per evaluated tree node” [frontier]. The protocol machinery (authenticated
verifier log, carried-frontier state) was kept rather than deleted, in
crates/muser-cluster/src/verifier*.rs, as unwired research substrate.
40.3 Rejection 2 — native NVFP4 speculative decode (Fallback B)
Remote verification lost to arithmetic, so the obvious next move is to keep speculation at home and put it on the fastest local lane we own. The native NVFP4 lane is that lane; give it speculative decoding too and the two wins should compound. Nothing about that reasoning is careless. It is simply wrong, and the first measurement said so.
We ran the W4A4 batched target execution of the speculative verify pass
directly. It came back at 6.81 tok/s against the 107.9 tok/s kquant
bar — not a regression, a collapse — and the ledger records where the time
went: “Of its 37.619 s decode span, target verification consumes 35.915 s
(95.5 %) … each 16-row target cycle is about 2.24 s versus 128.4 ms in the
L-series kquant reference” [ledger "F-series remediation context"]. Now look at which operation ate the span, because that is the
surprise. The W4A4 batched verify matmul is the one shape in the engine
where FP4 tensor arithmetic ought to shine — many rows at once, against
low-precision weights — and it is the very place the lane collapses
(Ch 33).
A collapse localized to one shape is a fork, not an ending. If batched W4A4
verification is what breaks, verify in some other precision: we built
“Fallback A”, a Mac weight-only E2M1 verifier, and measured it to its own
no-go. The best result was 227.864 ms GPU per 16-row cycle — “still
13.9 % over the preregistered 200 ms GPU admission gate and 1.77x the
128.400 ms kquant reference … its hard throughput ceiling is 70.2 verified
rows/s, below the shipped kquant lane’s 107.9 tok/s” [ledger "Fallback A follow-up — weight-only verifier final no-go"]. Much closer,
and still on the wrong side of a gate that had been written down before the
attempt began. That ordering is what turns a near miss into a verdict
instead of a negotiation. We kept the evidence for
both: [receipt goal-native-spec-local-verify-v7/] and its siblings under
goal-native-spec-* hold the local no-go, and [docs/nvfp4-fast-lane- evidence-20260817.md] records the 6.805 tok/s diagnostic with its scope.
With both remediation lanes measured out, the decision became a choice
between fallbacks rather than a retreat, and the operator recorded it word
for word: “Fallback B is selected. The product ships the native NVFP4 lane
without speculative decoding: 3.881s-class disaggregated prefill, ~35.5
tok/s plain decode, ~64ms warm prefix hits, determinism-pinned seam,
published drift envelope. Speculative decoding remains kquant-lane-only at
107.9 tok/s; the native lane’s fail-closed rejection of speculative configs
stays structural.”
[ledger "F-series shipping qualification amendment — Fallback B authorization", verbatim]
Two things about that quotation, both of which matter more than they look.
The ledger sentence continues into the Fallback A follow-up authorization,
elided here — and that follow-up is the no-go measured above, so the cut
hides nothing. More importantly, “~64ms warm prefix hits” must be read with
its scope attached: 64.631 ms is the shallow, 2,048-token warm-hit figure
[ledger P4 cell; claims #11]. Deep warm hits are a different animal
altogether: 0.6132 s for 65,536 tokens and 1.0566 s for 130,815 tokens, each
of them a single sample
[ledger "Kvpack ladder stage-5 isolated-depth verdict"],
Ch 25. A round figure with a tilde in front of it is
exactly the kind of thing that walks out of a ledger and into a slide, so
the scope travels with it here.
What the rejection preserves is a structural guard rather than a
convention. producer_mode: native together with DFlash fails closed at
server construction [crates/muser-server/src/state.rs:1666-1675], and
again in the qualifier [scripts/qualify_nvfp4_fast.py:333-336]: the
configuration cannot be expressed, let alone measured, in a serving path.
Nobody downstream has to remember why the combination is bad, because the
machine will not let them rediscover it in production.
It preserves an interpretive lesson too, the one from
Ch 32: quantization’s cost is never
global. The same NVFP4 weights are parity-within-noise at plain decode
(35.491 vs 35.440 tok/s [ledger P1.3]) and catastrophic in the batched
verify shape. Put the claim the other way around, since this is the idea
readers most often carry away broken: quantization does not make a model
uniformly worse, it makes particular shapes worse. The gate exists to
localize which shape you are standing in.
40.4 Rejection 3 — the ANE/CoreML route
This is the rejection that felt least like a failure, because nothing about it ever broke. Apple ships a second accelerator beside the GPU: the Neural Engine (ANE), a fixed-function unit programmed through Core ML. The draft model is the part of speculation you pay for on every token, accepted or not, so if the ANE could run the DFlash draft more cheaply than Metal, the whole speculative lane gets faster for nothing. That was the question, and it is a question only a stopwatch can answer.
The answer arrived slowly, in a lineage of focused, target-exact POCs. The
split v4–v6 generation reached only 0.644×/0.704×/0.711× of Metal — the best
ANE cell at 238.637 ms against Metal’s 153.681 ms on the comparable cell
[docs/release-provenance.md, ANE POC history]. Read as a series rather than a verdict, those three ratios are
encouraging: each revision closed part of the gap, and extrapolation is
seductive. So we ran one more, and we ran it the way you run an experiment
you intend to believe. The v9 fused-attention POC was warm, three
repetitions, 256 tokens, with an identical target-token digest across reps
and ANE/Metal draft acceptance of 238/259 (91.89 %). It produced the
steadiest numbers of the whole lineage — ANE raw times 5.118/5.113/5.073 s
at CV 0.40 %, against Metal’s 4.185/4.204/4.260 s at CV 0.75 % — and a
result needing no interpretation at all: “The resulting ANE/Metal throughput
ratio was 0.8266x” [docs/release-provenance.md §ANE v9 fused-attention POC]. Exact, stable, reproducible, and slower.
We kept the receipt for the stable result —
[receipt ane-v9-fused-sg4-256x3-20260814/] — and the earlier POC receipts
are retained too, as dated research evidence.
Because the route worked, the disposition had to be a scope decision rather
than a bug report: “No v0.1 launch claim. ANE is experimental/post-release,
excluded from qualification and candidate contents, and never selected by
auto” [claims #5]. The release provenance carries the standing override
beside it — public-CoreML ANE “is not a mandatory lane, release identity
input, seal member, or candidate artifact; v0.1 auto routing is
permanently Metal” [docs/release-provenance.md, v0.1 scope override].
Two things survive the decision. One is a boundary that protects the claim
surface: telemetry labels ANE counters experimental, and the metrics schema
forbids any ANE speed card outright [docs/metrics-schema.md §DFlash and optimization claims], so nobody can accidentally publish a chart of a lane
that lost. The other is the example itself, which is why this section is in
the book at all. This is a rejection on a measured ratio, not on taste: the
route was exact and functional, it was merely 0.827× slower, and that was
the entire argument. Holding that line matters most when the ancestor
context is loud — the Ferrite-lab 1.42× ANE+GPU concurrency figure that
circulates in that lineage is quarantined as [precedent-7B-ferrite], an
A18-class ancestor measurement and never a Muser result
[docs/launch-claims.md §Ground rules].
40.5 Rejection 4 — the 104-group norm-boundary fusion
This one comes straight out of Ch 35, which left a number sitting on the table. The production decode graph carries 104 separated norm-boundary closure groups where the legacy graph fuses them, and the decode deficit at the time stood at 22 %. Fuse the boundaries, remove the dispatch overhead, close a good part of the deficit. It is the most attractive class of optimization there is, because it appears to change no math at all — only when the math is scheduled.
That appearance is the trap, and it took three implementations to see the bottom of it. All three were measured the same way: against the pinned 2,048-token fixture, with full-logit hashing.
The first was the existing dual-norm fusion, already written and waiting. Its logit SHA changed, so it was rejected outright; the wall sample was never worth reading.
The second was more careful. A “pinned-reduction” dual norm reproduced llama’s 32-SIMD-group reduction twice instead of reorganizing it, and it worked: bit-exact, 760→655 groups, 40.330→39.274 ms GPU. For a while this looked like the answer. It was not, and the reason is project history rather than arithmetic — the exactness it demonstrated was self-consistency with our own earlier bytes, established before J0 made llama’s bytes the gate. Being exact against the wrong reference is not being exact.
The third was a hybrid retained-activation schedule, selecting the fast
fused boundaries only where they looked safe. We expected small, bounded
drift in exchange for real time. What came back is the most instructive
postmortem in the whole record: full-logit maximum absolute error
4.6300888e-4; normalized logprob maximum absolute error
3.197146176834309e-4, “above the 1e-4 contract”; 201,970 of 202,048
logits differed. And then the line that explains all of them: “the
first KV difference was layer 1, value plane element 524,115, with f16 bits
39,892 versus 39,893” [docs/ decode-dispatch-gap-20260815.md §Rejected hybrid postmortem], receipts kept
at [receipt pinned-token-parity-20260814-v{3,4}/].
Follow that chain slowly, because it is the lesson of the section. A single f16 value, one layer deep, differed by one representable step — and 201,970 logits moved with it. The hybrid did not introduce error; it introduced a different rounding order, and a transformer is a long enough amplifier to carry one such step out to nearly every logit in the vocabulary by the time the stack ends.
The verdict was written to be unarguable: “The 104-group fusion is not
eligible regardless of its wall sample because its logits changed”
[docs/decode-dispatch-gap-20260815.md §Landed and rejected reductions]. The disposition line in the
reconciliation table is two words long: “Existing fusion is not exact;
reject.” What survived the whole exercise was one exact removal — a single
last-row copy of 6,656 elements — worth −0.136 ms GPU (−0.34 %) and no wall
claim at all [docs/decode-dispatch-gap- 20260815.md].
So the rejection preserves the bit-exactness contract itself, and prices it.
Any future fusion must “reproduce the standalone reduction and store order
bit for bit. The current 104-group fusion is a negative fixture, not a
candidate” [docs/decode-dispatch-gap-20260815.md §Ranked remaining exact work]. Notice which way the tension resolved when a
fast, nearly-correct schedule met a tolerance it just missed: the hybrid was
removed, and the tolerance was left where it was
[docs/decode-dispatch-gap-20260815.md §Rejected hybrid postmortem]. That is the exactness contract of
Ch 32 enforcing itself at home — on
our own machine, against our own optimization — and not only across the wire
where someone else’s kernels are the suspect.
40.6 Rejection 5 — full send-during-prefill streaming
The handoff moves gigabytes only once CUDA has finished. So why must the segments wait? If they could leave during prefill, TTFT would fall by whatever overlap we managed to buy. Everyone asks this question eventually. We asked it early, answered “no”, and then had to answer it a second time — and the second answer is why this entry is here.
The first answer was structural rather than lazy. The original wire schedule
was tile-major with strict ordering, and strict tile order means no segment
can leave before the last NoPE layer has computed. That is a property of the
schedule, not a knob, so streaming was deferred and the register put the
deferral in the open where it could be argued with: “connector streaming
during prefill was analyzed and deferred (the wire schedule’s strict tile
order means no segment can leave before the last NoPE layer computes)”
[docs/launch- claims.md §Explicitly post-launch].
The second time round, the thing that got questioned was the schedule
itself. The 2026-08-19 rework switched both sides to a layer-major order,
and the surgery proved far smaller than anyone had feared: group by layer
instead of by tile and “each SWA group … sendable as soon as its 13 layers
exist mid-prefill and only the NoPE tiles trail,” carried by a dedicated
sender thread. The receipt shows it running — all 16 segments enqueued
during prefill, the first on the wire at +470 ms of a ~1.18 s prefill, TTFT
1.596 → 1.500 s median at CV 0.14 % [docs/disaggregated-prefill-sealing- plan-20260818.md §W2]. That run is retained as
[receipt stream4-p4-20260819/], with the register’s post-launch bullet and
the sealing plan’s W2 entry carrying the analysis on either side of it.
The verdict is therefore split, and labelled that way on purpose. SWA-group
streaming during prefill shipped. Full streaming stays deferred, for the
original reason now narrowed to the place it actually lives: the NoPE bulk
cannot start before the last NoPE layer computes. At depth that is not a
footnote. 95.7 % of a deep payload waits on layer 51, which is precisely why
a 130,815-token handoff idles the link for 41–47 s and then bursts 1.74 GB
onto it [docs/kvpack-merge-handoff- 20260820.md §6 "Pacing reality"] — the EEE story of
Ch 31. The register’s conservative wording
stands unchanged: neither full streaming nor its benefits may be implied
[docs/launch-claims.md §Explicitly post-launch].
The half that did not ship left the more durable artifact. The dependency is
now written down as a schedule invariant with a verifier standing behind
it — “The verifier enforces the new invariant (first segment before D2H
completion) with positive and negative tests”
[docs/disaggregated-prefill-sealing-plan-20260818.md §W2] — so the
property cannot quietly regress the next time somebody reorders the wire.
And the analysis survives as the design note for whoever wants the NoPE
trailing edge moved: the blocker is compute order, not transport
enthusiasm.
40.7 Rejection 6 — remote multimodal handoff
This hypothesis is the one nobody bothered to write down, which is precisely what a full matrix is for: if text prefill can be done remotely, why not vision? Ship the images to the GX10, run vision prefill there like any other prefill, and the disaggregated lane grows an arm.
The 2026-08-23 release-readiness attempt set out to bind the mandatory
remote packet and walked into a hard boundary — two of them, stacked. The
native arming wrapper scripts/qualify_nvfp4_fast.py admits only text and
target-plus-dflash, so --variant multimodal is rejected with exit 2
before the wrapper ever touches the node; the choice set is fixed right at
the argument parser [scripts/qualify_nvfp4_fast.py:47]. Behind that, the
live image’s source-matched request parser “accepts exactly a token-only
top-level request” [ledger "Release preparation — native multimodal matrix blocker", 2026-08-23]. The
capability was not slow, and it was not lossy. It was not expressible.
Then comes the part of this story worth stealing. We tried a direct
capability probe to route around the wrapper, and it timed out waiting for a
producer the direct qualifier cannot arm. That run could easily have been
filed as evidence about remote multimodal handoff — it looks like the
thing under test failing. It was recorded instead as
INVALID_WRONG_REASON: “not a multimodal correctness or performance
verdict, and it was not retried” [ledger, same entry]. With no valid
measurement to be had, the operator took the sealing plan’s explicit-gating
disposition: commit df2a0f9 appends the boundary and adds claim #17
[ledger "Release readiness attempt 2", 2026-08-23]. The evidence for the stop is retained whole, at
[receipt release-readiness-campaign-20260823/attempt-1/phase4/ native-multimodal-wrapper-commandability-attempt-1/…command.log] and
PHASE4_MULTIMODAL_STOP_VERDICT.json.
Claim #17 carries OPERATOR REVIEW REQUIRED wording, and it draws the line
exactly where the measurement stopped: “Multimodal requests are served with
local prefill; remote multimodal handoff is unqualified. … Do not imply
remote image transfer, remote vision prefill, or a remotely qualified
multimodal path” [claims #17].
So what does this rejection cost? Nothing a user can see. The local vision
lane remains mandatory — vision is one of the fifteen seal lanes and it
stays in scope [docs/private-release.md §2] — so an unqualified remote
path is held out of the contract without a capability leaving the product.
What it preserves beyond that is a piece of measurement hygiene worth
carrying into your own work: the INVALID_WRONG_REASON label. A run that
fails for reasons outside its own hypothesis is not evidence about that
hypothesis. It is retained, named for what it actually was, and not retried
into a verdict it never earned.
40.8 The smaller tombstones
The record holds more measured rejections than fit into sections of their own. What follows is deliberately compressed — inventory rather than story, because the stories differ only in the apparatus. Every one of them ran, and every one kept its receipt:
- Multi-stream TCP slicing — evaluated and rejected by the W0
measurement: “a single stream already saturates” the 9.40 Gbps link
[docs/disaggregated-prefill-sealing-plan-20260818.md §W1]. - The universal 15 % NVFP4 quality gate — retired after the E1
quant-vs-quant yardstick showed disagreement bands are content- and
depth-local (calibrated gates 8.796–15.299 %); the published form became
a content-sensitive envelope with the docs@65,536 exceedance (15.134 % vs
13.339 %) stated, not footnoted
[claims #10]. - The Inferact NVFP4 checkpoint — rejected after a full E2 sweep:
worse in all 30 cells, confident flips 52/95/176 vs RedHat’s 23/33/56 at
docs 8k/16k/32k, McNemar p ≤ 5.7e-05
[ledger "Checkpoint bake-off"], receipt[receipt nvfp4-bakeoff-20260817/checkpoint-decision.json]. Prefill keeps RedHatAI; decode keeps Dudeman. - Six Stage-B spec levers (b16 tile, mul_mm, multicol, two custom
split-K matvecs, SGM K-split) — probed exact and rejected; the L-series
n32 tile won instead
[ledger K0, K2]. - The historical 5.83× exact-mirror comparison — retired as a product
baseline; superseded by the accepted 3.881 s / ~6.5 s comparison and the
4.149× EEE-off median
[claims #6].
A pattern should be visible by now, and it is worth saying out loud, because it is this chapter’s actual argument. Every tombstone here is one of exactly three things: an exactness violation (40.5), a measured performance miss against a preregistered bar (40.2, 40.3, 40.4), or an unqualified capability kept out of contract by an explicit boundary (40.6, 40.7). No rejection in this record is “seemed like a bad idea.” All of them ran, and each had a number before it had a verdict.
40.9 The recurring question, answered
This book opened with one sentence masquerading as three questions, and promised to collect answers. It is time to pay that debt — one page, across all eight parts, every number already cited in its chapter.
What does one token cost? Sixteen point seven six gigabytes of reads
and about 53 GFLOP of arithmetic that hides completely underneath them —
28.22 ms at the kquant lane’s measured 35.440 tok/s, an effective read
rate of ~594 GB/s derived from that measurement
(Ch 1, [ledger P1.3]). The
cost is memory, almost purely: the GPU finishes the math long before the
bytes finish arriving, which is why the whole engine is organized around
bytes-per-token, and why quantization buys capacity (4.81 bits/param)
rather than decode speed — NVFP4 lands at parity-within-noise, 35.491 vs
35.440 tok/s [ledger P1.3].
Where does the time go? Into the weight stream, and into structure you
can name. The +196-closure dispatch gap reconciled exactly into 104
norm-boundary groups, 39 SWA staging groups, 52 KV-publication splits, and
one copy [Ch 35] — and the fusions that would remove the 104 groups
change bits (§40.5), so the time went into the exactness contract, paid
knowingly. On the wire, the time goes into a bill of charges that turned
out to be self-inflicted until proven otherwise: our own pacing pin (3.9 of
9.4 Gbps), our own ledger’s fsync tail, EEE’s blackouts on our own burst
schedule (Ch 31). At depth, TTFT is
compute-bound locally and wire-bound remotely: 570.122 s local vs 137.405 s
remote at the 130,815 class, 4.149× [claims #6].
What may be moved without breaking the exactness contract? Three
moves, each with its measured proof and its fence. Into a draft model:
DFlash speculation, exact by CPU verification against the full target
distribution, currently 1.23692× synthetic at 2,048 and — the honest edge
— 0.931 on high-acceptance shallow natural text; the record states the win
and the loss in the same breath [claims #15]
[docs/benchmarks.md §2]. Into a cache: kvpack warm reuse returning the
first token in 0.6132 s at 65,536 where cold is 68.6 s, bit-identical
[claims #11], and delta handoff moving 54.2851 % of full bytes with
byte-equal output [claims #12]. Across the wire: the entire
disaggregated lane, anchored by the integer-dot verification producer and
the kquant reference lock (Ch 32).
And what may not be moved: the anchor bytes themselves (J0), the
reduction DAGs behind them (J1), and any fusion that disturbs so much as
one f16 ULP in a layer-1 value (§40.5). The rejected lane of §40.2 marks
the far fence: even a provably lossless move fails if its verifier cost
eats the gain.
That is the book’s answer, and notice its shape: it is not a number but a contract with receipts — what moved, what it cost, what it could not touch, and where each fact is written down.
40.10 Where a reader goes from here
The book ends; the program does not. What follows are the open threads, each labeled by its evidence status in the register’s own vocabulary. Read the labels as carefully as the threads themselves: enrolled requirement, research, unwired, roadmap and open finding grant very different permissions to whoever picks the work up, and keeping them distinct is what stops an open question from quietly becoming a claim.
- Natural-workload gates as standing apparatus — enrolled requirement,
not yet a full matrix. The synthetic fixture certified a broken draft
lane for an entire campaign; natural-text cells are now a standing part
of the spec matrix, but broad natural-workload performance remains
explicitly unclaimed
[claims #2][ledger "ROOT CAUSE FOUND AND FIXED", consequence 2]. - The unwired V2 verifier protocol and the token-tree experiment —
research, unwired. The carried-frontier protocol exists
(
crates/muser-cluster/src/verifier_v2.rs); the hardware-aware token tree must beat the measured 20.15/40.04/55.96 ceilings with “measured emitted tokens per evaluated tree node”[frontier]. - Scale-out beyond 1× Mac + 1× GX10 — roadmap, must not be implied.
“1x Mac + 1x GX10 today. Do not imply a multi-GX10 cluster is running;
scale-out is roadmap”
[claims #8]; no multi-producer scheduler, no node discovery, no revocation flow yet[docs/launch-claims.md §Explicitly post-launch]. - Raw-dispatch attribution (the xctrace class of question) — planned,
not run at the pin. “A future
.gputracecapture should count raw kernel dispatches and encoder CPU cost”[docs/decode-dispatch-gap- 20260815.md §Ranked remaining exact work]; the ancestor’s equivalent experiment was attempted and blocked for named, non-privilege reasons[ferrite-book Ch 25]— the question survives both trees. - Sustained deep-load stability — open finding. One producer died on
the ninth consecutive deep handoff during the EEE-off sequence; the
eight-handoff soak passed, but “sustained-deep-load stability remains
open”
[claims #13]. - Deep reuse coverage — partial by record. Warm reuse measured at
65,536/130,815, delta at 65,536; “reuse and delta are not measured at
every depth, and every deep multimodal cell remains unrun”
[docs/ launch-claims.md §Explicitly post-launch].
And the meta-lesson to carry into whatever you build next: the answer to “what does one token cost?” rots the moment it leaves its receipt. Keep the ledger append-only, keep the gate ahead of the benchmark, keep the lock ahead of the claim — and when a beautiful number arrives from an all-accepting control, a half-windowed draft, or an asymmetric clock, write the tombstone yourself before someone else has to.
That is how you write an inference engine. You measure it until it tells you the truth, you reject everything that fails to, and you leave the receipts where the next person can find them.
References
[frontier]—docs/nvfp4-distributed-speculative-frontier-20260818.md: §Decision (checkpoint-unification insight, the 114.93 tok/s screen and the 99.151 % bar), §End-to-end linear-lane verdict (the four-trace table, verifier-only ceilings, receipt hashes).[claims #4],[claims #5],[claims #6],[claims #8],[claims #10],[claims #11],[claims #12],[claims #13],[claims #14],[claims #17]—docs/launch-claims.mdrows (OPERATOR REVIEW status stated where present).[ledger …]— F-series remediation context and Fallback A no-go; Fallback B verbatim authorization; “ROOT CAUSE FOUND AND FIXED”; “Checkpoint bake-off”; “Release preparation — native multimodal matrix blocker”; “Release readiness attempt 2”.[docs/decode-dispatch-gap-20260815.md]— §Landed and rejected reductions; §Rejected hybrid postmortem; §Ranked remaining exact work.[docs/release-provenance.md]— v0.1 scope override; §ANE v9 fused-attention POC (0.8266×, three-rep packet); earlier ANE POC lineage.[docs/disaggregated-prefill-sealing-plan-20260818.md]— §W0 (9.40 Gbps, multi-stream rejection), §W1, §W2 (layer-major streaming receipt), the 2026-08-23 multimodal amendment.[docs/kvpack-merge-handoff-20260820.md §6]— the NoPE-trailing-edge pacing analysis behind §40.6.[docs/private-release.md]— the fifteen mandatory lanes (thevisionlane stays).[crates/muser-server/src/state.rs:1666-1675],[scripts/qualify_nvfp4_fast.py:47, 333-336]— the fail-closed guards of §40.3 and §40.7.[crates/muser-cluster/src/verifier_v2.rs]— the unwired carried-frontier protocol of §40.10.[receipt …]— undermuser-receipt://:goal-native-spec-*/,ane-v9-fused-sg4-256x3-20260814/,pinned-token-parity-20260814-v{3,4}/,stream4-p4-20260819/,nvfp4-bakeoff-20260817/checkpoint-decision.json,release-readiness-campaign-20260823/attempt-1/phase4/….[ferrite-book Ch 25]— the ancestor’s falsification-ledger closer this chapter ports; its numbers are A18-class ancestor context, never Muser results.- glossary — terms introduced this chapter: falsified-hypothesis ledger, all-accept control, verifier-only ceiling, Fallback B, INVALID_WRONG_REASON, preregistered bar.
Appendix A — Glossary
Every term is defined in place at first use in the chapters; this is the index. (introduced in Ch N) marks the chapter that defines it; also lists later chapters that rely on it. Anchors are lowercase-hyphenated term names, e.g.
[SIMD group](glossary.md#simd-group).
104 norm-boundary groups
the three separated-norm families (+51 entry +52 post-attention +1 post-FFN) of the +196-closure serving gap; their fusion is rejected for breaching the 1e-4 logprob contract (Ch 19)
131,072-position limit
MUSE_MAX_CONTEXT, the model’s per-slot context ceiling; NoPE planes are allocated at exactly this capacity (Ch 22)
4-byte read-back
the GPU-resident greedy chain’s alternative: read only the argmax result slot per token (dflash_argmax_results, u32::MAX = fail-closed nonfinite flag) (Ch 21)
95.5/4.5 payload split
at depth, the 13 NoPE planes carry ≈95.5 % of a handoff’s bytes and the 39 SWA rings ≈4.5 %; why the wire economy is a NoPE economy (Ch 22)
accelerator lease
the flock on /tmp/ferrite.gpu.lock held by a GPU process for its lifetime; the same file discipline on both the Mac and the GX10 (Ch 28; also Ch 38)
AcceleratorPermit
the RAII guard returned by scheduler acquire; dropping it releases the accelerator and wakes the next waiter (Ch 34)
AcceleratorScheduler
the one Mutex+Condvar owner of the shared Metal queue; decode selected first, cyclic fairness (Ch 2)
acceptance rate
accepted proposals ÷ drafted proposals; gated per-request over a recent 8-round window with a 0.25 floor (Ch 8)
access pattern
what memory a kernel reads, in what order, how much; “where the bandwidth story lives” (Ch 6)
accounting-invariant metric
a timing column defined identically across engines (wall time at 131,008 is one; short-output decode is not) (Ch 38)
Activations pool
the per-sequence GPU buffers of Table 10.2, allocated once, reused every token, zero hot-path allocation (decode.rs:897) (Ch 10)
adjacent lease window
measuring two lanes back-to-back under one held accelerator lease so drift between them is bounded by seconds (Ch 38)
all-accept control
a diagnostic run under forced 100% acceptance (the distributed lane’s 110.59 tok/s standard trace) that proves the pipeline’s plumbing while proving nothing about throughput; never citable as serving performance. (Ch 33; also Ch 40)
ALPN
Application-Layer Protocol Negotiation: the protocol name agreed inside the TLS handshake; the data plane’s is exactly muser-kvpack-v2 (Ch 30)
anchor flip
the J0 operator revision that retired Muser’s self-referential production hash and made llama’s own bytes the exactness gate (Ch 38)
append
the fail-closed ring reservation: position must equal origin_logical+len, else CacheDiscontinuity (Ch 15)
append-only evidence volume
muser-receipt://, where receipts are immutable (created by exclusive temp+fsync+rename, never replaced) and records journal O_APPEND (Ch 39)
append_batch
the chunked ring reservation; overflow advances both origins by the evicted count and returns which source rows remain live (Ch 23)
argmax
the reduction returning the index of the largest element; the winning index is itself the token id, so greedy decoding needs no softmax (Ch 21)
arithmetic ABI
the pinned agreement in op order, reduction tree, and materialization dtype across vendors’ math (e.g. CUDA’s serial 128-dim attention reduction vs Metal’s 32-lane tree) (Ch 29)
arithmetic intensity
a workload’s FLOPs per byte read; decode’s is ~3.2, fixed by the model format (Ch 1)
asymmetric auth model
keyless loopback inference, bearer-or-dashboard management, cookie+CSRF+Origin mutations, authenticated-everything on LAN — security posture chosen by where you bind (Ch 37)
asymmetric quantization
two numbers per block (scale + min); handles blocks not centered on zero at the cost of the extra stored min (Ch 5)
atomic seal bundle
the final-campaign output: all lanes freshly rerun into a hidden directory, fsynced, then exposed with one rename; failure exposes nothing (Ch 39)
attention
the only cross-token mixer: each token’s Query scores against past Keys (softmax-weighted) and pulls a weighted sum of Values (Ch 9; also Ch 16)
attention route ladder
per-layer kernel selection: llama-pinned vec / splitk / ferrite-interleaved, gated by llama_vec_rows/llama_swa predicates (decode.rs:5646-5657) (Ch 10)
attention scale
1/√head_dim = 1/√128 ≈ 0.0884 applied to Q·K scores, independent of the folded qk_scale_factor (config.rs:279-281) (Ch 9)
attention-output gate
Muse Glimmer’s learned per-channel multiplier: attn_out[i] ← attn_out[i] · σ(gate_proj[i]), applied in attention space before o_proj (Ch 17)
attn_dim / kv_dim
query space n_heads×head_dim = 4,096 vs KV space n_kv_heads×head_dim = 256 (config.rs:268-273) (Ch 9)
bandwidth
how fast memory can hand bytes to the GPU, measured GB/s; the budget that governs decode (Ch 1)
batch-width boundary
the token-count at which the projection kernel changes; a floating-point reduction-order boundary kept llama-exact (Ch 13)
begin/finish_dflash_verify_suffix
the Mirror-SD split: run the verify block to a capture layer synchronously, submit the suffix layers + LM head without waiting (decode.rs:3298, 3635) (Ch 10)
bit-exactness-over-throughput
the disposition that keeps the 104 separated norm boundaries because their available fusion changes logprobs beyond contract (Ch 35)
bitrate
bits per weight of a stored format: payload bits plus header bits ÷ block size (Ch 5)
blit
a GPU block-copy executed via a blit command encoder rather than a compute kernel (Ch 3)
block
a small contiguous group of weights sharing one scale (and optionally a min); the unit of local quantization (Ch 5)
bound inventory
every queue in the server has a number (64 MiB bodies, 30 s timeouts, 256 connections, 64 admissions, 64-deep streams, 64 sessions); overflow is a status code, never a hang (Ch 37)
boundary-token hold-back
the receiver decodes the final prompt token locally, so KV ships for prompt − 1 tokens (one NoPE token = 13,312 B less payload) (Ch 26)
bounded-logit policy
a qualification contract mode (bounded-drift) in which greedy tokens must be bit-exact while full-logit drift must fit sealed bounds (native lane: max < 11.0, mean < 1.25), declared in the frozen identity and checked per sample and per summary. (Ch 32)
BPE tokenizer (merge-order aware)
GGUF byte-pair encoding respecting merge priority; vocab 202,048, identity bound by a metadata SHA-256 (Ch 9)
cache hit (economics)
an authenticated restore and-committed install; session continuations and fresh disaggregated prefills never count (Ch 25)
cached frequency table
the powf-built [head_dim/2] θ table uploaded once; kernels do one multiply, no pow (Ch 14)
CacheDiscontinuity
the fail-closed error when a KV append position is not exactly origin_logical + len (decode.rs:265-284) (Ch 10)
calibrated gate
a tolerance derived from a measured second quantization (Q6-vs-kquant disagreement) rather than chosen; used for the deep-ladder content controls so no threshold can be accused of convenience. (Ch 32)
canonical JSON
recursively key-sorted, compact JSON encoding shared by the Rust and Python sides so both compute identical bytes to hash and MAC (Ch 30)
carried frontier
the target-selected token held un-evaluated between speculative rounds; its explicit witness geometry prevents publishing state whose KV rows do not exist yet. (Ch 33)
causal mask
the rule forbidding a query from seeing keys at later positions (Ch 9)
chat shift unit
the atomic replay unit of context shift: a user message plus everything attached to its turn (assistant calls, tool results, images) (Ch 23)
chat template
the GGUF-embedded Jinja-style prompt renderer; pinned at exactly 7,167 bytes with its own SHA-256 (Ch 9)
codebook
the small table of allowed values a quantized index selects from; 4 bits select 16 entries (Ch 5)
command buffer
the “tape”: a recorded sequence of GPU instructions handed to the GPU in one shot at commit (Ch 2)
command-buffer amortization
the whole 52-layer token recorded onto one concurrent encoder, committed once per token (decode.rs:5448-5458) (Ch 10)
companion tensors
the bound .nvfp4_scale, .nvfp4_scale2 (and optional .nvfp4_input_scale_inv) tensors every NVFP4 weight matrix must carry, validated fail-closed at load (Ch 7)
comparator golden
the pinned llama.cpp byte artifact that gates exactness (since J0: the complete 808,192-byte f32 logit row, SHA-256 fc37487b…) (Ch 38)
compute command encoder
the recorder that writes dispatches onto a command buffer (Ch 2)
concurrent dispatch
an encoder (MTLDispatchType::Concurrent) that may overlap dispatches with no dependency between them (Ch 2)
concurrent dispatch set
multiple independent kernels encoded in one closure so the GPU may overlap them (Q/K/V/gate) (Ch 13)
consumer (receiver)
the process that accepts authenticated KV over Handoff V2 and decodes; today the Mac Metal engine (Ch 27)
content-local sensitivity
a quality exceedance confined to one content class at one depth (docs@65,536: 15.134% vs 13.339% top-token gate) that did not replicate cross-document; published, not capped. (Ch 32)
content-sensitive envelope
the published form of the NVFP4 quality result: gates are content- and depth-local, and the docs@65,536 exceedance is part of the claim (Ch 40)
context geometry
the enrolled sink+window shape (DFlashContextGeometry) bound to the draft’s digest; receivers must never infer it locally (Ch 8)
ContextPolicy (Shift/Error)
the server-owned context-overflow policy; the engine has no shift op (Ch 23)
counted-warmup convention
the disagg cells’ protocol of one uncounted warmup handoff before five counted reps; part of the claim, not a footnote (Ch 27)
CPU-side exact acceptance
speculative tokens accepted/rejected on the CPU against the target’s full distributions (verify_full_speculative_mt_ordered, sampling.rs:1033) (Ch 10)
cross-vendor arithmetic ABI
the pinned set of reduction orders and materialization dtypes that lets CUDA-produced logits match Metal bit-for-bit; what the wizard’s one-ULP chase (attempts 10–31) had to version. (Ch 32)
cross-vendor library
the strict-f32 recompile of muse_reference + nvfp4 whose arithmetic matches CUDA’s scalar boundaries for remote-parity routes (Ch 4)
CSRF
cross-site request forgery: a hostile page makes the victim’s browser send a cookie-authenticated request; countered by exact-Origin matching plus a constant-time token on mutations (Ch 37)
current-token bypass
reading the current token’s K/V as f32 from the activation buffers instead of the f16 plane; ferrite rung only (Ch 16)
cyclic slot rotation
fairness by ascending sequence-ID order resuming after the last-served ID, implemented identically at both scheduler levels (Ch 34)
D2H gather
the producer-side device-to-host copy of each computed KV layer into pinned host memory on a fenced CUDA side stream, ahead of the TLS send (Ch 29)
DC offset
a block mean far from zero; halves a symmetric grid’s effective precision, the case min+offset exists for (Ch 5)
decode
generating tokens one by one after the prompt has been read; the bandwidth-bound regime (Ch 1; also Ch 10)
decode-aware chunk shrinking
prefill boundaries collapse from 512 rows to 64 the moment any decoder queues, capping decode’s worst-case wait at one small interval (Ch 34)
decode-over-prefill priority
the scheduler rule that any waiting decode outranks all prefill: prefill acquires only when no decode is queued (Ch 34)
DecodeBatcher (250 µs rendezvous)
the server-side decode-step coalescer: request threads keep slot ownership while one elected runner waits ≤250 µs to pack up to four rows (disabled at parallel=1) (Ch 34)
delta handoff
a handoff armed on an exact held prefix so only the suffix crosses the wire; admission requires 256-aligned cut, nonempty suffix, exact held tokens (Ch 26)
delta witness
the receiver’s record of the observed (role, layer, start, count) segment stream, re-checked against the span schedule at prepare time on deferred delta handoffs (Ch 30)
dequantize
reconstruct an approximate value from a stored index: scale × index + min (or a float-LUT lookup) (Ch 5)
detached generation
a full replacement state built alongside live decode; swapped in only after the handoff seal validates (Ch 10; also Ch 30)
deterministic tiebreak
strictly-greater comparison so equal logits keep the lower index, matching the CPU first-maximum convention; load-bearing for byte-identical diffs (Ch 21)
DFlash
Muser’s five-layer kquant draft assistant, fed pinned target hidden states via a 33,280→6,656 fc projection (Ch 8; also Ch 10)
disaggregated prefill
splitting inference across machines by role: a producer prefills the prompt into KV, a consumer receives it and decodes (Ch 27)
dispatch
the four-line unit of GPU work: bind kernel, bind buffers, set constants, launch N threadgroups (Ch 2)
dispatch gap (+196)
the reconciled 760-vs-564 closure delta (104 norm-boundary + 39 SWA staging + 52 KV-publication + 1 copy); every cheap removal changes bits (Ch 10)
dispatch ladder
the ordered kernel choices per (dtype, token-count) in encode_quantized_matmul, kept source-pinned against llama.cpp (Ch 6)
divergence penalty
the serialization CUDA applies when threads of one warp take different branches (Ch 29)
documentation truth pass
the audit genre that re-reads claim-bearing documents against implementation and retained receipts (Ch 39)
dot product
the multiply-and-add pairing of two equal-length vectors into one scalar; the atom under every weight matrix (Ch 5; also Ch 13)
down projection
the FFN exit matvec W_down · ffn_mid ([19968→6656]); carries both Q4_K (74.76 MB) and Q6_K (109.03 MB, +45.8 %) tensors on the release artifact (Ch 19)
draft model
the small model that proposes tokens cheaply for a large target to verify; pure overhead that pays when its guesses are accepted (Ch 8)
draft trace
the deterministic per-round proposal list (draft_token_trace) that qualification compares exactly (Ch 8)
DRAM
dynamic RAM, the machine’s main system memory (96 GB on the decode Mac) (Ch 3)
drift envelope
the measured max/mean absolute full-logit (and KV) deltas between two engines’ outputs on a fixed fixture (native vs exact: 7.270581/1.040619 at 32 tokens; 10.884401/1.233789 at 2,048/256); deterministic but nonzero, and published as part of the claim. (Ch 32)
dual EOS
two end-of-generation control tokens (EOS 200,001 + EOT 200,008) merged into one stop set (Ch 9)
dual-eps fused tail
muser_fused_norm_residual_rms_norm_32sg: residual add + post-norm (eps 1e-8) + next norm (eps 1e-5) in one kernel; produces the next sub-block’s normed input (Ch 10)
dual-eps tail
the fused dispatch computing post-norm (eps 1e-8) + residual add + next pre-norm (eps 1e-5) in one kernel (Ch 12)
durable reservation
the write + fsync + rename + directory-fsync dance that persists a generation before any live engine state is published or the ACK leaves (Ch 30)
durable reserve pattern
the crash-safe commit sequence write-temp + fsync + rename + directory-fsync used by the replay ledger; its directory-fsync tail is why operational state must live on the internal disk, never the evidence volume. (Ch 31)
E2M1
the 4-bit float codebook: 1 sign + 2 exponent + 1 mantissa; values ±{0, 0.5, 1, 1.5, 2, 3, 4, 6} with relative spacing (Ch 7)
E4M3FN
the 8-bit float block scale: finite-only (NaN at 0x7f/0xff), max magnitude 448, exponent bias 7 (Ch 7)
Earley recognizer
the chart parser behind the grammar matcher: keeps every ambiguous parse stack alive and consumes tokens byte-wise, accepting partial UTF-8 sequences (Ch 21)
EEE (Energy-Efficient Ethernet)
the link’s low-power idle mode (LPI); on this lane’s burst schedule it produced retransmission blackouts quantized at 6.42 ± 0.03 s, so EEE-off is the enrolled link invariant. (Ch 31)
effective read rate
bytes-per-token × measured tokens-per-second (~594 GB/s for kquant decode); derived from measured throughput, not a spec (Ch 1)
embedding
a learned lookup table with one row per vocabulary token; “embedding a token” means reading its row (Ch 11)
embedding table (token_embd.weight)
the [hidden_dim × vocab_size] GGUF tensor, Q4_K (3,744 B/row) or F16 on Muser’s lanes (Ch 11)
encode_batch_hidden_range
the batch/serving encoder over a row range; mirrors encode_token’s op sequence (decode.rs:3858) (Ch 10; also Ch 36)
encode_token
the legacy single-token 52-layer Metal graph; teacher-forced benchmark and phase-profile route (decode.rs:5515) (Ch 10)
encrypted session envelope
the on-disk bundle format: Postcard bytes sealed with XChaCha20Poly1305 under a MUSER-SESSION-V3 magic, 0700 directory, atomic private write (Ch 37)
EOG exclusion
masking the request’s end-of-generation tokens inside the argmax reduction only, leaving stored logits byte-identical for logprob and session uses (Ch 21)
epsilon (ε)
the tiny constant inside the square root keeping the denominator positive; 1e-5 from the GGUF on Muse Glimmer (Ch 12)
exact speculative acceptance
Muser’s CPU-side verify_full_speculative_mt_ordered contract: full target distributions plus the pinned RNG stream, gated by all-logit-row comparators (Ch 21)
exact-token gate
a performance rep counts only if the two engines produced byte-identical tokens; divergent outputs mean different work, not different speed (Ch 38)
ExactIdentityV1
the compatibility namespace binding model, revision, adapter, tokenizer, chat-template, and context-policy identities; any difference is a miss (Ch 24)
exit 75
the producer’s fail-closed death code (EX_TEMPFAIL) on any engine-touched error; a dead producer is recoverable, a degraded one is not (Ch 28)
f16
IEEE 754 16-bit float (“half”): 1 sign + 5 exponent + 10 mantissa bits, ~3 decimal digits, range 2⁻¹⁴ to 65,504 (Ch 5)
f32
IEEE 754 32-bit float: 1 sign + 8 exponent + 23 mantissa bits, ~7 decimal digits (Ch 5)
fail-closed
refusing to proceed when a required ingredient is missing (e.g. Q6_K without the metallib aborts load) rather than silently substituting (Ch 4; also Ch 39)
Fallback B
the shipping disposition: native NVFP4 serves plain decode, speculation stays kquant-only, and producer_mode: native refuses DFlash at startup (Ch 7; also Ch 40)
falsification ledger
the recording device that lists each hypothesis with its evidence class and verdict (the frontier’s 14-attempt disposition table; the dispatch-gap reconciliation), so rejected designs teach instead of haunting. (Ch 33)
falsified-hypothesis ledger
the closing record of what was measured and rejected, each entry carrying hypothesis, experiment, receipt, verdict, and what the rejection preserves (Ch 40)
fast-math
a compiler contract allowing NaN/Inf assumptions and FP reordering for speed; ON for the serving library, OFF for cross-vendor parity (Ch 4)
fast-math library
Muser’s main runtime-compiled shader library, built with fast math on for speed at the token-boundary parity gate (Ch 29)
feature contract
release/feature-contract-v1.json, the frozen scope/hardware/policy file that (with findings and the lock) constitutes the campaign identity (Ch 39)
FFN (feed-forward network)
the per-token “thinking” sub-block; Muse Glimmer’s is SwiGLU at width 19,968 (Ch 9; also Ch 18)
findings register
release/findings-v1.json, the zero-waiver defect list; release requires zero open findings (Ch 39)
fingerprint
a record of what actually ran (e.g. the metallib’s SHA-256), derived from resolved state, not an env-var echo (Ch 4)
five-rep mean
the campaign’s unit of evidence: five counted repetitions after the stated warmup convention, reported as mean with coefficient of variation (Ch 38)
flash_contiguous
the prefill route predicate (origin 0, no wrap, fits capacity) that admits the direct store-then-attend routes before any staging (Ch 36)
FLOP
one floating-point operation (a multiply or an add); the unit of arithmetic budget (Ch 1)
fma
fused multiply-add, a·b+c with one rounding; the per-element dequant instruction of the 4r2s fallback (Ch 13)
footprint table
one-slot / four-slot KV bytes at 2k/32k/131k depth (0.191/0.518/1.827 GB per slot, decimal); topology-derived allocation, never peak RSS (Ch 22)
forward_decode_group
packed decode: 1..=4 sequences’ tokens in one concurrent encoder, one commit, one wait (decode.rs:4869) (Ch 10)
forward_into
the serving entry that routes one token to the batch graph and multi-token input to chunked prefill (decode.rs:2077) (Ch 10)
four unary nodes
the serving soft cap as llama.cpp builds it: ×scale, ×1/20, tanh, ×20 as separately published kernels, chosen so public bytes match the comparator (Ch 20)
full-distribution read-back
the serving route’s per-token copy of all 202,048 logits (~789 KiB) to the CPU, the price of exact sampling, grammar re-rolls, and speculative acceptance (Ch 21)
function constant
a per-PSO compile-time value ([[ function_constant(N) ]]) that specializes one source into many kernels (Ch 4)
fused gate+up route
ffn_q4k_gate_up_silu_4r2s, the opt-in (MUSER_FERRITE_FFN_GATE_UP) single-kernel SwiGLU widening; opt-in because its rounding differs from llama.cpp’s node graph (Ch 18)
gamma (γ)
the learned per-channel weight applied after normalization; re-learns the shape the norm flattened (Ch 12)
gate activation buffer
the separate 16 KiB σ(gate) tensor an unfused gate would materialize; Muser’s in-place kernel avoids it (Ch 17)
gather
reading many table rows at once indexed by an id array; decode’s embedding is a gather of batch width 1 (Ch 11)
GBNF
llama-style grammar format (Backus–Naur with charsets and token terminals) used for structured-output constraints (Ch 21)
GEMM
general matrix × matrix; the prefill-shape of every projection (Ch 10; also Ch 13)
generation number
the per-handoff monotonically increasing counter; admission requires strictly above the ledger’s committed high-water mark, and generation 0 is refused outright (Ch 30)
ggml kargs
llama.cpp’s packed C struct of tensor extents/strides (ne00, nb01, …) its Metal kernels take instead of scalars (Ch 13)
GGUF
the on-disk model-file format Muser reads: a small header plus weight tensors packed end to end (Ch 1; also Ch 9)
GPU
a processor built for ten thousand easy things at once; the unit of work is the thread (Ch 2)
GpuBuffer
Muser’s f32 activation buffer type: one shared MTLBuffer plus a length, with checked CPU slice access (Ch 3)
GpuBytes
Muser’s raw-byte buffer type, the only one that can carry the GGUF mmap (_mmap keeps the mapping alive) (Ch 3)
GpuByteView
a checked (buffer, offset, len) slice of a GpuBytes; the per-tensor weight handle kernels receive (Ch 3)
GpuHalfBuffer
Muser’s f16 (binary16) buffer type; kept distinct so an F16 KV plane can never be indexed as F32 (Ch 3)
GQA (grouped-query attention)
32 query heads share 2 KV heads (16:1); shrinks KV bytes 16× vs full multi-head (Ch 9)
GQA fan-in (32:2, 16:1)
32 query heads share 2 KV heads; kv_head = head/16; the KV read is 16× smaller than full MHA (Ch 16)
grammar rejection sampling
run the ordinary sampler first, check the winner against the grammar, re-roll with a mask only after rejection; the rejected draw still advances every RNG (Ch 21)
greedy decoding
always emitting the highest-scoring token; deterministic, hence the only policy the exact-token parity gate can diff (Ch 21)
grid
the entire launch: how many threadgroups; always shaped like the output (Ch 2)
growing plane
the NoPE regime where capacity is max_context, the ring never wraps, and origin_logical stays 0 (Ch 15)
GX10 (GB10)
the lab’s ASUS DGX Spark-class node: aarch64 host + NVIDIA GB10 GPU, prefill/storage node only, never a decode destination (Ch 28)
Hadamard product
element-wise vector multiply (a ⊙ b)[j] = a[j]·b[j], no cross-coordinate mixing (Ch 18)
half-bit embedding
decoding eight E2M1 codes as f16 bit patterns scaled by 2⁻¹⁴ and folding 2¹⁴ into the block scale; MLX-lineage trick (Ch 7)
half-split (NEOX) pairing
RoPE convention pairing x[i] with x[i+head_dim/2]; Llama-family convention, wrong for this checkpoint (Ch 14)
Handoff V2
the authenticated mTLS + HMAC-sealed tile transport from the GX10 producer (Part VI) (Ch 10; also Ch 24, 30)
hardware-aware token tree
the one surviving distributed-speculation experiment: spend otherwise-idle GX batch arithmetic covering near-miss branches, admitted only through a preregistered emitted-tokens-per-node screen against the 107.9 bar. (Ch 33)
hazard tracking
Metal’s automatic dependency ordering between dispatches touching the same buffer; Muser keeps it on (tracked) plus explicit barriers (Ch 3)
head-major layout
[kv_head][capacity][head_dim] storage keeping each KV head’s history contiguous; the NoPE plane’s layout (Ch 15)
head_dim
the width of one attention head’s Q/K/V vectors; 128 here, read from attention.key_length (not hidden/n_heads = 208) (Ch 9)
heads_per_kv
n_heads / n_kv_heads = 16 query heads served by each KV head (config.rs:274-276) (Ch 9)
HMAC
keyed hash-based message authentication code: a cryptographic tag computed over a message with a shared secret key — anyone holding the key can compute it, nobody without the key can forge it (Ch 30)
HMAC-sealed manifest
the terminal SealManifestV2: an HMAC-SHA256 tag over the canonical-JSON core binding the begin manifest, every segment descriptor, and the entire payload stream (Ch 30)
hmac_epoch
the counter-space version for the HMAC key id; re-enrollment mints a new epoch so a regenerated PKI starts a disjoint ledger space (Ch 30)
honesty tags
per-field labels on telemetry and claims: measured / target / mock (metrics schema) plus [precedent-7B-ferrite] and [roadmap] in the claims legend (Ch 39)
hybrid postmortem
the measured rejection record for aggressive norm fusion: logprob max error 3.197e-4 over contract, first divergence one f16 ULP in layer-1 V (Ch 19; also Ch 35)
Idempotency-Key replay
a retry of the same key+revision+request-digest returns the cached completion instead of generating again; the same key on a different request is a conflict (Ch 37)
inert guard
the discipline (honest skips, ambiguity refusal, load-time aborts) that keeps an unset flag from masquerading as an enabled one (Ch 4)
input_scale_inv
the optional scalar whose presence selects W4A4 arithmetic; must pair with muser.activation_precision=nvfp4 or the load fails (Ch 7)
installed payload
the measured goodput of a handoff: payload bytes over the producer’s TCP_INFO.busy_time, the campaign’s only trusted wire clock. (Ch 31)
integer-dot producer
the MUSER_NVFP4_EXACT=1 producer mode whose NVFP4 arithmetic is deterministic by integer construction; a verification anchor that is never served (“Verification only” in the lane matrix). (Ch 32)
integer-exact contraction
decoding E2M1/E4M3FN as Q1/Q9 integers so the block dot is an order-free i64 sum with a fixed 2⁻¹⁰/2⁻²⁰ denominator (Ch 7)
interleaved (GPT-J) pairing
RoPE convention pairing adjacent dimensions 2i and 2i+1 (vs half-split “NEOX”); the pinned checkpoint’s convention (Ch 9)
interleaved (NORM) pairing
RoPE convention rotating adjacent pairs (x[2i], x[2i+1]); Muse Glimmer’s convention (Ch 14)
interleaved A/B
measuring both engines in the same session, rep by rep, so machine-state noise is common-mode and cancels in the ratio (Ch 38)
INVALID_WRONG_REASON
the label for a run that failed for reasons outside its hypothesis; retained, named, and not retried into a verdict (Ch 40)
JIT compilation
compiling shader source at runtime (new_library_with_source) rather than loading a prebuilt library (Ch 4)
kernel
the per-thread program: one function every thread in a launch runs once over its own data slice (Ch 2)
kquant
llama.cpp’s K-quant family of block-quantized integer formats; Muser’s reference-lane weights, mixed per tensor (Ch 6)
KV cache
the store holding every visible past token’s K and V per layer so attention never recomputes them (Ch 15)
KV plane
one layer’s f16 key+value buffers with explicit ring metadata; live planes zero-fill by design (Ch 3)
KV publication
the store dispatch that makes the current token’s K/V visible to attention; a split closure per layer in production (Ch 15)
KV ring
the fixed 2,048-row token-major KV plane of each SWA layer; bounded at 2 MiB per layer forever (Ch 9)
KV row cost (1,024 B)
2 KV heads × head_dim 128 × 2 B f16 × (K+V): what one layer pays to cache one token; identical for both layer classes (Ch 22)
KV store + memory barrier
the vec-route pattern: store the K/V row, memory_barrier_with_resources, then attend (Ch 10)
kvpack
the vendored format/protocol family that saves, seals, verifies, and restores exact KV state; “exactness is the product; speed is the consequence” (Ch 24)
launch-claims register
docs/launch-claims.md, the table every piece of launch copy must be checked against; a number with no row does not ship (Ch 39)
layer (block)
one copy of the repeating unit: norm → attention → sandwich-norm residual → norm → FFN → sandwich-norm residual; Muse Glimmer has 52 (Ch 9)
layer exit tail
the second fused dual-eps kernel per layer: residual += post_norm(delta) at ε 1e-8, then next layer’s input = rms_norm(residual) at ε 1e-5 (Ch 19)
LayerNorm
normalize by subtracting the mean and dividing by the standard deviation; two reductions (Ch 12)
LayoutClassV2
a compact layer-class declaration (from..until step except, kv_heads, dtype, window_tokens) in a begin’s layout table (Ch 24)
leaf pin
rejecting any peer certificate whose leaf SHA-256 is not one of the enrolled digests, even when it chains to the trusted CA (Ch 30)
ledger-volume gate
the receiver’s bind-time refusal to run when its replay-ledger directory shows a >100 ms reserve-pattern tail (the 2026-08-18 evidence-volume lesson, enforced in code) (Ch 30)
legs_valid / leg_errors
the stage-5 gate that separates infrastructure failure from correctness results; a timeout can no longer publish as a cache mismatch (Ch 25)
link invariant
a link-level setting a measurement or claim is enrolled under (here: EEE off on the 10GbE path), shipped as production guidance rather than re-derived per run. (Ch 31)
llama-pinned kernels
PSOs loaded from the prebuilt llama.cpp metallib (MUSER_GGML_METALLIB) so arithmetic matches the comparator bit-for-bit (Ch 10)
llama_vec_rows predicate
vec-route eligibility: metallib present, len>0, capacity≥32, and contiguous (unwrapped or full ring) (Ch 16)
LM head
the final output.weight [6656 × 202048] projection to logits; a separate tensor from the embedding (untied) (Ch 9)
LM head (unembedding)
the largest matvec in the engine, output.weight [6656→202048] Q5_K ≈ 924.6 MB read once per token; the inverse role of the embedding (Ch 20)
local-prefill fallback
the engine’s complete Mac-local prefill path, used whenever the producer is unavailable; makes the producer a TTFT SPOF, never a correctness SPOF (Ch 27)
lockstep MAC
the fused-FFN pattern where one x load feeds both the gate and the up accumulator (and two rows) before touching memory again (Ch 18)
logprob
the natural-log probability the model assigns a token, read off the softmaxed distribution; the exactness contract bounds normalized-logprob error (1e-4 in the dispatch-gap rejection) (Ch 35; also Ch 38)
logit
one raw f32 score per vocabulary token, before any probability or bound; Muse emits 202,048 per token (Ch 20)
logit scale
the 0.196116 (= 1/√26) multiplier applied to logits BEFORE the soft cap; GGUF metadata muse-glimmer.logit_scale, not a code constant (Ch 9)
logit_scale
the 0.196116 multiplier applied to logits before the cap, read fail-closed from GGUF metadata muse-glimmer.logit_scale (numerically 1/√26) — not a code constant (Ch 20)
logits
raw per-vocab-entry scores, [202,048], before scale/cap (Ch 9)
M16 NVFP4 batch route
the W4A4 quantized-activation GEMM taken by 16-row NVFP4 chunks with 64-aligned inputs (opt-out MUSER_NO_M16_N32) (Ch 36)
marginal KV cost
the per-token bytes added past the window: 13,312 B/token (13 NoPE layers only) vs 53,248 B/token below 2,048 — exactly 13/52 (Ch 22)
mask/blk block classifier
llama’s flash_attn_ext_blk skip/partial/dense tile bytes, prepared once per chunk and shared by every full-attention layer, making the pinned causal kernel cheap on the masked triangle (Ch 36)
matvec
matrix-by-vector multiply: one dot product of a weight row against the input vector per output element; decode’s every projection (Ch 6; also Ch 10)
matvec / GEMV
matrix × vector multiply, the one operation decode performs over and over; ~2 FLOPs per weight element (Ch 1; also Ch 13)
max-subtraction
subtracting the row max before exp; algebraically exact, prevents overflow to inf/NaN (Ch 16)
maximal coupling
the speculative acceptance rule accept if rng ≤ min(p/q, 1) with a residual-corrected resample on rejection, which makes the output marginal exactly the target’s regardless of draft quality. (Ch 33)
memoryBarrierWithScope
the explicit barrier Muser plants between dispatch groups on a concurrent encoder, delimiting real graph dependencies (Ch 2)
Metal
Apple’s API for programming its GPUs: the MSL shading language, a host API, and a memory model (Ch 2)
MetalContext
Muser’s long-lived GPU state: device, queue, and the three kernel libraries (Ch 2)
MetalKvPlane
one layer’s K+V f16 buffer pair plus explicit ring metadata: capacity, len, origin_logical, origin_physical, head_major (decode.rs:182) (Ch 10; also Ch 15)
metallib
an MTLLibrary serialized to disk; loading it skips the frontend compiler entirely (Ch 4)
metallib pin
loading llama.cpp’s own prebuilt Metal library (MUSER_GGML_METALLIB) so comparator kernels run bit-identically instead of being re-expressed; fingerprinted by SHA-256 in every route identity (Ch 29)
MetalShared
one executor per accelerator: context, kernels, mmap’d weights, scheduler; shared by all slots (decode.rs:958) (Ch 10)
MetalSpeculativeCheckpoint
transactional KV protection: NoPE planes rewind metadata only; SWA planes retain the ≤16 rows a block may overwrite (decode.rs:213-226) (Ch 10)
min
the offset where an asymmetric block’s codebook starts; value = scale × index + min (Ch 5)
min+offset
the asymmetric-quantization trick: spend one extra stored number per block to place the grid exactly where the data lives (Ch 5)
Mirror-SD
the split-graph speculative verify overlap: execute the target through a capture layer synchronously, submit the remaining layers and LM head without waiting, and accept no result until the pending suffix completes (begin/finish_dflash_verify_suffix). (Ch 33)
miss control
the unrelated-prompt leg that must stay slow for a warm-hit claim to be valid; proof of reuse, not cache-forever (Ch 25)
mmap
mapping a file’s bytes directly into the process address space, lazily, page by page (Ch 3)
mock
the honesty tag for a field with no backing measurement; the dashboard renders it unavailable and historical Ferrite results are never inserted as live Muser metrics (Ch 39)
MSL
Metal Shading Language, the C++ dialect kernels are written in (Ch 2)
Mt19937
the in-tree bit-for-bit reimplementation of libc++’s std::mt19937 (llama.cpp’s sampler RNG), so seeded results are stable across engines and Rust releases (Ch 21)
MTLBuffer
Metal’s handle to a range of GPU-addressable memory (Ch 3)
MTLCommandQueue
the queue command buffers come from; created once at startup (Ch 2)
MTLDevice
the handle to the (one, system-default) GPU; allocates memory and compiles shaders (Ch 2)
MTLLibrary
a bundle of compiled kernel functions addressable by name; the middle compile stage (Ch 4)
mTLS (mutual TLS)
Transport Layer Security with both sides presenting certificates; Handoff V2 is TLS 1.3-only with an exact ALPN and pinned leaf certificates (Ch 30)
MuseConfig
the fully-resolved hyperparameter set parsed fail-closed from GGUF metadata; every field cited, no silent defaults (config.rs:106) (Ch 9)
MuseIdentity digest
one SHA-256 over all eight Muse identity dimensions; scopes the resident radix so a wrong identity is structurally unreachable (Ch 24)
MuseLayerKind
SlidingRope / FullNoPe enum; uses_rope() is true iff sliding (config.rs:55-70) (Ch 9)
MUSER_FERRITE_FFN_GATE_UP
opt-in (default OFF) fused ffn_q4k_gate_up_silu_4r2s FFN; opt-in because the pinned baseline packet regressed with it (decode.rs:980-983) (Ch 10)
muser_scale_softcap_inplace
the fused tail kernel: ×logit_scale then tanh soft cap in one pass over the logits row (muse_reference.metal:15) (Ch 10)
native lane
the product lane: Mac decodes NVFP4 weights directly, FP16 KV, F16 LM head, remote NVFP4 prefill (Ch 7)
natural-text cell
a real-corpus measurement standing alongside the synthetic matrix since the half-window bug; cross-engine outputs may diverge, so speed stands without an exactness gate (Ch 38)
negative fixture
a known-bad implementation retained to guard a contract (the 104-group fusion whose logits changed) (Ch 40)
nibble
four bits, values 0–15; two pack into one byte, low nibble first in every format in this book (Ch 5)
nonloopback bind gate
the server refuses to listen on a non-loopback address unless a TLS certificate, a mode-0600 key, and a mode-0600 API-key file are all supplied (Ch 37)
NoPE
the 13 full-attention layers ({3,7,…,51}) with no positional rotation at all; layer % 4 == 3 (Ch 14)
NoPE (FullNoPe)
full causal attention with NO positional rotation at all; the 13 layers at indices 3,7,…,51 (Ch 9)
NoPE growing plane
the 13 full layers’ head-major KV plane that grows to max_context; rows are position-free bytes (Ch 9)
NoPE tiles during prefill
the transfer schedule streaming the 13 position-free NoPE tiles (~6.5 MiB per 512 tokens) while CUDA prefill runs (schedule.rs:1-21) (Ch 10)
norm epsilon (ε)
the small constant under the RMS square root; 1e-5 from the GGUF for every norm except the two post-norms (Ch 9)
norm-boundary fusion
merging separated norm-boundary dispatches; rejected in Muser’s campaign for breaching logprob tolerance (Ch 12)
normalization
any op that pins a vector’s magnitude to a predictable band so deep stacks of multiplies don’t drift (Ch 12)
notarial vs non-notarial
sealed, independently reproducible release evidence versus unsealed engineering evidence; everything in the 2026-08 campaign is seal_eligible: false (Ch 39)
nr0 / rows_per_group
output rows each threadgroup reduces in the pinned matvec kernels; 2 for Q4_K/Q6_K, 1 for Q5_K (Ch 13)
NVFP4
NVIDIA’s 4-bit float weight format: E2M1 payloads, one E4M3FN scale per 16 values, one f32 scale2 per tensor; exactly 4.5 bits/weight (Ch 7)
Nvfp4ProducerMode
receiver config enum Exact | Native selecting the producer’s numeric contract; modes keep separated cache identities (Ch 7)
o_proj (output projection)
the matvec mixing the 32 gated attention heads (4,096 wide) back into the 6,656-wide residual stream; Q4_K [4096→6656] per layer (Ch 17)
one-button wizard
muser node add / dashboard Add node: preflight, pinned deploy, SHA-verified model placement, TLS+HMAC enrollment, daemon start, and the three-handoff qualification recipe (Ch 30)
one-row batch graph
the serving decode route: forward_batch with token_count = 1, chosen because it dispatches the exact pinned llama kernels (decode.rs:2085-2091) (Ch 10)
one-shot bandwidth hit
a per-token (not per-layer) weight read, like the LM head’s 924.6 MB: a flat slice of the budget, never a 52× multiplier (Ch 20)
online softmax
the running (max, sum, accumulator) formulation that never materializes the score array; rescales by exp(old−new) (Ch 16)
operational state
replay ledgers, sockets, locks: state that must live on the internal disk because the evidence volume’s fsync tail poisons TTFT (Ch 39)
OPERATOR REVIEW REQUIRED
the register tier for evidence-backed proposed wording that stays dead until the owner approves it, even if its reproduction gate passes (Ch 39)
origin_logical / origin_physical
the ring’s logical start vs its physical slot; placement is never derived from absolute position (Ch 10; also Ch 15)
overhead
the per-block header bytes (scales, mins) amortized over the block’s elements; what block size trades against local range (Ch 5)
pacing pin
the producer-side SO_MAX_PACING_RATE socket cap that deliberately holds the sender under line rate (8 Gbps against a ~9.4 Gbps path) so the kernel smooths the handoff’s bursts; fail-closed in both the readback and the receipt validator. (Ch 31)
pack
kvpack’s append-only durable container: 4 KiB header ‖ canonical manifest ‖ 4 KiB footer, commit written last, published by atomic rename (Ch 24)
packed decode group
forward_decode_group: 1..=4 ready decode rows from distinct slots packed into one concurrent encoder, one commit, one wait — one weight pass (Ch 34)
page alignment
new_buffer_with_bytes_no_copy requires page-aligned pointer and length; Apple Silicon pages are 16 KB (Ch 3)
page fault
the OS trap that pulls a chunk of a mapped file into memory on first touch (Ch 3)
parameter
one learned number inside the model, tuned during training; a 30B model holds ~30 billion (Ch 1)
parity ledger
the append-only campaign ledger (docs/goal-parity-ledger-2026-08.md) whose entries record hypothesis, change, exactness gate, verdict, and receipt paths (Ch 38)
partials [M, S, O]
the per-workgroup online-softmax state (max, denominator, weighted values) a reducer merges (Ch 16)
PCIe
the bus connecting CPU and discrete GPU; the memcpy path unified memory eliminates (Ch 3)
permutation-invariance
raw attention’s property of producing the same scores under token reordering; why position must be injected (Ch 14)
PhaseProfiler closures
the one-command-buffer-plus-wait units the dispatch gap counts; profiling closures, not raw Metal dispatches (Ch 10; also Ch 2, Ch 35)
pinned metallib (MUSER_GGML_METALLIB)
the prebuilt llama.cpp kernel library loaded as binary provenance for parity (Ch 13)
pinned vec per-query prefill
short chunks (<20 queries) run llama’s own vec flash kernel once per query row with exact visible prefixes, reusing the upstream PSO and reduction order (Ch 36)
positional encoding
any scheme baking token order into the vectors before attention, which is permutation-invariant without it (Ch 14)
post_norm_eps
the 1e-8 epsilon of the two sandwich post-norms; a llama.cpp graph constant, NOT GGUF metadata (config.rs:28) (Ch 9)
post_norm_eps (1e-8)
llama.cpp’s hard-coded epsilon for the two sandwich post-norms; not carried in the GGUF (Ch 12)
pre-decoded scales
decoding all 8 sub-block scale/min pairs once per super-block so the inner loop uses constants (Ch 13)
precise::cos / precise::sin
Metal’s per-call high-accuracy trig, used at RoPE’s large angles despite fast-math compilation (Ch 14)
prefill
reading the prompt (many tokens at once, weight rows reused); the compute-friendly regime (Ch 1; also Ch 10)
PREFILL_BATCH_TOKENS / MAX_TEACHER_FORCED_TOKENS
the 512-row idle-prefill chunk that shrinks to 64 rows once a decode waits (decode.rs:53-54) (Ch 10)
prefix_cut
the delta boundary lifted from raw JSON beside the typed begin manifest (the typed protocol drops unknown keys); 0 means full transfer (Ch 26)
PREPARED/staged-render/WAL/activation/ACK
the V2 research protocol’s durable verification transaction (fsynced commit WAL before idempotent renderer activation); implemented and fault-tested locally, deliberately unwired from serving. (Ch 33)
preregistered bar
a performance threshold fixed before the deciding runs (e.g. ≥ 99.151 % IID per-edge acceptance; the 200 ms GPU verifier gate) (Ch 40)
producer
the process that runs prefill and ships the KV; a role, not a machine (today: the resident vLLM NVFP4 process on the GX10) (Ch 27)
producer mode (exact/native)
the producer’s two lanes: native = tensor-core NVFP4 fast path; exact = integer-dot verification producer; selected by the Python-only MUSER_NVFP4_EXACT flag, recorded receiver-side as Nvfp4ProducerMode (Ch 28)
provenance.json
the vendored-tree manifest (schema muser.vendored-source.v1): upstream commit/tag/tree, per-file SHA-256 map, and recorded patches (Ch 24)
PSO
pipeline state object: one library function lowered all the way to machine code for this specific GPU; what set_compute_pipeline_state binds (Ch 4)
PsoCache
Muser’s in-process name→PSO registry; panics on an unregistered name rather than falling back (Ch 4)
public-logprob tolerance
the parity contract the legacy fused single-token graph breaches; the reason serving refuses it (Ch 10)
Q4_K
144 bytes per 256 elements (4.5 bits): two f16 super-scales, 8× 6-bit packed sub-scales/mins, 128 bytes of nibbles; min+offset (Ch 6)
Q5_K
176 bytes per 256 elements (5.5 bits): Q4_K plus a 32-byte high-bit plane (Ch 6)
Q6_K
210 bytes per 256 elements (6.5625 bits): signed 6-bit codes split across ql/qh planes, 16 int8 sub-scales, f16 super-scale last (Ch 6)
QK-norm
per-head RMSNorm (over each 128-vector) applied to Q and K before RoPE; eps 1e-5 (Ch 9; also Ch 14)
qk_scale_factor
≈3.87 scalar broadcast into every Q-norm weight by the converter; folded gain on top of 1/√128 (Ch 9)
qk_scale_factor (≈3.87)
the uniform Q-norm gain the converter synthesized; folded into Q, independent of the 1/√128 softmax scale (Ch 14)
QkNormProbe
load-time verification that q/k norm tensors are the converter’s constant broadcasts; aborts on a learned norm (loader.rs:98-138) (Ch 9; also Ch 14)
qualification recipe
the identity-declared check a node must pass to become healthy: exactly three ordered 2,048/256 handoffs, exact tokens under the lane’s logit policy (bounded for native/text; exact full logits plus DFlash trace for the combined lane) (Ch 30; also Ch 32)
quantization
storing each learned number in fewer bits than a full float (four-ish bits per weight on the kquant lane) (Ch 1; also Ch 5)
quantization error
the gap between the original value and its reconstruction; bounded by scale/2 for nearest rounding (Ch 5)
quantize
the inverse mapping: pick the codebook entry (index) closest to the original value (Ch 5)
query / key / value (Q/K/V)
what this token seeks / what each past token offers for matching / the payload it returns (Ch 16)
query head / KV head
one of 32 independent 128-wide attentions vs one of 2 shared K/V providers (Ch 9)
RAR non-hazard
read-after-read: concurrent readers of immutable data need no ordering; the packed decode group’s shared weight reads (Ch 35)
ratio convention
all throughput ratios in this book are llama ÷ muser; above 1.0 means Muser wins (Ch 38)
raw ceiling
the single-stream TCP rate the physical path sustains with zero tuning (9.40 Gbps pre-rebuild direct; 9.256 Gbps GX10→Mac on the switched fabric, asymmetric in reverse); a property of a topology that must be re-proven after any change. (Ch 31)
RAW hazard
read-after-write: a consumer kernel must see a producer’s bytes; the dominant decode dependency, ordered by the store→barrier→attend pattern (Ch 35)
read-modify-write
the hazard class of any in-place x = x ∘ y GPU statement; safe only when each thread owns disjoint elements and dispatches are ordered (Ch 17)
readiness receipt
the single receipt issued only after all findings are closed and every unsealed lane report belongs to the exact campaign identity (Ch 39)
red-team pass
self-audit by independent reviewers (ledger forensics, receipt recompute, statistics, framing) that certifies the evidence, not the decision built on it (Ch 39)
reference lock
an explicit lane users can route to when the native lane’s published sensitivity matters (the kquant lane), chosen manually because an automatic disagreement proxy would itself require a second reference computation. (Ch 32)
release lock
release/release-lock.json, the authoritative single file whose containment state disables seals, candidates, and publication regardless of evidence strength (Ch 39)
relocatable KV
NoPE cache bytes valid at any position (no rotation baked in); “relocate = memcpy” (Ch 14)
relocate = memcpy
the NoPE consequence: a KV tile for positions [a,b) can be planted anywhere without recomputation — “the whole kvpack free lunch” (lib.rs:9-10) (Ch 9)
remote KV install
begin_remote_kv_install: build detached planes, scatter received tiles, validate, then atomically swap into live decode (decode.rs:1852-1994) (Ch 10)
replay ledger
the durable per-key_id:epoch record of the highest committed generation; the only defense against a validly-signed replay of an already-installed handoff (Ch 30; also Ch 31)
residency set
an MTLResidencySet attaching the 16+ GiB weight arena once so Metal skips per-token residency work; fails open (Ch 3)
resident producer
the pinned, long-lived producer process in a docker container on the GX10 (vLLM at a pinned commit, digest-pinned image and checkpoint) (Ch 28)
residual relay
the two-buffer residual stream: activations.normed carries the residual, activations.post_norm the normed sub-block output; the last tail writes activations.hidden (decode.rs:5546-5548) (Ch 10)
residual stream (hidden state)
the per-token vector every layer only ever adds to; [6656] f32 in Muse Glimmer (Ch 9)
restart ritual
the only correct producer restart: move the O_EXCL startup receipt and RoPE cache aside, remove the stale socket, check the accelerator lease, restart, and wait for the fresh startup receipt (Ch 28)
reuse ladder
the ordered exact-prefix tiers: current session → resident → durable → remote; each stricter tier caps the one before (Ch 25)
revision CAS
optimistic concurrency for sessions: commit advances revision only if the record still holds the client’s expected value (Ch 37)
ring
the fixed-capacity circular store where new rows overwrite the oldest; capacity = min(max_context, 2,048) on SWA layers (Ch 15)
ring rotation
where origin_physical points; must be preserved on restore because attention’s float accumulation is scan-order sensitive (Ch 15)
rms / mean of squares
√((1/n)Σxⱼ²); equals the standard deviation exactly when the mean is zero (Ch 12)
RMSNorm
x / sqrt(mean(x²)+ε) ⊙ γ; no mean subtraction; rescales the stream to ~unit magnitude (Ch 9; also Ch 12)
roofline
the compute-vs-memory crossover picture; a workload below the machine’s balance point is memory-bound (Ch 1)
roofline flip
the ~500× jump in arithmetic intensity between decode (~3.2 FLOPs/byte) and a 512-token prefill chunk (~1,619 FLOPs/byte): the two regimes want opposite machines (Ch 27)
RoPE
rotary positional embedding: rotate Q/K pairs by position-dependent angles so Q·K depends only on position difference (Ch 14)
RoPE (rotary positional embedding)
position injected by rotating Q/K coordinate pairs by position-dependent angles; SWA layers only, theta 500,000 (Ch 9)
rope theta (freq base)
base frequency 500,000 (rope.freq_base_swa); larger theta ⇒ positions distinguishable over longer ranges (Ch 9)
rotation matrix
the 2×2 [cos −sin; sin cos] that spins one coordinate pair; RoPE’s only arithmetic (Ch 14)
route ladder
the four attention routes (llama vec / splitk / batch-store vec / ferrite interleaved) selected per layer per token (Ch 16)
row-major
weight layout where each output row’s elements are contiguous in memory; makes one row one DRAM span (Ch 13)
rsqrt vs 1/sqrt
fast-math reciprocal sqrt vs the IEEE-rounded pair; a few ULP per call that compound across a token (Ch 12)
sampler state
per-request RNG streams (distribution, XTC, mirostat, adaptive) plus scalars, snapshotted with sessions so a resumed request replays its exact draw sequence (Ch 21)
sampling
converting logits to a (temperature-scaled, top-k/top-p filtered) distribution and drawing a token; needs the full vocab row, not just the winner (Ch 21)
sandwich norm
Gemma-2-style placement: each sub-block’s output is normalized (post_attention_norm / post_ffw_norm) before the residual add (Ch 9; also Ch 12)
scale
the local step size of a block’s codebook, stored in floating point because it must span wide magnitude ranges (Ch 5)
scale2
the per-tensor f32 multiplier applied last in NVFP4’s pinned order (e2m1 × e4m3fn) × scale2 (Ch 7)
score
the scaled dot product Q·K/√128 that measures a past token’s relevance (Ch 16)
segment role
the closed 9-variant plane vocabulary (NoPE/SWA × K/V, packed tiles, DFlash, Auxiliary); the two-regime economics as wire types (Ch 24)
serial prefill dispatch
MUSER_SERIAL_PREFILL_DISPATCH: restores the serial prefill encoder for exact A/B against the default concurrent grouping (Ch 35)
session bundle
the durable unit binding model/tokenizer/template/layout-ABI/DFlash/vision identities to target KV, logits, RNG streams, sampler, grammar, detokenizer, and replay state (Ch 37)
SessionCacheSnapshot
the engine↔kvpack interchange cut: 39 SWA planes carry the logical tail, 13 NoPE planes [0, position), shape-gated fail-closed (Ch 23)
sigmoid
the logistic function σ(x) = 1/(1 + e^(−x)), mapping any real into (0, 1); the smooth on/off curve behind both Muse gates (Ch 17)
sigmoid attention gate
Muse Glimmer’s extra [6656→4096] projection; attn_out ⊙ σ(gate) applied between attention and o_proj (Ch 9)
SiLU
activation x·σ(x), also called Swish (Ch 9)
SiLU (Swish)
the activation x·σ(x): ReLU-like at the extremes, smooth, and slightly negative for small negative x (dips to ≈ −0.278 near x ≈ −1.28) (Ch 18)
SIMD group
exactly 32 threads executing in lockstep on one SIMD ALU; the unit that actually matters on Apple Silicon (Ch 2)
simd_sum
one-instruction 32-way reduction across a SIMD group’s lanes (Ch 2)
single-rep diagnostic
a one-sample cell marked † that never joins a matrix and cannot support a claim (Ch 38)
sink
the pinned first 64 rows of the DFlash context cache (DFLASH_CONTEXT_SINK_SIZE), part of the ABI, not GGUF metadata (Ch 8)
sliding_window_pattern
the GGUF period-4 key resolving [sliding, sliding, sliding, full]; missing key ⇒ panic, not a guess (Ch 9)
slot (serving)
one of 1..=4 resident request contexts with independent KV/RNG/sampler/grammar state over shared immutable weights; KV is the per-slot, depth-scaling term (Ch 22)
SlotPool
bounded server admission for the 1..=4 resident slots: at most 64 waiters, immediate overload rejection past that, and the permanent unhealthy latch on poisoned state (Ch 34)
slow-client cancellation
a streaming writer backpressured past its 5 s grace (channel depth 64) is cancelled with 499; a slow reader can waste its own request, never the accelerator (Ch 34)
slow-volume refusal
the receiver’s bind-time probe rejecting a replay-ledger volume whose reserve pattern (write+fsync+rename+dir-fsync) tails past 100 ms (Ch 24)
SoC
system-on-chip: CPU cores and GPU on one piece of silicon pointing at the same DRAM (Ch 3)
soft cap
l → 20·tanh(l/20): confines logits to (−20, 20) without a cliff; changes what cross-engine logit comparison means (Ch 9; also Ch 20)
softmax
exp(xᵢ)/Σⱼexp(xⱼ): turns attention scores into positive weights summing to 1 (Ch 9; also Ch 16)
source receipt
a provenance JSON binding a built artifact to its exact source commit, per-file hashes, and toolchain (Ch 4)
speculative acceptance rule
accept a draft token with probability min(p/q, 1) against the full target distribution; on rejection, sample the renormalized max(p−q, 0) residual (Ch 21)
speculative checkpoint
the per-block transactional rollback: NoPE planes rewind metadata only; SWA planes retain the ≤16 live ring rows a block overwrites (Ch 23)
speculative decoding
draft k tokens, verify all k+1 rows in one target batch; exact because acceptance is decided against full target distributions (Ch 8)
speculative verify
the block-shaped third regime: the target scores up to MAX_DFLASH_BLOCK = 16 drafted tokens in one pass (Ch 10)
splitk
splitting the KV scan across workgroups/SIMD groups, each producing partials the reducer combines (Ch 16)
staging generation
the full-capacity rebuild context (target + DFlash) kept deliberately outside the slot pool so atomic context shift can never become a fifth serving slot (Ch 34)
staging session
the one full-capacity generation reserved for atomic context rebuilds, deliberately outside the slot pool; publication is a pointer swap (Ch 23)
staging shadow
the detached F16 buffer where a wrapped SWA prefill stages old ring rows + the new chunk as one contiguous logical span before ring commit (Ch 23)
staging-shadow route
wrapped-SWA prefill: stage old ring rows plus the chunk into a detached F16 shadow (llama’s padded absolute indices for the one-row variant), attend from the shadow, commit ring metadata only after (Ch 36)
stateful generation
chat requests carrying session_id + expected_revision + Idempotency-Key (all or none); the server holds the frontier and refuses concurrent writers with 409 (Ch 37)
storage mode
Metal’s declaration of where buffer bytes live: Shared, Private, or Managed (Ch 3)
StorageModeShared
one copy of the bytes in unified memory, CPU- and GPU-visible; the only mode Muser uses (Ch 3)
strict-f32 cross-vendor library
the second compile of muse_reference.metal + nvfp4.metal with fast math off, used by MUSER_CROSS_VENDOR_QK routes so Q/K match the CUDA producer’s scalar boundaries exactly (Ch 29)
sub-block
the 32-element (K4/K5) or 16-element (K6) group inside a super-block carrying its own scale/min (Ch 6)
SUPERSEDED banner
the correction written on the artifact itself when a document’s numbers are invalidated; history is preserved, never edited (Ch 39)
supervisor latch
supervise_resident_producer.py’s policy of giving up after three consecutive failed starts (with doubling backoff) rather than flapping forever (Ch 28)
SWA (sliding-window attention)
each query sees only the previous 2,048 tokens (p1 − p0 < n_swa); Muse Glimmer’s 39 SlidingRope layers (Ch 9)
SWA amortization
the property that 39 of 52 layers stop billing at token 2,048, so a 64× longer context costs only ~16.75× more KV (Ch 22)
SWA layer
one of the 39 sliding-window layers (window 2,048) that carry RoPE (Ch 14)
SWA staging
copying a wrapped ring into llama’s padded absolute indices so the pinned kernel sees llama’s exact reduction lanes (Ch 16)
SWA window re-send
the delta’s mandatory fresh 2,048-token window (≈81.79 MB): held ring rows are position-bound (RoPE) and cannot serve the new tail (Ch 26)
SwiGLU
gated FFN: down(SiLU(gate·x) ⊙ (up·x)) over width 19,968 (Ch 9; also Ch 18)
symmetric quantization
one number per block (a scale), codebook spanning ±scale·max, zero maps to zero; cheapest, wastes range on offset blocks (Ch 5)
synthetic fixture
a deterministic fixed prompt (period-8 cycle of 9 token ids) enabling exact cross-engine comparison; predictable from token identity alone (Ch 38)
target
the full model whose exact distributions decide every accepted token; the draft never decides (Ch 8)
target-cache identity
the digest a receiver pins per producer recipe so KV entries from different recipes (exact vs native) can never alias; part of the cluster config, not derivable from the model file alone. (Ch 32)
target-engine epoch
the named identity of which engine executed authoritative target transitions (Mac/Metal vs GX/vLLM); silently switching epochs mid-session is not exact, so fallbacks need a signed seam. (Ch 33)
targeted resource barrier
memory_barrier_with_resources naming exactly the buffers a dependency flows through (K/V planes, partials, staged shadow) instead of a whole-scope stall (Ch 35)
teacher-forced
a decode benchmark cell that feeds known prior tokens (e.g. 32) rather than model-generated ones (Ch 1; also Ch 38)
teacher-forced route
feeding known tokens through the exact one-token graph to match the comparator’s no-readback policy (decode.rs:2118-2137) (Ch 10)
tensor core
a matrix-multiply unit consuming low-precision operands natively (FP4 on the GB10); the arithmetic engine of producer-side NVFP4 prefill (Ch 27)
theta / rope_base_swa
the base of the per-pair frequency table θᵢ = base^(−2i/head_dim), read from rope.freq_base_swa (Ch 14)
thread
one execution lane running the kernel once, identified by thread_position_in_grid (Ch 2)
threadgroup
a block of 32–1024 threads sharing on-chip memory and barriers; the unit of co-scheduling (Ch 2)
threadgroup barrier
threadgroup_barrier(...): every thread in a threadgroup waits until prior threadgroup-memory writes are visible (Ch 2)
threadgroup memory
on-chip shared memory visible to all threads of one threadgroup (threadgroup(0)) (Ch 2)
TLB
translation-lookaside buffer, the small cache of virtual→physical page translations; fewer buffers means less pressure (Ch 3)
token
one unit of text, roughly a piece of a word; the model emits tokens one at a time (Ch 1; also Ch 9)
token-major layout
[capacity][kv_dim] storage keeping one token’s two KV-head rows adjacent; the SWA ring’s layout (Ch 15)
TPOT (time per output token)
the decode-dominated per-token latency a streaming user feels; DFlash’s target, as TTFT is disaggregation’s (Ch 36)
tracked-by-default (b9678d4)
every Muser buffer allocates with Metal hazard tracking on; the one global-untracked experiment changed DFlash conditioning with identical greedy IDs and was reverted (Ch 35)
transactional checkpoint
the pre-round KV snapshot (MetalSpeculativeCheckpoint): NoPE rewind is metadata-only, SWA rings retain the ≤16 rows a block may overwrite (Ch 8)
transfer status vocabulary
starting → transferring → destination_committed → source_restored → completed, with ambiguous and source_restored_remote_retained as crash-safe degradations (Ch 26)
transformer
an attention-plus-FFN block stack that maps a token sequence to a next-token prediction; the 2017 architecture of [arxiv:1706.03762] (Ch 9)
tree reduction
parallel reduction by stride-halving folds with a barrier per pass; log₂(n) steps inside one threadgroup (Ch 21)
trust ladder
the three strictness rungs of the disaggregated lanes: bit-exact full handoff → exact tokens + declared bounded logits → parity-within-noise; each rung is declared in the lane’s identity, with its costs and what it rules out. (Ch 32)
TTFT (time to first token)
the latency from prompt submission to the first generated token; the user-visible quantity prefill dominates (Ch 27)
TTFT cliff
the linear, compute-bound growth of local prefill TTFT with prompt depth (6.48 s at 2,048 to 570.12 s at the 131k class on one Mac) (Ch 27)
two-phase migration
copy/move where the destination durably commits before a move may delete the source; status idempotently queryable after ambiguous failures (Ch 26)
two-phase reduction
chunk the input (⌈202,048/1,024⌉ = 198 threadgroups), reduce each chunk to a (value, index) partial, then reduce the partials in one final threadgroup (Ch 21)
ULP
unit in the last place: the smallest increment a floating-point representation can express at a given exponent; the rejected hybrid’s first divergence was a single f16 ULP flip in one layer-1 V value (Ch 35; also Ch 32)
unfused control route
the serving default FFN widening: two pinned ggml matvecs plus the pointwise muser_silu_mul_inplace, exact to the comparator’s graph (Ch 18)
unhealthy latch
poisoned accelerator/session state flips a permanent flag: every request 503s until an operator restarts; fail-closed as user-visible HTTP (Ch 37)
unified memory
Apple Silicon’s single physical DRAM pool shared by CPU and GPU; no copy needed to move data between them (Ch 3)
untied embeddings
token_embd.weight and output.weight are two independent required tensors (config.rs:294-298) (Ch 9; also Ch 11)
verifier-only ceiling
output_tokens ÷ sum(remote verifier wall): an upper bound granting zero cost to drafting, transport, installation, and scheduling; the decisive rejection bound when it sits under the bar. (Ch 33; also Ch 40)
verify length
proposals per round: exactly 3, 7, or 15; harness pins 15, serving froze 7 for natural-text robustness (Ch 8)
visible window
min(position+1, window) rows the kernel may read; SWA masks by addressing, never reading out-of-window tokens (Ch 16)
vocab / vocab_size
the set (and count) of token ids; 202,048 for Muse Glimmer (Ch 11)
VRAM
a discrete GPU’s own private memory pool, filled by copying across PCIe (Ch 3)
W4A16
weight-only NVFP4 mode: 4-bit weights, wide activations quantized per 256-value super-block to a Q8-K-style grid; the selected product artifact (Ch 7)
W4A4
weights and activations both 4-bit (FP4 groups of 16, gated by input_scale_inv); on the Mac, qualified for batch parity only (Ch 7)
WAR hazard
write-after-read: an overwriter must wait for outstanding readers — the ring-lap risk explicit origin bookkeeping prevents (Ch 35)
warm hit
a request served entirely from installed state: no producer compute, no transfer (producer_driven: false); shallow 64.631 ms vs deep 0.6132 s / 1.0566 s are different scopes (Ch 25)
warp
CUDA’s unit of 32 threads executing in lockstep; the counterpart abstraction to Metal’s SIMD group, and the reason the ferrite-lineage kernels ported cleanly (Ch 29)
WAW hazard
write-after-write: two writers of one buffer must serialize or the final bytes are order-dependent (Ch 35)
WebSocket ticket
the 30-second single-use secret minted by /v1/ws-tickets so long-lived bearer keys never ride URLs; consumed and removed on first use (Ch 37)
weight
one learned coefficient of the model, multiplied in during the forward pass; Muse Glimmer has ≈27.9 B of them by tensor count (Ch 5)
weight-stream-bound
the structural cost of a remote verifier: one full model-weight read per verification round, which no protocol tuning removes (why the linear distributed lane’s ceilings capped at 20.15–55.96 tok/s). (Ch 33)
weights
the parameters collectively: the giant tables of learned numbers stored on disk in a GGUF (Ch 1)
wire as memory bus
the Part VI physical picture: between a CUDA address space and a Metal address space, the network carries what a memcpy would on either machine alone (Ch 29)
wire floor
the minimum transfer time for a given payload at a given rate (2k ≈ 224 ms, 131k ≈ 3.75 s at 3.9 Gbps); the irreducible wire bill before any overhead. (Ch 31)
witnessed final logits
the exact-hit requirement that a cut ending a generation carries the final target distribution with the KV (Ch 25)
workgroup cap (nwg=32)
the split schedule’s fixed 32 workgroups matching llama’s launch; the admitted “depth-rent” vs higher occupancy (Ch 16)
zero-copy
getting data to the GPU without moving bytes: the mmap’d GGUF becomes one MTLBuffer as-is (Ch 3)
Appendix B — The kernel dispatch table
status: draft · path: Muse Glimmer, pinned Muser tree
The decode path on one page: every kernel the engine dispatches for one token, in execution order, with the shader it comes from, the Rust wrapper that binds it, what it reads and writes, and the chapter that explains it. All
file:linetags were verified against the pinned tree6d0807da(see PINNED.md).
B.1 The three kernel sources (legend)
Muser deliberately runs compute from three libraries (Ch 4,
crates/muser-engine/src/metal/context.rs:59-131). Every table row below
says which source its kernel comes from:
| Tag | Source | Built | Code |
|---|---|---|---|
| S | Serving concat library — 27 .metal files concatenated with include_str!, compiled at engine init with fast-math ON (MSL 3.1). Muser-authored muse_reference.metal + nvfp4.metal plus the ferrite-lineage extraction | runtime new_library_with_source | context.rs:59-110; 66-name PIPELINES registry metal/encode.rs:21-88 |
| X | Strict-f32 cross-vendor library — the same two Muser source files (muse_reference + nvfp4), recompiled with fast-math OFF to match CUDA’s explicit scalar boundaries for the exact/verification lanes | runtime new_library_with_source | context.rs:111-121; PSO fields metal/encode.rs:205-277 |
| L | llama.cpp pinned metallib — prebuilt .metallib from llama.cpp commit 89e0aa6f…, loaded from the MUSER_GGML_METALLIB path; supplies the ggml matvec/matmul/norm/rope/unary and flash_attn_ext kernels Muser refuses to re-express | new_library_with_file | context.rs:122-131; PSO picks metal/encode.rs:278-293; flash PSO table LlamaFlashAttnPipelines metal/encode.rs:150-167 |
Rows tagged X run only under MUSER_CROSS_VENDOR_QK (Appendix C);
they are the strict arithmetic-ABI mirror, not the serving default.
B.2 Per-layer decode chain (52 layers, execution order)
From encode_token (crates/muser-engine/src/decode.rs:5515-5906), the
teacher-forced single-token graph. The serving route packs 1..=4 resident
sequences through forward_decode_group → encode_decode_group
(decode.rs:4869, :4954), which mirrors this op sequence row for row
(every wrapper below appears there too, with rows 1..=4); where the
serving route differs, the row says so. Everything from embedding to
softcap is one Metal command buffer per token (concurrent dispatch type,
explicit barriers, decode.rs:5448-5460).
| # | Stage | Kernel / function | Src | Shader (file:line) | Rust dispatch (file:line) | Reads → writes (one line) | Ch |
|---|---|---|---|---|---|---|---|
| 1 | Embedding lookup | muser_embedding_q4k (F16 table: muser_embedding_f16) | S | shaders/muse_reference.metal:961 (nvfp4.metal:755) | encode_embedding_q4k metal/encode/qkv.rs:338 (bind :365); walk decode.rs:5524 | one u32 token id + one Q4_K embedding row → the [hidden_dim] f32 residual stream | 11 |
| 2 | Entry norm (weight = ones) | llama kernel_rms_norm_mul_f32_4; fallback rms_norm_batch | L / S | llama metallib; shaders/ferrite/rmsnorm_batch_tail.metal:1 | encode_rms_norm_mul metal/encode/norm.rs:244 (bind :270); decode.rs:5535 | hidden row → normed (RMS, eps 1e-5, × ones) | 12 |
| 3 | Attention pre-norm — layer 0 only (later layers receive it fused from the previous tail) | same as row 2 | L / S | same as row 2 | encode_rms_norm_mul norm.rs:244; decode.rs:5553-5564 | normed → post_norm with layer.attn_norm | 12 |
| 4 | Q, K, V, gate projections — one concurrent set of 4 matvecs sharing the input row | llama kernel_mul_mv_q4_K_f32 (Q5_K q5_K, Q6_K q6_K); fallbacks muser_matvec_q4k_4r2s, muser_matvec_q5k_4sg; NVFP4 muser_nvfp4_w4a4_matvec_c1; F16 muser_f16_matvec_c1 | L / S | llama metallib (pick metal/encode.rs:278-280); muse_reference.metal:735 / :790; nvfp4.metal:312 / :738 | encode_projection decode.rs:6044 → encode_quantized_matmul qkv.rs:414 (ggml bind :439, fallback bind :464) / encode_nvfp4_matmul qkv.rs:128 / encode_f16_matmul qkv.rs:68; call decode.rs:5569-5598 | post_norm row + the four weight matrices → q, k, v, gate activations | 13 |
| 5 | Per-head QK-norm (parameterless; the ≈3.87 scale is folded into the norm weights) | same ggml/rms_norm_batch path as row 2; DFlash’s own route uses rms_norm_per_head (B.6) | L / S | llama metallib; rmsnorm_batch_tail.metal:1; ferrite/rms_norm_per_head.metal:15 (registry encode.rs:58) | encode_qk_norm norm.rs:286 → encode_rms_norm_mul norm.rs:244; Q call decode.rs:5600-5608, K call :5609-5617 | 128-wide head slices of q and k, normalized in place | 14 |
| 6 | RoPE — SWA layers only (uses_rope(), config.rs:68-70); interleaved GPT-J pairs; NoPE full layers skip the dispatch entirely | rope_norm_batch_cached (or llama rope_norm_f32 pinned PSO) | S / L | shaders/ferrite/rope.metal:624 (plain rope_batch_cached :566) | encode_rope_norm_batch_cached metal/encode/rope.rs:45 (ggml pick :88-137, bind :139); decode.rs:5621-5640 | cached frequency table + q,k → rotated q,k in place | 14 |
| 7a | KV store (token-major ring) + attention — SWA, vec-eligible | muser_kv_store_f16, then memory barrier, then llama kernel_flash_attn_ext_vec_f16_dk128_dv128 (+ flash_attn_ext_pad when visible % 32 ≠ 0, + flash_attn_ext_vec_reduce) | S + L | muse_reference.metal:979; llama metallib (LlamaFlashAttnPipelines encode.rs:150-167) | encode_kv_store_f16 attn.rs:635 (bind :647); encode_llama_flash_attn_decode_vec_f16 attn.rs:437; decode.rs:5660-5693 | K,V rows → ring slot write_physical; then q + whole ring → attention | 15, 16 |
| 7b | Attention — SWA fallback (window not 32-aligned, or no metallib) | muser_attention_decode_splitk_f16 + muser_attention_decode_splitk_reduce_f32 | S | muse_reference.metal:1052 + :1169 | encode_attention_decode_splitk_f16 attn.rs:708 (binds :748, :776); decode.rs:5695-5723 | q + ring (split-K partials per workgroup) → attention; geometry splitk_geometry attn.rs:888-896 | 16 |
| 7c | KV store (head-major plane) + attention — NoPE, vec-eligible | muser_kv_store_batch_f16 + barrier + llama vec kernel (ns10 = 128) | S + L | muse_reference.metal:1203; llama metallib | encode_kv_store_batch_f16 attn.rs:787 (bind :812); attn.rs:437; decode.rs:5728-5770 | K,V rows → growing plane at position; q + plane → attention | 15, 16 |
| 7d | Attention — NoPE fallback | flash_attn_decode_vec_f16_gqa_interleaved + flash_attn_decode_reduce_v2 | S | shaders/ferrite/flash_attn_decode_vec_contiguous_f16.metal:494; flash_attn_decode_reduce_v2.metal:4 | encode_ferrite_attention_decode_interleaved_f16 attn.rs:189 (PSOs encode.rs:298-313); decode.rs:5772-5790 | q + head-major plane (partials) → attention; also re-reads current k,v to dodge a store-load race | 16 |
| — | Route ladder predicates | llama_vec_rows = (strict ‖ has_llama_flash_attn_vec) && len>0 && capacity≥32 && (origin_physical==0 ‖ len==capacity); llama_swa = llama_vec_rows && len % 32 == 0 | — | — | decode.rs:5645-5657 | — | 16 |
| 8 | Sigmoid attention-output gate | sigmoid_gate_inplace | S | shaders/ferrite/sigmoid_gate.metal:7 | encode_sigmoid_gate metal/encode/gate.rs:7 (bind :17); decode.rs:5793-5799 | attention × sigmoid(gate) → attention in place | 17 |
| 9 | o_proj matvec | same stack as row 4 | L / S | same as row 4 | project decode.rs:5800 → project_tokens :5909-5930 → encode_projection :6044 | gated attention + output weights → projected | 17 |
| 10 | Fused post-attention tail: residual add + post-norm (eps 1e-8) + FFN-norm (eps 1e-5) | muser_fused_norm_residual_rms_norm_32sg | S | shaders/ferrite/rmsnorm_batch_tail.metal:147 | encode_fused_norm_residual_rms_norm_32sg norm.rs:163 (via …_32sg_batch :190, bind :227); decode.rs:5806-5818 | normed (residual) + projected → post_norm; 32 SIMD groups, 1,024 threads, 144 B threadgroup | 12, 17 |
| 11 | FFN gate+up — fused dual-read when MUSER_FERRITE_FFN_GATE_UP is set and both tensors are Q4_K (the release artifact is); else exact control | ffn_q4k_gate_up_silu_4r2s; control path: two row-4 matvecs + muser_silu_mul_inplace | S | shaders/ferrite/ffn_fused_tail.metal:496; muse_reference.metal:4 | encode_ffn_q4k_gate_up_silu_4r2s ffn.rs:10 (bind :25) / encode_silu_mul ffn.rs:38 (bind :49); decode.rs:5819-5862 | post_norm read once → SiLU(gate)·up written to ffn_gate | 18 |
| 12 | ffn_down matvec | same stack as row 4 | L / S | same as row 4 | decode.rs:5863-5868 → :6044 | ffn_gate + ffn_down weights → projected | 19 |
| 13 | Fused post-FFN tail: residual + post-FFN-norm (1e-8) + next layer’s attn-norm (last layer: final norm) | muser_fused_norm_residual_rms_norm_32sg | S | rmsnorm_batch_tail.metal:147 | norm.rs:163; next_norm selected decode.rs:5869-5876, dispatch :5877-5889 | normed + projected → next layer’s input (or hidden on layer 51) | 12, 19 |
Serving-route variants of rows 2/10/13: the packed decode group uses the
same …_32sg kernel with rows up to 4 (encode_fused_norm_residual_rms_norm_32sg_batch, norm.rs:190, call decode.rs:5128, :5202);
the batch-prefill graph uses the rows-general
muser_fused_norm_residual_rms_norm_batch_dual_eps
(rmsnorm_batch_tail.metal:250, calls decode.rs:4513, :4656).
Under MUSER_CROSS_VENDOR_QK every fused tail decomposes into strict
cross-vendor kernels (X) with barriers at each model-dtype boundary
(norm.rs:208-225).
B.3 Once-per-token tail
| # | Stage | Kernel / function | Src | Shader (file:line) | Rust dispatch (file:line) | Reads → writes | Ch |
|---|---|---|---|---|---|---|---|
| 14 | Final RMSNorm | fused into row 13’s tail on the single-token graph (last layer’s next_norm = output_norm, decode.rs:5874-5875); the decode-group path emits a separate norm | llama kernel_rms_norm_mul_f32_4 / rms_norm_batch | L / S | fused: decode.rs:5869-5888; separate: encode_rms_norm_mul decode.rs:5229-5241 | hidden → normed logits input | 20 |
| 15 | LM head matvec | same matvec stack as row 4 (kquant); NVFP4 lane: unquantized F16 head muser_f16_matvec_c1 — the ~3.46 ms/token cost that keeps NVFP4 decode at parity, not faster | L / S | llama metallib; nvfp4.metal:738 | project decode.rs:5892-5897 → :6044 / qkv.rs:68 | hidden + vocab projection → logits[vocab] | 20 |
| 16 | Logit scale + soft cap | muser_scale_softcap_inplace; or, to match llama’s graph literally, four ggml unary nodes (scale → tanh → scale) | S / L | shaders/muse_reference.metal:15; llama metallib unary (PSO pick encode.rs:289-290) | encode_scale_softcap lmhead.rs:163 → …_count :188 (ggml quartet :230-259); decode.rs:5898-5905 | logits × logit_scale (= 1/√26 ≈ 0.196116, GGUF metadata, config.rs:190-192) then tanh at softcap 20, in place | 20 |
| 17 | Sampling read-back | argmax / MT19937 sampling on CPU over the read-back distribution; a GPU argmax pair exists for the no-readback benchmark lanes: argmax_f32_phase{1,2} + greedy_argmax_f32_phase{1,2} | S (GPU lanes) | shaders/ferrite/argmax_f32.metal:7, :41, :77, :125 | CPU: Session::decode api.rs:696-741 + sampling.rs (distribution buffer retained in place, api.rs:700-703); GPU: encode_argmax_f32_rows lmhead.rs:83, encode_greedy_argmax_f32 lmhead.rs:123 | full-vocab f32 row read back once per token (4 bytes out on the greedy GPU lane) | 21 |
B.4 Prefill batch kernels (the second graph)
Prefill is not decode with more rows: it is a separate batch graph,
Session::prefill (api.rs:634) → forward_batch (decode.rs:2857) →
forward_batch_hidden (:3788) → encode_batch_hidden_range
(:3858-4365), chunked at PREFILL_BATCH_TOKENS = 512 idle / 64 once a
decode waits (decode.rs:53-54, :2095-2113). Projections go through
encode_batch_projection (decode.rs:5946-5980); attention routes at
decode.rs:4090-4365 (Ch 36):
| Stage | Kernel / function | Src | Shader (file:line) | Rust dispatch (file:line) | Reads → writes | Ch |
|---|---|---|---|---|---|---|
| Embedding (per chunk row) | muser_embedding_q4k | S | muse_reference.metal:961 | encode_embedding_q4k_from_u32_buffer qkv.rs:376; decode.rs:1756 | u32 token buffer row → batch hidden | 36 |
| Projections, NVFP4 M16 route (16 rows, n_in % 64 == 0) | muser_nvfp4_w4a4_quantize_m16 + muser_nvfp4_w4a4_prequant_m16_n32 (B.5) | X | nvfp4.metal:468 + :504 | encode_nvfp4_w4a4_prequant_m16 qkv.rs:13; picked decode.rs:5955-5977 | activations quantized once, then one weight-stationary 32-row tile per projection | 36 |
| Projections, small batch 4..=8 rows | llama kernel_mul_mv_ext_{q4,q5,q6}_K_f32_r1_{2..5} | L | llama metallib (LlamaMulMvExtPipelines encode.rs:174-179) | encode_quantized_matmul qkv.rs:482-508 | the llama-pinned dispatch boundary; changing it breaks logprob parity (qkv.rs:476-481) | 36 |
| Projections, 16-row K-quant blocks | m16_q4k_n32 / m16_q5k_n32 / m16_q6k_n32 | S | shaders/ferrite/batch_m16_n32.metal:59 / :266 / :163 | encode_quantized_matmul qkv.rs:556-579 | DFlash verify/draft blocks; weight-stationary n32 tile, 6 KiB threadgroup | 36, 33 |
| Projections, aligned Q4_K batches | matmul_q4k_batch_sgm_aligned | S | shaders/ferrite/batch_sgm_q4_aligned.metal:69 | encode_quantized_matmul qkv.rs:584-601 | Ferrite’s accepted high-occupancy SIMD-group-matrix GEMM | 36 |
| Projections, general batch | llama kernel_mul_mm_q{4,5,6}_K_f32 (aligned/bounds); fallback muser_matmul_q4k/_q5k | L / S | llama metallib; muse_reference.metal:912 / :929 | qkv.rs:604-640 | the roofline-flipped batch GEMM regime | 36 |
| KV store, contiguous route | muser_kv_store_batch_f16 | S | muse_reference.metal:1203 | attn.rs:787; decode.rs:4135, :4388 | chunk K,V rows → plane/ring before attention reads them back | 36 |
| Attention (a): short chunks, < 20 queries | llama vec kernel, one unmasked launch per query row | L | llama metallib (attn.rs:437 family) | llama_vec_prefill_route_available decode.rs:65; per-row launches decode.rs:4153-4179 | q row + visible cache → one attention row | 36 |
| Attention (b): NoPE at llama chunk bounds | muser_fa_causal_mask_f16 + llama flash_attn_ext_blk (once per chunk) + llama kernel_flash_attn_ext_f16_dk128_dv128 | S + L | muse_reference.metal:1514; llama metallib | encode_llama_fa_prefill_mask_blk attn.rs:266 (binds :283, :307); encode_llama_flash_attn_prefill_f16 attn.rs:328; decode.rs:4197-4208 | causal f16 mask + skip/partial/dense block bytes, then the masked causal prefill kernel | 36 |
| Attention (c): local FlashAttention-2 | flash_attn_v2; one-query GQA specialization muser_flash_attn_decode_gqa_fa2 | S | shaders/ferrite/flash_attn_v2.metal:59; flash_attn_decode_gqa_fa2.metal:39 | encode_flash_attention_v2 attn.rs:13 (specialization :61-79); decode.rs:4227, :4329 | q chunk + f16 KV cache → attention chunk | 36 |
| SWA ring wrap: staging shadow | muser_stage_swa_prefill_f16 (chunked) / muser_stage_swa_llama_decode_f16 (single-row, llama’s 256-row-padded indices) | S | muse_reference.metal:1240 / :1281 | encode_stage_swa_prefill_f16 attn.rs:103; encode_stage_swa_llama_decode_f16 attn.rs:145; decode.rs:4264, :4281 | old ring rows + new chunk → detached F16 shadow; ring metadata committed after (append_batch, decode.rs:4348) | 36, 23 |
| NoPE prefill fallback | muser_attention_prefill_flash_f16 | S | muse_reference.metal:1409 | encode_attention_prefill_f32 attn.rs:830 (bind :861); decode.rs:4359 | q chunk + current K,V + cache planes → attention chunk (one threadgroup per (head, token)) | 36 |
Note the last row: the Rust wrapper is named
encode_attention_prefill_f32, but at the pin it binds
muser_attention_prefill_flash_f16 (attn.rs:861). A sibling kernel
muser_attention_prefill_f32 (muse_reference.metal:1326) exists in the
registry but is not dispatched by this route (see B.8, conflict 3).
B.5 NVFP4 lane kernels (native 4-bit weights)
All in shaders/nvfp4.metal (Muser-authored); the W4A4 integer
contraction and its two scalar epilogue multiplies compile in the
no-fast-math library (X) so they match the ModelOpt/MLX integer
order (qkv.rs:203-205, Ch 7). Dispatch: encode_nvfp4_matmul
(qkv.rs:128) picks by activation scale presence and column count.
| Kernel | Src | Shader (file:line) | Role | Ch |
|---|---|---|---|---|
muser_nvfp4_matvec_c{1,2,4,8,16} | X | nvfp4.metal:226 | plain dequantizing NVFP4 matvec; width-1 is the decode kernel, wider calls cover DFlash verification and bounded prefill | 7 |
muser_nvfp4_a16_q8_matvec | X | nvfp4.metal:622 | weight-only W4A16 route (no input_scale_inv): activations dynamically quantized to Q8 per 16-block, n_in % 256 | 7 |
muser_nvfp4_w4a4_matvec_c{1,2,4,8,16} | X | nvfp4.metal:312 | W4A4 integer-dot matvec family (weight-stationary 2/4-column specializations) | 7 |
muser_nvfp4_w4a4_m16_n32 | X | nvfp4.metal:337 | 16-column weight-stationary tile, N=32 output rows | 7, 36 |
muser_nvfp4_w4a4_quantize_m16 + muser_nvfp4_w4a4_prequant_m16_n32 | X | nvfp4.metal:468 + :504 | exact two-pass M=16 route: quantize activations once, then tile — the prefill/verify pair (disabled by MUSER_NO_M16_N32) | 36 |
muser_f16_matvec_c{1,2,4,8,16} | X | nvfp4.metal:738 | F16 weights on the NVFP4 lane, incl. the unquantized LM head | 7 |
muser_embedding_f16 | X | nvfp4.metal:755 | F16 embedding table lookup (row 1’s F16 branch) | 7 |
muser_nvfp4_dequant_fixture | X | nvfp4.metal:775 | test fixture: bit-exact dequant of every finite E4M3FN scale | 7 |
B.6 DFlash draft kernels (speculative lane)
The five-layer draft runs its own graph in metal/dflash.rs (Ch 8).
Prepared-greedy layer loop metal/dflash.rs:1061-1180 (per layer ×5):
| Stage | Kernel | Src | Shader (file:line) | Rust dispatch (file:line) | Ch |
|---|---|---|---|---|---|
| Input norm | rms_norm_batch (ggml rms_norm when present) | L / S | rmsnorm_batch_tail.metal:1 | encode_rms_norm_mul norm.rs:244; metal/dflash.rs:1063 | 8 |
| q/k/v projections | dense f32 matmul_f32_batch_tiled (batch ≥ 4) / matmul_f32_batch; kquant sidecar → the B.4 batch stack incl. m16_*_n32 blocks | S | batch_f32_support.metal:45 / :7 | encode_projection metal/dflash.rs:382 → encode_dense_f32_batch encode.rs:443 / encode_quantized_matmul qkv.rs:408 | 8 |
| QK-norm (draft is Qwen-style: real weights, not folded scales) | rms_norm_per_head | S | ferrite/rms_norm_per_head.metal:15 | encode_rms_norm_per_head encode.rs:589 (bind :605); metal/dflash.rs:1085-1101 | 8 |
| RoPE — NeoX pairing, not the target’s interleaved pairs | rope_batch_cached | S | ferrite/rope.metal:566 | encode_rope_neox_batch_cached encode.rs:638 (bind :674); metal/dflash.rs:1107-1114 | 8 |
| Dual-context attention (64-row sink + sliding window) | dflash_dual_attention_f32 | S | ferrite/attention_dflash_dual.metal:15 | encode_dflash_dual_attention encode.rs:690; metal/dflash.rs:1119 | 8 |
| o_proj → fused residual+norm | fused_residual_rms_norm_batch | S | rmsnorm_batch_tail.metal:72 | encode_fused_residual_norm encode.rs:554 (bind :578); metal/dflash.rs:1143 | 8 |
| gate/up projections → SwiGLU | silu_hadamard_batch | S | batch_ffn_activation_tail.metal:14 | encode_silu_hadamard_batch encode.rs:482; metal/dflash.rs:1165 | 8 |
| down projection → residual add | residual_add_batch | S | batch_f32_support.metal:155 | encode_residual_add_batch encode.rs:500; metal/dflash.rs:1178 | 8 |
| Final norm | rms_norm_batch_inplace | S | rmsnorm_batch_tail.metal:42 | encode_rms_norm_inplace encode.rs:525 (bind :531); metal/dflash.rs:1185 | 8 |
| Capture pack (target hidden states → draft input) | pack_dflash_layer_major_f32; copy_f32_buffer | S | copy_f32_buffer.metal:18; :3 | encode_pack_dflash_layer_major encode.rs:769; metal/dflash.rs:744 | 8 |
| Verify side (target, 16-row blocks) | m16_q{4,5,6}k_n32 (B.4) — the L-series tile that took the 16-row verify matmul from ~148 to ~83 ms/cycle | S | batch_m16_n32.metal:59/163/266 | encode_quantized_matmul qkv.rs:556-579 via the mirror-SD suffix begin_dflash_verify_suffix decode.rs:3298 | 33 |
| Verify side, opt-in multi-column matvec | muser_matvec_multicol_{q4k,q5k,q6k}_c{1,2,4} (macro) | S | ferrite/matvec_multicol.metal:400 | MultiColPipelines multicol.rs:84-103; gate MUSER_MULTI_COL_VERIFY multicol.rs:70-82; also the decode-group route encode_quantized_decode_group qkv.rs:234 | 33 |
B.7 Strict cross-vendor kernels (X, MUSER_CROSS_VENDOR_QK)
For reference: muser_cross_vendor_{q4k,q5k,q6k} (projections,
muse_reference.metal:73/146/221), muser_cross_vendor_rms_per_head
(:301), _rms_unweighted (:332) + _mul_weight (:358),
_swiglu (:414), _scale (:433), _tanh (:442), _rope
(:457) / _rope_neox (:497), _attention_decode (:555),
_attention_prefill (:617), _sigmoid_gate (:680),
_dual_norm_residual (:690), _residual_add (:724) — all in
muse_reference.metal, all compiled fast-math OFF (context.rs:111-121).
These replace their S-tagged serving counterparts row-by-row when the
flag is set (e.g. decode.rs:5645, norm.rs:208-225, gate.rs:14).
B.8 Conflicts found while building this table
Where the research map, a chapter, and the pinned tree disagreed, the tree won; the disagreements:
- QK-norm kernel (row 5). The research map lists
rms_norm_per_headas the decode-path QK-norm kernel. At the pin, the target route (encode_qk_norm,norm.rs:286) delegates to the same ggml rms-norm path as row 2 (or the cross-vendor decomposition);rms_norm_per_headlives in the registry (encode.rs:58) and is the DFlash QK-norm kernel (encode.rs:589). Ch 14 already words this correctly (“exists in the pipeline registry”); the table records the tree’s behavior. - Sigmoid gate (row 8). Ch 17 quotes
muse_reference.metal:680, which at the pin ismuser_cross_vendor_sigmoid_gate(the strict variant). The live serving kernel issigmoid_gate_inplace(ferrite/sigmoid_gate.metal:7, dispatchedgate.rs:17); the cross-vendor kernel is the X mirror of the same math. Both are recorded. - NoPE prefill fallback (B.4 last row). The research map says the
fallback dispatches
muser_attention_prefill_f32(muse_reference.metal:1326). At the pin the wrapperencode_attention_prefill_f32bindsmuser_attention_prefill_flash_f16(attn.rs:861,muse_reference.metal:1409);muser_attention_prefill_f32is in the 66-name registry (encode.rs:42) but has no dispatch on this route. - Chapter in-body line tags. Several chapters cite line numbers
inside a kernel body rather than its
kernel voidline (Ch 11muse_reference.metal:973for a kernel that starts:961; Ch 15:1224vs kernel start:1203; Ch 16flash_attn_decode_vec_contiguous_f16.metal:519vs:494). All resolve within the quoted kernel; this table cites thekernel voidlines.
What comes next: the lane matrix and every MUSER_* flag an operator
can actually meet — Appendix C.
Appendix C — Lanes, flags, and environment
status: draft · path: Muse Glimmer, pinned Muser tree
Two tables: the shipped lane matrix (what each weight lane is for, with its evidence tags), and the curated
MUSER_*environment surface — the flags a reader operating or qualifying the engine can actually meet. Every meaning below was read from the code that consumes the flag, at pinned commit6d0807da(see PINNED.md); the raw grep inventory holds ~84 names, most of them test/bench fixture plumbing that this table deliberately omits.
C.1 The lane matrix
The shipped matrix at [docs/muser-architecture.md] (§ “Lanes”), with the
measured cells from the campaign ledger. Ratios are llama ÷ muser
(>1.0 means muser wins); every number keeps its scope tag.
| Lane | Prefill | Decode | Speculative | Intended use |
|---|---|---|---|---|
Native NVFP4 (muser.weight_precision=nvfp4, loader.rs:73-90) | GX10 tensor-core FP4 via disaggregated Handoff V2; Mac-local batch graph as fallback (prefill.rs:10-12) | Mac NVFP4 weights, FP16 KV: 35.491 tok/s vs kquant 35.440 — parity within noise (+0.1444 %), never claimed faster [claims #11] | Rejected, fail-closed (Fallback B): native W4A4 batched verify ran 6.805 tok/s against the 107.9 bar [nvfp4-fast-lane-evidence; ledger F-series] | Fast product lane |
kquant / reference (q4_k_xl, the release artifact) | Mac batch prefill graph (Appendix B.4) | 35.440 tok/s (CV 0.037 %), the control cell [ledger P1.3] | 107.9 tok/s — the pre-window-fix five-rep verdict (107.9136, ratio 1.3273) that survives as the kquant spec bar; the current fixed-window synthetic restatement is 1.23692 @2,048 / 1.20323 @16,384 / 1.19616 @32,768, five exact reps each [ledger L2; claims #15] | Speculative serving + the explicit reference lock |
Exact NVFP4 (MUSER_NVFP4_EXACT=1, producer-side Python) | Integer-dot verification producer on the GX10 | Mac NVFP4 weights (same decode) | Verification only | Deterministic anchor for bounded-logit policies [docs/muser-architecture.md §Lanes; Ch 32] |
Two boundaries worth restating (Ch 32–33):
MUSER_NVFP4_EXACTdoes not exist in Rust. It is read only by the producer-side Python on the node —resident_producer.py:32-36maps it to the closedexact/nativeproducer lane (values other than0/1abort), and the native benchmark refuses=1outright because importing the exact modules would invalidate the native-path claim (benchmark_native_prefill.py:99-102). The Mac-side counterpart is the receiver config enumNvfp4ProducerMode(muser-cluster/src/config.rs), not an env flag.- Decode parity is a paired five-rep measurement on one cell; the
unquantized F16 LM head (~3.46 ms/token vs kquant 1.75) is why the
native lane’s edge is only +0.1444 %
[claims #11].
C.2 The curated MUSER_* flag table
Grouped by subsystem. “Default” is what the code does when the flag is absent, where that is evident from the consumer. Unless noted, a flag’s presence (any value) enables it.
Engine and lane selection (Rust, muser-engine)
| Flag | Meaning (from the consuming code) | Default | Chapter |
|---|---|---|---|
MUSER_GGML_METALLIB | Path to the pinned llama.cpp .metallib; loads the third kernel source (context.rs:122-131) and enables the ggml matvec/matmul/norm/rope/unary and flash_attn_ext PSOs. Q6_K tensors refuse to load without it (decode.rs:114) and the llama attention routes expect it fail-closed (attn.rs:493) | unset — llama-pinned kernels unavailable; engine runs fallback kernels where they exist | 4, 13, 16 |
MUSER_GGML_METALLIB_RECEIPT | Provenance receipt path for the metallib, consumed by node qualification tooling when no explicit --ggml-metallib-receipt was given (muser-server/src/node/mod.rs:83) | unset | 4, 38 |
MUSER_CROSS_VENDOR_QK | Switches every op in the token graph to the strict-f32 cross-vendor kernels (fast-math OFF library): projections (qkv.rs:311), norms and fused tails decompose with barriers at each model-dtype boundary (norm.rs:208-225), attention (decode.rs:5645), gate (gate.rs:14), softcap (lmhead.rs:197). The CUDA-parity arithmetic ABI lane | unset — serving fast-math kernels | 29, 32 |
MUSER_CROSS_VENDOR_ROPE_CACHE | Path to a retained RoPE frequency-table file: must be a regular file (no symlinks) of exactly context_length × head_dim × 4 bytes, else fail-closed (decode.rs:1217-1248); also routes RoPE through the cross-vendor kernel (rope.rs:62-64) | unset — in-memory cached frequencies | 32 |
MUSER_CROSS_VENDOR_ROPE_BYPASS | Skips the RoPE dispatch entirely in cross-vendor comparisons (rope.rs:65-67) | unset — RoPE runs | 14, 32 |
MUSER_FERRITE_FFN_GATE_UP | Uses the fused Ferrite ffn_q4k_gate_up_silu_4r2s FFN kernel — only when both gate and up tensors are Q4_K, which the release artifact’s are (decode.rs:5819-5836; gate read once decode.rs:1334) | unset — exact two-matvec + muser_silu_mul_inplace control | 18 |
MUSER_NO_FUSED_PREFILL_DUAL_NORM | Diagnostic control: splits the fused dual-eps norm tails into their separate kernels (decode.rs:1330-1331) | unset — fused tails on | 12, 35 |
MUSER_SERIAL_PREFILL_DISPATCH | Encodes the prefill graph serially (non-concurrent dispatch type) (decode.rs:1332-1333) | unset — concurrent dispatch | 35, 36 |
MUSER_NO_LLAMA_FA_PREFILL | Forces the local FlashAttention-2 route over the llama-pinned prefill kernel: the explicit-disable input of llama_fa_prefill_route_available (decode.rs:56-63, read :1481) | unset — llama pinned prefill kernel eligible at ≥ 20 queries | 36 |
MUSER_MULTI_COL_VERIFY | Default-off multi-column verify matvec: =1 admits only dtypes whose multi-column output is bitwise identical to the per-token matvec (Q4_K, Q5_K); =all adds Q6_K (agrees to a few ULP, not bitwise) (multicol.rs:49-82) | unset — one full matvec per token per projection | 33 |
MUSER_NO_M16_N32 | Disables the M16 weight-stationary tiles: the kquant 16-row blocks (qkv.rs:556-579) and the NVFP4 two-pass prequant M16 route (decode.rs:5955-5958, qkv.rs:163-168) | unset — M16 tiles eligible | 33, 36 |
Profiling and diagnostics
| Flag | Meaning | Default | Chapter |
|---|---|---|---|
MUSER_METAL_PHASE_PROFILE | Runs the single-token graph through a PhaseProfiler (one command buffer + wait per closure) and prints the labeled per-phase report (decode.rs:5440-5447; also the one-token branch :2133). The instrument behind the +196-closure dispatch-gap accounting — closures, not raw Metal dispatches | unset | 35 |
MUSER_METAL_BATCH_PHASE_PROFILE | Same profiler for the batch (prefill/decode-group) graphs (decode.rs:2881, :3184) and the muser-metal-phase-diagnostic binary (muser-bench/src/metal_phase.rs:138) | unset | 35, 36 |
MUSER_STREAM_DECODE_PROFILE | =1 installs streamed-decode diagnostics retrievable via take_stream_decode_diagnostics (decode.rs:31-35) | unset | 10 |
MUSER_METAL_LIVE_TRACE | =1 live-trace mode in muser-metal-phase-diagnostic; must be absent or exactly 1, and forbids the isolated phase profilers (metal_phase.rs:132-142) | unset | 35 |
MUSER_METAL_CAPTURE_PAUSE_MS + MUSER_METAL_CAPTURE_READY_FILE | GPU frame-capture rendezvous in the phase diagnostic: an O_EXCL ready file plus a pause (both or neither; requires live trace) (metal_phase.rs:364-382) | unset | 35 |
MUSER_TTFT_CAPTURE_READY_FILE + MUSER_TTFT_CAPTURE_PAUSE_MS | Server-TTFT capture rendezvous in scripts/bench_server_ttft.py:197-230: creates an O_EXCL muser.server-ttft-capture-ready.v1 JSON file, fsyncs it, then pauses 1,000–30,000 ms so an external capture can attach before the measured request | unset | 31, 38 |
DFlash (speculative lane)
| Flag | Meaning | Default | Chapter |
|---|---|---|---|
MUSER_DFLASH_VERIFY_LEN | Overrides the default verification length for tuning runs; read exactly once (a mid-process change would falsify earlier reported draft_len) (muser-server/src/openai.rs:56-67) | DFLASH_VERIFY_LEN = 7 (openai.rs:49) — the frozen serving length | 33 |
MUSER_DFLASH_GATE | =off is the diagnostic kill switch for the acceptance window gate (8-round window, 0.25 floor, warmup 2, re-qualify backoff); any other value keeps the gate on (dflash/spec.rs:120-154) | gate on | 33 |
MUSER_DFLASH_CYCLE_TRACE | =1 populates the per-cycle DFlashCycleTrace (draft/verify/cycle ns, drafted/accepted); never consulted by any route or acceptance decision (spec.rs:95-96, :147) | unset — empty trace | 33, 38 |
MUSER_DFLASH_PRE_DRAFT_IDLE_MS | Diagnostic idle injection before draft rounds; requires MUSER_DFLASH_CYCLE_TRACE=1 (spec.rs:1718-1726) | unset | 33 |
MUSER_DFLASH_MIRROR_OVERLAP | =1 enables the exact mirror-SD overlap route (draft overlapping target verify), only on macOS+Metal and only when the projection backend supports it (spec.rs:443-452) | unset | 33 |
MUSER_DFLASH_SAMPLED_REPLAY | =1 keeps the previous sampled-verification route (verify-all, rollback, re-run) for one release; default is the single-pass transactional route (spec.rs:2326-2341) | single-pass | 33 |
MUSER_DFLASH_SINK / MUSER_DFLASH_WINDOW | Diagnostic-only overrides of the draft context sink/window geometry; absent keeps the shipped (GGUF-read) geometry (spec.rs:126-133, :520-537) | shipped geometry — sink 64, window from dflash.attention.sliding_window | 8 |
MUSER_DFLASH_PREPARE_TRACE | Trace prints around prepared-draft execution (spec.rs:1083-1140) | unset | 8 |
MUSER_DFLASH_CAPTURE_FC_PIPELINE | Selects the public-CoreML FC-slice capture pipeline inside the staged target verifier — the previously invisible v8 overlap cost (spec.rs:456-458) | unset | 33 |
GX10 / wire (producer-side, on the node)
These are Python-side flags in scripts/gx10/; the Mac Rust code only
measures what they configure.
| Flag | Meaning | Default | Chapter |
|---|---|---|---|
MUSER_NVFP4_EXACT | Producer-side Python only (§C.1): 0/1 selects the closed native/exact producer lane; any other value aborts (resident_producer.py:32-36); the native benchmark refuses =1 (benchmark_native_prefill.py:99-102) | 0 — native | 28, 32 |
MUSER_GX10_PACING_BYTES_PER_SECOND | Overrides the handoff payload pacing ceiling without a redeploy; a value the kernel refuses still fails closed in configure_linux_pacing (scripts/gx10/llamacpp/muser_v2_send.py:55-76) | 1_000_000_000 B/s (8.0 Gbps — ~15 % under the 9.4 Gbps raw line) | 31 |
MUSER_GX10_WIRE_TRACE | Gates per-segment wire telemetry; unset, empty, or 0 disables (muser_v2_send.py:956-960) | off | 31 |
MUSER_VLLM_WATCHDOG_SECONDS | Watchdog timeout for the resident vLLM producer container (resident_producer.py:24) | 900 s | 28 |
MUSER_DFLASH_JOBS_FIFO | FIFO path over which the resident producer hands DFlash capture jobs to muser_vllm (resident_producer.py:484; consumer muser_vllm/dflash_capture.py:352) | set by the producer runtime | 28, 33 |
MUSER_ACCELERATOR_LEASE_WAIT_SECONDS | Bounded wait (clamped 0–60 s) for the node’s accelerator lease before the producer fails (resident_producer.py:40-44) | 0 — no wait | 28, 38 |
Qualification and bench discipline
| Flag | Meaning | Default | Chapter |
|---|---|---|---|
MUSER_ACCELERATOR_LEASE | =1 is the proof that the process is a child of accelerator_safe.py; fail-closed bench executors refuse to run GPU work without it (muser-bench/src/remote.rs:371, kvpack.rs:104) | unset — execution refused | 38 |
MUSER_REMOTE_QUALIFY | Path override for the qualification binary the node tooling drives (muser-server/src/node/mod.rs:135-139) | <repo>/target/release/muser-remote-qualify | 30, 38 |
MUSER_REMOTE_QUALIFY_SERIAL | Runs the remote qualification reps serially instead of interleaved (remote.rs:1666, :2534) | unset — interleaved | 38 |
MUSER_REMOTE_CACHE_DIFF | Emits a full KV-plane diff when a qualification cell diverges (remote.rs:590, :1624) | unset | 32 |
MUSER_REMOTE_FIRST_DIVERGENCE | Enables the first-divergence hunt (the layer-0 ladder chase of the wizard story; remote.rs:319, :1921) | unset | 32 |
MUSER_REMOTE_CACHE_PROBE | Layer-0/token-one KV probe used on divergence (remote.rs:1852) | unset | 32 |
MUSER_CUDA_METAL_COMPAT_STRICT (with the MUSER_CUDA_CPU_ORDER_* family) | Comparator-side: lives in the patched llama.cpp build (scripts/gx10/llamacpp/muser_cuda_metal_compat.patch), where it forces llama’s CUDA/CPU-order arithmetic variants for cross-vendor parity comparisons — not read by Muser’s own Rust | unset in the engine | 29, 32 |
Server runtime
| Flag | Meaning | Default | Chapter |
|---|---|---|---|
MUSER_HOME | The server’s home root: default model path (model.rs:129), TLS material root (tls.rs:25), session store layout (session_store.rs:157), dashboard/static root (axum_httpd.rs:1362) | falls back to $HOME-relative paths | 37 |
MUSER_HOST / MUSER_PORT | Bind host and port, wired through clap’s env on the server CLI (cli.rs:226-232, :407-411) | port 4949 | 37 |
Omitted, and why
The raw inventory (_research/env-flags-raw.md) holds ~84 names; the
remainder are deliberately left out of this table:
MUSER_CACHE_ABIis not an env flag at the pin — it is the const cache-ABI identity string"muser-muse-glimmer-f16-logits-v2"inmuser-kvpack/src/layout.rs:18(the research map’s §14 row for it is wrong; the string simply matched theMUSER_*grep).MUSER_MC_NSG/MUSER_MC_NR0are shader-side constants that must match the dispatch shape, documented atmulticol.rs:41-44— not runtime flags.MUSER_MODEL/MUSER_MODEL_SHA256gate the release-real-model test identity (chat_template.rs:241-246) — test-only.MUSER_COMPARATOR_*,MUSER_BUILD_*,MUSER_NVFP4_QKV_*,MUSER_LLAMA_*(stage/fixture vars),MUSER_GX10_*stage-dir vars — fixture plumbing for the parity comparators, not operator surface.MUSER_ACCELERATOR_LEASE_FD,MUSER_ACCELERATOR_CELL,MUSER_DFLASH_ATTENTION_F32,MUSER_DFLASH_ROPE_NCO,MUSER_TTFT_CAPTURE_REUSE_PROMPT,MUSER_DEBUG_STOP_AFTER_LAYER— single-use diagnostic/test hooks whose consumers I could not confidently explain from code in the time budget; omitted rather than guessed.
What comes next: the master bibliography — Appendix D.
Appendix D — Bibliography
Master list, merged and deduplicated from every chapter’s
## Referencessection (mirrored per-chapter so a reader need not hunt the back of the book). Tag conventions are defined in the README.
Muser source (pinned revision)
Quoted and cited throughout. Paths relative to the Muser repository root at pin 6d0807da (see PINNED.md).
[crates/muser-bench/src/composite_dflash.rs:246-251]— the[crates/muser-bench/src/m16.rs:137-226]— theSHAPEStable: dtypes,[crates/muser-bench/src/m16.rs:202-225]— draft shapes (fc 33280→6656,[crates/muser-bench/src/main.rs:304-341]—route_identity: resolved[crates/muser-bench/src/main.rs:305-346]—route_identity: the[crates/muser-bench/src/remote.rs:3-10],[:32],[:36]— the remote[crates/muser-bench/src/remote.rs:3-8, 32-33, 690-694]— the[crates/muser-bench/src/remote.rs:3-8, :33]— the 256-token +[crates/muser-bench/src/remote.rs:3-8, :33]— the 256-token exact[crates/muser-cluster/src/config.rs:10-18],:128-131—[crates/muser-cluster/src/config.rs:128-131],[crates/muser-cluster/src/config.rs:13-18, 128-132]—[crates/muser-cluster/src/config.rs:41-49, 103-115, 128-131]—[crates/muser-cluster/src/config.rs:44-49]— enrollment-stamped[crates/muser-cluster/src/control.rs:1-13]— control-plane scope and[crates/muser-cluster/src/lib.rs:5-22]— the crate’s one-sentence[crates/muser-cluster/src/lib.rs:9-22]— 1× Mac + 1× GX10 launch[crates/muser-cluster/src/muse_sink.rs]— detached-generation contract[crates/muser-cluster/src/receiver.rs:108-148]— the ledger-volume[crates/muser-cluster/src/receiver.rs:108-150]— ledger-volume gate.[crates/muser-cluster/src/receiver.rs:108-206]—[crates/muser-cluster/src/schedule.rs:19-26, 75-90, 124-129]— NoPE[crates/muser-cluster/src/schedule.rs:84-157]— the span schedule:[crates/muser-cluster/src/security.rs:355-491]—ReplayLedger[crates/muser-cluster/src/security.rs]— ALPN constant (21), TLS 1.3[crates/muser-cluster/src/transport.rs:15-16, 19-38, 83-95]— magic,[crates/muser-cluster/src/verifier_v2.rs]— the unwired carried-frontier[crates/muser-engine/src/config.rs:169-181]— fail-closed GGUF metadata[crates/muser-engine/src/config.rs:286]—assert_tensor_shapes: every- `[crates/muser-engine/src/decode.rs:1020-1026, 1331-1333, 4920-4937,
[crates/muser-engine/src/decode.rs:1020-1030]—AcceleratorScheduler,[crates/muser-engine/src/decode.rs:1195-1206]—load_shared:[crates/muser-engine/src/decode.rs:136-139],:1209— Metal-path[crates/muser-engine/src/decode.rs:213-226]—[crates/muser-engine/src/decode.rs:3293-3369, 3635-3666]—[crates/muser-engine/src/decode.rs:41-46]— llama’s nwg=32/nsg-growth[crates/muser-engine/src/decode.rs:53]—PREFILL_BATCH_TOKENS = 512,[crates/muser-engine/src/decode.rs:5432-5463]—forward_token: the[crates/muser-engine/src/decode.rs:6044-6091]— projection routing;[crates/muser-engine/src/decode.rs:6124-6279]—EncodeTarget,[crates/muser-engine/src/decode.rs:954-984]—MetalShared: one[crates/muser-engine/src/dflash.rs:1-5]— the module contract (five[crates/muser-engine/src/dflash/config.rs:59-61]—[crates/muser-engine/src/dflash/hidden.rs:44-52]— target-layer-gated[crates/muser-engine/src/dflash/spec.rs:16-98]—DFlashSpecStats[crates/muser-engine/src/dflash/weights.rs:35-66]— the dual loader[crates/muser-engine/src/gguf/types.rs:33-39]—NVFP4_E2M1/[crates/muser-engine/src/gguf/types.rs:9-127]—GgmlType, block[crates/muser-engine/src/lib.rs:14]— “The pinned target artifact is[crates/muser-engine/src/lib.rs:14]— the pinned artifact byte size[crates/muser-engine/src/lib.rs:163-171]— the “no Xcode step,[crates/muser-engine/src/loader.rs:72-91]—weight_precision:[crates/muser-engine/src/loader.rs:72-91]— fail-closed lane pairing.[crates/muser-engine/src/metal/buffer.rs:7-14]—shared_tracked()and[crates/muser-engine/src/metal/context.rs:32-42]—MetalContext[crates/muser-engine/src/metal/context.rs:32-42]—MetalContextwith[crates/muser-engine/src/metal/context.rs:36-39, 49-53, 111-131]— the[crates/muser-engine/src/metal/encode.rs:21-88]— the 66-name[crates/muser-engine/src/metal/encode.rs:278-280]— the pinned[crates/muser-engine/src/metal/encode.rs:500-523]—[crates/muser-engine/src/metal/encode/multicol.rs:192]— a real[crates/muser-engine/src/metal/encode/multicol.rs:192]— the real[crates/muser-engine/src/metal/encode/multicol.rs:90-132, 208-211, 458-464][crates/muser-engine/src/metal/encode/norm.rs:115-116],[crates/muser-engine/src/metal/encode/norm.rs:236-240, 270-277]— the[crates/muser-engine/src/metal/encode/qkv.rs:128-227]—[crates/muser-engine/src/metal/encode/qkv.rs:13],[crates/muser-engine/src/metal/encode/qkv.rs:414-641]—[crates/muser-engine/src/metal/pso_cache.rs:45-49]— the[crates/muser-engine/src/metal/pso_cache.rs:9-50]— the in-process[crates/muser-engine/src/metal/residency.rs:1-107]— the[crates/muser-engine/src/prefill.rs:6-12]— “prefill of T tokens ≈ one[crates/muser-engine/src/quant/blocks.rs:100-103]— the Q5→Q8 transcode[crates/muser-engine/src/quant/k_block.rs:12-49]—dequant_q4_k, this[crates/muser-engine/src/quant/k_block.rs:169-177]—dot_q4_k_f32_llama[crates/muser-engine/src/quant/k_block.rs:51-105]—dequant_q5_kand[crates/muser-engine/src/quant/k_block/q6.rs:15-75]—[crates/muser-engine/src/quant/nvfp4.rs:1-6]— the pinned format[crates/muser-engine/src/sampling.rs:1001-1097]— the MT-pinned[crates/muser-engine/src/sampling.rs:1001-1097]— the source-pinned[crates/muser-engine/src/shaders/ferrite/batch_m16_n32.metal:59-88]—[crates/muser-engine/src/shaders/ferrite/rmsnorm_batch_tail.metal:1-33][crates/muser-engine/src/shaders/ferrite/sigmoid_gate.metal:7-16]and[crates/muser-engine/src/shaders/muse_reference.metal:735-788]—[crates/muser-engine/src/shaders/nvfp4.metal:1-5]— the GPU format doc;[crates/muser-engine/src/shaders/nvfp4.metal:226+]— the[crates/muser-engine/src/weights.rs:163-218]—MuseWeights::open[crates/muser-engine/src/weights.rs:25-27]— companion-tensor suffixes;[crates/muser-engine/src/weights.rs:4-7]— row-contiguity / prefill[crates/muser-engine/tests/muse_golden.rs:97-108]— the pinned-artifact[crates/muser-server/src/chat_template.rs:237-261]— therelease_gguf[crates/muser-server/src/chat_template.rs:237-261]— the Mac-side[crates/muser-server/src/node/artifacts.rs:188-210, 472-488]—[crates/muser-server/src/node/mod.rs:80-83]—MUSER_GGML_METALLIB[crates/muser-server/src/node/registry.rs:29-76]—- `[crates/muser-server/src/node/smoke.rs:43-52, 451-469, 538-569,
[crates/muser-server/src/state.rs:1666-1675]— the native+DFlash[crates/muser-server/src/state.rs:1666-1675],[crates/muser-server/src/state.rs:1667-1678]—[scripts/accelerator_safe.py:190-197, 202-203]— append-only journal,[scripts/accelerator_safe.py]— dry-run default (:35-38), lock path[scripts/compile_llama_metallib.sh]— revision pinning, clean-tree[scripts/gx10/durable_fsync_probe.py:19-22]— the standalone tail[scripts/gx10/llamacpp/muser_v2_send.py:54-76]— the 8 Gbps[scripts/gx10/llamacpp/spark_kv_export.cpp:1-30]— the integer-exact[scripts/gx10/README.md]— diagnostic flow, en0/en1 rule, ~9.4 Gbps[scripts/gx10/README.md]— the five diagnostic tools and the[scripts/gx10/restart_resident_producer.py]— the restart ritual:[scripts/gx10/tcp_probe.py],[scripts/gx10/durable_fsync_probe.py],[scripts/gx10/vllm/benchmark_native_prefill.py:98-102],[scripts/gx10/vllm/benchmark_native_prefill.py:99-102]— the native[scripts/gx10/vllm/benchmark_native_prefill.py:99-102],[scripts/gx10/vllm/muser_native_prefilld.py:2-9]— control-plane[scripts/gx10/vllm/muser_native_prefilld.py:514-577]— producer[scripts/gx10/vllm/muser_vllm/connector.py:184-285]— the CUDA side of[scripts/gx10/vllm/native_onboarding_identity_v1.json:57-71]— the[scripts/gx10/vllm/native_onboarding_identity_v1.json]— the frozen[scripts/gx10/vllm/resident_producer.py]— pinned vLLM commit (21),[scripts/gx10/vllm/supervise_resident_producer.py]— the supervisor:[scripts/qualify_nvfp4_fast.py:307-308]— lease enforcement below the[scripts/qualify_nvfp4_fast.py:333-336]— the qualifier’s matching[third_party/kvpack/crates/kvpack-handoff/src/canonical.rs:8-36]—[third_party/kvpack/crates/kvpack-handoff/src/handoff_v2.rs]—[third_party/kvpack/crates/kvpack-handoff/src/mac.rs]—MacKeyscripts/compile_llama_metallib.sh— builds the pinned metallib.third_party/kvpack/crates/kvpack-handoff/src/handoff_v2.rs:28-91—third_party/kvpack/crates/kvpack-handoff/src/manifest.rs:56-66—third_party/kvpack/provenance.json— schema, upstream commit/tag/tree,third_party/kvpack/README.md— replay semantics (quoted), the
Muser engineering documents
Under docs/ in the Muser repository.
- [docs/decode-dispatch-gap-20260815.md] — closure accounting (common math)
- [docs/decode-dispatch-gap-20260815.md] — common-math closure accounting
- [docs/decode-dispatch-gap-20260815.md] — the 760/564 reconciliation, the
- [docs/decode-dispatch-gap-20260815.md] — the closure-count reconciliation;
- [docs/extraction-manifest.md] —
silu_fastand the shader lineage from - [docs/release-provenance.md:822-823] —
logit_scale=0.196116with [docs/benchmarks.md §1]— the six-depth plain matrix ratios.[docs/benchmarks.md §2]— natural-text wins/losses and the[docs/benchmarks.md §2]— verify-length conventions and the[docs/benchmarks.md]— §1 (35.44/35.49 parity-within-noise),[docs/benchmarks.md]— §Methodology (ratio convention, five-rep/CV,[docs/benchmarks.md]— §Methodology (repetition and floor conventions),[docs/campaign-review-brief-20260820.md],[docs/decode-dispatch-gap-20260815.md §Rejected hybrid postmortem]—[docs/decode-dispatch-gap-20260815.md]— closure-vs-dispatch[docs/decode-dispatch-gap-20260815.md]— instrument label correction;[docs/decode-dispatch-gap-20260815.md]— read in full for this chapter:[docs/decode-dispatch-gap-20260815.md]— the +196 reconciliation (104[docs/decode-dispatch-gap-20260815.md]— the +196-closure[docs/decode-dispatch-gap-20260815.md]— the 39 staging families and the[docs/decode-dispatch-gap-20260815.md]— the 39 staging groups and 52[docs/decode-dispatch-gap-20260815.md]— the 52 publication splits and[docs/decode-dispatch-gap-20260815.md]— the closure families of[docs/decode-dispatch-gap-20260815.md]— the closure reconciliation this[docs/decode-dispatch-gap-20260815.md]— the exactness-contract[docs/decode-dispatch-gap-20260815.md]— §Landed and rejected[docs/decode-dispatch-gap-20260815.md],[docs/disaggregated-prefill-sealing-plan-20260818.md §W1]— the[docs/disaggregated-prefill-sealing-plan-20260818.md]— §2 payload[docs/disaggregated-prefill-sealing-plan-20260818.md]— §2 producer[docs/disaggregated-prefill-sealing-plan-20260818.md]— §4 (KV-size[docs/disaggregated-prefill-sealing-plan-20260818.md]— §4 (W4A4[docs/disaggregated-prefill-sealing-plan-20260818.md]— §W0 (9.40 Gbps,[docs/disaggregated-prefill.md §Correctness]— refusals as passing[docs/disaggregated-prefill.md §What you need]— the[docs/disaggregated-prefill.md]— operating characteristics and the[docs/disaggregated-prefill.md]— roles-not-machines; the two-jobs[docs/documentation-truth-pass-20260815.md]— the audit table;[docs/extraction-manifest.md §Stage 2]— the fifteen byte-for-byte[docs/extraction-manifest.md]— per-file provenance for the[docs/extraction-manifest.md]— the ancestor’s ring-modulus hazard and[docs/goal-parity-ledger-2026-08.md]— the parity gates the pinned[docs/gx10-return-runbook-2026-08.md §2]— the live stale-generation[docs/gx10-return-runbook-2026-08.md]— enrolled lane constants[docs/kv-reuse-frontier-20260820 §1-2, §4]— the crossover and[docs/kvpack-merge-handoff §6]— the ~82 MB SWA / 95.7 % NoPE pacing[docs/kvpack-merge-handoff-20260820.md §3 D1]— the payload[docs/kvpack-merge-handoff-20260820.md §6]— the NoPE-trailing-edge[docs/kvpack-merge-handoff-20260820.md]— §3 D1 (payload 1,823,184,896[docs/kvpack-merge-handoff-20260820]— §1 the merge ruling, §3 F1[docs/kvpack-merge-handoff.md §3 D2]— the 2026-08-20 audit correcting[docs/kvpack.md]— the reuse ladder (Table 25.1), miss-control[docs/kvpack.md]— the stance (“exactness is the product”), the three[docs/launch-claims-review-20260824.md]— the pre-decision review[docs/launch-claims.md]— #2 (local matrix), #6 (the disaggregated[docs/launch-claims.md]— #8 (topology), #13 (producer self-recovery[docs/launch-claims.md]— #9 (attempt-31 exactness and wire rates).[docs/launch-claims.md]— #9 (wizard attempts 9 and 31, their rates[docs/launch-claims.md]— the register; preamble (OPERATOR REVIEW[docs/memory-footprint.md]— 1.827 GB/slot and 7.306 GB/four-slot KV at[docs/memory-footprint.md]— 96 GB M3 Ultra host, KV formula and the[docs/memory-footprint.md]— artifact sizes, KV formula, the[docs/memory-footprint.md]— batch workspace widths; chunk arithmetic.[docs/memory-footprint.md]— DFlash GGUF 1,631,205,312 B.[docs/memory-footprint.md]— KV formula, the 96 GB budget table, the[docs/memory-footprint.md]— the 1,024 B row formula, the one-slot /[docs/memory-footprint.md]— the 1,024 B/row formula and the[docs/memory-footprint.md]— the 1,024 B/row KV constant §16.7[docs/metrics-schema.md]— honesty-tag legend;nodes[]mock;[docs/muser-architecture.md §Context and sessions]— the five-sentence[docs/muser-architecture.md §Durable and remote KV]— enrollment key[docs/muser-architecture.md §Durable and remote KV]— the three-line[docs/muser-architecture.md §Durable and remote KV]— v0.1 topology,- `[docs/muser-architecture.md §Product boundary, §Context and sessions,
[docs/muser-architecture.md §Slots and scheduling]— the one-scheduler[docs/muser-architecture.md]— the lane matrix (exact-flag row:[docs/muser-architecture.md]— the lane matrix; “DFlash drafts are[docs/muser-architecture.md]— §Context and sessions (shift semantics[docs/muser-architecture.md]— §Slots and scheduling (shared weights,[docs/nvfp4-distributed-speculative-frontier-20260818.md]— the[docs/nvfp4-fast-lane-evidence §Measured product numbers]— the[docs/nvfp4-fast-lane-evidence-20260817.md §Measured product numbers]—[docs/nvfp4-fast-lane-evidence-20260817.md]— Fallback B disposition,[docs/nvfp4-fast-lane-evidence-20260817.md]— installed payload[docs/nvfp4-fast-lane-evidence-20260817.md]— the 6.805 tok/s[docs/nvfp4-fast-lane-evidence-20260817.md]— the 6.805 tok/s native[docs/nvfp4-fast-lane-evidence-20260817.md]— §Product route[docs/one-button-onboarding.md §Starting the production consumer]—[docs/one-button-onboarding.md]— the six stages, the security model,[docs/private-release.md]— the fifteen mandatory lanes (thevision[docs/private-release.md]— the freeze→run→readiness→seal flow[docs/redteam-review-campaign-brief-20260820.md §Verdict]— no[docs/release-provenance.md]— v0.1 scope override; §ANE v9crates/muser-kvpack/src/economics.rs+[docs/kvpack-economics.md]—
Campaign ledger and claims register
The measured-record instruments: docs/goal-parity-ledger-2026-08.md and docs/launch-claims.md.
- [ledger Stage B close-out] —
docs/goal-parity-ledger-2026-08.md, L0 - [ledger wizard attempt 9 / nvfp4-fast-lane-evidence §Determinism] — the
- [nvfp4-fast-lane-evidence-20260817] / [ledger F-series] — the native
[claims #10]— native NVFP4 quality gates and the published docs@65,536[claims #11]—docs/launch-claims.mdrow 11: plain Mac NVFP4 35.491[claims #11]— kquant 35.440 / NVFP4 35.491 tok/s scope (§6.1, §6.9).[claims #11]— the scope language carried in §25.3–25.4 (original[claims #11],[claims #12],[claims #15]—[docs/launch-claims.md]:[claims #12]— both delta cells, the admission rule, and the[claims #15],[claims #3],[claims #16],[claims #4]—[claims #15],[claims #6]—docs/launch-claims.md: speculative restatement scope; TTFT disaggregation scope.[claims #2],[claims #6],[claims #9],[claims #11],[claims #14],[claims #3],[claims #4],[claims #14],[claims #15],[claims #4],[claims #10],[claims #11]—docs/launch-claims.md:[claims #4],[claims #5],[claims #6],[claims #8],[claims #10],[claims #6],[claims #15],[claims #16]— TTFT disaggregation scope;[claims #6],[claims #9],[claims #13]—docs/launch-claims.md:[claims #9],[claims #10],[claims #11]—docs/launch-claims.md:[ledger "EEE A/B at 130815"]— the 570.122 s local deep-prefill mean[ledger "Kvpack ladder stage-5 isolated-depth verdict"]— Table 25.3’s[ledger "Kvpack ladder stage-6 delta-witness verdict"]+[ledger e426ec0 postmortem]— the retracted 65,536 `outputs_match:[ledger L0]— same ledger, “microbenchmark-first apparatus and the[ledger N2]— receiver phases constant ~0.2 s (the non-wire cost).[ledger P1.3]—docs/goal-parity-ledger-2026-08.md, kquant/NVFP4[ledger P4]— the shallow warm-hit five-sample cell, the 723.90×[ledger T-series "Delta-only prefill (W3)"]+[ledger §"EEE link ruling — operator decision (2026-08-20)"],[ledger §"Final GX10 campaign attempt 4"],[ledger §2b, 2026-08-24]— the arithmetic-ABI chase, attempts 10–31,[ledger §2b]— the wizard attempts 10–31 arithmetic-ABI chase (one[ledger §L2 Stage B verdict],[ledger §Stage B L0/L1],[ledger §L2 Stage B verdict],[ledger §Stage B L1],[ledger §N2],[ledger §N5],[ledger §P4]— the EEE collapse[ledger §P1.3],[ledger §2b 2026-08-24],[ledger §P1.3],[ledger §P1.4],[ledger §F-series remediation],[ledger §Stage B L0],[ledger §Stage B L1]—[ledger §T-series]—docs/goal-parity-ledger-2026-08.md: T0 raw[ledger …]— “eight-handoff deep soak” (2026-08-23); attempt-4[ledger …]— “Phase 4 disaggregated GX10→Mac context matrix” (the[ledger …]—docs/goal-parity-ledger-2026-08.md: “Stage A entry gate”[ledger …]— F-series remediation context and Fallback A no-go;[ledger …]— preamble; “Synthetic spec matrix deep-cell restatement”[ledger, "Phase 2 non-spec context matrix"]/[ledger](docs/goal-parity-ledger-2026-08.md) — Arc 1 (0.781× → the[ledger](docs/goal-parity-ledger-2026-08.md) — the campaign matrices
Retained receipts (append-only evidence volume)
Under muser-receipt://; append-only, never written by book work.
- [receipt
muser-receipt://pinned-token-parity-20260814-v3/], [receipt final-campaign-20260823/attempt-4/soak/run-attempt-3/SOAK_VERDICT.json]- `[receipt kvpack-ladder-20260820/attempt-10-…-stage6-delta/
[receipt phase4-disagg-20260820/130815-g900091/]— the[receipt phase4-disagg-20260820/130815-g900091/]— the 1,823,184,896 B[receipt phase4-disagg-20260820/130815-g900091/out-p4/f-p4-text-g900091-client.json][receipt wizard-validation-20260823/attempt-9-native-live-20260824T051305Z/validation-summary.json][receipt …]— undermuser-receipt://:- `[receipts kvpack-ladder-20260820/attempt-9-20260822T074100Z-stage5-warmhit/
The ancestor book (pedagogical lineage)
The Ferrite inference book — structural lineage only; its numbers are ancestor context, never Muser results.
- [ferrite-book Ch 16] — the ancestor’s residual-fused o_proj matvec; the
- [ferrite-book Ch 17] — the ancestor’s SwiGLU chapter; the fused-vs-control
- [ferrite-book Ch 18] — the ancestor’s Q4_K_M mix table and the
- [ferrite-book Ch 19] — the ancestor’s LM-head chapter: “17× more rows”,
- [ferrite-book Ch 20] — the ancestor’s argmax chapter: the two-phase
- [ferrite-book Ch 5] — the ancestor Q4_K chapter; Muser’s
dequant_q4_k - [ferrite-book Ch 5] — the ancestor’s Q4_K chapter, whose
- [ferrite-book Ch 8] — the ancestor spine chapter (pedagogical lineage; its Figure 8.1 fusion list is Ferrite’s, not Muser’s).
[ferrite-book Ch 10]— the ancestor’s RMSNorm chapter (worked example[ferrite-book Ch 11, Ch 12]— the ancestor’s v4/4sg GEMV chapters[ferrite-book Ch 13]— the ancestor’s RoPE chapter (permutation-[ferrite-book Ch 14]— the ancestor’s paged cache, kept as contrast[ferrite-book Ch 14]— the ancestor’s paged-Q8 cache (the 2×2 device[ferrite-book Ch 14]— the ancestor’s paged-Q8 cache: the dual-derivation[ferrite-book Ch 15]— the ancestor’s attention chapter (the[ferrite-book Ch 1]— the ancestor’s “one token, costed by hand” device[ferrite-book Ch 21],[ferrite-book Ch 22], `[ferrite-book[ferrite-book Ch 21],[ferrite-book Ch 23 §23.7]— lineage: the[ferrite-book Ch 23]— the roofline-flip device this chapter ports; its[ferrite-book Ch 24]— the ancestor’s measurement chapter: noise[ferrite-book Ch 25]— the ancestor’s falsification-ledger closer this[ferrite-book Ch 2]— the ancestor’s tape-recorder analogy and[ferrite-book Ch 3]— the ancestor’s unified-memory chapter: the[ferrite-book Ch 4]— the ancestor’s compilation chapter: the[ferrite-book Ch 9]— the ancestor’s CPU-lookup embedding and the
Vendor documentation
[CUDA §streams],[CUDA §thread-hierarchy]— NVIDIA CUDA C++ Programming Guide: streams and the thread hierarchy (the CUDA-vs-Metal contrasts of Ch 29).[Metal-PG]— Apple, Metal Programming Guide: “Command Buffers,” “Functions,” “Pipeline States,” “Resource Objects,” andmaxThreadsPerThreadgroup/ threadgroup-memory limits (quoted across Part I and Ch 4).[Metal-SS]— Apple, Metal Shading Language Specification: “Address Spaces,” “Function Constants,” “SIMD-group Functions” (thesimdgroupoperations every kernel chapter relies on).[Metal-SS §simd-group-functions]— same specification, the SIMD-group functions section (cited where reduction order matters).
Papers
[arxiv:1512.03385]— He et al., Deep Residual Learning (the residual stream’s origin).[arxiv:1706.03762]— Vaswani et al., Attention Is All You Need (the transformer this book re-derives from scratch).[arxiv:1710.05941]— Ramachandran et al., Searching for Activation Functions (the SwiGLU lineage).[arxiv:1910.07467]— Zhang & Sennrich, Root Mean Square Layer Normalization (RMSNorm; Ch 12).[arxiv:2002.05202]— Shazeer, GLU Variants Improve Transformer (SwiGLU; Ch 18).[arxiv:2104.09864]— Su et al., RoFormer (the RoPE origin paper; Ch 14).[arxiv:2302.01318]— Chen et al., the independent lossless speculative-decoding statement (Ch 33’s exactness contract).[arxiv:2305.13245]— Ainslie et al., GQA (the 16:1 bandwidth lever; Ch 9 and Ch 16).
Upstream interop targets
(no entries)
PINNED — the exact trees this book quotes
This book is written against pinned revisions. When the trees move, the book does not silently follow; a re-pin is an explicit event.
Muser (the engine this book explains)
- Repository: the muser repository (
<muser-checkout>at pin time) - Pinned commit:
6d0807da975d3628f874df6b36ac9cc2af3723f2(feat(dashboard): live chat pane with token streaming) - Branch:
main - Tree state at pin: clean (working tree == HEAD), so quoting from the working tree is quoting the pin.
- Model target: the pinned Muse Glimmer GGUF (identity validated at
startup: revision, byte size, SHA-256 — see
docs/muser-architecture.md). - Compatibility reference: llama.cpp commit
89e0aa6fd362617d9073e0dafc18e41241521572. - Rules inherited from the Muser repo while working against it: the
release lock is authoritative (this book creates no seals, tags, or
candidates); evidence is read from
muser-receipt://(append-only — never written by book work); files under~/.muser/**/secretsand pki dirs are never read;scripts/accelerator_safe.pygates any accelerator run (book work is read-only, so none are needed).
The ancestor book (pedagogical source)
- Repository: the private ferrite-rs research repository
- Path:
inference-book/ - Identity: “The Inference Book on Apple Metal — How Ferrite runs
Qwen2.5-1.5B, one kernel at a time” (25 chapters + appendices, STYLE
contract,
SUMMARY.md). - Use in this book: structural and pedagogical lineage. Ferrite
measurements (A18 Pro, 8 GB, 45.95 GB/s ceiling, 33.20 tok/s tg128, …)
are ancestor context and are always labeled as such, per the Muser launch
claims register’s
[precedent-7B-ferrite]discipline (docs/launch-claims.mdground rules).
Verification recipe
cd <muser-checkout> && git rev-parse HEADmust print the pinned commit. If it does not, quotes in this book may have drifted — checkgit diff <pin>..HEAD -- <quoted path>before trusting afile:line.- Every
[crates/...:LINE]tag in a chapter must resolve to the quoted content at the pin. - Every number must carry a tag that resolves to a doc, a ledger entry, or a
receipt path under
muser-receipt://.
STYLE — the writing contract for this book
Every chapter-writing agent and every review agent reads this file first. If a chapter violates a rule below, the reviewer flags it and the writer fixes it before the chapter is marked
polished.
0. The single overriding principle
Didactic above all. Imagine the reader has never seen a GPU shader and has
never read a transformer paper. The first time any technical term appears —
softmax, nibble, SIMD group, residual, GQA, f16, Q4_K, matvec,
threadgroup, KV cache, NVFP4, speculative decoding — it is defined
in place with one sentence and (where useful) one tiny diagram or one worked
number, before it is used to explain anything. A term already defined in an
earlier chapter is cross-linked to that chapter and not redefined, but a
one-line reminder in parentheses is encouraged.
1. Chapter status line
Every chapter’s second line (right under the # title) is:
> **status:** stub | draft | polished · **path:** Muse Glimmer, pinned Muser tree
Never delete this line; reviewers flip draft → polished.
2. The standard kernel-chapter skeleton
A chapter about one kernel follows this structure (sections in this order):
- What it computes — the math, in plain words + one formula block.
- Why it exists — the role in the transformer; what breaks if you skip it.
- The matrix operation, explained — first time a matmul/dot/softmax/reduction appears, draw the shapes and show a 2×2 worked example by hand.
- The Metal kernel — quote the
kernel voidsignature and the inner loop from source (withfile:linetags). Explain line by line. - The Rust dispatch — the encode/dispatch wrapper, grid/threadgroup sizes, buffer binding. Quote it.
- The access pattern — what memory is read, in what order, how much. This is where the bandwidth story lives.
- Tradeoffs — required section. At least two of:
- “Why this way and not the obvious alternative” + the measured consequence.
- “It may seem better to do X, but doing X destroys Y” with a citation.
- A dead alternative (rejected/fail-closed) and why it died, if relevant.
- Where the gap lives (kernel chapters only) — how this kernel contributes
to the measured decode-gap story (see
[docs/decode-dispatch-gap-20260815.md]), or explicitly “this kernel is not the gap.” - References — the chapter’s own bibliography (see §5).
Non-kernel chapters (Metal model, quantization, architecture, KV cache, transport, orchestration, measurement) adapt this skeleton but keep sections 1, 2, 4–5, 7, 8 where applicable.
3. Diagram rules
- Mermaid for: flow diagrams, pipeline diagrams, state machines, decision trees, the forward-pass block diagram, the handoff transaction, the compile pipeline.
- ASCII for: byte layouts, memory maps, buffer-binding tables, wire-frame layouts, register/SIMD lane decompositions, anything where character-columns carry meaning.
- Mermaid never carries a byte offset. If a diagram needs
0x00,0x10, offsets, it is ASCII. - Every diagram has a caption (
*Figure N.M: ...*) and is referenced in prose (“see Figure 11.2”). - Prefer annotated diagrams over walls of text. A reader should be able to study the figure and get the gist before reading the paragraph.
4. Code blocks
- Quote real source with
file:linetags, read from the pinned Muser tree at<muser-checkout>(see PINNED.md). Convention:
For// crates/muser-engine/src/shaders/ferrite/matvec_multicol.metal:17 kernel void matvec_q4k_f32_v4(...) { ... }third_party/kvpack, tag with the kvpack-relative path. - Keep quotes tight: the kernel signature + the load + the inner loop + the write-back. Do not dump 200 lines. Link to the file for the rest.
- If you trim lines, insert
// …on its own line and say so: (lines 60–120 elided: scale decoding, see file). - Rust dispatch wrappers: quote the function and the dispatch call. Always state grid and threadgroup size in prose too.
- Never paraphrase code as if it were quoted. If it’s a paraphrase, write it as prose, not in a code fence.
- Never invent a file, function, constant, or number. If you cannot find it in
the tree, either hunt until you do or write
[unverified].
5. Citations and per-chapter bibliography
- Every chapter ends with a
## Referencessection. - Citation tags in prose:
[crates/.../file.rs:LINE]— Muser source (pinned revision).[docs/<file>.md]/[docs/<file>.md §N]— Muser engineering docs.[ledger §N]— the campaign ledgerdocs/goal-parity-ledger-2026-08.md.[claims #N]—docs/launch-claims.mdrow N.[receipt muser-receipt://...]— retained evidence.[scripts/...]— tooling, includingscripts/gx10/.[ferrite-book Ch N]— the ancestor Ferrite book, pedagogical lineage only.[Metal-SS §N],[Metal-PG §…],[CUDA §…]— vendor documentation.[arxiv:XXXX.YYYYY]— arXiv paper.[vLLM …],[llama.cpp …]— upstream interop targets.
- A measurement number is always followed by its tag.
- Claims about why something is the way it is: cite a file, a doc, a ledger
entry, or a receipt. If you cannot, write
[unverified]or rephrase as a question. Do not produce fluent authoritative paragraphs from pattern-matching. (The Muser repo’s epistemic rule; the book inherits it.) - Ferrite-lineage numbers (A18 Pro, Qwen2.5-1.5B, 33.20 tok/s, 45.95 GB/s,
[precedent-7B-ferrite]figures) are ancestor context, never Muser results. If cited, label them as Ferrite-lineage measurements explicitly.
6. The tradeoff discipline
Every kernel chapter’s “Tradeoffs” section must connect to measured reality, not intuition. Acceptable forms:
- “Fusing X into Y changed the dispatch-gap accounting by N groups
[docs/decode-dispatch-gap-20260815.md]; the unfused path cost was M groups/token[file:line].” - “The alternative passed the correctness gate but regressed production by
−N %
[ledger §K].” - “The linear distributed-verifier lane reached 110.59 tok/s only on an
all-accept control; real acceptance collapsed to 9.23–38.07 % and the lane
was rejected
[claims #14].”
Unacceptable: “This is the standard pattern,” “GPU memory works best when…”, “Fusing is generally faster” — without a measurement.
7. Voice
- Second person (“you”) is fine and friendly.
- Short sentences. One idea per paragraph.
- No emojis. No marketing tone. No hype.
- When a concept is subtle, say so: “This is the part that trips people up.”
- When something is genuinely a hack or a wart, name it.
- When something is fail-closed on purpose, explain what failure it prevents and what the operator sees when it trips. Muser’s fail-closed culture is a subject of this book, not an obstacle to it.
8. Numbers
- Throughput/latency numbers always carry their scope and source tag, and carry the campaign’s framing (five-repetition means, synthetic vs natural, notarial vs non-notarial) when the claim depends on it. Where the scope language is long, link to the claims row instead of paraphrasing it loosely.
- Byte sizes and offsets: compute them in the chapter and show the arithmetic so the reader can re-derive.
- Never silently mix Ferrite-lineage hardware numbers (A18 Pro) with Muser measurements. Say which machine made which number.
9. Cross-references and segues
- Link to other chapters by relative path:
[Ch 13](13-the-qkv-gate-matvec-family.md). - Every chapter ends with a “What comes next” transition (one short paragraph, no heading, before References or as the last prose) that tells the reader what question is now open and which chapter answers it. Every chapter’s introduction opens by recalling where the previous chapter left off (one or two sentences; Part-openers recall the previous Part).
- Maintain the glossary: when you define a term in a chapter, add it to the glossary with a back-reference to the chapter that introduced it.
- The first time a term appears in a chapter, link it to its glossary entry:
[SIMD group](glossary.md#simd-group).
10. What kills a chapter (reviewer veto triggers)
- An undefined term used as if understood.
- A “why” with no citation or
[unverified]tag. - A mermaid diagram carrying byte offsets.
- A quoted code block that is actually paraphrase, or a
file:linetag that does not resolve in the pinned tree. - A tradeoff section with no measurement.
- A number with no source tag.
- A missing entry/exit segue.
- Ferrite-lineage numbers presented as Muser measurements.
Narrative: receipts need a story
The governing essay is “Notes on the Synthesis of Labyrinths”: present the labyrinth of the work — the forks, the dead ends, the reasoning at each junction — not a flattened list of outcomes. Receipts stay, always; what changes is how they enter the prose.
- Open a section on the question it answers before any apparatus appears.
- A failed attempt is a war story, in order: the fork, what we tried, what we expected, what happened, what it taught, and only then what shipped. Never a staccato list of verdicts with citations.
- Weave evidence into the sentence (“the run that proved this is retained: […]”) instead of stacking bracket tags mid-thought. One tag per sentence is a good ceiling; the rest can close the paragraph.
- A digression must announce its own relevance in its first sentence.
- Restate the one or two genuinely hard ideas of a chapter in fresh words; pay for it by abbreviating inventory.
- The voice is “we”: we tried things, expected things, and were surprised, and the reader is walking beside us.