# Taming the Beast: Evaluate, Diagnose, Iterate

Anyone can run a training script. The difference between a trained dragon and a lucky one is **evaluation** — measuring, diagnosing, and iterating like an engineer. This is the lesson that turns your mediocre v1 into a v3 you actually trust, and it's the discipline most hobbyists skip.

## The Three Levels of Evaluation

**Level 1 — Loss curves (free, automatic).** You already have these from training. They tell you about the *process*: did the model learn, did it overfit? They tell you nothing about whether the answers are actually good.

**Level 2 — Your test set (the real exam).** Remember `test.jsonl` — the slice the model has never seen? Run every test prompt through the trained model and grade the outputs against your goal card from lesson 2. This is your ground truth.

**Level 3 — Model-as-judge (scaling up level 2).** Have a strong model (Claude, or your biggest local model) grade each answer against a rubric. Perfect for re-grading 30+ answers on every iteration without losing an evening.

<div class="concept-box">
<h4>Concept</h4>
Grade against your <strong>goal card</strong>, not against vibes. Ember's card says: structure (answer → explanation → next step), voice, zero invented facts. So the rubric is three yes/no questions per answer. Score = counts, not feelings. "It seems better" is not a measurement.
</div>

## Reading the Curves: Three Classic Shapes

<svg viewBox="0 0 720 280" width="100%" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Three loss curve shapes: healthy, overfitting, undertrained">
    <!-- panel 1: healthy -->
  <path d="M30 230 L30 60 M30 230 L220 230" stroke="#3a4356" stroke-width="1" fill="none"/>
  <path d="M35 80 C 70 140, 110 180, 215 195" stroke="#4fffb0" stroke-width="2" fill="none"/>
  <path d="M35 75 C 70 130, 110 170, 215 180" stroke="#7b8cff" stroke-width="2" fill="none" stroke-dasharray="5 4"/>
  <text x="125" y="256" text-anchor="middle" fill="#e6e9f0" font-family="-apple-system, sans-serif" font-size="13px">Healthy</text>
  <text x="125" y="272" text-anchor="middle" fill="#9aa3b5" font-family="-apple-system, sans-serif" font-size="11px">both fall, small gap</text>
  <!-- panel 2: overfitting -->
  <path d="M280 230 L280 60 M280 230 L470 230" stroke="#3a4356" stroke-width="1" fill="none"/>
  <path d="M285 80 C 320 140, 360 190, 465 205" stroke="#4fffb0" stroke-width="2" fill="none"/>
  <path d="M285 75 C 330 130, 380 150, 400 145 C 430 140, 450 110, 465 90" stroke="#7b8cff" stroke-width="2" fill="none" stroke-dasharray="5 4"/>
  <text x="375" y="256" text-anchor="middle" fill="#e6e9f0" font-family="-apple-system, sans-serif" font-size="13px">Overfitting</text>
  <text x="375" y="272" text-anchor="middle" fill="#9aa3b5" font-family="-apple-system, sans-serif" font-size="11px">val loss turns upward</text>
  <!-- panel 3: undertrained -->
  <path d="M530 230 L530 60 M530 230 L720 230" stroke="#3a4356" stroke-width="1" fill="none"/>
  <path d="M535 80 C 570 100, 620 115, 715 125" stroke="#4fffb0" stroke-width="2" fill="none"/>
  <path d="M535 75 C 570 95, 620 108, 715 115" stroke="#7b8cff" stroke-width="2" fill="none" stroke-dasharray="5 4"/>
  <text x="625" y="256" text-anchor="middle" fill="#e6e9f0" font-family="-apple-system, sans-serif" font-size="13px">Undertrained</text>
  <text x="625" y="272" text-anchor="middle" fill="#9aa3b5" font-family="-apple-system, sans-serif" font-size="11px">still falling at the end</text>
  <text x="60" y="52" fill="#9aa3b5" font-family="-apple-system, sans-serif" font-size="11px">— train &nbsp; ---- validation</text>
</svg>

**Healthy:** both curves descend, validation slightly above train, both flattening. Ship it to the test set.

**Overfitting:** validation loss bottoms out and climbs while train keeps dropping. The dragon is memorizing. Fixes, in order of power: **more/better data**, fewer epochs (stop where val loss bottomed), lower rank, higher LoRA dropout.

**Undertrained:** both still falling when training ended. Easiest fix in the book: train longer, or raise the learning rate a notch.

## The Diagnosis Table

Numbers point at the problem; reading actual outputs identifies it. After each run, read 10 test outputs and match symptoms:

| Symptom in outputs | Likely cause | Fix |
|---|---|---|
| Ignores your format sometimes | Inconsistent examples, or too few | Clean data; add format-heavy examples |
| Parrots training answers verbatim | Overfitting | Fewer epochs; more data variety |
| Great format, weak substance | Rank too low, or base too small | r=16→32; consider 8B base |
| Invents facts confidently | No refusal examples in data | Add 20–30 explicit refusal demonstrations |
| Worse than base at general chat | Overtrained / catastrophic forgetting | Fewer steps; mix in 10–20% general chat examples |
| Randomly switches language | Mixed-language data without pattern | Make language pairing explicit per example |

<div class="honest-note">
<h4>Honest Note</h4>
Catastrophic forgetting is real: hammer a model with 2,000 support tickets and it may get <em>worse</em> at everything else. LoRA's frozen base limits the damage (delete the adapters and the base is untouched), but adapters can still overpower behavior. The standard vaccine: keep a small slice of general conversation data in the mix.
</div>

## The Iteration Loop

Your workflow from here is a loop, and each pass takes an evening, not a week:

1. **Evaluate** v_N on the test set → scores on your goal-card rubric
2. **Read** the 10 worst outputs → pick the *one* biggest problem
3. **Fix the data first** (it's the cause ~80% of the time), hyperparameters second
4. **Retrain** → v_N+1, **re-evaluate same test set** → compare numbers
5. Stop when you hit your goal card's success test — Ember's was ≥25/30 structured, 0 invented facts

<div class="pro-tip">
<h4>Pro Tip</h4>
Change <strong>one thing per iteration</strong>. If you add data, lower the learning rate, and bump the rank all at once, the score moves and you learn nothing about why. Slow is smooth, smooth is fast — this loop converges in 3–4 disciplined passes.
</div>

<div class="try-it">
<h4>Try It</h4>
Run your full test set through v1 and grade it with your rubric — the honest baseline number. Then do exactly one iteration of the loop (probably: add refusal examples and re-clean formatting) and post v2's score next to v1's. That one comparison teaches you more about fine-tuning than any blog post ever will.
</div>

<div class="checkpoint">
<h4>Checkpoint</h4>
You can read the three loss-curve shapes, diagnose the six classic output symptoms, and you're running a one-change-per-pass iteration loop with real scores. Your dragon is tamed. Next: advanced training — preference tuning, bigger models, and shrinking Ember for deployment.
</div>
