Tidqom — Source-linked AI and developer tools
AITid
AI

Fine-Tuning a Small Model on a Laptop with LoRA: Start to Finish

I fine-tuned a 3B model on 800 support tickets using a 12GB GPU. Dataset format, hyperparameters that worked, and how I knew it had gone wrong.

Sarah Chen profile photo
July 29, 2026 · 4 min read
Fine-Tuning a Small Model on a Laptop with LoRA: Start to Finish — AI

What I did

taught a 3B base model to answer support tickets in our house style and format. 800 examples, 12GB GPU, 38 minutes of training, one clearly better model. Total cost: electricity.

First, the honest framing: fine-tuning teaches style, format, and vocabulary — not facts. If you want the model to know your product documentation, use retrieval. If you want it to reply the way your team replies, fine-tune. I wasted a week learning this the wrong way round.

The dataset is 90% of the work

Related: Midjourney vs DALL-E vs Flux: A Practical Image-Tool Comparison Framework →

Related: Which GPU Should You Buy for Local AI in 2026? (I Tested Five Price Brackets) →

Format is boring JSONL:

json
{"messages":[{"role":"system","content":"You are a support agent for Acme."},{"role":"user","content":"My invoice shows the wrong VAT."},{"role":"assistant","content":"Thanks for flagging that..."}]}

Rules I now follow after getting it wrong:

  • Consistency beats volume. 500 examples in one consistent voice beat 5,000 mixed ones. My first dataset mixed formal and casual replies and the model learned to be randomly inconsistent.
  • Include the hard cases. If 5% of tickets are refund disputes, make sure the dataset contains them, or the model will confidently improvise on exactly the cases that matter.
  • Strip real names, emails and order numbers. Models memorise. This is not optional.
  • Hold back 10% for evaluation before you start, not after.

The training script

Related: 9 Free AI Coding Tools Every Developer Should Try in 2026 →

Advertisement — In Article

Related: MCP Servers, Explained by Someone Who Wired Five of Them Up →

python
from datasets import load_dataset
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training
from trl import SFTTrainer, SFTConfig

base = "Qwen/Qwen3-3B-Instruct" tok = AutoTokenizer.from_pretrained(base)

bnb = BitsAndBytesConfig(load_in_4bit=True, bnb_4bit_quant_type="nf4", bnb_4bit_compute_dtype="bfloat16", bnb_4bit_use_double_quant=True) model = AutoModelForCausalLM.from_pretrained(base, quantization_config=bnb, device_map="auto") model = prepare_model_for_kbit_training(model)

peft = LoraConfig(r=16, lora_alpha=32, lora_dropout=0.05, bias="none", task_type="CAUSAL_LM", target_modules=["q_proj","k_proj","v_proj","o_proj","gate_proj","up_proj","down_proj"])

ds = load_dataset("json", data_files={"train":"train.jsonl","test":"eval.jsonl"})

trainer = SFTTrainer( model=get_peft_model(model, peft), train_dataset=ds["train"], eval_dataset=ds["test"], args=SFTConfig(output_dir="out", num_train_epochs=3, per_device_train_batch_size=1, gradient_accumulation_steps=8, learning_rate=2e-4, lr_scheduler_type="cosine", warmup_ratio=0.03, logging_steps=10, eval_strategy="steps", eval_steps=50, save_steps=50, bf16=True, gradient_checkpointing=True, max_seq_length=1024), ) trainer.train() trainer.save_model("out/final")

terminal

The hyperparameters that mattered

Related: Sora 2 Review: OpenAI's Video Model Is Finally Useful for Real Work →

Related: Transcribing Audio Locally with Whisper: My Actual Workflow (No Uploads) →

Advertisement — In Article
SettingMy valueWhat happens if you change it
r (rank)168 underfit on my data; 64 gained nothing and used more memory
lora_alpha32Keep at 2× rank as a starting rule
learning rate2e-41e-3 diverged; 5e-5 barely moved
epochs31 was undercooked, 6 memorised the training set
target_modulesall projectionsAttention-only was noticeably weaker for style transfer
max_seq_length1024Set it to your real p95 length; higher costs memory for nothing

Peak VRAM with these settings on a 3B model: about 9.5GB. A 7B model with the same config lands near 14GB — reduce max_seq_length to 512 if you are on 12GB.

Experience log: how I knew it went wrong

Related: How to Clone Your Voice with AI in 2026 (Free and Paid Options) →

Related: Self-Hosting n8n on a $6 VPS: Full Setup, Backups Included →

Run 1: loss dropped beautifully, output was garbage. Cause: my JSONL had the assistant turn included in the prompt formatting twice. The model learned to repeat the question. Always print three fully-formatted training samples before starting a run — print(tok.apply_chat_template(ds["train"][0]["messages"], tokenize=False)). Thirty seconds of checking, hours saved.

Run 2: training loss 0.11, eval loss climbing after step 120. Textbook overfitting. Fix: dropped from 6 epochs to 3 and added lora_dropout=0.05. If eval loss turns upward, stop; nothing later in the run will save it.

Run 3: the model answered every question in the support format, including "what is 2+2". Over-specialisation. This is expected and is fine if the model has one job. If you need general ability too, mix in 10–15% general instruction examples.

Evaluating without kidding yourself

Related: CUDA Out of Memory: 9 Fixes That Actually Worked on My GPU →

Loss numbers are not quality. I built a 40-question held-out set and scored answers on three things: correct format, correct tone, no invented policy. Base model scored 12/40 on format. Fine-tuned scored 37/40. That number is the one I reported, not the loss curve.

Using the result

Related: How I Cut My OpenAI API Bill by 71% Without Changing the Product →

bash
# merge adapter into the base for deployment
python -c "
from peft import AutoPeftModelForCausalLM
m = AutoPeftModelForCausalLM.from_pretrained('out/final', device_map='cpu')
m.merge_and_unload().save_pretrained('merged')"

Then convert to GGUF if you want it in Ollama. The adapter file itself is about 40MB — small enough to version-control alongside the dataset that produced it, which I now do.

FAQ

Do I need thousands of examples? No. 300–1,000 high-quality, consistent examples covers most style-transfer tasks.

Can I fine-tune on CPU? Technically. Practically, no — expect days instead of minutes.

LoRA or full fine-tuning? LoRA, unless you have multiple high-memory GPUs and a specific reason. The quality gap for style tasks is small and the cost gap is enormous.

How do I add new facts? You do not, reliably. Use retrieval. This is the single most common misunderstanding about fine-tuning.

Advertisement

Related Articles

مقالات ذات صلة — تابع القراءة داخل الموقع

View all in AI

مواضيع مقترحة · Suggested Topics

استكشف مواضيع ومحاور ذات صلة بهذا المقال — روابط داخلية لتعميق قراءتك.

The Daily Pulse

Newsletter delivery is not connected yet. This form only saves your address in this browser; no email is sent.

Get concise, source-linked technology notes without the hype.

Advertisement