cd /news/machine-learning/a-7-5b-model-beat-a-24b-on-my-coding… · home topics machine-learning article
[ARTICLE · art-83031] src=dev.to ↗ pub= topic=machine-learning verified=true sentiment=· neutral

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.

read22 min views1 publishedAug 1, 2026

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.

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 - MIT OR Apache-2.0.

── more in #machine-learning 4 stories · sorted by recency
── more on @gemma-4-e4b 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/a-7-5b-model-beat-a-…] indexed:0 read:22min 2026-08-01 ·