EngineeringGuides

LoRA vs QLoRA: A Technical Comparison for LLM Fine-Tuning

LoRA and QLoRA are the two dominant methods for parameter-efficient LLM fine-tuning. This guide breaks down how they work, when to use each, and includes code examples.

Headshot of Iddo Gino
Iddo Gino · Founder & CEO
Green and red neural network mesh visualization representing LoRA and QLoRA model weight adaptation paths
Photo by Pietro Jeng on Unsplash

When you're picking between LoRA and QLoRA for fine-tuning, the real question is simple: how much GPU memory do you have? LoRA (Low-Rank Adaptation) freezes pre-trained weights and trains small adapter matrices, cutting trainable parameters by up to 10,000x. QLoRA (Quantized LoRA) does the same thing but first compresses the frozen base model to 4-bit precision, slashing VRAM requirements by another 4x. A 65B-parameter model that would normally need a multi-GPU cluster fits on a single 48GB GPU. Both produce adapters that merge into the base model at inference time with zero added latency. In practice, the choice between LoRA and QLoRA boils down to hardware budget versus training throughput.

This article covers the mechanics of each method, concrete memory and speed tradeoffs grounded in the original papers (Hu et al., 2021; Dettmers et al., 2023), working code for both, and guidance on when to pick one over the other.

How LoRA Works

Microsoft Research introduced LoRA and published it at ICLR 2022. The idea is refreshingly simple. Instead of updating every weight in a pre-trained Transformer, you freeze the whole thing and inject small, trainable rank-decomposition matrices into each layer.

Take a weight matrix W of dimensions d x k. LoRA adds a parallel path through two smaller matrices: a "down-projection" A (r x k) and an "up-projection" B (d x r), where r is the rank, typically 16 to 64. The forward pass becomes:

output = W*x + (B*A)*x

Only A and B get trained. For a 7B-parameter model, that drops trainable parameters from billions to a few million, roughly 0.1-1% of the original count. What does that buy you? A 7B LoRA fine-tune needs around 16-24 GB of VRAM, compared to 56-120 GB for full fine-tuning.

After training, you merge the adapter matrices back into the base weights (W' = W + B*A). One model, no architectural overhead. That's a concrete advantage over bottleneck adapter methods that bolt on extra sequential layers and slow down inference.

LoRA dominates the ecosystem now. According to Hugging Face's analysis, 98.4% of all PEFT-based fine-tuned models on the Hub use LoRA.

What QLoRA Adds

QLoRA came from the University of Washington NLP group and was presented as an oral at NeurIPS 2023. It builds directly on LoRA with three memory-focused innovations:

4-bit NormalFloat (NF4) quantization. The frozen base model weights get quantized to a new 4-bit data type that's information-theoretically optimal for normally distributed weights. Values are normalized to [-1, 1] using quantile quantization.

Double quantization. The quantization constants themselves are quantized, shrinking their memory footprint from 0.5 bits per parameter to 0.127 bits per parameter.

Paged optimizers. NVIDIA unified memory pages optimizer states between GPU and CPU RAM during memory spikes, preventing out-of-memory crashes on tight hardware.

The LoRA adapters still train in 16-bit precision. Only the frozen base gets compressed. Gradient fidelity stays intact while the memory floor drops dramatically. The QLoRA paper showed that their best model family, Guanaco, reached 99.3% of ChatGPT performance on the Vicuna benchmark after just 24 hours of training on a single GPU.

LoRA vs QLoRA: Key Differences

The LoRA vs QLoRA fine tuning decision comes down to a few measurable tradeoffs:

| Dimension | LoRA | QLoRA | |---|---|---| | Base model precision | FP16 / BF16 | 4-bit NF4 | | Adapter precision | FP16 / BF16 | BF16 | | VRAM (7B model) | ~16-24 GB | ~4-10 GB | | VRAM (70B model) | ~160 GB | ~48 GB | | Training throughput | Baseline | ~39-50% slower | | Quality vs full FT | Comparable | Within 1-3% | | Inference latency | Zero (after merge) | Zero (after merge) | | Min hardware (7B) | 1x A10G / RTX 4090 | 1x RTX 3090 / T4 |

Where does the throughput penalty come from? On-the-fly dequantization during the forward pass. Those 4-bit weights have to be converted back to 16-bit for matrix multiplication on every step. Research shows LoRA achieves roughly 2x the throughput of QLoRA, putting QLoRA at approximately 39-50% slower depending on model size and hardware. For most teams, that's acceptable because the alternative is not training at all on the hardware they've got.

Quality differences are small. The original QLoRA paper showed NF4 with double quantization fully recovered 16-bit LoRA performance on MMLU. One study reported Spearman correlations of 0.81-0.9 between LoRA and QLoRA outputs on preference evaluations, meaning quantization has only a minor effect on response distributions.

Full Fine-Tuning vs LoRA vs QLoRA

| | Full Fine-Tuning | LoRA | QLoRA | |---|---|---|---| | Trainable params | 100% | 0.1-1% | 0.1-1% | | VRAM (7B) | 56-120 GB | 16-24 GB | 4-10 GB | | Training cost (7B) | ~$322 (8x A100) | ~$13 (1x A10G) | ~$5-8 (1x T4/RTX) | | Checkpoint size | Full model (GBs) | Adapter only (MBs) | Adapter only (MBs) | | Quality ceiling | Highest | Near-full | Near-full | | Multi-task serving | Separate models | Swap adapters | Swap adapters |

Cost figures come from Philipp Schmid's FLAN-T5-XXL benchmarks: a LoRA run on a single A10G cost approximately $13 and produced an 84 MB checkpoint, while full fine-tuning of the same model required 8x A100 40GB GPUs at approximately $322. QLoRA pushes costs lower still by fitting on cheaper GPU instances.

How to Fine-Tune with LoRA (Hugging Face PEFT)

The standard stack in 2026 is Hugging Face PEFT + TRL + bitsandbytes. Install everything with:

pip install transformers datasets accelerate peft bitsandbytes trl

A minimal LoRA fine-tuning script:

from peft import LoraConfig
from trl import SFTConfig, SFTTrainer

peft_config = LoraConfig(
    r=32,
    lora_alpha=16,
    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"],
)

training_args = SFTConfig(
    learning_rate=2.0e-4,
    per_device_train_batch_size=2,
    gradient_accumulation_steps=8,
    num_train_epochs=1,
    output_dir="./output",
)

trainer = SFTTrainer(
    model="Qwen/Qwen2-0.5B",
    args=training_args,
    train_dataset=dataset,
    peft_config=peft_config,
)
trainer.train()

How to Fine-Tune with QLoRA

QLoRA adds exactly one thing on top: a BitsAndBytesConfig that tells the loader to quantize the base model to 4-bit NF4:

import torch
from peft import LoraConfig
from transformers import BitsAndBytesConfig
from trl import SFTConfig, SFTTrainer

bnb_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_quant_type="nf4",
    bnb_4bit_compute_dtype=torch.bfloat16,
    bnb_4bit_use_double_quant=True,
)

peft_config = LoraConfig(
    r=32,
    lora_alpha=16,
    lora_dropout=0.05,
    bias="none",
    task_type="CAUSAL_LM",
)

trainer = SFTTrainer(
    model="meta-llama/Llama-2-7b-hf",
    args=SFTConfig(
        learning_rate=2.0e-4,
        per_device_train_batch_size=1,
        gradient_accumulation_steps=16,
        output_dir="Llama-2-7b-QLoRA",
    ),
    train_dataset=dataset,
    quantization_config=bnb_config,
    peft_config=peft_config,
)
trainer.train()

Key settings: always use bnb_4bit_quant_type="nf4" over "fp4" (NF4 consistently outperforms FP4 on benchmarks), set compute dtype to bfloat16 (fp16 causes a ~20% training failure rate on 7B models), and enable double quantization for an extra ~0.4 bits/param savings with negligible quality cost.

Saving and Merging Adapters

Both methods save only the adapter weights, a few megabytes rather than the full model:

# Save adapter checkpoint
trainer.save_model("path/to/adapters")

# Later: load and merge for production inference
from peft import PeftModel
from transformers import AutoModelForCausalLM

base_model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen2-0.5B")
model = PeftModel.from_pretrained(base_model, "path/to/adapters")
model = model.merge_and_unload()  # Single merged model, zero adapter overhead

For QLoRA, merge_and_unload() dequantizes and merges, producing a full-precision model suitable for serving with vLLM or TGI. vLLM also supports runtime adapter swapping without restarting the inference server. That enables multi-tenant serving where different customers run different adapters against a shared base model.

Hyperparameter Cheat Sheet

Based on the Hugging Face PEFT documentation and Unsloth's tuning guide, here are recommended starting points:

| Parameter | Simple tasks (chat, classification) | Complex tasks (code, math, domain) | |---|---|---| | Rank (r) | 8-16 | 32-64 | | Alpha | r or 2r | r or 2r | | Dropout | 0.05 (or 0 for speed) | 0.05 (or 0 for speed) | | Target modules | all-linear | all-linear | | Learning rate | 2e-4 | 1e-4 to 2e-4 | | Batch size (effective) | 16 | 16 | | Epochs | 1-2 | 2-3 |

Use use_rslora=True (Rank-Stabilized LoRA) when training at ranks above 32. It scales alpha by the square root of the rank, improving stability. Watch for overfitting: if training loss drops below 0.2, reduce epochs or increase regularization.

When to Use LoRA vs QLoRA

Choose LoRA when:

Looking for models to fine-tune? See our roundup of the top open-source LLMs in the 3B-8B parameter range.

Choose QLoRA when:

For most teams starting out, QLoRA is the pragmatic default. The throughput penalty is rarely the actual bottleneck; data preparation and evaluation usually eat far more wall-clock time than the training loop itself.

Deploy Fine-Tuned Models as Autonomous Agents

Gamut connects your LoRA-tuned models to any tool via MCP servers, handling multi-step workflows without infrastructure management. Ship agents that run 24/7 on your domain-specialized models.