Chapter 7 of 8
Evaluating a fine-tune without fooling yourself
A fine-tune that looks great on the one prompt you kept retrying is worthless, and it is the most common way people lie to themselves in this field. The only honest question is whether the target metric improved on a held-out set the model was not tuned to impress. So you build an eval: a set of prompts you did not cherry-pick, a scoring function that measures the behavior you actually wanted, and a before-and-after comparison of the base model against the fine-tuned one. In this lab you evaluate the Academy's real LoRA fine-tune. Its goal was to steer the model toward security and verification language, so the metric is a domain score over a held-out prompt set. You score the base and the fine-tuned model on the same held-out prompts and prove the fine-tune improved the target metric across the whole set, not just on one lucky prompt.
The lab: read it, then run it
#!/usr/bin/env python3
"""
LAB TR7: Evaluating a fine-tune without fooling yourself.
A fine-tune that looks great on the one prompt you kept trying is worthless. The
only honest question is: did the target metric improve on a HELD-OUT set the model
was not tuned to impress. So you build an eval: a set of prompts you did not
cherry-pick, a scoring function that measures the behavior you actually wanted,
and a before/after comparison of the base model against the fine-tuned one.
This lab evaluates the Academy's real LoRA fine-tune. The goal of that fine-tune
was to steer the model toward SECURITY / VERIFICATION language. So the metric is a
domain score: how strongly each generation uses that vocabulary. It runs the base
and the LoRA-adapted model over a held-out prompt set, scores both, and PROVES the
fine-tune improved the target metric across the set (not just on one lucky prompt).
Fast: it loads checkpoints and generates deterministically, about a second.
Run: python3 modules/academy-content/labs/training/tr7-evaluate-finetune.py
"""
import sys, os
_labs = None
for _c in [os.path.join(os.path.dirname(__file__), "..") if "__file__" in globals() else None,
os.path.join(os.getcwd(), "..", "labs"), os.path.join(os.getcwd(), "labs")]:
if _c and os.path.isdir(os.path.join(_c, "_models")): _labs = os.path.abspath(_c); break
if _labs: sys.path.insert(0, os.path.join(_labs, "_models"))
from load import load_tinygpt, load_tinygpt_with_lora, generate
# A HELD-OUT prompt set. These are not the single prompt you tune against; a fair
# eval scores behavior across several inputs the model was not optimized to game.
EVAL_PROMPTS = [
"a neural network is",
"the best way to learn is",
"a good program is one that",
]
# The target behavior: security / verification vocabulary. The metric counts hits.
THEME = ["verify", "trust", "input", "secur", "check", "test", "attacker", "proof", "run"]
def domain_score(text):
low = text.lower()
return sum(low.count(w) for w in THEME)
def evaluate(model):
"""Run the model over the held-out set and return the total target-metric score."""
total = 0
rows = []
for p in EVAL_PROMPTS:
out = generate(model, p, n=120).replace("\n", " ")
s = domain_score(out)
total += s
rows.append((p, s, out[:60]))
return total, rows
def main():
print("STEP 1: score the BASE model on the held-out set")
base = load_tinygpt()
base_total, base_rows = evaluate(base)
for p, s, snip in base_rows:
print(" score %2d %-28r %s" % (s, p, snip))
print(" base total metric: %d" % base_total)
print("")
print("STEP 2: score the FINE-TUNED (LoRA) model on the same held-out set")
lora = load_tinygpt_with_lora()
lora_total, lora_rows = evaluate(lora)
for p, s, snip in lora_rows:
print(" score %2d %-28r %s" % (s, p, snip))
print(" fine-tuned total metric: %d" % lora_total)
improved = lora_total > base_total
meaningful = lora_total >= base_total + len(EVAL_PROMPTS) # a real lift, not noise
ok = improved and meaningful
print("")
print("STEP 3: before/after on the held-out metric")
print(" base %d -> fine-tuned %d (lift %+d)" % (base_total, lora_total, lora_total - base_total))
print(" improved across the held-out set (not one lucky prompt): %s" % ("YES" if ok else "NO"))
print("")
print("FINE-TUNE IMPROVED THE TARGET METRIC: %s" % ("YES" if ok else "NO"))
if not ok:
sys.exit(1)
if __name__ == "__main__":
main()
Runnable lab
tr7-evaluate-finetune.pyScore base vs fine-tuned on a held-out prompt set with a target metric, and prove the fine-tune improved it.
Proves: FINE-TUNE IMPROVED THE TARGET METRIC: YES
Runs in your browser via Pyodide. First run loads the runtime once; no install, no server.
On a held-out set of prompts the fine-tune was not tuned against, the base scored near zero on the target metric and the fine-tuned model scored many times higher, a real lift across every prompt rather than one lucky hit. That is what evaluation buys you: the difference between believing a fine-tune worked and knowing it. Two habits to carry. Build the eval set from cases you did not train on and did not cherry-pick, and always compare against a baseline, usually the base model or the current production model, so the number means something. This is the same discipline the whole Evaluation and Testing course is built on. Last chapter: the judgment call that comes before any of this, when to fine-tune at all.
Check your understanding
1. Why evaluate a fine-tune on a held-out set instead of a single prompt?
2. Why compare the fine-tuned model against the base model?
3. What should the eval metric measure?