🧠 第七章 绘画模型训练:创意生成与视觉设计
📄 一句话概括

一个 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 秒实验

  1. 把真实秩从 64 改成 200,再跑一遍,看 r=8 的误差变成多少。 真实秩越高,低秩近似越吃力——这是 LoRA 失效的第一种情况。
  2. 把幂律指数从 1.4 改成 0.3(奇异值几乎不衰减),看 r=8 的误差。 会飙到 0.9 以上:不是所有矩阵都能低秩近似。
  3. 把 r 一路加到 256、512,看占比和误差。占比涨到 50%、100%,误差一动不动——这就是白存。

常见误区

  1. 「r 越大效果越好」。r 太大只会过拟合,出图「只会画训练集那几张」,文件还翻倍。 换画风 r=8 到 16 就够。
  2. 「LoRA 训练特别省显存」。省的是需要梯度的参数量,底模还是整个要装进显存。
  3. 「任何任务都能用低秩近似」。取决于权重改动的谱。改的是「风格」这类整体性偏移时最有效。
自测三问(点开对答案) 1. LoRA 里的 r 是什么? —— 秩。它限制 ΔW 用两个小因子相乘时最多能表达多复杂的改动。r 越小参数越少,能表达的改动越单薄。 2. 为什么 r=8 就能还原得不错? —— 因为权重改动的奇异值衰减很快,前几项占了绝大部分能量,后面几十项几乎可以扔掉。 3. LoRA 省了什么、没省什么? —— 省参数、省存储、省分发;不省显存里的底模,也不省数据标注的人力。

原理清楚了,下一步装工具。但在点安装之前,先算三笔账——盘装得下吗、加载要多久、显存要多少。

相关笔记