基础知识 #

矩阵基础 #

在深入 LoRA 之前,我们需要先理解矩阵的基本概念,特别是矩阵的秩和分解。

矩阵的秩 #

矩阵的秩(Rank)是矩阵的一个重要属性,它描述了矩阵中线性无关的行或列的数量。

text
┌─────────────────────────────────────────────────────────────┐
│                    矩阵的秩                                   │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  定义:矩阵中线性无关的行(或列)的最大数量                    │
│                                                             │
│  示例:                                                     │
│  ┌─────────────┐     ┌─────────────┐                       │
│  │ 1  2  3    │     │ 1  2  3    │                       │
│  │ 2  4  6    │     │ 0  1  0    │                       │
│  │ 3  6  9    │     │ 0  0  1    │                       │
│  └─────────────┘     └─────────────┘                       │
│     rank = 1              rank = 3                         │
│  (行2=2×行1, 行3=3×行1)  (满秩矩阵)                         │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Python 示例:计算矩阵的秩 #

python
import numpy as np

A = np.array([
    [1, 2, 3],
    [2, 4, 6],
    [3, 6, 9]
])

B = np.array([
    [1, 2, 3],
    [0, 1, 0],
    [0, 0, 1]
])

print(f"矩阵 A 的秩: {np.linalg.matrix_rank(A)}")
print(f"矩阵 B 的秩: {np.linalg.matrix_rank(B)}")

输出:

text
矩阵 A 的秩: 1
矩阵 B 的秩: 3

低秩矩阵的特点 #

text
低秩矩阵的特点:
├── 可以用更少的参数表示
├── 存在大量冗余信息
├── 可以分解为两个小矩阵的乘积
└── 在机器学习中广泛存在

矩阵分解 #

矩阵分解是将一个矩阵表示为多个矩阵乘积的过程。LoRA 的核心就是利用低秩分解来表示权重更新。

奇异值分解(SVD) #

SVD 是最经典的矩阵分解方法,任何矩阵都可以进行 SVD 分解。

text
┌─────────────────────────────────────────────────────────────┐
│                    奇异值分解 (SVD)                           │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  对于任意矩阵 W ∈ R^(m×n),存在分解:                        │
│                                                             │
│  W = U × Σ × V^T                                            │
│                                                             │
│  其中:                                                     │
│  ├── U ∈ R^(m×m): 左奇异向量矩阵(正交矩阵)                 │
│  ├── Σ ∈ R^(m×n): 奇异值对角矩阵                            │
│  └── V ∈ R^(n×n): 右奇异向量矩阵(正交矩阵)                 │
│                                                             │
│  低秩近似:                                                 │
│  W ≈ U_r × Σ_r × V_r^T                                      │
│  其中 r << min(m, n)                                        │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Python 示例:SVD 分解 #

python
import numpy as np

W = np.random.randn(100, 100)

U, S, Vt = np.linalg.svd(W, full_matrices=False)

print(f"原始矩阵形状: {W.shape}")
print(f"U 形状: {U.shape}")
print(f"S 形状: {S.shape}")
print(f"Vt 形状: {Vt.shape}")

r = 10
W_approx = U[:, :r] @ np.diag(S[:r]) @ Vt[:r, :]

print(f"\n低秩近似 (r={r}):")
print(f"近似矩阵形状: {W_approx.shape}")
print(f"相对误差: {np.linalg.norm(W - W_approx) / np.linalg.norm(W):.4f}")

低秩分解 #

LoRA 使用的是更简单的低秩分解形式:

text
┌─────────────────────────────────────────────────────────────┐
│                    LoRA 低秩分解                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  目标:用低秩矩阵近似权重更新 ΔW                              │
│                                                             │
│  ΔW ≈ B × A                                                 │
│                                                             │
│  其中:                                                     │
│  ├── ΔW ∈ R^(d×k): 权重更新矩阵                             │
│  ├── B ∈ R^(d×r): 低秩矩阵 B                                │
│  ├── A ∈ R^(r×k): 低秩矩阵 A                                │
│  └── r << min(d, k): 秩远小于矩阵维度                       │
│                                                             │
│  参数量对比:                                                │
│  ├── 原始: d × k                                            │
│  └── LoRA: d × r + r × k = r × (d + k)                      │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Python 示例:低秩分解 #

python
import torch

d, k, r = 4096, 4096, 8

W = torch.randn(d, k)

A = torch.randn(r, k) * 0.01
B = torch.zeros(d, r)

delta_W = B @ A

W_new = W + delta_W

print(f"原始参数量: {d * k:,}")
print(f"LoRA 参数量: {d * r + r * k:,}")
print(f"压缩比: {d * k / (d * r + r * k):.1f}x")
print(f"参数占比: {(d * r + r * k) / (d * k) * 100:.2f}%")

输出:

text
原始参数量: 16,777,216
LoRA 参数量: 65,536
压缩比: 256.0x
参数占比: 0.39%

神经网络微调基础 #

什么是微调? #

微调(Fine-tuning)是在预训练模型基础上,使用特定任务数据进行进一步训练的过程。

text
┌─────────────────────────────────────────────────────────────┐
│                    微调流程                                   │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  预训练阶段:                                                │
│  ┌─────────────┐                                            │
│  │ 大规模数据   │ → 训练 → 通用预训练模型                     │
│  │ (互联网文本) │                                            │
│  └─────────────┘                                            │
│                         ↓                                   │
│  微调阶段:                                                  │
│  ┌─────────────┐                                            │
│  │ 特定任务数据 │ → 训练 → 任务特定模型                       │
│  │ (标注数据)   │                                            │
│  └─────────────┘                                            │
│                                                             │
└─────────────────────────────────────────────────────────────┘

全参数微调 #

传统的全参数微调会更新模型的所有参数。

python
import torch
import torch.nn as nn

model = nn.Linear(4096, 4096)

optimizer = torch.optim.AdamW(model.parameters(), lr=1e-5)

for param in model.parameters():
    param.requires_grad = True

print(f"可训练参数: {sum(p.numel() for p in model.parameters()):,}")

冻结部分参数 #

另一种策略是冻结大部分参数,只训练部分层。

python
import torch
import torch.nn as nn

class SimpleModel(nn.Module):
    def __init__(self):
        super().__init__()
        self.layer1 = nn.Linear(4096, 4096)
        self.layer2 = nn.Linear(4096, 4096)
        self.layer3 = nn.Linear(4096, 4096)
    
    def forward(self, x):
        x = self.layer1(x)
        x = self.layer2(x)
        x = self.layer3(x)
        return x

model = SimpleModel()

for name, param in model.named_parameters():
    if 'layer3' not in name:
        param.requires_grad = False

trainable = sum(p.numel() for p in model.parameters() if p.requires_grad)
total = sum(p.numel() for p in model.parameters())
print(f"可训练参数: {trainable:,} / {total:,} ({trainable/total*100:.1f}%)")

Transformer 架构基础 #

LoRA 主要应用于 Transformer 模型,了解其架构对于理解 LoRA 的应用至关重要。

Transformer 核心组件 #

text
┌─────────────────────────────────────────────────────────────┐
│                    Transformer 架构                           │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  输入嵌入                                                    │
│      │                                                      │
│      ▼                                                      │
│  ┌─────────────────────────────────────────────────────┐   │
│  │              Transformer Block × N                   │   │
│  │  ┌─────────────────────────────────────────────┐   │   │
│  │  │           Multi-Head Attention               │   │   │
│  │  │  ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐           │   │   │
│  │  │  │Wq   │ │Wk   │ │Wv   │ │Wo   │ ← LoRA目标│   │   │
│  │  │  └─────┘ └─────┘ └─────┘ └─────┘           │   │   │
│  │  └─────────────────────────────────────────────┘   │   │
│  │                      │                              │   │
│  │                      ▼                              │   │
│  │  ┌─────────────────────────────────────────────┐   │   │
│  │  │         Feed-Forward Network                 │   │   │
│  │  │  ┌──────────┐    ┌──────────┐              │   │   │
│  │  │  │W_up      │    │W_down    │ ← LoRA目标  │   │   │
│  │  │  └──────────┘    └──────────┘              │   │   │
│  │  └─────────────────────────────────────────────┘   │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘

注意力机制中的线性层 #

python
import torch
import torch.nn as nn

class MultiHeadAttention(nn.Module):
    def __init__(self, d_model=4096, n_heads=32):
        super().__init__()
        self.d_model = d_model
        self.n_heads = n_heads
        self.head_dim = d_model // n_heads
        
        self.q_proj = nn.Linear(d_model, d_model)
        self.k_proj = nn.Linear(d_model, d_model)
        self.v_proj = nn.Linear(d_model, d_model)
        self.o_proj = nn.Linear(d_model, d_model)
    
    def forward(self, x):
        batch_size, seq_len, _ = x.shape
        
        q = self.q_proj(x)
        k = self.k_proj(x)
        v = self.v_proj(x)
        
        q = q.view(batch_size, seq_len, self.n_heads, self.head_dim).transpose(1, 2)
        k = k.view(batch_size, seq_len, self.n_heads, self.head_dim).transpose(1, 2)
        v = v.view(batch_size, seq_len, self.n_heads, self.head_dim).transpose(1, 2)
        
        attn = torch.matmul(q, k.transpose(-2, -1)) / (self.head_dim ** 0.5)
        attn = torch.softmax(attn, dim=-1)
        
        out = torch.matmul(attn, v)
        out = out.transpose(1, 2).contiguous().view(batch_size, seq_len, self.d_model)
        
        return self.o_proj(out)

mha = MultiHeadAttention()
print("注意力层参数:")
for name, param in mha.named_parameters():
    print(f"  {name}: {param.shape}")

LoRA 应用的目标层 #

text
LoRA 通常应用的目标模块:

注意力层:
├── q_proj (Query 投影)
├── k_proj (Key 投影)
├── v_proj (Value 投影)
└── o_proj (Output 投影)

前馈网络层:
├── gate_proj (门控投影)
├── up_proj (上投影)
└── down_proj (下投影)

选择策略:
├── 最小配置: q_proj, v_proj
├── 标准配置: q_proj, k_proj, v_proj, o_proj
└── 完整配置: 所有线性层

参数效率对比 #

不同微调方法的参数量 #

python
def count_parameters(model_size="7B", method="full"):
    params = {
        "7B": {"d": 4096, "layers": 32, "attn_per_layer": 4, "ffn_per_layer": 3},
        "13B": {"d": 5120, "layers": 40, "attn_per_layer": 4, "ffn_per_layer": 3},
        "70B": {"d": 8192, "layers": 80, "attn_per_layer": 4, "ffn_per_layer": 3},
    }
    
    config = params[model_size]
    d = config["d"]
    layers = config["layers"]
    
    attn_params = layers * config["attn_per_layer"] * d * d
    ffn_params = layers * config["ffn_per_layer"] * d * d * 2.75
    
    total = attn_params + ffn_params
    
    if method == "full":
        return total
    elif method == "lora":
        r = 8
        lora_params = layers * 4 * (d * r + r * d)
        return lora_params
    elif method == "qlora":
        r = 64
        qlora_params = layers * 4 * (d * r + r * d)
        return qlora_params
    
    return 0

print("参数量对比:")
for model in ["7B", "13B", "70B"]:
    full = count_parameters(model, "full")
    lora = count_parameters(model, "lora")
    print(f"\n{model} 模型:")
    print(f"  全参数微调: {full/1e9:.2f}B")
    print(f"  LoRA (r=8): {lora/1e6:.2f}M ({lora/full*100:.2f}%)")

显存需求计算 #

python
def estimate_memory(model_params_b, method="full", precision="fp16"):
    bytes_per_param = {"fp32": 4, "fp16": 2, "bf16": 2, "int8": 1, "int4": 0.5}
    
    model_memory = model_params_b * bytes_per_param[precision]
    
    if method == "full":
        gradient_memory = model_memory
        optimizer_memory = model_memory * 2
        total = model_memory + gradient_memory + optimizer_memory
    elif method == "lora":
        lora_ratio = 0.005
        gradient_memory = model_memory * lora_ratio
        optimizer_memory = gradient_memory * 2
        total = model_memory + gradient_memory + optimizer_memory
    elif method == "qlora":
        model_memory = model_params_b * 0.5
        lora_ratio = 0.01
        gradient_memory = model_params_b * lora_ratio * 2
        optimizer_memory = gradient_memory * 2
        total = model_memory + gradient_memory + optimizer_memory
    
    return total

print("显存需求估算 (GB):")
for model in [("7B", 7), ("13B", 13), ("70B", 70)]:
    name, params = model
    full = estimate_memory(params, "full", "fp16")
    lora = estimate_memory(params, "lora", "fp16")
    qlora = estimate_memory(params, "qlora", "int4")
    print(f"\n{name} 模型:")
    print(f"  全参数微调: {full:.1f} GB")
    print(f"  LoRA: {lora:.1f} GB")
    print(f"  QLoRA: {qlora:.1f} GB")

梯度与优化 #

梯度计算基础 #

python
import torch

x = torch.randn(10, 4096, requires_grad=True)
W = torch.randn(4096, 4096, requires_grad=True)

y = x @ W

loss = y.sum()

loss.backward()

print(f"权重梯度形状: {W.grad.shape}")
print(f"输入梯度形状: {x.grad.shape}")

LoRA 的梯度流 #

text
┌─────────────────────────────────────────────────────────────┐
│                    LoRA 梯度流                                │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  前向传播:                                                   │
│  h = Wx + BAx = (W + BA)x                                   │
│                                                             │
│  反向传播:                                                   │
│  ∂L/∂h ← 从上层传入                                         │
│                                                             │
│  ∂L/∂B = ∂L/∂h × (Ax)^T                                     │
│  ∂L/∂A = B^T × ∂L/∂h × x^T                                  │
│                                                             │
│  W 的梯度: 不计算(冻结)                                    │
│  A, B 的梯度: 正常计算和更新                                 │
│                                                             │
└─────────────────────────────────────────────────────────────┘

优化器状态 #

python
import torch
import torch.nn as nn

def compare_optimizer_memory():
    d = 4096
    
    W = nn.Linear(d, d)
    A = nn.Linear(d, 8, bias=False)
    B = nn.Linear(8, d, bias=False)
    
    params_full = list(W.parameters())
    params_lora = list(A.parameters()) + list(B.parameters())
    
    opt_full = torch.optim.AdamW(params_full, lr=1e-4)
    opt_lora = torch.optim.AdamW(params_lora, lr=1e-4)
    
    def count_state_bytes(optimizer):
        total = 0
        for state in optimizer.state.values():
            for tensor in state.values():
                if isinstance(tensor, torch.Tensor):
                    total += tensor.numel() * 4
        return total
    
    print(f"全参数优化器状态: {d*d*2*4/1e6:.1f} MB")
    print(f"LoRA 优化器状态: {(d*8+8*d)*2*4/1e3:.1f} KB")

compare_optimizer_memory()

下一步 #

现在你已经掌握了 LoRA 的基础知识,接下来学习 核心原理,深入了解 LoRA 的数学推导和架构设计!

最后更新:2026-04-05