LLM Fine-Tuning Lesson 5 of 6 ⏱️ 4 min read

Evaluating Fine-Tuned Models

Measure whether fine-tuning helped: held-out test sets, task metrics, LLM-as-judge, regression checks and catastrophic forgetting.

Updated Oct 30, 2026

Did fine-tuning actually help?

A lower training loss doesn't mean a better model. A fine-tuned model can sound more confident while being less accurate, follow the format but lose general ability, or simply memorise the training set. Evaluation is how you prove the fine-tune is worth deploying.

Always compare against a baseline

Evaluate at least two systems on the same test set:

  1. Baseline — the base model with your best prompt (and RAG if you use it).
  2. Fine-tuned model — with the production system prompt.

If the fine-tuned model isn't clearly better on the metrics you care about, don't ship it.

The test set

  • Use the held-out test split the model never saw during training or tuning.
  • Include easy, hard and edge cases, plus inputs the model should refuse or ask about.
  • Make it representative of real traffic — if 30% of real questions are about fees, roughly 30% of the test set should be too.
  • 100–300 well-chosen examples is enough to start.

Choose metrics that match the task

Task type Good metrics
Classification / routing Accuracy, precision, recall, F1, confusion matrix
Extraction to JSON Valid-JSON rate, field-level exact match
Fixed-format output Format compliance rate (checked in code)
Open-ended answers LLM-as-judge with a rubric, human ratings
Style / tone Pairwise preference (which answer is better?)

Automatic checks in code are the most reliable — use them wherever the output has a checkable structure.

Example: scoring a classification fine-tune

Python
from collections import Counter

def evaluate(predict, test_rows):
    """predict: function(text) -> label; test_rows: list of {"text": ..., "label": ...}"""
    correct, confusion = 0, Counter()
    for row in test_rows:
        pred = predict(row["text"]).strip().lower()
        gold = row["label"].lower()
        correct += pred == gold
        confusion[(gold, pred)] += 1
    print(f"accuracy: {correct / len(test_rows):.1%}")
    for (gold, pred), n in confusion.most_common():
        if gold != pred:
            print(f"  {gold!r} predicted as {pred!r}: {n}")

test_rows = [
    {"text": "How much is the semester fee?", "label": "fees"},
    {"text": "My portal password isn't working", "label": "technical"},
    {"text": "Can I change my programme after admission?", "label": "admissions"},
]

def baseline_predict(text):          # replace with a call to your model
    return "fees" if "fee" in text else "admissions"

evaluate(baseline_predict, test_rows)

Output for this toy baseline:

Plaintext
accuracy: 66.7%
  'technical' predicted as 'admissions': 1

Run the same function for the base model and the fine-tuned model, and compare. The confusion list tells you exactly which categories need more training examples.

LLM-as-judge for open-ended outputs

For answers without one correct string, use a strong LLM as a judge with a clear rubric:

Text
You are grading an admissions assistant's answer.

Question: {question}
Reference facts: {reference}
Answer to grade: {answer}

Score each from 1 to 5:
- Correctness: matches the reference facts, no invented details
- Completeness: answers everything that was asked
- Format: 2-3 sentences, polite tone

Return JSON: {"correctness": n, "completeness": n, "format": n, "reason": "..."}

Good practice: hide which model produced which answer, randomise the order in pairwise comparisons, and spot-check the judge against human ratings on a sample.

Check for regressions and forgetting

Fine-tuning can damage abilities you didn't train on — catastrophic forgetting. Keep a small regression set of general tasks (simple maths, summarisation, instruction following, refusing harmful requests) and confirm scores didn't drop.

Watch the training curves

  • Training loss falling, validation loss falling → still learning.
  • Training loss falling, validation loss rising → overfitting; use an earlier checkpoint or fewer epochs.
  • Both flat → learning rate too low, or the data isn't teaching anything new.

Before you ship: a checklist

  • Beats the baseline on the main metric by a meaningful margin.
  • No significant drop on the regression set.
  • Format compliance is near 100% where the format matters.
  • Safety behaviour (refusals) is unchanged or better.
  • Latency and cost are acceptable.
  • Results are saved with the dataset version and training settings, so they're reproducible.

Common mistakes

  • Evaluating on training data. It measures memorisation, not skill.
  • Judging by a handful of examples. Ten nice-looking answers prove nothing.
  • Using only BLEU/ROUGE for chat answers. Word overlap metrics miss correctness and helpfulness.
  • No baseline. Without it you can't tell whether the fine-tune helped.

Key takeaways

  • Compare fine-tuned and baseline models on the same held-out test set.
  • Prefer automatic, task-specific checks; use LLM-as-judge with a rubric for open-ended answers.
  • Test for forgetting and safety regressions, not just the target task.
  • Ship only when the improvement is clear, measured and reproducible.
Finished this lesson?
Tick it off to track your LLM Fine-Tuning progress.
Next lesson