Base Model vs Fine-Tuned Model: How to Compare Them
Score the base model on the held-out test set first, train, then read the two numbers side by side with the teacher’s score between them. The gap is what fine-tuning bought. Skipping the first measurement is the most common reason a training run can’t be justified afterwards.
What you need before you start
Three things, all of them settled before you train.
| Requirement | Detail |
|---|---|
| A held-out test set | The same file for both measurements, no exceptions |
| A base model score | Taken before training, on that file |
| Identical scoring | Same metric, same judge model, same few-shot count |
If you’re training from production traces the platform does the first two for you: the upload-traces pipeline evaluates the original model that generated the traces on the test set as part of processing. For a hand-curated dataset you take the base measurement yourself.
Step 1: Score the base model on the test set
For a trace-derived seed dataset, the number is already there once processing finishes:
distil seed-dataset status <seed-dataset-id>
distil seed-dataset metrics --output json <seed-dataset-id> | jq .base_model_performance
distil seed-dataset metrics reports the base model’s performance on the generated or supplied test set, under base_model_performance. Poll seed-dataset status first. The metrics field reads null until processing has succeeded.
Write the number down somewhere outside the platform. You’re going to quote it in a comparison months later, and you want the metric and judge model recorded next to it.
Step 2: Train the student on the same seed dataset
Nothing here should change the data. Generation writes the training set from that seed dataset, and training reads what generation produced.
distil training-dataset create-from-seed-dataset <seed-dataset-id>
# Output: Synthetic data generation started. Training Dataset ID: <training-dataset-id>
distil slm create-from-training-dataset <training-dataset-id>
# Output: Training started. SLM ID: <slm-id>
distil slm status <slm-id>
Resist the temptation to improve the test set between the two measurements. A test set edited after you saw the base score is no longer a fair comparator, and neither number means what you think it does. If the test set is wrong, fix it and re-take the base measurement.
Step 3: Read the two numbers with the teacher between them
Three scores, not two. The training docs frame a successful run as the student landing “reasonably close to the teacher model (typically within one standard deviation)”, and the base score tells you how far it travelled to get there.
| Pattern | Reading |
|---|---|
| Base low, student ≈ teacher | The expected result. Fine-tuning did the work |
| Base low, student well below teacher | Under-trained, or the synthetic data missed the test distribution |
| Base already ≈ teacher | The base model could do the task. Check whether you need to train at all |
| Student below base | Something is wrong. See why did my fine-tuned model get worse |
The third row is a real outcome and it’s worth taking seriously before spending a run. Is fine-tuning worth it and does base model accuracy predict fine-tuned performance both address it directly.
Step 4: Diff the predictions, not just the scores
Aggregate scores tell you whether to ship. Per-example predictions tell you what changed.
distil seed-dataset download-traces-predictions <seed-dataset-id> --file-name base.jsonl
distil slm download-predictions <slm-id> --file-name tuned.jsonl
Both files are JSON Lines. Join them on the input and split the result into four buckets: both correct, both wrong, base wrong and tuned right, base right and tuned wrong. That last bucket is worth an hour of your time. A fine-tuned model that has lost capabilities it previously had is exhibiting catastrophic forgetting, and no aggregate score will surface it.
What the published gaps look like
For calibration, these are real base-to-fine-tuned deltas from distil labs benchmarks. All were measured on held-out sets never seen during training or synthetic data generation.
From our 12-model benchmark, Qwen3-4B-Instruct-2507 against a GPT-OSS-120B teacher:
| Benchmark | Base | Fine-tuned | Teacher |
|---|---|---|---|
| TREC | 0.51 | 0.93 | 0.89 |
| Docs | 0.64 | 0.84 | 0.82 |
| Ecommerce | 0.75 | 0.90 | 0.88 |
| HotpotQA | 0.88 | 0.93 | 0.93 |
| Mental Health | 0.78 | 0.82 | 0.81 |
| Roman Empire QA | 0.65 | 0.80 | 0.75 |
| SQuAD 2.0 | 0.26 | 0.71 | 0.52 |
| Banking77 | 0.87 | 0.89 | 0.92 |
From the platform benchmark, a Llama3 3B student prompted versus trained: PII redaction 0.54 to 0.87, HotpotQA 0.80 to 0.95, pizza tool calling 0.0 to 0.70, git tool calling 0.03 to 0.95.
Two patterns are worth extracting. Tool calling shows the largest gaps because a prompted small model frequently can’t produce a schema-valid call at all, so a base score near zero is normal rather than a measurement error. And the gap shrinks where the base model was already competent: Banking77 moved two points, SQuAD 2.0 moved forty-five.
Making sure the comparison is fair
Five things that quietly invalidate a base-versus-tuned comparison:
- Different few-shot counts.
evaluation.num_few_shot_examplesdefaults to 1 andtuning.num_few_shot_examples_studentdefaults to 0. A base model prompted with examples and a student evaluated without them aren’t being asked the same question. The config reference has both. - A test set that moved. Regenerating traces between runs produces a new test set. Compare within one seed dataset.
- A different judge. Switching
llm_as_a_judge_model_namerescales everything, as LLM-as-judge evaluation explains. - A gap inside the noise. Judge scores in our published work carry standard deviations up to 0.020, and reference-free ratings around ±0.03.
- A test set too small to resolve the gap. Check it against how big should your test set be before quoting the delta.
When all five hold, the base-to-tuned delta isolates the one variable you changed, which makes it the number worth quoting. Record the metric, the judge model and the test set size alongside it.