基础知识 #
矩阵基础 #
在深入 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