AI EngineeringZero to ProductionHome·About·Contact
Fine-tuning · Chapter FT6

Evaluate & serve the model

A fine-tune is done when you've proven it helped — target up, general capability intact. Then serving it is just serving a model: the whole inference track applies.

⏱️ ~1.5 hours🧪 1 lab🎯 Advanced
⚙️ To run this for realThe code here is complete and correct as written. To actually execute it you'll need:
  • a GPU + pip install vllm (serving engine)
Reading and learning works without any of this — run when you're ready. IDs/ARNs in examples are placeholders; swap in your own.

Learning objectives

  • Evaluate a tuned model: did it improve the target without regressing?
  • Guard against catastrophic forgetting and overfitting.
  • Serve the tuned model (merged or adapter) via the inference stack.
  • Close the loop: eval-gated promotion, like the rest of the course.
▶ Runnable companionThe code in this lesson is also saved under code/ft6-eval-serve/ in the course, with a README. Run the scripts or copy the configs directly.

Did it actually help? advanced

A fine-tune isn't done when training finishes — it's done when you've proven it helped. Run the evals from Ch 5 on both the base and tuned model: the target metric should rise and general capability shouldn't drop. Tuning that improves one thing while quietly breaking others is the most common failure.

Base model baseline Tuned model candidate Eval both on same set target + regression Promote only if better no regression
🗺️ How to read this diagram

This flow is the quality gate a fine-tune must pass before you ship it. The rule: prove the tuned model is better and not secretly worse elsewhere. Read left to right.

  • Base model (left) is your baseline — the original, untuned model you're trying to beat.
  • Tuned model is the candidate — the one you just trained and hope is an improvement.
  • Eval both on same set is the fair test: run both models on the identical questions and measure two things — the target skill you tuned for and any regression (general ability slipping).
  • Promote only if better (right) is the gate: you ship the tuned model only if it wins on the target no regression — better at the job without getting dumber overall.

In short: Never ship a fine-tune just because training finished. Ship it only when the same-set comparison proves it beats the base and didn't break general ability.

Catastrophic forgetting is realTune too hard on a narrow task and the model forgets general ability. Always keep a general eval set alongside your task eval — if general scores drop, back off epochs, lower the learning rate, or add diverse data (FT2).

Serve it expert

Serving a tuned model is just serving a model — the whole Inference track applies. Merge the adapter (FT4) and serve on vLLM, or keep adapters separate and hot-swap. Your app code doesn't change; only the model does.

Example code for learning — review, test, and adapt it before running against real or production systems. Commands can create, change, or delete resources. See the Terms & Disclaimer.
Lab FT6.1 · Eval base vs tuned, then serve
eval_serve.py# 1) Evaluate both on the SAME set (reuse your Ch 5 harness)
def load_task_evals(): ...       # your task eval cases
def load_general_evals(): ...    # a general-capability eval set
def run_evals(model_path, eval_cases): ...   # generate + score, return a float

cases, general_cases = load_task_evals(), load_general_evals()

def score(model_path, eval_cases):
    # generate answers, compute your metric (accuracy / format-valid % / judge), average:
    return run_evals(model_path, eval_cases)

base_score       = score("mistralai/Mistral-7B-Instruct-v0.3", cases)
tuned_score      = score("out/merged-model", cases)
base_general     = score("mistralai/Mistral-7B-Instruct-v0.3", general_cases)
tuned_general    = score("out/merged-model", general_cases)
gen_regress = tuned_general < base_general          # did general ability drop?
print(f"target: {base_score:.2f} -> {tuned_score:.2f} | regressed: {gen_regress}")

# 2) Only if better AND no regression, serve it (IC6):
#    python -m vllm.entrypoints.openai.api_server --model out/merged-model
▶ How this works

This script is the fair before/after test. It scores the base model and the tuned model on the same questions — twice: once on your task, once on general ability — then decides whether tuning actually helped. The ... bodies are placeholders you fill in with your Chapter 5 eval code.

  1. The three def lines at the top are stubs: load_task_evals gives your task-specific test cases, load_general_evals gives a broad set, and run_evals generates answers and returns a single score.
  2. score(model_path, eval_cases) is a small helper that runs a model over a set of cases and returns one average number — higher is better.
  3. The four score(...) calls produce the key numbers: base_score vs tuned_score on the task, and base_general vs tuned_general on general ability — always comparing the two models on the identical cases.
  4. gen_regress = tuned_general < base_general is the safety check: it's True if the tuned model got worse at general tasks (catastrophic forgetting).
  5. The commented vllm line at the bottom is how you'd serve the model — but only after the numbers say it's better with no regression.

What the output means: The print shows something like target: 0.71 -> 0.86 | regressed: False: the task score rose from 0.71 to 0.86 and general ability did not drop — a clean win worth shipping.

Try this: Imagine the line printed regressed: True. That means your tune improved the task but broke general ability — the fix is fewer epochs, a lower learning rate, or more varied data (FT2), not shipping it.

Same discipline as everywhere elseThis is the eval-gated rollout from O3 and W9, applied to fine-tuning: a candidate ships only when evals prove it beats the incumbent without regressing.

Exercise FT6.1 — Prove your tune

Context: The whole track lands here: a tune earns production only if it wins on the task without regressing generally — and if it regressed, the fix is to diagnose and re-tune, not to ship.

Your task: Evaluate your FT3/FT5 model against the base on a task metric AND a general set, and only if it wins on the task without regressing generally, merge and serve it on vLLM.

Requirements:

  • Score base vs tuned on both a task set and a general set
  • Promote only on a task win with no general regression
  • On a pass, merge and serve on vLLM
  • On a regression, diagnose (too many epochs? too narrow data?) and re-tune
  • Treat the two-metric gate as the promotion contract

💡 Hint: This is the FT6 gates applied to your own model — a regression sends you back to the FT2/FT3 knobs, not forward to serving.

🪜 Practice ladder beginner → industry

Six graded exercises, easy to real-world. Try each before opening its solution.

Exercise 1 · Compare base vs tuned on the same setBeginner

Context: A tune is only done once you've proven it helped, and a tuned score means nothing without the base model's score on the exact same questions.

Your task: Given per-question scores for base and tuned on the same questions, compute each model's accuracy and the improvement.

Requirements:

  • Accuracy = mean of the 0/1 correctness list
  • Score base and tuned on identical questions
  • Print both accuracies and the delta as percentages
  • Reinforce that a raw tuned score is meaningless without the same-set base comparison

💡 Hint: Both lists cover the same questions, so a plain mean of each and their difference is the entire answer.

Show solution

The fair same-set comparison, in plain Python:

base  = [0,1,1,0,1,0,1,1,0,1]   # 1 = correct on that question
tuned = [1,1,1,0,1,1,1,1,0,1]

def acc(scores): return sum(scores)/len(scores)
print(f"base : {acc(base):.0%}")    # 60%
print(f"tuned: {acc(tuned):.0%}")   # 80%
print(f"delta: {acc(tuned)-acc(base):+.0%}")  # +20%

Always evaluate both models on the identical questions — a raw tuned score means nothing without the base to beat.

Exercise 2 · Detect catastrophic forgettingIntermediate

Context: Tuning too hard can lift the target while regressing general ability (catastrophic forgetting), so promotion needs a two-metric gate, not a single number.

Your task: Given target and general scores for base and tuned, gate promotion: promote only if the target rises AND general ability doesn't drop.

Requirements:

  • Compute target-improved and general-regressed booleans
  • Allow a small tolerance band on the general drop
  • Return PROMOTE, REJECT–forgetting, or REJECT–no-gain
  • Catch forgetting rather than trusting the target metric alone

💡 Hint: Two conditions must both hold; the general-drop check needs a tolerance so noise doesn't look like forgetting.

Show solution

Check both metrics, not just the one you tuned for:

def promote(base_target, tuned_target, base_general, tuned_general, tol=0.02):
    improved = tuned_target > base_target
    regressed = (base_general - tuned_general) > tol
    if improved and not regressed:
        return "PROMOTE — target up, general held"
    if improved and regressed:
        return "REJECT — target up but GENERAL REGRESSED (catastrophic forgetting)"
    return "REJECT — target did not improve"

print(promote(0.60, 0.82, 0.75, 0.74))   # PROMOTE (general within tol)
print(promote(0.60, 0.85, 0.75, 0.68))   # REJECT — forgetting

A tune that wins the target but drops general ability is the most common silent failure. Keeping a general eval set alongside the task set is what catches it.

Exercise 3 · Guard against overfitting with a held-out setAdvanced

Context: Improvement on the training distribution can just be memorization; a big train-vs-held-out gap is the overfitting signal.

Your task: Compare tuned accuracy on train-like vs held-out questions and flag overfitting when the gap is large.

Requirements:

  • Compute gap = train_acc − heldout_acc
  • Flag OVERFIT when the gap exceeds a tolerance, else OK
  • Format the gap as a percentage
  • Point to FT2 remedies (more diverse data, fewer epochs, lower LR)

💡 Hint: It's a single subtraction against a threshold — a wide gap means the model learned the train set, not the task.

Show solution

Quantify the train-vs-held-out gap:

def overfit_check(train_acc, heldout_acc, gap_tol=0.10):
    gap = train_acc - heldout_acc
    if gap > gap_tol:
        return f"OVERFIT risk — gap {gap:.0%} (memorizing; add data / fewer epochs)"
    return f"OK — gap {gap:.0%} within tolerance (generalizing)"

print(overfit_check(0.95, 0.72))   # OVERFIT risk — gap 23%
print(overfit_check(0.83, 0.80))   # OK — gap 3%

A model that aces training-like questions but stumbles on held-out ones has memorized, not learned. The fix is the FT2 levers: more diverse data, fewer epochs, lower learning rate.

Exercise 4 · Aggregate a promotion scorecardExpert

Context: The course closes every loop with a scorecard; here you combine target improvement, the general-regression guard, and the overfit gap into one pass/fail verdict.

Your task: Combine the three prior gates into a single promotion scorecard with reasons.

Requirements:

  • Check target improved, no general regression (within tolerance), not overfit (gap within tolerance)
  • Collect the failing checks
  • Return PROMOTE only if none failed, else HOLD with reasons
  • Include the per-check PASS/FAIL map

💡 Hint: Reuse the three earlier rung checks verbatim; PROMOTE is just the case where the failed-check list is empty.

Show solution

One gate that combines every check:

def scorecard(m):
    checks = {
        "target improved":   m["tuned_target"] > m["base_target"],
        "no gen regression": (m["base_general"] - m["tuned_general"]) <= 0.02,
        "not overfit":       (m["train_acc"] - m["heldout_acc"]) <= 0.10,
    }
    failed = [k for k,ok in checks.items() if not ok]
    return ("PROMOTE" if not failed else "HOLD: " + ", ".join(failed)), checks

metrics = dict(base_target=0.60, tuned_target=0.81, base_general=0.75,
               tuned_general=0.74, train_acc=0.88, heldout_acc=0.80)
verdict, checks = scorecard(metrics)
print(verdict)                    # PROMOTE
for k,v in checks.items(): print(f"  {k}: {'PASS' if v else 'FAIL'}")

Eval-gated promotion turns "training finished" into "proven better and not secretly worse". Only a candidate that passes all three checks ships.

Exercise 5 · Eval both then serve (real, needs GPU/libs)Professional

Context: Serving a tuned model is identical to serving any model — only the path changes — so the one genuinely new step is the eval gate before you serve.

Your task: Show the real close-the-loop step: eval base vs tuned on the same harness, then serve the merged model on vLLM, using only real APIs and labelling it as needing a GPU + libraries.

Requirements:

  • Define an evaluate(model_path, questions) that returns an accuracy float
  • Eval the base and the merged (FT4) model on the same question set
  • Gate with an assertion that tuned beats base
  • Serve via a vllm serve line only after the gate passes
  • Note the libs: transformers vllm

💡 Hint: Serving is unchanged from any model — the merged path goes straight to vllm serve; the assertion is the only new gate.

Show solution

The real eval-then-serve loop — needs a GPU + pip install transformers vllm:

# 1) Evaluate BOTH on the SAME set (reuse your Ch 5 harness)
def evaluate(model_path, questions):
    # ... load model, run generate(), score against references ...
    return accuracy   # float

base_acc  = evaluate("mistralai/Mistral-7B-Instruct-v0.3", QS)
tuned_acc = evaluate("out/merged-model", QS)              # merged adapter (FT4)
assert tuned_acc > base_acc, "do NOT promote a non-improvement"

# 2) Serve the tuned model — identical to serving any model (IC6)
#    vllm serve out/merged-model --port 8000
#    app code is unchanged; only the model path differs

Serving a tuned model is just serving a model — the whole Inference track applies. The only new step is the eval gate that must pass before promotion.

Exercise 6 · Own the promotion policy for a tuned-model pipelineIndustry scenario

Context: As a lead you encode the promotion policy once as thresholds, and CI decides whether each nightly tune auto-promotes to staging — policy as code.

Your task: Define the CI gate that decides whether a nightly fine-tune auto-promotes: encode thresholds, a regression guard, and a rollback trigger, then print the decision for a candidate run.

Requirements:

  • Encode thresholds (min target gain, max general drop, max overfit gap)
  • Compute each metric for the candidate run
  • Append a human-readable reason per threshold breach
  • Return AUTO-PROMOTE when clean, else BLOCK + alert with reasons
  • Make the thresholds a single policy object

💡 Hint: It's the scorecard from rung 4 with the tolerances pulled out into a policy dict — CI reads the reasons list to decide promote vs block.

Show solution

The promotion policy a CI job would enforce:

POLICY = {"min_target_gain": 0.03, "max_gen_drop": 0.02, "max_overfit_gap": 0.10}

def ci_gate(run, policy=POLICY):
    gain = run["tuned_target"] - run["base_target"]
    gen_drop = run["base_general"] - run["tuned_general"]
    gap = run["train_acc"] - run["heldout_acc"]
    reasons = []
    if gain < policy["min_target_gain"]: reasons.append(f"gain {gain:+.0%} too small")
    if gen_drop > policy["max_gen_drop"]: reasons.append(f"general dropped {gen_drop:.0%}")
    if gap > policy["max_overfit_gap"]: reasons.append(f"overfit gap {gap:.0%}")
    return ("AUTO-PROMOTE to staging" if not reasons
            else "BLOCK + alert: " + "; ".join(reasons))

run = dict(base_target=0.60, tuned_target=0.66, base_general=0.75,
           tuned_general=0.745, train_acc=0.84, heldout_acc=0.78)
print(ci_gate(run))   # AUTO-PROMOTE to staging

A lead owns the thresholds, not the individual runs: encode the minimum gain, the regression guard, and the overfit ceiling once, and let CI promote or block every nightly tune automatically.

✓ Checkpoint — you can move on when you can…

  • Evaluate tuned vs base on the same set.
  • Detect and prevent catastrophic forgetting.
  • Serve the tuned model via the inference stack.
  • Apply eval-gated promotion to a fine-tune.

Knowledge check check yourself

✓ Knowledge check

Why must you evaluate a fine-tuned model on both a task set and a general-capability set before shipping it?

Show answer
The most common fine-tuning failure is improving the target while quietly degrading general ability (catastrophic forgetting). Evaluating base vs tuned on the identical task set proves the target rose, and the general set catches regression; you promote only if the target improved with no general regression.
✓ Knowledge check

If your tuned model shows regressed: True (general ability dropped), what are the fixes — and what should you NOT do?

Show answer
Back off: use fewer epochs, a lower learning rate, or add more diverse/on-distribution data (FT2), then re-tune and re-evaluate. Do not ship it — a tune that improves the task but breaks general capability fails the eval-gated promotion rule.
© 2026 studybydoing.in · AI Engineering: Zero to Production · All rights reserved. · About · Privacy Policy · Terms · Contact
Educational content, provided as-is and without warranty. Code samples are examples — review, test, and adapt them before using in production. See the Terms of Use & Disclaimer. Use at your own risk.
© studybydoing.in