Fine-Tuning Hyperparameters & Tactics
Introduction
You now have the machinery: L193 (LoRA & QLoRA Explained) gave you the adapter, L191 (Numerical Precision & Quantization) the 4-bit base, L190 (The Memory Bottleneck & Memory Math) the budget. This lesson closes the section by turning all of it into a runnable recipe — and, more importantly, teaching the craft that separates a fine-tune that works from one that quietly fails: reading a training run.
Here's the honest truth most tutorials skip: the hyperparameters themselves are nearly a solved problem — there's a strong default recipe you can almost always start from. The skill isn't guessing the numbers; it's watching two loss curves and knowing which knob to turn when they misbehave. By the end you'll read learning_rate, num_train_epochs, and lora_alpha not as mystery dials but as levers with predictable failure modes — and you'll have felt all four in the lab.

The Whole Job: Reading Two Curves
Strip away everything else and fine-tuning monitoring is two numbers over time:
- Training loss — the L189 (How Training Actually Works) loss, measured on the data the model is learning from. It should fall steadily.
- Validation loss — the same loss measured on a held-out split the model never trains on. This is the one that tells the truth about whether the model is actually getting better vs. just memorizing.
A held-out validation set is non-negotiable — without it you are flying blind. Four shapes tell you everything you need to know:
| What you see | Diagnosis | The move |
|---|---|---|
| Both curves ↓ together, small gap | ✅ Converging | Healthy — stop near the flattening |
| Train ↓, val ↑ (turns up) | ❌ Overfitting | Early-stop, fewer epochs, more data, dropout |
| Both stuck high | ⚠️ Underfitting | Raise LR, add epochs, raise rank |
| Loss spikes / NaN | ❌ Diverging | LR too high — lower it, clip gradients |
The gap between val and train loss is the overfitting signal. Every knob below is just a way to move these two curves — and the lab at the end lets you bend them with your own hands.
The #1 Knob: Learning Rate
If you tune one thing, tune this. The learning rate is the step size for the gradient update from L189 (How Training Actually Works) — and it's both the most powerful and the most dangerous knob.
- Too high → the loss spikes, oscillates, or NaNs. Training diverges; the weights overshoot the minimum every step. (Also a frequent cause of fast overfitting on short runs.)
- Too low → the loss crawls. You waste your epochs barely learning — the classic underfit.
- Just right → a smooth, steady descent.
The key fact for our setting: LoRA tolerates a much higher LR than full fine-tuning. Because you're only training a tiny low-rank adapter (L193), a bigger step is safe — and necessary to learn in a few epochs. Typical values:
- LoRA / QLoRA: ~2e-4 (often 1e-4 to 3e-4).
- Full fine-tuning: ~1e-5 to 5e-5 — ~10× lower, because every weight is moving.
Pair it with a schedule (next section), and when in doubt, lower it — a slightly-too-low LR wastes time; a too-high one wastes the whole run.
Epochs, Dataset Size & Overfitting
An epoch is one full pass over your training data. The instinct "more training = better" is exactly wrong here, and it's the single most common way fine-tunes fail.
- Epochs: 1–3 for instruction tuning. Past that, on a typical dataset, the model stops learning the task and starts memorizing the examples — train loss keeps dropping while val loss climbs. That's the overfitting curve from the table.
- Dataset size is the other half of the equation. A small dataset (a few hundred to a couple thousand rows) overfits fast — sometimes within the first epoch. A larger, more varied set (L195 (Data Quality, Coverage & Quantity) covers this) tolerates more epochs before it turns.
- Rank interacts too: a higher LoRA r (more capacity, L193) can fit — and overfit — faster.
This is why you watch the validation curve instead of picking a fixed number of epochs. The right stopping point is where val loss bottoms out, not a round number you chose in advance. In the lab, shrink the dataset and crank the epochs — you'll watch the val curve turn up and find that bottom yourself.
The Supporting Knobs: Batch, Accumulation, Schedule, Regularization
Beyond LR and epochs, a handful of settings shape how the descent happens:
- Batch size — how many examples per gradient step. Bigger = smoother, more stable gradients, but more VRAM (the L190 budget). On a small GPU you often can't fit the batch you want — which is where the next knob saves you.
- Gradient accumulation — the practical trick: run several small micro-batches, sum their gradients, and only then take one optimizer step.
batch=4 × accumulation=4gives an effective batch of 16 with the memory of 4. This is how you simulate a big batch on a small card. - Warmup + LR schedule — start the LR near zero and ramp up over the first ~3% of steps (avoids a destructive jolt while the optimizer settles), then decay it — a cosine schedule is the common default — so the model takes fine steps as it converges.
- Regularization —
weight_decay(~0.01) andlora_dropout(~0.05) gently fight overfitting; raise them (e.g. dropout 0.1) if the val curve turns up early.
lora_alpha (α ≈ 2r) from L193 rounds out the set. None of these are as decisive as LR or epochs — but they're the difference between a fine run and a clean one.
The Recipe: A Strong Default to Start From
Here's the honest shortcut: for instruction SFT on ~5–50k examples of a Llama-3.x or Qwen2.5 model, start from this exact config and only change what the curves tell you to. These numbers are the well-worn community default in 2026 — they'll get you a good run on the first try far more often than hand-picked values.
from trl import SFTConfig, SFTTrainer
from peft import LoraConfig
# A STRONG default recipe. Start here; let the loss curves tell you what to change.
peft_config = LoraConfig(
r=16, # adapter capacity (4-64); 16 is the sweet spot (L193)
lora_alpha=32, # update scaling, ~2*r
lora_dropout=0.05, # light regularization
target_modules='all-linear', # attn + MLP > attention-only
task_type='CAUSAL_LM',
)
args = SFTConfig(
learning_rate=2e-4, # LoRA likes ~10x higher LR than full FT (1e-5..5e-5)
lr_scheduler_type='cosine', # ramp up, then decay
warmup_ratio=0.03, # ease in over the first ~3% of steps
num_train_epochs=3, # 1-3; more overfits fast on small data
per_device_train_batch_size=4,
gradient_accumulation_steps=4, # -> EFFECTIVE batch 16 on a small GPU
bf16=True,
max_grad_norm=1.0, # gradient clipping - tames loss spikes
eval_strategy='steps', eval_steps=50, # WATCH the validation loss
logging_steps=10,
)Tactics: Early Stopping & Reading It Live
The recipe gets you a good start; the tactics keep the run from going off the rails. The most important one is early stopping — let the trainer halt automatically when validation loss stops improving, and keep the checkpoint at the val-loss minimum, not the last (often overfit) step. That one setting saves more fine-tunes than any clever hyperparameter.
from transformers import EarlyStoppingCallback
# Keep the BEST checkpoint (val-loss minimum), not the last step.
args.load_best_model_at_end = True
args.metric_for_best_model = 'eval_loss'
args.greater_is_better = False
trainer = SFTTrainer(
model=base, args=args, peft_config=peft_config,
train_dataset=train_ds, eval_dataset=val_ds, # a held-out split is non-negotiable
callbacks=[EarlyStoppingCallback(early_stopping_patience=3)], # stop if val stalls
)
trainer.train()
# Reading the two curves WHILE it runs - the whole skill in 4 lines:
# train DOWN, val DOWN (together) -> healthy, keep going
# train DOWN, val UP -> OVERFITTING (early-stop / fewer epochs / more data)
# train FLAT, val FLAT (both high) -> UNDERFITTING (raise LR, add epochs, raise r)
# loss SPIKES / NaN -> LR TOO HIGH (lower LR; max_grad_norm clips it)Other tactics worth knowing: gradient clipping (max_grad_norm=1.0, already in the recipe) caps the gradient size to tame spikes; gradient accumulation (also in the recipe) buys you a bigger effective batch; and don't trust loss alone at the end — run the actual task eval (the Container-4 evals skillset) on the best checkpoint, because a lower val loss doesn't always mean a better model for your task.
See It: The Hyperparameter Tuning Lab
Now bend the curves yourself. Start from the recipe (the ↺ Load the recipe button) and watch a clean convergence. Then break it on purpose: crank the learning rate until the loss explodes (diverging); drop it until the curves crawl (underfitting); shrink the dataset and add epochs until the validation curve turns up and the early-stop marker appears (overfitting). The verdict names what you're seeing and the fix — train your eye until you can diagnose a run at a glance.

Notice the pattern: there's a basin of good settings around the recipe, and you fall out of it in recognizable ways. You're not memorizing numbers — you're learning to read the descent.
Why This Matters
Hyperparameter intuition is what turns fine-tuning from a dice roll into an engineering discipline. The engineer who can glance at a loss curve and say "that's overfitting — early-stop and add data" ships a working adapter in one afternoon; the one who can't burns a week and a GPU budget on runs that memorize noise. And because the default recipe is so good, the leverage is almost entirely in the diagnosis — a skill you can only build by watching curves move, which is exactly what the lab is for. This closes Section 2: Fine-Tuning Mechanics — you can now go from "should I fine-tune?" all the way to a tuned, monitored, early-stopped run. Next comes the thing that matters even more than any hyperparameter: the data.
🧪 Try It Yourself
Diagnose three runs. Open the Tuning Lab and reproduce each failure mode, then write the one-line fix for each:
- Make it diverge. Push the learning rate up until the loss explodes. What's the fix, and what does
max_grad_normdo here? - Make it overfit. Set the dataset small (~500) and epochs high (8–10). Where does the early-stop marker land, and name three different ways to fix overfitting.
- Make it underfit. Drop the learning rate to ~1e-5. Why do both curves stay high, and which two knobs pull it back?
Then hit ↺ Load the recipe and confirm it converges cleanly. Bonus: from the recipe, batch=4 × grad_accum=4 — what's the effective batch, and why would you raise grad_accum instead of batch on a small GPU?
Mental-Model Corrections
- "More epochs = a better model." Past 1–3, you usually get a worse one — train loss falls while val loss climbs. Watch the val curve, not the epoch counter.
- "Use the same learning rate as full fine-tuning." No — LoRA wants ~10× higher (~2e-4 vs ~2e-5), because only a tiny adapter is moving. Copy a full-FT LR into a LoRA run and it barely learns.
- "Lowest training loss = best model." A near-zero training loss with a rising validation loss is the signature of overfitting — the model memorized your data. The val curve is the honest one.
- "Bigger batch size is always better, and I can't afford it." Bigger is smoother but you don't need the VRAM for it — gradient accumulation gives you the effective batch cheaply.
- "I'll find magic hyperparameters." The recipe is already strong. The real skill is diagnosis — reading the curves and turning the one knob that's wrong.
Key Takeaways
- The job is reading two curves: train loss (data it learns from) and validation loss (held-out). Their gap is the overfitting signal.
- Four shapes, four moves: both ↓ = converging (stop near flat); train ↓ / val ↑ = overfitting (early-stop, fewer epochs, more data); both high = underfitting (raise LR, add epochs); spikes/NaN = LR too high.
- LR is the #1 knob — LoRA wants ~2e-4, ~10× higher than full FT's ~2e-5; when unsure, lower it.
- Epochs 1–3; small datasets overfit fast — let the val minimum pick the stopping point, not a round number.
- Start from the recipe —
r=16, α=32, lr=2e-4, cosine+warmup, 3 epochs, effective batch 16 via grad accumulation, all-linear— and add early stopping to keep the best checkpoint.
That closes Section 2: Fine-Tuning Mechanics. Next, Section 3 — L195 (Data Quality, Coverage & Quantity): the dataset that decides whether any of these knobs matter.