Fine‑Tuning LLMs in 2026: LoRA, QLoRA, and vLLM

When to fine‑tune vs RAG, adapter sizing defaults, evaluation you can trust, and fast serving with paged attention.

By AI Mastermind Lab · Published 1 Mar 2026

Fine‑tuning gives an LLM new habits and stronger consistency for your specific product tasks; retrieval (RAG) gives it fresh facts and citations. In 2026, most reliable systems combine both.

TL;DR

  • Fine‑tune when you need new behavior, domain style, or strict output formats that prompting + RAG cannot reliably produce. Keep RAG for fresh or fast‑changing knowledge and for citations.
  • Start with small, well‑curated instruction data. Use parameter‑efficient adapters (LoRA/QLoRA). Measure formatting accuracy and task quality, not just loss.
  • Serve with an inference engine that supports paged/segmented KV‑cache and continuous batching (e.g., vLLM). Add speculative decoding to cut tail latency.

When To Fine‑Tune vs. Use RAG

Use this quick decision guide:

  • Need up‑to‑date facts, references, or long‑tail entities → Prefer RAG. Add strong retrieval + re‑ranking and cite sources.
  • Need consistent structure (JSON, SQL, DSL), brand tone, or task‑specific steps → Fine‑tune a small adapter on examples showing exactly the target behavior.
  • Hybrid most often wins: RAG for facts; a fine‑tuned policy for style/formatting and tool‑calling reliability.

Data: What To Collect (and What To Skip)

  • Format: instruction JSONL. Each record: {"instruction": ..., "input": ..., "output": ...}. Keep fields consistent; avoid hidden conventions.
  • Quality over size: 3k–30k examples are enough for many product features. Favor diverse prompts and edge cases over near‑duplicates.
  • Balance: ensure class balance (e.g., success vs. refusal), include counter‑examples, and cover hard negatives.
  • Deduplicate aggressively: near‑duplicate prompts or outputs make training look good while hurting generalization.
  • Fixed dev set: hold back 5–10% for evaluation; never train on it.

Example record:

{"instruction": "Convert the spec to valid JSON schema.", "input": "type: user\nfields: id:int, email:string", "output": "{\n  \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n  \"type\": \"object\",\n  \"properties\": {\n    \"id\": {\"type\": \"integer\"},\n    \"email\": {\"type\": \"string\", \"format\": \"email\"}\n  },\n  \"required\": [\"id\", \"email\"]\n}"}

Adapters: LoRA vs QLoRA

  • LoRA (BF16/FP16 base): Great when you have multi‑GPU memory.
  • QLoRA (4‑bit NF4 base): Best single‑GPU option; trains low‑VRAM while preserving quality.
  • Targets: start with attention projections (q_proj, k_proj, v_proj, o_proj) and MLP (gate_up_proj / down_proj in LLaMA‑like models). Freeze norms and embeddings.
  • Sizing defaults: r=8–16, alpha=16–32, dropout=0.05–0.1. Scale up only if eval plateaus.

Training Recipes (2026)

  • Supervised fine‑tuning (SFT): Cross‑entropy on instruction data. Focus on format fidelity and task success.
  • Preference optimization (DPO/KTO/ORPO): If outputs must align with human preferences, add a short stage on chosen vs. rejected pairs. Keep it small (e.g., 5–20% of SFT tokens) to avoid regressions.
  • Mixed‑format tasks: Train SFT with explicit format instructions + few‑shot exemplars; then optionally add a tiny DPO stage using a JSON‑valid vs. invalid discriminator to reduce format drift.

Suggested hyper‑params (tune per model):

  • Optimizer: AdamW (β1=0.9, β2=0.95), weight decay 0.01 (exclude norms/biases)
  • LR: 1e‑4 (LoRA) or 2e‑4 (QLoRA), cosine decay, warmup 3–5%
  • Batch: effective 64–256 sequences (use grad accumulation)
  • Seq length: match what you can serve; don’t train longer than your production max
  • Regularization: grad clip 1.0; label smoothing 0.0–0.1 if outputs are peaky

Evaluation: Make Failures Actionable

Track both quality and robustness:

  • Formatting: exact match on JSON validity; function‑call argument validity; SQL parse success.
  • Task metrics: exact match / F1 / edit distance; BLEU/ROUGE only when necessary.
  • Safety/guardrails: refusal rate on disallowed prompts; jailbreak resistance on a fixed red‑team set.
  • Latency and cost: tokens/sec, p95 latency, GPU‑hours per 1k requests.
  • Error taxonomy: categorize failures (formatting, hallucination, refusal, tool‑call args, latency). Fix the biggest bucket first.

Tip: Maintain a “golden set” of 100–500 prompts with deterministic checkers (JSON Schema, SQL parser, unit tests). Run it on every change.

Serving Fast: vLLM + Quantization + Speculation

  • Engine: vLLM (paged attention, continuous batching) is a strong default for server inference.
  • Quantization for serving: 4‑bit/8‑bit weight‑only and KV‑cache quantization can reduce memory and increase batch size. Validate quality.
  • Speculative decoding: Use a small draft model or a lightweight proposer to reduce tail latency with minimal quality loss.

vLLM quickstart example:

pip install vllm
python -m vllm.entrypoints.openai.api_server \
  --model meta-llama/Llama-3-8b-instruct \
  --tensor-parallel-size 1 \
  --max-model-len 8192 \
  --port 8000

Minimal LoRA/QLoRA (Transformers + PEFT)

from datasets import load_dataset
from transformers import AutoModelForCausalLM, AutoTokenizer, TrainingArguments, Trainer
from peft import LoraConfig, get_peft_model
import torch

model_id = "meta-llama/Llama-3-8b-instruct"
model = AutoModelForCausalLM.from_pretrained(model_id, torch_dtype=torch.bfloat16, device_map="auto")
tokenizer = AutoTokenizer.from_pretrained(model_id)

# QLoRA option
# from transformers import BitsAndBytesConfig
# quant = BitsAndBytesConfig(load_in_4bit=True, bnb_4bit_quant_type="nf4", bnb_4bit_compute_dtype=torch.bfloat16)
# model = AutoModelForCausalLM.from_pretrained(model_id, quantization_config=quant, device_map="auto")

peft_cfg = LoraConfig(r=16, lora_alpha=32, lora_dropout=0.05, target_modules=["q_proj","k_proj","v_proj","o_proj"]) 
model = get_peft_model(model, peft_cfg)

train = load_dataset("json", data_files="train.jsonl")["train"]
val = load_dataset("json", data_files="dev.jsonl")["train"]

def format_example(ex):
    prompt = f"Instruction: {ex['instruction']}\nInput: {ex.get('input','')}\nAnswer:"
    text = prompt + ex["output"]
    toks = tokenizer(text, truncation=True, max_length=2048)
    toks["labels"] = toks["input_ids"].copy()
    return toks

train_tok = train.map(format_example, remove_columns=train.column_names)
val_tok = val.map(format_example, remove_columns=val.column_names)

args = TrainingArguments(
    output_dir="out",
    per_device_train_batch_size=2,
    gradient_accumulation_steps=16,
    learning_rate=1e-4,
    num_train_epochs=3,
    lr_scheduler_type="cosine",
    warmup_ratio=0.05,
    logging_steps=50,
    save_steps=1000,
    bf16=True,
)

trainer = Trainer(model=model, args=args, train_dataset=train_tok, eval_dataset=val_tok)
trainer.train()
model.save_pretrained("lora")

Practical Checklists

Before training:

  • Define the single metric that matters (e.g., JSON validity rate). Make it auto‑checkable.
  • Curate 3–10 canonical examples per sub‑task; add edge cases.
  • Decide max sequence length and the longest output you can serve.

Before shipping:

  • Run the golden set; require no regression on key metrics.
  • Load test the serving path (real prompts, realistic batch patterns).
  • Add monitoring for p95 latency, error rate, formatting failures.

FAQ

  • Can I combine RAG and fine‑tuning? Yes—RAG for facts, a small adapter for style/format/tool calls. Most teams do both.
  • Do I need RLHF? Not for most product features. SFT + a small DPO/KTO/ORPO pass often suffices.
  • How many examples do I need? Start with 1–3k; grow only if eval plateaus. Always improve quality before size.

Related

  • See: /knowledge-base/rag-best-practices-2026
  • See: /knowledge-base/vector-databases-for-rag
  • See: /knowledge-base/speculative-decoding-inference-2026