localaiforum
Log in
Guide โ€บ Working with models

Fine-tuning with LoRA & QLoRA

Fine-tuning tunes behavior, not knowledge. It is the right tool when the model knows the material but performs the wrong dance: wrong output format, wrong tone, wrong domain dialect, wrong refusal calibration. It is the wrong tool for "knows my documents" โ€” that is retrieval: RAG.

Fine-tune or not

Goal Reach for
Answer questions from my documents RAG
Always emit my exact JSON/XML format LoRA โ€” or grammar-constrained decoding
Write in a consistent house style LoRA
Stop refusing legitimate domain requests LoRA, with careful data
Speak my domain's dialect fluently LoRA, then RAG on top
Reason better Bigger or better base model โ€” tuning will not add IQ

If a prompt or a schema can do it, prefer the prompt or the schema. Tune when you catch yourself pasting the same 300 tokens of instructions into every call.

LoRA in one paragraph

Freeze every base weight. For each target weight matrix, add two small trainable matrices โ€” A (rร—d) and B (dร—r) โ€” and train only those. The update is low-rank:

W' = W + (alpha / r) ยท BยทA      # A is rร—d, B is dร—r, rank r << d

Concrete: a d = 4096 matrix at rank r = 16 needs 2 ร— 4096 ร— 16 = 131k adapter parameters instead of 4096ยฒ โ‰ˆ 16.8M dense ones โ€” 0.8% of that matrix. Across a 7โ€“8B model, adapters total ~40โ€“80M parameters: 0.5โ€“1% of the base, a few hundred MB. That is enough capacity to move style and format hard, because those behaviors live in strongly low-rank directions.

QLoRA: one consumer card

QLoRA keeps the frozen base quantized to 4-bit (NF4) during training and runs the adapters in 16-bit. Working numbers on current stacks: a 7โ€“8B model QLoRA-trains on a single 24GB GPU; a 3B trains comfortably in 8GB. Gradient checkpointing and paged optimizers are handled by the tooling โ€” what you supply is the budget math: VRAM math.

Data beats everything

500โ€“5,000 high-quality pairs beat 100k scraped pairs, every time. The rules that matter:

  • One consistent format. Use the exact chat template you will serve with โ€” a train/serve template mismatch undoes the whole run.
  • Quality over volume. Each pair should be an output you would accept from the model. Deduplicate; drop lazy examples.
  • Coverage. Include the cases you want the model to decline or handle specially โ€” otherwise it overgeneralizes its new behavior onto everything.
  • Contamination checks. Make sure eval questions do not appear in the training data โ€” otherwise you are grading memorization.
  • Held-out eval set. 50โ€“100 examples you never train on, graded before and after.

What a pair looks like (the messages format most tools consume):

{"messages": [{"role": "system", "content": "You are a 1998-era equipment manual assistant."},
              {"role": "user", "content": "How do I reset the service counter?"},
              {"role": "assistant", "content": "Press MENU and hold for 3 seconds..."}]}

Tools

Tool Why pick it
Unsloth fastest single-GPU trainers, free community notebooks โ€” the easy start
Axolotl config-driven, wide dataset format support, reproducible recipes
LLaMA-Factory GUI plus CLI, strong multilingual dataset ecosystem

As of this writing all three are actively maintained: unsloth, axolotl, llama-factory.

Hyperparameters that actually matter

In order of impact:

  • Learning rate โ€” 1e-4 to 2e-4 for QLoRA is the sane band; too high is the usual cause of mush.
  • Epochs โ€” 1โ€“3. More is where the yes-man comes from.
  • Rank / alpha โ€” r 8โ€“32 with alpha 16โ€“32 covers most behavior tweaks; bigger rank for domain-heavy shifts.
  • Context length โ€” set to your longest real example, not the trainer default.
  • Batch size โ€” 1โ€“8 is normal for QLoRA on one card; it is a throughput knob, not a quality one.

If a run is disappointing, fix data first, learning rate second, rank last.

Time and cost

QLoRA on 2,000 pairs, 3 epochs, 7โ€“8B model: expect 1โ€“3 hours on one RTX 3090/4090-class card. That is electricity, not API bills, and iteration is overnight-at-worst. This is why local fine-tuning is a hobby, not a procurement process.

Budget for iteration: the first run teaches you the data problems, the second is the one you keep.

Evaluate before you ship

Run a fixed suite of 30โ€“50 prompts โ€” including the held-out set โ€” before and after; diff the outputs side by side. Then regression-check general chat: the classic failure is a domain-tuned model that answers everything in bug-report format. If general quality dropped, use fewer epochs, a lower learning rate, or mix 10โ€“30% general instruction data into the run.

Loss curves are for spotting divergence, not for grading: training loss should fall and flatten. The eval suite is the arbiter.

Export and serve

Two paths from a trained adapter:

  • Merge LoRA into the base at fp16, then convert to GGUF for llama.cpp โ€” the forum-friendly artifact: one file, any engine: model formats, quantization.
  • Keep the adapter separate for transformers/vLLM, which load base + adapter at runtime โ€” cheaper to iterate, engine-specific.

Failure modes

  • Overfit / "yes-man" โ€” echoes your prompt, agrees with everything; training loss low, eval worse. Fix: fewer epochs, lower LR, more diverse data.
  • Catastrophic forgetting โ€” domain skill up, general ability down. Fix: mix general data in; lower LR.
  • Template drift โ€” trained with one chat template, served with another; symptoms are the classics in troubleshooting.

DPO after SFT

When format is right but preferences are off โ€” too verbose, wrong tone, over-refusing โ€” a small DPO run on preference pairs (chosen vs rejected responses) adjusts behavior without another SFT pass. Cheap, stable, and the standard second stage of the SFT-then-DPO recipe.

Related: rag ยท model formats ยท quantization ยท vram math