Skip to main content
Start your own AI-powered blog — freeGet started →

Fine-Tune Llama 3.1 8B with QLoRA on a Consumer GPU

Fine-Tune Llama 3.1 8B with QLoRA on a Consumer GPU
Photo by DΛVΞ GΛRCIΛ on pexels

Fine-Tune Llama 3.1 8B with QLoRA on a Consumer GPU

Two llamas in the Andes mountains with scenic views of Cotopaxi volcano. Photo by DΛVΞ GΛRCIΛ on Pexels

Quick Answer: You can fine-tune Llama 3.1 8B on any GPU with 8GB+ VRAM using QLoRA. With a RTX 3090/4090/5090 (24GB+), you can fine-tune at 4-bit quantization, batch size 4-8, sequence length 2048, completing most datasets in 1-8 hours. The key tools: transformers, peft, bitsandbytes, and trl (TRL) from Hugging Face. Expected cost: $0 (your own GPU) or ~$2-5 on RunPod/Vast.ai for a full training run. This tutorial covers dataset preparation, LoRA config, training, saving, and deployment.

What You Need (Hardware + Software)

Minimum Hardware

GPUVRAMWorks?Batch SizeTraining Time (1K samples)
RTX 306012 GB2-4~4-6 hours
RTX 309024 GB4-8~2-3 hours
RTX 409024 GB4-8~1.5-2.5 hours
RTX 509032 GB8-16~1-2 hours
M4 MaxUnified2-4~3-5 hours
Free Colab T416 GB⚠️ Limited1-2~8-10 hours (may disconnect)

Software Stack

code
Python 3.10+
PyTorch 2.4+ (CUDA 12.1+)
transformers 4.45+
peft 0.12+
bitsandbytes 0.44+
trl 0.10+
datasets 2.20+
accelerate 0.32+

Step 1: Install Dependencies

bash
# Create environment
python -m venv llama-finetune
source llama-finetune/bin/activate  # or .\venv\Scripts\activate on Windows

# Install core packages
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu124
pip install transformers peft bitsandbytes trl accelerate datasets
pip install wandb     # optional: for experiment tracking
pip install xformers  # optional: memory-efficient attention

Verify GPU Access

python
import torch
print(f"CUDA available: {torch.cuda.is_available()}")
print(f"GPU: {torch.cuda.get_device_name(0)}")
print(f"VRAM: {torch.cuda.get_device_properties(0).total_memory / 1e9:.1f} GB")

Step 2: Prepare Your Dataset

Format: Conversational (ChatML)

The recommended format for Llama 3.1 is the ChatML format:

json
{
  "messages": [
    {"role": "system", "content": "You are a helpful assistant specialized in Python programming."},
    {"role": "user", "content": "Write a Python function to reverse a linked list."},
    {"role": "assistant", "content": "Here's a Python function to reverse a linked list..."}
  ]
}

Load Your Dataset

python
from datasets import load_dataset

# Option 1: Load from Hugging Face Hub
dataset = load_dataset("your-username/your-dataset", split="train")

# Option 2: Load from local JSONL file
dataset = load_dataset("json", data_files="my_data.jsonl", split="train")

# Option 3: Create from list of dicts
dataset = load_dataset(
    "json",
    data_files=[{"train": "train.jsonl", "test": "test.jsonl"}]
)

# Format messages for the model
def format_chat(example):
    """Convert messages list to a single string with ChatML format."""
    formatted = ""
    for msg in example["messages"]:
        if msg["role"] == "system":
            formatted += f"<|begin_of_text|><|start_header_id|>system<|end_header_id|>
{msg['content']}<|eot_id|>
"
        elif msg["role"] == "user":
            formatted += f"<|start_header_id|>user<|end_header_id|>
{msg['content']}<|eot_id|>
"
        elif msg["role"] == "assistant":
            formatted += f"<|start_header_id|>assistant<|end_header_id|>
{msg['content']}<|eot_id|>
"
    return {"text": formatted}

dataset = dataset.map(format_chat)

# Split into train/test
dataset = dataset.train_test_split(test_size=0.1, seed=42)
train_dataset = dataset["train"]
eval_dataset = dataset["test"]

print(f"Train: {len(train_dataset)} samples, Test: {len(eval_dataset)} samples")

Step 3: Load the Model in 4-bit

python
import torch
from transformers import (
    AutoModelForCausalLM,
    AutoTokenizer,
    BitsAndBytesConfig,
    TrainingArguments,
)
from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training
from trl import SFTTrainer

# 4-bit quantization config
bnb_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_quant_type="nf4",          # NormalFloat4 (optimal for 4-bit)
    bnb_4bit_use_double_quant=True,     # Double quantization (saves ~0.5 GB)
    bnb_4bit_compute_dtype=torch.bfloat16,  # Compute in bf16
)

# Load model
model_name = "meta-llama/Llama-3.1-8B-Instruct"
model = AutoModelForCausalLM.from_pretrained(
    model_name,
    quantization_config=bnb_config,
    device_map="auto",              # Automatic GPU placement
    trust_remote_code=True,
    use_flash_attention_2=True,     # Faster training, needs xformers
)

# Load tokenizer
tokenizer = AutoTokenizer.from_pretrained(model_name)
tokenizer.pad_token = tokenizer.eos_token
tokenizer.padding_side = "right"    # Important for Llama models

# Prepare model for k-bit training
model = prepare_model_for_kbit_training(model)

Memory Check After Loading

bash
Model loaded in 4-bit: ~5.5 GB VRAM
With Python overhead: ~6.5 GB
Remaining for training: ~17.5 GB (on 24GB GPU)

Step 4: Configure LoRA

python
# LoRA configuration
lora_config = LoraConfig(
    r=16,                           # Rank: 8-64 typical. Higher = more expressiveness, more memory
    lora_alpha=32,                  # Scaling factor (often 2x rank)
    target_modules=[                 # Which modules to apply LoRA to
        "q_proj",
        "k_proj",
        "v_proj",
        "o_proj",
        "gate_proj",
        "up_proj",
        "down_proj",
    ],
    lora_dropout=0.05,              # Regularization
    bias="none",
    task_type="CAUSAL_LM",
)

# Apply LoRA to the model
model = get_peft_model(model, lora_config)
model.print_trainable_parameters()
# Expected output: trainable params: ~42M / 8B = ~0.5% of total params

LoRA Rank Guide

RankTrainable ParamsMemory OverheadQualityUse Case
r=8~21MLowGoodSmall datasets, simple tasks
r=16~42MMediumBetterDefault — works for most
r=32~84MHigherBestComplex tasks, larger datasets
r=64~168MHighMarginalUsually overkill for 8B

Woman exploring Lightroom tutorials on computer screen, perfect for online education themes. Photo by Ravi Kant on Pexels

Step 5: Training Arguments and Run

python
# Training arguments
training_args = TrainingArguments(
    output_dir="./llama-8b-qlora-output",
    num_train_epochs=3,                     # Number of passes over data
    per_device_train_batch_size=4,          # Adjust based on VRAM
    per_device_eval_batch_size=2,
    gradient_accumulation_steps=4,          # Effective batch = 4×4=16
    gradient_checkpointing=True,            # Saves VRAM at cost of speed
    optim="paged_adamw_8bit",               # 8-bit optimizer (saves VRAM)
    logging_steps=10,
    learning_rate=2e-4,                     # Typical for LoRA
    weight_decay=0.001,
    fp16=False,
    bf16=True,                              # Use bf16 if supported (H100, 5090)
    max_grad_norm=0.3,
    warmup_ratio=0.03,
    lr_scheduler_type="cosine",
    evaluation_strategy="steps",
    eval_steps=50,
    save_strategy="steps",
    save_steps=100,
    save_total_limit=3,
    report_to="wandb",                      # Or "none" to disable
    run_name="llama-8b-qlora-run-1",
)

# Initialize trainer
trainer = SFTTrainer(
    model=model,
    tokenizer=tokenizer,
    args=training_args,
    train_dataset=train_dataset,
    eval_dataset=eval_dataset,
    max_seq_length=2048,                    # Max context length for training
    dataset_text_field="text",              # Field containing formatted text
    packing=False,                          # Packing = faster but less controlled
)

# Start training
trainer.train()

GPU Memory During Training

Batch SizeContext LengthVRAM UsageGPU Needed
22048~12 GBRTX 3060 12GB
42048~16 GBRTX 3090/4090
82048~22 GBRTX 5090
44096~20 GBRTX 4090/5090
84096~30 GB2x GPU or RTX 5090

Step 6: Save and Merge

Save LoRA Adapter (Small, Fast)

python
# Save just the LoRA adapter weights (~42 MB)
model.save_pretrained("./llama-8b-qlora-adapter")
tokenizer.save_pretrained("./llama-8b-qlora-adapter")

Merge with Base Model (Full Model, Slow)

python
from peft import PeftModel

# Load base model in full precision for merge
base_model = AutoModelForCausalLM.from_pretrained(
    "meta-llama/Llama-3.1-8B-Instruct",
    torch_dtype=torch.bfloat16,
    device_map="auto",
)

# Load LoRA adapter
merged_model = PeftModel.from_pretrained(base_model, "./llama-8b-qlora-adapter")

# Merge and unload
merged_model = merged_model.merge_and_unload()

# Save merged model (full ~16 GB for 8B FP16)
merged_model.save_pretrained("./llama-8b-merged")
tokenizer.save_pretrained("./llama-8b-merged")

Step 7: Deploy with Ollama

Convert to GGUF and Run Locally

bash
# 1. Convert merged model to GGUF
pip install llama-cpp-python
python convert_hf_to_gguf.py ./llama-8b-merged \
    --outfile ./llama-8b-merged-q4_k_m.gguf \
    --outtype q4_k_m

# Or use llama.cpp's convert script
git clone https://github.com/ggerganov/llama.cpp
cd llama.cpp
python convert.py ../llama-8b-merged
./quantize ../llama-8b-merged/ggml-model-f16.gguf \
    ../llama-8b-merged-q4_k_m.gguf q4_k_m

# 2. Create Ollama model
# Create Modelfile:
cat > Modelfile << EOF
FROM ./llama-8b-merged-q4_k_m.gguf
TEMPLATE """{{ .System }}
{{ .Prompt }}"""
PARAMETER temperature 0.7
PARAMETER top_p 0.9
EOF

# 3. Create and run
ollama create my-finetuned-model -f Modelfile
ollama run my-finetuned-model

Deploy with vLLM (Production)

bash
# Serve the merged model with vLLM
python -m vllm.entrypoints.openai.api_server \
    --model ./llama-8b-merged \
    --dtype bfloat16 \
    --max-model-len 4096 \
    --gpu-memory-utilization 0.90

# Or serve the LoRA adapter on top of base model
python -m vllm.entrypoints.openai.api_server \
    --model meta-llama/Llama-3.1-8B-Instruct \
    --enable-lora \
    --lora-modules my-adapter=./llama-8b-qlora-adapter

Expected Training Times by GPU

1,000 Training Samples, 3 Epochs, Seq Length=2048

GPUBatch SizeTime to TrainCost (Cloud)
RTX 3060 (12 GB)2~5 hours~$2.50 on Vast.ai
RTX 3090 (24 GB)4~2.5 hours~$1.50 on Vast.ai
RTX 4090 (24 GB)4~2 hours~$2.00 on RunPod
RTX 5090 (32 GB)8~1.5 hoursOwned
H10016~30 minutes~$1.50 on RunPod

Scaling Up

Dataset SizeEpochsRTX 3090RTX 4090RTX 5090
500 samples3~1 hour~45 min~30 min
1,000 samples3~2.5 hours~2 hours~1.5 hours
5,000 samples3~12 hours~9 hours~6 hours
10,000 samples3~24 hours~18 hours~12 hours

Common Pitfalls and Solutions

ProblemCauseSolution
CUDA Out of MemoryBatch size too largeReduce per_device_batch_size to 2 or 1, enable gradient_checkpointing
Loss goes to NaNLearning rate too highReduce learning_rate to 1e-4 or 5e-5
Model repeats same textOverfitting on small datasetReduce epochs to 1-2, add weight decay
Training too slowUnoptimized attentionInstall xformers, enable flash_attention_2
Tokenizer warningsPadding sideSet tokenizer.padding_side="right"
LoRA not learningWrong target modulesInclude all projection layers: q, k, v, o, gate, up, down

Related Reads

Key Takeaways

  • Use QLoRA with 4-bit quantization to fine-tune Llama 3.1 8B on GPUs with 8GB+ VRAM—RTX 3090/4090/5090 enables batch sizes 4-8 at 2048 sequence length, completing 1K samples in 1.5-3 hours with bitsandbytes, peft, and trl libraries.
  • Format datasets in ChatML with <|start_header_id|> and <|eot_id|> tokens (e.g., {"messages": [{"role": "system", "content": "..."}, ...]}), then map to a single text field for training using dataset.map(format_chat).
  • Configure LoRA with r=16, lora_alpha=32, and target modules q_proj, k_proj, v_proj, o_proj, gate_proj, up_proj, down_proj to balance memory (42M trainable params) and quality—higher ranks (e.g., 32) suit complex tasks but increase VRAM usage.
  • Set training arguments with gradient_accumulation_steps=4, gradient_checkpointing=True, bf16=True, and optim="paged_adamw_8bit" to optimize VRAM; use per_device_train_batch_size=4 on 24GB GPUs for stable training at ~16GB VRAM usage.
  • Save the LoRA adapter (~42MB) for lightweight deployment or merge it with the base model (16GB FP16) for standalone use—convert to GGUF for Ollama or serve via vLLM with --enable-lora for multi-adapter support.
  • Troubleshoot common issues: reduce batch size/learning rate (e.g., 2e-4) for OOM/NaN loss, enable xformers for faster attention, and ensure tokenizer.padding_side="right" to avoid tokenizer warnings.

Frequently Asked Questions

Can I fine-tune Llama 3.1 8B on a 8GB GPU?

With QLoRA 4-bit and batch size 1, yes. You'll need gradient checkpointing and may need to use gradient accumulation of 4+ to maintain effective batch size. Training will be slow (1-2 samples/second) but it works.

How much data do I need for fine-tuning?

100-500 high-quality examples for noticeable improvement in a specific task. 1,000-5,000 for significant behavioral change. Quality matters more than quantity — 200 curated examples beats 10,000 noisy ones.

Should I merge the LoRA adapter or keep it separate?

Keep it separate for development (fast upload/download, low storage). Merge it for deployment (single model file, no dependency on adapter loading). Use vLLM's native LoRA support if you want to serve multiple adapters from one base model.

What's the difference between full fine-tune and LoRA?

Full fine-tune updates all 8B parameters (needs 16x more VRAM). LoRA updates ~42M parameters (0.5%). LoRA achieves 70-95% of full fine-tune quality depending on the task and dataset size. For consumer GPUs, use LoRA.

Can I fine-tune on cloud GPUs instead of buying one?

Yes — RunPod, Vast.ai, Lambda Labs, and Google Colab all have suitable GPUs. A full QLoRA training run on an RTX 4090 costs ~$2-5. This is the most cost-effective approach if you fine-tune infrequently.

S
Synor

1 followers

Deep dives on GPUs, decentralized AI, crypto, and open-source ML — buying guides, benchmarks, and tax/compliance explainers.

Comments

Sign in to join the conversation

No comments yet. Be the first to share your thoughts!

More from Synor

Recommended for you