Skip to content

llama: published MLX affine quants (8-bit and 4-bit) read in place, beside the bf16 default (builds on #1415) - #1416

Closed
BobClawblaw wants to merge 7 commits into
ashhart:mainfrom
BobClawblaw:llama-affine-pr
Closed

BobClawblaw wants to merge 7 commits into
ashhart:mainfrom
BobClawblaw:llama-affine-pr

Conversation

@BobClawblaw

@BobClawblaw BobClawblaw commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

What

The llama family serves published MLX affine quants (8-bit and 4-bit, groups of 64) of a Llama release in place, as mlx_lm writes them, beside its bf16 default (builds on #1415). Nothing is converted, by us or at load. Checked on two community quants of Nanbeige4.3-33B, pinned. They are lossy; their cost against the release is measured below, and the bf16 release stays the recipe's default.

How

  • weights.zig: Linear = union{bf16, affine}. Each of q, k, v, o, gate, up and down is read as its own {weight (u32 words), scales, biases} triple, and the embedding and head as mlx_lm quantizes them. Shapes, dtypes and byte extents are checked at load. config.zig admits a uniform quantization table (affine, groups of 64, 4 or 8 bits) and refuses anything else by name.
  • Decode windows run the core's row kernels (row_projection.zig), and prompt chunks the core's tensor-unit affine matmul (affine_mm.zig), as the other affine families do.
  • The embedding's rows come from a new family kernel, ll_embed_affine (zig/kernels/metal/llama/glue.metal), which computes MLX's dequantize bf16(bf16(scale * q) + bias).
  • Kernel contract: ll_embed_affine has gates 1-4 at 4 and 8 bits (zig/tests/contract/metal_llama.zig) and a limits entry. affine_mm at 8 bits in groups of 64, a shape the published 8-bit quants use, is registered in metal_main.zig and the registry. tf-kernel-contract --only llama: SUMMARY 23 kernels: 23 pass gates 1-4, 0 fail, 0 known findings; 0 controls: 0 red as planted, 0 missed; 0 limit problems; affine_mm q8 g64 passes gates 1-4.
  • capabilities --json lists bf16, mlx-q4g64 and mlx-q8g64 for llama.
  • The sums scratch is allocated only for quantized checkpoints, so the bf16 path is as in llama: Nanbeige4.3-33B on Metal, the bf16 release served as published, every kernel in the contract #1415.
hf download pratikgx/Nanbeige4.3-33B-MLX-4bit --revision 883c8bda53b2062ff7e37f178ccf503c79674797 --local-dir nanbeige4.3-33b-4bit
hf download fcmeyer/Nanbeige4.3-33B-MLX-8bit --revision 5b206655a9974094df4258cc2381765ee6329af2 --local-dir nanbeige4.3-33b-8bit

Both state Apache-2.0, like the release.

Cost against the release (M5 Ultra; the HF reference and harness plans of #1415)

The reference is HF's bf16 run of Nanbeige/Nanbeige4.3-33B @ c1093c501cc6460427c0ac634db7223d0450c0ee over 1,500 teacher-forced positions. MLX is MLX 0.32.4 with mlx_lm 0.32.2 running the same quant files.

bf16 8-bit 4-bit
engine vs HF: mean KL, top-1 0.0015, 99.1% 0.00234, 98.7% 0.0849, 91.1%
MLX vs HF: mean KL, top-1 0.00272, 98.7% 0.0851, 90.7%
engine vs MLX: mean KL, top-1 0.00255, 98.7% 0.00166, 98.5%
greedy chats equal to MLX's (64 tokens, of 6) 2 3
greedy chats equal to HF's (64 tokens, of 6) 3 4 0 (all part within 16 tokens)
decode, 1 stream / 2 streams together 16.4 / 28-32 tok/s 27.9-28.4 / 49.2-51.5 46.0-46.7 / 76.5-79.3
cold prompt 2.1k / 8.3k / 33k / 61k tokens 1,259 / 1,313 / 1,074 / 866 tok/s 1,268 / 1,253 / 1,022 / 832 1,369 / 1,348 / 1,080 / 872
weights 61.7 GiB 32.77 17.35

The engine is as close to the release as MLX is on each quant. 16- and 7-row windows and one-row steps agree bit for bit on both. Every harness serving check passes on both: structured 18/18 (drafted == plain 12/12), tools 5/5, sampling 9/9, turns resumed 5,493 of 5,649 tokens, soak 60/60, concurrent 12/12, streaming, multi-turn, stop, thinking, max_tokens.

Receipt

  • Environment: TensorFold at this branch (main d0c9dc9 plus these commits), Zig 0.17.0, one Apple M5 Ultra (256 GB unified, macOS 27, tensor units). Nanbeige/Nanbeige4.3-33B @ c1093c501cc6460427c0ac634db7223d0450c0ee, pratikgx/Nanbeige4.3-33B-MLX-4bit @ 883c8bda53b2062ff7e37f178ccf503c79674797 and fcmeyer/Nanbeige4.3-33B-MLX-8bit @ 5b206655a9974094df4258cc2381765ee6329af2, each served with --context 131072 --parallel 2 --prompt-cache-gib 8 and LLAMA_LOAD_LIMIT_GB=150, with an idle server of another model loaded beside it.
  • Exactness, tools/bench_concurrent.py --levels 1,2 --tokens 128 --temperatures 0,0.7 --alone --serial on each quant: every reply equals its request alone and with "draft": false (alone 12/12, serial 12/12, failed 0, on each).
  • Decode speed, tools/bench_openai.py --tokens 256 --reps 3, bf16 → 8-bit → 4-bit:
    • greedy: 16.4 → 28.1 → 46.2 tok/s (chat) and 18.4 → 28.1 → 46.3 (raw prompt)
    • sampled at 0.7: 16.2-16.3 → 27.8 → 45.4
    • two streams, bench_concurrent, greedy: 31.3-34.4 → 49.8-50.4 → 76.4-76.8
  • Prompt speed, tools/bench_cache.py --require-cold, a 9,000-token prompt cold: 7.18 s (8-bit) and 6.64 s (4-bit). Repeated raw-token prompts in bench_cache.py report cached_tokens 0: the family's prompt cache resumes chat turns, and the harness's turns check resumes 5,493 of 5,649 tokens in 0.24-0.28 s against 4.15-4.49 s cold. The cold first request is what the receipt asks for.
  • bf16 A/B, llama: Nanbeige4.3-33B on Metal, the bf16 release served as published, every kernel in the contract #1415's head against this one on the release: the 1,500- and 64-token logits are bit-identical, a greedy chat is identical, and the 64-row bench step is 62.0 / 62.3 / 61.6 ms against 62.4 / 61.7 / 61.5 ms.
  • Every differing choice, judged by llama: Nanbeige4.3-33B on Metal, the bf16 release served as published, every kernel in the contract #1415's llama_teacher_judge.py against MLX's one pass, MLX's decode steps and a float64 pass on the quant's exact weights: on the 8-bit quant 1,481 / 1,484 / 1,478 of 1,500 equal (red at 315, 1370, 1447, 1461 / 401, 425 / 425, 1370, 1447), on the 4-bit 1,477 / 1,482 / 1,479 (red at 1321 / none / 533, 600, 1355, 1418). At every red position the exact margin is under 0.46; the engine's logit error against the float64 pass (0.126 and 0.141 root-mean-square at the top tokens) is MLX's (0.117-0.133); MLX's own paths are red against it at 2-5 positions each. The recipe has both tables and the greedy chats' partings.
  • No other model server or GPU job on the Mac (28 Oct 2026; a CPU float64 reference run overlapped the first minutes): the bf16 release's logits bit-identical to llama: Nanbeige4.3-33B on Metal, the bf16 release served as published, every kernel in the contract #1415's branch; both quants' logits bit-identical to the judged runs and exact 0 differing rows; on the 4-bit quant, concurrent == solo for six prompts and resumed == fresh for three chats (8,521-8,528 tokens kept).
  • Tests run, on these commits over main 03d08f1 (the pushed heads are rebased onto d0c9dc9, which adds only a CI workflow time limit): on llama: a smaller Llama drafting for the served one (--drafter), replies unchanged (builds on #1415) #1417's head, which carries all three, zig build test, test-kernel-contract, test-golden, fuzz-cortheon, dev-tools, test-server-security and test-pull-security all exit 0, and on llama: Nanbeige4.3-33B on Metal, the bf16 release served as published, every kernel in the contract #1415's and llama: published MLX affine quants (8-bit and 4-bit) read in place, beside the bf16 default (builds on #1415) #1416's heads zig build test and test-kernel-contract exit 0; zig fmt --check is clean and the lean check finds 0 new problems (428 on main and on each head); llama_teacher_judge_test.py passes; tf-kernel-contract --only llama on the M5 Ultra: SUMMARY 23 kernels: 23 pass gates 1-4, 0 fail, 0 known findings; 0 controls: 0 red as planted, 0 missed; 0 limit problems (21 kernels on llama: Nanbeige4.3-33B on Metal, the bf16 release served as published, every kernel in the contract #1415's head). Rebuilt on the rebased heads, the bf16 release's 1,500-position logits are bit-identical to the judged run on all three, both quants' to theirs, and a drafted greedy chat (128 tokens) equals the plain one. The pushed heads differ from the ones CI ran on only in comments and docstrings (the lean check's one-line rule); fmt, lean, the judge's tests, the build and zig build test were rerun on them.
  • Pushed head: rebased onto main d0c9dc9 (Release 1.1.8 and a CI workflow time limit), whose commits since the last push touch none of this family's files or the paths it serves through.
  • Not run: the harness quality suite; CUDA, HIP, M1-M4.

@ashhart

ashhart commented Oct 27, 2026

Copy link
Copy Markdown
Owner

Nothing converts at load, which is right. But when quantization is set, weights.load requires fused qkv_proj and gate_up_proj tensors. A real MLX affine checkpoint (mlx_lm's split q/k/v and gate/up) is therefore refused with a missing-tensor error, and only llama_convert.py output loads. We'd be shipping our own lossy derived weights (4-bit: KL 0.082, top-1 91.1%) rather than serving a user's MLX checkpoint as published. Please read MLX's own split layout in place, and leave conversion to the user's tools.

Also: capabilities --json still advertises only bf16. The bf16 path gains a sums scratch buffer, so its "bit-identical" claim needs the device A/B.

@BobClawblaw

Copy link
Copy Markdown
Contributor Author

Thanks, agreed. Pushed 0500646 (one commit on #1415's head f92916d, which is rebased onto main 15a0b2a):

MLX's own layout, read in place. weights.zig now reads mlx_lm's split checkpoint as written: each of q, k, v, o, gate, up and down is its own {weight (u32 words), scales, biases} triple (Linear = union{bf16, affine}), and the embedding and head are quantized as mlx_lm leaves them. Decode windows use the core's row kernels (row_projection.zig) and prompt chunks its affine matmul (affine_mm.zig), as the other affine families do. The embedding's rows come from a new family kernel, ll_embed_affine in zig/kernels/metal/llama/glue.metal, which computes MLX's dequantize (bf16(bf16(scale * q) + bias)). tools/zig/llama_convert.py and the fused qkv/gate_up paths are gone. Nothing is converted, by us or at load.

Checked on two published community quants, pinned: pratikgx/Nanbeige4.3-33B-MLX-4bit @ 883c8bda53b2062ff7e37f178ccf503c79674797 and fcmeyer/Nanbeige4.3-33B-MLX-8bit @ 5b206655a9974094df4258cc2381765ee6329af2 (both Apache-2.0, like the release). Against the same HF bf16 reference as #1415 (1,500 teacher-forced positions), and against MLX 0.32.4 with mlx_lm 0.32.2 running the same files:

8-bit 4-bit
engine vs HF: mean KL, top-1 0.00234, 98.7% 0.0849, 91.1%
MLX vs HF: mean KL, top-1 0.00272, 98.7% 0.0851, 90.7%
engine vs MLX: mean KL, top-1 0.00255, 98.7% 0.00166, 98.5%

The engine is as close to the release as MLX is on each quant. 16- and 7-row windows and one-row steps agree bit for bit on both. Every harness serving check passes on both: structured 18/18, tools 5/5, sampling 9/9, turns, soak 60/60, concurrent 12/12, streaming, multi-turn, stop, thinking, max_tokens.

Contract. ll_embed_affine is registered (gates 1-4 in zig/tests/contract/metal_llama.zig for 4 and 8 bits, a limits entry in metal_llama_all.zig): SUMMARY 23 kernels: 23 pass gates 1-4, 0 fail, 0 known findings; 0 controls: 0 red as planted, 0 missed; 0 limit problems. The 8-bit published quants run affine_mm at 8 bits in groups of 64, a shape it had not been registered at, so that is added to metal_main.zig and the registry too: it passes gates 1-4.

Capabilities. capabilities --json lists bf16, mlx-q4g64 and mlx-q8g64.

bf16 A/B. The sums scratch is now allocated only for quantized checkpoints, so the bf16 path allocates what it did in #1415. On the M5 Ultra, #1415's head against this one on the release: the 1,500-position logits are bit-identical, the greedy chat is identical, and the bench step is 62.0 / 62.3 / 61.6 ms against 62.4 / 61.7 / 61.5 ms (three runs each, 64-token windows).

The PR body is rewritten for the new scope (published quants, no conversion). On these commits over main a1e6d34: zig build test, test-kernel-contract, test-golden, fuzz-cortheon, dev-tools, test-server-security, test-pull-security all exit 0; zig fmt --check is clean; the lean check finds 0 new problems (428 on main and on the head); tf-kernel-contract --only llama on the M5 Ultra: SUMMARY 23 kernels: 23 pass gates 1-4, 0 fail, 0 known findings; 0 controls: 0 red as planted, 0 missed; 0 limit problems. After the rebase, both quants' 1,500-position logits and a greedy chat are bit-identical to the measured build. Main then took MOSS-Image (#1457), DFlash2 and MTP fixes for other families, a GLM CUDA log message and an opt-in prompt-cache rule for CUDA families (off by default): none of it in this family's files or the paths it serves through. Rebased onto main 15a0b2a for this push, the stack's top head (#1417's, which carries these commits) reran the build, tf-kernel-contract --only llama, test-kernel-contract, zig fmt --check and the bf16, quant and drafted bit-identity checks, and all pass.

@BobClawblaw BobClawblaw changed the title llama: MLX affine 8-bit and 4-bit conversions of a release, served beside the bf16 default (builds on #1415) llama: published MLX affine quants (8-bit and 4-bit) read in place, beside the bf16 default (builds on #1415) Oct 28, 2026
@ashhart

ashhart commented Oct 28, 2026

Copy link
Copy Markdown
Owner

Thanks @BobClawblaw. The published split MLX layout is now read in place (zig/src/families/llama/weights.zig:70, :117, :123), nothing is converted at load (:91), and the formats are advertised (zig/src/native/metal.zig:54).

Two asks:

  1. The quants against their own references. The 4-bit and 8-bit checkpoints agree with their quantised references at 98.7% and 98.5% of teacher-forced positions, and in two and three of six greedy chats (docs/recipes/nanbeige-4.3-33b-metal.md:154-155). Please list every differing next-token choice for both, with the contenders' margins, as llama: Nanbeige4.3-33B on Metal, the bf16 release served as published, every kernel in the contract #1415's table does, and make the comparison fail when a difference isn't a tie. A difference that isn't a tie needs its cause found, as with llama: Nanbeige4.3-33B on Metal, the bf16 release served as published, every kernel in the contract #1415's row 1370.
  2. A clean receipt with one model process: the bf16 path against llama: Nanbeige4.3-33B on Metal, the bf16 release served as published, every kernel in the contract #1415's, then concurrent against solo and resumed against fresh for one quant.

#1415's second point (the exactness mode always exits 0) applies to this branch too.

A new `llama` family for the native Metal engine: dense Llama-architecture
checkpoints (heads of 128, default RoPE, no biases, an untied head) in bf16,
read in place from the shards with nothing converted. It keeps HF
modeling_llama's rounding points (a bf16 residual, LlamaRMSNorm, RoPE with
bf16 cos and sin, SiLU), runs prompt chunks on gemm_nax's tensor-unit GEMM and
decode windows on a simdgroup-a-row matvec, decode attention on sdpa_vector
lanes at absolute positions, and sampled draws on tf_sample_full. tf-llama-run
checks greedy tokens, every position's logits, and that windows and one-row
steps agree bit for bit.

Measured on an M5 Ultra against HF's bf16 run of the release: 1,500 positions
teacher-forced at mean KL 0.0015 and top-1 99.1%; decode 16.4 tokens/s a
stream and 28-32 with two; cold prompts 1.3k tokens/s at 8k tokens and 563 at
131k. docs/recipes/nanbeige-4.3-33b-metal.md has the rest.
The family's Metal kernels through gates 1-5 (zig/tests/contract/metal_llama*.zig, the roll call and sources in
contracts.zig): the glue (embedding, the norms at HF's rounding points, the residual add, RoPE with bf16 cos and sin,
the SiLU gate, the decode attention's partials and their merge, the argmax draw by its stated NaN rule), the six
row-projection forms, the four GEMM forms and the prompt attention at both served geometries, each with its float64
reference, served shapes and tiles, launched as forward.zig launches it; gate 5 reads the pipelines on this GPU.

tf-kernel-contract --only llama on the M5 Ultra: 21 kernels, 21 pass gates 1-4, 0 fail, 0 known findings, 0 limit
problems.
…out tensor units by name; the teacher-forced misses and the licence in the doc

tf-llama-run takes exact's window width as an argument and loses its
environment switches (LLAMA_LAYERS, LLAMA_WINDOW).
… near-tie, a float64 reference, the doc's misses and a one-process receipt
…eside the bf16 default

The family reads an MLX affine checkpoint as mlx_lm writes it: each projection
its own {u32 words, bf16 scales, bf16 biases} triple in groups of 64 at 4 or 8
bits, the embedding and head quantized, the norms bf16, and config.json's
uniform quantization table. Nothing is converted, by us or at load. Decode
windows run the core's row kernels, prompt chunks its tensor-unit affine
matmul; the embedding's rows come from a new family kernel, ll_embed_affine,
MLX's dequantize bf16(bf16(scale * q) + bias), registered with the contract.
affine_mm's 8-bit groups-of-64 form is registered too. capabilities lists
bf16, mlx-q4g64 and mlx-q8g64. The sums scratch is allocated only for
quantized checkpoints, so the bf16 path is as before.

Nanbeige4.3-33B on an M5 Ultra, two published quants pinned
(pratikgx/Nanbeige4.3-33B-MLX-4bit, fcmeyer/Nanbeige4.3-33B-MLX-8bit),
against HF's bf16 run of the release over 1,500 positions: 8-bit mean KL
0.00234, top-1 98.7% (MLX on the same files: 0.00272); 4-bit 0.0849, 91.1%
(MLX: 0.0851). Windows and one-row steps agree bit for bit on both; the
release's logits are bit-identical to before. The recipe doc has the tables;
the quants are lossy and the release stays the default.
…wo paths and a float64 pass, and a one-process receipt
@BobClawblaw

Copy link
Copy Markdown
Contributor Author

Thanks. Pushed 138b785 (one commit on #1415's new head):

1. Every differing choice on both quants. tools/zig/llama_teacher_judge.py (from #1415, the repository's near-tie rule, exits 1 on anything else) judges the engine's 1,500 teacher-forced positions on each quant against three references on the same files:

  • MLX 0.32.4 / mlx_lm 0.32.2, one pass over all positions (the arithmetic of its prompts);
  • MLX's one-token decode steps (the arithmetic of its greedy replies);
  • tools/zig/llama_f64_reference.py on the quant: its weights read as scale * q + bias exactly, float64, one rounding at the end.
engine against 8-bit 4-bit
MLX, one pass 1,481 equal; red at 315, 1370, 1447, 1461 1,477 equal; red at 1321
MLX, decode 1,484 equal; red at 401, 425 1,482 equal; all 18 near-ties (pass)
float64 1,478 equal; red at 425, 1370, 1447 1,479 equal; red at 533, 600, 1355, 1418

The recipe now has a table per quant with every position where the engine differs from any of them: each reference's logits for the contenders and both verdicts.

Causes. At every red position the exact margin between the contenders is under 0.46: 8-bit 401 0.39, 425 three tokens within 0.13, 1370 three within 0.16, 1447 0.45; 4-bit 533 0.33, 600 0.42, 1355 three within 0.45, 1418 three within 0.26. Every bf16 path carries more error than that. Against the float64 pass, at each position's exact top 8 tokens and less the row's common shift, the root-mean-square logit error over all 1,500 positions is:

engine MLX decode MLX one pass HF bf16
bf16 release 0.119 0.120
8-bit 0.126 0.117 0.126
4-bit 0.141 0.133 0.128

MLX's own paths are red against the float64 pass at the same kind of positions: on the 8-bit quant at 425 and 1461 (one pass) and 1370 and 1447 (decode), on the 4-bit at four (one pass) and five (decode), four of them shared with the engine's. The largest engine-MLX difference, 8-bit 1461 (NaN at 38 in MLX's one pass, 32 in the engine), is MLX's one pass: its own decode agrees with the engine and the float64 pass (non first). I also checked whether rounding the dequantized weights to bf16 as mx.dequantize does would explain the reds: a float64 pass on those weights gives the same picture (engine and both MLX paths red at five positions each on the 4-bit quant), so it doesn't.

The greedy chats: they part from MLX's on four of six chats (8-bit) and three (4-bit). At each parting token, both sides' logits as each greedy run reaches them (the engine's chunks and one-row steps; mlx_lm's generate_step, the prompt but its last token in one pass, then steps):

quant chat position MLX's top two engine's top two rule
4-bit chat0t 4 152382 42.5, 152402 42.5 152402 42.75, 152382 42.5 near-tie
4-bit chat1 28 570 37.5, 28746 37.5 28746 37.75, 570 37.25 red
4-bit chat2 40 21933 19.125, 5562 19 5562 19.125, 21933 19.125 near-tie
8-bit chat0 41 11859 38.25, 20011 38.25 20011 38.5, 11859 38.25 near-tie
8-bit chat1 2 15686 39.25, 24225 39.25 24225 39, 15686 38.75 near-tie
8-bit chat2 28 145762 26.5, 152404 26.5 152404 26.625, 145762 26.375 red
8-bit chat3t 30 152367 36.25, 152411 36.25 152411 36.25, 152367 36 near-tie

At both red ones the float64 pass picks the engine's token (4-bit chat1: 28746 37.7532 against 570 37.4911; 8-bit chat2: 152404 26.5785 against 145762 26.4806). MLX rounds each pair to a tie, and its argmax takes the lower id.

Row 1447 (red on the 8-bit quant against float64, where MLX's decode ties the same two tokens) has a larger engine error than most, so I traced it on the bf16 release, where it is a near-tie. I stopped the engine's forward after each layer and after each layer's attention (a local probe, not in the PR), and compared with HF's hidden states and the float64 pass. The engine's residual tracks HF's error through layer 35. At layers 36-40 its attention sublayers add 1.7-4 times HF's error and its MLPs grow theirs by 18-21% against HF's 7-10%. After that both grow at the same rate, ending at 12.4% (engine) against 4.8% (HF) relative error at layer 51. The excess sits mostly in the massive-activation channel 4086 (750-1,700 over those layers, a bf16 step of 4-8). Rounded at the head, the engine ties instead and with, which float64 puts 0.37 apart. I haven't narrowed it to one operation: every kernel passes the contract's gates against its reference, and over all 1,500 positions the engine's error equals HF's (0.119 against 0.120). If you'd like this traced to a kernel, I'll do that before the merge.

exact (#1415's, now failing on any difference) finds 0 differing rows on both quants after the 64- and 1,500-token prompts.

2. A clean receipt, no other model server or GPU job on the Mac, this branch's build (a CPU float64 reference run overlapped its first minute):

  • The bf16 release's 1,500-position logits are bit-identical to llama: Nanbeige4.3-33B on Metal, the bf16 release served as published, every kernel in the contract #1415's branch, and a greedy chat's tokens equal.
  • Both quants' 1,500-position logits are bit-identical to the runs judged above.
  • 4-bit, concurrent against solo: six prompts sent two at a time on --parallel 2, every token array equal to the request alone.
  • 4-bit, resumed against fresh: three chats with an 8,500-token first turn; the second turn resumed from the prompt cache (8,521-8,528 of 8,554-8,558 tokens kept) returns the same ids as without the cache.
Token ids
prompt 0, alone and two at a time: [2359, 825, 3324, 2028, 152402, 152350, 3002, 152382, 14634, 261, 9120, 873, 349, 7605, 269, 311, 152369, 397, 58625, 1053, 72009, 152363, 358, 261, 6312, 2488, 152361, 14650, 2888, 1795, 2392, 849, 404, 7615, 152361, 14650, 1368, 689, 4614, 152382, 311, 152369, 397, 58625, 1053, 152361, 14650, 9399, 48299, 152361, 11609, 58625, 152382, 375, 152374, 152368, 5318, 152368, 152363, 375, 152374, 152367, 5318, 152367, 152361, 17654, 4424, 152361, 130712, 152361, 40596, 3741, 152470, 17654, 8873, 37226, 152361, 23425, 2488, 152361, 14650, 2603, 3324, 358, 2392, 152361, 28972, 754, 1565, 4230, 152470, 18628, 38503, 152361, 13, 166104, 13, 13, 7255, 8792, 13, 1537, 4182, 42936, 152374, 152349, 152382, 483, 152373, 4229, 483, 152382, 13, 264, 7292, 13, 264, 11859, 269, 311, 152369, 397, 58625, 1053, 72009, 152361, 13, 13]
prompt 1, alone and two at a time: [2359, 825, 298, 6595, 298, 2028, 152382, 471, 129236, 289, 1358, 18446, 2542, 269, 13690, 322, 6361, 1229, 14650, 2603, 3324, 5969, 1358, 18446, 2888, 152361, 14650, 38503, 152361, 40194, 55146, 11577, 152382, 27997, 102359, 1480, 13969, 25842, 602, 152397, 6361, 15686, 6720, 152397, 1582, 1545, 11577, 417, 50595, 7923, 455, 3307, 152361, 28972, 1358, 18446, 152361, 13, 166104, 13, 13, 31952, 3745, 4722, 1094, 10434, 152363, 297, 724, 388, 24225, 6558, 152440, 152350, 10569, 152363, 3545, 7660, 9744, 269, 13969, 25842, 602, 8221, 703, 269, 3961, 4819, 152361, 675, 11577, 152363, 2403, 55146, 11577, 152363, 22417, 6361, 1831, 289, 544, 11280, 2665, 269, 13690, 152361, 1107, 261, 816, 152363, 724, 457, 1277, 663, 3030, 442, 269, 7542, 152363, 457, 1125, 269, 13690, 152440, 152350, 23835, 6361, 1831]
prompt 2, alone and two at a time: [2092, 1445, 152441, 152443, 152700, 9004, 5157, 14636, 29362, 2442, 67613, 10097, 8215, 152372, 152930, 5562, 152431, 152700, 9004, 152389, 1112, 24621, 4227, 29362, 152473, 152420, 12066, 152388, 6681, 152425, 152372, 152593, 957, 87446, 133816, 27067, 957, 152372, 18763, 1818, 13131, 152432, 29362, 6615, 2137, 152389, 680, 152700, 9004, 152473, 152367, 152361, 124405, 4268, 20239, 5848, 152372, 152836, 53513, 957, 154119, 152464, 60356, 133816, 152404, 5789, 3052, 3432, 152389, 152375, 152361, 29666, 1008, 152441, 57111, 152442, 152468, 10617, 152372, 9329, 2137, 152545, 45352, 12209, 6615, 4323, 152372, 4143, 7374, 152466, 152667, 6499, 152627, 152389, 4586, 53083, 152389, 13, 166104, 13, 13, 29362, 4268, 20239, 5848, 152372, 152836, 53513, 957, 154119, 152464, 60356, 133816, 152404, 5789, 3052, 3432, 152389, 82372, 152441, 57111, 152442, 152468, 10617, 152372, 9329, 2137, 152545]
prompt 3, alone and two at a time: [2359, 825, 298, 6595, 298, 2028, 152382, 471, 1886, 2053, 4443, 290, 261, 13695, 4055, 152363, 609, 1326, 950, 1229, 14650, 2603, 38503, 2053, 3517, 152361, 28972, 609, 1326, 950, 152361, 17654, 341, 26091, 1494, 152470, 471, 584, 1326, 950, 152383, 2888, 2053, 6234, 3517, 152361, 2105, 5083, 152361, 13, 166104, 13, 13, 152367, 152361, 583, 3786, 1920, 152369, 2565, 9558, 337, 4299, 37496, 152361, 13, 152375, 152361, 84984, 40706, 290, 4783, 15040, 152361, 13, 152385, 152361, 12272, 286, 3348, 415, 4082, 56420, 152361, 13, 152395, 152361, 68456, 80991, 404, 37496, 10845, 152361, 166101]
prompt 4, alone and two at a time: [2359, 825, 298, 6595, 298, 2028, 152382, 471, 11859, 261, 13505, 1637, 358, 12645, 471, 19276, 152383, 297, 471, 20722, 152383, 337, 269, 5463, 290, 4624, 1229, 14650, 2692, 13505, 849, 2888, 152361, 14650, 2603, 3324, 13505, 1637, 152361, 17562, 290, 4624, 152382, 20150, 152363, 4624, 152361, 28972, 3486, 13505, 152361, 2105, 5083, 2070, 152361, 13, 166104, 13, 13, 152405, 13, 259, 152383, 19276, 1832, 471, 89344, 748, 13, 259, 152383, 20722, 1832, 471, 31974, 152383, 13, 152406, 166101]
prompt 5, alone and two at a time: [2359, 825, 3324, 2921, 25528, 152361, 14650, 2603, 7615, 152361, 16354, 152382, 36054, 152343, 152385, 337, 486, 152375, 152361, 152343, 152367, 152375, 36054, 374, 152343, 152395, 2432, 290, 152343, 152385, 152363, 1838, 152343, 152395, 152412, 152375, 11924, 152411, 152361, 14770, 20089, 152361, 13, 166104, 13, 13, 152422, 152411, 166101]
chat 0 turn 1: [2359, 825, 3324, 2028, 152402, 152350, 2251, 152382, 471, 21301, 14504, 10824, 2617, 152343, 152385, 152413, 6005, 14650, 11401, 1962, 152361, 14650, 2603, 38503, 152361, 14650, 3876, 3354, 152361, 11080, 3517, 152382, 471, 152368, 152368, 152368, 152367, 14504, 152369, 152367, 10824, 2617, 152343, 152385, 152413, 289, 152343, 152375, 270, 358, 3947, 9917, 2577, 1768, 14504, 152369, 152367, 152361, 17654, 4424, 14504, 152369, 152367, 152361, 14650, 7923, 754, 5083, 152361, 13, 166104, 13, 13, 32604, 152369, 152367, 10824, 2617, 152343, 152385, 152413, 152361, 166101]
chat 0 turn 2, fresh and resumed (8521 of 8558 kept): [764, 2028, 322, 10227, 689, 820, 1119, 388, 4145, 337, 14504, 152369, 152367, 298, 11985, 2617, 152343, 152385, 152413, 152361, 20691, 417, 269, 1962, 6934, 152382, 13, 13, 152383, 152368, 152368, 152368, 152367, 14504, 152369, 152367, 10824, 2617, 152343, 152385, 152413, 289, 152343, 152375, 270, 358, 3947, 9917, 152383, 13, 13, 6114, 388, 4145, 152343, 152375, 8905, 152361, 13, 166104, 13, 13, 1874, 4145, 1472, 152375, 8905, 454, 326, 15392, 152343, 152385, 152413, 439, 10824, 415, 14504, 152369, 152367, 289, 152343, 152375, 270, 358, 3947, 9917, 472, 166101]
chat 1 turn 1: [2359, 825, 3324, 2028, 152402, 152350, 2251, 152382, 471, 4894, 1094, 7643, 11795, 358, 3947, 67904, 2351, 269, 865, 152343, 152394, 152368, 3517, 6005, 14650, 14480, 865, 152343, 152394, 152368, 3517, 290, 2753, 1962, 152361, 14650, 1275, 3517, 888, 3947, 67904, 152361, 2269, 152402, 152350, 11401, 865, 152343, 152394, 152368, 3517, 152361, 13, 13, 40563, 152382, 13, 152368, 152368, 152368, 152368, 14504, 152369, 152368, 10824, 2617, 152343, 152368, 289, 152343, 152367, 270, 358, 3947, 67904, 13, 152368, 152368, 152368, 152367, 14504, 152369, 152367, 10824, 2617, 152343, 152385, 152413, 289, 152343, 152375, 270, 358, 3947, 9917, 13, 152368, 152368, 152368, 152375, 14504, 152369, 152375, 10824, 2617, 152343, 152413, 152395, 289, 152343, 152385, 270, 358, 3947, 9917, 13, 152368, 152368, 152368, 152385, 14504, 152369, 152385, 10824, 2617, 152343, 152367, 152367]
chat 1 turn 2, fresh and resumed (8528 of 8554 kept): [764, 2028, 7717, 298, 1014, 548, 3517, 2351, 269, 865, 152343, 152394, 152368, 3517, 11795, 358, 3947, 471, 82440, 2577, 13, 13, 4546, 532, 611, 918, 269, 865, 152343, 152394, 152368, 3517, 326, 7707, 152343, 152368, 152368, 152368, 152368, 298, 152343, 152368, 152368, 152395, 152398, 1123, 13, 13, 152369, 152343, 152368, 152368, 152368, 152368, 152382, 14504, 152369, 152368, 10824, 2617, 152343, 152368, 289, 152343, 152367, 270, 358, 3947, 67904, 13, 152369, 152343, 152368, 152368, 152368, 152367, 152382, 9917, 13, 152369, 152343, 152368, 152368, 152368, 152375, 152382, 9917, 13, 152369, 152343, 152368, 152368, 152368, 152385, 152382, 9917, 13, 152369, 152343, 152368, 152368, 152368, 152395, 152382, 9917, 13, 152369, 152343, 152368, 152368, 152368, 152394, 152382, 9917, 13, 152369, 152343, 152368, 152368, 152368, 152408, 152382, 9917, 13, 152369, 152343, 152368, 152368]
chat 2 turn 1: [2359, 825, 3324, 2028, 152402, 152350, 2251, 152382, 471, 3512, 322, 269, 3947, 343, 1326, 152343, 152368, 152368, 152398, 152368, 6005, 14650, 11401, 2753, 1962, 3517, 152361, 14650, 1151, 1326, 152343, 152368, 152368, 152398, 152368, 152361, 461, 2070, 152382, 13, 152368, 152368, 152411, 152398, 14504, 152369, 152394, 10824, 2617, 152343, 152375, 152398, 152385, 289, 152343, 152367, 152375, 270, 358, 3947, 9917, 13, 152368, 152368, 152398, 152368, 14504, 152369, 152408, 10824, 2617, 152343, 152385, 152385, 152368, 289, 152343, 152367, 152385, 270, 358, 3947, 67904, 13, 152368, 152368, 152398, 152367, 14504, 152369, 152368, 10824, 2617, 152343, 152385, 152408, 152413, 289, 152343, 152367, 270, 358, 3947, 9917, 13, 6114, 3947, 343, 1326, 152343, 152368, 152368, 152398, 152368, 322, 67904, 152361, 14650, 2603, 38503, 152361, 28972, 754, 5083, 152361, 13, 166104, 13]
chat 2 turn 2, fresh and resumed (8525 of 8555 kept): [764, 2028, 322, 10227, 689, 269, 3947, 343, 1326, 152343, 152368, 152368, 152398, 152368, 297, 952, 1326, 152343, 152368, 152368, 152398, 152367, 442, 269, 2753, 1962, 2692, 152361, 13, 13, 4546, 532, 1151, 805, 3517, 152382, 13, 13, 6161, 152343, 152368, 152368, 152398, 152368, 152382, 1495, 152368, 152368, 152398, 152368, 14504, 152369, 152408, 10824, 2617, 152343, 152385, 152385, 152368, 289, 152343, 152367, 152385, 270, 358, 3947, 67904, 152608, 13, 13, 6161, 152343, 152368, 152368, 152398, 152367, 152382, 1495, 152368, 152368, 152398, 152367, 14504, 152369, 152368, 10824, 2617, 152343, 152385, 152408, 152413, 289, 152343, 152367, 270, 358, 3947, 9917, 152608, 13, 13, 6114, 152382, 13, 152369, 9846, 152343, 152368, 152368, 152398, 152368, 570, 3947, 1472, 82440, 454, 13, 152369, 9846, 152343, 152368, 152368, 152398, 152367, 570, 3947, 1472, 569]

CI on these commits over main 03d08f1 (the pushed heads are rebased onto d0c9dc9, which adds only a CI workflow time limit): on #1417's head, which carries all three, zig build test, test-kernel-contract, test-golden, fuzz-cortheon, dev-tools, test-server-security and test-pull-security all exit 0, and on #1415's and #1416's heads zig build test and test-kernel-contract exit 0; zig fmt --check is clean and the lean check finds 0 new problems (428 on main and on each head); llama_teacher_judge_test.py passes; tf-kernel-contract --only llama on the M5 Ultra: SUMMARY 23 kernels: 23 pass gates 1-4, 0 fail, 0 known findings; 0 controls: 0 red as planted, 0 missed; 0 limit problems (21 kernels on #1415's head). Rebuilt on the rebased heads, the bf16 release's 1,500-position logits are bit-identical to the judged run on all three, both quants' to theirs, and a drafted greedy chat (128 tokens) equals the plain one. The pushed heads differ from the ones CI ran on only in comments and docstrings (the lean check's one-line rule); fmt, lean, the judge's tests, the build and zig build test were rerun on them.

@ashhart

ashhart commented Oct 28, 2026

Copy link
Copy Markdown
Owner

Thanks @BobClawblaw. On 138b7852, the recipe lists every differing quant choice with both margins against the prompt, decode and float64 references. The checker exits 1 on a policy failure, and the exactness exit from #1415 is in. The two greedy chat disagreements are explained against float64.

What's left is the teacher-forced failures. Seven positions still fail against float64: 425, 1370 and 1447 at 8 bits, and 533, 600, 1355 and 1418 at 4 bits. The trace through layers 36-40 and channel 4086 stops before it names the operation that adds the error, and an average over all 1,500 positions can't clear a failing row. Please find that operation and correct it, keeping the current contender and margin rule, and give each affected row's own error. Until those rows pass, the quants can't land.

The non-finite request for the checker on #1415 applies here too.

@BobClawblaw

Copy link
Copy Markdown
Contributor Author

Thanks. We looked for the operation, and there isn't one to correct. Rather than ask for an exception to the rule, we're withdrawing the quants: closing this PR, and #1417 now builds on #1415 alone, with its drafter served from the bf16 release. Here's what we found, for the record.

Per operation. A local probe (not in any PR) stopped the engine's forward at row 1447 at layers 36, 38 and 40, on the bf16 release and on the 8-bit quant, and kept every operation's output. A float64 checker then recomputed each operation from the engine's own input buffers, with the reference's rounding points and the quant's exact weights (scale * q + bias). For each one: q|k|v, attention, o_proj, both residual adds, gate, up, silu(gate) * up and down, the engine's output is within half a bf16 step of the exact result at the vector's scale (0.06 to 0.50 steps), on both weight sets. Each operation is correctly rounded given its inputs. Elementwise, a few values sit one step off where the fp32 sum and the exact sum fall on opposite sides of a rounding boundary.

A perfect implementation of the rounding points. llama_f64_reference.py --bf16-points runs the model in float64 but rounds to bf16 at every one of the reference's rounding points (each norm, projection, RoPE product and sum, attention output, the MLP's products, each residual add). Nothing that keeps those rounding points can do better. Judged against the float64 pass with the same contender and margin rule, it fails too: at 425 on the 8-bit quant, and at 533, 539, 1355 and 1418 on the 4-bit. Four of the seven failing positions can't pass for any implementation of these rounding points.

The other three (8-bit 1370 and 1447, 4-bit 600) pass for the perfect implementation and fail for the engine. The difference is the fp32 accumulation inside the projections, the one-step flips above, compounded over 52 layers. MLX's GPU decode fails the same rule at 1370, 1447 and 600 for the same reason. Matching the perfect implementation bit for bit would take exact sums in every projection.

Each failing position's own error (float64 logits of the three contenders; the engine's; the largest gap):

quant, position float64 engine largest error perfect rounding points
8-bit 425 39.743, 39.631, 39.613 40.25, 39.75, 40 0.507 fails
8-bit 1370 38.184, 38.102, 38.027 38.25, 38.25, 38 0.148 passes
8-bit 1447 35.494, 35.040, 34.801 34.75, 34.75, 34.5 0.744 passes
4-bit 533 31.705, 31.372, 28.959 31.875, 31.875, 29.125 0.503 fails
4-bit 600 28.915, 28.492, 27.871 28.625, 28.625, 27.75 0.290 passes
4-bit 1355 29.102, 28.759, 28.653 29.25, 29.25, 29.25 0.597 fails
4-bit 1418 39.309, 39.168, 39.053 39.75, 39.5, 39.5 0.447 fails

If you later want lossy community quants judged against a reference with the same rounding points, the code is on our fork's llama-affine-pr branch and can come back as its own PR. The probe and the --bf16-points mode can come with it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants