Skip to main content
You have rows worth training on and a locked test set. Three steps are left. Write the rows to a file a trainer reads. Train, on your own GPU, on Modal, or on Prime Intellect, with your own keys. Then prove it: run the old model and the new one on the same held-out tasks, and check that the difference is bigger than the noise. If it is not, nothing happened, and saying so is the result.

The mechanism

export writes one JSON object per line, in the shape trainers read, and refuses a broken row. The training is one command on your Modal account: the SFT recipe trains a small open model with a LoRA adapter on that file, on one A10G, in about ten minutes, for under twenty cents. It also runs the base model over the held-out tasks three times before it trains anything, because a score bounces from run to run and you need to know by how much before you can call a change real. That spread is the noise floor. The proof is a paired comparison. Every held-out task is run by both models, the difference is taken task by task, and the interval on the average difference has to exclude zero and clear the noise floor. Paired, because the same hard task is hard for both models, and pairing cancels that out. The library also refuses to compare two runs that were not on the same tasks.

Run it

Lessons 2 to 6 used the stand-in agent, because the ideas run offline. This lesson trains a real model, so it is the one lesson that needs an account: modal token new on your laptop. The judge is lesson 3’s, unchanged, and that is the point: it reads messages, so the same program scores the stand-in’s rows here and the trained model’s rows in step 3. Step 1 writes more rows than the earlier lessons, because sixty-four rows train nothing: four wordings of every ask, split by task the way lesson 5 said to, the passes kept the way lesson 6 said to.
Forty-six rows: the stand-in’s passes on the training tasks, each one a system prompt, the ask, the lookup, what came back, and a reply that led with it. holdout.jsonl carries the forty locked tasks with the answer the fake world gave each lookup, so the trained model is asked the same question in the same world. Step 2 is the training. One command, on your account, from the directory the two files are in. It loads Qwen/Qwen2.5-1.5B-Instruct, runs the base model over the held-out asks three times, trains a LoRA adapter for forty steps, runs the adapter once on the same asks and the same seed, and writes every sampled row back to your laptop as holdout_rows.jsonl. This is what it printed on the run the rest of this page reads from.
Step 3 is the proof, on your laptop, from that file. The judge scores a real model’s rows the same way it scored the stand-in’s. The three base passes give the noise floor, and compare takes it directly.
Read the last line. Forty tasks, paired. The base model did the job on 25% of tries; the trained one on 73%. The difference is 48 points, and the interval on it runs from 34 to 62. It does not touch zero, and it clears the one point that re-running the eval moves the number on its own (three base passes landed within half a point of each other), so the verdict is PASS. Had it read +0.05 [-0.03..+0.13], the verdict would be no difference, and the honest sentence is “training did not move it”. Two more things the line says. pass^4 went from 7% to 41%: the share of asks the model gets right every time, which is what a customer sees, went up six times over and is still under half. And forty tasks is fewer than the 89 that lesson 4’s holdout_size asked for; the interval is as wide as that implies, and the gain cleared it because it is large, not because the set is.
One PASS is one PASS. Three passes of the base model measured how much the eval moves; the training ran once. Run the training again under a different --run-name before you tell anyone. A result you can repeat is the only kind this library is built to produce.

Then it starts again

The model you served is now the agent. Its traffic is the next set of rows. Lesson 2 to lesson 7, again, on what it still gets wrong. That is the loop, and the reason the agent gets better while it works.

Where to go now

  • Quickstart: the same program, with your own agent and your own judge.
  • Your model and your key: name the model as a string, keep the key in the provider’s own variable.
  • Evals: the measurement half on its own, for a team that is not ready to train.
  • Train on your own GPU: the SFT recipe this lesson ran, and GRPO and DPO on the same base, with the paired delta at the end.
  • Papers, reproduced: one recent post-training paper per recipe, each with its verdict.

Where it comes from

  1. Miller, E. Adding Error Bars to Evals. arXiv:2411.00640, 2024. The paired difference, and why it is the right test for before and after.
  2. Hu, E. J. et al. LoRA: Low-Rank Adaptation of Large Language Models. arXiv:2106.09685, 2021. The adapter the recipe trains.
  3. Lambert, N. Reinforcement Learning from Human Feedback. arXiv:2504.12501, 2025. Chapters Instruction Finetuning and Evaluation: SFT, and the noise floor from re-running an eval.
  4. Shao, Z. et al. DeepSeekMath. arXiv:2402.03300, 2024. The GRPO recipe next to this one.

Next

A teacher can score every word: training with no reward at all, when that beats a reward, and when it cannot.
Last modified on September 21, 2026