ADR 0037: keep the MLX #3912 fix and promote 0.34 on mlx-metal — campaign, OCRBench, SPEC H15–H18, results - #324
Merged
Conversation
…ncoder
Two corrections to claims made earlier today.
1. "Twelve runs split cleanly by build across both think modes" counted only
one of the suite's TWO finetext arms. vision_suite.py writes
scores_<tag>.json and finetext_probe.py writes ft_<tag>.json; they are
independent generations. The scores_ arm holds a 0.34.0 capture scoring 4
with byte-identical provenance to an ft_ capture scoring 3 — same
prompt_sha, images_sha, window, eval_count and answer_chars, texts differing
by one character, RNK against RMK, ground truth RNK. Replaced with N=10 per
quantization:
31b-mlx-bf16 10/10 at 4 31b-mxfp8 0/10 31b-nvfp4 2/10
A single capture of this cell reports the minority mode about one time in
eight.
2. "31b-mxfp8 carries a bf16 vision tower" was wrong. Its vision down_proj
blob IS byte-identical to bf16's (9,916,560 B, digest 4b80a6f9), and I
generalised from that one tensor to all 356. In fact 162 of its vision
layers are 8-bit; only down_proj was left at bf16. So mxfp8 is not a
bf16-encoder arm, and the inference drawn from it — that the language model
alone flips the glyph — is withdrawn. Surfaced by the MLX-CUDA session's
registry audit, which found our local 31b-nvfp4 and the library's current
tag share a config digest but differ in 194 vision layers.
What survives is stronger than what it replaces: the cell does not order by
numerical precision. 31b-mxfp8 has a bf16 down_proj — the exact tensor ollama#3912
corrupts — an 8-bit tower and an 8-bit LM, and scores 0/10, below the all-4-bit
31b-nvfp4's 2/10. A metric on which more precision scores worse is not
measuring quality and cannot be read as a verdict on the encoder.
Adds the 2026-09-18 campaign: five nvfp4 models think-off, two think-on, on
0.34.0-maxusai-8a7ba949, both phases rc=0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ified tags Our local gemma4:31b-nvfp4 and the registry's current tag share a config digest while differing in 194 vision layers: ours has a 4-bit vision tower, the registry now ships that tag with a bf16 one. A pull would silently replace the checkpoint this campaign measured, under the same name, with no version change. Copies the three 31b MLX checkpoints to tower-qualified tags — manifests only, so the blobs are shared and the copies cost no disk: gemma4:31b-nvfp4-tower-nvfp4 4-bit tower, 4-bit LM gemma4:31b-mxfp8-tower-mxfp8 bf16 down_proj, 8-bit tower, 8-bit LM gemma4:31b-mlx-bf16-tower-bf16 bf16 tower, bf16 LM The -tower-mxfp8 name is deliberately lossy; the table in the doc is the authority, since that tower is 8-bit in 162 of 356 vision layers with only down_proj at bf16 — the reason it never meets the ollama#3912 defect even pre-fix. 12b needs no such tag: it has no vision_tower.encoder at all, only a vision_embedder, which is why it was structurally immune throughout. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…table end to end All 1000 OCRBench v1 items, gemma4:31b-nvfp4 think-off, same local checkpoint under both archived binaries so only the build differs: 0.34.0-maxusai-8a7ba949 (MLX d9add9d1, FIXED) 835/1000 = 0.8350 0.33.2-maxusai-2b95b4a5 (MLX c793734e, BROKEN) 833/1000 = 0.8330 both right 826 | both wrong 158 | FIXED only 9 | BROKEN only 7 discordant 16, exact McNemar two-tailed p = 0.8036, churn 1.6% Statistically indistinguishable. A kernel that corrupts 232722 of 294912 elements at K=4304 in isolation moves two items in a thousand end to end. Two corrections this run forced: - The earlier 200-item slice gave 4-0 to the fixed build and was reported as directionally unanimous but underpowered. The direction was noise: across the remaining 800 items the split is 5 fixed / 7 broken. - That slice was also unrepresentative. Per-chunk accuracy runs 0.875, 0.845, 0.945, 0.845, 0.665, so reading the first 200 as 87.5% and calling it a match for the tech report's 88.3 was wrong. The full-benchmark figure for this checkpoint is 83.5%, and the gap is attributed to nvfp4 quantization by plausibility, not measurement — 31b-mlx-bf16 has not been run over the 1000. The decision stands on correctness, not accuracy: do not revert the kernel, because the fixed one is arithmetically right and matches MLX-CUDA's golden delta exactly at 0.0898 — not because reverting would cost OCR accuracy, which at this precision it would not. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…tal; SPEC H15-H18; commit the results ADR 0037 records the decision: the MLX pin stays at d9add9d1 with ml-explore/mlx#3912, no revert and no compat patch restoring the old fp_qmm_t loop bound. It rests on correctness, not accuracy — the fixed kernel matches MLX-CUDA's golden delta exactly at 0.0898, and on all 1000 OCRBench v1 items the two kernels are indistinguishable (835 vs 833, exact McNemar p = 0.80). ADR 0036 already accepted the same 4 -> 3 move on 31b's 9px cell as the cost of the faithful computation on the GGUF path; two unrelated correctness fixes on two engines move that cell the same way. SPEC vision-harness-reuse gains four rules this investigation forced, each with an honest conformance row (three are not enforced): H15 one capture of a quality cell is one draw — measure a rate H16 both finetext arms count; the renderer shows only the suite arm (summarize_engine_compare.py falls back to ft_ only when absent) H17 a checkpoint is its MANIFEST digest — not its tag, and not its config digest, which local 31b-nvfp4 and 31b-mlx-bf16 share H18 cross-build accuracy needs a paired test on many items; a tier move needs the unquantized arm on the same binary Promotes the mlx-metal surface: README deploy line and the release matrix's mlx-metal row, generated by release_matrix.py from the build's own preflight run and spliced beside the CUDA row, whose run was never committed — regenerating the whole matrix here would have erased it. The matrix comment records why the two rows come from two hosts and two stamps (0.34.0-maxusai vs 0.34.1-dynres, flagged against ADR 0032). The deploy line also records that the Metal launchd environment does not set OLLAMA_MLX_DRAFT_UNDER_GRAMMAR=0, unlike the CUDA container. Committed results, assembled from the gitignored run captures by script (ADR 0012 rule 8), serialized one record per line: bench-runs/ocrbench-v1-1000-gemma4-31b-nvfp4-0340-vs-0332.json bench-runs/finetext-9px-31b-quant-reps-2026-09-18.json bench-runs/vision-campaign-2026-09-18-mlx8a7ba949.json preflight/runs/preflight-mlx-metal-0340-8a7ba949.json (force-added, as the 0.33.0 fold's closing Metal run was; the invalid --container native run of the same build is deliberately left out) BINARIES.md marks 8a7ba94 promoted and warns that the 0.33.2 rollback target reinstates the defect. The campaign doc records the manifest digest of every checkpoint it measured, per H17. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Author
|
Consolidated into #327, which merges this PR with the other two OCRBench streams (#323 CUDA, #324 mlx-metal, #325 ROCm/GGUF). It contains every commit from all three, so merging #327 closes them together and there is no three-way conflict to resolve later. What moved, so nothing is lost:
All three runs agree: no quantisation difference resolves on any host, and OCRBench cannot separate the kernels where the goldens can. 🤖 Generated with Claude Code |
This was referenced Sep 19, 2026
glennneuber
pushed a commit
that referenced
this pull request
Sep 19, 2026
main took 0037 for "keep the MLX ollama#3912 kernel fix" when #324 merged, so this branch's 0037 and main's are different decisions under one number. The merge shows CLEAN because the filenames differ -- git has no reason to object, and the collision would only surface when someone cites "ADR 0037" and gets the wrong document. Mechanical: two git mv plus the six references in README.md, ocrbench-quantisation-ladder.md and spec/vision-harness-reuse.md. Patterns are anchored to "ADR 003x" and the full filenames, because a bare 0037 also matches the 1.003776 JPEG constant in synthetic-triggers/README.md. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
glennneuber
pushed a commit
that referenced
this pull request
Sep 19, 2026
…he mlx-metal promotion #324 added the Apple Silicon deployment to the same block this branch was updating with the ADR 0036 rebuild stamp. Both are true and neither replaces the other: the CUDA host runs 0.34.1-dynres-16-g16649e8 with the drafting knob, the Apple Silicon host runs 0.34.0-maxusai-8a7ba949 without it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
glennneuber
added a commit
that referenced
this pull request
Sep 19, 2026
Consolidates #323, #324 and #325 into one OCRBench test, renderer and format. #325 was closed unmerged, so this carries the only copy of the ROCm ladder and its summarizer. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
glennneuber
added a commit
that referenced
this pull request
Sep 19, 2026
Closes the ADR 0036 record: the deployed stamp on the CUDA host, the production verification, and the retirement-register row. Main merged in twice to keep both deployment lines after #324 and #328 edited the same block. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes the 0.34 fold's Metal kernel question and promotes the build on the mlx-metal surface.
Decision — ADR 0037
Keep the MLX pin at
d9add9d1with ml-explore/mlx#3912. No revert, no compat patch restoring the oldfp_qmm_tloop bound. Decided on correctness, not accuracy:OCRBench cannot tell the kernels apart; the golden delta can (fixed = MLX-CUDA's
0.0898exactly). ADR 0036 already accepted the same 4→3 move on 31b's 9px cell as the cost of the faithful path on GGUF — two unrelated correctness fixes, two engines, same cell, same direction.SPEC —
vision-harness-reuseH15–H18quant_dims.py)Promotion
0.34.0-maxusai-8a7ba949has served:11435since 2026-09-18. README deploy line + the release matrix's mlx-metal row, generated byrelease_matrix.pyfrom the build's preflight run (PASS 19 / SKIP 12) and spliced beside the CUDA row — whose run was never committed, so regenerating the whole matrix here would have erased it.Two things found while promoting, recorded rather than fixed:
0.34.0-maxusai-<sha>, the fold0.34.1-dynres-0-g<sha>, same commit.OLLAMA_MLX_DRAFT_UNDER_GRAMMAR=0, unlike the CUDA container (ADR 0033, the fold's unbounded-retention finding). Changing it is a persistent config change plus a service restart, left to the operator.Committed results
Assembled from gitignored captures by script (ADR 0012 rule 8), one record per line:
bench-runs/ocrbench-v1-1000-gemma4-31b-nvfp4-0340-vs-0332.json— 1000 paired itemsbench-runs/finetext-9px-31b-quant-reps-2026-09-18.json— N=10 × 3 quants, both-arm historybench-runs/vision-campaign-2026-09-18-mlx8a7ba949.json— every campaign cell, both armspreflight/runs/preflight-mlx-metal-0340-8a7ba949.json— force-added like the 0.33.0 fold's; the invalid--container nativerun is left outRetractions (all mine, corrected in the docs they reached)
31b-mxfp8carries a bf16 vision tower" generalised from one tensor; 162 of 356 vision layers are 8-bit.🤖 Generated with Claude Code