3.1 LoRA 低秩分解的数学直觉与参数作用位置:为什么只动几百万参数就能改模型 读者读完这一节,应该能拿走一个清晰的心智模型:LoRA 没有去「改」底座那几亿甚至几十亿的参数,而是在旁边「挂」了两个又瘦又小的矩阵,只训练这两个小矩阵;同时你会明白,挂在哪些层(targetmodules)直接决定了你的微调在「教模型什么」。前者解释了「为什么便宜」,后者解释了「为什么有效」。 3.1.1 先说结论:全量微调的更新,本质上也常常是低秩的 很多人对 LoRA 的直觉是「它用低秩近似偷了懒,所以是次优的」。但一个反直觉的事实是:哪怕是标准的全量微调,权重更新矩阵 ΔW 在经验上也往往是低「内在秩(intrinsic rank)」的。 什么意思?
读者读完这一节,应该能拿走一个清晰的心智模型:LoRA 没有去「改」底座那几亿甚至几十亿的参数,而是在旁边「挂」了两个又瘦又小的矩阵,只训练这两个小矩阵;同时你会明白,挂在哪些层(target_modules)直接决定了你的微调在「教模型什么」。前者解释了「为什么便宜」,后者解释了「为什么有效」。
很多人对 LoRA 的直觉是「它用低秩近似偷了懒,所以是次优的」。但一个反直觉的事实是:哪怕是标准的全量微调,权重更新矩阵 ΔW 在经验上也往往是低「内在秩(intrinsic rank)」的。
什么意思?假设你有一个 d×d 的权重矩阵(比如 4096×4096),全量微调会去学一个同样大小的 ΔW。但研究者发现,这个 ΔW 的有效信息其实可以用远少于 d 个维度来承载——用 PCA 之类的方法去看,ΔW 的奇异值(svd 分解后的特征值)往往前几十个就占了绝大部分能量,后面一长串几乎是零。换句话说,模型要「学会一件新任务」这件事本身,并不需要一个满秩的更新,它只需要一个低秩方向的偏移。
LoRA 做的,就是不去等全量微调慢慢把 ΔW 学出来,而是直接假设 ΔW 是低秩的,只参数化那个低秩部分。这既省了参数,又常常和全量微调的效果相当,因为它本来就是任务内在需要的东西。
下面这张图把「全量微调」和「LoRA」放在同一视角下对比:
LoRA 的核心公式只有一行。原始的前向计算是:
h = W₀ · x
其中 W₀ 是预训练好的、被冻结的权重(下标 0 表示「原始」)。LoRA 不改 W₀,而是额外加一项:
h = W₀ · x + ΔW · x = W₀ · x + B · A · x
这里:
r × d_in(降维),d_out × r(升维),乘积 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 就是你能容纳的「调整容量」。
实践上有个初始化细节值得知道:A 通常用随机高斯初始化,B 初始化为全零。这意味着训练刚开始时 B·A = 0,模型行为和没加 LoRA 的基座完全一样(ΔW=0),然后随着训练才慢慢长出更新。这个「从零起步」的设计很妙——它保证了 LoRA 不会在训练第一步就破坏基座已有的能力,调参时也更稳。
公式讲完了,但「挂在哪儿」才是 LoRA 真正决定效果的地方。在 Transformer(以及 GLM-5.2 这类自回归大模型)里,每层的计算主要分成两大块:
q_proj、k_proj、v_proj、o_proj 四个线性投影。gate_proj、up_proj、down_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)。
下面这张图示意了「注入位置」与「模型学会了什么」的对应关系:
我的建议(基于踩坑经验,不是铁律):
q/k/v/o_proj 通常就够,容量小、不易过拟合。gate_proj/up_proj/down_proj 也加上,容量更大。get_peft_model 配合 model.print_trainable_parameters() 确认真实命中的层名,再去细调专家层。GLM-5.2 的具体模块命名请以智谱官方文档为准,不要凭记忆硬填——这是 MoE 上最常见的「适配器没接上」元凶。很多人以为 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 的更新反而可能因为缩放太小而几乎不动。两者要一起看。
这里补一个「为什么能 work」的直觉,帮助你在调参时心里有底,而不是把它当黑盒。
大模型是**过度参数化(over-parameterized)**的:它实际需要的「有效维度」远小于参数量。就像一家大公司,处理某个新业务不需要全员重新培训,只需要调一个精干的专项小组。LoRA 就是这个「专项小组」——它不动全员(冻结 W₀),只训练小组(B、A)。
再加上 3.1.1 说的「任务内在低秩」,这个小组的维度 r(比如 8~64)刚好够用。于是:
这就是为什么 LoRA 能在「一张消费级显卡 + 几千条数据」条件下,就把 GLM-5.2 调成某个垂直领域的样子。它不是「偷工减料」,而是「精准用工」。
结合 GLM-5.2 的 MoE 特性,我给你一套不会踩空的实操流程,而不是一句「填 q_proj」就完事:
q_proj/v_proj 这种最小集合跑几百步,确认 print_trainable_parameters() 命中且 loss 在动。k_proj/o_proj 乃至专家层投影。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 也挂适配器,请以智谱官方文档为准;先用最小集合验证命中,比一次堆满参数更省时间。
给一份「看现象反推怎么调」的速查,免得你对着不降的 loss 发呆:
lora_dropout、减 epoch。下面这张决策图把上面几点收拢成可执行的判断:
当你把 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=64;r=128, alpha≈90),而不是死守 alpha=2r。这不是玄学,是让「容量」和「步幅」解耦的工程修正。
把 LoRA 讲这么好,也得泼盆冷水——低秩假设有它的边界,至少有两类任务它帮不上大忙:
认清这两点,你就不会在 LoRA 不灵时盲目加大 r,而是先问:「这任务到底是不是低秩、是不是表征层面的事?」问对问题,比堆参数省时间。
最后补一个联动效应,很多人调参时分开看 r 和 batch,其实它们暗中相关:r 越大,适配器容量越大,「记忆」训练样本的能力越强,也就越容易在小批次、少 epoch 下过拟合。所以当你调大 r 时,往往要同步考虑「要不要降 lr、加 dropout、或加大数据多样性」来平衡。反过来,若你数据很少又不得不上大 r(比如任务本身就高秩),就要更保守地控 epoch,宁可欠拟合也别背下训练集。把 r 和 batch/epoch 当一组联动旋钮,而不是孤立调,是 LoRA 调参成熟度的分水岭。
一句话带走:LoRA 不「改」基座,它「挂」一对瘦小的 B·A 矩阵,只训它们;挂在哪些层决定模型学什么,r 决定容量、alpha/r 决定步幅。在 GLM-5.2 这种 MoE 上,先打印真实层名、用最小 target 集合验证命中,比堆参数更稳;r 超过 64 时记得用 rsLoRA 的 √r 思路配 alpha,别让步幅被压蔫。把这一节的图存进脑子,下一节讲 QLoRA 的 4-bit 量化时,你会发现自己已经站在了「为什么能这么省显存」的门槛上。