FromZero2AI
CoursesPlaygroundBlog
Support on Ko-fi
LLM Fine-Tuning & Adaptation • Module B: Training in PracticeLesson 5: Training Your First Fine-Tuned Model
PreviousNext

Lesson 5: Training Your First Fine-Tuned Model

Use the Hugging Face Trainer with LoRA adapters to fine-tune a Mistral 7B model on a custom dataset. Set learning rate, batch size, and warmup steps. Monitor loss curves.

Built with AI for beginners. Free forever.

Support on Ko-fi•About•Blog•Privacy Policy•Terms of Service

This project guides you through training a LoRA fine-tuned model from start to finish: dataset creation, QLoRA training on a cloud GPU, wandb loss monitoring, and checkpoint evaluation.

What You Are Building

A fine-tuned Llama 3.2 3B model specialized for structured data extraction: given a product description, output a clean JSON with name, price, and features. You will train on 500 synthetic examples and evaluate against a 50-example test set.

Setup: Cloud GPU Environment

# Recommended GPU options (in cost order, cheapest first):
# - Google Colab Pro (T4/A100, $10/mo) — easiest for first run
# - Lambda Labs (H100, $2.49/hr) — good performance/cost
# - RunPod (A100 80GB, $1.89/hr) — cheapest A100
# - Modal (A100, ~$2/hr, serverless) — no setup

# Install on your cloud GPU instance:
pip install transformers peft datasets trl bitsandbytes accelerate
pip install wandb              # Loss tracking
pip install huggingface_hub    # Push model to Hub
huggingface-cli login          # Authenticate with your HF token
wandb login                    # Authenticate with wandb

Phase 1: Generate Training Data

data_gen.py

Generate 500 (product_description, json_output) pairs using claude-haiku-4-5-20251001

Use the synthetic data generation pattern from Lesson 4. Save to data/train.json

Generate 50 held-out test examples

Keep test set completely separate — save to data/test.json. Never use for training.

Run deduplication and quality filters

MinHash dedup + length filter. Log how many examples were removed.

Sample 10 training examples and verify them manually

Spot-check: are the outputs valid JSON? Are they accurate?

Phase 2: QLoRA Training

train.py
import wandb
wandb.init(project="product-extraction-ft", name="llama-3b-qlora-v1")

# Use the QLoRA script from Lesson 3 with these settings:
lora_config = LoraConfig(r=16, lora_alpha=32, target_modules=["q_proj","v_proj"])

training_args = TrainingArguments(
    output_dir="./checkpoints",
    num_train_epochs=3,
    per_device_train_batch_size=4,
    gradient_accumulation_steps=4,    # Effective batch = 16
    learning_rate=2e-4,
    warmup_ratio=0.05,
    save_strategy="steps",
    save_steps=50,
    evaluation_strategy="steps",
    eval_steps=50,
    load_best_model_at_end=True,
    metric_for_best_model="eval_loss",
    report_to="wandb",                # ← Log to wandb automatically
)

# Expected training time: ~15–25 min on A100, ~45 min on T4

Run training and watch wandb loss curves

Train loss should decrease steadily. Eval loss should track train loss. Divergence = overfitting.

Identify the best checkpoint (lowest eval_loss)

load_best_model_at_end=True handles this automatically

Merge LoRA adapters into base model

merged = peft_model.merge_and_unload() — required for efficient inference

Phase 3: Evaluation

eval.py
import json
from transformers import pipeline

# Load your fine-tuned model
pipe = pipeline("text-generation", model="./merged-model", device_map="auto")

# Load test set
test_cases = json.loads(open("data/test.json").read())

correct, total = 0, 0
json_parse_errors = 0

for case in test_cases:
    prompt = f"### Instruction:\n{case['instruction']}\n\n### Input:\n{case['input']}\n\n### Response:\n"
    output = pipe(prompt, max_new_tokens=200, do_sample=False)[0]["generated_text"]
    response = output[len(prompt):]  # Strip prompt from output

    try:
        predicted = json.loads(response.strip())
        expected = json.loads(case["output"])

        # Check if required fields match
        if predicted.get("name") == expected.get("name") and predicted.get("price") == expected.get("price"):
            correct += 1
        total += 1
    except json.JSONDecodeError:
        json_parse_errors += 1
        total += 1

print(f"Field accuracy: {correct}/{total} = {correct/total:.1%}")
print(f"JSON parse errors: {json_parse_errors}/{total} = {json_parse_errors/total:.1%}")

# Target: >90% field accuracy, <5% parse errors

Phase 4: Push to Hugging Face Hub

Push merged model to your HF Hub repo

merged.push_to_hub("your-username/llama-3b-product-extractor")

Write a model card (README.md) on the Hub

Document: base model, training data size, eval metrics, intended use, limitations

Test the model via HF Inference API

Hit the endpoint with 5 real product descriptions not in your training set

Stretch Goals

  • Hyperparameter sweep: Train with r=8 and r=32, compare eval accuracy
  • Data scaling: Train with 100, 250, 500 examples — plot accuracy vs dataset size
  • Base model comparison: Fine-tune Mistral 7B on the same data, compare results

In the next lesson, we evaluate what the fine-tuned model can and cannot do — and learn to read training curves to diagnose overfitting and underfitting.