Fine-Tuning LLMs on Consumer Hardware: LoRA Guide 2024

Train custom models on your GPU using LoRA/QLoRA — no cloud credits needed.

Introduction

Fine-tuning used to require A100s and thousands of dollars. With LoRA (Low-Rank Adaptation) and QLoRA (Quantized LoRA), you can fine-tune 7B-70B models on a single 24GB GPU (RTX 3090/4090) or even 12GB with quantization.

What is LoRA?

LoRA freezes the base model and trains only small adapter matrices:

Base Model (frozen) + LoRA Adapters (trainable) = Fine-tuned Model 

Benefits:

  • 100-1000x fewer trainable parameters
  • Fast training (minutes to hours)
  • Small checkpoint files (MBs not GBs)
  • Mergeable back into base model

Hardware Requirements

Model Size Method VRAM Needed GPU Example
7B LoRA (fp16) ~16 GB RTX 3090/4090
7B QLoRA (4-bit) ~8 GB RTX 3060 12GB
13B QLoRA (4-bit) ~12 GB RTX 3080 12GB
33B QLoRA (4-bit) ~20 GB RTX 3090/4090
70B QLoRA (4-bit) ~40 GB Dual 3090/4090

Setup Environment

# Create environment conda create -n lora python=3.10 -y conda activate lora # Install dependencies pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121 pip install transformers peft trl accelerate bitsandbytes datasets wandb 

Prepare Dataset

// data/train.jsonl {"text": "### Instruction:\nWrite a Python function to reverse a string\n### Response:\ndef reverse_string(s):\n return s[::-1]"} {"text": "### Instruction:\nExplain what a decorator does in Python\n### Response:\nA decorator is a function that takes another function and extends its behavior..."} 

Format: Alpaca/ShareGPT style with ### Instruction: and ### Response: markers.

Training Script (QLoRA)

# train_lora.py import os import torch from datasets import load_dataset from transformers import ( AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig, TrainingArguments ) from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training from trl import SFTTrainer # Config MODEL_ID = "mistralai/Mistral-7B-v0.1" # or "meta-llama/Llama-2-7b-hf" OUTPUT_DIR = "./mistral-7b-code-lora" DATA_PATH = "data/train.jsonl" # 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 model = AutoModelForCausalLM.from_pretrained( MODEL_ID, quantization_config=bnb_config, device_map="auto", trust_remote_code=True ) model = prepare_model_for_kbit_training(model) # LoRA config lora_config = LoraConfig( r=64, # Rank (higher = more capacity)  lora_alpha=16, # Scaling factor  target_modules=["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"], lora_dropout=0.05, bias="none", task_type="CAUSAL_LM" ) model = get_peft_model(model, lora_config) model.print_trainable_parameters() # Trainable params: ~0.5% of total  # Tokenizer tokenizer = AutoTokenizer.from_pretrained(MODEL_ID, trust_remote_code=True) tokenizer.pad_token = tokenizer.eos_token tokenizer.padding_side = "right" # Dataset dataset = load_dataset("json", data_files=DATA_PATH, split="train") # Training arguments training_args = TrainingArguments( output_dir=OUTPUT_DIR, per_device_train_batch_size=2, gradient_accumulation_steps=4, warmup_steps=10, max_steps=100, # Adjust for your dataset  learning_rate=2e-4, fp16=True, logging_steps=5, save_strategy="steps", save_steps=50, report_to="wandb", # or "none"  optim="paged_adamw_8bit" ) # Trainer trainer = SFTTrainer( model=model, train_dataset=dataset, dataset_text_field="text", max_seq_length=512, tokenizer=tokenizer, args=training_args, packing=False ) # Train trainer.train() # Save trainer.model.save_pretrained(OUTPUT_DIR) tokenizer.save_pretrained(OUTPUT_DIR) print(f"Model saved to {OUTPUT_DIR}") 

Run Training

# Single GPU python train_lora.py # Multi-GPU (if available) accelerate launch --multi_gpu train_lora.py 

Merge & Export

# merge_lora.py from peft import PeftModel from transformers import AutoModelForCausalLM, AutoTokenizer import torch BASE_MODEL = "mistralai/Mistral-7B-v0.1" LORA_PATH = "./mistral-7b-code-lora" OUTPUT_PATH = "./mistral-7b-code-merged" # Load base model = AutoModelForCausalLM.from_pretrained( BASE_MODEL, torch_dtype=torch.float16, device_map="auto" ) # Load & merge LoRA model = PeftModel.from_pretrained(model, LORA_PATH) model = model.merge_and_unload() # Save merged model.save_pretrained(OUTPUT_PATH) tokenizer = AutoTokenizer.from_pretrained(BASE_MODEL) tokenizer.save_pretrained(OUTPUT_PATH) 

Convert to GGUF (llama.cpp)

# Install llama.cpp git clone https://github.com/ggerganov/llama.cpp cd llama.cpp && make # Convert python convert-hf-to-gguf.py ../mistral-7b-code-merged \ --outfile ../mistral-7b-code.Q4_K_M.gguf \ --outtype q4_k_m 

Inference with Ollama

# Create Modelfile cat > Modelfile << 'EOF' FROM ./mistral-7b-code.Q4_K_M.gguf TEMPLATE "{{ .System }}\n\n{{ .Prompt }}" PARAMETER temperature 0.7 PARAMETER top_p 0.9 EOF # Create model ollama create mistral-code -f Modelfile # Test ollama run mistral-code "Write a Python decorator for timing functions" 

Cost Breakdown (RTX 3090 24GB)

Item Cost
Electricity (4 hrs @ 350W) ~$0.20
GPU depreciation ~$1.00
Total per run ~$1.20

vs. Cloud (A100 40GB): $1.50-3.00/hr → $6-12 for same training.

Tips for Success

  1. Dataset quality > quantity — 500 clean examples beat 5000 noisy ones
  2. Start small — 100 steps, check loss, then scale
  3. Monitor VRAM — Reduce batch size if OOM
  4. Use gradient checkpointing — model.gradient_checkpointing_enable()
  5. Eval regularly — Keep a validation set

Common Issues

Error Fix
CUDA OOM Reduce batch size, increase grad accumulation, use 4-bit
Loss not decreasing Increase learning rate, check data format
NaN loss Lower learning rate, add gradient clipping
Slow training Enable flash attention, use packing=True

Conclusion

LoRA/QLoRA democratized LLM fine-tuning. With a consumer GPU, you can create domain-specialized models for coding, writing, analysis — whatever your use case. Start with 7B QLoRA, iterate on data, and deploy locally via Ollama or llama.cpp.

Next: Try DPO/ORPO for preference alignment, or merge multiple LoRAs for multi-task models.

Part of the LLM Fine-Tuning series on Dev.to