一个 512×512 的层全量要 262144 个参数,LoRA 取 r=8 只训 8192 个,占 3.1%。 而这 3.1% 把矩阵还原出来的相对误差只有 0.0959。 原因不是巧合:权重的改动量本身就是「低秩」的。
前六章处理的都是文字——微调、知识库、检索、引用。这一章换一个模态:让模型学画画。 第一步不急着装软件,先把 LoRA 这个方法的原理弄明白。
一、一个 512×512 的层,全量要 26 万个参数
模型里一层线性变换就是一个矩阵。512 进 512 出,参数量就是 512 × 512 = 262144。
微调改的就是这个矩阵——在原来的权重上加一个改动量 ΔW。
全量微调把 ΔW 整个存下来,一个层就是 26 万个浮点数。
LoRA 不存 ΔW,它存 ΔW 的两个小因子:ΔW = B × A,其中 A 是 r×512、B 是 512×r。
r 取 8 时,两个矩阵加起来只有 8192 个参数。
这个 r 就叫秩。它限制了这个改动量最多能表达多复杂的东西——r 越小,能表达的改动越「单薄」。
二、低秩:改动量本来就是薄的
数学上任何矩阵都能做奇异值分解,奇异值越大说明那一「方向」越重要。 如果一个矩阵的奇异值衰减得很快,那前几项就占了绝大部分能量,后面的几乎可以扔掉。 Demo 造了一个秩为 64、奇异值按幂律衰减的矩阵来模拟真实的 ΔW,然后截断到前 r 项看还原得多准: r=4 误差 0.1708,r=8 降到 0.0959,r=16 到 0.0512,r=64 直接归零。 演示里那个「幂律衰减」是我造的,不是量出来的。 但真实的权重更新确实也长这样——这就是有人敢用 3% 参数去近似一整个矩阵的底气。 如果换成一个奇异值平坦的随机矩阵,r=8 的误差会接近 1,那 LoRA 就彻底失效了。
三、秩选多大:看任务,不看论文
把 Demo 那张表摊开看:
| 秩 r | LoRA 参数 | 占全量 | 重构相对误差 |
|---|---|---|---|
| 4 | 4096 | 1.6% | 0.1708 |
| 8 | 8192 | 3.1% | 0.0959 |
| 16 | 16384 | 6.2% | 0.0512 |
| 32 | 32768 | 12.5% | 0.0245 |
| 64 | 65536 | 25.0% | 0.0000 |
| 换画风、加人物这类任务,r=8 到 16 通常就够。要塞进大量新概念(比如一整套建筑风格、 | |||
| 一个不存在的物种),才往 32 以上走。 | |||
| r 加大的代价有两笔:文件线性变大,以及越容易把训练集背下来(过拟合)。 | |||
| 小数据集配大 r,出图会「只会画训练集里那几张」。 | |||
| 注意 r=64 之后误差是 0 ——那不是「更好」,是因为那个矩阵的真实秩就是 64。 | |||
| 再往上加秩只是多存一堆废参数。真实模型没有这么整齐的秩,所以误差也不会真的归零。 |
四、LoRA 省什么、不省什么
省参数、省存储、省分发。 一个画风 LoRA 常见 10 到 100 MB, 而底模是 2 到 7 GB——差两个数量级。这也是为什么网上能随便下载别人的 LoRA,底模却要自己下。 不省显存里的底模。 训练时底模还是要整个装进显存,LoRA 只是让「需要梯度的那部分」变小了。 所以「LoRA 很省显存」这句话只对了一半。 也不省数据标注。 下一节就要开始准备图,每张图还要打标签——那才是这类训练最耗人力的地方。 一句话总结:LoRA 省的是「存什么」,不省「算什么」和「标什么」。
五、动手 Demo:截断到前 r 项:3.1% 的参数还原到 0.096 的误差
造一个 512×512、真实秩 64、奇异值按幂律衰减的矩阵模拟权重改动量,做奇异值分解后截断到前 r 项, 量每个 r 下的参数量、占全量的比例,以及重构的相对误差。
U = np.linalg.qr(rg.normal(0, 1, (512, 64)))[0]
V = np.linalg.qr(rg.normal(0, 1, (512, 64)))[0]
sv = 1.0 / (1.0 + np.arange(64)) ** 1.4 # 奇异值幂律衰减
W = (U * sv) @ V.T
u, s, vt = np.linalg.svd(W, full_matrices=False)
for r in (4, 8, 16, 32, 64, 128):
Wr = (u[:, :r] * s[:r]) @ vt[:r] # 截断到前 r 项
err = np.linalg.norm(W - Wr) / np.linalg.norm(W)
par = 512 * r * 2 # A 与 B 两个矩阵
print(r, par, 100.0 * par / 262144, err)
实跑输出:
--------------------------------------------------------------------
LoRA 只训 3.1% 的参数:8192 对 262144
--------------------------------------------------------------------
秩 r LoRA 参数 占全量 重构相对误差
---------------------------------------
4 4096 1.6% 0.1708
8 8192 3.1% 0.0959
16 16384 6.2% 0.0512
32 32768 12.5% 0.0245
64 65536 25.0% 0.0000
128 131072 50.0% 0.0000
全量微调这个 512×512 层要 **262144** 个参数;LoRA 取 r=8 只要 **8192**,占 3.1%。
而这 3.1% 能把矩阵还原到什么程度?看最后一列:
**r=4 时误差还有 0.1708,r=16 降到 0.0512,r=64 才归零。**
下降快是因为这个矩阵的奇异值本身按幂律衰减——
**前几项就占了绝大部分能量,后面几十项几乎可以扔掉。**
**真实的权重更新也长这样**,这就是 LoRA 敢只用 3% 参数的底气。
注意 r=64 之后误差是 0:那个矩阵的真实秩就是 64,**再往上加秩只是多存废参数。**
这就是 LoRA 的全部原理:**微调时权重的改动量是低秩的**。
所以不必存整张矩阵,存两个小矩阵的乘积就行。
秩选多大?看任务。**换画风、加人物这类任务 r=8 到 16 通常够。**
要塞进大量新概念,才往 32 以上走。
代价那一栏也别忽略:LoRA 文件小,**但训练时底模还是要整个装进显存。**
省的是参数和存储,不是底模那份开销。
第一列是秩,第二列是参数量,第三列是占比。
r=8 时 8192 个参数,占 3.1%——这就是「只训 3%」的出处。
关键是第四列:误差从 0.1708 一路降到 0.0959、0.0512、0.0245,到 r=64 归零。 参数只花了 3%,
误差已经压到一成以内。
误差下降得快,是因为构造时把奇异值做成了 1/(1+k)^1.4 的幂律。
这个形状是我设的,但不是我瞎设——真实权重更新的谱也长这样,
所以「低秩近似能用」这件事在实践里成立。
r=128 那行误差还是 0、参数却翻倍,
说明超出真实秩之后全是废参数。选 r 的时候要找的是「误差已经够小、再加没收益」那个拐点。
要说清局限:这里只算了一个层、一个矩阵。
真实 LoRA 要同时改几十个注意力层,但每层的账都是同一套算法,结论也一样。
30 秒实验
- 把真实秩从 64 改成 200,再跑一遍,看 r=8 的误差变成多少。 真实秩越高,低秩近似越吃力——这是 LoRA 失效的第一种情况。
- 把幂律指数从 1.4 改成 0.3(奇异值几乎不衰减),看 r=8 的误差。 会飙到 0.9 以上:不是所有矩阵都能低秩近似。
- 把 r 一路加到 256、512,看占比和误差。占比涨到 50%、100%,误差一动不动——这就是白存。
常见误区
- 「r 越大效果越好」。r 太大只会过拟合,出图「只会画训练集那几张」,文件还翻倍。 换画风 r=8 到 16 就够。
- 「LoRA 训练特别省显存」。省的是需要梯度的参数量,底模还是整个要装进显存。
- 「任何任务都能用低秩近似」。取决于权重改动的谱。改的是「风格」这类整体性偏移时最有效。
自测三问(点开对答案)
1. LoRA 里的 r 是什么? —— 秩。它限制 ΔW 用两个小因子相乘时最多能表达多复杂的改动。r 越小参数越少,能表达的改动越单薄。 2. 为什么 r=8 就能还原得不错? —— 因为权重改动的奇异值衰减很快,前几项占了绝大部分能量,后面几十项几乎可以扔掉。 3. LoRA 省了什么、没省什么? —— 省参数、省存储、省分发;不省显存里的底模,也不省数据标注的人力。原理清楚了,下一步装工具。但在点安装之前,先算三笔账——盘装得下吗、加载要多久、显存要多少。
- ☐ (本节完,下一节 7.1.2 安装SD-Trainer)