Benchmark 20 August 2026 AI-written

Every 256K-capable local LLM on my Mac Studio M3 Ultra, ranked. The winner decodes at 60.45 tokens/sec.

Sixteen model and engine configurations at a real quarter-million-token prompt. The ranking is the least interesting thing I found.

TL;DR

Nemotron-3.5-Lightning-30B-A3B on mlx-lm 8-bit: 60.45 tok/s decode (mean of 4 runs, coefficient of variation 1.3%), 986 tok/s prefill, 8/8 needle recall at 262,223 tokens, 38.7 GB allocator peak, 1379 MHz with 0% of samples clamped. It beats second place by 1.59x, and its worst run beats second place's best run by 1.45x, so the ordering does not depend on how you average.

It does not win by being fast. A 2B model beats it at 2k. It wins by degrading slowly: across 2k to 262k its decode falls 1.83x where a dense 14B's falls 10.68x, because its allocator grows 1.045x where the 14B's grows 4.17x. At this depth a model is mostly its KV cache, and this one barely has one.

The three results I did not expect: architecture beats parameter count (a 30B-A3B beats an 80B-A3B by 2.3x on decode, using less memory); the recall gate never once excluded a model for failing to retrieve, because all five sub-8/8 scores I published were artifacts of my own generation budget; and a "1M-native" model scores 8/8 at 262k and 0/8 at 955,201 tokens on the same weights. Ten of my own claims were retracted along the way, which is the part of this worth copying.

Decode, tokens per second

Every configuration that scored 8/8, at 234,158 to 262,302 tokens of context. Ranked by decode because at this depth prefill is paid once and decode is paid per token.

Qwen3.5-2B * mlx-lm bf16
65.31
Nemotron-3.5-Lightning-30B-A3B mlx-lm 8-bit
60.45
Qwopus3.5-4B-Coder mlx-lm 4-bit
37.97
Qwen3-Next-80B-A3B mlx-lm 4-bit
26.79
Qwen3.8-27B-abliterated-oQ4e oMLX 0.6.2 + DFlash2
18.61
Qwen3.6-35B-A3B mlx-lm 8-bit
17.7 to 26.4
Qwen2.5-7B-Instruct-1M mlx-lm 8-bit
11.67
Qwen3-4B-Instruct-2507 mlx-lm 4-bit
10.15
Qwen3.6-27B mlx-lm 8-bit
7.89
Qwen3.8-27B-Abliterated mlx-lm 8-bit
6.96
Qwen3.6-35B-A3B oMLX 0.5.5
6.86
Qwen3.6-27B llama.cpp Q8_0
5.10
Qwen3.8-27B-abliterated-oQ4e oMLX 0.6.2 + dual-ANE + MTP k=3
3.98
Qwen3.8-27B-abliterated-oQ4e oMLX 0.6.2 baseline
3.91
Qwen3.6-27B oMLX 0.5.5
3.66
Qwen3.6-27B * surge (my C + Metal engine)
0.537

Linear scale. Where a row spans a range across runs the bar is drawn at the low end and the label shows the full range. * Qwen3.5-2B is measured but is not the pick, and surge's row is a stale lower bound; both are explained below.

Prefill, tokens per second

Same runs, different ordering. Prefill spans 678x across this table, and the fastest prefill engine on a given model is often its slowest decoder.

Qwen3.5-2B * mlx-lm bf16
2026.6
Nemotron-3.5-Lightning-30B-A3B mlx-lm 8-bit
986
Qwen3.6-35B-A3B oMLX 0.5.5
696.7
Qwen3.6-35B-A3B mlx-lm 8-bit
519
Qwopus3.5-4B-Coder mlx-lm 4-bit
484 to 719
Qwen3-Next-80B-A3B mlx-lm 4-bit
397
Qwen2.5-7B-Instruct-1M mlx-lm 8-bit
191.6
Qwen3-4B-Instruct-2507 mlx-lm 4-bit
175
Qwen3.8-27B-abliterated-oQ4e oMLX 0.6.2 baseline
159.0
Qwen3.8-27B-abliterated-oQ4e oMLX 0.6.2 + dual-ANE + MTP k=3
147.0
Qwen3.6-27B oMLX 0.5.5
128.7
Qwen3.6-27B mlx-lm 8-bit
114.0
Qwen3.8-27B-abliterated-oQ4e oMLX 0.6.2 + DFlash2
102.0
Qwen3.6-27B llama.cpp Q8_0
95.6
Qwen3.6-27B * surge (my C + Metal engine)
2.99 compute

The abliterated 27B on mlx-lm is omitted here: its run was clock-contaminated, so its prefill is not a reportable number even though its decode and recall are.

Every number

The depth column is the point of this table. Two of the acceleration recipes I tested publish their headline numbers at 2,048 tokens, and putting those beside a 262k row without the depth visible would be the most misleading thing I could do here.

Model Engine or recipe Depth tested Prefill t/s Decode t/s Recall Memory Median MHz
Qwen3.5-2B mlx-lm bf16 234,158 2026.6 65.31 8/8 56.5 GiB sys 1376
Nemotron-3.5-Lightning-30B-A3B mlx-lm 8-bit 262,223 986 60.45 8/8 38.7 GB alloc 1379
Qwopus3.5-4B-Coder mlx-lm 4-bit 234,158 484 to 719 37.97 8/8 27.9 GB alloc 1372 to 1377
Qwen3-Next-80B-A3B mlx-lm 4-bit 262,257 397 26.79 8/8 71.1 GB alloc 1153
Qwen3.8-27B-abliterated-oQ4e oMLX 0.6.2 + DFlash2 234,158 102.0 18.61 8/8 133.4 GiB sys 732
Qwen3.6-35B-A3B mlx-lm 8-bit 234,158 519 17.7 to 26.4 8/8 114.6 GiB sys 1193
Qwen2.5-7B-Instruct-1M mlx-lm 8-bit 262,268 191.6 11.67 8/8 24.53 GB alloc 729
Qwen3-4B-Instruct-2507 mlx-lm 4-bit 262,257 175 10.15 8/8 43.7 GB alloc 890
Qwen3.6-27B mlx-lm 8-bit 234,158 114.0 7.89 8/8 132.3 GiB sys 708
Qwen3.8-27B-Abliterated mlx-lm 8-bit 262,302 not reportable 6.96 8/8 75.5 GB alloc 695
Qwen3.6-35B-A3B oMLX 0.5.5 234,158 696.7 6.86 8/8 87.6 GiB sys not recorded
Qwen3.6-27B llama.cpp Q8_0 234,158 95.6 5.10 8/8 85.2 GiB sys 655
Qwen3.8-27B-abliterated-oQ4e oMLX 0.6.2 + dual-ANE + MTP k=3 234,158 147.0 3.98 8/8 115.6 GiB sys 902
Qwen3.8-27B-abliterated-oQ4e oMLX 0.6.2 baseline 234,158 159.0 3.91 8/8 115.7 GiB sys 957
Qwen3.6-27B oMLX 0.5.5 234,158 128.7 3.66 8/8 101.9 GiB sys not recorded
Qwen3.6-27B surge (my C + Metal engine) 234,158 2.99 compute 0.537 8/8 47.0 GiB alloc 684
GLM-5.3-Flash (320B-A18B) glm53_flash_mlx (patched mlx-vlm), mixed 4/8-bit 234,143 42.3, limiter-shaped 14.83 8/8 retrieval and association, never terminates 270.11 GB alloc / 366.7 GiB sys 504, 11 pct clamped
Qwen3.8-27B, BASE weights mlx-lm 8-bit 262,302 105.3 7.12 FAIL, never terminates 76 GB alloc 504
Qwen2.5-7B-Instruct-1M mlx-lm 8-bit 955,201 50.2 2.89 FAIL 0/8, degenerate 151.8 GiB sys see caveats
Falcon-H1-7B mlx-lm 4-bit about 262k not reportable not reportable FAIL 0/8, degenerate not reportable not reportable

Memory is reported on two bases that are not interchangeable, and the label says which: alloc is MLX's device allocator peak for that process, sys is the whole machine's peak footprint. I published a memory winner twice on a mixed basis and had to retract it twice.

Two models are absent rather than failed. DeepSeek-V4-Flash (304B-A13B) is blocked on stock mlx-lm by a runtime gap in fp4-packed MXFP4 expert dequantization, which is an engine limitation and not a model result. Ling-3.0-tiny was downloaded as a candidate and turned out to be ineligible: its trained context is 131,072, so it never belonged in a 262k comparison.

The two published claims, at their real depth

These are the numbers that motivated the acceleration campaign. They are kept out of the charts above and shown here with their depth attached, because that is the only honest way to put them near a 262k row.

Published claim Engine Depth Prefill t/s Decode t/s
oMLX dual-ANE recipe, as published oMLX 0.6.1 dual-ANE 2,048 431.8 81.1
DFlash2, as published various engines, M5 Max short context not published 2.7x to 3.4x speedup

Take the data. Everything in the charts and tables above, plus the failures, the published claims and the models that were not measured, is published as one JSON file: local-llm-256k-leaderboard.json. The page and the file render from the same source, so they cannot disagree.

Architecture beats parameter count, decisively

The top three are all mixture-of-experts with linear or hybrid attention. A 30B-A3B beats an 80B-A3B by 2.3x on decode while using roughly half the memory (38.7 GB against 71.1 GB), and a 4B sits between them. Meanwhile the dense 27B, running the same weights on the fastest engine available to it, decodes at 7.89.

The reason is that dense full attention pays O(n squared) per layer, and n here is a quarter of a million. The hybrid and linear-attention models only pay that on a minority of their layers. You can watch the term appear directly: the same 7B model at 262,268 tokens prefills at 191.6 tok/s and at 955,201 tokens prefills at 50.2, so 3.64x more tokens cost 13.9x more time.

The engine comparison lands in a similar place. On the one model where I ran all four engines head to head on an identical prompt, oMLX has the fastest prefill and the slowest decode, mlx-lm decodes 1.49x faster than llama.cpp, and all four score 8/8. Recall at this depth is engine-independent. Speed is not. I have since taken that 1.49x apart, and it is a single run per arm that only just clears my own threshold for trusting one.

Why the winner wins: it degrades slowly

The table above says rank 1 wins. It does not say why, and the answer turned out not to be speed. I ran the winner and a dense Qwen2.5-14B-Instruct-1M across four depths on the same needles, and the gap between them grows by 5.8x as the context does.

Depth Nemotron-3.5-Lightning-30B-A3B Qwen2.5-14B-Instruct-1M Ratio
~2k 110.49 38.03 2.91x
~8k 107.52 34.27 3.14x
~32k 100.13 24.47 4.09x
~262k 60.45 3.56 16.98x

Across that range the winner's decode falls 1.83x. The 14B's falls 10.68x. The mechanism is visible in the allocator: the winner grows from 37.00 to 38.67 GB, a factor of 1.045, while the 14B grows from 16.69 to 69.67 GB, a factor of 4.17. At a quarter of a million tokens a model is mostly its KV cache, and this one barely has one.

So the honest statement of the result is not that rank 1 is fast. A 2B model beats it at 2k. It is that rank 1 is flat, and flatness is what a long context actually rewards. That also means the ranking on this page is a ranking at one depth, and would reorder at a shorter one.

The recall gate never excluded a model for failing to retrieve

This is the result I would most like other people to steal, because it says a common benchmark practice is broken.

I published five recall scores below 8/8 and treated each as a model property. Every one was wrong, and every one came back 8/8 when re-run. Qwen3.6-35B-A3B went 2/8 to 8/8. Qwopus3.5-4B went 1/8 to 8/8. Qwen3.5-2B went 1/8 to 8/8. Qwen3.6-27B went 2/8, then 4/8, then 8/8. In each case exactly one thing changed: the generation budget went from 300 tokens to 3,000 or 6,000.

The mechanism is that these are reasoning models. Given a short budget they are still inside their scratchpad when the cap hits, so the scorer reads an empty answer and records a retrieval failure. I was measuring my own harness. My first explanation was that the chat template triggered it, and that was also wrong: Qwopus, handed a raw file with no template at all, emitted a think tag as its very first token. Reasoning mode is a property of the model, not of the prompt format.

# The rule that decides whether a recall score means anything.
# It keys on the GENERATION, not the prompt: models enter reasoning
# mode unprompted, so a raw untemplated prompt is not a defence.
think_open   = "<think>"  in generation
think_closed = "</think>" in generation
hit_cap      = generated_tokens >= max_gen

if think_open and not think_closed and hit_cap:
    verdict = "NOT REPORTABLE"   # you measured the budget, not the model

Four models do genuinely fail, and here is the useful distinction: all four fail by degenerating, not by missing the needles. Falcon-H1-7B emits token soup. Base Qwen3.8-27B retrieves perfectly and then cannot stop, emitting 74,278 characters of verbatim haystack without ever closing its think block, even at a 16,000-token budget, while answering cleanly at 2k, 8k and 32k. Qwen2.5-7B-Instruct-1M repeats the filler sentence to the cap. GLM-5.3-Flash, described below, is the fourth and fails the same way as the base 27B. Retrieval has not once been the binding constraint at this depth.

One intervention repaired a genuine failure, which nothing else in this project managed. The abliterated build of Qwen3.8-27B, same base revision and quantization, stops on its own after 533 tokens with a closed think block and scores 8/8, using 3% of the budget the base weights exhausted. Abliteration is a refusal-direction weight edit aimed at censorship, and it happened to fix termination. I would not have predicted the direction.

"1M-native" is a claim about training, not about behaviour

Qwen2.5-7B-Instruct-1M advertises a trained context of 1,010,000 tokens. I built a 955,201-token needle prompt, deliberately inside that ceiling, with the needles at the same proportional depths as the 262k one so the two tests measure the same thing.

Same weights, same needles Prefill t/s Decode t/s Recall
262,268 tokens191.611.678/8
955,201 tokens50.22.890/8, degenerate

The machine is not the limitation. It ingested all 955,201 tokens with the truncation guard passing, in 5.29 hours of prefill, peaking at 151.8 GiB with zero swap. A 512GB M3 Ultra can hold and process a million-token context.

The model is the limitation, and given my own history of budget artifacts the distinguishing check matters: it never opened a think block, so there was no scratchpad to truncate. The output is verbatim repetition of the haystack filler to the cap. The model works perfectly at a quarter of its advertised context and degenerates at 95% of it, with no warning in between and nothing in its configuration marking the boundary. The only way to know a model's usable context is to measure it at the depth you intend to use.

Three acceleration paths, one base model, one winner

The last stretch tested three published accelerations against the same base model at the same depth, so they are comparable to each other and to one baseline rather than to marketing.

Path, all on Qwen3.8-27B at 234,158 tokens Decode t/s Verdict
oMLX 0.6.2 baseline3.91reference
dual-ANE prefill + MTP k=33.98no effect
DFlash2 speculative decoding18.614.76x
abliteration (mlx-lm, different quant)6.96repaired a failure

DFlash2 is the only one that survives to this depth, and it beats its own published claim of 2.7 to 3.4x, which was measured on an M5 Max at short context. I had predicted the opposite: I expected speculative decoding to shrink at 262k because every forward pass is bound by reading the KV cache. It helps precisely because that KV read is amortized across several accepted tokens instead of repeated per token. The cost is a 36% prefill regression, because the 3.6 GB drafter pushes peak memory from 115.7 to 133.4 GiB and that drops the sustainable clock from 957 to 732 MHz. The trade pays back after roughly 4,100 generated tokens.

The dual-ANE recipe delivers nothing, and I could have known before measuring. Its kernel is fixed-shape at exactly 2,048 tokens and accelerates MLP layers rather than attention, and the recipe's own published results are all at a prompt length of 2,048. At 262k, attention dominates and the shape does not match, so the Neural Engine drew 0.00 W across all 636 telemetry samples in both arms, and the baseline arm was faster than the accelerated one.

The 2B beats the winner, and it is still not the pick

Qwen3.5-2B tops both charts on raw numbers: 2026.6 tok/s prefill, 65.31 tok/s decode, 8/8 recall, and it is the one model small enough to finish before the firmware limiter engages. If you rank by this benchmark alone, it wins.

I am not shipping it, and I want to be precise about why, because one of my two reasons is weaker than it first looked. The reason I originally gave was that its 8/8 is on the 234,158-token raw prompt while the winner's is on the harder 262,223-token templated one. That argument is weak, because second place is measured at 234,158 too, and I kept it on the board.

The reason that actually holds is about the metric. Needle-in-a-haystack measures retrieval, not comprehension. Copying eight literal 8-digit codes out of filler text is the weakest long-context task there is, and a 2B scoring 8/8 on it tells you nothing about whether it can reason over a quarter-million tokens. That is a reason to distrust this benchmark as a sole metric, which is a conclusion about my measurement rather than about the model. The honest statement is that the 2B wins the test I ran, and the test is not sufficient.

The other flagged row is my own engine, surge, a C and Metal inference engine I have been building. Its 0.537 tok/s is real but stale: it was measured before a decode-attention rewrite that is 28.3x faster on this exact shape in isolation. That does not license moving it up the table, because attention is one term of a decode step and nobody has measured the end-to-end result. Re-measuring costs about 31 hours of GPU time, roughly 99% of it in a prefill path that work did not touch, so the row stays as measured and labelled.

The largest model I have run here, and why it is not ranked

GLM-5.3-Flash is 320B total and 18B active across 45 layers, 181.9 GB of weights. It is by a wide margin the largest thing this machine has ever run: 270.11 GB of allocator peak against a 366.7 GiB system peak, on a box with 512 GB. The prefill alone took 92 minutes.

It scored 8/8 retrieval and 8/8 association at 234,143 tokens. That is genuine perfect recall, and on the hardest configuration here. It is also not on the leaderboard, because it never terminated: 6001 tokens generated against a 6000-token cap, a unique-word ratio of 0.15 where clean prose is 1.0, and a tail visibly looping back over codes it had already found. That is the base Qwen3.8-27B failure exactly, so it gets the same treatment.

Worth flagging that my own guard did not catch this. The automated inconclusive check read False, purely because there is no unterminated think tag in this model's output to key on. It keyed on the marker instead of the artifact, which is the same mistake described in the retractions below, surfacing in a different guard.

Two caveats on its numbers. Its 42.3 tok/s prefill is limiter-shaped and must not be compared with clean rows: median 504 MHz with 11% of samples at or under 400, touching the 338 MHz floor, which is what a 366.7 GiB footprint predicts given the clock-versus-footprint bound above. And its correctness rests on a third-party fork of the runtime whose claim of 1e-6 parity against transformers is the author's, tested by the author, not verified here. Its depth reads 234,143 rather than the 234,158 elsewhere in the table because it is the same prompt file measured with a different tokenizer.

The protocol, and why it is this paranoid

A firmware power limiter on this machine clamps the GPU to 338 MHz under sustained load and understates a run by 2x to 5x. Every row above had to clear the same gate.

# What every published row here had to clear, in order.
pgrep bench                     # never contend for the GPU
sudo fanpro mode curve
sleep 8                         # SET_MODE race: without this, the next line is dropped
sudo fanpro set all 3625
sudo fanpro fans                # VERIFY 3400-3760 rpm; fanpro can latch monitor-only
sleep 120                       # let the limiter release BEFORE gating (see below)
gemm_gate                       # > 20.5 TFLOPS, or the GPU is still clamped
# ... run ...
# decode by least-squares slope, not wall-clock division
# truncation guard: built == saw, truncated == False
# macmon: median MHz AND percent of samples <= 400 MHz

The order in that snippet is load-bearing, and I had it backwards for the whole project. I wrote it as "gate, then fans" and only caught it when a run aborted twice, reporting 19.55 and 18.23 TFLOPS on an idle machine that I had hand-probed at 21.35 a minute earlier. Gating with the fans on auto measures a partially clamped GPU, for the reason in the next paragraph, so the gate ends up answering "are the fans spun up" rather than "is the GPU fresh". With the fans pinned and 120 seconds of idle first, the same probe reads 21.80 and the run proceeds. The dangerous part is that the abort is indistinguishable from a genuinely busy machine, so an automated loop retries forever. It can only cause a false failure, never a false pass, so no number above is affected.

Two findings from this project change how the machine has to be used. First, the limiter tracks fan speed, not die temperature: I quietened the fans mid-run to check, and the GPU went from 0% clamped at 60.6 C to 77% clamped at 58.3 C. It clamped while getting cooler, and restoring the fans released it. This machine cannot be quiet and produce valid measurements at the same time.

Second, the achievable clock is bounded by the whole machine's memory footprint, not by cooling. Runs under about 93 GiB reached about 1377 MHz; runs over about 114 GiB never exceeded 1153, across cooldowns from 0 to 300 seconds. A deliberate 300-second cooldown produced one model's slowest run. This makes cross-model clock-matching impossible, and it means the winner's small footprint is part of why the machine runs fast on it. It also means a resident model server ruins your benchmark: an idle server holding 31.7 GB moved one run from 1379 MHz to a hard clamp.

Ten retractions, and the single pattern behind them

I withdrew ten published claims during this work. Listing them is more useful than any single number above, because every one came from attributing a difference to a mechanism without isolating the variable.

"llama.cpp is the fastest engine" was written while the comparison row was limiter-shaped. "surge is 2.15x slower end to end" was GPU drift, and I should have noticed that prefill fell in the same pair while prefill was untouched by the change. "The recall collapse is real" was the generation-budget artifact, five times over. "The decode column mixes metrics, which explains the gaps" was refuted by measuring both metrics from one generation: they agree to 1%, and the gaps were ordinary run-to-run variance. "Nemotron wins on every axis" was false; it loses the memory axis on both bases.

Two more came after this post first went up, and both are the same shape. I published that "three of five models are slower on prose than on code" and withdrew it when two of the three differences turned out to be run-to-run variance rather than a property of the prompt. And I published a 1.52x coding-quality lead for the winner over a 4B coder, then retracted the headline entirely: it largely measured who ran out of generation budget, because 64% of the 4B's completions produced no extractable code against the winner's 24%. Among completions that did produce code the ranking inverts. Conditioning on that is biased too, so this project currently has no defensible verdict on coding at all.

Two of the ten were caught by arithmetic rather than by measurement. I published a correlation as differing between two memory unit bases when neither variable in it was the memory figure, which is impossible on its face. And I introduced a duplicate finding number that silently broke every cross-reference after it. Both were found by re-reading, not by re-running.

The real noise scale on this machine, for anyone calibrating their own: about 1% from the choice of metric, about 12% from re-running an identical configuration, and up to 1.5x when the clock differs. Prefill reproduces to 0.08%. Any comparison tighter than 12% needs repeated runs, and I have the receipts for what happens when it does not get them.

Caveats, honestly

This is one machine, a Mac Studio M3 Ultra with 512GB and 80 GPU cores, and one benchmark family. Needle-in-a-haystack is a retrieval test, so nothing here measures whether any of these models can reason over long context; the section on the 2B is the honest consequence of that.

Nothing here ranks coding ability. I did measure it, on 182 LiveCodeBench problems, and then retracted the headline: the run was confounded by generation budget, and among the completions that produced any code at all the ranking reversed. A re-run at an adequate budget is the only thing that would settle it, and the two attempts at that so far have not been servable on this machine. So the honest position is that coding is measured and undecided, not that it favours the winner.

The depth column is not uniform: rows read 234,158 or about 262,2xx depending on whether the prompt went through a chat template, and the difference is real though small. Two rows carry specific defects that I have marked rather than smoothed: the abliterated 27B's prefill is clock-contaminated and not reportable, and the 955,201-token run had its fans reduced mid-run, so its speed figures are not fully protocol-compliant while its recall verdict is unaffected. Run counts differ by row, from one to seven, and only the top two have enough repeats to quote a variance. The oMLX rows have an indirect truncation guard: those runs could not prove full ingestion directly, so it was inferred from recall, wall time and peak memory. I have since closed that on two of those rows, and the result is worth stating because it corrects me rather than the software. Both return 234,158 of 234,158 tokens served, not truncated, so the inference was right. The cause of the gap was mine: OpenAI-style streaming omits the token-usage block unless the client asks for it, and my client never did. I re-ran the 35B-A3B row second on purpose, because it is the only one carrying a superlative (the 696.7 tok/s fastest prefill above), where a short-served prompt would have collapsed the claim rather than nudged it. It came back at full clock and 7% faster, 745.7 tok/s, which is inside this machine's run-to-run noise, so the table keeps the published figure. Three rows keep the indirect label; none owns a superlative. And the median clock is missing for two of them because I had not yet started recording it.

Data provenance

Every figure comes from runs on my own machine between 10 and 27 August 2026, each with its own log directory, per-token timestamps and a per-second macmon telemetry trace. One exception to the "my own machine, my own stack" rule is flagged where it appears: the GLM-5.3-Flash row runs on a third-party fork of the runtime, so its correctness rests on the fork author's parity testing rather than on mine. Decode is a least-squares slope over per-token timestamps rather than wall-clock division; both metrics were measured from the same generation on three models and agree to within 1.2%. Prefill and recall come from the runs' own JSON output. Recall is scored against the built prompt's own needle record, never against a source file, after that mistake produced a spurious score once. The generation-truncation guard keys on the model's output, not on the prompt. Numbers here are transcribed from a single reconciled summary table in my working notes rather than re-derived per row, because re-deriving is how several of the retractions above happened.