2024-11-05 · 3 min read
QLoRA: 4-bit Training on Modest GPUs
QLoRA makes fine-tuning practical on consumer hardware by quantizing the base model to 4 bits while keeping the LoRA matrices in higher precision. The result is a dramatic reduction in memory footprint with negligible quality loss.
How QLoRA works
Instead of loading a 7B model in fp16 (14 GB), QLoRA loads it in NF4 (4-bit normalized float), using only ~5 GB. The frozen weights stay quantized. The LoRA matrices stay in bf16, so gradients and optimizer state remain high precision where it matters.
The math: a 7B model in fp16 needs ~28 GB for gradients and ~56 GB for AdamW optimizer state. With 4-bit quantization, the base model alone drops to ~2 GB, leaving room for training on a 12-16 GB card.
Memory requirements by model size
| Base model | FP16 | INT8 | NF4 | NF4 + gradients + optimizer |
|---|---|---|---|---|
| 7B | 14 GB | 8 GB | 5 GB | ~8–9 GB total |
| 8B | 16 GB | 9 GB | 5.5 GB | ~9–10 GB total |
| 13B | 26 GB | 15 GB | 9 GB | ~13–14 GB total |
An RTX 3060 (12 GB) can train a 7B LoRA with QLoRA comfortably; an RTX 4090 can handle 13B.
Setup with bitsandbytes
The implementation relies on bitsandbytes for 4-bit quantization. Here's a complete example:
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
from peft import LoraConfig, get_peft_model
from trl import SFTTrainer, TrainingArguments
# 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, # nested quantization
)
# Load base model quantized
model = AutoModelForCausalLM.from_pretrained(
"Qwen/Qwen2.5-7B-Instruct",
quantization_config=bnb_config,
device_map="auto",
)
# LoRA config
lora_config = LoraConfig(
r=8,
lora_alpha=16,
target_modules=["q_proj", "v_proj"],
lora_dropout=0.05,
bias="none",
task_type="CAUSAL_LM",
)
model = get_peft_model(model, lora_config)
# Training
trainer = SFTTrainer(
model=model,
args=TrainingArguments(
output_dir="./qlora-output",
num_train_epochs=3,
per_device_train_batch_size=1,
gradient_accumulation_steps=8,
learning_rate=2e-4,
lr_scheduler_type="cosine",
),
train_dataset=dataset,
)
trainer.train()
Quality impact
Training a Qwen2.5-7B adapter with QLoRA vs fp16 LoRA on the same dataset typically shows <1% downstream task degradation. For style and tone adapters, the difference is imperceptible. For knowledge-intensive tasks (e.g., structured extraction), the gap may be slightly larger but still acceptable.
The trade-off is worth it: 2–3x faster training, 6–7x lower VRAM usage, zero loss in released adapter quality.
When QLoRA is overkill
If your base model is already small (2–3B parameters), the quantization overhead becomes noticeable. A 3B model in fp16 is only 6 GB; standard LoRA fits on most GPUs without tricks.
For very narrow adapters where you're sensitive to every bit of fidelity (e.g., legal document extraction), fp16 LoRA is still safer.
Deployment note
A QLoRA adapter is just a LoRA adapter — it's distribution-agnostic. Once trained, it loads into a full-precision base model at inference time with no quality penalty. The quantization is only for training efficiency.
For ModelForgeLab, QLoRA adapters are marked with a badge during the training phase, but the released artifact is standard LoRA. The distinction helps users understand that a particular adapter was trained frugally on consumer hardware without compromising quality.
Practical command
mfl train qwen25-7b-support-tone \
--method qlora \
--rank 8 \
--epochs 3 \
--batch-size 1 \
--gradient-accumulation 8 \
--upload data/support_pairs.jsonl
QLoRA removes the barrier to adapter training. A laptop with an RTX 3060 can now train adapters that were previously possible only on A100s. That democratization is the point.