A 7.5B model beat a 24B on my coding benchmark. A developer built a 56-task coding benchmark with hidden tests and ran 16 model configurations on the same hardware, finding that a 7.5B-parameter model (gemma-4-e4b) scored 42/56, beating a 24B model (devstral-small-2-24b) at 40/56, and that the ranking does not match public leaderboards. The benchmark uses deterministic verification (cargo test, pytest, node) instead of LLM judges, and the developer found that error bars are a property of the model, not the benchmark, with one model swinging 8 points between identical runs. Every number below was recounted from the committed SCORES- .tsv files, not transcribed from notes. The raw data - 36 rows, one per run, each with its full failure list - is RESULTS-q56.csv https://github.com/mrdushidush/claudette/blob/battery/q50-quality-corpus/runs/eval-2026-05-29/battery/RESULTS-q56.csv . gemma-4-e4b is 7.5B parameters in a 4.97 GiB file . It scored 42/56 . devstral-small-2-24b is 24B in 11.90 GiB. It scored 40/56 . gpt-oss-20b scored 38. Both gemma-4-12b variants - one of them at Q8 0, more than twice the precision - landed at 42.5 and 41. Three runs each. The ranges do not overlap. On a small card, the 5 GiB model is not the compromise option , and no public leaderboard would have told you that. Everyone has opinions about which local model to code with, which quantisation to run, and what KV cache setting costs you. Almost all of those opinions come from a public leaderboard, or from one run of one config on somebody's box. So I built a 56-task coding benchmark that grades with hidden tests, and ran 16 model configurations through it on the same card at the same constants, with every held-constant The ranking does not match any public leaderboard; in one case it inverts one outright. And the noise result surprised me more than the ranking did: the error bar is a property of the model, not of the benchmark. One model swings 8 points between identical consecutive runs while another 56 medium-hard coding tasks run through a real agent loop - the model reads files, edits them, runs tests, and iterates until it says it is done. Grading is hidden verification : That last point matters more than it sounds. The lesson this project inherited from its own ancestors: LLM judges inflate. Deterministic ground truth may be lowered by a model, never raised. So there is no judge here at all - just cargo test , pytest , and node . Tasks are test-first gated . A task enters the corpus only if its verifier fails on the untouched fixture and passes on fixture + reference solution. That rules out tasks that are accidentally already passing, and tasks that are impossible. The discriminating axis turned out not to be algorithmic difficulty. It is unstated correctness traps - the degenerate input, the falsy-vs-absent distinction, the boundary the prompt implies Corpus: 56 tasks - 15 python, 14 rust, 10 js, 9 ts, 8 shell. By kind: 18 implement-spec, 7 boundary, 6 bugfix, 6 api-misuse, 5 error-handling, 4 refactor, 4 perf, 4 multi-file, 2 concurrency. Box: RTX 5060 Ti 16 GB, Windows 11, LM Studio. Held constant on every row: ctx 32768, KV cache q8 0, numParallelSessions 1, one agent session at a time, identical agent binary. Every row's actual values are recorded rather than assumed - see "the method lesson that cost the most", which is the section I would most want another benchmarker to read. Full 56 tasks on every row. Weak scores are data, not failures - a benchmark that reports only winners tells you nothing about the shape of the field. | model | params | quant | GiB | runs | median | range | n | wall-clock | |---|---|---|---|---|---|---|---|---| google/gemma-4-26b-a4b-qat | 26B-A4B | Q4 0 | 13.45 | 55, 54, 55 | 55 | 1 | 3 | 71–76 min | | unsloth/gemma-4-26B-A4B-it | 26B-A4B | UD-Q4 K M | 15.78 | 54 | 54 | - | ⚠️1 | 71 min | qwen3.6-35b-a3b-mtp@iq3 s | 35B-A3B | IQ3 S 3.06bpw | 12.67 | 49, 50, 50, 50, 48, 52 | 50 | 4 | 6 | 21–31 min | | qwen3.6-35b-a3b@iq4 xs | 35B-A3B | UD-IQ4 XS 4.25bpw | 16.51 | 50 | 50 | - | ⚠️1 | 53 min | | qwen3.6-35b-a3b-mtp GPU-3 | 35B-A3B | IQ4 XS 3.53bpw | 14.59 | 49 | 49 | - | ⚠️1 | 43 min | | qwen3.6-35b-a3b-mtp GPU-4 | 35B-A3B | IQ4 XS 3.97bpw | 16.43 | 49 | 49 | - | ⚠️1 | 51 min | | qwen3-coder-30b-a3b-instruct | 30B-A3B | UD-Q4 K XL | 16.45 | 43 | 43 | - | ⚠️1 | 58 min | | google/gemma-4-12b | 12B | Q8 0 | 11.80 | 42, 43 | 42.5 | 1 | 2 | 44–53 min | google/gemma-4-e4b | 7.5B | Q4 K M | 4.97 | 43, 42, 42 | 42 | 1 | 3 | 30–35 min | | north-mini-code-1.0 Cohere | 30B-A3B | UD-Q3 K M | 13.24 | 42 | 42 | - | ⚠️1 | 82 min | | google/gemma-4-12b-qat | 12B | Q4 0 | 6.50 | 41 | 41 | - | ⚠️1 | 57 min | | devstral-small-2-24b-2512 | 24B | IQ4 XS | 11.90 | 40, 39, 40 | 40 | 1 | 3 | 19–22 min | | openai/gpt-oss-20b | 20B-A3.6B | MXFP4 | 11.28 | 40, 38, 32 | 38 | ★8 | 3 | 10–12 min | | qwen3.5-4b | 4B | UD-Q8 K XL | 5.54 | 31, 33, 33 | 33 | 2 | 3 | 33–42 min | | google/gemma-4-e2b | 4.6B | Q4 K M | 3.19 | 31, 31, 31 | 31 | 0 | 3 | 23 min | | unsloth/granite-4.1-8b | 8B | Q8 0 | 8.70 | 25 | 25 | - | ⚠️1 | 22 min | Sizes are the measured GGUF file in GiB , not a vendor marketing figure and not lms ls which reports decimal GB and folds in the mmproj projector . Quant names are exact, because "gemma-4-12b" at Q8 0 and at Q4 0 are two different experiments. On the rows marked ⚠️1: a single run carries an error bar this benchmark can now put a number on, and it is not small - see the noise section. Nothing load-bearing in this piece rests on an n=1 row. Where one does QAT at 12B , I say so and decline to make the claim. One model was excluded rather than scored: nemotron-3-nano-omni-30b-a3b took 299 seconds on the corpus's easiest task vs 18 s for devstral and projected to roughly 4.5 hours. At that The single most repeated claim in local-model threads is some version of "just run the biggest model that fits". Here is what the gemma-4 family does, one architecture, one vendor, four sizes: | model | params | quant | GiB | median | |---|---|---|---|---| | gemma-4-e2b | 4.6B | Q4 K M | 3.19 | 31 | | gemma-4-e4b | 7.5B | Q4 K M | 4.97 | 42 | | gemma-4-12b | 12B | Q8 0 | 11.80 | 42.5 | | gemma-4-26b-a4b-qat | 26B-A4B | Q4 0 | 13.45 | 55 | 4.6B → 7.5B +11.0 7.5B → 12B +0.5 ← the plateau 12B → 26B +12.5 Scaling 7.5B → 12B buys half a point. That is the flat stretch, and it is flat despite the 12B carrying the better quantisation - Q8 0 against the 7.5B's Q4 K M, which is the conservative But it is bounded on both sides by real cliffs. Below it, dropping to 4.6B costs 11 points . Above it, the 26B MoE gains 12.5 . "Size buys nothing" is true in a band, and the band is narrower than the slogan. Which is where the title comes from. gemma-4-e4b clears devstral-small-2-24b on under a third of its parameters and 42% of its file size - and it does it across the whole family gpt-oss-20b 38 , north-mini-code And active parameters do not explain it either. gemma-4-26b-a4b activates about 4B parameters per token - roughly what the 4.6B e2b activates - and scores 55 against 31 . Same active compute, 24 points apart. Whatever is doing the work here, it is neither total size nor active size on its own. None of Result 1 is visible from a leaderboard, and that is not an accident - the ranking they produce is different from mine at the top of the table too. A vendor comparison table puts Gemma-4-31B roughly 21 points below Qwen3.6-35B on SWE-bench Verified. On this corpus the gemma MoE beats the qwen champion 55 to 50 . LiveCodeBench v6 anti-correlates outright : gemma scores 77.1 there and 55/56 here; qwen scores 80.4 there and 50/56 here. Higher on the board, lower on my tasks - in both directions, on the two models I care most about. And LiveCodeBench's own table for these models is 0-verified and 53-self-reported . It is worse than "different benchmarks measure different things", because several of the obvious alternatives are simply not usable any more: BigCodeBench has been frozen since 2025-04-14 202 rows, zero 2026 models and Aider's polyglot benchmark went dead 2025-10-04 . Meanwhile published work shows that scaffold choice alone swings the same weights about 2× on Terminal-Bench - 24.6% on the official board against 51.5% vendor-claimed for the same model. That last number is the actual mechanism, and it is why I do not think my ranking is "righter" than LiveCodeBench's. A leaderboard measures a model answering in a box. What you experience is a model driving your agent, with your tool schema, your context window, your file-edit format. The scaffold is not a detail wrapped around the model; on Terminal-Bench's own numbers it is worth as much as the model choice. Use leaderboards to decide what to download. Never use them to predict what will happen inside your own harness. The ranking above took 34 full runs to discover and could not have been Four quantisations of one MoE family, same KV cache and context: | variant | bpw | GiB | score | |---|---|---|---| | MTP-GPU-2 | 3.06 | 12.67 | 50 median of 6 | | MTP-GPU-3 | 3.53 | 14.59 | 49 | | MTP-GPU-4 | 3.97 | 16.43 | 49 | | UD-IQ4 XS | 4.25 | 16.51 | 50 | Flat across the entire 3.06 → 4.25 bpw range , while file size varies by nearly 4 GiB and end-to-end speed varies about 3×. The lowest bpw of the four is the best deal on this hardware, and it is not close - it is the only one that stays fully VRAM-resident. Caveat, stated plainly: this is one MoE family on one 16 GB card, and three of those four rows are single runs. It is not "quantisation never matters", and I would not assume it transfers to dense models, which are materially more quant-sensitive. The claim is narrower and better supported: within the band people actually argue about, on a card this size, for this family, the extra bits bought no measurable code quality and cost real speed. Quantisation-aware training looks free, and I have two pairs that appear to show it: | pair | higher precision | QAT | gap | |---|---|---|---| | gemma-4-26b-a4b | UD-Q4 K M 15.78 GiB → 54 n=1 | Q4 0 13.45 GiB → 55 n=3 | Q4 K M vs Q4 0 | | gemma-4-12b | Q8 0 11.80 GiB → 42.5 n=2 | Q4 0 6.50 GiB → 41 n=1 | ★Q8 0 vs Q4 0 | These are not the same experiment, and collapsing them into one "QAT is free" line is wrong in both directions. At 26B it compares Q4 K M against Q4 0 - both 4-bit-class, a near-like-for-like Except the 12B QAT arm is one run , against a non-QAT arm whose own two runs span 42–43. One point of difference, inside both models' measured spread. So I am not claiming it. The experiment that would settle it is two more runs of gemma-4-12b-qat , and until those exist the honest statement is: QAT at 26B costs nothing measurable; QAT at 12B is untested. I am flagging this at length because it is the shape of claim that gets repeated forever after one run, and I nearly published it. This is the half I did not expect to be interesting. For a long time I carried a single global noise figure - "±3" - and applied it to every comparison. That was always wrong , and it took replicating several models to see it. Consecutive runs, identical config, identical engine, same box, nothing changed between them: | model | runs | median | range | |---|---|---|---| | gemma-4-e2b | 31, 31, 31 | 31 | 0 | | gemma-4-e4b | 43, 42, 42 | 42 | 1 | | devstral-small-2-24b | 40, 39, 40 | 40 | 1 | | gemma-4-26b-a4b-qat | 55, 54, 55 | 55 | 1 | | qwen3.5-4b | 31, 33, 33 | 33 | 2 | | champion 3.06bpw | 49, 50, 50, 50, 48, 52 | 50 | 4 | openai/gpt-oss-20b | 40, 38, 32 | 38 | ★8 | gpt-oss-20b swings 8 points across three identical runs. Its published single-run number - the 40 that would have gone in the table if I had run it once, as one normally does - is the top of its range, not its centre. Single-run reporting flattered it by two points and nothing in that run signalled it. Meanwhile gemma-4-e2b ran three times and produced not just the same score but the identical failure list - the same 25 tasks, the same error text. The only diffs between run 2 and run 3 The obvious explanation for gpt-oss's swing is "it is borderline on lots of tasks, so it coin-flips them". That explanation is dead , and e2b is what killed it: e2b fails more tasks than gpt-oss - 25 against 16–24 - with What separates them is how they fail. gpt-oss loses tasks to agent mechanics : a bare top-level return producing ERR INVALID TYPESCRIPT SYNTAX , SyntaxError: Unexpected end of , literal input \n escape sequences written into a .mjs instead of real newlines. Its worst run lost five tasks to syntax , not reasoning. Whether a generation comes out as a parseable file is close to a coin flip. Missing an edge case, by contrast, is a stable property of what the model knows. ⇒ A model's error bar is predicted by its failure mode. This was a hypothesis after the first replication night and it then held as a prediction on e2b, at the opposite end of the quality range. The practical consequence: you cannot import someone else's error bar, and you cannot import mine. If you are comparing two local models and the gap is under 3 points, you do not have a This one is new, it is a benchmark-design finding, and I have not seen any local-model leaderboard report it. qwen3.5-4b ran three times at temperature 0: 31, 33, 33 . Ordinary-looking noise. Then I looked at which tasks failed: And the confirmation: a fourth run had been started on r1's day and killed partway through. Over the 33 tasks it graded, it matches r1 exactly . So day one has two agreeing runs, day two has two agreeing runs, and the two sessions disagree with each other. ⇒ ★ Every determinism claim in this campaign is scoped to a single session. e2b 's "31/56 three times, range 0, identical failure list" and e4b 's byte-identical replicates were all gathered with the replicates consecutive inside one session . That design I do not know the mechanism yet. Candidates: the model server was restarted between sessions and something non-obvious differs in its state; a driver or engine load-order effect; machine load see the next result, which accounts for 3 of the 10 flips but not the other 7 . What I can say is that running your replicates back-to-back will understate your error bar , and that is exactly how everybody runs replicates, including me. If you take one methodological thing from this piece, take this: space your replicates across sessions, or state that you did not. The champion configuration, six runs at matched constants: 49, 50, 50, 50, 48, 52. Median 50, range 4. Now look at which tasks failed: | run | failures | |---|---| | 1 | Q03 Q05 Q25 Q45 Q50 Q51 Q52 | | 2 | Q03 Q05 Q13 Q25 Q51 Q52 | | 3 | Q03 Q05 Q13 Q25 Q51 Q52 | | 4 | Q03 Q05 Q13 Q26 Q36 Q51 | | 5 | Q01 Q03 Q05 Q13 Q25 Q46 Q49 Q51 | | 6 | Q03 Q25 Q46 Q52 | Thirteen distinct tasks rotate through the failure slots. Exactly one - Q03 - failed all six times. Frequencies: Q03 6/6, Q51 5/6, Q25 5/6, Q05 5/6, Q52 4/6, Q13 4/6, Q46 2/6, and six So almost every "this local model can't handle X" claim you read is derived from a single run, and on this evidence that claim is unfounded roughly 12 times out of 13. I know because I made exactly that mistake. Three runs in, I had confidently written down a "consistent failure set" of five tasks. The next run passed two of them and produced two first-time failures I had never seen. My five-task set was a three-sample artifact. It cost me two wasted evaluations. I built a cheap screening gate on those five tasks; it green-lit two candidate configurations, both of which then tied the champion exactly on the full battery. The gate was measuring rotation, not quality. Worse, when I rebuilt the screen properly - 12 tasks, scored in aggregate, with a rejection threshold derived from a bound rather than picked - it still failed. The bound assumed a challenger would pass everything outside the screen. Three different models broke it , one by eight tasks. A subset screen now gets used only to reject fast, never to promote, bound, or crown. If you benchmark local models: score in aggregate, over more than one run, and never conclude anything from the identity of a failing task. A popular claim is that q8 KV cache costs quality against fp16. I tested it twice: | config | ctx | score | wall-clock | |---|---|---|---| | KV q8 0 | 32768 | 50/56 | 23.2 min | | KV f16 | 32768 | 50/56 | 26.2 min | | KV f16 | 65536 | 50/56 | 58.8 min | +0 quality at either context size, and fp16 was slower in both - 1.13× at matched context, 2.79× once context went to 65536 and VRAM hit 96.7%. Note carefully what that 2.8× actually was. It was not KV precision - it was context size pushing VRAM to the edge. I only know because the second fp16 run changed one variable; the first had moved two at once and would have supported the wrong conclusion, confidently. The embarrassing part: I had been daily-driving that slow corner for two days, on the strength of a promising subset-screen result that turned out to be the rotation from Result 6. There is a matching cliff on the other side. Splitting MoE experts between GPU and CPU has a usable band of about 15.1–15.6 GB on a 16.3 GB card , and both errors are expensive. Too many experts on CPU strands the card: one model at a bad split ran 113 s/task and at a good one 75 s/task - 35 minutes saved on a single row, with no score change . Too few thrashes into Windows shared memory: at 97.7% VRAM one task took 306 seconds , and after re-splitting, 37 seconds. 8.3×. I have a standard probe: three prompts, streaming, temperature 0, median tok/s, against a 15 tok/s floor. It was wrong twice, in opposite directions. One configuration probed faster than the champion 73 vs 70 tok/s and then ran 2.48× slower on same-verdict tasks. Another probed 1.74× faster after a tuning change and delivered 2.2× end-to-end. The reason is structural: an agent battery is dominated by prompt processing over a growing KV cache , not by token generation on short prompts. The probe measures the thing that is not the Use generation-speed probes as a floor for rejecting hopeless candidates. Never cite one as evidence a configuration is fast. Four of the 56 tasks grade performance by wall-clock - naive vs linear-time, sized for a wide separation. Comparing qwen3.5-4b 's cross-day replicates exposed what that really measures: | task | day 1 | day 2 | verdict | |---|---|---|---| | Q12 perf, rust | 90 s | 24 s | FAIL → PASS | | Q32 perf, js | 51 s | 29 s | FAIL → PASS | | Q41 perf, ts | - | - | flipped the other way | Identical weights, identical config, 3.75× on the same task . Day 1's whole pass ran 27% slower because I was actively using the machine. Those verdicts measured my box. This is a real defect in my corpus and I would rather report it than quietly widen the thresholds. Anyone grading agent performance by wall-clock inherits it. The fixes available are: run perf tasks only on a verified-idle box, record a load probe per run and reject contaminated runs, or grade complexity structurally instead of by clock. I have not picked one yet. ⚠️ It does not explain the session split in Result 5: 7 of the 10 flipped tasks are not perf tasks. Machine load is one contaminant, not the mechanism. Several models lost tasks not to bad reasoning but to not being able to emit a valid file : gpt-oss-20b lost return , one writing literal \n escape sequences into a .mjs . north-mini-code-1.0 lost 2 the same way - an unterminated triple-quoted string, and a Rust E0425 . granite-4.1-8b likewise. devstral reached for a These are edit-mechanics and environment-awareness failures, not intelligence failures, and they are invisible in a bare pass rate. They are also the most actionable thing here for anyone building an agent: the model that reasons well but cannot reliably write a file is worse in practice than its benchmark score suggests - and, per Result 4, it is also the model whose score you can trust It is the strongest argument I have that agent benchmarks should grade mechanics separately from reasoning. Mine currently does not, and that is a gap. The spec for this benchmark had always listed "held constant for every row: ctx 32768, KV q8 0, parallel 1". Nothing ever recorded or verified it. The KV cache setting in LM Studio is sticky, global, per-model, survives unloads, is not a load flag, and is not reported by lms ps --json . It silently flipped to fp16 on my box and stayed there for two days. Rows from before and after were compared as though they were comparable. A held-constant nobody measures is not held. Every run now emits its actual ctx, KV type, parallelism, VRAM, GPU/CPU expert split, model path, agent version and binary mtime into a metadata file, and historical rows are explicitly marked inferred rather than measured so a reconstruction can never be cited as evidence. Enforcement caught more than it had any right to. A pre-load config writer found: | model | field | found | should be | |---|---|---|---| | gpt-oss-20b | numParallelSessions | 4 | 1 | | qwen3-coder-30b | ctx | 8096 | 32768 | | nemotron-3-nano-omni | ctx | 24000 | 32768 | | qwen3.5-4b | ctx | 32800 | 32768 | | qwen3.5-4b | KV cache | unset → f16 | q8 0 | The numParallelSessions: 4 one is my favourite, because it splits the KV allocation across four slots behind an lms ps that proudly reports CONTEXT 32768 . Two older numbers I had been citing as priors turned out never to have been measured at the benchmark's own constants. Three ops traps each came within minutes of publishing a fake row: lms load never creates the per-model config file. lms load fuzzy-matches when no key matches exactly. qwen3.6-35b-a3b-mtp - @iq3 s . Caught about 40 seconds into a run that would otherwise have looked completely normal.The general lesson, and the reason this section exists at all: A benchmark's integrity failures do not look like errors. They look like successful runs. Between the early rows and the late ones, LM Studio auto-updated its inference engine from 2.25.2 to 2.27.1 at 01:21, with auto-update on by default. So I owed a control: re-run the champion, unchanged, on the new engine. The first control scored 48/56 against a baseline whose runs had never gone below 49, with two never-before-failed tasks and 1.33× slower wall-clock. Two signals pointing the same way. It would have been very easy to write "the engine update cost 2 points". The second control scored 52/56 - the highest the champion has ever scored - in 21m23s, its normal speed. Neither the deficit nor the slowdown reproduced. Across all six matched runs the I am reporting the sequence rather than just the conclusion, because the one-run version of this was a publishable-looking finding that was simply wrong, and it is the same lesson as Result 6 arriving from a different direction. Your inference engine is still a variable: pin it, record it, and expect it to update itself while you sleep - mine did, and its auto- delete setting can garbage-collect the old engine you would need to reproduce anything. The corpus works because the hidden tests and reference solutions are hidden. Publishing them destroys the instrument for every model trained afterwards. Publishing nothing makes every number above an unverifiable claim. I had planned a split - ship 15 tasks complete, seal 41. That plan is moot: the full corpus has been publicly cloneable since 2026-07-25 , on a branch of a public repository, hidden tests and Contamination date: 2026-07-25.Every model in the table above was released before that date and therefore could not have trained on this corpus. All 36 runs stand. Any model released after it must be treated as potentially contaminated on these 56 tasks. So: everything ships - all 56 fixtures, all hidden verifiers, all reference solutions, the harness, the per-run metadata, and RESULTS-q56.csv with every run's full failure list. Clone it and check any number in this piece. That makes Q56 a one-shot instrument against future models, which is a real cost and I am not going to dress it up. The successor corpus is being built sealed, and the design lesson is one I would pass on to anyone building a benchmark they intend to keep: decide your publication posture before the first push, not after 36 runs. A public repo is public in every branch. What survives publication completely is the part I actually care about: the methodology and the measurement results. That leaderboards anti-correlate, that error bars are model properties, that The harness runs from any clone and takes a model id, so "bench your own model" is a real ask rather than an aspiration. The three most useful contributions, in order: The corpus, the harness, all 36 runs and every run's failure list are at github.com/mrdushidush/claudette https://github.com/mrdushidush/claudette/tree/battery/q50-quality-corpus/runs/eval-2026-05-29/battery - MIT OR Apache-2.0.