Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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:

PartWhat it teaches
I — The problem and the Metal compute modelNo 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 — QuantizationHow ~30 billion weights fit in a ~17 GB GGUF, and what each lane pays.
III — The modelMuse Glimmer’s 52-layer graph, and the forward pass at a glance.
IV — The decode path, kernel by kernelOne chapter per stage, in execution order. The spine of the book.
V — The KV cache and kvpackThe 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 laneWhy a Mac and a GB10 are better together than either alone — and what it costs to trust someone else’s prefill.
VII — Orchestration and servingHow 52 layers × a dozen kernels × 4 slots become one disciplined owner of one accelerator.
VIII — Measurement and evidenceHow 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]:

LanePrefillDecodeSpeculativeIntended use
Native NVFP4Spark tensor-core FP4Mac NVFP4 weights, FP16 KV, 35.491 tok/sRejected (fail-closed)Fast product lane
kquant/referenceReference pathkquant, 35.440 tok/s107.9 tok/sSpeculative + reference lock
Exact NVFP4 flagInteger-dot verification producerMac NVFP4Verification onlyDeterministic 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 the ferrite/ 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.md row N.
  • [receipt <path>] — evidence under muser-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]:

FieldValueSource
layers52muse_golden.rs:97
hidden dim6,656muse_golden.rs:98
attention heads : KV heads32 : 2 (head_dim 128)muse_golden.rs:99-101
attention width (32 × 128)4,096derived
KV width (2 × 128)256derived
FFN intermediate dim19,968muse_golden.rs:102
vocab size202,048muse_golden.rs:103
embedding / LM headuntied (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:

  1. 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.
  2. Read bytes faster — a different memory system. Not a lever you have on a fixed Mac; the ceiling is the machine’s.
  3. 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 thingSizeNotes
target GGUF (weights)16,756,681,056 Bmmap/page-cache backed
KV planes, 4 slots × 131,072 ctx7.306 GB4 × (39 × 2,048 + 13 × 131,072) × 1,024 B
DFlash draft GGUF1,631,205,312 Bloaded only when configured
vision projector1,400,328,928 Bloaded only when configured
f32 batch-activation widths~0.99 GBreused scratch; prefill chunks at 512
macOS + everything elsethe restnot 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 .metal source 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-318 the 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] — the release_gguf test: 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 (verified delta_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:

  1. 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 .metal files, of which two are Muser-authored and 27 sit in the ferrite/ lineage directory, plus one bench-only candidate shader under crates/muser-bench/. Where an inherited kernel came from is recorded rather than remembered; the extraction manifest is retained [docs/extraction-manifest.md].
  2. 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.
  3. 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 .metal source 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) when MUSER_GGML_METALLIB is 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_grid so 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) and simd_shuffle(val, lane). A reduction that would take log₂(32) = 5 barrier-separated passes between independent threads takes one simd_sum inside 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:

  1. Each thread accumulates partial sums of squares over float4 (4-wide) loads, striding by 128 — the strided loop that shares work across the threadgroup.
  2. simd_sum(sum_sq) collapses each SIMD group’s 32 partials to one value in a single instruction.
  3. Lane 0 of each SIMD group writes its group’s value into threadgroup memory (shared[sgitg]).
  4. 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.
  5. Thread 0 combines the 4 group values, computes the inverse root mean square, parks it at shared[4].
  6. A second barrier, then every thread re-reads inv_rms and 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:

  1. Acquire the accelerator (AcceleratorScheduler::acquire, [crates/muser-engine/src/decode.rs:1040]).
  2. queue.new_command_buffer() — get a blank tape.
  3. Create the encoder (concurrent on the token path; serial where the A/B flag says so).
  4. (Hundreds of times) bind pipeline + bind buffers + set constants + dispatch — via encode_* functions, grouped into barrier-delimited closures.
  5. encoder.end_encoding() — stop recording.
  6. command_buffer.commit() — press play; the GPU starts, asynchronously.
  7. 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-47 device/queue creation; :149-211 the deadline-bounded wait_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 the concurrent_prefill_dispatch A/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 the dispatch closure helper.
  • [crates/muser-engine/src/shaders/ferrite/rmsnorm_batch_tail.metal:1-33]rms_norm_batch (the three-units reduction kernel); :147-201 the 32sg dual-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-1343set_value and dispatch_1d; :21-88 — the 66-name PIPELINES registry.
  • [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, and set_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 with synchronize/didModifyRange calls. 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. Commit b9678d4 flipped 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 memoryBarrierWithScope groups 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. len counts floats. CPU access goes through checked as_slice / as_mut_slice views 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. _mmap holds the file mapping alive; the leading underscore suppresses a lint for a field that exists for its lifetime, not its value — drop the last GpuBytes, the Arc count hits zero, the mapping is released.
  • GpuByteView — a borrowed slice of a GpuBytes: (buffer, offset, len), created by the checked view(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:

  • zeros allocates and CPU-memsets — the default for everything (GpuBytes::zeros at buffer.rs:59-73, GpuBuffer::zeros at :182-196, GpuHalfBuffer::zeros at :238-244). The cost is paid once at allocation, never on the hot path.
  • uninitialized exists on GpuHalfBuffer only, 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::newGpuHalfBuffer::zeros ([crates/muser-engine/src/decode.rs:229-244]), with an explicit in-source justification at decode.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 with MetalKvPlane::uninitialized and 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 StorageModeShared buffer 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 MetalShared design ([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 the b9678d4 untracked-mode incident comment; :28-56 the four substrate types; :59-73, 182-196, 238-244 the zero-initializing allocators; :246-277 GpuHalfBuffer::uninitialized and the 0xDEAD poison; :91-130 from_mmap with the page-rounding contract; :328-376 the poison and page-rounding tests; :16-26 getpagesize.
  • [crates/muser-engine/src/weights.rs:163-218]MuseWeights::open (whole-file mmap + per-tensor index with bounds checks); :40-48 TensorView; :350-373 TensorLayout; :375-378 mapped_file().
  • [crates/muser-engine/src/decode.rs:1195-1206]load_shared: from_mmap + residency set; :148-161 Projection::view / nvfp4_scale_view; :229-244, 246-261 the two MetalKvPlane constructors; :1350-1352 the live-plane zero-fill rationale; :1862-1888 the detached remote-install generation; :954-984 MetalShared.
  • [crates/muser-engine/src/metal/residency.rs:1-107] — the MTLResidencySet owner (raw msg_send!, fails open below macOS 15).
  • [crates/muser-engine/src/metal/encode/multicol.rs:192] — the real offset-view set_buffer bind.
  • [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’s library, cross_vendor_library, and ggml_library fields are for, and what a dispatch_thread_groups call 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”:

  1. .metal source text — a C++ dialect a human writes. Pure text.
  2. 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 .metallib file is exactly this stage serialized to disk — a library you can load without ever running the frontend compiler.
  3. 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 to set_compute_pipeline_state on 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.md digest 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]MetalContext with the three libraries; :46-110 device/queue + the 24-file fast-math concat; :111-121 the strict-f32 recompile; :122-131 metallib loading; :149-211 the 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-name PIPELINES registry; :90-139 MetalKernels’ typed fields; :202-315 the constructor wiring all three sources; :278-293 ggml pipeline construction; :370-385 supports_projection (the Q6_K no-fallback policy); :798-835 ferrite_f16_pipeline (function-constant slots 40/92/98); :837-866 ggml_matvec_pipeline (slots 600+); :957-1003 the mul_mv_ext group and FC_MUL_MV; :1079-1210 the 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-1310MissingProjectionKernel; :1256-1263 — CPU-built RoPE frequency table.
  • [scripts/compile_llama_metallib.sh] — revision pinning, clean-tree checks, append-only outputs, the source_receipt.v1 schema.
  • [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_RECEIPT plumbing in node onboarding.
  • [docs/extraction-manifest.md] — per-file provenance for the ferrite/ 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 + MTLBinaryArchive cache Muser dropped, the precise:: 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:

  1. 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.
  2. Error is bounded by scale/2. Every true value lies within half a step of a level, so |error| ≤ scale/2 by construction. Here 0.015; we measured ≤ 0.01.
  3. 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.md row 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_llama doc: 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-blockscmeff. scale = d·sceff. min = dmin·m
02040.03125×20 = 0.6250.0078125×4 = 0.03125
112160.03125×12 = 0.3750.0078125×16 = 0.125
24480.03125×44 = 1.3750.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_k and 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_xl default).
  • [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] — the SHAPES table: 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 pinned kernel_mul_mv_q{4,5,6}_K_f32 metallib PSOs.
  • [crates/muser-engine/src/shaders/muse_reference.metal:735-788]muser_matvec_q4k_4r2s; :41-62 muser_decode_all_q4k_scales; :961-977 muser_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_k is 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. NVFP4Native 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 scale2 per 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 twin muser_e2m1_round at nvfp4.metal:121). A format is not just its decode table — it is also how you got there.
  • The scale2 is 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:

  • absentweight-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].
  • presentW4A4: 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 sideMUSER_NVFP4_EXACT is a Python environment flag for the producer container (scripts/gx10/vllm/benchmark_native_prefill.py:99-102 even refuses to benchmark native with it set; the resident daemon pins MUSER_NVFP4_EXACT=0 at scripts/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-51 E2M1 LUT and the Q1/Q9 integer encodings; :109-171 E4M3FN decode and the ModelOpt rounding boundaries; :241-255 dequant_nvfp4_row; :349-379 dot_nvfp4_a16_q8_f32.
  • [crates/muser-engine/src/gguf/types.rs:33-39]NVFP4_E2M1 / F8_E4M3FN registration 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-290 nvfp4_aux validation (shape, dtype, NaN rejection, positive scalar scale2, activation-precision pairing); :519-536 the fixture test this chapter’s worked example mirrors.
  • [crates/muser-engine/src/shaders/nvfp4.metal:1-5] — the GPU format doc; :17-42 LUT and integer twins; :155-158 the MLX half-bit embedding; :183-242 the A16 matvec family; :613-702 muser_nvfp4_a16_q8_matvec; :704-770 the F16 matvec/embedding kernels; :775-788 the dequant fixture kernel.
  • [crates/muser-engine/src/metal/encode/qkv.rs:128-227]encode_nvfp4_matmul dispatch and geometries; :8-65 the two-pass M16 prequant route; :348-360 F16-layout detection; :674-699 the bit-exact E4M3FN sweep test.
  • [crates/muser-engine/src/decode.rs:6044-6091] — projection routing; :3254-3291 the verify-route banner (mode names as the engine prints them).
  • [crates/muser-cluster/src/config.rs:10-18], :128-131Nvfp4ProducerMode; 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_EXACT is 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: validate refuses 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_rows accepts a row only when its layer is on the pinned list. The list itself lives in DFlashConfig.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-335 lists 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_greedy embeds its 16-token block with target.embed_tokens and scores the draft’s block output with target.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.k is Q4_K, draft.v Q6_K, draft.fc Q4_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-57 DFlashContextGeometry and the exact byte bound; :86-91 the window-collapse doc (72.5% → 2.2%); :109-136 the loud fallback; :180-255 the GGUF config path (one-based target layers, gguf-kquant); :263-268 the five-layer enforcement.
  • [crates/muser-engine/src/dflash/weights.rs:35-66] — the dual loader and the projection shell; :115-161 validate_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-134 the disable-gate constants; :184-230 windowed gate + re-qualification; :730-770 draft_greedy (verify lengths 3|7|15, seed echo).
  • [crates/muser-engine/src/sampling.rs:1001-1097] — the MT-pinned stream and verify_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-stamped dflash_context_geometry bound 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 and DFLASH_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)

HyperparameterValueParsed from GGUF keyRelease-test assertion
n_layers52muse-glimmer.block_count [crates/muser-engine/src/config.rs:169][crates/muser-engine/tests/muse_golden.rs:97]
hidden_dim6,656muse-glimmer.embedding_length [config.rs:170][muse_golden.rs:98]
n_heads (query)32muse-glimmer.attention.head_count [config.rs:171][muse_golden.rs:99]
n_kv_heads2muse-glimmer.attention.head_count_kv [config.rs:172-173][muse_golden.rs:100]
head_dim128muse-glimmer.attention.key_length (falls back to hidden/n_heads) [config.rs:174-176][muse_golden.rs:101]
intermediate_dim (FFN)19,968muse-glimmer.feed_forward_length [config.rs:180][muse_golden.rs:102]
vocab_size202,048muse-glimmer.vocab_size (falls back to the GGUF token list) [config.rs:209-211][muse_golden.rs:103]
sliding_window2,048muse-glimmer.attention.sliding_window [config.rs:189][muse_golden.rs:104]
context_length131,072muse-glimmer.context_length [config.rs:181][muse_golden.rs:105]
SWA / full layers39 / 13muse-glimmer.attention.sliding_window_pattern = 4 [config.rs:347-361][muse_golden.rs:109-116]
rms_eps1e-5muse-glimmer.attention.layer_norm_rms_epsilon (required) [config.rs:184-188][crates/muser-engine/src/lib.rs:11-13]
post_norm_eps1e-8not in the GGUF — llama.cpp graph constant [config.rs:23-28][config.rs:28]
rope_base_swa500,000muse-glimmer.rope.freq_base_swa (falls back to rope.freq_base) [config.rs:200-202][crates/muser-engine/src/rope_nco.rs:14]
logit_scale0.196116 (= 1/√26)muse-glimmer.logit_scale (required) [config.rs:190-192][docs/release-provenance.md:823]
final_logit_softcap20muse-glimmer.final_logit_softcapping (llama.cpp default 30) [config.rs:195-197][crates/muser-engine/src/lib.rs:186]
end-of-generationEOS 200,001 + EOT 200,008tokenizer.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}.*)ShapeRole
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-256 114f55ebdc1804c1af371197b9fdf2d6bb925966c9dfe46b73782a71bc07965e, 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,001 and EOT 200,008, with the eot/eom metadata 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-93 MuseLayerKind/uses_rope/layer % 4 == 3; :158-211 MuseConfig::from_gguf (every key of Table 9.1); :268-281 attn_dim/kv_dim/heads_per_kv/attn_scale; :286-333 tensor-shape contract; :347-381 SWA-pattern resolver; :390-403 and loader.rs:98-138 QkNormProbe.
  • 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-only 1/√26 literal).
  • 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 = 16 tokens 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:

  1. The serving decode path is the one-row batch graph: forward_intoforward_batch (decode.rs:2857) → forward_batch_hidden (decode.rs:3788) → encode_batch_hidden_range (decode.rs:3858), with token_count = 1. One row, exact pinned kernels.
  2. 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 encoder encode_decode_group (decode.rs:4954) exists precisely to run “batch rows of the same op sequence”.
  3. Multi-sequence decode packs up to four sequences into one graph: forward_decode_group (decode.rs:4869) accepts 1..=4 models sharing one MetalShared executor, encodes all rows with one concurrent encoder, commits once, waits once (decode.rs:4920-4937). This is the rendezvous the server’s 250 µs DecodeBatcher coalesce 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, then next_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_norm selected at decode.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 twin muser_fused_norm_residual_rms_norm_batch_dual_eps [rmsnorm_batch_tail.metal:250], bound at decode.rs:4510-4525.
  • (b) Scale + soft cap (box ⑮) — live. muser_scale_softcap_inplace multiplies by logit_scale and 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_norm and 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 finished SiLU(gate) ⊙ up row — but only under MUSER_FERRITE_FFN_GATE_UP and 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 plus muser_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 kindVec-eligibleKernel sequenceFallback
SWA (39)yes, window % 32 == 0muser_kv_store_f16 → barrier → llama-pinned flash_attn_ext_vec
SWA (39)nomuser_kv_store_f16muser_attention_decode_splitk_f16 + reducetoken-major ring walk
NoPE (13)yesmuser_kv_store_batch_f16 → barrier → llama-pinned vec
NoPE (13)noflash_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:

  1. 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 points Session::verify_batch and begin/finish_dflash_verify_suffix [crates/muser-engine/src/api.rs:913; decode.rs:3298, 3635].
  2. 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].
  3. 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)

BufferWidth (elements)BytesWritten by
token_ids64 × u32256host staging (teacher-forced width)
normed6,65626,624residual stream (§10.4), entry norm, tails
post_norm6,65626,624sub-block outputs / next normed input
projected6,65626,624o_proj / ffn_down results
hidden6,65626,624final normed stream (LM head input)
q4,09616,384④⑤⑥
k, v256 each1,024 each④⑤⑥
gate4,09616,384④ (sigmoid source)
attention4,09616,384⑦ (gated in place by ⑧)
attention_partials32 heads × 32 groups × (2+128)532,480splitk / vec reduce scratch
attention_mask131,072 × u16262,144vec-kernel mask
swa_llama_mask131,072 × u16262,144preset −∞ f16 pattern
attention_kv_pad (+_masked)32-row pad blocks65,536 + 65,600vec-kernel 32-row rounding
ffn_gate, ffn_up19,968 each79,872 each⑪ (gate becomes SiLU⊙up in place)
logits202,048808,192⑭⑮, read back once per token
dflash_hidden16 × 6,656425,984speculative capture
dflash_logits16 × 202,04812,931,072verify-block logit rows
dflash_argmax_*partials + results≈ 25,400no-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 kindCountCapacityPlane pair size (K+V)
SWA ring39min(max_context, 2,048)2,048 × 256 × 2 B × 2 = 2,097,152 B = 2 MiB
NoPE growing13max_contextC × 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-226MetalKvPlane and the speculative checkpoint contract.
  • crates/muser-engine/src/decode.rs:265-314 — fail-closed append/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-2137forward_into and the serving-route comment; teacher-forced sink.
  • crates/muser-engine/src/decode.rs:2857-2927forward_batch entry.
  • crates/muser-engine/src/decode.rs:3298-3407, 3635-3666begin/finish_dflash_verify_suffix; the boundary-norm rule.
  • crates/muser-engine/src/decode.rs:3788-3912forward_batch_hidden / encode_batch_hidden_range.
  • crates/muser-engine/src/decode.rs:4473-4549, 4570-4611llama_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-5513forward_token; phase labels (the op census).
  • crates/muser-engine/src/decode.rs:5515-5907encode_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-in ffn_q4k_gate_up_silu_4r2s.
  • crates/muser-engine/src/shaders/muse_reference.metal:4, 15-27muser_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:730draft_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 index produces output[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_dim picks 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. The min(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_value is 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_min unpacks 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 is d × sc × nibble − dmin × m, where d/dmin are 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:

  1. 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.
  2. One kernel serves every batch width. The same muser_embedding_q4k covers 1-token decode, 64-row teacher-forced blocks, and 512-row prefill chunks (qkv.rs:376’s tokens parameter); 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-959muser_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-977muser_embedding_q4k, the kernel this chapter dissects.
  • crates/muser-engine/src/shaders/muse_reference.metal:27-39muser_f16 and muser_scale_min (the f16 header read and the 6-bit scale/min unpack).
  • crates/muser-engine/src/metal/encode/qkv.rs:338-373encode_embedding_q4k, the dispatch wrapper; :361 the row_bytes formula; :376-411 the batch sibling.
  • crates/muser-engine/src/metal/encode.rs:1337-1343dispatch_1d (the dispatch_threads grid used here).
  • crates/muser-engine/src/decode.rs:5523-5533 — the call site, first dispatch of encode_token.
  • crates/muser-engine/src/decode.rs:1207-1216 — embedding loaded as a Projection from token_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-297token_embd.weight shape [hidden_dim, vocab_size] asserted at load.
  • crates/muser-engine/tests/muse_golden.rs:96,101hidden_dim = 6,656, vocab_size = 202,048 asserted 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-741Session::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:

  1. 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.
  2. 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-5 is read from the GGUF key muse-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-8 is 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 read 1e-5 everywhere — 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:

  1. Vectorized load — the float4 cast reads four floats per transaction; n4 = n/4 iterations instead of n.
  2. Strided partial sum — thread tid walks i = tid, tid+128, …, accumulating dot(xb4[i], xb4[i]) (a 4-way sum of squares per load).
  3. simd_sum — one hardware instruction collapses 32 lanes to their sum, no barrier, in-lockstep.
  4. 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.
  5. Single-thread combine — thread 0 sums the four partials and computes the reciprocal root; a second barrier broadcasts it.
  6. 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 computes inv_src with eps1 = 1e-8 — the post-norm.
  • Pass 2 does the residual update hidden += src × inv_src × weight1 and, in the same loop, accumulates the sum of squares of the updated hidden — the read of hidden and the write and the reduction share one pass over the data.
  • Pass 3 computes inv_hidden with 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:

#callwrapper (file:line)kernelε
1entry normencode_rms_norm_mul decode.rs:5534-5544ggml pinned (or rms_norm_batch)1e-5
2layer-0 attn normencode_rms_norm_mul decode.rs:5553-5565same1e-5
3post-attn + FFN pre-norm, ×52encode_fused_norm_residual_rms_norm_32sg decode.rs:5806-5818…_32sg1e-8 then 1e-5
4post-FFN + next attn norm, ×52same wrapper decode.rs:5877-5889…_32sg1e-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-33rms_norm_batch, the ferrite-lineage base kernel.
  • crates/muser-engine/src/shaders/ferrite/rmsnorm_batch_tail.metal:142-201muser_fused_norm_residual_rms_norm_32sg, the dual-eps fused tail.
  • crates/muser-engine/src/shaders/ferrite/rmsnorm_batch_tail.metal:250-322muser_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/sqrt header and the accumulation-order commentary.
  • crates/muser-engine/src/metal/encode/norm.rs:244-278encode_rms_norm_mul (ggml-pinned preference, 128-thread fallback).
  • crates/muser-engine/src/metal/encode/norm.rs:53-94encode_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:287kernel_rms_norm_mul_f32_4 from the pinned metallib.
  • crates/muser-engine/src/config.rs:23-28MUSE_POST_NORM_EPS = 1e-8 and the muse-glimmer.cpp:67 provenance comment.
  • crates/muser-engine/src/config.rs:117-120, 184-188rms_eps/post_norm_eps fields; 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 2simd_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_f32 kernels), 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 multiplymatvec, 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):

projectionshape (in × out)output feeds
attn_q.weight6,656 × 4,096the 32 query heads
attn_k.weight6,656 × 256the 2 KV heads’ keys
attn_v.weight6,656 × 256the 2 KV heads’ values
attn_gate.weight6,656 × 4,096the 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 own N_SG for these kernels (the function constants pinned at registration bake nsg=2, encode.rs:844-851).
  • grid ceil(n_out / (rows_per_group × 2)) — each threadgroup owns rows_per_group output rows: 2 rows per group for Q4_K and Q6_K, 1 for Q5_K, matching llama’s per-format nr0.
  • 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 = 24,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 setbase_row = group * 8 + simd * 4: 8 rows per threadgroup (2 SIMD groups × 4), each lane holding four private accumulators, one per row.
  • Pre-decoded scalesmuser_decode_all_q4k_scales (muse_reference.metal:41-62) unpacks all eight sub-block scale/min pairs once per super-block per row, folding in d and 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 — lane lane reads one byte block[16 + quant_group*32 + lane]; its low nibble covers one 32-element sub-block, its high nibble the partner sub-block (low/high input 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_sum per 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 — the tokens == 1 pinned-matvec dispatch (quoted in §13.4.1); :451-474 the fallbacks; :476-508 the mul_mv_ext boundary; :550-623 the M16/SGM/GEMM batch ladder; :1292-1317 GgmlKargsMulMv.
  • crates/muser-engine/src/metal/encode.rs:278-280 — the pinned kernel names; :837-866 ggml_matvec_pipeline (nsg=2 function constants); :959-1003 the mul_mv_ext groups; :370-385 supports_projection (Q6_K pinned-only).
  • crates/muser-engine/src/metal/context.rs:122-131MUSER_GGML_METALLIB loading of the pinned llama.cpp metallib.
  • crates/muser-engine/src/shaders/muse_reference.metal:735-788muser_matvec_q4k_4r2s (quoted in §13.4.2); :41-62 the scale decoder; :790-831 muser_matvec_q5k_4sg.
  • crates/muser-engine/src/decode.rs:5566-5598 — the four-projection concurrent set; :6044-6089 encode_projection; :2085-2093 the serving-routing comment; :1294-1310 per-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:

  1. Per-head QK-norm — an RMSNorm applied to each attention head’s 128-wide slice of Q and K separately (rms_norm_per_head family; 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.
  2. RoPE (Rotary Positional Embedding) — a per-position rotation of Q and K, applied only on the 39 sliding-window layers.
  3. 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:

  1. Absolute embeddings — learn one vector per position, add it to the token embedding. Simple; generalizes poorly past trained positions, and distances must be learned.
  2. Relative biases — add a per-pair bias to each attention score. Generalizes better; modifies the attention kernel itself.
  3. 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 % 64 at width 128. pi is exactly the i of θᵢ.
  • 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 angle pos·θᵢ 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 requires theta = 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 and rope_norm_batch_cached (quoted).
  • crates/muser-engine/src/shaders/ferrite/rope.metal:566-608rope_batch_cached, the NEOX sibling kept deliberately separate.
  • crates/muser-engine/src/metal/encode/rope.rs:45-152 — the dispatch: cross-vendor NCO route, pinned kernel_rope_norm_f32 route, 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-71MuseLayerKind, uses_rope() and the muse-glimmer.cpp:93 provenance.
  • crates/muser-engine/src/config.rs:84-102, 336-381 — the il % 4 == 3 partition and the fail-closed pattern resolution.
  • crates/muser-engine/src/config.rs:139-141, 277-281, 383-403, 426-430qk_scale_factor, attn_scale, QkNormProbe, and the scale test.
  • crates/muser-engine/src/metal/encode/norm.rs:280-303encode_qk_norm and 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 (the GpuHalfBuffer type is the constraint). F32 planes exist only in the kvpack interchange, never in live decode.
  • capacity / len — the plane holds at most capacity token rows; len are 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:

  1. Fail-closed continuity. position != origin_logical + len is an error, not a modulo — a skipped or replayed position can never silently alias a live row (decode.rs:117-121 defines the error; prefill’s module doc states the invariant: “physical placement is never derived from absolute position”, prefill.rs:15-17).
  2. NoPE degenerates to append-only. A NoPE plane’s capacity is max_context; len < capacity always holds in a valid session, so the else branch never runs, origin_logical stays 0, and the modulo vanishes — the “ring” is a growing array.
  3. 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-190MetalKvPlane.
  • crates/muser-engine/src/decode.rs:228-261 — zero-filled and uninitialized constructors.
  • crates/muser-engine/src/decode.rs:263-314append / append_batch (the ring arithmetic; quoted).
  • crates/muser-engine/src/decode.rs:374-417detached_from and 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 in encode_token; :117-121 CacheDiscontinuity.
  • crates/muser-engine/src/shaders/muse_reference.metal:979-992muser_kv_store_f16 (quoted).
  • crates/muser-engine/src/shaders/muse_reference.metal:1203-1234muser_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-16PlaneEncoding (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_bytes verified).
  • [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 (nb11 is 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 one float4 slice (lane * 4), reused for all 32-token blocks.
  • The scoredot(q, key) is a 4-lane partial; simd_sum folds 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 by n_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_factor rescale of Figure 16.2, then weights and the V-accumulate.
  • Partials [max, sum, weighted-V] — one per (head, workgroup), stride 2 + 128 floats (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 as ns10 = 256 (a full token row per KV row) — the two GgmlMetalKargsFlashAttnExtVec layouts at attn.rs:503-511.
  • Split-K by construction. llama’s kernel launches nwg = 32 workgroups 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 own kernel_flash_attn_ext_vec_reduce merges them (attn.rs:616-630).
  • Ragged visibility handled llama’s way. If visible % 32 ≠ 0, a kernel_flash_attn_ext_pad dispatch first rounds the tail (attn.rs:525-559) — which is exactly what the SWA len % 32 predicate 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:

routewrappers (file:line)kernelsbarrier between store and attention?
SWA vecencode_kv_store_f16 attn.rs:635 + encode_llama_flash_attn_decode_vec_f16 attn.rs:437muser_kv_store_f16 → pinned vec + pad + reduceyes (decode.rs:5669-5670)
SWA fallbackstore + encode_attention_decode_splitk_f16 attn.rs:708store → muser_attention_decode_splitk_f16…_reduce_f32barrier on partials (attn.rs:774)
NoPE vecencode_kv_store_batch_f16 attn.rs:787 + vec wrappermuser_kv_store_batch_f16 → pinned vec + reduceyes (decode.rs:5746-5747)
NoPE fallbackencode_ferrite_attention_decode_interleaved_f16 attn.rs:189ferrite interleaved (fused store) → reduce_v2fused (…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-1167muser_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-896 splitk_geometry (with the pinned-schedule test at :976-983).
  • crates/muser-engine/src/metal/encode/attn.rs:185-257 — the ferrite interleaved wrapper; :102-183 the SWA staging kernels and their llama-lanes comment.
  • crates/muser-engine/src/metal/encode.rs:149-167, 1066-1072, 1079-1249LlamaFlashAttnPipelines, 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-46MAX_DECODE_SPLIT_WORKGROUPS and the depth-rent comment (quoted).
  • crates/muser-engine/src/config.rs:274-281heads_per_kv, attn_scale; :426-430 the 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 into activations.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):

xe^(−x)σ(x)meaning
−454.60.018almost fully closed
−12.720.269mostly closed
01.00.500half open
+10.3680.731mostly open
+40.0180.982almost 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 is device float*, not const. 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 element gid, and the if (gid < n) guard covers the ragged tail when the grid rounds up past n.
  • 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-16sigmoid_gate_inplace, the whole kernel (this chapter’s primary source; the header dispatch comment at :5 is stale versus the wrapper — dispatch_1d uses dispatch_threads, encode.rs:1337-1342).
  • crates/muser-engine/src/metal/encode/gate.rs:7-23encode_sigmoid_gate, the Rust dispatch (cross-vendor twin selected at :14-15).
  • crates/muser-engine/src/shaders/muse_reference.metal:680-688muser_cross_vendor_sigmoid_gate, the strict no-fast-math sibling.
  • crates/muser-engine/src/decode.rs:5793-5805 — the gate dispatch and o_proj call in encode_token; :5449-5451 the one-encoder contract; :2085-2091 the serving-versus-legacy exactness comment; :5112-5116 the packed decode group’s shared gate.
  • crates/muser-engine/src/decode.rs:6044-6091encode_projection, the dtype router; crates/muser-engine/src/metal/encode/qkv.rs:429-450 the one-token pinned-metallib matvec path; :451-459 the muser_matvec_q4k_4r2s fallback.
  • crates/muser-engine/src/metal/encode.rs:278-280ggml_q4k/q5k/q6k PSO registration against the pinned llama.cpp metallib; :1337-1342 dispatch_1d.
  • crates/muser-engine/src/config.rs:266-273attn_dim/kv_dim (“the space the attention-output gate lives in”); :300-314 the per-layer tensor shape contract including attn_gate.weight [h, attn] and attn_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 projected output.
  • 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 13Ch 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:

xsilu(x)relu(x)note
00.0000.000both zero
10.7311.000silu passes ~73 %
−1−0.2690.000silu dips negative
21.7622.000converging to identity
−4−0.0720.000the 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_gate slot 0, W_up slot 1), the input x (slot 2), the output out (slot 3), and rows/cols as inline constants (19,968 / 6,656 on 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/ir select 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-sums sumy into registers once per block iteration. The four sumy partial 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/sumy feed two calls to q4k_v4_dual_row_mac — once against the gate rows, once against the up rows at the same base_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_sum collapses each accumulator across the 32 lanes; lane 0 of each SIMD group writes the two finished rows via apply_activation(g, u). There is no cross-SIMD combine and no threadgroup_barrier — whole-row ownership per SIMD group means each group’s simd_sum is 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-561ffn_q4k_gate_up_silu_4r2s, the fused Q4_K SwiGLU kernel (primary source; the _4sg ancestor variant at :295, _4sg_tgcache at :392).
  • crates/muser-engine/src/shaders/ferrite/ffn_fused.metal:14-30FC_FFN_ACTIVATION function constant + apply_activation (PSO-build-time SiLU/GELU selection).
  • crates/muser-engine/src/shaders/ferrite/_q4k_helpers.metal:34-88q4k_v4_dual_row_mac, the dual-row Q4_K MAC both projections share.
  • crates/muser-engine/src/metal/encode/ffn.rs:7-36encode_ffn_q4k_gate_up_silu_4r2s (grid (i_dim/4, 1, 1) × 64 threads); :38-60 encode_silu_mul (control-route activation).
  • crates/muser-engine/src/shaders/muse_reference.metal:4-13muser_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; :1334 the MUSER_FERRITE_FFN_GATE_UP env read; :2085-2091 the serving-exactness comment; :5165-5185 the 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-74 silu_fast.
  • crates/muser-engine/src/config.rs:180intermediate_dim from muse-glimmer.feed_forward_length.
  • crates/muser-bench/src/m16.rs:171-175ffn_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_fast and the shader lineage from Ferrite 83cfd55…/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 17Ch 18 (the layer so far; ffn_mid is 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’s float4s (i += 1024); each of the 32 SIMD groups reduces with simd_sum, writes one partial to shared[sgitg]; thread 0 sums the 32 partials in order and posts rsqrt(mean + eps1) to shared[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 into hidden, and accumulates dot(value, value) for the second norm. This is where hidden += … physically happens: the same buffer is read, added into, and rewritten, float4 by disjoint float4.
  • Pass 3 (the next norm). With inv_hidden from the second reduction, output[i] = hidden[i] · inv_hidden · weight2. weight1 is the layer’s post_ffw_norm; weight2 is the next layer’s attn_norm (or the final output_norm after 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:

  • hiddenactivations.normed: the running residual, updated in place.
  • srcactivations.projected: the down-proj output (scratch, like o_proj’s output in Ch 17).
  • next_outputactivations.post_norm for layers 0..50 (the next layer’s normalized input), but activations.hidden for 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.
  • weight1layer.post_ffn_norm (1e-8), weight2 ← the next layer’s attn_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”]:

familyproductionlegacydeltadisposition
Entry/attention norm boundary532+51fusion not exact; reject
SWA wrapped-ring staging390+39keep until bit-exact replacement
KV publication and attention10452+52session structure, keep
Post-attention residual + FFN norm10452+52fusion not exact; reject
Post-FFN residual + next/output norm5352+1fusion not exact; reject
Last-row copy10+1removed, 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-201muser_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-160 the batch dual-eps sibling; the strict decomposed route under MUSER_CROSS_VENDOR_QK.
  • crates/muser-engine/src/decode.rs:5863-5889 — down projection + tail + next_norm selection in encode_token; :5806-5818 the first (post-attention) tail; :1328-1334 the fusion-control flags; :2085-2091 the 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-280 PSO registration.
  • crates/muser-bench/src/m16.rs:179-198ffn_down-q4k / ffn_down-q6k shape-dtype evidence for the mix; docs/quickstart.md:16 the Q6_K fail-closed routing note.
  • crates/muser-engine/src/reference.rs:526-541 — the oracle’s post-FFN norm, residual add, and l_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/], [receipt muser-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.hidden through 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:

  1. The final norm — an RMSNorm with the output_norm.weight γ vector and the ordinary rms_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 its next_output selection writes activations.hidden through output_norm (decode.rs:5869-5876, Ch 19 §19.5). The serving batch graph can also run it as its own encode_rms_norm_mul node (decode.rs:5229-5241) — node shape follows the pinned graph, the math does not change.
  2. 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-25muser_scale_softcap_inplace, the fused scale+tanh kernel (fallback route).
  • crates/muser-engine/src/metal/encode/lmhead.rs:163-267encode_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-80 encode_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-5905 LM head + softcap dispatches; :5229-5241 the batch route’s separate final norm.
  • crates/muser-engine/src/config.rs:190-197logit_scale (required GGUF key muse-glimmer.logit_scale) and final_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’s 1.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_output capture.
  • crates/muser-bench/src/m16.rs:195-197lm_head 6656->202048 q5k shape/dtype evidence.
  • [docs/release-provenance.md:822-823] — logit_scale=0.196116 with final_logit_softcapping=20 on 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-39argmax_f32_phase1 (the chunk tree); :41-70 argmax_f32_phase2; :72-158 the greedy pair with the nonfinite latch and EOG exclusion (headers quote the contract).
  • crates/muser-engine/src/metal/encode/lmhead.rs:83-120encode_argmax_f32_rows; :123-161 encode_greedy_argmax_f32 (the inter-phase barrier).
  • crates/muser-engine/src/api.rs:696-737Session::decode: the retained distribution, CPU argmax, fail-closed finite scan; :1842-1849 the CPU argmax; :1851-1856 ensure_finite_logits.
  • crates/muser-engine/src/decode.rs:1626-1728forward_greedy_streaming, the GPU-resident chain; :1688-1693 the 4-byte read and the u32::MAX fail-closed check; :1730-1765 the embedding-from-argmax-buffer link; :3659 the batch graph’s full-vocab read-back; :2118-2123 the teacher-forced no-readback contract.
  • crates/muser-engine/src/sampling.rs:53-120Mt19937 and its libc++ conversions; :399-509 distribution_ordered (the sampler chain); :1008-1097 verify_full_speculative_mt / _mt_ordered (exact acceptance); :1105-1133 the libc++ vector test.
  • crates/muser-server/src/openai.rs:4266-4379AdaptiveSamplerState / RequestSamplerState (four RNG streams, snapshot/restore); :4413-4509 sample_or_argmax (logit bias, repeat penalties, grammar rejection sampling); :4982 grammar_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] — maxThreadsPerThreadgroup and 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 at min(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_context capacity (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 slotOne-slot KVFour-slot KV
8,1920.191 GB0.763 GB
32,7680.518 GB2.072 GB
131,0721.827 GB7.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:

  1. 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].
  2. 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 --parallel in 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-93 the layer % 4 == 3 partition rule.
  • crates/muser-engine/src/decode.rs:182-190MetalKvPlane (f16 fields); :1338-1358 per-layer-kind capacity allocation (min(max_context, sliding_window) vs max_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-16PlaneEncoding::{F16Le, F32Le} (interchange encodings, not live planes).
  • crates/muser-server/src/state.rs:221-243 — slot independence vs shared weights; the out-of-pool staging generation.
  • 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_bytes verified); 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:

  1. The reader wants per-head spans. The pinned llama.cpp flash_attn_ext vec kernel addresses KV head-major with ns10 = 128 strides (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.
  2. Append never wraps. §23.2’s NoPE degeneration means physical order is logical order, origin_logical = 0 forever, 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.
  3. 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 in write_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-122CacheDiscontinuity.
  • crates/muser-engine/src/decode.rs:263-314append / append_batch (append_batch quoted; §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-417 detached_from and the rotation-preserving install; :376-383 the order-sensitivity comment and the named replay test.
  • crates/muser-engine/src/decode.rs:4290-4352 — the wrapped SWA staging route and its append_batch commit; :4356 the 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-138encode_stage_swa_prefill_f16 (signature quoted); :140-144 the 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-219ContextPolicy (quoted); :240-243 the out-of-pool staging generation.
  • crates/muser-server/src/openai.rs:5241-5317prepare_with_context_policy (the turn-dropping loop); :5338-5354 compact_raw_prompt; :5356-5391 complete_chat_turns / shift_chat_units; :5408-5413 lineage validation; :1547-1552 the context epoch; :1563-1637 the staging rebuild (spine quoted); :2790-2811 the swap helpers.
  • crates/muser-kvpack/src/session.rs:164-166ProductionF16Required (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]:

  1. Is this cache the cache I asked for? — keyed, fail-closed identity.
  2. Have I seen this request before? — replay protection.
  3. 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-91HmacIdentityV2, SegmentRoleV2, SegmentDescriptorV2, BeginManifestV2 (quoted); :100-160 begin validation; :233-246 SealCoreV2 / SealManifestV2 (quoted); :248-287 signing; :307-314 the HandoffSinkV2 shadow contract.
  • third_party/kvpack/crates/kvpack-handoff/src/manifest.rs:56-66ExactIdentityV1 (quoted); :85-129 LayoutClassV2 (quoted) with the windowed-class rule; :347-368 LayerHeaderV1; :397-410 the 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-58MuseIdentity and its digest (quoted); :69-136 the descriptor builder incl. K4 binding and the exact-logits plane; :138-163 validate_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-50ReceiverConfigV2 (quoted): pins, key paths, replay floor, identity fields.
  • crates/muser-cluster/src/transport.rs:15-16, 35-46KVPKV2 framing; the raw-JSON prefix_cut lift.
  • 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_cut has 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) and scripts/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]:

RungWhat happensResult
Warm prefix, one MacLocal resident/durable cache answers from installed state~65 ms to resume a shallow warm prefix
Warm prefix at depth, remote producerProducer holds the exact prefix; no compute, no transfer0.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 handoffOnly the missing suffix crosses the wire54.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:

  1. 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).
  2. 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 a ChunkPool with weak-reference dedup, and a global byte-budget LRU (default 8 GiB) bounds the tier [docs/kvpack-merge-handoff §4].
  3. 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 — the MUSE_EXACT_LOGITS_LAYER of [docs/kvpack-merge-handoff §4]. The writer side is worth following because of its order: session.rs:183-219 lays the planes down, and the logits plane — the one that makes a cut exact, below — is written last, at :189-200.
  4. 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 cellFive-rep samplesMedianCV
Warm resident prefix hit65.263, 64.502, 64.631, 64.621, 64.921 ms64.631 ms0.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"]:

depthleggenerationfirst-token stotal sproducer exit
65,536cold96020968.616674.52170
65,536warmn/a (no producer)0.61326.5360n/a
65,536miss control (8,192)96021010.556712.89940
130,815cold960211147.8321157.79740
130,815warmn/a (no producer)1.056611.0625n/a
130,815miss control (8,192)96021210.508812.85130

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:

  1. 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).
  2. No producer drive on either warm hit [claims #11]. The warm leg’s “n/a (no producer)” row is the receipt’s producer_driven: false: zero remote compute, zero wire. The Mac answers from its own installed state.
  3. 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 MuseIdentity digest (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,536 outputs_match: false cell: 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 and find_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:

armgenerationprompt tokensprefix cutpayload bytesproducer total s
prefix identity witness96021332,7690517,996,54430.7269
delta handoff96021465,53632,768517,983,23263.4784
full reference96021565,5360954,190,84864.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-130PREFIX_CUT_ALIGN, muse_schedule_span_for (tiles, window clamp, layer-major streaming order).
  • crates/muser-cluster/src/identity.rs:143-160validate_prefix_cut (quoted abridged): alignment, nonempty suffix, exact held prefix, span schedule.
  • crates/muser-kvpack/src/reuse.rs:27-66, 226-237RemoteReuseAction and remote_action (quoted); unaligned partials left uninstalled.
  • crates/muser-server/src/openai.rs:2907-2918arm_remote_delta (fetched-tiers-only rule).
  • crates/muser-cluster/src/transport.rs:35-46 — the raw-JSON prefix_cut lift; [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-3958 ambiguous-failure recording; :3960-3977 idempotent 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-4710 run_storage_restore’s move semantics and source_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.

DepthLocal TTFT (mean)
2,0486.48 s
8,19226.77 s
16,38454.79 s
32,768114.31 s
65,536247.88 s
131,008-class570.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], receipt nvfp4-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 under kvpack-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]:

DepthLocal TTFTRemote TTFTPayoff
2,0486.48 s1.520 s4.26×
32,768114.31 s30.489 s3.75×
130,815570.12 s137.405 s4.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 againstCh 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), Mac en0 192.0.2.10, Wi-Fi en1 never 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), decide latch (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 of MUSER_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]Nvfp4ProducerMode and the native-mode/DFlash enrollment refusal.
  • [scripts/gx10/vllm/native_onboarding_identity_v1.json] — the frozen producer identity chain (checkpoint d5109a1…, image 593b96a, 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
1Warp: 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)
2Tensor-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 shapesAsk each silicon for what it is shaped to do — the Ch 27 roofline, enforced by lane (§29.4)
3Host/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)
4Streams + events: side streams, async copies, event fences (connector.py)One queue, record-then-commit command buffers Ch 2One 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 homePinned llama.cpp metallib as Muser’s third kernel source Ch 4Bit-parity with the comparator beats re-expressing ggml kernels in native Metal (§29.7)
6Determinism 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-vendorLogit 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 matvecmuser_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 with simd_sum reduction.
  • [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+] — the muser_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_INVARIANT limits, “nobody achieves bitwise CUDA↔Metal”, Apple-side install design), §2 (connector streaming status).
  • [docs/disaggregated-prefill.md §What you need] — the MUSER_CROSS_VENDOR_QK=1 requirement 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, fixes 27b5790/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)?

  1. It came from the enrolled producer, not an impostor with a valid certificate from some other CA. → mTLS with pinned leaves.
  2. 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.
  3. 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:

  1. create_new (O_EXCL) + mode 0600 — the temp file cannot clobber or alias anything;
  2. write_all then sync_all() — the new high-water mark is on the platter before it becomes authoritative;
  3. rename — one atomic step replaces the ledger;
  4. 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 wizardmuser 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):

#StageWhat it does
1preflightSSH with BatchMode (fails closed rather than prompting), aarch64, NVIDIA driver, docker — nothing copied
2deployPushes the pinned runtime container (a sha256:… id, never a mutable tag); records the container receipt
3modelPlaces the pinned GGUFs under lane_dir, verifying byte sizes and SHA-256 against the release artifact manifest
4enrollNode-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
5daemonStarts the producer daemon in the container, pointed at the enrolled PKI and model
6smokenetqual (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_connection leaf pins (196–213), MacKey loading contract (55–95), private-key file contract (289–335), ReplayLedger admit/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 in prepare_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-time verify_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 0stable: 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):

ConditionRepProducer wire sinstalled Gbpsverify+install+seal+commit
as-is113.250.0930.20 s
as-is26.580.1870.16 s
as-is319.750.0620.20 s
serial quiesce119.540.0630.20 s
serial quiesce20.225.5260.20 s
serial quiesce36.570.1870.19 s
EEE off (probe)10.206.2150.20 s
EEE off (probe)20.245.2080.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:

  1. 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].
  2. 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].
  3. handoff_report.py — turns one qualification packet’s retained receipts into the per-repetition phase table (producer sched/first/ d2h/hash/pack/seal/wire/gbps beside receiver drain/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”; “d2h or first_layer growing 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].
  4. restart_resident_producer.py / supervise_resident_producer.py — the fail-closed producer’s restart ritual (stale O_EXCL startup receipt, RoPE cache, and socket must be moved aside; a bare docker restart is 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, en1 prohibition, 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]ReplayLedger admission/reserve semantics (reserve before publication, latched degradation) and the write+fsync+rename+dir-fsync persist_replay_state.
  • [crates/muser-cluster/src/receiver.rs:108-206]check_ledger_volume / probe_ledger_reserve and 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 Gbps HANDOFF_PACING_BYTES_PER_SECOND, its rationale docstring, the MUSER_GX10_PACING_BYTES_PER_SECOND override, fail-closed readback.
  • [scripts/gx10/vllm/muser_native_prefilld.py:514-577] — producer receipt validation incl. payload_pacing_bps >= 4_000_000_000 and the linux-tcp-info-busy-time-v1 wire 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 exactrequire_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:

LanePrefillDecodeSpeculativeIntended use
Native NVFP4Spark tensor-core FP4Mac NVFP4, 35.491 tok/sRejected (fail-closed)Fast product lane
kquant/referenceReference pathkquant, 35.440 tok/s107.9 tok/sSpeculative + reference lock
Exact NVFP4 flagInteger-dot verification producerMac NVFP4Verification onlyDeterministic 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]:

QuantityMeasured envelope (fast vs exact)
Greedy token agreement32/32, 100%
First divergent tokennone
Full-logit maximum absolute error7.270581
Full-logit mean absolute error1.040619
KV key maximum absolute delta across 52 layers9.625
KV value maximum absolute delta across 52 layers18.458008
Per-layer KV key mean-absolute range0.015648–0.431876
Per-layer KV value mean-absolute range0.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 declared bounded-drift rule (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, the a3bbd72f… 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-once wiring.
  • [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_sha256 validation; 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_EXACT is 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 q and the target’s p at the same token. Accept with probability min(p/q, 1) — the maximal-coupling rule behind Leviathan et al. and the independent DeepMind formulation [arxiv:2302.01318; frontier §"The target identity invariant"]. One uniform_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 rand algorithm 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"]:

sinkwindowmuser acceptancedecode
641024 (shipped)2.2%0.535
110242.2%
1327682.2%0.534
1204872.5%1.315
64204872.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]:

TraceOutputRoundsAccepted / draftedAcceptanceMeasured tok/sVerifier-only ceiling
Standard51235477 / 477100.00%110.59125.61
Documentation256109147 / 1,5929.23%15.5320.15
Python25655201 / 76426.31%11.1740.04
Rust25639217 / 57038.07%15.4155.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]:

#HypothesisReceiptVerdict
1Optimize the Mac Dudeman verifierMeasured: 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)
2Second resident Dudeman verifier on GX10, linear chainAuthenticated composite import, end-to-end traces (§33.5)Rejected for general serving
3Existing RedHat session as target, Mac DFlash proposesMeasured mismatch screen: 190,449 teacher-forced rows, 8.116% RedHat/Dudeman top-1 disagreement; docs 15.428% → projected 42.82 tok/sAlgorithmically valid, fails the speed gate on docs
4Shared-Gumbel couplingLiterature + reference implementationRetained experimental; changes sampler semantics
6Stateless GX verifier, authenticated Mac log, prefix-cache soft stateV2 transcript/journal implemented; service unimplementedPreferred service architecture, if a lane ever returns
10Uncertainty-aware token tree on GXMeasured batch curve; rank/coverage unmeasuredOnly remaining performance experiment
14Cross-checkpoint KV translator / CRDT branch union / wrong target certifyingInvariant rejection + published approximate systemsRejected 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_f64 draw 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, the min(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-226 the speculative checkpoint.
  • [crates/muser-bench/src/remote.rs:3-8, :33] — the 256-token + full-logit-row comparison and DFLASH_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:

  1. 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.
  2. 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].
  3. 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 inside muser-engine that 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 inside muser-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:

  1. 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].
  2. One thread is elected (the first to find running == false sets it and becomes the runner; the others go back to sleep) [crates/muser-server/src/state.rs:341-360].
  3. 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);
}
  1. One row runs session.decode; two-to-four rows run Session::decode_group — the engine entry that fronts forward_decode_group and then fans results back out, one per session [crates/muser-engine/src/api.rs:796-826; crates/muser-server/src/state.rs:398-435].
  2. 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 Sessionstaging — 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-998MetalShared (the shared inventory) and MetalMuseModel (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-2113forward_into and the decode-aware chunk shrinking (quoted).
  • crates/muser-engine/src/decode.rs:4866-4952forward_decode_group: the 1..=4 packed graph, one encoder/commit/wait.
  • crates/muser-engine/src/api.rs:602-613, 796-826Session inventory; decode_group fan-out.
  • crates/muser-server/src/state.rs:221-254, 269-441, 444-483, 512-590, 1054-1056InferenceRuntime fields (quoted), DecodeBatcher and run_one_batch (quoted), rotation key, SlotPool (quoted), admission, --parallel bound.
  • crates/muser-server/src/openai.rs:4331-4337, 649, 665, 2266-2270, 3480-3482RequestSamplerState (quoted); 499 mapping; per-request grammar/detokenizer/stop construction; n in 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:

  1. 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.
  2. 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Δ closuresWhat it isDisposition
Norm-boundary groups+10451 entry/attn-norm boundaries + 52 post-attn/FFN-norm pairs + 1 post-FFN/output boundary, separated instead of fusedExisting fusion not exact; reject
SWA wrapped-ring staging+39one 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+52store dispatch and attention dispatch as separate closures, once per layerSession/publication structure; keep — combining closures alone removes no kernel math
Last-row copy+1one bookkeeping copy of the final hidden rowNo 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]:

StepGroupsGPU msFull-logit SHA-256 changed?Verdict
Baseline76040.330historical pre-J0 reference
Direct one-row LM-head input (copy elision)75940.194nolanded
Existing dual-norm fusion65540.614yesrejected: not exact
Pinned-reduction dual norm65539.274nohistorical only
Eight-head one-query GQA FA265537.097nohistorical 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 sealed OverlapProof type 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 no Op enum 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, the b9678d4 reversal), 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-277shared_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/concurrent GraphEncoder; the broad-scope closure barrier (quoted); the dispatch trampoline.
  • 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-6236PhaseProfiler as 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: T tokens 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_batchforward_batch_hiddenencode_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-3855forward_batchforward_batch_hiddenencode_batch_hidden chain.
  • crates/muser-engine/src/decode.rs:3857-4007encode_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-5980encode_batch_projection and 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 and kernel_flash_attn_ext_f16_dk128_dv128 wrappers 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:

RoutesPurpose
/, /dashboardthe same-origin dashboard (live process state only)
/snapshotJSON snapshot of live engine state (management auth)
/metrics, /telemetryPrometheus text; temporary SSE keyframes
/health, /v1/health, /healthzhealth — 503 when latched unhealthy (§37.8)
/models, /v1/models, /propsmodel identity/properties surface
/slots, /slots/{id}resident-slot inspection and actions (erase)
/tokenize, /detokenize, /apply-templatetokenizer/template endpoints
/embedding, /embeddings, /v1/embeddingsembedding endpoints
/completion, /completions, /v1/completionsllama-compatible completions
/api/generate, /generateOllama-compatible aliases
/v1/chat/completions (+/chat/completions, /control)the chat route of §37.3; mid-stream reasoning control
/v1/stream (GET/DELETE), /v1/streams/lookupresumable SSE streams and lookup
/v1/dashboard/loginAPI-key → cookie exchange (§37.6)
/v1/ws-ticketsmint 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)
/streamauthenticated WebSocket telemetry
/v1/nodes (+/{name}/progress)GX10 node enrollment/progress (management auth)
/__muser/benchmark/shutdownbenchmark-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:

  1. POST /v1/sessions/{id}/migrate and POST /__muser/v1/session-transfers/prepare start an export — mode copy or move, 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].
  2. 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 by MAX_TRANSFER_BYTES = 32 GiB [crates/muser-server/src/session_store.rs:16].
  3. POST /__muser/v1/session-transfers/{transfer_id}/commit completes 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:

BoundValueWhere
Request body64 MiB (MAX_BODY)axum_httpd.rs:53, 544
Request-body timeout30 saxum_httpd.rs:545
Concurrent HTTP requests256axum_httpd.rs:546
Migration payload laneunlimited body / 60 min / 4 concurrentaxum_httpd.rs:547-554
Single transfer payload32 GiBsession_store.rs:16
Waiting admissions64 (MAX_QUEUED_REQUESTS)state.rs:253
Streaming channel depth64 (STREAM_CHANNEL_DEPTH)axum_httpd.rs:54
Backpressured writer5 s grace, then cancel (499)axum_httpd.rs:55, 2271-2289
Logical sessions64session_store.rs:14
Idempotency records per session64 (then cleared)session_store.rs:398-400
Resident generations4 + 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-55MAX_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/health and /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 and consume_ticket.
  • crates/muser-server/src/axum_httpd.rs:4845-4916valid_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-16SessionBundle (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_fields DTOs.
  • [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:

  1. 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.
  2. 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.
  3. 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_time survived, 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.0 Gbps installed payload and DFLASH_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]:

DepthDecode meanDecode CVPrefill meanPrefill CV
2,0481.05040.47 %1.03970.12 %
8,1921.04290.32 %1.02080.06 %
16,3841.04140.50 %1.01850.06 %
32,7681.04790.92 %1.01710.05 %
65,5361.02741.39 %1.01630.11 %
131,0081.02770.43 %1.01390.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:
cellacceptance before → afterdecode before → after
python suffix 81921.1 % → 72.7 %0.535 → 1.322
rust 204836.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 --execute it 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.lock via flock (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, pkill cannot run under the lease except through the narrowly shaped --allow-profiler admission for a direct gputrace headless-profile --attach-launched command [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.jsonl entry, both fsynced with directory durability [scripts/accelerator_safe.py:200-224, 427-429]. Every table in this chapter resolves to receipts like these under muser-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: false and 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.md rows (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 …] — under muser-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 restart is 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: native together 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-001SEC-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 mock tags, 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; state containment, 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:

  1. The hypothesis — what we hoped was true.
  2. The experiment — what was actually measured, under what scope.
  3. The receipt — where the evidence lives.
  4. The verdict — what was concluded, in the record’s own words where possible.
  5. 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]:

TraceAcceptanceMeasured tok/sVerifier-only ceiling
Standard (all-accept control)100.00 %110.59125.61
Documentation9.23 %15.5320.15
Python26.31 %11.1740.04
Rust38.07 %15.4155.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 mandatoryvision 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 apparatusenrolled 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 experimentresearch, 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× GX10roadmap, 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 .gputrace capture 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 stabilityopen 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 coveragepartial 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.md rows (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 (the vision lane 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 …] — under muser-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)

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:line tags were verified against the pinned tree 6d0807da (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:

TagSourceBuiltCode
SServing 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 extractionruntime new_library_with_sourcecontext.rs:59-110; 66-name PIPELINES registry metal/encode.rs:21-88
XStrict-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 lanesruntime new_library_with_sourcecontext.rs:111-121; PSO fields metal/encode.rs:205-277
Lllama.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-expressnew_library_with_filecontext.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_groupencode_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).

#StageKernel / functionSrcShader (file:line)Rust dispatch (file:line)Reads → writes (one line)Ch
1Embedding lookupmuser_embedding_q4k (F16 table: muser_embedding_f16)Sshaders/muse_reference.metal:961 (nvfp4.metal:755)encode_embedding_q4k metal/encode/qkv.rs:338 (bind :365); walk decode.rs:5524one u32 token id + one Q4_K embedding row → the [hidden_dim] f32 residual stream11
2Entry norm (weight = ones)llama kernel_rms_norm_mul_f32_4; fallback rms_norm_batchL / Sllama metallib; shaders/ferrite/rmsnorm_batch_tail.metal:1encode_rms_norm_mul metal/encode/norm.rs:244 (bind :270); decode.rs:5535hidden row → normed (RMS, eps 1e-5, × ones)12
3Attention pre-norm — layer 0 only (later layers receive it fused from the previous tail)same as row 2L / Ssame as row 2encode_rms_norm_mul norm.rs:244; decode.rs:5553-5564normedpost_norm with layer.attn_norm12
4Q, K, V, gate projections — one concurrent set of 4 matvecs sharing the input rowllama 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_c1L / Sllama metallib (pick metal/encode.rs:278-280); muse_reference.metal:735 / :790; nvfp4.metal:312 / :738encode_projection decode.rs:6044encode_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-5598post_norm row + the four weight matrices → q, k, v, gate activations13
5Per-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 / Sllama metallib; rmsnorm_batch_tail.metal:1; ferrite/rms_norm_per_head.metal:15 (registry encode.rs:58)encode_qk_norm norm.rs:286encode_rms_norm_mul norm.rs:244; Q call decode.rs:5600-5608, K call :5609-5617128-wide head slices of q and k, normalized in place14
6RoPE — SWA layers only (uses_rope(), config.rs:68-70); interleaved GPT-J pairs; NoPE full layers skip the dispatch entirelyrope_norm_batch_cached (or llama rope_norm_f32 pinned PSO)S / Lshaders/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-5640cached frequency table + q,k → rotated q,k in place14
7aKV store (token-major ring) + attention — SWA, vec-eligiblemuser_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 + Lmuse_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-5693K,V rows → ring slot write_physical; then q + whole ring → attention15, 16
7bAttention — SWA fallback (window not 32-aligned, or no metallib)muser_attention_decode_splitk_f16 + muser_attention_decode_splitk_reduce_f32Smuse_reference.metal:1052 + :1169encode_attention_decode_splitk_f16 attn.rs:708 (binds :748, :776); decode.rs:5695-5723q + ring (split-K partials per workgroup) → attention; geometry splitk_geometry attn.rs:888-89616
7cKV store (head-major plane) + attention — NoPE, vec-eligiblemuser_kv_store_batch_f16 + barrier + llama vec kernel (ns10 = 128)S + Lmuse_reference.metal:1203; llama metallibencode_kv_store_batch_f16 attn.rs:787 (bind :812); attn.rs:437; decode.rs:5728-5770K,V rows → growing plane at position; q + plane → attention15, 16
7dAttention — NoPE fallbackflash_attn_decode_vec_f16_gqa_interleaved + flash_attn_decode_reduce_v2Sshaders/ferrite/flash_attn_decode_vec_contiguous_f16.metal:494; flash_attn_decode_reduce_v2.metal:4encode_ferrite_attention_decode_interleaved_f16 attn.rs:189 (PSOs encode.rs:298-313); decode.rs:5772-5790q + head-major plane (partials) → attention; also re-reads current k,v to dodge a store-load race16
Route ladder predicatesllama_vec_rows = (strict ‖ has_llama_flash_attn_vec) && len>0 && capacity≥32 && (origin_physical==0 ‖ len==capacity); llama_swa = llama_vec_rows && len % 32 == 0decode.rs:5645-565716
8Sigmoid attention-output gatesigmoid_gate_inplaceSshaders/ferrite/sigmoid_gate.metal:7encode_sigmoid_gate metal/encode/gate.rs:7 (bind :17); decode.rs:5793-5799attention × sigmoid(gate) → attention in place17
9o_proj matvecsame stack as row 4L / Ssame as row 4project decode.rs:5800project_tokens :5909-5930encode_projection :6044gated attention + output weights → projected17
10Fused post-attention tail: residual add + post-norm (eps 1e-8) + FFN-norm (eps 1e-5)muser_fused_norm_residual_rms_norm_32sgSshaders/ferrite/rmsnorm_batch_tail.metal:147encode_fused_norm_residual_rms_norm_32sg norm.rs:163 (via …_32sg_batch :190, bind :227); decode.rs:5806-5818normed (residual) + projectedpost_norm; 32 SIMD groups, 1,024 threads, 144 B threadgroup12, 17
11FFN 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 controlffn_q4k_gate_up_silu_4r2s; control path: two row-4 matvecs + muser_silu_mul_inplaceSshaders/ferrite/ffn_fused_tail.metal:496; muse_reference.metal:4encode_ffn_q4k_gate_up_silu_4r2s ffn.rs:10 (bind :25) / encode_silu_mul ffn.rs:38 (bind :49); decode.rs:5819-5862post_norm read once → SiLU(gate)·up written to ffn_gate18
12ffn_down matvecsame stack as row 4L / Ssame as row 4decode.rs:5863-5868:6044ffn_gate + ffn_down weights → projected19
13Fused post-FFN tail: residual + post-FFN-norm (1e-8) + next layer’s attn-norm (last layer: final norm)muser_fused_norm_residual_rms_norm_32sgSrmsnorm_batch_tail.metal:147norm.rs:163; next_norm selected decode.rs:5869-5876, dispatch :5877-5889normed + 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

#StageKernel / functionSrcShader (file:line)Rust dispatch (file:line)Reads → writesCh
14Final RMSNormfused 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 normllama kernel_rms_norm_mul_f32_4 / rms_norm_batchL / Sfused: decode.rs:5869-5888; separate: encode_rms_norm_mul decode.rs:5229-5241hidden → normed logits input20
15LM head matvecsame 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 fasterL / Sllama metallib; nvfp4.metal:738project decode.rs:5892-5897:6044 / qkv.rs:68hidden + vocab projection → logits[vocab]20
16Logit scale + soft capmuser_scale_softcap_inplace; or, to match llama’s graph literally, four ggml unary nodes (scale → tanh → scale)S / Lshaders/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-5905logits × logit_scale (= 1/√26 ≈ 0.196116, GGUF metadata, config.rs:190-192) then tanh at softcap 20, in place20
17Sampling read-backargmax / 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, :125CPU: 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:123full-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):

StageKernel / functionSrcShader (file:line)Rust dispatch (file:line)Reads → writesCh
Embedding (per chunk row)muser_embedding_q4kSmuse_reference.metal:961encode_embedding_q4k_from_u32_buffer qkv.rs:376; decode.rs:1756u32 token buffer row → batch hidden36
Projections, NVFP4 M16 route (16 rows, n_in % 64 == 0)muser_nvfp4_w4a4_quantize_m16 + muser_nvfp4_w4a4_prequant_m16_n32 (B.5)Xnvfp4.metal:468 + :504encode_nvfp4_w4a4_prequant_m16 qkv.rs:13; picked decode.rs:5955-5977activations quantized once, then one weight-stationary 32-row tile per projection36
Projections, small batch 4..=8 rowsllama kernel_mul_mv_ext_{q4,q5,q6}_K_f32_r1_{2..5}Lllama metallib (LlamaMulMvExtPipelines encode.rs:174-179)encode_quantized_matmul qkv.rs:482-508the llama-pinned dispatch boundary; changing it breaks logprob parity (qkv.rs:476-481)36
Projections, 16-row K-quant blocksm16_q4k_n32 / m16_q5k_n32 / m16_q6k_n32Sshaders/ferrite/batch_m16_n32.metal:59 / :266 / :163encode_quantized_matmul qkv.rs:556-579DFlash verify/draft blocks; weight-stationary n32 tile, 6 KiB threadgroup36, 33
Projections, aligned Q4_K batchesmatmul_q4k_batch_sgm_alignedSshaders/ferrite/batch_sgm_q4_aligned.metal:69encode_quantized_matmul qkv.rs:584-601Ferrite’s accepted high-occupancy SIMD-group-matrix GEMM36
Projections, general batchllama kernel_mul_mm_q{4,5,6}_K_f32 (aligned/bounds); fallback muser_matmul_q4k/_q5kL / Sllama metallib; muse_reference.metal:912 / :929qkv.rs:604-640the roofline-flipped batch GEMM regime36
KV store, contiguous routemuser_kv_store_batch_f16Smuse_reference.metal:1203attn.rs:787; decode.rs:4135, :4388chunk K,V rows → plane/ring before attention reads them back36
Attention (a): short chunks, < 20 queriesllama vec kernel, one unmasked launch per query rowLllama metallib (attn.rs:437 family)llama_vec_prefill_route_available decode.rs:65; per-row launches decode.rs:4153-4179q row + visible cache → one attention row36
Attention (b): NoPE at llama chunk boundsmuser_fa_causal_mask_f16 + llama flash_attn_ext_blk (once per chunk) + llama kernel_flash_attn_ext_f16_dk128_dv128S + Lmuse_reference.metal:1514; llama metallibencode_llama_fa_prefill_mask_blk attn.rs:266 (binds :283, :307); encode_llama_flash_attn_prefill_f16 attn.rs:328; decode.rs:4197-4208causal f16 mask + skip/partial/dense block bytes, then the masked causal prefill kernel36
Attention (c): local FlashAttention-2flash_attn_v2; one-query GQA specialization muser_flash_attn_decode_gqa_fa2Sshaders/ferrite/flash_attn_v2.metal:59; flash_attn_decode_gqa_fa2.metal:39encode_flash_attention_v2 attn.rs:13 (specialization :61-79); decode.rs:4227, :4329q chunk + f16 KV cache → attention chunk36
SWA ring wrap: staging shadowmuser_stage_swa_prefill_f16 (chunked) / muser_stage_swa_llama_decode_f16 (single-row, llama’s 256-row-padded indices)Smuse_reference.metal:1240 / :1281encode_stage_swa_prefill_f16 attn.rs:103; encode_stage_swa_llama_decode_f16 attn.rs:145; decode.rs:4264, :4281old ring rows + new chunk → detached F16 shadow; ring metadata committed after (append_batch, decode.rs:4348)36, 23
NoPE prefill fallbackmuser_attention_prefill_flash_f16Smuse_reference.metal:1409encode_attention_prefill_f32 attn.rs:830 (bind :861); decode.rs:4359q 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.

KernelSrcShader (file:line)RoleCh
muser_nvfp4_matvec_c{1,2,4,8,16}Xnvfp4.metal:226plain dequantizing NVFP4 matvec; width-1 is the decode kernel, wider calls cover DFlash verification and bounded prefill7
muser_nvfp4_a16_q8_matvecXnvfp4.metal:622weight-only W4A16 route (no input_scale_inv): activations dynamically quantized to Q8 per 16-block, n_in % 2567
muser_nvfp4_w4a4_matvec_c{1,2,4,8,16}Xnvfp4.metal:312W4A4 integer-dot matvec family (weight-stationary 2/4-column specializations)7
muser_nvfp4_w4a4_m16_n32Xnvfp4.metal:33716-column weight-stationary tile, N=32 output rows7, 36
muser_nvfp4_w4a4_quantize_m16 + muser_nvfp4_w4a4_prequant_m16_n32Xnvfp4.metal:468 + :504exact 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}Xnvfp4.metal:738F16 weights on the NVFP4 lane, incl. the unquantized LM head7
muser_embedding_f16Xnvfp4.metal:755F16 embedding table lookup (row 1’s F16 branch)7
muser_nvfp4_dequant_fixtureXnvfp4.metal:775test fixture: bit-exact dequant of every finite E4M3FN scale7

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):

StageKernelSrcShader (file:line)Rust dispatch (file:line)Ch
Input normrms_norm_batch (ggml rms_norm when present)L / Srmsnorm_batch_tail.metal:1encode_rms_norm_mul norm.rs:244; metal/dflash.rs:10638
q/k/v projectionsdense f32 matmul_f32_batch_tiled (batch ≥ 4) / matmul_f32_batch; kquant sidecar → the B.4 batch stack incl. m16_*_n32 blocksSbatch_f32_support.metal:45 / :7encode_projection metal/dflash.rs:382encode_dense_f32_batch encode.rs:443 / encode_quantized_matmul qkv.rs:4088
QK-norm (draft is Qwen-style: real weights, not folded scales)rms_norm_per_headSferrite/rms_norm_per_head.metal:15encode_rms_norm_per_head encode.rs:589 (bind :605); metal/dflash.rs:1085-11018
RoPE — NeoX pairing, not the target’s interleaved pairsrope_batch_cachedSferrite/rope.metal:566encode_rope_neox_batch_cached encode.rs:638 (bind :674); metal/dflash.rs:1107-11148
Dual-context attention (64-row sink + sliding window)dflash_dual_attention_f32Sferrite/attention_dflash_dual.metal:15encode_dflash_dual_attention encode.rs:690; metal/dflash.rs:11198
o_proj → fused residual+normfused_residual_rms_norm_batchSrmsnorm_batch_tail.metal:72encode_fused_residual_norm encode.rs:554 (bind :578); metal/dflash.rs:11438
gate/up projections → SwiGLUsilu_hadamard_batchSbatch_ffn_activation_tail.metal:14encode_silu_hadamard_batch encode.rs:482; metal/dflash.rs:11658
down projection → residual addresidual_add_batchSbatch_f32_support.metal:155encode_residual_add_batch encode.rs:500; metal/dflash.rs:11788
Final normrms_norm_batch_inplaceSrmsnorm_batch_tail.metal:42encode_rms_norm_inplace encode.rs:525 (bind :531); metal/dflash.rs:11858
Capture pack (target hidden states → draft input)pack_dflash_layer_major_f32; copy_f32_bufferScopy_f32_buffer.metal:18; :3encode_pack_dflash_layer_major encode.rs:769; metal/dflash.rs:7448
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/cycleSbatch_m16_n32.metal:59/163/266encode_quantized_matmul qkv.rs:556-579 via the mirror-SD suffix begin_dflash_verify_suffix decode.rs:329833
Verify side, opt-in multi-column matvecmuser_matvec_multicol_{q4k,q5k,q6k}_c{1,2,4} (macro)Sferrite/matvec_multicol.metal:400MultiColPipelines multicol.rs:84-103; gate MUSER_MULTI_COL_VERIFY multicol.rs:70-82; also the decode-group route encode_quantized_decode_group qkv.rs:23433

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:

  1. QK-norm kernel (row 5). The research map lists rms_norm_per_head as 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_head lives 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.
  2. Sigmoid gate (row 8). Ch 17 quotes muse_reference.metal:680, which at the pin is muser_cross_vendor_sigmoid_gate (the strict variant). The live serving kernel is sigmoid_gate_inplace (ferrite/sigmoid_gate.metal:7, dispatched gate.rs:17); the cross-vendor kernel is the X mirror of the same math. Both are recorded.
  3. 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 wrapper encode_attention_prefill_f32 binds muser_attention_prefill_flash_f16 (attn.rs:861, muse_reference.metal:1409); muser_attention_prefill_f32 is in the 66-name registry (encode.rs:42) but has no dispatch on this route.
  4. Chapter in-body line tags. Several chapters cite line numbers inside a kernel body rather than its kernel void line (Ch 11 muse_reference.metal:973 for a kernel that starts :961; Ch 15 :1224 vs kernel start :1203; Ch 16 flash_attn_decode_vec_contiguous_f16.metal:519 vs :494). All resolve within the quoted kernel; this table cites the kernel void lines.

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 commit 6d0807da (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.

LanePrefillDecodeSpeculativeIntended 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 GX10Mac NVFP4 weights (same decode)Verification onlyDeterministic anchor for bounded-logit policies [docs/muser-architecture.md §Lanes; Ch 32]

Two boundaries worth restating (Ch 32–33):

  • MUSER_NVFP4_EXACT does not exist in Rust. It is read only by the producer-side Python on the node — resident_producer.py:32-36 maps it to the closed exact/native producer lane (values other than 0/1 abort), and the native benchmark refuses =1 outright 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 enum Nvfp4ProducerMode (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)

FlagMeaning (from the consuming code)DefaultChapter
MUSER_GGML_METALLIBPath 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 exist4, 13, 16
MUSER_GGML_METALLIB_RECEIPTProvenance 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)unset4, 38
MUSER_CROSS_VENDOR_QKSwitches 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 laneunset — serving fast-math kernels29, 32
MUSER_CROSS_VENDOR_ROPE_CACHEPath 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 frequencies32
MUSER_CROSS_VENDOR_ROPE_BYPASSSkips the RoPE dispatch entirely in cross-vendor comparisons (rope.rs:65-67)unset — RoPE runs14, 32
MUSER_FERRITE_FFN_GATE_UPUses 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 control18
MUSER_NO_FUSED_PREFILL_DUAL_NORMDiagnostic control: splits the fused dual-eps norm tails into their separate kernels (decode.rs:1330-1331)unset — fused tails on12, 35
MUSER_SERIAL_PREFILL_DISPATCHEncodes the prefill graph serially (non-concurrent dispatch type) (decode.rs:1332-1333)unset — concurrent dispatch35, 36
MUSER_NO_LLAMA_FA_PREFILLForces 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 queries36
MUSER_MULTI_COL_VERIFYDefault-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 projection33
MUSER_NO_M16_N32Disables 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 eligible33, 36

Profiling and diagnostics

FlagMeaningDefaultChapter
MUSER_METAL_PHASE_PROFILERuns 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 dispatchesunset35
MUSER_METAL_BATCH_PHASE_PROFILESame 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)unset35, 36
MUSER_STREAM_DECODE_PROFILE=1 installs streamed-decode diagnostics retrievable via take_stream_decode_diagnostics (decode.rs:31-35)unset10
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)unset35
MUSER_METAL_CAPTURE_PAUSE_MS + MUSER_METAL_CAPTURE_READY_FILEGPU 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)unset35
MUSER_TTFT_CAPTURE_READY_FILE + MUSER_TTFT_CAPTURE_PAUSE_MSServer-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 requestunset31, 38

DFlash (speculative lane)

FlagMeaningDefaultChapter
MUSER_DFLASH_VERIFY_LENOverrides 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 length33
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 on33
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 trace33, 38
MUSER_DFLASH_PRE_DRAFT_IDLE_MSDiagnostic idle injection before draft rounds; requires MUSER_DFLASH_CYCLE_TRACE=1 (spec.rs:1718-1726)unset33
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)unset33
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-pass33
MUSER_DFLASH_SINK / MUSER_DFLASH_WINDOWDiagnostic-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_window8
MUSER_DFLASH_PREPARE_TRACETrace prints around prepared-draft execution (spec.rs:1083-1140)unset8
MUSER_DFLASH_CAPTURE_FC_PIPELINESelects the public-CoreML FC-slice capture pipeline inside the staged target verifier — the previously invisible v8 overlap cost (spec.rs:456-458)unset33

GX10 / wire (producer-side, on the node)

These are Python-side flags in scripts/gx10/; the Mac Rust code only measures what they configure.

FlagMeaningDefaultChapter
MUSER_NVFP4_EXACTProducer-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 — native28, 32
MUSER_GX10_PACING_BYTES_PER_SECONDOverrides 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_TRACEGates per-segment wire telemetry; unset, empty, or 0 disables (muser_v2_send.py:956-960)off31
MUSER_VLLM_WATCHDOG_SECONDSWatchdog timeout for the resident vLLM producer container (resident_producer.py:24)900 s28
MUSER_DFLASH_JOBS_FIFOFIFO 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 runtime28, 33
MUSER_ACCELERATOR_LEASE_WAIT_SECONDSBounded wait (clamped 0–60 s) for the node’s accelerator lease before the producer fails (resident_producer.py:40-44)0 — no wait28, 38

Qualification and bench discipline

FlagMeaningDefaultChapter
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 refused38
MUSER_REMOTE_QUALIFYPath override for the qualification binary the node tooling drives (muser-server/src/node/mod.rs:135-139)<repo>/target/release/muser-remote-qualify30, 38
MUSER_REMOTE_QUALIFY_SERIALRuns the remote qualification reps serially instead of interleaved (remote.rs:1666, :2534)unset — interleaved38
MUSER_REMOTE_CACHE_DIFFEmits a full KV-plane diff when a qualification cell diverges (remote.rs:590, :1624)unset32
MUSER_REMOTE_FIRST_DIVERGENCEEnables the first-divergence hunt (the layer-0 ladder chase of the wizard story; remote.rs:319, :1921)unset32
MUSER_REMOTE_CACHE_PROBELayer-0/token-one KV probe used on divergence (remote.rs:1852)unset32
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 Rustunset in the engine29, 32

Server runtime

FlagMeaningDefaultChapter
MUSER_HOMEThe 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 paths37
MUSER_HOST / MUSER_PORTBind host and port, wired through clap’s env on the server CLI (cli.rs:226-232, :407-411)port 494937

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_ABI is not an env flag at the pin — it is the const cache-ABI identity string "muser-muse-glimmer-f16-logits-v2" in muser-kvpack/src/layout.rs:18 (the research map’s §14 row for it is wrong; the string simply matched the MUSER_* grep).
  • MUSER_MC_NSG / MUSER_MC_NR0 are shader-side constants that must match the dispatch shape, documented at multicol.rs:41-44 — not runtime flags.
  • MUSER_MODEL / MUSER_MODEL_SHA256 gate 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 ## References section (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] — the SHAPES table: 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]MetalContext with
  • [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_k and
  • [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] — the release_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]MacKey
  • scripts/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_fast and the shader lineage from
  • [docs/release-provenance.md:822-823] — logit_scale=0.196116 with
  • [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 (the vision
  • [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 v9
  • crates/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.md row 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 …] — under muser-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,” and maxThreadsPerThreadgroup / threadgroup-memory limits (quoted across Part I and Ch 4).
  • [Metal-SS] — Apple, Metal Shading Language Specification: “Address Spaces,” “Function Constants,” “SIMD-group Functions” (the simdgroup operations 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/**/secrets and pki dirs are never read; scripts/accelerator_safe.py gates 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.md ground rules).

Verification recipe

  1. cd <muser-checkout> && git rev-parse HEAD must print the pinned commit. If it does not, quotes in this book may have drifted — check git diff <pin>..HEAD -- <quoted path> before trusting a file:line.
  2. Every [crates/...:LINE] tag in a chapter must resolve to the quoted content at the pin.
  3. 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):

  1. What it computes — the math, in plain words + one formula block.
  2. Why it exists — the role in the transformer; what breaks if you skip it.
  3. The matrix operation, explained — first time a matmul/dot/softmax/reduction appears, draw the shapes and show a 2×2 worked example by hand.
  4. The Metal kernel — quote the kernel void signature and the inner loop from source (with file:line tags). Explain line by line.
  5. The Rust dispatch — the encode/dispatch wrapper, grid/threadgroup sizes, buffer binding. Quote it.
  6. The access pattern — what memory is read, in what order, how much. This is where the bandwidth story lives.
  7. Tradeoffsrequired 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.
  8. 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.”
  9. 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:line tags, read from the pinned Muser tree at <muser-checkout> (see PINNED.md). Convention:
    // crates/muser-engine/src/shaders/ferrite/matvec_multicol.metal:17
    kernel void matvec_q4k_f32_v4(...) { ... }
    
    For 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 ## References section.
  • 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 ledger docs/goal-parity-ledger-2026-08.md.
    • [claims #N]docs/launch-claims.md row N.
    • [receipt muser-receipt://...] — retained evidence.
    • [scripts/...] — tooling, including scripts/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:line tag 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.