Skip to content
First Flight: Train Your First LoRA
← Back to Course Lesson 5 / 8

First Flight: Train Your First LoRA

Today Ember flies. You'll pick a base model, attach LoRA adapters, and run a real fine-tune on your own machine — the Mac path with MLX, the PC path with Unsloth. By the end of this lesson you will have adapter weights on disk and a model that already sounds different from the base.

What LoRA Actually Does (60 Seconds of Theory)

Fine-tuning all weights of even a 4B model needs ~70 GB. LoRA's insight: you don't need to change the big weight matrices — you can add a small correction alongside them. For each targeted matrix, LoRA trains two thin matrices (A and B) whose product is the correction. Only those train; the base stays frozen.

Base weights W 4,000,000,000 params FROZEN ❄ + A B TRAINABLE 🔥 adapters A×B — often <1% of params = Ember base + your behavior

Concept

The knob called rank (r) is the thickness of those adapter matrices. r=8 is a light touch (tone), r=16–32 is standard (our project), r=64+ for heavier task learning. Higher rank = more capacity to learn = more memory and more risk of memorizing your data instead of learning from it.

Picking the Base Model

For Ember we want a small, modern, instruction-tuned model with good multilingual skills. In 2026 the sweet spot for this project is Qwen3-4B-Instruct (great multilingual, permissive license) — with Llama-3.2-3B-Instruct and Gemma-3-4B as solid alternatives. From lesson 3's math: QLoRA on 4B needs ~4–6 GB. Both machines fit easily — deliberately, since your first run should never also be a memory fight.

Always start from the -Instruct version, not the raw base — it already knows how to hold a conversation; you're adjusting behavior, not teaching dialogue from zero.

Path A — Mac Studio (MLX)

Install once, then train:

pip install mlx-lm
mlx_lm.lora \
  --model Qwen/Qwen3-4B-Instruct \
  --train \
  --data ./data \
  --batch-size 4 \
  --iters 600 \
  --learning-rate 1e-5 \
  --adapter-path adapters/ember-v1

--data ./data points at the folder with train.jsonl / valid.jsonl from lesson 4. MLX downloads the model on first run, then you'll see a step counter with train loss and, periodically, val loss. On the M3 Ultra, this run takes roughly 20–40 minutes.

Talk to your dragon (base + adapters, no merging needed):

mlx_lm.generate \
  --model Qwen/Qwen3-4B-Instruct \
  --adapter-path adapters/ember-v1 \
  --prompt "My order hasn't arrived and it's been 10 days."

Path B — PC with RTX 4090 (Unsloth)

Install (a CUDA-enabled PyTorch must be present), then this is the whole training script:

from unsloth import FastLanguageModel
from trl import SFTTrainer, SFTConfig
from datasets import load_dataset

model, tokenizer = FastLanguageModel.from_pretrained(
    "unsloth/Qwen3-4B-Instruct",
    max_seq_length=2048,
    load_in_4bit=True,          # QLoRA: 4-bit frozen base
)
model = FastLanguageModel.get_peft_model(model, r=16, lora_alpha=16)

dataset = load_dataset("json", data_files="data/train.jsonl", split="train")
dataset = dataset.map(lambda ex: {"text": tokenizer.apply_chat_template(
    ex["messages"], tokenize=False)})

trainer = SFTTrainer(
    model=model, tokenizer=tokenizer, train_dataset=dataset,
    args=SFTConfig(
        per_device_train_batch_size=4,
        gradient_accumulation_steps=2,
        num_train_epochs=3,
        learning_rate=2e-4,
        logging_steps=10,
        output_dir="adapters/ember-v1",
    ),
)
trainer.train()
model.save_pretrained("adapters/ember-v1")

On the 4090 this finishes in roughly 8–15 minutes. Unsloth prints loss every 10 steps — same numbers to watch, same meaning.

Pro Tip

If you hit out-of-memory, turn these three knobs in order: (1) reduce max_seq_length (2048 → 1024), (2) reduce batch size and raise gradient accumulation to compensate, (3) enable gradient checkpointing (use_gradient_checkpointing="unsloth" / MLX --grad-checkpoint). Same levers on both machines, in the same order.

Reading the Loss (Your First Flight Instruments)

Loss measures "how surprised the model is by the correct answer" — lower is better. Healthy first flight:

  • Train loss drops fast early (say 2.1 → 1.3 in the first 100 steps), then flattens gently.
  • Val loss follows it down, a little higher.
  • Warning sign: val loss starts climbing while train loss keeps falling. That's overfitting — the dragon is memorizing the food instead of learning to hunt. Lesson 6 deals with it properly.

Honest Note

Your v1 will be noticeably better at the shape of answers and only somewhat better at judgment. That's expected — you trained ~200 examples for a few hundred steps. Resist the urge to fix it by training 10× longer; v1's job is to prove the pipeline, and lesson 6's job is to make it good.

Try It

After training, run the same 5 prompts through the base model and through base + adapters, side by side. Save both outputs to a file. Seeing the voice change in a diff is the moment this stops being theory — and that file becomes your first evaluation artifact for the next lesson.

Checkpoint

You trained real LoRA adapters on your own hardware, you can chat with base+adapters, and you know the three out-of-memory levers and the overfitting warning sign. Next: grading your dragon honestly — and making v2 better than v1.