AI Engineering No. 242

Fine-Tuning skill for Claude Code

Fine-tune only after the prompt baseline plateaus. Claude starts by asking whether fine-tuning is the right tool, measuring the best prompt and retrieval setup first. When tuning is warranted it builds deduplicated, consistently labeled chat datasets with leak-free splits, trains LoRA or QLoRA with TRL using conservative defaults and val-loss checkpointing, and compares the result to the prompted baseline through the real serving stack.

  • namefine-tuning
  • size180 lines · ≈2,200 tokens
  • files1
  • licenseMIT
  • updated

What Claude sees before loading it

This description is the only part of the skill that sits in context all the time. Claude reads it to decide when to load the full playbook below.

description: Decides whether to fine-tune versus improve prompts or add retrieval, then runs supervised fine-tuning and preference tuning of open-weight LLMs with LoRA and QLoRA using Hugging Face TRL and PEFT - dataset construction and cleaning, chat templates, hyperparameters, overfitting checks, and evaluation against the prompted baseline. Use when someone asks to fine-tune or train a model on their data, when building an SFT or DPO dataset, or when a LoRA run produces worse results than the base model.

Try asking

  • Should we fine-tune a model on our support tickets or just improve the prompt?
  • Train a LoRA adapter on this JSONL dataset and tell me if it beats the base model
  • Our fine-tuned model rambles past the answer and ignores the format. What went wrong?

Use it when

  • Someone proposes fine-tuning a model on company data
  • You are building an SFT or DPO dataset from logs or expert labels
  • A LoRA or QLoRA run underperforms the base model or overfits
  • You want a small self-hosted model to match a large prompted one

Skip it when

How to check the result

On a deduplicated held-out test set, the fine-tuned model beats both the base model with the best prompt and the original prompted baseline, measured through the serving stack.

The common mistake it prevents

Fine-tuning to inject document knowledge that retrieval would serve better, then discovering the model hallucinates details and goes stale with every content update.

~/.claude/skills/fine-tuning/SKILL.md

--- name: fine-tuning description: Decides whether to fine-tune versus improve prompts or add retrieval, then runs supervised fine-tuning and preference tuning of open-weight… license: MIT ---

Fine-tuning is justified when a prompted (and, if needed, retrieval-augmented) baseline has been measured and plateaued, and a tuned model beats it on a held-out eval at an acceptable cost to train and serve. A fine-tune that was never compared with a good prompt on the same eval is an expensive guess.

Should you fine-tune?

GoalBetter first choiceFine-tune when
Model lacks knowledge of your documentsRAG (rag-pipelines)Almost never; fine-tuning teaches knowledge poorly and it goes stale
Consistent format, style, or tonePrompt + examples, structured outputsPrompt is long and costly per call, or format still drifts at scale
Narrow classification / extraction at high volumePrompted small modelNeed lower latency/cost than any prompted model gives at the target accuracy
Domain-specific behavior a strong model does with a long promptPrompt cachingYou want a small self-hosted model to match it (distillation)
Teach a new skill the base model cannot do at allBigger modelRarely works with small data; expect thousands of high-quality examples
Data cannot leave your infrastructureLocal prompted model (local-llms)Prompted local model falls short of the bar

Hosted fine-tuning availability differs by provider and model and changes often; check the provider’s current documentation. This skill covers open-weight models, where you control the whole pipeline.

Workflow

- [ ] 1. Baseline: best prompt (+ RAG) on the eval set; record score and cost
- [ ] 2. Build and clean the dataset; split train/val/test
- [ ] 3. Pick base model and method (LoRA, QLoRA, full)
- [ ] 4. Train with conservative hyperparameters, watching val loss
- [ ] 5. Evaluate on the held-out test set vs. baseline and base model
- [ ] 6. Merge or serve adapters; re-run eval on the serving stack

2. Dataset

Format: chat-style JSONL, one conversation per line, matching how you will call the model.

{"messages": [{"role": "system", "content": "You classify support tickets."}, {"role": "user", "content": "My card was charged twice"}, {"role": "assistant", "content": "{\"category\": \"billing\", \"urgency\": \"high\"}"}]}
ItemGuidance
Size200-1,000 excellent examples for style/format; 1K-10K for narrow tasks; quality beats quantity
SourceReal inputs; outputs written or reviewed by experts, or generated by a stronger model and filtered
CoverageMatch the production distribution, plus edge cases and “decline” examples
CleaningDeduplicate (exact and near-duplicate), remove PII, fix inconsistent labels, drop truncated outputs
SplitsTrain/val/test 80/10/10 by source or customer, so near-duplicates do not leak into test
ConsistencySame system prompt and format as inference; label guidelines written down

Read 50 random examples before training. Every inconsistent label teaches the model to be inconsistent.

Preference data (for DPO): {"prompt": ..., "chosen": ..., "rejected": ...} with real, meaningful differences; collect from human comparisons or graded model samples.

3. Base model and method

  • Start from the instruct/chat variant of a current open model family in the 1-8B range for narrow tasks; go larger only if the eval demands it.
  • Check the license permits your use.
MethodMemoryUse when
LoRA (bf16 base)Base weights + small adaptersDefault on a GPU that fits the base model in bf16
QLoRA (4-bit base)~1/4 of bf16 for the baseLimited GPU memory; small quality cost
Full fine-tuneWeights + gradients + optimizer state (~16 bytes/param with AdamW)Large data, maximum quality, multi-GPU budget

4. Train (TRL + PEFT)

import torch
from datasets import load_dataset
from peft import LoraConfig
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
from trl import SFTConfig, SFTTrainer

model_id = "your-org/base-instruct-model"
ds = load_dataset("json", data_files={"train": "train.jsonl", "validation": "val.jsonl"})

bnb = BitsAndBytesConfig(load_in_4bit=True, bnb_4bit_quant_type="nf4",
                         bnb_4bit_compute_dtype=torch.bfloat16)          # QLoRA; omit for LoRA
model = AutoModelForCausalLM.from_pretrained(model_id, quantization_config=bnb,
                                             dtype=torch.bfloat16, device_map="auto")
tokenizer = AutoTokenizer.from_pretrained(model_id)

peft_config = LoraConfig(r=16, lora_alpha=32, lora_dropout=0.05,
                         target_modules="all-linear", task_type="CAUSAL_LM")

args = SFTConfig(
    output_dir="out/ticket-lora",
    num_train_epochs=2,
    per_device_train_batch_size=4,
    gradient_accumulation_steps=4,          # effective batch 16
    learning_rate=2e-4,                     # LoRA; use ~1e-5 to 2e-5 for full fine-tunes
    lr_scheduler_type="cosine",
    warmup_ratio=0.03,
    bf16=True,
    gradient_checkpointing=True,
    eval_strategy="steps", eval_steps=50,
    save_strategy="steps", save_steps=50,
    load_best_model_at_end=True, metric_for_best_model="eval_loss",
    logging_steps=10,
    assistant_only_loss=True,               # train on assistant turns, not prompts
    report_to="none",
)
trainer = SFTTrainer(model=model, args=args, train_dataset=ds["train"],
                     eval_dataset=ds["validation"], processing_class=tokenizer,
                     peft_config=peft_config)
trainer.train()
trainer.save_model("out/ticket-lora/final")

TRL and transformers rename arguments between releases (torch_dtype became dtype, evaluation_strategy became eval_strategy, tokenizer became processing_class). Check the installed versions (pip show trl transformers peft) and their docs when an argument is rejected. Loss masking options also differ by version; confirm only assistant tokens contribute to loss.

Hyperparameter defaults and when to move them:

KnobDefaultMove when
LoRA rank r16 (alpha = 2r)8 for tiny datasets; 32-64 for harder tasks with more data
Learning rate1e-4 to 2e-4 (LoRA)Lower if val loss spikes or outputs degrade
Epochs1-3Stop at the val-loss minimum; more epochs on small data memorizes
Max sequence lengthp99 of your examplesTruncation silently cuts assistant answers

For preference tuning after SFT, TRL’s DPOTrainer with beta around 0.1 and a lower learning rate (5e-7 to 5e-6 for full; ~5e-5 for LoRA) is the usual starting point.

5. Evaluate

  • Compare three systems on the same held-out test set: base model + best prompt, the fine-tuned model, and the original strong prompted baseline (possibly a hosted model).
  • Also run a small general-capability and safety check: fine-tuning can erase instruction following or refusals outside the task.
  • Inspect outputs for memorized training strings (exact-match search against train set).
  • Use llm-evals for graders and paired comparison.

6. Serve

  • Merge LoRA into the base for simple serving (model.merge_and_unload() on a bf16 base, not a 4-bit one), or serve adapters dynamically (vLLM and others support multiple LoRA adapters on one base).
  • Use the exact chat template from training at inference. Template mismatch is the most common cause of “worked in the notebook, garbage in production”.
  • Re-run the eval through the actual serving stack, including any quantization (GGUF, AWQ) applied for deployment.

Gotchas

  • Training loss going down proves nothing; val loss rising while train loss falls means overfitting. Keep the best checkpoint by val metric.
  • Training on prompt tokens (no loss masking) wastes capacity and can make the model echo inputs.
  • Missing EOS token at the end of assistant turns causes the tuned model to ramble past the answer.
  • Pad token set to EOS with right-padding during training but left-padding needed for batched generation; configure both explicitly.
  • Contaminated test sets (near-duplicates of training examples) inflate scores. Split by source and deduplicate across splits.
  • Fine-tuning on outputs from a hosted model may be restricted by that provider’s terms; check before distilling.

Output contract

**Decision:** fine-tune because <baseline score/cost plateau evidence>
**Data:** <n> train / <n> val / <n> test, sources, cleaning steps, label guide link
**Model/method:** <base model, license>, <LoRA r/alpha | QLoRA | full>, <lr, epochs, seq len>
**Results (test):** baseline prompt <a>, base+prompt <b>, fine-tuned <c>; general check <pass/fail>
**Serving:** <merged | adapter>, <quantization>, eval on serving stack <score>

About this skill

Written and maintained by the Claude Skills editors to the same editorial standard as every skill in the directory: a selective trigger, a real workflow, known pitfalls and a verification step. First published , last updated . MIT licensed.

esc