Research log Small Model Experimentation
GitHub

Qwen3.5-4B Generator-Verifier Gap

Small models grade their own code only after

The one idea you need

Picture a coder who dashes off eight rough versions of a function. Asked to grade them on sight, they nod along and mark almost everything correct. Given a minute to trace each line, they turn sharp — reliably catching the broken ones.

The question

If a small model writes eight tries at a coding problem, can it reliably tell which ones actually work — without running the code?

What we found

Only after it thinks. Judging on sight, the model rubber-stamps 91% of its tries as correct while just 77% truly pass — barely a check, mostly agreeing with itself. Given room to reason first, it becomes a real critic: letting it pick its best of eight lifts success from 77% to 86%, closing three-quarters of the gap to a flawless picker's 89% ceiling.

Why it matters

Before training a separate model to rank candidate answers, first have the model reason through and grade its own — zero extra training, it beats blind picking, and it works from the task text alone with no hidden tests.

Best of eight tries, blind pick vs a thinking self-check77% → 86%against a flawless picker's 89% ceiling
Share of the gap to the ceiling that thinking closes24% → 75%snap-judging vs thinking self-check
Answers the snap-judge calls correct91% vs 77% truerubber-stamping, not real judgment
How reliably it ranks a good answer above a bad one77% → 93%snap-judging vs thinking
On this page
  1. Results at a glance
  2. Overview
  3. Report
    1. Summary
    2. Research Program Fit
    3. Method
    4. Results
    5. Controls
    6. Oracle Versus Deployable Evidence
    7. Interpretation
    8. Next Experiments
    9. Artifact Manifest
  4. Experiment log
  5. Figures
  6. Data files
  7. Reproduce
  8. Related

Results at a glance 3

How good the picked answer is, by how it was chosen

How to read

Four accuracy bars for the coding tasks, left to right: picking one of eight tries at random, the snap-judge's pick, the thinking-judge's pick, and a flawless picker that peeks at hidden tests. Taller is better; the last bar is the ceiling.

0%25%50%75%100%pass@1 (random pick)pass@1 (random pick)77.1%no-think verifier pickno-think verifier pick80%thinking verifier pickthinking verifier pick86%oracle pass@8oracle pass@889%

Takeaway → The thinking-judge bar nearly reaches the flawless ceiling and towers over random and snap-judge picks — reasoning before judging recovers most of the achievable gain.

Data table
conditionMBPP accuracy
pass@1 (random pick)77.1%
no-think verifier pick80%
thinking verifier pick86%
oracle pass@889%

Numbers from experiments/qwen35_4b_generator_verifier_gap/runs/summary.json

Technical framing

Best-of-8 accuracy: the model's own verifier closes most of the oracle gap — A zero-training thinking self-verifier picks best-of-8 at 86%, closing 75% of the pass@1-to-oracle gap (24% without thinking).

Skill at telling its own right answers from wrong ones

How to read

Two skill scores — grading accuracy that corrects for guessing, and how reliably a correct answer outranks a wrong one — each shown for snap-judging (lighter) versus thinking (darker). Higher is better; 50% is pure guessing.

0%25%50%75%100%balanced accuracybalanced accuracy62.7%82.7%AUROCAUROC77.3%92.6%

Takeaway → Both scores jump sharply with thinking — grading from 63% to 83% — turning a near-coin-flip judge into a dependable critic of its own code.

Data table
conditionno-thinkthinking
balanced accuracy62.7%82.7%
AUROC77.3%92.6%

Numbers from experiments/qwen35_4b_generator_verifier_gap/runs/summary.json

Technical framing

Intrinsic self-verification skill: thinking turns the model into a real critic — Thinking adds +0.20 balanced accuracy and +0.15 AUROC when judging the model's own candidates correct vs incorrect.

How often each judge calls an answer correct, versus the truth

How to read

Three bars: the share of candidates the snap-judge calls correct, the share the thinking-judge calls correct, and the true pass rate as the honest target. Landing closer to the true rate is better; far above it means rubber-stamping.

0%25%50%75%100%no-think verifierno-think verifier91.2%thinking verifierthinking verifier82.8%actual correct rateactual correct rate77.1%

Takeaway → The snap-judge sits well above the truth at 91% against a real 77%; thinking pulls its approvals down toward the actual rate — a sign of genuine judgment.

Data table
conditionrate judged correct (say-A)
no-think verifier91.2%
thinking verifier82.8%
actual correct rate77.1%

Numbers from experiments/qwen35_4b_generator_verifier_gap/runs/summary.json

Technical framing

Yes-bias control: without thinking the verifier mostly agrees with the generator — No-think says 'correct' 91% of the time vs a true 77% pass rate; thinking lowers the yes-bias toward the truth.

In the author’s words from the Overview · “Results”

Checking is easier than doing — but only with thinking. No-think self-verification is weak/yes-biased (AUROC 0.77, says "correct" 91%); thinking makes it a real critic (AUROC 0.93). C2 is plumbing, not capability. The model's own black-box, training-free, deployable thinking-verifier closes 75% of the pass@1→oracle gap. The selection program has real headroom. C9 inversion: thinking helps verification (+0.20 balanced-acc) at least as much as generation — its deepest value may be helping the model know which answer is right.

Overview

Research Program

  • Program: evidence_conditioned_selection (bridges to test_time_reasoning_budget via the thinking axis).
  • Program question: is the C2 selection wall (coverage ≫ deployable selection) a capability limit (the model can't tell right from wrong any better than it can produce right) or a plumbing limit (verification is good, but current selectors waste it)?
  • Prior anchors: claim C2 (qwen35_4b_retrieval_adapt_verify_scale, qwen35_4b_real_sample_verify_commit); the whole selection program builds trained selectors and reports SELECTED accuracy but never isolates the upstream black-box primitive of self-verification. Claim C9 (thinking helps generation content).

Question

Is checking easier than doing for a frozen 4B? Measure its intrinsic verification skill (given a candidate solution, judge correct/incorrect as a black box — no execution, no hidden tests) against its generation skill (pass@1) on the same MBPP items, at no-think and thinking-on. And can the model's own verifier close the pass@1 → oracle-pass@k gap — i.e. is C2 fixable? Does thinking help verification asymmetrically more than it helped generation (checking is often the easier reasoning problem)?

Hypothesis

If verification balanced-accuracy ≫ generation pass@1 and verifier-selected accuracy approaches oracle pass@k, the selection program has real headroom (C2 is plumbing). If verification ≈ generation and verifier-selection ≈ pass@1, C2 is a capability limit and the portfolio should pivot to coverage.

Setup

  • Model Qwen3.5-4B frozen (bf16, fast path). MBPP sanitized test, 100 tasks, k=8 no-think candidates, execution-labeled.
  • Verification judge: present the task spec + one example assert + the candidate code; read the A/B logit at a forced answer position → P(correct) (A=32 correct, B=33 incorrect, single tokens). No-think: one forward. Thinking: generate up to a 1024-token budget, force </think>, then read the A/B logit.
  • Metrics: generation pass@1 / oracle pass@k; verification balanced-accuracy + AUROC (discriminate the model's OWN correct vs incorrect candidates) at no-think and think; verifier-selected best-of-k accuracy vs pass@1 vs oracle (the fraction of the pass@1→oracle gap the verifier closes); the thinking asymmetry.
  • Controls: foreign-solution judge (a different task's candidate → should be rejected, P(A) low) tests that the verifier actually reads the task; say-A rate + balanced accuracy control the yes-bias.

Run

HF_HUB_OFFLINE=1 PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True \
  ../../.venv/bin/python scripts/run.py --tasks 100 --k 8 --budget 1024
../../.venv/bin/python scripts/verify.py      # execution labels
../../.venv/bin/python analysis/analyze.py     # metrics + figure

Results

Full results in reports/report.md.

quantityno-thinkthinking
verification balanced accuracy0.6270.827
verification AUROC0.7730.926
verifier-selected best-of-8 (deployable)0.8000.860
pass@1(0.771)→oracle(0.890) gap closed+24%+75%
foreign reject rate1.001.00
  • Checking is easier than doing — but only with thinking. No-think self-verification is weak/yes-biased (AUROC 0.77, says "correct" 91%); thinking makes it a real critic (AUROC 0.93).
  • C2 is plumbing, not capability. The model's own black-box, training-free, deployable thinking-verifier closes 75% of the pass@1→oracle gap. The selection program has real headroom.
  • C9 inversion: thinking helps verification (+0.20 balanced-acc) at least as much as generation — its deepest value may be helping the model know which answer is right.

Interpretation

The selection bottleneck isn't that the model can't tell right from wrong — it can, well, once it thinks. The highest-leverage "plumbing" for C2 is thinking-augmented self-verification (cheaper and stronger than the trained selectors the corpus favored). Thinking (C9) and selection (C2) meet in thinking-verification. See claim C10.

Artifacts

  • src/judge_lib.py (generation + A/B-logit verification judge), src/tasks.py. scripts/run.py, scripts/verify.py, analysis/analyze.py. data/records.jsonl, data/labels.jsonl, data/tasks.json (small, in-repo). No external artifacts (behavioral + logit-read; no activation caching).

Report

Rendered from reports/report.md

Summary

Is the C2 selection wall (coverage ≫ deployable selection) a capability limit or a plumbing limit? We measure a frozen Qwen3.5-4B's intrinsic verification skill (black-box: judge a candidate correct/incorrect from the A/B logit, no execution, no hidden tests) against its generation skill (pass@1) on the same MBPP items, at no-think and thinking-on. Result: checking is easier than doing — but only with thinking. No-think self-verification is weak and heavily yes-biased (balanced-acc 0.627, AUROC 0.773, says "correct" 91% of the time — essentially the generator agreeing with itself). Thinking turns it into a real critic (balanced-acc 0.827, AUROC 0.926). And the model's own thinking-verifier — zero training, fully deployable — selects best-of-8 at 0.860, closing 75% of the pass@1→oracle gap (0.771 → 0.890), vs 24% for no-think. So C2 is plumbing, not capability: the selection program has real headroom, and the lever is thinking-augmented self-verification. This also inverts C9: thinking helps verification (+0.20 balanced-acc) at least as much as it helped generation — its deepest value may be helping the model know which answer is right, not just produce right ones.

Research Program Fit

Bridges evidence_conditioned_selection (C2) and test_time_reasoning_budget (C9). C2 has been treated as an empirical wall across ~160 experiments, all of which build trained selectors and report selected accuracy; none isolated the upstream black-box primitive of self-verification, or asked whether the wall is verification capability vs evidence plumbing. This is that one measurement.

Method

  • Model Qwen3.5-4B frozen (bf16, fast path). MBPP sanitized test, 100 tasks, k=8 no-think candidates, execution-labeled (pass rate 0.771).
  • Black-box verification judge: present the task spec + one example assert + the candidate code, read P(correct) from the A(=correct, tok 32) vs B(=incorrect, tok 33) logit at a forced answer position — no execution, no hidden tests. No-think = one forward; thinking = generate up to 1024 thinking tokens, force </think>, then read the A/B logit at "Answer: ".
  • Metrics: generation pass@1 / oracle pass@k; verification balanced-accuracy + AUROC (discriminate the model's own correct vs incorrect candidates); verifier-selected best-of-k vs pass@1 vs oracle (gap closed); the thinking asymmetry. Controls: foreign-solution judge (a different task's candidate → should be rejected); say-A rate + balanced accuracy control the yes-bias.

Results

quantityno-thinkthinking
verification balanced accuracy0.6270.827
verification AUROC0.7730.926
say-A (correct) rate0.910.83
verifier-selected best-of-80.8000.860
pass@1→oracle gap closed+24%+75%
foreign reject rate1.001.00

Generation: pass@1 0.771, oracle pass@8 0.890. Figure: analysis/gen_verify.png.

Finding 1 — checking is easier than doing, but only with thinking

No-think self-verification is weak (balanced-acc 0.627, AUROC 0.773) with a strong yes-bias (say-A 0.91 > base pass 0.771) — the no-think "verifier" mostly re-agrees with the generator. Thinking makes it a genuine critic: balanced-acc 0.827, AUROC 0.926, and a lower yes-bias (0.83). So the model can tell its own good solutions from its bad ones — when it thinks.

Finding 2 — C2 is plumbing, not a capability wall

Using the model's own (black-box, training-free, deployable) verifier to pick best-of-8 lifts pass@1 0.771 → 0.860 with thinking (0.800 no-think), against an oracle of 0.890 — closing 75% of the achievable gap (24% no-think). The selection program the corpus never stopped building has real headroom; the missing piece was thinking-augmented self-verification, not a better trained selector.

Finding 3 — thinking helps verification (the C9 inversion)

Thinking raised verification balanced-acc +0.20 (AUROC +0.15), comparable to or exceeding its effect on generation (C9: greedy +15pp). Thinking's value is not only in producing correct answers but in recognizing them — which is exactly the primitive selection needs.

Controls

Foreign-solution judgments (a different task's candidate spliced in) are rejected at rate 1.00 in both modes → the verifier genuinely reads the task, not a length/format heuristic. Balanced accuracy and the say-A rate control for the yes-bias (a constant "A" predictor scores 0.5 balanced-acc; no-think's 0.627 shows weak-but-real discrimination, thinking's 0.827 shows strong discrimination).

Oracle Versus Deployable Evidence

The verification judge uses only the task spec + candidate (no execution, no hidden tests), so verifier-selected accuracy (0.860 thinking) is deployable. pass@8 = 0.890 is the non-deployable oracle ceiling. So a deployable, zero-training thinking-verifier recovers 75% of the oracle headroom — notably, without the visible-test signal the earlier thinking controller relied on (which was bounded by visible-test false-passes at ~0.91 deployable / 0.93 oracle on a different pool).

Interpretation

For a small model on this benchmark, the selection bottleneck is not that the model can't tell right from wrong — it can, quite well, once it thinks (AUROC 0.93). The corpus's C2 wall is an evidence-plumbing problem, and the highest-leverage plumbing is the model's own thinking-verifier. This unifies the corpus's two strongest recent threads: thinking (C9) and selection (C2) meet in thinking-augmented verification, which is cheaper and stronger than the trained selectors the selection program has favored, and a natural next controller signal.

Limitations

  • MBPP (basic, likely partly contaminated), n=100, single seed; candidates are no-think generations (a think-generated pool may be harder to verify). The verifier sees one example assert (as the generator did). The "checking > doing" comparison is across different scales (discrimination AUROC vs generation pass@1); the deployable selection result (verifier-selected vs pass@1 vs oracle) is the apples-to-apples headline.

Next Experiments

  • Wire the thinking-verifier as the controller signal (vs / combined with the visible test) and measure the deployable accuracy-vs-token Pareto — does self-verification beat the visible-test C2 wall at matched cost?
  • Verify a think-generated candidate pool (harder negatives) and on a contamination-controlled substrate.
  • Iterated generate→self-verify→revise loop: does the strong thinking-verifier drive self-correction?

Artifact Manifest

See artifact_manifest.yaml. Small records/labels + summary + figure in-repo; no external artifacts.

Experiment log 4

Show the running log (4 entries)

Scaffold

Attacks the C2 selection bottleneck at its root: is the wall a verification-CAPABILITY limit or a selection-PLUMBING limit? Measures the frozen 4B's intrinsic black-box verification skill vs its generation skill on the same MBPP items, at no-think and thinking-on. Also inverts C9 (thinking helps generation content; does it help verification asymmetrically?).

Method

  • k=8 no-think candidates per task (execution-labeled). Verification judge: task spec + candidate code -> read A/B logit -> P(correct); A=32/B=33 single tokens. No-think = one forward; thinking = generate up to 1024 thinking tokens, force </think>, then read the A/B logit at "Answer: ".
  • Metrics: generation pass@1/pass@k; verification balanced-accuracy + AUROC; verifier-selected best-of-k vs pass@1 vs oracle (gap closed); thinking asymmetry. Controls: foreign-solution judge, say-A rate.

Smoke

4 tasks x k=4: pipeline validated. P(A) ~0.65-0.88 on own (correct) candidates, ~0.01 on FOREIGN candidates (verifier correctly rejects a different task's solution) -- the judge reads the task. Thinking raised P(A) on correct candidates (0.7 -> 0.9+); need incorrect candidates (full run) to see discrimination.

Results (see reports/report.md)

Generation pass@1 0.771, oracle pass@8 0.890. Verification balanced-acc/AUROC: no_think 0.627/0.773 (say-A 0.91, heavy yes-bias), think 0.827/0.926 (say-A 0.83). Verifier-selected best-of-8: no_think 0.800 (+24% of the pass@1->oracle gap), think 0.860 (+75%). Foreign reject 1.00 both. Findings: checking is easier than doing but only WITH thinking; C2 is plumbing not capability (deployable thinking-verifier closes 75% of the oracle gap, no training/execution); thinking helps verification (+0.20 bal-acc) >= generation (C9 inversion). Claim C10.

Note: judge_think initially generated thinking for the whole 800-item list in one _gen call (fine for the 16-item smoke, would OOM at 800) -- fixed with chunked OOM-resilient processing before the full run.

Figures 1

gen verify
gen verify · analysis/

Data files 1

Result tables and metrics copied from the experiment folder — preview inline or open the raw file.

Reproduce

Smoke test

../../.venv/bin/python scripts/run.py --smoke

Full run

HF_HUB_OFFLINE=1 PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True ../../.venv/bin/python scripts/run.py --tasks 100 --k 8 --budget 1024

Run steps are documented inside the experiment folder (README and scripts).

Browse the experiment folder on GitHub ↗