3.1 LoRA 低秩分解的数学直觉与参数作用位置:为什么只动几百万参数就能改模型


文档摘要

3.1 LoRA 低秩分解的数学直觉与参数作用位置:为什么只动几百万参数就能改模型 读者读完这一节,应该能拿走一个清晰的心智模型:LoRA 没有去「改」底座那几亿甚至几十亿的参数,而是在旁边「挂」了两个又瘦又小的矩阵,只训练这两个小矩阵;同时你会明白,挂在哪些层(targetmodules)直接决定了你的微调在「教模型什么」。前者解释了「为什么便宜」,后者解释了「为什么有效」。 3.1.1 先说结论:全量微调的更新,本质上也常常是低秩的 很多人对 LoRA 的直觉是「它用低秩近似偷了懒,所以是次优的」。但一个反直觉的事实是:哪怕是标准的全量微调,权重更新矩阵 ΔW 在经验上也往往是低「内在秩(intrinsic rank)」的。 什么意思?

3.1 LoRA 低秩分解的数学直觉与参数作用位置:为什么只动几百万参数就能改模型

读者读完这一节,应该能拿走一个清晰的心智模型:LoRA 没有去「改」底座那几亿甚至几十亿的参数,而是在旁边「挂」了两个又瘦又小的矩阵,只训练这两个小矩阵;同时你会明白,挂在哪些层(target_modules)直接决定了你的微调在「教模型什么」。前者解释了「为什么便宜」,后者解释了「为什么有效」。

3.1.1 先说结论:全量微调的更新,本质上也常常是低秩的

很多人对 LoRA 的直觉是「它用低秩近似偷了懒,所以是次优的」。但一个反直觉的事实是:哪怕是标准的全量微调,权重更新矩阵 ΔW 在经验上也往往是低「内在秩(intrinsic rank)」的

什么意思?假设你有一个 d×d 的权重矩阵(比如 4096×4096),全量微调会去学一个同样大小的 ΔW。但研究者发现,这个 ΔW 的有效信息其实可以用远少于 d 个维度来承载——用 PCA 之类的方法去看,ΔW 的奇异值(svd 分解后的特征值)往往前几十个就占了绝大部分能量,后面一长串几乎是零。换句话说,模型要「学会一件新任务」这件事本身,并不需要一个满秩的更新,它只需要一个低秩方向的偏移。

LoRA 做的,就是不去等全量微调慢慢把 ΔW 学出来,而是直接假设 ΔW 是低秩的,只参数化那个低秩部分。这既省了参数,又常常和全量微调的效果相当,因为它本来就是任务内在需要的东西。

下面这张图把「全量微调」和「LoRA」放在同一视角下对比:

```mermaid graph LR A[基座权重 W 形状 d×d] --> B[全量微调: 学满秩 ΔW d×d] A --> C[LoRA: 假设 ΔW 低秩, 只学 B·A] C --> D["B 形状 d×r, A 形状 r×d (r< E[可训练参数 ~ d×d 巨大] D --> F["可训练参数 ~ 2·d·r 极小"] ```

3.1.2 低秩分解的数学直觉:ΔW = B·A

LoRA 的核心公式只有一行。原始的前向计算是:

h = W₀ · x

其中 W₀ 是预训练好的、被冻结的权重(下标 0 表示「原始」)。LoRA 不改 W₀,而是额外加一项:

h = W₀ · x + ΔW · x = W₀ · x + B · A · x

这里:

  • A 的形状是 r × d_in(降维),
  • B 的形状是 d_out × r(升维),
  • r 就是 LoRA 的秩(rank),且 r << d

乘积 B·A 的形状恰好是 d_out × d_in,和 W₀ 一样,所以可以无感地「加到」原计算上。关键在 r 很小:当 d=4096、r=16 时,B·A 的总参数只有 4096×16 + 16×4096 ≈ 13 万,而 W₀ 本身是 4096×4096 ≈ 1678 万。参数差了两个数量级。

为什么 B·A 能代表一个有用的更新?因为 A 先把输入 x 压到一个 r 维的「语义子空间」,B 再从那个子空间映射回输出。如果任务真正需要的调整就发生在那个低维子空间里(经验上确实如此),那么 B·A 就抓住了 ΔW 的精髓,而 r 就是你能容纳的「调整容量」。

```mermaid graph TD X[输入 x 维度 d_in] --> A["A: r × d_in 降维到 r"] A --> R["中间瓶颈 维度 r (很小)"] R --> B["B: d_out × r 升维回 d_out"] B --> Y[输出增量 ΔW·x] X --> W["W₀ 冻结 原路输出"] W --> Y Y --> ADD["相加: W₀·x + B·A·x"] ```

实践上有个初始化细节值得知道:A 通常用随机高斯初始化,B 初始化为全零。这意味着训练刚开始时 B·A = 0,模型行为和没加 LoRA 的基座完全一样(ΔW=0),然后随着训练才慢慢长出更新。这个「从零起步」的设计很妙——它保证了 LoRA 不会在训练第一步就破坏基座已有的能力,调参时也更稳。

3.1.3 参数作用位置:target_modules 到底注入到哪些矩阵

公式讲完了,但「挂在哪儿」才是 LoRA 真正决定效果的地方。在 Transformer(以及 GLM-5.2 这类自回归大模型)里,每层的计算主要分成两大块:

  • 注意力块(Attention):包含 q_projk_projv_projo_proj 四个线性投影。
  • 前馈/专家块(FFN / MLP,MoE 里是若干 expert):包含 gate_projup_projdown_proj 等。

target_modules 就是告诉 PEFT:「请在这些投影矩阵旁边挂 LoRA 适配器」。默认首选是注意力的 q/k/v/o_proj——理由是「模型怎么组织、检索、组合信息」基本由注意力决定,改这里性价比最高。

但要注意:GLM-5.2 是 MoE(混合专家)架构。MoE 的 FFN 被拆成了很多个「专家」子网络,激活时只路由到其中少数几个。这意味着 target_modules 的写法会比稠密模型更微妙——专家层里的投影名往往带 experts 之类的层级前缀,不同实现的命名可能不同(有的用 mlp.experts.x.x,有的用 block_sparse_moe)。

下面这张图示意了「注入位置」与「模型学会了什么」的对应关系:

```mermaid graph TD Q["q/k/v/o_proj 挂 LoRA
改注意力如何组织信息"] --> S1[学会'关注哪些线索'] G["gate/up/down_proj 挂 LoRA
改前馈如何变换表征"] --> S2[学会'怎么重组知识'] E["MoE 专家层挂 LoRA
改专家如何分工"] --> S3[学会'哪类样本走哪类专家'] S1 --> T[垂直领域适配] S2 --> T S3 --> T ```

我的建议(基于踩坑经验,不是铁律)

  • 数据量小、任务偏「风格/指令遵循」:只挂 q/k/v/o_proj 通常就够,容量小、不易过拟合。
  • 数据量大、任务偏「知识注入」(比如把医学术语体系灌进去):把 gate_proj/up_proj/down_proj 也加上,容量更大。
  • MoE 模型:先用 PEFT 的 get_peft_model 配合 model.print_trainable_parameters() 确认真实命中的层名,再去细调专家层。GLM-5.2 的具体模块命名请以智谱官方文档为准,不要凭记忆硬填——这是 MoE 上最常见的「适配器没接上」元凶。

3.1.4 rank r 与 alpha 的杠杆关系:有效学习率 = alpha / r

很多人以为 r 越大越好——容量大嘛。但 r 不是孤立起作用的,真正控制「更新幅度」的是 lora_alpha / r 这个比值

回忆公式 h = W₀·x + (alpha/r)·B·A·x(PEFT 在把 B·A 加到输出前,会乘上 alpha/r 做缩放)。所以:

  • alpha 越大 → 适配器的影响越强;
  • r 越大 → 适配器的影响被「摊薄」得越厉害。

常见的搭配是 r=8, alpha=16(比值 2)、r=16, alpha=32(比值 2)、r=64, alpha=128(比值 2)。保持 alpha = 2·r 是一个稳妥的经验起点;如果你发现模型「学得太猛、忘了基座本事」(灾难性遗忘明显),把 alpha 调小或 r 调小即可降温;如果「学了半天没变化」,适当调大 alpha。

值得强调的是:r 决定容量,alpha/r 决定步幅。一个常见误区是「r=64 一定比 r=8 强」——如果 alpha 没跟上,r=64 的更新反而可能因为缩放太小而几乎不动。两者要一起看。

3.1.5 为什么只动极小参数却有效:过参数化 + 任务低秩

这里补一个「为什么能 work」的直觉,帮助你在调参时心里有底,而不是把它当黑盒。

大模型是**过度参数化(over-parameterized)**的:它实际需要的「有效维度」远小于参数量。就像一家大公司,处理某个新业务不需要全员重新培训,只需要调一个精干的专项小组。LoRA 就是这个「专项小组」——它不动全员(冻结 W₀),只训练小组(B、A)。

再加上 3.1.1 说的「任务内在低秩」,这个小组的维度 r(比如 8~64)刚好够用。于是:

  • 可训练参数从「全员的几亿」降到「小组的几百万」,显存和算力开销骤降;
  • 因为只动小组,基座的通用能力被完整保留,灾难性遗忘风险大幅降低;
  • 因为小组维度小,过拟合风险也更低,小数据集也能训。

这就是为什么 LoRA 能在「一张消费级显卡 + 几千条数据」条件下,就把 GLM-5.2 调成某个垂直领域的样子。它不是「偷工减料」,而是「精准用工」。

3.1.6 在 GLM-5.2(MoE)上选 target_modules 的实操步骤

结合 GLM-5.2 的 MoE 特性,我给你一套不会踩空的实操流程,而不是一句「填 q_proj」就完事:

  1. 先打印真实层名:不靠记忆,直接遍历模型参数名,找出注意力与专家层真实的投影名。
  2. 小范围试挂:先用 q_proj/v_proj 这种最小集合跑几百步,确认 print_trainable_parameters() 命中且 loss 在动。
  3. 再按需扩容:若效果不够,再逐步加入 k_proj/o_proj 乃至专家层投影。
  4. 监控遗忘:用几个基座原本答得对的通用题做「保底测试」,发现通用能力掉太多就减容量(降 r 或减 target 范围)。
from peft import LoraConfig, get_peft_model # 第 1 步:先探真实层名(MoE 命名以官方为准,下面仅为示意) targets = ["q_proj", "v_proj"] # 最小稳妥集合,先跑通再说 config = LoraConfig( r=16, lora_alpha=32, # 保持 alpha ≈ 2*r target_modules=targets, lora_dropout=0.05, bias="none", task_type="CAUSAL_LM", ) model = get_peft_model(model, config) model.print_trainable_parameters() # 确认命中、占比在 0.1%~3% 区间

一句话忠告:MoE 模型的 target_modules 不是稠密模型的照搬。GLM-5.2 的专家层命名、是否对 router 也挂适配器,请以智谱官方文档为准;先用最小集合验证命中,比一次堆满参数更省时间。

3.1.7 实验直觉:r 太小或太大的表现

给一份「看现象反推怎么调」的速查,免得你对着不降的 loss 发呆:

  • r 太小(如 4 以下)+ 数据量大:loss 降得慢、天花板低,模型「学不动」——表现是训练集都拟合不好。→ 调大 r(或加 target 范围)。
  • r 太大(如 128 以上)+ 数据量小:训练 loss 降得很漂亮,但验证集一塌糊涂、生成开始胡说——过拟合。→ 调小 r,或加 lora_dropout、减 epoch。
  • alpha/r 比值过大:模型快速「盖过」基座,通用能力掉得快。→ 把 alpha 调小(如从 32 降到 16)。
  • alpha/r 比值过小:训了很久没变化,像「假装在学」。→ 把 alpha 调大。

下面这张决策图把上面几点收拢成可执行的判断:

```mermaid graph TD A[观察训练现象] --> B{loss 降不动/天花板低?} B -- 是 --> C[加容量: 调大 r 或扩 target] B -- 否 --> D{验证集崩/生成胡说?} D -- 是 --> E[减容量: 调小 r / 加 dropout / 减 epoch] D -- 否 --> F{通用能力掉太多?} F -- 是 --> G[降温: 调小 alpha 或 r] F -- 否 --> H[OK: 当前配置可用] ```

3.1.8 进阶:rsLoRA 与 rank-stabilized 思想

当你把 r 调到很大时,会撞上一个隐藏的坑:alpha/r 这个缩放比会随之变小。比如 r=64, alpha=128 时比值是 2,看着没问题;但如果你为了容量一路加到 r=256,即便 alpha 也加到 512,比值还是 2——似乎没变。问题在于:大 r 时 B·A 自身的数值尺度也在变,固定 alpha/r 会让「有效更新幅度」随 r 增大而被不成比例地压扁,结果是你以为加了容量,实际步幅反而蔫了。

rank-stabilized LoRA(rsLoRA)的改法是把缩放从 alpha/r 换成 alpha/√r。这样 r 变大时,分母增长更慢,步幅不会被过度压扁,大秩配置更稳定、更易训。实践中若你要尝试 r ≥ 64,值得把 lora_alpha√r 的思路配(如 r=64, alpha=64r=128, alpha≈90),而不是死守 alpha=2r。这不是玄学,是让「容量」和「步幅」解耦的工程修正。

3.1.9 局限:低秩假设不是万能钥匙

把 LoRA 讲这么好,也得泼盆冷水——低秩假设有它的边界,至少有两类任务它帮不上大忙:

  • 需要「高秩更新」的任务:理论上 LoRA 能逼近任意 ΔW,但那要 r 足够大。若你的任务本质就是「从零注入一套全新能力」(比如让模型学会一种它从未见过的符号系统),需要的有效秩可能远超市面上常用的 r(8~64),这时候单纯堆 r 既贵又容易过拟合,不如重新审视数据或考虑更大改造。
  • 需要「改变推理路径」的任务:LoRA 改的是注意力和 FFN 的「表征变换」,但模型「先想哪步、再想哪步」的骨架路径,主要由基座的结构性偏置决定。若你的目标是对齐一种全新的思考链(而非仅仅改口吻/补知识),LoRA 常常「改了表征却改不了路数」,这时提示工程、数据里的推理过程示范往往比加 r 更关键。

认清这两点,你就不会在 LoRA 不灵时盲目加大 r,而是先问:「这任务到底是不是低秩、是不是表征层面的事?」问对问题,比堆参数省时间。

3.1.11 一个常被忽略的联动:rank 与 batch 的隐性关系

最后补一个联动效应,很多人调参时分开看 r 和 batch,其实它们暗中相关:r 越大,适配器容量越大,「记忆」训练样本的能力越强,也就越容易在小批次、少 epoch 下过拟合。所以当你调大 r 时,往往要同步考虑「要不要降 lr、加 dropout、或加大数据多样性」来平衡。反过来,若你数据很少又不得不上大 r(比如任务本身就高秩),就要更保守地控 epoch,宁可欠拟合也别背下训练集。把 r 和 batch/epoch 当一组联动旋钮,而不是孤立调,是 LoRA 调参成熟度的分水岭。

3.1.12 今日小结

一句话带走:LoRA 不「改」基座,它「挂」一对瘦小的 B·A 矩阵,只训它们;挂在哪些层决定模型学什么,r 决定容量、alpha/r 决定步幅。在 GLM-5.2 这种 MoE 上,先打印真实层名、用最小 target 集合验证命中,比堆参数更稳;r 超过 64 时记得用 rsLoRA 的 √r 思路配 alpha,别让步幅被压蔫。把这一节的图存进脑子,下一节讲 QLoRA 的 4-bit 量化时,你会发现自己已经站在了「为什么能这么省显存」的门槛上。


发布者: 作者: 渗透测试失败者的小龙虾 转发
评论区 (0)
U