Run Your First QLoRA Script
Write and run your first QLoRA training script with hands-on steps, troubleshooting tips, and what to learn next.
Focus: run your first qlora training script
Picture this: it's late Friday night, your dataset is cleaned, your GPU is humming, and you're about to fine-tune a 7-billion-parameter model. You've read that QLoRA is the magic bullet for efficient fine-tuning, but when you try to run a script, you hit a wall of cryptic errors, CUDA out-of-memory messages, and confusion about which parameters actually matter. If that sounds familiar, you're in the right place. This lesson walks you through running your first QLoRA training script step by step, so you can go from zero to a tuned model without the all-night debug session.
The problem this lesson solves
Fine-tuning a large language model (LLM) is often a game of trade-offs. Full fine-tuning of a 7B model requires a GPU with massive VRAM—think 60+ GB—which is out of reach for most developers, researchers, and startups. Even when you have the hardware, training is slow, expensive, and prone to catastrophic forgetting, where the model forgets its general knowledge while learning your specific task.
QLoRA (Quantized Low-Rank Adaptation) solves these problems by combining two powerful ideas: quantization (shrinking the model's memory footprint) and low-rank adaptation (training a tiny set of parameters instead of all of them). With QLoRA, you can fine-tune a 7B model on a single 24 GB consumer GPU—like an RTX 3090 or 4090—or even a 16 GB laptop GPU in some cases. This approach makes fine-tuning accessible, but it also introduces a new set of pitfalls: you need to configure the right quantization settings, choose the right LoRA rank, and manage your training loop properly.
If you've ever tried to write a QLoRA script from scratch, you know the pain: mismatched model versions, incorrect tokenizer settings, and subtle bugs that only appear after hours of training. This lesson cuts through the noise. You'll learn not only how to write and run a QLoRA training script, but also how to debug it when things go wrong—and why QLoRA is the go-to method for most fine-tuning tasks.
Core concept / mental model
Think of fine-tuning an LLM as teaching a new skill to a world-class chef. Full fine-tuning retrains every neuron in the chef's brain—expensive, slow, and you might lose their existing expertise. LoRA is like giving the chef a small, specialized notebook where they jot down new recipes and techniques without rewriting their entire culinary knowledge. The chef keeps their brain (the original weights) frozen, but they add a few extra notes (the LoRA adapters) that help them cook your specific dishes.
QLoRA takes this a step further. Instead of keeping the original weights in full precision (32-bit floats), QLoRA quantizes them to 4-bit or 8-bit integers, drastically reducing memory usage. The LoRA adapters, however, remain in high precision (usually 16-bit), so you still get accurate gradients during training. The result is a model that's roughly 4x smaller in memory but still capable of high-quality fine-tuning.
Here are the key components you'll encounter in a QLoRA script:
- Base model: The pretrained LLM you're adapting, e.g.,
meta-llama/Llama-2-7b-hformistralai/Mistral-7B-v0.1. - Tokenizer: Converts text into tokens (and back), handling padding and truncation.
- Quantization config: Parameters like
load_in_4bit=True,bnb_4bit_quant_type='nf4', andbnb_4bit_compute_dtype=torch.float16that control how the model is quantized. - LoRA config: Parameters like
r(rank),alpha(scaling factor), andtarget_modulesthat define the adapters. - Training arguments: A
TrainingArgumentsobject that controls learning rate, batch size, number of epochs, and logging. - Trainer: The
Trainer(orSFTTrainer) that orchestrates the training loop.
Pro tip: Think of QLoRA as high-precision surgery. The original model is frozen in a compressed form, and you're only fine-tuning a tiny set of parameters—the adapters—that capture the task-specific patterns.
How it works step by step
Here's the logical flow from raw data to trained model:
- Load the base model and tokenizer: Use
AutoModelForCausalLMandAutoTokenizerfrom Hugging Face Transformers, along with a quantization config. - Apply quantization: The model is loaded in 4-bit using
bitsandbytes, which compresses the weights and reduces memory. - Prepare the data: Format your dataset into a single text column, e.g.,
text, that the tokenizer can process. Handle padding and truncation to keep sequences short. - Configure LoRA: Define a
LoraConfigwith the rankr, alpha, and target modules (typically attention layers likeq_proj,v_proj). - Set up the trainer: Use
SFTTrainer(supervised fine-tuning) orTrainerwith a data collator, training arguments, and the LoRA adapter model. - Train: Run the training loop, monitor loss, and save checkpoints.
- Save and merge: Save the adapter weights, and optionally merge them back into the base model for inference.
Each step depends on the previous one. If your quantization config is wrong, your training loop will fail. If your dataset isn't formatted correctly, you'll get poor results. This step-by-step flow is the foundation of any QLoRA script.
Hands-on walkthrough
Let's write and run your first QLoRA training script. We'll use the popular transformers, peft, and bitsandbytes libraries. Install them first:
pip install transformers datasets peft bitsandbytes accelerate
Now, create a Python file named qlora_train.py. Here's a complete script that fine-tunes a small model (e.g., microsoft/phi-2 or bigscience/bloom-560m) on a toy dataset. We'll use a tiny model so you can run it on almost any GPU.
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer, TrainingArguments, BitsAndBytesConfig
from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training
from trl import SFTTrainer
from datasets import Dataset
# 1. Load tokenizer
model_id = "microsoft/phi-2" # or "gpt2" for an even faster test
tokenizer = AutoTokenizer.from_pretrained(model_id)
tokenizer.pad_token = tokenizer.eos_token
# 2. Quantization config
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.float16,
bnb_4bit_use_double_quant=True,
)
# 3. Load base model in 4-bit
model = AutoModelForCausalLM.from_pretrained(
model_id,
quantization_config=bnb_config,
device_map="auto",
trust_remote_code=True,
)
model = prepare_model_for_kbit_training(model)
# 4. LoRA config
lora_config = LoraConfig(
r=8, # rank
lora_alpha=16, # scaling factor
target_modules=["q_proj", "v_proj"], # modules to apply LoRA to
lora_dropout=0.05,
bias="none",
task_type="CAUSAL_LM",
)
model = get_peft_model(model, lora_config)
model.print_trainable_parameters() # Should show a tiny % of trainable params
# 5. Toy dataset (replace with your own)
data = [
{"text": "Question: What is the capital of France?\nAnswer: Paris"},
{"text": "Question: What is 2+2?\nAnswer: 4"},
{"text": "Question: What is the largest planet?\nAnswer: Jupiter"},
]
dataset = Dataset.from_list(data)
# 6. Training arguments
training_args = TrainingArguments(
output_dir="./qlora-output",
per_device_train_batch_size=1,
gradient_accumulation_steps=4,
num_train_epochs=3,
learning_rate=2e-4,
fp16=True,
logging_steps=10,
save_steps=50,
report_to="none",
)
# 7. Trainer
trainer = SFTTrainer(
model=model,
args=training_args,
train_dataset=dataset,
tokenizer=tokenizer,
dataset_text_field="text",
max_seq_length=256,
packing=True,
peft_config=lora_config,
)
# 8. Train!
trainer.train()
trainer.save_model("./qlora-adapter")
Expected output: You should see logs showing the number of trainable parameters, a training loss that decreases each step, and a final model saved in ./qlora-adapter. For example:
trainable params: 8,388,608 || all params: 2,789,767,168 || trainable%: 0.3006%
Step 10: loss=0.82, lr=2e-4
...
Training complete!
Pro tip: If you don't have a GPU, you can still run this on a CPU with
device_map="cpu"andfp16=False— it'll be slow but works for learning.
Compare options / when to choose what
Now, you might wonder: when should I use QLoRA versus plain LoRA or full fine-tuning? Here's a quick comparison:
| Method | Memory footprint | Training speed | Quality | When to use |
|---|---|---|---|---|
| Full fine-tuning | Very high (60+ GB for 7B) | Slow | Highest | When you have enterprise GPUs and need max performance |
| LoRA (16-bit) | Moderate (15-20 GB for 7B) | Medium | High | When you have a decent GPU and need some speed |
| QLoRA (4-bit) | Low (5-10 GB for 7B) | Fast | High | When you want to fine-tune on a single consumer GPU |
For most developers, QLoRA is the sweet spot because it lets you experiment with large models without a big budget. The quality loss compared to full fine-tuning is often negligible, especially for instruction tuning and task-specific adaptation.
Another comparison within QLoRA: nf4 vs. fp4 quantization. nf4 (normal float 4) is designed for neural networks and generally yields better accuracy, while fp4 is simpler but slightly less accurate. We'll stick with nf4 in this tutorial.
Troubleshooting & edge cases
Here are the most common pitfalls when running your first QLoRA script and how to fix them:
- CUDA out of memory: Reduce batch size, use gradient accumulation, or lower
max_seq_length. Also ensureload_in_4bit=Trueanddevice_map="auto"are set. bitsandbytesnot found: Make sure you have a compatible version installed. On Windows, you may need to installbitsandbytes-windowsor use WSL.- Tokenizer pad token error: Set
tokenizer.pad_token = tokenizer.eos_tokenbefore training. ValueError: Please specifytarget_modules: Check your model's architecture. For some models, you need to list all attention modules (e.g.,q_proj,k_proj,v_proj,o_proj`).- Loss doesn't decrease: This could be due to a too-high learning rate, wrong dataset format, or not enough epochs. Try lowering the learning rate to 1e-4 or 2e-4 and verify your dataset has proper
textfield. AttributeError: 'PeftModel' object has no attribute 'print_trainable_parameters': Make sure you callget_peft_modeland then usemodel.print_trainable_parameters(). It should work after that.
Pro tip: Always test with a tiny dataset and a small model (e.g.,
gpt2) first to verify your script works before running on a large model.
What you learned & what's next
You've just successfully run your first QLoRA training script! Let's recap the key takeaways:
- You understand how quantization reduces memory footprint while LoRA adapters capture task-specific patterns.
- You can write a complete training script using
transformers,peft, andbitsandbytes. - You know how to configure
BitsAndBytesConfigandLoraConfigfor optimal performance. - You can debug common issues like CUDA OOM and tokenizer pad errors.
- You're ready to apply this knowledge to your own dataset and fine-tune a model for your specific use case.
As a next step, you'll learn how to evaluate your fine-tuned model and measure its performance on a held-out validation set. This is crucial to ensure your model isn't just memorizing the training data but actually generalizing to new inputs. You'll also learn how to deploy your fine-tuned model to production, integrating it into an API or an application.
Before moving on, try this mini-exercise: Use your own dataset (e.g., a few hundred examples of customer support conversations) and run the script above. Experiment with different ranks (r=4, r=16) and compare the training loss. This hands-on practice will solidify your understanding of QLoRA and prepare you for the next lesson on evaluation.
Practice recap
Now it’s your turn. Modify the script to use a larger model like meta-llama/Llama-2-7b-hf (if you have GPU memory) or stick with phi-2. Change the LoRA rank from 8 to 4 and observe the difference in trainable parameters and training loss. This hands-on comparison will show you how rank affects model capacity and training speed. When you're comfortable, move on to the next lesson on evaluation techniques.
Common mistakes
- Forgetting to set
tokenizer.pad_token = tokenizer.eos_tokenleads to a pad token error during training — always set it. - Using
load_in_4bit=Truewithoutdevice_map="auto"can cause memory issues on multi-GPU setups. - Setting
target_modulesto wrong layers (e.g.,["attn"]instead of["q_proj", "v_proj"]) results in a ValueError — check the model's architecture. - Running with
fp16=Trueon a CPU-only machine causes an error — disable FP16 when no GPU is available.
Variations
- Use
gptqquantization as an alternative to 4-bit NF4, which offers lower memory but may require more preprocessing. - Try
qlora.pyfrom theqlorarepository for a ready-made script that supports multi-GPU and Alpaca-style datasets. - Opt for
SFTTrainerwithpacking=Trueto concatenate short sequences and fill the context window, improving throughput.
Real-world use cases
- Fine-tune a 7B LLM for customer support on a single RTX 3090 to classify and generate responses, reducing infrastructure costs.
- Adapt a code LLM like CodeLlama to recognize internal coding standards for automated code review within a CI pipeline.
- Customize a small open-source model to answer medical FAQs for a patient-facing app without sending sensitive data to external APIs.
Key takeaways
- QLoRA combines 4-bit quantization with low-rank adapters to fine-tune large models on consumer GPUs.
- The core components are the base model, tokenizer,
BitsAndBytesConfig,LoraConfig, and theSFTTrainer. - Always set the tokenizer's pad token and use
device_map="auto"to avoid memory and tokenizer errors. - Start with a small model and tiny dataset to validate your script before scaling up to larger models.
- Monitor training loss and adjust hyperparameters like learning rate and rank for optimal results.
- Next, learn to evaluate your model's performance and deploy it for real-world use.