LoRA数学原理:从矩阵分解到参数更新
说实话,很多同学第一次接触LoRA时,都被那些数学符号吓住了。什么低秩分解、秩的概念、ΔW = BA…… 我当年也一样,看了半天论文,脑子里全是问号。
但你别怕。LoRA的数学原理,说白了就一句话:用两个小矩阵,去近似一个大矩阵的更新量。
今天我就带你把这个过程拆开揉碎,讲清楚。
1. 秩(Rank)是什么?
先聊一个基础概念:秩。
我习惯这么理解——秩就是一个矩阵里,真正有用的信息维度数。
举个例子。你有一张表格,记录了100个人的身高和体重。虽然数据有100行,但本质上只有两个维度在变化。这个矩阵的秩,最多就是2。
为什么?因为身高和体重之间高度相关。你完全可以只用一条直线(一个维度)来近似描述它们的关系。
在神经网络里也一样。一个权重矩阵W,虽然可能有1000×1000那么大,但它的有效变化空间往往很小。这就是LoRA能工作的前提。
核心观点:大模型微调时,权重的更新量ΔW,通常是一个低秩矩阵。这意味着我们可以用更少的参数来表示它。
2. 矩阵低秩分解:把大象装进冰箱
好,现在我们知道ΔW是低秩的了。那怎么用少量参数表示它呢?
答案就是矩阵低秩分解。
假设ΔW是一个m×n的矩阵,秩为r。那么我们可以把它分解成两个矩阵的乘积:
ΔW = B × A
其中:
- B 是 m × r 的矩阵
- A 是 r × n 的矩阵
- r 远小于 m 和 n
你想想看,原来ΔW有 m×n 个参数。现在呢?只有 m×r + r×n 个参数。
举个例子。如果m=1000,n=1000,r=8:
- 原来参数:1,000,000
- 分解后参数:1000×8 + 8×1000 = 16,000
参数减少了98.4%。这就是LoRA的魔力。
我的经验:我在项目中遇到过,有些同学觉得r越大越好。其实不是。r=8在很多任务上已经够用了。r太大反而容易过拟合,而且失去了低秩分解的意义。
3. LoRA的秩参数r:怎么选?
r是LoRA里唯一需要你手动调的超参数。它决定了表达能力和参数量的平衡。
| r值 | 参数量 | 表达能力 | 适用场景 |
|---|---|---|---|
| 1-4 | 极低 | 弱 | 简单任务、资源受限 |
| 8-16 | 低 | 中等 | 大多数通用任务 |
| 32-64 | 中等 | 强 | 复杂任务、领域适配 |
| 128+ | 高 | 极强 | 几乎不压缩,接近全量微调 |
我个人习惯,先从r=8开始试。如果效果不够,再逐步增大。别一上来就搞个r=128,那还不如直接全量微调呢。
避坑指南:我曾经在一个文本分类任务上,把r设成了64,结果训练了3个小时,效果还不如r=8。后来发现是过拟合了。记住:r越大,可学习的参数越多,但并不意味着效果越好。
4. 权重更新公式 ΔW = BA
现在我们来写公式。
原始的前向传播是:
h = W × x
加上LoRA后变成:
h = (W + ΔW) × x
= (W + B × A) × x
= W × x + B × A × x
注意看,这里有个关键点:W本身是冻结的,不更新。我们只更新B和A。
所以实际计算时,是两条路径并行:
- 原始路径:W × x(冻结,不更新)
- LoRA路径:B × A × x(可训练)
最后把两个结果加起来。
为什么这样设计?因为W已经在大规模数据上预训练好了,我们不想破坏它。LoRA只是在它旁边搭了一条"小径",让模型能适应新任务。
5. 前向传播中的LoRA
前向传播时,LoRA的流程是这样的:
- 输入x进入网络
- 到达目标层(比如注意力层的Q、K、V投影矩阵)
- 同时计算两条路径:
- W × x(原始路径)
- A × x → 得到中间结果(维度r)→ B × 中间结果(维度m)
- 两条路径的结果相加,得到最终输出
这里有个细节:A先把输入降到r维,B再升回m维。这个"降维-升维"的过程,就是低秩分解的精髓。
我的习惯:我一般把A初始化为随机高斯分布,B初始化为全零。这样一开始LoRA路径的输出是0,不会干扰原始模型。随着训练进行,B逐渐学到有用的东西。
6. 反向传播中的LoRA
反向传播时,梯度只流经B和A,不流经W。
具体来说:
∂L/∂B = ∂L/∂h × (A × x)^T
∂L/∂A = B^T × ∂L/∂h × x^T
你看,梯度计算只涉及B和A,以及输入x。W完全不动。
这意味着:
- 显存占用大幅降低(不需要存W的梯度)
- 训练速度更快(只更新少量参数)
- 可以适配不同的模型架构(只要改一下插入位置)
注意:虽然W不更新,但前向传播时仍然需要加载W到显存。所以LoRA节省的是优化器状态和梯度的显存,不是模型本身的显存。
7. 知识体系总览
下面这张图,帮你把整个LoRA的数学原理串起来:
8. 一个完整的代码示例
最后,给你看一个PyTorch风格的LoRA层实现。代码不长,但把上面讲的数学原理都体现出来了:
class LoRALayer(nn.Module):
def __init__(self, in_dim, out_dim, rank=8):
super().__init__()
# 原始权重 W 冻结
self.W = nn.Parameter(torch.randn(out_dim, in_dim), requires_grad=False)
# LoRA 参数 A 和 B
self.A = nn.Parameter(torch.randn(rank, in_dim) * 0.01)
self.B = nn.Parameter(torch.zeros(out_dim, rank))
def forward(self, x):
# 原始路径
h1 = F.linear(x, self.W)
# LoRA 路径:先降维再升维
h2 = F.linear(F.linear(x, self.A), self.B)
# 两条路径相加
return h1 + h2
你看,核心就这几行。A负责降维,B负责升维,W保持不动。
我的建议:刚开始学LoRA时,别急着调参。先把这个代码跑通,理解每一步在干什么。然后试着改改r值,看看效果变化。实践出真知。
好了,LoRA的数学原理就讲到这里。记住三个关键词:低秩、分解、冻结。搞懂了这三个,你就掌握了LoRA的核心。