LoRA Concept › Visualizing Low-Rank Approximation

Michael BrenndoerferNovember 30, 20256 min read
Language AI HandbookAll chapters
Part 35 of 67Parameter-Efficient Fine-tuning
Chapter 255LoRA Concept › Visualizing Low-Rank Approximation
Reading section 5 of 13 · Visualizing Low-Rank Approximation · 6 minSection 5 of 13 · 6 min

Book contents

Section 5 of 13 · 6 min read

LoRA Concept › Visualizing Low-Rank ApproximationOne section · Sections in this chapter5 / 13 ↓
Reading tools Definitions & inline links

Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.

Article links

Make inline references clickable

To build intuition for how well low-rank matrices can approximate full matrices, let's see how reconstruction error changes with rank. This visualization helps us understand why LoRA works: if typical weight updates have low intrinsic rank, then a low-rank representation should capture most of their information with minimal error. We will create a synthetic example that mimics the structure of real weight updates, combining a low-rank signal with some noise.

In[8]:
Code
import numpy as np

# Create a matrix that simulates a "structured" weight update
# Real weight updates tend to have low intrinsic rank
np.random.seed(42)
true_rank = 16  # The "true" intrinsic rank
d, k = 256, 256

# Generate a low-rank matrix with some noise (simulating real weight updates)
U = np.random.randn(d, true_rank)
V = np.random.randn(true_rank, k)
low_rank_signal = U @ V
noise = np.random.randn(d, k) * 0.1  # Small noise
target_matrix = low_rank_signal + noise

# Compute SVD and test reconstruction at various ranks
U_full, S_full, Vt_full = np.linalg.svd(target_matrix, full_matrices=False)

ranks = list(range(1, 65))
errors = []
relative_errors = []
target_norm = np.linalg.norm(target_matrix, "fro")

for r in ranks:
    # Reconstruct with rank r
    reconstruction = U_full[:, :r] @ np.diag(S_full[:r]) @ Vt_full[:r, :]
    error = np.linalg.norm(target_matrix - reconstruction, "fro")
    errors.append(error)
    relative_errors.append(error / target_norm * 100)
Out[9]:
Visualization
Line plot of Frobenius reconstruction error versus rank from 1 to 64, with a steep drop and a red dashed vertical line at rank 16.
Frobenius norm reconstruction error versus approximation rank for a synthetic weight update matrix with true rank 16. Error drops sharply until rank 16, then flattens as additional dimensions only capture noise.
Line plot of relative reconstruction error percentage versus rank, with the 5% threshold marked in orange and a red dashed line at the true rank 16.
Relative reconstruction error as a percentage of the total matrix norm. Below rank 16, each added dimension contributes meaningfully; above it, error stays near the 5% threshold set by noise.

The plots show that once we reach the true intrinsic rank of the matrix, reconstruction error drops dramatically. Notice how the error curve has a sharp elbow around rank 16, which corresponds to the true rank of the underlying signal we constructed. Before this point, each additional rank dimension captures substantial information, leading to large reductions in error. After this point, additional dimensions primarily capture noise, yielding diminishing returns. For matrices with inherent low-rank structure, even aggressive compression preserves most information.

This is exactly what LoRA exploits: if weight updates during fine-tuning are approximately low-rank, we can represent them efficiently without losing task performance. The key insight is that we do not need to know the true rank in advance. By choosing a rank that captures most of the error reduction, we achieve excellent approximations with minimal parameter counts. In practice, ranks between 4 and 64 cover the large majority of real-world LoRA applications, and the appropriate range for a given task is often determined through simple ablation experiments.

Out[10]:
Visualization
Scatter-line plot of singular value magnitudes 1 to 50, with red markers for the first 16 and blue for the rest, separated by a vertical dashed boundary.
Singular value magnitude spectrum for the synthetic weight update matrix. The first 16 values (red) are substantially larger than the remaining values (blue), revealing the low-dimensional structure of the matrix's primary signal.
Cumulative variance explained rising steeply to near 100% by component 16, with horizontal reference lines at 95% and 99%.
Cumulative variance explained by successive singular value components. The top 16 components capture over 99% of the matrix information, showing that a rank-16 approximation is nearly lossless.
Rank needed to capture 95% of variance: 15
Rank needed to capture 99% of variance: 16

The singular value spectrum reveals why low-rank approximation works so well. The left panel shows that singular values drop sharply after the true rank of 16, meaning the remaining dimensions contribute little information. The right panel shows cumulative variance: a small number of dimensions capture nearly all the meaningful structure in the matrix. This pattern is exactly what we expect from fine-tuning updates, where task-specific modifications concentrate along a few important directions in weight space. When you see a spectrum like this in practice, it is a direct signal that LoRA will work well for the corresponding weight matrix.