LoRA (Low-Rank Adaptation)

ML Training & Fine-Tuning intermediate How AI Works 12 min read

Overview

LoRA (Low-Rank Adaptation) is a parameter-efficient fine-tuning technique introduced by researchers at Microsoft in 2021. It addresses a fundamental problem in modern AI engineering: large language models like GPT-3 (175B parameters) or LLaMA-70B are extraordinarily expensive to fine-tune using traditional methods, which update every single weight in the model. Full fine-tuning of a 175B-parameter model requires enormous GPU memory, produces a separate copy of all model weights for each task, and can take days or weeks on expensive hardware.

LoRA solves this by freezing the pre-trained model weights entirely and injecting small, trainable low-rank decomposition matrices into specific layers of the model. Instead of updating a full weight matrix W (which might have dimensions 4096 × 4096), LoRA learns a low-rank update ΔW = BA, where B is a 4096 × r matrix and A is an r × 4096 matrix. The rank r is typically 8 or 16 — orders of magnitude smaller than the full dimension. This reduces trainable parameters by 10,000× compared to full fine-tuning while achieving comparable or even identical task performance.

LoRA has become the de facto standard for fine-tuning large language models. It is supported natively in Hugging Face PEFT, Unsloth, Axolotl, LLaMA-Factory, and virtually every modern fine-tuning framework. Understanding LoRA is essential for any AI engineer who needs to customize model behavior without the cost of full fine-tuning.

How It Works

The Core Intuition: Low-Rank Updates

The key insight behind LoRA comes from research showing that the weight updates during fine-tuning have a low “intrinsic rank.” In other words, even though the weight matrices in a transformer are large (e.g., 4096 × 4096 = ~16.7 million parameters per matrix), the actual change needed to adapt the model to a new task can be captured by a much lower-dimensional representation.

Key Concept: Low-Rank Decomposition

Instead of learning a full update matrix ΔW of shape d × d, LoRA decomposes it into two smaller matrices: ΔW = BA, where B has shape d × r and A has shape r × d. With rank r = 8 and d = 4096, this reduces parameters from 16.7M to just 65,536 per layer — a 256× reduction.

Mathematical Formulation

For a pre-trained weight matrix W₀ ∈ ℝ^(d×k), LoRA constrains the update by representing it with a low-rank decomposition:

W = W₀ + ΔW = W₀ + BA

Where:
  W₀ ∈ ℝ^(d×k)  — frozen pre-trained weights
  B  ∈ ℝ^(d×r)  — trainable down-projection
  A  ∈ ℝ^(r×k)  — trainable up-projection
  r  << min(d, k) — the rank (typically 8, 16, or 32)

Forward pass:
  h = W₀x + BAx = W₀x + (α/r) · BAx

Where α is the LoRA scaling factor (lora_alpha)

Matrix A is initialized with random Gaussian values and B is initialized to zero, so at the start of training ΔW = BA = 0 and the model behaves identically to the pre-trained version. The scaling factor α/r controls how much the learned adaptation influences the output.

Where LoRA Is Applied

In a transformer model, LoRA adapters are typically inserted into the linear projection layers within the self-attention mechanism. The most common target modules are:

Research from the QLoRA paper demonstrated that applying LoRA to all linear layers (both attention and MLP) yields the best results, with minimal difference between ranks of 8 and 256 when all layers are targeted.

Key Hyperparameters

Configuring LoRA involves three primary hyperparameters:

Practical Example with PEFT

from peft import LoraConfig, get_peft_model

config = LoraConfig(
    r=16,
    lora_alpha=32,
    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(base_model, config)
model.print_trainable_parameters()
# Output: trainable params: 5,324,800 || all params: 6,738,415,616
# || trainable%: 0.079%

Merging at Inference Time

One of LoRA’s most powerful properties is that the adapter weights can be merged back into the base model weights at deployment time. Since W = W₀ + BA is a simple matrix addition, the merged model has exactly the same architecture and inference latency as the original — zero overhead. This also means you can swap different LoRA adapters for different tasks without keeping multiple full model copies.

Practical Applications

Multi-Task Serving

Organizations can maintain a single base model and dozens of LoRA adapters — one per customer, language, or domain. At inference time, the correct adapter is loaded (a few MB vs. many GB for a full model). Frameworks like vLLM and LoRAX support serving multiple LoRA adapters simultaneously from a single base model in GPU memory.

Domain Adaptation

LoRA is widely used to adapt general-purpose models to specialized domains: medical terminology, legal language, financial analysis, or code generation. A LoRA adapter for a 7B model typically weighs 10–50 MB, compared to 14 GB for the full model weights.

Instruction Tuning and Chat

Many open-source chat models (e.g., Alpaca, Vicuna early versions) were created by applying LoRA fine-tuning to a base LLaMA model using instruction-following datasets. This made it possible for individual researchers with a single GPU to create instruction-tuned models.

Image Generation (Stable Diffusion)

LoRA became enormously popular in the Stable Diffusion community for training custom art styles, characters, and concepts. A LoRA adapter for an image model is typically 4–100 MB and can be combined with other LoRAs at inference time by adding their weight deltas.

Key Takeaways

Further Reading