Skip to content

QLoRA: Quantized LoRA - Complete Comparison with LoRA

Overview

QLoRA (Quantized Low-Rank Adaptation) combines INT4 quantization with LoRA to enable fine-tuning of large language models on consumer GPUs. It achieves 4x memory reduction over standard LoRA while maintaining 99%+ accuracy.

  • Paper: "QLoRA: Efficient Finetuning of Quantized LLMs"
  • Authors: Dettmers et al., University of Washington (2023)
  • Key Innovation: Quantize base model + LoRA adapters
  • Impact: Makes 70B models trainable on single consumer GPU
  • Adoption: Industry standard for affordable fine-tuning

LoRA vs QLoRA: Quick Comparison

Memory Requirements:

LoRA (Standard):
  - Llama 2 7B: 14GB (float16)
  - LoRA adapters: 64MB (float16)
  - Gradients: 14GB
  - Optimizer states: 28GB
  - Total: ~56GB needed
  - Hardware: 2× A100 or 1× H100

QLoRA (Quantized):
  - Llama 2 7B: 3.5GB (int4 quantized)
  - LoRA adapters: 64MB (float16)
  - Gradients (for LoRA only): 64MB
  - Optimizer states (for LoRA only): 128MB
  - Total: ~4GB needed
  - Hardware: Single RTX 4090 or single A100!

Savings: 56GB → 4GB = 93% memory reduction!
Cost reduction: $600/hour → $25/hour (96% cheaper!)

Trade-off:
  - LoRA: Full precision, minimal quality loss
  - QLoRA: Quantized base, tiny adapters, 99% quality
  - Choice: Use QLoRA for budget, LoRA for quality

The Problem: Why QLoRA is Needed

LoRA's Limitation: Still Requires GPU Memory

Even with LoRA, training Llama 2 7B is expensive:

Setup:
  - Model: Llama 2 7B (float16)
  - Batch size: 4
  - Sequence length: 2048
  - LoRA rank: 8
  - Goal: Fine-tune on custom data

Memory breakdown:
  - Model weights (frozen): 14GB (must load full model)
  - Gradients: 14GB (need to backprop through all layers)
  - Optimizer states (Adam): 28GB (momentum + variance)
  - LoRA adapters: 64MB (trainable)
  - Activations: ~5GB (intermediate results)
  - Total: ~61GB

GPU memory needed:
  - Single A100 (40GB): NOT enough!
  - Need 2× A100s minimum
  - Cost: $1200+ per day for 24-hour training

Who can afford this?
  - Large companies: Yes
  - Startups: Maybe
  - Individuals/researchers: Probably not

Problem: LoRA still requires full-precision model!

Quantization: The Key Insight

Observation:
"Fine-tuning doesn't need full precision for base model"

Standard fine-tuning assumes:
  - Base model: Updated (needs high precision)
  - Result: Must keep in float32/float16

LoRA insight:
  - Base model: Frozen (not updated)
  - Only adapters: Updated (needs high precision)
  - Result: Can quantize base model!

QLoRA insight:
  - Base model: Frozen AND quantized (int4)
  - LoRA adapters: Updated in float16
  - During backprop: Dequantize base, compute gradients for adapters only
  - Result: 4x memory reduction!

Why it works:
1. Base model doesn't change, so quantization is permanent
2. Only adapters need gradient computation (small!)
3. Gradient computation is memory-light
4. Dequantization happens on-the-fly (cheap)

QLoRA: How It Works

Architecture Comparison

STANDARD FINE-TUNING:

Forward Pass:
  - Model (float32): 28GB
  - Compute activations
  - Store for backward

Backward Pass:
  - Use stored activations
  - Compute gradients (float32): 28GB
  - Update all weights

Memory peak: 56GB+

LoRA:

Forward Pass:
  - Model (float16): 14GB
  - Compute activations
  - Store for backward

Backward Pass:
  - Use stored activations
  - Compute gradients for ALL weights: 14GB (overhead!)
  - But only update LoRA weights
  - Rest discarded

Memory peak: 28GB+

QLoRA:

Forward Pass:
  - Model (int4): 3.5GB (quantized, loaded as-is)
  - Dequantize on-the-fly (temporary, freed)
  - Compute activations
  - Store for backward

Backward Pass:
  - Use stored activations
  - Dequantize model (temporary, freed)
  - Compute gradients ONLY for LoRA: 64MB
  - Update LoRA weights only
  - Everything else freed

Memory peak: 4GB

Savings: 7.5x memory reduction!

Data Flow Diagram

STANDARD LoRA:

Input → Model (float16, 14GB) → Activations → Backward pass
           ↓
        LoRA adapters (64MB)
           ↓
      Output (LoRA applied)

Memory usage:
  - Model: 14GB (constant)
  - Activations: ~5GB (during forward)
  - Gradients: 14GB (during backward)
  - Total peak: ~33GB

QLoRA:

Input → Model (int4, 3.5GB) → Dequantize → Activations → Backward pass
           ↓                      ↓
         [Int4 weights]   [Float16, temporary]
           ↓                      
        LoRA adapters (64MB)
           ↓
      Output (LoRA applied)

Memory usage:
  - Model: 3.5GB (stored as int4)
  - Dequantized: ~14GB (temporary during compute, freed immediately)
  - Activations: ~2GB (lighter due to lower precision)
  - LoRA gradients: 64MB (only trainable adapters)
  - Total peak: ~4GB

Key: Dequantized weights NOT kept in memory (freed after use)

Memory Savings: Detailed Analysis

For Different Model Sizes

7B Model (Llama 2 7B):

                    LoRA (float16)    QLoRA (int4)    Savings
─────────────────────────────────────────────────────────────
Model size          14GB              3.5GB           75%
Gradients           14GB              0.06GB          99%
Optimizer states    28GB              0.12GB          99.5%
Activations         5GB               2GB             60%
LoRA adapters       64MB              64MB            0%
─────────────────────────────────────────────────────────────
Total peak          ~61GB             ~4GB            93%

Cost per day training:
  - LoRA: $1200 (2× A100)
  - QLoRA: $25 (single RTX 4090)
  - Savings: 97.9%!

13B Model:

                    LoRA (float16)    QLoRA (int4)    Savings
─────────────────────────────────────────────────────────────
Total peak          ~120GB            ~8GB            93%

Cost:
  - LoRA: $2400 (4× A100)
  - QLoRA: $50 (single A100)
  - Savings: 97.9%!

70B Model:

                    LoRA (float16)    QLoRA (int4)    Savings
─────────────────────────────────────────────────────────────
Total peak          ~600GB            ~40GB           93%

Cost:
  - LoRA: $12,000 (20× A100) - NOT PRACTICAL
  - QLoRA: $240 (single A100)
  - Now trainable on single GPU!

Breakdown by Component

Memory Components:

QLoRA vs LoRA (7B model):

1. Model Weights:
  - LoRA: 14GB (float16, frozen)
  - QLoRA: 3.5GB (int4, frozen) → 75% reduction

2. Gradient Computation:
  - LoRA: 14GB (must compute for all weights)
  - QLoRA: 64MB (only for LoRA adapters) → 99% reduction

   Why? Gradients only needed for parameters being updated!

3. Optimizer States (Adam):
  - LoRA: 28GB (momentum + variance for all params)
  - QLoRA: 128MB (only for LoRA params) → 99.5% reduction

4. Activations:
  - LoRA: 5GB (full precision needed)
  - QLoRA: 2GB (can use lower precision) → 60% reduction

5. LoRA Adapters:
  - LoRA: 64MB
  - QLoRA: 64MB (same)

Total:
  - LoRA: 14 + 14 + 28 + 5 + 0.064 = 61GB
  - QLoRA: 3.5 + 0.06 + 0.12 + 2 + 0.064 = ~5.7GB

Actual QLoRA peak: ~4GB (accounting for optimizations)

Implementation Comparison

LoRA Implementation

from peft import LoraConfig, get_peft_model
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch

# Load model in full precision (float16)
model = AutoModelForCausalLM.from_pretrained(
    "meta-llama/Llama-2-7b-hf",
    torch_dtype=torch.float16,  # Full precision
    device_map="auto"
)

# Configure LoRA
lora_config = LoraConfig(
    r=8,
    lora_alpha=16,
    target_modules=["q_proj", "v_proj"],
    lora_dropout=0.05,
    bias="none",
    task_type="CAUSAL_LM"
)

# Apply LoRA
model = get_peft_model(model, lora_config)

print(model.print_trainable_parameters())
# trainable params: 4,194,304 || all params: 6,738,415,616 || trainable%: 0.06

# Training (requires 56GB+ GPU memory)
trainer = Trainer(model=model, ...)
trainer.train()

QLoRA Implementation

from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
import torch

# Quantization config (int4)
bnb_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_quant_type="nf4",         # NormalFloat4 (better than regular int4)
    bnb_4bit_compute_dtype=torch.bfloat16,  # Compute in bfloat16 (efficient)
)

# Load model in int4
model = AutoModelForCausalLM.from_pretrained(
    "meta-llama/Llama-2-7b-hf",
    quantization_config=bnb_config,
    device_map="auto"                  # Automatically handles large models
)

# Prepare for training
model = prepare_model_for_kbit_training(model)

# Configure LoRA (same as before)
lora_config = LoraConfig(
    r=8,
    lora_alpha=16,
    target_modules=["q_proj", "v_proj"],
    lora_dropout=0.05,
    bias="none",
    task_type="CAUSAL_LM"
)

# Apply LoRA (on top of quantized model)
model = get_peft_model(model, lora_config)

print(model.print_trainable_parameters())
# trainable params: 4,194,304 || all params: 6,738,415,616 || trainable%: 0.06
# (same as LoRA! but only 64MB LoRA weights needed)

# Training (only requires 4GB GPU memory!)
trainer = Trainer(model=model, ...)
trainer.train()

Key Difference in Code

LoRA:
  - Step 1: Load model in float16
  - Step 2: Apply LoRA
  - Step 3: Train

QLoRA:
  - Step 1: Load model in int4 (quantization config)
  - Step 2: Prepare for training (prepare_model_for_kbit_training)
  - Step 3: Apply LoRA
  - Step 4: Train

The extra "prepare_model_for_kbit_training" step
handles the dequantization and gradient computation efficiently!

Accuracy Comparison

Does Quantization Hurt Quality?

Benchmark: Fine-tune Llama 2 7B on various tasks

Task             Model        Accuracy    vs Original
─────────────────────────────────────────────────────
MMLU             LoRA         45.9%       -1.3% (vs original 47.2%)
                 QLoRA        45.3%       -1.9%
                 Difference:  -0.6%       (minimal!)

ARC-Challenge    LoRA         53.8%       -1.0%
                 QLoRA        53.2%       -1.6%
                 Difference:  -0.6%       (minimal!)

HellaSwag        LoRA         84.1%       -0.9%
                 QLoRA        83.8%       -1.2%
                 Difference:  -0.3%       (very small!)

Perplexity       LoRA         6.02        +0.25
(WikiText2)      QLoRA        6.12        +0.35
                 Difference:  +0.1        (negligible!)

TruthfulQA       LoRA         47.2%       +2.1%
                 QLoRA        46.8%       +1.7%
                 Difference:  -0.4%       (minimal!)

Conclusion:
  - QLoRA loses <1% accuracy compared to LoRA
  - Cost reduction: 97.9% with <1% quality loss!
  - HIGHLY RECOMMENDED trade-off

Why Quality Loss is Minimal

Reasons QLoRA maintains quality:

1. Base Model Frozen:
  - Only adapters are trained (float16, full precision)
  - Quantization doesn't affect gradient computation
  - Gradients computed with full precision!

2. Dequantization During Computation:
  - Base model weights dequantized on-the-fly
  - Computation happens in float16
  - Only stored as int4 (for memory)
  - Like having full precision, just compressed storage

3. Low-Rank Adaptation:
  - LoRA only adapts a small fraction of parameters
  - Adapter precision matters most
  - Base model quantization is secondary
  - Adapters are full precision anyway

4. INT4 NormalFloat (NF4) Quality:
  - Better than standard int4 quantization
  - Preserves distribution of model weights
  - Designed specifically for LLMs
  - Only 0.5-1% quality loss vs float16

Result: Total quality loss ≈ 0.5-1% (acceptable trade-off!)

Performance Comparison

Training Speed

Benchmark: Fine-tune Llama 2 7B on 100K examples

Hardware: Single A100 40GB GPU

                    LoRA            QLoRA           Difference
─────────────────────────────────────────────────────────────────
Max batch size      8               16              2x larger!
Training time       4 hours         3.5 hours       12% faster
Tokens/sec          2500            3000            20% faster
Memory peak         38GB            4GB             90% reduction

Why QLoRA is faster:
1. Larger batch size possible (4→16)
  - More parallelism
  - Better GPU utilization

2. Less memory pressure
  - Smaller gradient buffers
  - Less frequent memory allocation
  - Better cache behavior

3. Simpler operations
  - No need to manage large gradient tensors
  - Faster optimizer updates (only LoRA params)
  - Overall: 12-20% faster than LoRA!

Inference Speed

Benchmark: Generate 256 tokens on Llama 2 7B

                    LoRA            QLoRA           Difference
──────────────────────────────────────────────────────────────
Load time           5 seconds       8 seconds       +60% (one-time)
Inference speed     200 tok/sec     150 tok/sec     -25% slower

Why QLoRA inference is slower:
1. Dequantization overhead
  - Int4 → Float16 conversion needed
  - ~5-10% overhead per forward pass
  - But minimal (handled by fused kernels)

2. Slightly less optimized kernels
  - Standard kernels optimized for float16
  - Quantization-aware kernels still catching up
  - Expected to improve in future versions

Trade-off for inference:
  - Load time: +60% (one-time, not critical)
  - Generation speed: -25% (per-token, but still fast)
  - Use LoRA for inference, QLoRA for training!

Solution: Train with QLoRA, merge and convert to LoRA for inference

Real-World Scenarios

Scenario 1: Budget-Conscious Researcher

Goal: Fine-tune Llama 2 7B on custom dataset
Budget: $100 total
Equipment: Personal laptop with RTX 4090

With LoRA:
  - Hardware needed: 2× A100 GPUs ($1200/day rental)
  - Training cost: $4,800 per 24 hours (4-day training)
  - Total: $19,200 (WAY over budget!)
  - Conclusion: Not feasible ❌

With QLoRA:
  - Hardware: Personal RTX 4090 (~$1600 one-time purchase)
  - Training cost: Free (own hardware)
  - Training time: 48 hours
  - Total: $1600 (initial investment, then free forever)
  - Conclusion: Feasible! ✓

Impact: Makes fine-tuning accessible to everyone!

Scenario 2: Startup MVP Development

Goal: Train multiple specialized models
Models: 3 different Llama 2 7B variants
Timeline: 2 weeks

With LoRA:
  - Cost per model: $4,800 (4-day training, 2× A100)
  - Total cost: $14,400
  - Feasible but expensive

With QLoRA:
  - Cost per model: $0 (using own RTX 4090)
  - Can train sequentially: 3 × 2 days = 6 days
  - Total cost: $0 (only electricity ~$20)
  - Total savings: $14,400!
  - Conclusion: 720x cost reduction!

Timeline advantage:
  - LoRA: 4 days × 3 = need 3 GPU sets (expensive)
  - QLoRA: 2 days × 3 = can do sequentially (free)
  - Feasibility: QLoRA enables this entirely

Scenario 3: Large Company Production System

Goal: Maintain 50 specialized Llama 2 7B models
Scenario: Different models for different use cases
Monthly retraining: Update all models

With LoRA:
  - Models: 50 specialized versions
  - GPU allocation: 25× A100 GPUs (continuous)
  - Monthly cost: $18,000 (retraining all)
  - Yearly cost: $216,000
  - Operational burden: Massive

With QLoRA:
  - Models: 50 specialized versions (same)
  - GPU allocation: 4× A100 GPUs (shared for training)
  - Monthly cost: $1,440 (efficient scheduling)
  - Yearly cost: $17,280
  - Operational burden: Minimal
  - Savings: $198,720/year (92% reduction!)

Plus:
  - Can train models 3x faster (larger batch sizes)
  - Faster iteration = faster innovation

Detailed Comparison Table

Aspect                      LoRA            QLoRA           Winner
─────────────────────────────────────────────────────────────────────
Memory Required             56GB (7B)       4GB (7B)        QLoRA
Cost                        $1200/day       $25/day         QLoRA (48x)
Accuracy Loss               1%              1.5%            LoRA (0.5% diff)
Training Speed              Baseline        12-20% faster   QLoRA
Inference Speed             Baseline        -25%            LoRA
Inference Memory            14GB            3.5GB           QLoRA
Hardware Required           2× A100         1× RTX 4090     QLoRA
Time to Fine-tune (7B)      4 hours         3.5 hours       QLoRA
Batch Size Possible         8               16              QLoRA (2x)
Scalability to 70B          Impractical     Practical!      QLoRA
Quality for Production      99%             98.5%           LoRA (0.5%)
Code Complexity             Simple          Simple          Tie
Maintenance Burden          Low             Low             Tie

Recommendation:
Training phase:     Use QLoRA (47x cheaper, 12% faster)
Inference phase:    Merge to full LoRA (25% faster inference)
Production scale:   Use QLoRA (enables 70B models)
Research/Accuracy:  Use LoRA (marginally better accuracy)

When to Use QLoRA vs LoRA

Use QLoRA When:

✅ Budget is limited (no $ for GPU rentals)
✅ Training on personal hardware (RTX 4090, etc)
✅ Fine-tuning 70B+ models (practical alternative to impractical LoRA)
✅ Need fast iteration (training multiple models)
✅ Accuracy difference of 1-2% is acceptable
✅ Can use merged model for inference (overcome speed penalty)
✅ Startup/academic setting (no GPU budget)

Use LoRA When:

✅ Unlimited GPU budget (enterprise)
✅ Accuracy is critical (financial models, medical)
✅ Inference speed matters (real-time systems)
✅ Already have GPU infrastructure
✅ Training time is not critical
✅ Inference must happen immediately (no merging time)
✅ Standard production systems (not research)

Use LoRA + Merge Strategy When (Best of Both):

Training: QLoRA (cheap, fast, 4GB memory)
  - Train on consumer GPU
  - Iterate quickly
  - Cost: $20-50 total

Deployment: Merge + LoRA format (or full model)
  - Merge for production (inference)
  - Enjoy full speed (no dequantization overhead)
  - No inference penalty
  - Cost: $0 (already trained)

Best approach: Train with QLoRA, deploy with merged LoRA

Backward Compatibility and Merging

Merging QLoRA Weights

# After QLoRA training, merge weights

from peft import AutoPeftModelForCausalLM

# Load QLoRA model
model = AutoPeftModelForCausalLM.from_pretrained(
    "./qlora-checkpoint"
)

# Merge LoRA weights into base model
merged_model = model.merge_and_unload()

# Now it's a regular model (no QLoRA overhead)
merged_model.save_pretrained("./merged-model")

# Inference with merged model:
# - No dequantization needed during inference
# - Full speed (no 25% penalty)
# - Can quantize to int4 separately if needed
# - No LoRA adapter overhead

Performance After Merging

QLoRA Training → Merged Model Inference

Inference speed comparison:

Standard LoRA inference:     200 tok/sec
QLoRA inference (merged):    200 tok/sec (2.8x faster than during QLoRA training!)

Why?
1. LoRA adapters no longer applied at inference time
2. Full model can use more optimized kernels
3. No dequantization overhead if already merged
4. Back to standard model performance

Strategy:
  - Train: QLoRA (slow, cheap)
  - Merge: Combine LoRA with base
  - Deploy: Full speed inference (no penalty)

Result: Best of both worlds!

Key Takeaways

🔑 QLoRA enables training 70B models on single consumer GPU
💾 93% memory reduction vs standard LoRA (56GB → 4GB)
💰 97.9% cost reduction ($1200/day → $25/day)
12-20% faster training (larger batch sizes)
📊 Only 1-2% accuracy loss (acceptable trade-off)
🎯 Perfect for researchers, startups, and accessible AI


Comparison Summary

Aspect LoRA QLoRA Trade-off
Memory 56GB 4GB 93% savings
Cost $1200/day $25/day 97.9% savings
Accuracy 100% (baseline) 98.5% -1.5%
Training Speed Baseline +12-20% Faster
Inference Speed Baseline -25% Merge for speed
70B Model Impractical Practical QLoRA wins
Hardware 2× A100 1× RTX 4090 QLoRA accessible

Further Reading