Case study · post-training
Post-Training Lab
SFT vs DPO vs GRPO for code repair on Qwen2.5-Coder-1.5B, measured under the OctoPack paper's own frozen HumanEvalFix protocol, with no benchmark-derived training data. The data won, the RL didn't, and a random-reward control is how I know.
The target, fixed before training
The untrained 1.5B model scored 17.6% pass@1 on HumanEvalFix under the paper's protocol. I locked the target before any training: beat OctoCoder, which reports 30.4%. OctoCoder is StarCoder instruction-tuned, a 15.5B-parameter model. The claim standard was strict, too: the mean and every seed above the line.
- Frozen rulerThe paper's HumanEvalFix protocol, locked and committed before training
- Data factoryAn LLM breaks clean functions; each bug must compile and fail a real test
- Three armsSFT, DPO and GRPO at matched compute, with paired seeds
- ControlsA random-reward GRPO run, and three seeds for the headline
- Scaffold testBest-of-n with test execution and self-repair, on both base and trained models
The result
Bars run from 0 to 50%. Every value links to the line it comes from.
What drove it: data, not RL
The winning change was data. For $0.34 in API calls, an LLM broke clean functions into 2,551 bugs written in the benchmark's docstring style, covering all 374 source functions. Every bug had to compile and fail a real test before it counted.
The RL arms tied or nudged. To check the nudge, I ran GRPO with a random reward. It reproduced the gain: real minus random came to +0.61 pass@1, which is zero within noise. So I credit the data, not the RL signal, at this scale.
Fine-tune or scaffold? The retry ladder
Next I wrapped both the base and the trained model in the same verifier-guided scaffold: sample several fixes, run each problem's tests in a sandbox, repair on failure. Training's value shrinks as the scaffold gets more tries:
Limits
- One benchmark (164 problems), one model family for the headline.
- The comparison sets a 2023 model against a 2024 base model; newer bases start stronger.
- The target is OctoCoder's published number, not a re-run of OctoCoder under my harness.