Fine-Tuning with LoRA and Its Relatives: A Practical Guide to PEFT

Everything around the low-rank update: why a few million trainable parameters can steer an 8-billion-parameter model, which LoRA variant to pick, how to set rank and learning rate, and how to serve many adapters on one GPU.

11 min read Updated LoRA PEFT fine-tuning QLoRA DoRA

LoRA has become the default way to adapt an open-weight model: it trains a fraction of a percent of the parameters, fits on one GPU and produces a file small enough to email. Running Models Locally introduces it with a QLoRA setup. This article goes deeper: the wider family of methods, how rank and scaling behave, which variants matter, what careful experiments say, and how to serve adapters in production.

What fine-tuning changes

Fine-tuning continues training a pretrained model on a narrower dataset. Three kinds of training signal are common, and all of them work with the methods below:

  • Supervised fine-tuning. SFT trains on example conversations, teaching format, style and narrow skills.
  • Preference tuning. Direct preference optimization (DPO) and its relatives train on pairs of better and worse answers, without a separate reward model.
  • Reinforcement learning. A reward function or verifier scores the model's answers, and the update favours high-reward outputs. This is how reasoning models learn to think before answering.

Full fine-tuning updates every weight, and most of its cost lies outside the weights themselves: each parameter also needs a gradient and, with Adam, two optimiser statistics, so training takes several times the memory of inference (the numbers are worked out here). Every fully fine-tuned variant is also a complete copy of the model to store and serve.

A 2020 study by Aghajanyan and colleagues explains why a cheaper approach can work. Pretrained language models turned out to have a low intrinsic dimension for fine-tuning: optimising just 200 parameters, randomly projected back into the full weight space, brought RoBERTa to 90% of full fine-tuning performance on the MRPC paraphrase task, and larger models had even lower intrinsic dimension. The change that adaptation needs lives in a small subspace. Parameter-efficient fine-tuning (PEFT) exploits this by restricting the update to a small structure while the original weights stay frozen.

The PEFT family

PEFT methods differ in where they attach to the transformer and in what they learn:

  • Adapters. Houlsby and colleagues (2019) inserted small bottleneck networks after the attention and MLP sublayers. Only the adapters train, but they are extra layers on every token and add latency.
  • Soft prompts. Prompt tuning learns "virtual token" embeddings prepended to the input; prefix tuning learns key and value vectors prepended inside every attention layer. Prompt tuning catches up with full fine-tuning only at multi-billion-parameter scale, and the prefix occupies context on every call.
  • Rescaling. IA³ learns vectors that multiply the attention keys, values and the MLP's inner activations. It trains even fewer parameters than LoRA and can be folded into the weights.
  • Selective tuning. BitFit trains only bias terms. Most current LLMs have no biases in their linear layers, so little is left to train.
  • Reparameterisation. LoRA and its variants express the change to each weight matrix as a low-rank product beside the frozen matrix, mergeable after training. This is the default for SFT, preference tuning and RL.

GaLore competes for the same hardware budget from another direction: it trains all the weights but projects gradients into a low-rank subspace, shrinking the optimiser state enough that its authors pretrained a 7B model on a 24 GB consumer GPU. It suits updates that genuinely need full rank, such as continued pretraining on a large corpus.

Where parameter-efficient methods attach to a transformer block A transformer block drawn left to right: token embeddings, attention with Q, K, V and O projections, an inserted adapter, and the MLP with gate, up and down projections. Prompt tuning attaches to the input embeddings, prefix tuning adds learned keys and values to attention in every layer, and IA³ adds scaling vectors to keys, values and the MLP. Below, LoRA and its variants attach a trainable low-rank update beside the frozen linear layers of both attention and the MLP. Prompt tuning learned input tokens Prefix tuning learned K, V per layer IA³ vectors rescale K, V and MLP Embeddings input tokens Attention Q, K, V, O Adapter bottleneck MLP gate, up, down next block LoRA, DoRA, VeRA, AdaLoRA a trainable low-rank update beside each frozen linear layer
Where each family attaches. Outlined boxes are the frozen block, tinted boxes are trainable additions, and the dashed adapter is a new layer in the forward pass. LoRA-style updates can be merged into the frozen matrices; adapters and soft prompts stay in the forward pass.

LoRA mechanics in depth

For a frozen weight matrix W0 with dout rows and din columns, LoRA learns a down-projection A of shape r × din and an up-projection B of shape dout × r, and the layer computes h = W0·x + (α / r)·B·A·x. A starts random and B starts at zero, so training begins from the base model's exact behaviour. The factor α/r sets how strongly the update counts.

Each adapted matrix adds r × (din + dout) parameters. For a 4096 × 4096 attention projection at rank 16 that is 16 × 8,192 = 131,072 parameters, against 16.8 million in the matrix itself: under 0.8%. Rank 16 on all seven linear projections of Llama 3.1 8B (four in attention, three in the MLP, in each of 32 layers) gives about 42 million trainable parameters, roughly half a percent of the model. About two thirds of them sit in the MLP, whose matrices are 4096 × 14,336.

Rank and alpha

Rank caps how many independent directions the update can use: a capacity setting, not a quality dial. A low rank is enough to teach a format or a tone; datasets carrying much new information need more. Alpha acts like part of the learning rate; the LoRA paper notes that with Adam, tuning α is roughly equivalent to tuning the learning rate. Some practitioners tie α to the rank (α = r or 2r); others keep α fixed, which makes the best learning rate nearly rank-independent. Pick one convention and tune the learning rate within it.

Which matrices to adapt

The original paper adapted only the attention query and value projections. Later work, including the study discussed below, found that adapting every linear layer, and the MLP above all, matters more than raising the rank. In Hugging Face PEFT, target_modules="all-linear" does this for most architectures. Embedding and output layers normally stay frozen unless you add new tokens.

Merging

After training, W0 + (α/r)BA is an ordinary matrix of the original shape. Merge it and the model runs at exactly the base model's speed. Keep it separate and you can swap adapters at run time, at the cost of a small extra matrix multiplication per adapted layer.

LoRA variants worth knowing

Dozens of variants have been published. Those below are implemented in Hugging Face PEFT or in widely used training tools, and each addresses a specific weakness.

VariantWhat changesWhen to reach for it
QLoRAFrozen base stored in 4-bit NF4 with double quantization; paged optimiser statesThe base model does not fit in GPU memory at 16-bit
DoRASplits each weight into a magnitude vector and a direction; LoRA updates the directionClosing a remaining gap to full fine-tuning; trains somewhat slower
rsLoRAScales the update by α/√r instead of α/rHigh ranks, which otherwise learn little more than low ones
LoRA+Gives B a larger learning rate than AFaster convergence; gains vary between setups
PiSSA, LoftQInitialise A and B from the weight's principal singular directions (PiSSA), or to offset quantization error (LoftQ)Faster convergence; quantized bases at 2–4 bits
VeRAShares frozen random matrices across layers and trains only small scaling vectorsExtremely small adapters, for example one per customer
AdaLoRASVD-style update that prunes unimportant singular values, moving rank where it is neededTight parameter budgets
LoHa, LoKrBuilds the update from Hadamard or Kronecker productsImage diffusion models

Two of these are worth understanding. rsLoRA starts from Kalajdzievski's observation that with the standard α/r factor the update shrinks as rank grows, so rank 256 learns little more than rank 16. Dividing by √r keeps the update's size stable, so a higher rank buys real capacity. DoRA lets the magnitude of each weight train separately from its direction, which plain LoRA struggles to imitate, and still merges into a single matrix. In PEFT both are single flags on LoraConfig.

What careful experiments say

LoRA learns less and forgets less. Biderman and colleagues (2024) compared LoRA with full fine-tuning on code and mathematics, for instruction tuning on about 100,000 examples and for continued pretraining on 20 billion tokens. In standard low-rank settings LoRA learned the target domain substantially worse, but preserved the base model's abilities outside it better than full fine-tuning, weight decay or dropout. Full fine-tuning learned weight changes with a rank 10 to 100 times higher than typical LoRA configurations. So LoRA is a partial defence against catastrophic forgetting, not a cure. Shuttleworth and colleagues (2024) traced its forgetting to intruder dimensions: new high-ranking singular directions in LoRA-trained matrices that full fine-tuning does not create. Scaling them down after training restored much of the pretraining behaviour, and they accumulate when adapters are trained one after another.

LoRA without regret. In September 2025 John Schulman and colleagues at Thinking Machines compared the two methods while sweeping the learning rate separately for every configuration, so that no method lost through a poor setting. They found that:

  • for supervised fine-tuning on small and medium datasets, LoRA on all layers matched full fine-tuning. It fell behind only when the data carried more information than the adapter could hold, and then gradually;
  • attention-only LoRA underperformed MLP-only LoRA with a comparable parameter count, and adding attention to the MLP brought no further gain;
  • LoRA tolerated large batch sizes worse than full fine-tuning, whatever the rank;
  • for reinforcement learning even rank 1 matched full fine-tuning in their runs, which they explain by policy-gradient RL extracting only on the order of one bit of information per episode;
  • the best LoRA learning rate was about ten times the full fine-tuning one and close to rank-independent, and LoRA needed a little over two thirds of the compute per step.

The two studies fit together once data volume is counted: 20 billion tokens of continued pretraining is the kind of load under which an adapter runs out of capacity. For a few thousand to a few hundred thousand examples of a behaviour, well-tuned LoRA on all layers gives up little or nothing.

A practical training recipe

Most failed fine-tunes fail on data, not hyperparameters. Before training:

  • Use the model's chat template and the exact conversation format, system prompt included, that you will use at inference. TRL applies the template automatically to data in the standard messages format.
  • Prefer clean to large. A few hundred to a few thousand reviewed, consistent examples usually beat a large noisy set.
  • Hold out two test sets first: one for the target task and one of general tasks that must not get worse. Rerun both as evals after every training run.

Reasonable starting points for supervised training of a 7–8B model: all linear layers, rank 16 to 64, α equal to r or 2r, a learning rate around 1–2 × 10−4 with a short warm-up, an effective batch of 8 to 32 sequences, and one to three epochs. If held-out loss rises while training loss falls, stop earlier or lower the learning rate. Switching on rsLoRA changes the effective scale, so search the learning rate again.

# In a virtual environment: pip install trl peft datasets
from datasets import load_dataset
from peft import LoraConfig
from trl import SFTConfig, SFTTrainer

# One conversation per line:
# {"messages": [{"role": "system", "content": "..."},
#               {"role": "user", "content": "..."},
#               {"role": "assistant", "content": "..."}]}
data = load_dataset("json", data_files={"train": "train.jsonl", "test": "test.jsonl"})

peft_config = LoraConfig(
    r=16,
    lora_alpha=32,                  # update scaled by lora_alpha / r
    lora_dropout=0.05,
    target_modules="all-linear",    # attention and MLP projections
    task_type="CAUSAL_LM",
    # use_dora=True,                # DoRA: separate magnitude and direction
    # use_rslora=True,              # rsLoRA: lora_alpha / sqrt(r); retune the LR
)

args = SFTConfig(
    output_dir="ticket-triage-lora",
    learning_rate=2e-4,             # roughly 10x a typical full fine-tuning LR
    num_train_epochs=2,
    per_device_train_batch_size=4,
    gradient_accumulation_steps=4,  # effective batch: 16 conversations
    bf16=True,
    logging_steps=10,
)

trainer = SFTTrainer(
    model="meta-llama/Llama-3.1-8B-Instruct",
    args=args,
    train_dataset=data["train"],
    eval_dataset=data["test"],
    peft_config=peft_config,
)
trainer.train()
trainer.save_model()                # saves the adapter, not a copy of the base model

The same peft_config works with TRL's DPOTrainer for preference data and GRPOTrainer for reinforcement learning. To fit a larger model, load the base in 4-bit for QLoRA as shown in Running Models Locally.

Serving, merging and combining adapters

Merging folds an adapter into the base weights and produces an ordinary model that any runtime can serve, including llama.cpp and Ollama after conversion to GGUF. Keeping adapters separate lets one copy of the base model serve many of them: ten fine-tunes of an 8B model are either ten 16 GB copies or one 16 GB base plus ten small files.

from peft import AutoPeftModelForCausalLM
from transformers import AutoTokenizer

model = AutoPeftModelForCausalLM.from_pretrained("ticket-triage-lora")
merged = model.merge_and_unload()    # W0 + (alpha / r)·BA; adapter layers removed
merged.save_pretrained("triage-merged")
AutoTokenizer.from_pretrained("ticket-triage-lora").save_pretrained("triage-merged")

# Then, in a llama.cpp checkout:
#   python convert_hf_to_gguf.py triage-merged --outfile triage-f16.gguf
#   llama-quantize triage-f16.gguf triage-q4_k_m.gguf Q4_K_M
Merged copies versus one base model with many adapters Left: three merged models, each a full 16 GB copy of the base model with one adapter folded in, so memory grows with every task. Right: GPU memory holds one frozen base model. Requests for support, sql and triage are routed to LoRA 1, 2 and 3, small adapters applied alongside the shared base model, and the requests are batched together in one forward pass. Merged copies Base + LoRA 1, merged 16 GB Base + LoRA 2, merged 16 GB Base + LoRA 3, merged 16 GB memory grows with every task One base model, many adapters requests GPU memory support sql triage LoRA 1 LoRA 2 LoRA 3 Base model frozen, 16 GB batched in one forward pass
Merging is simplest to deploy but stores a full copy per task. Multi-adapter serving keeps one base model in memory and applies each request's adapter on the fly, so requests for different adapters share a batch. Sizes assume an 8B model in 16-bit.

Requests for different adapters arrive mixed together, and switching adapters between batches would leave the GPU idle. S-LoRA (2023) showed how to serve thousands of adapters from one GPU: the base model runs once for the whole batch, custom kernels apply each request's own low-rank product, and adapters move between CPU and GPU memory on demand. vLLM uses the same approach:

# One base model, three adapters, all trained on this exact checkpoint
vllm serve meta-llama/Llama-3.1-8B-Instruct \
    --enable-lora \
    --lora-modules support=./support-lora sql=./sql-lora triage=./ticket-triage-lora \
    --max-lora-rank 64 \
    --max-loras 3

# The adapter name goes in the "model" field
curl http://localhost:8000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{"model": "triage",
       "messages": [{"role": "user", "content": "Printer on floor 3 shows error 0x61"}]}'

--max-lora-rank must cover the largest adapter's rank, and --max-loras caps how many different adapters share one batch. Serve every adapter on the base model it was trained on: one trained on another checkpoint, or with QLoRA on a 4-bit base, may load without error and still behave differently.

Adapters can also be combined. Task arithmetic treats a fine-tune's weight difference as a vector to add to or subtract from the base model, and TIES-merging improves on plain addition by trimming small changes and resolving sign conflicts first; PEFT supports both for LoRA adapters. Merged skills interfere, so evaluate the combination. For distinct tasks, routing each request to its own adapter is usually safer.

Choosing a method, and what goes wrong

Fine-tuning is the tool for behaviour: format, tone, a narrow task done cheaply by a small model, or a policy learned from preferences or rewards. For knowledge that changes, retrieval is cheaper and easier to audit (see Embeddings, Vector Databases and RAG). Among training methods, start with LoRA on all layers; use QLoRA if memory is short; try DoRA, or a higher rank with rsLoRA, if evaluation shows a gap to a full fine-tuning baseline; and keep full fine-tuning or GaLore for large data volumes.

SymptomLikely causeFix
Training loss falls, evals do not moveEval set does not reflect the target behaviour, or learning rate too lowCheck evals against real inputs; sweep the learning rate
Repeats training answers, worse on new inputsOverfittingFewer epochs, more varied data
General skills dropForgettingLower learning rate; mix in general data
Ignores the new format, or never stopsChat template or end-of-turn token differs between training and servingFormat data with the tokenizer's chat template
No gain from a higher rankTargeting or learning rate is the bottleneckTarget all linear layers; retune; rsLoRA at high rank

All of these methods find the update with gradient descent. A November 2026 study explores a different route, in which a frontier LLM edits weights directly by reasoning about what they compute; see Weight-Space Programming. As of November 2026 that is a research result, and LoRA remains the practical tool.

Key takeaways

  • PEFT works because the change fine-tuning needs lives in a small subspace; LoRA captures it with a low-rank product beside each frozen matrix.
  • Adapt all linear layers, MLP included. Rank is a capacity setting that matters mainly for large datasets.
  • Tune the learning rate: LoRA's best rate is roughly ten times that of full fine-tuning.
  • QLoRA saves memory, DoRA and rsLoRA close remaining gaps, and VeRA and AdaLoRA cut parameters further.
  • LoRA forgets less than full fine-tuning but is not immune; evaluate general skills too.
  • Merge adapters for simple deployment, or serve many on one base model with vLLM.

Sources and further reading

  1. Hu et al., LoRA: Low-Rank Adaptation of Large Language Models (2021).
  2. Aghajanyan et al., Intrinsic Dimensionality Explains the Effectiveness of Language Model Fine-Tuning (2020).
  3. Houlsby et al., Parameter-Efficient Transfer Learning for NLP (2019).
  4. Liu et al., DoRA: Weight-Decomposed Low-Rank Adaptation (2024).
  5. Kalajdzievski, A Rank Stabilization Scaling Factor for Fine-Tuning with LoRA (2023).
  6. Biderman et al., LoRA Learns Less and Forgets Less (2024).
  7. Shuttleworth et al., LoRA vs Full Fine-tuning: An Illusion of Equivalence (2024).
  8. Schulman and Thinking Machines Lab, LoRA Without Regret (2025).
  9. Sheng et al., S-LoRA: Serving Thousands of Concurrent LoRA Adapters (2023).
  10. Hugging Face, PEFT documentation: LoRA, DoRA, rsLoRA, PiSSA, IA³, prompt tuning and adapter merging.