LoRA vs QLoRA: Fine-Tuning on Consumer GPUs Explained
LoRA vs QLoRA: Fine-Tuning on Consumer GPUs Explained
Photo by Jeremy Bezanger on Unsplash
Quick Answer: LoRA and QLoRA are both parameter-efficient fine-tuning methods that train small adapter weights instead of the full model. LoRA keeps the base model in FP16 and trains adapters in FP16 — it's faster and higher quality but uses more VRAM. QLoRA quantizes the base to 4-bit (NF4) and trains adapters in FP16 — it uses 4x less VRAM for the base model with minimal quality loss. For 7B models, LoRA needs ~16GB VRAM while QLoRA needs ~8GB. For 70B models, LoRA needs ~160GB (impossible on consumer GPUs) while QLoRA needs ~35-48GB (possible on RTX 5090 or A6000).
How Each Method Works
LoRA (Low-Rank Adaptation)
LoRA adds trainable low-rank matrices to the attention layers while keeping the original weights frozen:
┌───────────────────────────────────────────┐
│ Frozen Base Model (FP16) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Layer 1 │ │ Layer 2 │ │ Layer 3 │ │
│ │ W (frozen) │ W (frozen) │ W (frozen) │
│ │ + LoRA A×B │ + LoRA A×B │ + LoRA A×B │
│ └─────┬────┘ └─────┬────┘ └─────┬────┘ │
│ │ │ │ │
│ └─────────────┴─────────────┘ │
│ Trainable: LoRA adapters (0.1-1% of params)│
│ Stored in: FP16 │
│ VRAM for base: 2 bytes/param │
└───────────────────────────────────────────┘
Memory formula: 2B × n_params + 2B × rank_params + grad + opt
QLoRA (Quantized LoRA)
QLoRA quantizes the base model to 4-bit (NF4) and adds frozen LoRA adapters:
┌───────────────────────────────────────────┐
│ Quantized Base Model (NF4 4-bit) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Layer 1 │ │ Layer 2 │ │ Layer 3 │ │
│ │ W (NF4) │ │ W (NF4) │ │ W (NF4) │ │
│ │ + LoRA │ │ + LoRA │ │ + LoRA │ │
│ └─────┬────┘ └─────┬────┘ └─────┬────┘ │
│ │ │ │ │
│ └─────────────┴─────────────┘ │
│ Base stored in: NF4 (4-bit) │
│ LoRA stored in: FP16 │
│ VRAM for base: 0.5 bytes/param │
│ Double quantization: extra savings │
└───────────────────────────────────────────┘
Memory formula: 0.5B × n_params + 2B × rank_params + grad + opt
Memory Comparison Table
Full Training Memory Breakdown (7B Model, Rank=16)
| Component | LoRA (FP16 Base) | QLoRA (NF4 Base) |
|---|---|---|
| Base model weights | 14.0 GB (FP16) | 3.5 GB (NF4) |
| LoRA adapters | 0.03 GB | 0.03 GB |
| Gradients | 0.03 GB | 0.03 GB |
| Optimizer (AdamW, 8 states) | 0.24 GB | 0.24 GB |
| Activations (+ grad checkpointing) | 2.0 GB | 2.0 GB |
| KV cache + buffers | 1.0 GB | 1.0 GB |
| Total | ~17.3 GB | ~6.8 GB |
Memory Needed by Model Size (Rank=16, Batch=1, Gradient Checkpointing)
| Model Size | LoRA (FP16) | QLoRA (NF4) | Savings |
|---|---|---|---|
| 1B | ~4 GB | ~2 GB | 50% |
| 3B | ~8 GB | ~4 GB | 50% |
| 7B | ~17 GB | ~7 GB | 59% |
| 8B | ~20 GB | ~8 GB | 60% |
| 13B | ~30 GB | ~12 GB | 60% |
| 34B | ~72 GB | ~22 GB | 69% |
| 70B | ~160 GB | ~45 GB | 72% |
| 120B | ~280 GB | ~75 GB | 73% |
Key takeaway: LoRA can fine-tune up to 13B models on consumer GPUs (RTX 4090 24GB). QLoRA extends that to 34B (RTX 4090) and 70B (RTX 5090 32GB or A6000 48GB). For 70B+, QLoRA is the only option without a multi-GPU setup.
Quality Benchmarks
Llama 3.1 8B — Fine-Tuned on Alpaca Eval
| Method | Win Rate vs GPT-4 | MMLU | GSM8K | Memory Used |
|---|---|---|---|---|
| Full fine-tune (FP16) | 68.2% | 68.4% | 82.1% | 56 GB |
| LoRA (r=16, FP16) | 66.8% | 67.9% | 81.5% | 17 GB |
| LoRA (r=64, FP16) | 67.5% | 68.1% | 81.8% | 19 GB |
| QLoRA (r=16, NF4) | 65.1% | 66.8% | 80.2% | 7 GB |
| QLoRA (r=64, NF4) | 66.2% | 67.4% | 80.9% | 9 GB |
Llama 3.1 70B — Fine-Tuned on GSM8K
| Method | GSM8K Accuracy | Memory | GPU Required |
|---|---|---|---|
| Full fine-tune (FP16) | 94.0% | 700 GB | 8x H100 |
| LoRA (r=16, FP16) | 91.8% | 160 GB | 2x A100 |
| QLoRA (r=16, NF4) | 89.5% | 45 GB | 1x A6000 or RTX 5090 |
| QLoRA (r=64, NF4) | 90.3% | 48 GB | 1x A6000 |
Quality gap: QLoRA typically achieves 90-97% of LoRA's quality and 85-95% of full fine-tuning quality. The gap narrows with higher rank (r=64) and good datasets.
Photo by Bmonster Lab on Pexels
Training Speed Comparison
Llama 3.1 8B — 1,000 Examples, 3 Epochs
| Method | RTX 4090 | RTX 5090 | H100 |
|---|---|---|---|
| LoRA (r=16) | 22 min | 14 min | 6 min |
| QLoRA (r=16) | 32 min | 19 min | 9 min |
| QLoRA slower by | ~45% | ~36% | ~50% |
Llama 3.1 70B — 1,000 Examples, 3 Epochs
| Method | A6000 (48GB) | RTX 5090 (32GB) | H100 (80GB) |
|---|---|---|---|
| LoRA (r=16) | ❌ (OOM) | ❌ (OOM) | ~45 min |
| QLoRA (r=16) | ~4.5 hrs | ~5.5 hrs (tight) | ~1.5 hrs |
Why QLoRA is slower:
- Dequantization overhead: NF4 weights must be dequantized on-the-fly during forward pass
- Memory bandwidth bottleneck: Smaller base model in 4-bit still requires full bandwidth for dequantization
- Paged optimizers: When VRAM is tight, paged AdamW swaps optimizer states to CPU, adding latency
GPU Requirements by Model Size
| Model Size | LoRA GPU | QLoRA GPU | Footprint Difference |
|---|---|---|---|
| 1B | Any 4GB+ GPU | Any 2GB+ GPU | Minimal |
| 3B | RTX 3060 12GB | Any 4GB+ GPU | 2x difference |
| 7B | RTX 4070+ (16GB) | RTX 3060 12GB | LoRA needs more |
| 8B | RTX 4070 Ti+(16GB) | RTX 3060 12GB | 2.5x difference |
| 13B | RTX 4090 (24GB) | RTX 4070 Ti+(16GB) | 2.5x difference |
| 34B | A6000 (48GB) | RTX 4090 (24GB) | 3x difference |
| 70B | 2x A100 (160GB) | RTX 5090/A6000 ~(48GB) | 4x difference |
When to Use Each Method
Use LoRA When You:
| Condition | Priority |
|---|---|
| Have enough VRAM for the base model in FP16 | Critical |
| Need the highest possible fine-tune quality | High |
| Want faster training (QLoRA is 35-50% slower) | Medium |
| Are fine-tuning models <= 13B | Common case |
| Care about minimal quality degradation (<1%) | High |
Use QLoRA When You:
| Condition | Priority |
|---|---|
| Need to fine-tune 34B+ models on consumer GPUs | Critical |
| Have limited VRAM (12-24GB cards) | High |
| Want to fine-tune 70B on a single GPU | Common use case |
| Can accept 1-3% quality loss vs full fine-tuning | Medium |
| Have more time than GPU budget | Medium |
Hybrid Strategy
Some teams use both:
- QLoRA for prototyping (fast iteration, find the right dataset/hyperparams)
- Then LoRA for production (higher quality final model)
Code Comparison: LoRA vs QLoRA Setup
LoRA Setup
from transformers import AutoModelForCausalLM
from peft import LoraConfig, get_peft_model
# Load model in FP16
model = AutoModelForCausalLM.from_pretrained(
"meta-llama/Llama-3.1-8B",
torch_dtype=torch.float16,
device_map="auto",
)
# LoRA config
lora_config = LoraConfig(r=16, lora_alpha=32, ...)
model = get_peft_model(model, lora_config)
QLoRA Setup
from transformers import AutoModelForCausalLM, BitsAndBytesConfig
from peft import LoraConfig, get_peft_model
# 4-bit quantization config
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.bfloat16,
bnb_4bit_use_double_quant=True,
)
# Load model quantized
model = AutoModelForCausalLM.from_pretrained(
"meta-llama/Llama-3.1-8B",
quantization_config=bnb_config,
device_map="auto",
)
# Same LoRA config
lora_config = LoraConfig(r=16, lora_alpha=32, ...)
model = get_peft_model(model, lora_config)
Related Reads
- QLoRA 4-bit Fine-Tuning Tutorial: Single GPU, 7B to 70B
- Fine-Tune Llama 3.1 8B with QLoRA on a Consumer GPU
- RunPod vs Vast.ai vs Lambda Labs: Best GPU Cloud for LLMs
Advanced Quantization Techniques in QLoRA
QLoRA’s 4-bit quantization (NF4) is just the starting point. The method incorporates double quantization to further reduce memory usage without sacrificing precision. Double quantization works by first quantizing the 4-bit weights into smaller blocks (e.g., 64 or 256 values per block) and then quantizing the quantization constants themselves. This adds a second layer of compression, typically saving an additional 0.3–0.5 bytes per parameter. For a 70B model, this translates to ~2–3GB of extra VRAM savings—critical when pushing the limits of a 48GB GPU.
Another optimization is block-wise quantization, where weights are quantized in smaller chunks (e.g., 64 or 128 elements) rather than globally. This reduces quantization error by adapting to local weight distributions, improving the signal-to-noise ratio during dequantization. The trade-off is slightly higher memory overhead for storing per-block scales and zeros, but the quality gains often justify it. For example, block-wise NF4 can recover ~0.5–1% of the accuracy lost with naive 4-bit quantization on tasks like GSM8K or MMLU.
For practitioners, these techniques are configurable via the BitsAndBytesConfig in Hugging Face’s transformers library. Key parameters include:
bnb_4bit_quant_type: Choose between"fp4"(floating-point 4-bit) or"nf4"(normal float 4-bit). NF4 is preferred for most LLMs due to its better dynamic range.bnb_4bit_use_double_quant: Enable/disable double quantization (default:True).bnb_4bit_quant_storage: Storage dtype for quantized weights (e.g.,torch.uint8).
Experimenting with these settings can yield marginal but meaningful improvements in edge cases, such as fine-tuning on noisy or small datasets.
---
Optimizing Training Efficiency for LoRA and QLoRA
Beyond memory savings, training speed is a critical bottleneck for both LoRA and QLoRA. The primary culprit for QLoRA’s slower performance is dequantization overhead: during the forward pass, 4-bit weights must be converted back to FP16/BF16 for computation, adding latency. This overhead scales with model size and batch size. For instance, a 70B model with QLoRA on an A6000 may spend ~30% of its training time on dequantization alone.
To mitigate this, practitioners can leverage mixed-precision training and kernel fusion. Using torch.bfloat16 for compute (via bnb_4bit_compute_dtype) instead of FP16 reduces memory bandwidth pressure while maintaining numerical stability. Additionally, libraries like bitsandbytes fuse dequantization and matrix multiplication into a single kernel, reducing memory round-trips. For example, enabling bitsandbytes’s fused kernels can cut QLoRA training time by ~15–20% on Ampere GPUs (e.g., RTX 30/40 series).
Gradient checkpointing is another essential tool, though it’s already widely used. By recomputing activations during the backward pass instead of storing them, it reduces activation memory by ~50–70%. However, this comes at the cost of ~20–30% longer training times. For LoRA, gradient checkpointing is often unnecessary for models ≤13B, but for QLoRA, it’s critical to fit larger models into limited VRAM.
Batch size also plays a pivotal role. While LoRA can often train with batch sizes of 4–8 on a 24GB GPU, QLoRA may require batch sizes of 1–2 to avoid OOM errors. This exacerbates the speed gap, as smaller batches reduce GPU utilization. Techniques like gradient accumulation (simulating larger batches by accumulating gradients over multiple steps) can help, but they don’t fully offset the overhead. For example, training a 70B model with QLoRA on an RTX 5090 might require a batch size of 1 with 8 steps of gradient accumulation, adding significant latency.
---
Adapter Merging and Deployment Strategies
After fine-tuning, the choice between merging adapters or keeping them separate impacts both inference performance and flexibility. For LoRA, merging the adapter weights into the base model (via model.merge_and_unload()) eliminates the need for separate adapter storage and reduces inference latency by ~10–20%. This is ideal for production deployments where latency is critical, such as real-time chatbots or API services. However, merging is irreversible and requires re-fine-tuning if the adapter needs updates.
For QLoRA, merging is more nuanced. Since the base model is quantized, merging the adapter weights into a 4-bit base model is not straightforward. Instead, practitioners typically dequantize the base model to FP16/BF16, merge the adapter, and then optionally re-quantize the merged model for deployment. This adds an extra step but ensures the merged model retains the quality benefits of FP16 while still fitting into limited VRAM. For example, a 70B model fine-tuned with QLoRA can be merged into an FP16 base, reducing inference VRAM from ~45GB to ~35GB—still feasible on an A6000 but with better performance.
An alternative is dynamic adapter loading, where adapters are kept separate and loaded on-the-fly during inference. This is useful for multi-tenant deployments (e.g., serving multiple fine-tuned models from a single base model) but adds complexity to the inference pipeline. Libraries like peft support this via PeftModel, which dynamically applies adapters to the base model. The trade-off is higher memory usage (since both the base model and adapter must be loaded) and slightly slower inference (~5–10% latency increase).
For edge deployments (e.g., mobile or embedded devices), adapter distillation can further reduce footprint. This involves training a smaller adapter (e.g., rank=8) to mimic the behavior of a larger one (e.g., rank=64), reducing memory and compute requirements. While this sacrifices some quality, the drop is often negligible for specific tasks. For example, distilling a rank-64 LoRA adapter to rank-8 might reduce GSM8K accuracy by ~1% but cut adapter size by 8x—critical for devices with <8GB RAM.
Key Takeaways
- LoRA trains adapters in FP16 while keeping the base model frozen in FP16, requiring ~16GB VRAM for a 7B model; QLoRA quantizes the base to 4-bit (NF4), reducing VRAM to ~8GB for the same model with minimal quality loss.
- QLoRA enables fine-tuning 70B models on a single consumer GPU (e.g., RTX 5090 or A6000), where LoRA would require ~160GB VRAM—making QLoRA the only viable option for large models without multi-GPU setups.
- QLoRA is ~35-50% slower than LoRA due to dequantization overhead and memory bandwidth bottlenecks, but the trade-off is justified when VRAM constraints are critical (e.g., 34B+ models).
- For production use, LoRA is preferred when VRAM is sufficient (≤13B models) due to higher quality and faster training; QLoRA is ideal for prototyping or when targeting 34B+ models on limited hardware.
- Quality gaps between QLoRA and LoRA are typically 1-3% on benchmarks (e.g., GSM8K, MMLU), narrowing with higher LoRA ranks (e.g., r=64) or better datasets—acceptable for most applications but not high-stakes domains.
- Hybrid workflows leverage QLoRA for rapid prototyping (e.g., dataset/hyperparameter tuning) and switch to LoRA for final production models to balance speed and quality.
Frequently Asked Questions
Can I use QLoRA with any model?
QLoRA works with any Transformer model that transformers supports and has linear layers. This includes Llama, Mistral, Qwen, DeepSeek, Gemma, Phi, and most other popular architectures. It doesn't work with non-transformer models.
Is QLoRA quality good enough for production?
Yes. For most applications, the 1-3% quality gap between QLoRA and full fine-tuning is imperceptible in chat, Q&A, and content generation tasks. For high-stakes applications (medical, legal), LoRA or full fine-tuning is recommended.
How do I choose the LoRA rank?
Start with r=16 (good quality, low memory). For complex tasks, try r=32 or r=64. Higher rank improves quality but increases memory and training time. The quality improvement from r=16 to r=64 is typically 1-3% on benchmarks.
Can I convert a QLoRA adapter to LoRA?
The adapter weights are the same format. The difference is the base model precision. You can train with QLoRA (4-bit base), then merge the adapter with an FP16 base model for better inference performance without quantization overhead.
Does QLoRA work for multimodal models?
Yes, but QLoRA is typically applied to the language model component only. Vision encoders are usually frozen during fine-tuning. For vision-language models, the VRAM savings from QLoRA are especially valuable since the vision encoder adds extra memory overhead.


Comments
Sign in to join the conversation
No comments yet. Be the first to share your thoughts!