NiLabs
Project · In progress

ladder

Small open models fine-tuned for competitive programming, trained entirely on free GPUs. A generated program is run against the problem’s actual test cases, and it either passes them or it does not.


Status

The pipeline is complete and tested end to end. No trained checkpoint is published yet, and there are no benchmark numbers to quote. The results table in the repository is empty on purpose; it gets filled in from a real run rather than an estimate. An early evaluation was inconclusive because the generation budget was set far below what the model needed, and it is being redone.

One constraint drives everything

Everything has to run on a free Kaggle or Colab T4. That rules out full fine-tuning, bf16 and flash-attention, and it decides most of the rest of the design. The interesting question is not what you can build with a cluster; it is what is still reachable without one.

  • Data — Codeforces problems with reasoning traces, decontaminated against common benchmarks. Each row carries public, private and generated test cases
  • Training — 4-bit QLoRA on Qwen2.5-Coder-3B-Instruct, loss on the assistant turn only
  • Eval — generate a program, run it against up to 20 test cases in a subprocess with time and memory limits, report pass@k

The training data is not all correct

The reasoning traces in the corpus are model output, not verified solutions. Running each trace’s own solution against its own problem’s test cases:

SampleTraces whose solution passes
Easy problems (div2 A/B)16 / 16
Mixed sample with div1 D/E/G/H36 / 50

Every failure was a wrong answer on a hard problem, not a crash or a timeout. These are confident, well-structured, incorrect solutions, concentrated on exactly the problems worth learning from.

So data prep runs the eval’s judge across the training set and drops the traces that fail. It costs about eight minutes of CPU and no GPU time at all, because data prep happens before you ever start a GPU session.

Why not a model judge

Because a model judge measures whether the output looks like a solution, and that is not the question. Competitive programming is one of the few domains where correctness is not a matter of opinion: there are test cases, and the program either passes them or it does not.

Getting that judge right turned out to be harder than expected. Three separate bugs in it passed their unit tests while being wrong, including a sandboxing flag that removed Python’s exit() builtin, and problems with multiple valid outputs that cannot be exact-matched at all. A judge that is quietly broken is worse than no judge, because it produces confident wrong numbers.

Try it

Data prep is CPU-only, so do that part anywhere.

pip install -e .
ladder build-data --config configs/smoke-1.5b.yaml --out data/smoke

Then on a GPU box:

pip install -r requirements-train.txt
ladder train --config configs/smoke-1.5b.yaml --data data/smoke
ladder eval  --config configs/smoke-1.5b.yaml --adapter outputs/smoke-1.5b

The smoke config is a 20-step run on a 1.5B base. It exists to prove the whole pipeline works end to end in about fifteen minutes, before you spend free GPU hours on the real thing.