Research log Small Model Experimentation
GitHub

Qwen35 4B WHY-Think Scale

Design frozen: the CORRECTED dual-channel WHY curriculum - a genuine step-by-step <think> derivation AND strippable #WHY: code comments - built to scale WHY without destroying the 4B's native thinking (GPU sweep not yet run)

The one idea you need

This small model is a THINKING model: before it answers it writes a hidden step-by-step reasoning trace, and its coding skill depends heavily on that trace - it is the single most-replicated finding in this project. An earlier winning idea taught the model WHY each line of correct code is correct by attaching a short true reason to every line as a comment (graders ignore comments, so any score gain was a clean signal that explaining WHY helped). But that curriculum, and the version that scaled it up, left the model's hidden thinking channel almost empty during training - which risks teaching the model to STOP thinking, wrecking the very ability its coding rests on. This experiment is the correction: every training example now carries BOTH channels - a genuine hidden derivation that reads the spec, decides an approach, builds the code step by step, and traces a real worked example by hand to check the answer, AND the clean code with the strippable WHY comments. So the model learns to think richly first, then write well-reasoned code. The question is the same scaling question as before - does teaching WHY keep improving the score as you add more data, up to a peak - but asked the RIGHT way for a thinking model, and measured with thinking turned ON (the shared exam was previously, mistakenly, run with thinking OFF).

The question

Can we teach a thinking model WHY code is correct WITHOUT emptying out (and destroying) its native hidden reasoning - by training a genuine step-by-step think derivation alongside the WHY-commented code - and does that dual-channel WHY signal SCALE (climb to a peak, stay flat, or collapse) on the coding exams when measured with thinking turned ON? And first: can we generate that much genuinely-diverse, self-checked, contamination-free dual-channel data?

What we found

This phase built and proved the machinery; the GPU training sweep has not run yet. The generator now emits, for every example, a real hidden reasoning trace generated mechanically from the program's shape - parse the spec, pick an approach (a running total? a builder? a search?), build the code step by step, then trace one of the example's own tests by hand, running the program to get every intermediate value so the trace is true - plus the clean code with strippable WHY comments. Every example is checked by actually running it: strip the comments and the code passes all its tests; the commented version runs identically; and the hidden trace's worked example is recomputed byte-for-byte and must match real execution (a tamper of any traced number is rejected). Measured on a 5000-example sample it hits all 59 program families across 13 task kinds, keeps 100% distinct programs, carries about 1,196 distinct comment-reasoning patterns AND about 4,997 distinct think-derivation skeletons (out of 5000 - the reasoning genuinely varies, it is not one template); at 10,000 examples the think skeletons stay ~100% distinct. Nothing looks like the benchmarks (zero banned benchmark names anywhere - prompt, hidden think, or answer - and zero distinctive shared code fragments through 10,000 examples). Crucially the added thinking fits the training window: the real tokenizer render tops out at 739 tokens (median 467) against a 4096 cap, with zero examples truncated. On top sits a five-rung ladder (2000/5000/10000/20000/40000), each corpus fingerprinted so it rebuilds identically, a fail-closed trainer that trains a fresh small adapter per rung at ONE epoch (unlimited unique data, never re-shown), with a POSITIVE weight on the think channel so the native thinking is preserved and shaped rather than emptied, and a per-rung exam sweep on HumanEval + MBPP run with thinking ON and the base co-measured each rung (no stale thinking-off number carried). Tests pass.

Why it matters

This is Phase A of a scale-then-reinforcement-learning plan, and it fixes a design flaw that could have quietly sabotaged the whole WHY family: training a thinking model with an empty think channel can teach it to stop thinking, and this model's coding skill lives in its thinking. By putting a true, self-checked reasoning derivation in the think channel AND keeping the clean WHY-commented code, the curriculum teaches WHY while PRESERVING the model's native reasoning - and the measurement is finally thinking-ON, matching how the model actually works. The winning rung becomes the supervised foundation the reinforcement-learning phase builds on; a flat curve would honestly reprice the WHY family instead. The retention read alone - does thinking-on coding survive training - is a first-order result.

Distinct think derivations~4,997distinct think skeletons out of 5000 rows (not one template)
WHY families5913 task categories; all 59 hit at 5000 rows; 100% unique programs
Think fits the window739 tokreal-tokenizer render max (median 467) vs 4096 cap; 0 truncated
Scaling ladder2k-40kfive sha-pinned rungs; measured thinking-ON, base co-measured
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. Results (2026-07-19, thinking-on, shared fitness harness)
    8. Interpretation
    9. Next Experiments
    10. Artifact Manifest
  4. Experiment log
  5. Reproduce
  6. Related

Results at a glance 1

Planned dual-channel WHY-think ladder: does the WHY signal climb to a peak (thinking-on)? DESIGN chart (GPU sweep not yet run); the climb curve is a HYPOTHESIS SHAPE, not a measurement. This is the CORRECTED dual-channel WHY curriculum: a genuine step-by-step <think> derivation (parse spec -> approach -> build -> trace a real worked example, byte-verified against execution) AND strippable #WHY: code comments, so training teaches WHY while PRESERVING the 4B's native thinking (its coding depends on the think trace). The base is CO-MEASURED thinking-ON for every rung (the old thinking-OFF 76.2% number is deliberately NOT carried); y is plotted as problems vs that co-measured base, so the 0 line IS the base. Measured construction facts: 59 families, ~1,196 distinct #WHY templates and ~4,997 distinct think skeletons at 5000 rows, 100% unique programs, real-tokenizer render max 739/4096 tokens. The alternative hypotheses are FLAT (WHY does not scale on this surface) or a COLLAPSE at the large rungs (the synthetic surface overfits); the peak rung becomes the SFT foundation for the RLVR phase.

HumanEval pass@1 vs co-measured base (problems, thinking-on) ↑ · WHY-think training rows (planned ladder: 2000 / 5000 / 10000 / 20000 / 40000) →

02461k2k3k4khypothesis: dual-channel WHY climbs to a peak (schematic)hypothesis: dual-…co-measured base (thinking-on, per rung)co-measured base …
Data table
WHY-think training rows (planned ladder: 2000 / 5000 / 10000 / 20000 / 40000)co-measured base (thinking-on, per rung)hypothesis: dual-channel WHY climbs to a peak (schematic)
200002
500004
1k05
2k04
4k02

Numbers from experiments/qwen35_4b_why_think_scale/reports/preregistration.md

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

When run, runs/measure/rung_<rows>.json records each rung's four pass@1 numbers (base co- measured thinking-on), the paired McNemar deltas, and the rung-vs-base problem deltas; the assembled curve pass@1(rows) locates the peak. Dual-channel construction facts already established (see reports/report.md): 59 families / 13 categories; 5000-row sample: 59/59 families, 1196 distinct #WHY: templates, 4997/5000 distinct think skeletons, 100% unique programs; 20000-row build 100% unique. Token budget: real pinned tokenizer full render max 739 tokens (p95 619, median over 5000 rows — 0 over the 4096 cap. … Read the full result →

Overview

Research Program

  • Program: agentic_breadth_installation (cognitive-core coding sub-program)
  • Program question: can real, transferable coding capability be INSTALLED into base Qwen/Qwen3.5-4B by designed, contamination-free curricula, proven by transfer — and specifically, does teaching the 4B WHY a correct answer is correct SCALE to a peak worth an RLVR foundation, WITHOUT destroying the model's native thinking?
  • Prior anchors: experiments/qwen35_4b_coding_fitness_harness (the shared eval, being fixed to thinking-on + 8192 budget). Bet #4 (qwen35_4b_why_comment_install) gave the program's biggest fast gain — HumanEval +5 on the clean, comment-inert test — but was underpowered and did not stack. qwen35_4b_why_scale_ladder scaled that WHY curriculum but with a MINIMAL think block.

Question

Qwen3.5-4B is a THINKING model whose coding performance depends on its <think> trace (the repo's most-replicated finding). The prior WHY curriculum put reasoning in inline #WHY: comments and left <think> minimal — which risks DESTROYING the model's native thinking. Does a CORRECTED dual-channel WHY curriculum — a genuine step-by-step derivation IN the <think> block AND the strippable #WHY: comments — SCALE (climb to a peak, stay flat, or collapse) on HumanEval/MBPP measured THINKING-ON, and become the SFT foundation for the RLVR phase?

Hypothesis

Because the 4B's coding depends on its think trace, training WITH a rich, true <think> derivation (not an empty one) should preserve and shape thinking while teaching WHY, so the WHY signal scales at least as well as the comment-only version without the empty-think retention risk. Each row teaches: think richly (derive the solution and verify it with a real worked example) in the native channel, then emit clean-but-#WHY:-annotated code. Because comments are inert to the execution grader, any pass@1 gain is an unconfounded CODE improvement. Honest prior: this is a foundation-building bet; a flat curve remains a likely, informative outcome.

Setup

  • Model: only Qwen/Qwen3.5-4B (rev 851bf6e8…), measured and trained WITH thinking on; one fresh r32/a64 QLoRA adapter per rung from the base_reserialized composite in a single stage.
  • Dataset/task source: scripts/gen_why_think_curriculum.py (construction seed 95200) — 59 parameterized synthetic families producing dual-channel rows (messages = plain "write a function" prompt with spec + signature + public asserts; think = a GENUINE derivation emitted BY CONSTRUCTION, NO teacher model, with a real worked-example trace; answer = clean correct code with strippable #WHY: comments). Every row verified by real execution. Rungs at 2000/5000/10000/20000/40000 rows sha-pinned in data/ladder_manifest.json; the corpora are large, deterministically regenerable, gitignored under large_artifacts/.
  • Train/eval split: training is spec -> dual-channel (think + #WHY code) over synthetic families; evaluation is the held-out spec -> code HumanEval (164) + MBPP (200) — disjoint surfaces, grader ignores comments, measured thinking-on.
  • Baseline: base Qwen/Qwen3.5-4B, CO-MEASURED thinking-on on the same shared harness per rung (no hardcoded thinking-off anchor).
  • Controls: contamination firewall (banned-name audit zero hits over prompt + think + answer; distinctive code 7-gram overlap zero at scale); comments inert to the grader; the think derivation byte-verified against real execution; the base composite authenticated fail-closed before training.
  • Primary metric: greedy pass@1 on HumanEval + MBPP (shared harness, thinking-on), swept per rung.
  • Oracle-only metrics: none gate here; the agentic duet-eval is a follow-on confirm on the peak composite.
  • Hidden-label boundary: benchmarks are executed, never read as data.

Run

Smoke (no GPU, no writes) — compiles, verifies base provenance + fixture, builds a small verified dual-channel corpus, checks the ladder-manifest shas, runs the tests:

python scripts/run.py --smoke

Build the ladder corpora locally from the committed manifest (CPU, model-free):

python scripts/run.py --stage gen-ladder

GPU stages (each gated behind a staged adversarial review; per rung; see reports/preregistration.md for the exact commands and checkpoint order):

python scripts/run.py --stage train   --rows 2000    # r32/a64, epochs 1, seed 95201
python scripts/run.py --stage merge   --rows 2000    # vendored composite merger
python scripts/run.py --stage measure --rows 2000    # shared HumanEval+MBPP harness, thinking-on
# ... repeat --rows 5000 / 10000 / 20000 / 40000 and assemble the curve.

Results

Not yet run. The train/merge/measure stages are gated behind staged reviews and are a SWEEP the orchestrator runs rung-by-rung. When run, runs/measure/rung_<rows>.json records each rung's four pass@1 numbers (base co- measured thinking-on), the paired McNemar deltas, and the rung-vs-base problem deltas; the assembled curve pass@1(rows) locates the peak.

Dual-channel construction facts already established (see reports/report.md):

  • 59 families / 13 categories; 5000-row sample: 59/59 families, 1196 distinct #WHY: templates, 4997/5000 distinct think skeletons, 100% unique programs; 20000-row build 100% unique.
  • Token budget: real pinned tokenizer full render max 739 tokens (p95 619, median 467) over 5000 rows — 0 over the 4096 cap.

  • Contamination through 10000 rows: 663 banned names after whitelist, 0 whole-word hits over prompt + think + answer; 0 distinctive shared 7-grams (78 structural idioms).
  • Unit tests green; run.py --smoke green; boundary drills refuse.

Interpretation

Pending the sweep. A rising-then-peaking curve makes the peak rung the SFT foundation for the RLVR phase (Phase B) and shows the dual-channel design scales WHY without the empty-think retention risk. A flat curve reprices the WHY mechanism on this surface. A collapse bounds the usable dose. The design goal is to preserve the model's native thinking WHILE teaching WHY — the retention read (thinking-on pass@1 not dropping) is itself a first-order result.

Knowledgebase Update

  • Program evidence updated: pending the ladder sweep.
  • Program backlog updated: pending the ladder sweep.
  • Claim ledger updated: pending the ladder sweep (design-only work manufactures no claim).

Artifacts

  • scripts/gen_why_think_curriculum.py — the dual-channel, scale-capable WHY-think generator (59 families, phrase-pool rationales, per-row truth audit + byte-verified worked-example think trace, contamination self-heal, deterministic per seed 95200).
  • scripts/build_ladder.py — builds the five rung corpora + the sha-pinned data/ladder_manifest.json; --verify regenerates and checks shas.
  • scripts/contamination.py + data/contamination/banned_function_names.json.
  • scripts/train_trial.py (fail-closed per-rung trainer), vendored scripts/train_think.py + scripts/merge_adapter.py, scripts/measure_transfer.py (per-rung sweep via the shared harness, thinking-on), scripts/run.py.
  • data/ladder_manifest.json, data/provenance/base_reserialized.json.
  • configs/, reports/ (preregistration, report, artifact manifest), tests/.
  • reports/artifact_manifest.yaml.

Report

Rendered from reports/report.md

FINISHED 2026-07-19 — see §Results. NEGATIVE for the dual-channel design, POSITIVE for the method it surfaced (claim C60). The synthetic-<think> scale ladder collapses coding; a 2×2 ablation pins the cause on synthetic-think supervision; rejection-sampled NATIVE think retains. Only rungs 2k/5k of the sha-pinned ladder were run — the collapse is monotonic (worse as loss drops) with no pre-collapse peak, so 10k/20k/40k of a known-broken design were not spent; the diagnostic ablation + native-trace confirmation replaced them.

Summary

Phase A of the owner's scale-then-RLVR plan, CORRECTED. The prior WHY curricula (qwen35_4b_why_comment_install, qwen35_4b_why_scale_ladder) taught the 4B WHY each line of a correct solution is correct via inline #WHY: comments and left the <think> block minimal. But Qwen3.5-4B is a THINKING model whose coding performance depends on its <think> trace (the repo's most-replicated finding; the shared coding harness was even mistakenly measuring thinking-OFF, now being fixed to thinking-on + 8192 budget). Training the 4B with an empty/near-empty think target risks DESTROYING its native thinking. This cell builds the CORRECTED dual-channel curriculum: a GENUINE step-by-step derivation IN the <think> block AND the strippable #WHY: comments. Each row teaches the model to think richly (derive the solution and verify it with a real worked example) in the native channel, then emit clean-but-#WHY:- annotated code. It builds the sha-pinned five-rung ladder (2000/5000/10000/20000/ 40000) and the per-rung train/eval sweep, measured THINKING-ON, to find the WHY peak as the SFT foundation for the RLVR phase — without the empty-think retention hazard.

Research Program Fit

The program installs real, transferable coding capability into base Qwen/Qwen3.5-4B by designed, contamination-free curricula proven by transfer. Bet #4 (WHY-comment) was the strongest fast lever but underpowered and comment-only; the scale-ladder scaled it but with a minimal think block. This cell asks the scaling question the RIGHT way for a thinking model: with a genuine think derivation trained alongside the #WHY: code, does the WHY signal CLIMB to a peak worth an RLVR foundation, stay flat, or COLLAPSE — measured thinking-on — while PRESERVING the native thinking the 4B's coding depends on? HumanEval/MBPP are the fast transfer + retention signal; the agentic duet-eval is a follow-on confirm on the peak composite.

Method

  • Dual-channel generator (scripts/gen_why_think_curriculum.py, construction seed 95200). --rows N produces exactly N verified rows for any N up to ~30000+, deterministically. 59 parameterized synthetic families across 13 categories (arithmetic accumulation, list reduce, list transform-by-hand, conditional chains, parity/modular/digit arithmetic, nested loops, pairwise/adjacent comparisons, bounded search, state machines, string manipulation, dict aggregation). Per row:

    • messages: a plain write a function prompt (spec + signature + public asserts),

    NO instruction to think or comment (both behaviours must be the model's DEFAULT).

    • think: a GENUINE forward derivation emitted MECHANICALLY from the family

    AST/shape — parse the spec (goal/inputs/output) -> choose an approach from the code SHAPE (accumulator / builder / running-extreme / spread / branch / search / dict) phrased as a decision -> build the solution step by step in construction order -> trace a REAL worked example (one of the task's asserts, executed line by line, values byte-true) -> conclude into the answer. It is NOT the #WHY: comments joined.

    • answer: the CLEAN correct code WITH inline #WHY: comments, strippable via the

    distinct marker.

  • Per-row truth audit (never ship an unverified row), by REAL CPython execution. (1) STRIP the #WHY: comments -> clean code passes ALL asserts; (2) the COMMENTED code runs and passes them IDENTICALLY; (3) the marker is strippable; (4) every #WHY: is line-specific and varies within the row; (5) the think's worked-example trace matches ACTUAL execution — a deterministic rng-free trace CORE (Trace f(args): <var> moves 0 -> 4 -> 12 -> 24, so it returns 24.) is recomputed byte-for-byte at verification and must appear verbatim; (6) the think has an approach-decision phrase and is NOT the joined #WHY: comments. Safety: restricted builtins, no imports/I/O, bounded for-loops only, a step cap. Banned-vocabulary self-heal rejects any row (prompt / think / answer) carrying a benchmark name.
  • Contamination firewall (scripts/contamination.py, committed fixture of all 668 HumanEval + MBPP function names, 663 after the language whitelist). Zero whole-word hits over prompt + THINK + answer; a present-only code-only 7-gram aid finds zero distinctive shared spans vs the benchmark solutions.
  • Scale ladder (scripts/build_ladder.py). Corpora at 2000/5000/10000/20000/ 40000 rows (fixed seed 95200, different N), each verified + contamination-audited, sha-pinned in data/ladder_manifest.json (which also pins the generator sha and the fixture sha). The corpora are large and deterministically regenerable, so they live gitignored under large_artifacts/; --verify regenerates each rung.
  • Install (scripts/train_trial.py -> vendored scripts/train_think.py). One fresh r32/a64 adapter per rung, lr 1e-5, batch 1, grad-accum 8, max-length 4096, w_think 0.2 (POSITIVE: preserves + shapes the native thinking — the crux), w_close 0.2, seed 95201, from the base_reserialized composite (authenticated FAIL-CLOSED). Epoch schedule = 1 epoch at every rung (owner directive): unlimited unique data, vary data VOLUME not epochs; optimizer steps = rows / 8 = 250 / 625 / 1250 / 2500 / 5000.
  • Merge (vendored scripts/merge_adapter.py) with --base-model = the base composite -> merged/why_think_<rows>.
  • Measure (scripts/measure_transfer.py -> SHARED harness, referenced not copied). Base and each rung composite, HumanEval 164 + MBPP 200, greedy pass@1, identical vLLM path, THINKING-ON (8192 budget). Base CO-MEASURED thinking-on per rung; NO hardcoded thinking-off anchor. A SWEEP: all four numbers + paired McNemar deltas + rung-vs-base deltas recorded per rung; the orchestrator assembles pass@1(rows).

Results

Pending the sweep. runs/measure/rung_<rows>.json will carry pass_at_1{base,rung}{humaneval,mbpp} (base co-measured thinking-on), the pass counts, the McNemar b/c paired deltas per dataset, and the rung-vs-base problem deltas; the assembled curve pass@1(rows) locates the peak. Deployable evidence is a pass@1 gain over the co-measured base (a real code improvement because the grader ignores comments); the retention guard is the paired dataset staying within tolerance (and, since thinking is TRAINED not emptied, the native thinking being preserved).

Dual-channel construction facts already established:

  • Diversity. 5000-row sample: 59/59 families across 13 categories, 1196 distinct normalized #WHY: templates, 4997/5000 distinct think skeletons (the derivation genuinely varies across families and rows, not one template), 100% unique clean programs. 10000-row: 1197 #WHY: templates, 9985 distinct think skeletons, 100% unique. 20000-row: 100% unique.
  • Contamination: 663 banned benchmark names after whitelist, 0 whole-word hits over every row's prompt + think + answer; 0 distinctive shared 7-grams (78 shared structural control-flow idioms) at 10000 rows (HF cache present so the aid RAN).
  • Token budget: the think lengthens the render, so it is capped. Real pinned- tokenizer full render (chat + think + </think> + answer) over 5000 rows: max 739 tokenizer tokens (median 467, p95 619, min 330) — 0 rows over the 4096 max- length cap, so the trainer's zero-skip contract holds by construction. Conservative >=3-char/token estimate: max 695. Character render max 2084.
  • Determinism: the corpus is a pure function of (seed 95200, N); two rebuilds are byte-identical; the ladder --verify regenerates every rung to its pinned sha.
  • Unit tests green (dual-channel: worked-example trace re-executed independently; think has approach + trace and is not the joined #WHY:; tamper drills refuse a corrupted trace value / a think set to the joined comments / a missing approach; diversity at 5000/10000/20000; #WHY:-truth re-executed by a separate grader; safety/termination; contamination zero at 10000; determinism; base auth fail-closed; 1-epoch schedule; ladder-manifest sha pinning).

Controls

  • Contamination firewall (banned-name audit over prompt + think + answer + distinctive code 7-gram overlap), both zero at scale, so any benchmark movement cannot be memorization.
  • Comments are inert to the execution grader, so any pass@1 gain is a CODE gain — the design property that makes this the clean test.
  • The think derivation is byte-verified against real execution (the worked-example trace's values and final result are recomputed independently), so the taught reasoning is TRUE, not a plausible-sounding fabrication.
  • The dual channel is itself the control on the empty-think retention hazard: a POSITIVE w_think trains a genuine think trace, so a thinking-on retention drop (if any) is attributable, not silently baked in by an empty target.
  • Base composite authenticated fail-closed (tree + weights) before every rung's training and merge.
  • Identical measurement path for base and every rung (shared harness), base co- measured thinking-on per rung, so deltas are directly comparable.

Oracle Versus Deployable Evidence

Deployable evidence = a pass@1 gain over the co-measured base on HumanEval/MBPP (real, held-out spec -> code generation, comments ignored by the grader, measured thinking-on), assembled into the scaling curve. The retention guard (the paired dataset within tolerance; native thinking preserved) is a control on the forgetting risk, not a capability claim. The agentic duet-eval is the eventual deployable target but is a manual follow-on confirm on the peak composite, not gated here. No metric here uses hidden labels beyond the per-rung pass@1 reads.

Results (2026-07-19, thinking-on, shared fitness harness)

Base (thinking-on, 8192 budget): HumanEval 147/164 (89.6%), MBPP 151/200 (75.5%).

Scale ladder (dual-channel: synthetic <think> @w=0.2 + inline #WHY: code), 1 epoch:

rowsopt stepstrain_lossHumanEvalMBPP
20002505.854129 (−18)136 (−15)
50006252.700121 (−26)119 (−32)

Monotonic COLLAPSE — lower loss (tighter fit to the synthetic target) ⇒ worse coding; no pre-collapse peak. Diagnostic on rung 5k (29 regressions vs 3 gains): native thinking SHORTENS (median 608→503 tok; regression set 759→540) and answers BALLOON (140→319 tok). The model is dragged toward my shallower templated reasoning + verbose commented code.

2×2 ablation @ 5000 rows (same recipe: lr 1e-5, r32/a64, bs1 ga8, seed 95201):

armsynth-think supervised (w=0.2)#WHY: in codeHumanEvalMBPP
base147151
full (rung 5k)yesyes121 (−26)119 (−32)
nowhyyesno113 (−34)124 (−27)
thinkfreeno (w=0)yes138 (−9)129 (−22)
cleanfreeno (w=0)no145 (−2)142 (−9)

Synthetic-think SUPERVISION is the dominant damage: turning it off is +17 HE (WHY on) to +32 HE (WHY off). #WHY: comments are secondary (+7 HE, +13 MBPP when removed). cleanfree (clean code + native/unsupervised think) is nearly retention-neutral ⇒ the synthetic PROBLEM distribution is fine; the hand-authored ANNOTATIONS were toxic.

Rejection-sampling confirmation (native think, execution-verified): 3000 disjoint synthetic problems (offset 10000 of the 40k pool), sampled from base (K=2, temp 0.8, thinking-budget 8192), execution-filtered against the problem asserts (100% solved, 99.6% per-sample pass), trained on native think + native CLEAN code, 1 epoch:

armthink sourceweightHumanEvalMBPP
nowhy (from ablation)authored (synthetic)0.2113 (−34)124 (−27)
rft_w0.2HARVESTED (native)0.2148 (+1)142 (−9)
rft_w1.0HARVESTED (native)1.0148 (+1)139 (−12)

Same recipe + same weight, only synthetic→native think ⇒ +35 HumanEval (113→148). Native retains; native think survives FULL w=1.0 supervision (148). The residual MBPP −9 is a thinking-LENGTH bias: base hits the 8192 think-budget on 106/200 MBPP, the RFT model on only 21; training on easy short-trace problems globally shortened thinking, costing the long-budget-dependent hard MBPP problems (fixable with harder problems).

The RFT harvesting scripts are preserved under scripts/rft/ (build_problems.py, sample.sh, filter_build.py, train/ablation drivers) for reproducibility; corpora + adapters are ephemeral large-artifacts (not committed).

Interpretation

The dual-channel WHY-think design FAILS: you cannot improve — or even retain — a near-ceiling reasoner by SUPERVISING it on <think> traces worse than its own. The base's 89.6% HumanEval IS its native reasoning; AST-templated derivations are strictly worse and regress it, more so the better the model fits them. The durable rule: good think blocks must be HARVESTED from the model (execution-verified rejection sampling / STaR), never hand-authored. Native-trace RFT is a retention-SAFE SFT substrate. Separately, the corrected thinking-on baseline (HumanEval 89.6%, near ceiling) closes the "push function-writing up with SFT" goal — the old 76% was a thinking-off measurement artifact. The real prize is the agentic gap (duet-eval 8/35 = 23%), which needs multi-step BEHAVIOR curricula + RLVR, not more function completion. Successor: point the harvest method at a synthetic execution-verified AGENTIC environment (mirror the duet raw 4-tool schema) feeding an SFT warm-start, then RLVR.

Next Experiments

  • Phase B (RLVR) from the peak rung composite as the SFT foundation — the pre-committed successor if a rung clears the bar.
  • If flat: reprice the WHY family; take RLVR from base.
  • If collapse: document the overfit boundary; the small rung is the foundation.
  • Follow-on: the agentic duet-eval confirm on the peak composite (thinking-on).

Artifact Manifest

See artifact_manifest.yaml — the rung corpora (large, deterministically regenerable, sha-pinned in data/ladder_manifest.json) and the trained adapters / merged composites live under large_artifacts/ (omitted from git); the generator, contamination fixture, ladder manifest, base provenance copy, and receipts are in-repo and reproducibility-critical.

Experiment log 2

Show the running log (2 entries, 2026-07-18)

2026-07-18 — Dual-channel design freeze (model-free, no GPU stage run)

Built the CORRECTED WHY curriculum (owner directive). The prior WHY curriculum (experiments/qwen35_4b_why_scale_ladder) put its reasoning in inline #WHY: comments and left the <think> block minimal. Qwen3.5-4B is a THINKING model whose coding performance depends on its <think> trace (the repo's most- replicated finding; the coding harness was even mistakenly measuring thinking-OFF and is being fixed to thinking-on + 8192 budget). Empty-think WHY training risks DESTROYING the model's native thinking, so this cell puts a GENUINE step-by-step derivation in the <think> channel AND keeps the inline #WHY: comments.

Constructed and verified model-free (no seed consumed by a model event; construction seed 95200, training seed 95201 reserved by design):

  • Generator scripts/gen_why_think_curriculum.py (seed 95200): copied the proven 59-family scale-capable generator and EXTENDED it with the dual-channel think derivation. Each row's think is emitted MECHANICALLY from the family AST/shape: parse the spec (goal/inputs/output) -> choose an approach FROM THE CODE SHAPE (accumulator / builder / running-extreme / branch / search / dict) phrased as a decision -> build the solution step by step in construction order -> trace a REAL worked example (one of the task's asserts, executed line by line, every value byte-true) -> conclude into the answer. The worked-example CORE is a deterministic, rng-free string recomputed byte-for-byte at verification.
  • Per-row truth audit kept AND extended: strip #WHY: -> clean code passes all asserts; commented code runs identically; marker strippable; every #WHY: line- specific/non-boilerplate; AND (new) the think has an approach-decision phrase, a worked-example trace matching real execution, and is NOT the joined #WHY: comments; safety/termination; determinism (2 builds identical).
  • Measured (5000-row sample, seed 95200): 59/59 families across 13 categories; 100% unique programs; 1196 distinct normalized #WHY: templates; 4997/5000 distinct think skeletons (the derivation genuinely varies, not one template). 10000-row: 59 families, 1197 #WHY: templates, 9985 distinct think skeletons, 100% unique.
  • Token budget: the think lengthens the render, so it is capped. Real pinned tokenizer full render (chat + think + </think> + answer) over 5000 rows: max 739 tokens, p95 619, median 467, min 330 — 0 over the 4096 cap. Conservative >=3-char/token estimate: max 695.
  • Contamination through 10000 rows: 663 banned benchmark names after whitelist, 0 whole-word hits over prompt + THINK + answer; 0 distinctive shared code 7-grams (78 structural idioms) vs benchmark solutions (HF cache present, aid RAN).
  • Ladder data/ladder_manifest.json: rungs 2000/5000/10000/20000/40000, sha- pinned (generator sha + fixture sha + per-rung corpus sha); corpora are large, deterministically regenerable, gitignored under large_artifacts/.
  • Recipe (frozen): one fresh r32/a64 QLoRA adapter per rung from the fail-closed base_reserialized composite, lr 1e-5, batch 1, grad-accum 8, max-length 4096, w_think 0.2 (>0: preserves + shapes thinking), w_close 0.2, 1 epoch every rung (owner directive), seed 95201. Vendored trainer (sha e0eca2a2...) + merger (sha cb9af8b4...) byte-identical.
  • Measurement: the SHARED coding-fitness harness (referenced, not copied), being fixed to thinking-on + 8192 budget; base is CO-MEASURED thinking-on per rung, so NO hardcoded thinking-off 76.2% anchor is carried.

GPU stages (train/merge/measure per rung) are gated behind staged adversarial reviews (reports/compute_review.md, merge_review.md, measure_review.md) that are created later; none has run. This is Phase A of scale-then-RLVR: the peak rung becomes the SFT foundation for the subsequent RLVR phase.

Scaffold

Created as a new experiment scaffold, then rebuilt as the dual-channel WHY-think scale ladder (cloned from experiments/qwen35_4b_why_scale_ladder).

Reproduce

Smoke test

python scripts/run.py --smoke

Full run

python scripts/run.py --stage train --rows N && python scripts/run.py --stage merge --rows N && python scripts/run.py --stage measure --rows N  # per rung in {2000,5000,10000,20000,40000}

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

Browse the experiment folder on GitHub ↗