
“LoRA 为什么必须把一个矩阵初始化为 0”——这个问题我最早是在一次技术评审上被问住的。当时我刚写完一版手搓 LoRA还得意于自己把公式背得滚瓜烂熟结果对面一句“为什么偏偏是 B 置零而不是 A 置零换成 1e-6 行不行”我当场卡住。回去翻了实现代码和原始设计思路又跑了几个对比实验才彻底想明白。先说结论这个零初始化不是顺手写的工程小技巧而是整套 LoRA 数学设计的地基一旦动了它微调的稳定性、超参数语义、梯度流方向全部会变味。这篇文章就把整个推导和实验过程摊开讲。1. 先看一个我手写 LoRA 时踩出来的反例B 不初始化为零会发生什么很多讲 LoRA 的文章都把“B 初始化为 0A 初始化为高斯随机”一笔带过好像这只是实现惯例。我第一次写代码时也这么想既然 A 已经随机了B 用 0.001 的小常数做初始化总比严格零更“灵活”吧结果这个决定让我多烧了两周时间。1.1 复现方式一个十几行的简化实现我先交代实验配置。假设要微调一个预训练好的文本编码器输入维度 d768LoRA 秩 r8旁路矩阵 A 的形状是 (r, d)B 的形状是 (d, r)更新公式是W W0 (α / r) · B · A其中 W0 冻结α 是缩放因子我固定为 16。对比组只有一处不同对照组把 B 严格初始化为 0实验组把 B 初始化为全矩阵填充 0.001。A 两组都用相同的随机种子初始化。优化器用 AdamW学习率 2e-4训练 10 个 epoch数据来自某个中文文本分类任务。朴素地想0.001 相对于预训练权重动辄几十上百的范数来说微不足道应该不会有什么影响。前向传播也确实是y W0 · x (α / r) · B · A · xB 非零旁路输出顶多是主输出的千分之一量级。但我忽略了一个事实预训练模型在 W0 这个状态下是经过了长时间优化才收敛到的好状态任何偏离无论多小都会改变模型的实际函数表示。1.2 我从这个反例里看到的三个异常信号实验组跑到第三个 epoch 的时候问题开始显性化。第一训练开始前模型在下游测试集上的初始 loss 比对照组高。同一个预训练权重同一个评测集对照组因为 B0初始输出和原模型完全一致所以 loss 就是最初的“冷启动值”实验组因为 B 带了一个小扰动初始输出已经有偏差表现为 loss 凭空高了零点几个点。这说明模型在训练开始前就已经“不在原来的状态”了。第二前几百个 step 的梯度范数明显偏大。由于旁路在初始时就向输出注入了随机方向的扰动反向传播到旁路参数的梯度不仅要拟合任务信号还额外承担了“抹掉这个随机扰动”的任务。对照组的梯度范数曲线在前 100 步单调下降实验组则出现了一段诡异的平台期。深度学习里平台期一般意味着优化器需要先做无用功。第三训练结束后实验组在下游任务上的指标比对照组低了大概 1.5 个百分点。旁路矩阵的最终范数也大幅超过对照组。我后来复盘时意识到非零初始化的 B 让优化器在“错误起点”上额外走了一段路学习率在这种偏离状态下又显得太大导致旁路参数最终落在了一个更嘈杂的区域。如果你也想复现很简单在任意一套 LoRA 实现里把nn.Parameter(torch.zeros(...))改成nn.Parameter(torch.full(..., 0.001))其余全部不动跑一个 epoch 观察训练初期 loss 和梯度范数立刻能看到差异。想看更极端的效果把 0.001 换成 0.01差异会更明显。这个反例其实已经能回答初始化的部分意义了零初始化保证模型在训练开始前的函数输出和预训练模型“严格相等”而不是“约等于”。2. 为什么必须是零预训练权重是“已知的好状态”而微调是在它附近找更优点既然实验证明非零小扰动有害那为什么有害这就要回到微调这个动作的本质上去看。2.1 微调的本质是在好点邻域内搜索更优点如果把预训练权重 W0 看作参数空间中的一个点那么它是经过了海量数据优化后到达的一个相对优秀的点。下游微调并不是要从头训练一个新模型而是希望在 W0 附近找到一个更贴合目标任务的点。这个过程很像你在一个已经调平的三脚架上拧螺丝三脚架目前是稳的你的任务是把桌面再调高两厘米。合理的操作是在现有基础上微调螺丝如果先把三脚架的一只腿掰歪再开始调那所有后续操作都同时包含了“扶正”和“调高”两个目标难度陡增。LoRA 用低秩旁路来实现这个“微调”动作。旁路方程是ΔW (α / r) · B · A如果 B 初始为 0那么训练开始瞬间 ΔW 0模型参数就是 W0模型行为与预训练模型完全一致。这意味着优化器从第 1 步开始就站在“已知的好点”上做局部搜索。每一步的更新都会谨慎地沿着任务梯度移动不会出现一开始就偏离原有行为空间的问题。2.2 前向传播公式零初始化保证“函数级”相等不只是数值上相等很多人觉得零初始化只是在数值上让 ΔW 等于 0。其实更准确的说法是它让整个模型对应的输入-输出函数在初始时刻与原模型完全相同。对任意输入 x都有y W0 · x这是逐个样本、逐个 token、逐个输出维度都成立的等式。而非零初始化时y W0 · x (α / r) · B0 · A0 · x多出来的这一项依赖于 x也依赖于 A0 的随机取值。对于一个 batch 内的不同样本这个扰动的方向和幅度都不同相当于给原模型叠了一层随机噪声函数。之前的实验结果已经表明这个噪声不是“小到可以忽略”而是会被梯度优化器检测到并被修正消耗实实在在的训练预算。2.3 “很小的 epsilon”为什么不能等价于零读者可能会说你上面那个实验用的是 0.001如果把 B 初始化成 1e-8 呢总可以忽略吧数学上B 趋于零时扰动确实趋于零看似等价。但实际实现中这个“趋于零”的极限过程并不安全。原因有三个。第一A 的默认初始化往往是标准正态分布每个元素都是 O(1) 量级。B0 · A0 做矩阵乘法后旁路输出是 r 个随机方向的线性叠加。即使 B0 每个元素是 1e-8乘积作用在 x 上之后扰动幅度还会被 d 维向量求和放大。项数越多累积误差越大并不是直观上那个 1e-8 直接等于扰动幅度。第二非零初始化会让优化器在第一个 step 就同时接收到两个任务一是拟合目标任务二是消除初始扰动对输出的影响。这两个任务在梯度上是耦合的导致学习率的选择变得极其敏感。学习率小了扰动迟迟消除不掉学习率大了扰动带来的梯度又会引发震荡。第三也是最微妙的一点预训练权重是一个承载了复杂知识分布的高维对象。对它而言即使是很小的函数扰动也可能在某些输入子空间里被显著放大。模型在高维空间里对噪声远比我们想象的要敏感。保险的方案永远是精确地让 ΔW 初始为零而不是“近似为零”。3. 梯度流的约束为什么不能让 A 也为零或者让两个矩阵都随机初始化前向传播解释了 B0 保证了初始状态不变但还不足以说明整套初始化方案的完整逻辑。一个很自然的疑问是既然 ΔW 初始为零这么好那我把 A 和 B 全置零不就行了答案是不行因为梯度流会被彻底堵死。3.1 直接推导两个矩阵的梯度公式设旁路输出为t (α / r) · B · A · x模型损失 L 对 t 的梯度记作 g对 B 和 A 的梯度分别是∂L/∂B (α / r) · g · xᵀ · Aᵀ ∂L/∂A (α / r) · Bᵀ · g · xᵀ这里能看到一个非常关键的不对称性∂L/∂B 的表达式中乘了 A而 ∂L/∂A 的表达式中乘了 B。换句话说A 是否为零决定了 B 能否获得梯度B 是否为零决定了 A 能否获得梯度。如果 A 和 B 都初始化为零那么第一个 step 里∂L/∂B 0∂L/∂A 0两个矩阵都得不到任何梯度信号参数永远停在零处训练直接失效。所以两个矩阵不能同时为零。如果 A 为零、B 非零那么第一个 step 里 ∂L/∂B 乘以 A仍然为零B 冻结A 虽然有梯度因为 B 非零但 A 的更新无法影响输出因为 B·A 依然为零。这相当于一边在动另一边被焊死整个旁路始终卡在输出为零的状态。所以 A 0, B ≠ 0 这种组合也不行。只剩下唯一合理的选择A 非零B 为零。此时 ∂L/∂B 非零B 可以在第一步获得梯度并开始更新∂L/∂A 严格为零A 暂时保持不动。等到第二步 B 不再是零矩阵A 才开始获得梯度并更新。这个先后顺序不是缺陷而是设计好的节奏。3.2 如果两个矩阵都用高斯随机初始化会破坏梯度流的节奏那有人会问A 和 B 都随机初始化不是也能得到梯度吗是能但会引入两个新问题。第一个问题是“方向延迟”。随机初始化意味着初始 ΔW 有一个随机方向。这个方向是从高斯分布里无意义地抽出来的和目标任务没有任何关系。优化器开始工作后第一优先级不是学任务而是把旁路从那个随机的初始方向掰回正常轨道。越是接近满秩的大模型这个“掰回来”的过程越浪费有效训练步数。第二个问题是“幅度耦合”。B 和 A 都有非零初值时ΔW 的有效尺度 (α/r) · ‖B‖ · ‖A‖ 拆不开。你调学习率实际上是在同时调整三个不独立的东西学习率本身、B 的初始尺度、A 的初始尺度。超参数变成了一锅粥。零初始化把这锅粥里的一个变量直接清零让 ΔW 的初始尺度严格为零剩下的 α 和 r 才能独立地控制更新幅度。3.3 零初始化的本质打破 A 和 B 的角色对称性把前向公式和反向公式放在一起看会发现这套初始化的深层含义是给两个矩阵分配了完全不同的角色。A 是随机初始化的它承担的是“承接梯度”的职责B 是零初始化的它承担的是“构建输出方向”的职责。训练中 B 先被激活决定在当前随机方向词典 A 上选择哪些方向来修正输出随后 A 根据 B 的选择调整词典本身。这种非对称设计类似把微调问题分解成了两步先在固定方向的基底上做线性组合B 的工作再优化基底本身A 的工作。如果两个矩阵都随机初始化角色就混在一起优化器既要同时适应随机方向还要承担初始扰动带来的额外负担。零初始化用最干净的方式切开了这两个阶段。4. 缩放因子 α/r 和零初始化的配合超参数语义为什么必须建立在零初始化之上LoRA 的参数更新公式里总有一个常数项ΔW (α / r) · B · A很多教程把这个系数解释成“控制 LoRA 在最终权重中的占比”但没有人细说为什么它必须结合零初始化来看。这里其实藏着一个很容易被忽略的设计逻辑。4.1 α 和 r 是你的第二套学习率常规微调中权重更新的幅度由学习率单独控制。LoRA 中多了一个维度旁路矩阵 B 和 A 的乘积尺度。即使学习率不变如果 B·A 的范数因为初始化不同而发生变化最终 ΔW 的更新幅度也不同。α / r 这个系数就是用来显式管理旁路尺度的。零初始化让这个管理变得非常干净。训练开始瞬间ΔW 为零α 取什么值都不会影响初始模型行为。只有训练开始后B 和 A 逐步变大α / r 才开始真正影响每一步的更新幅度。这等于把“模型初始状态”和“更新幅度控制”两个问题彻底解耦了。打个比方α / r 就像是音量旋钮而零初始化确保你调整音量旋钮时音响本身并未开始播放任何声音。如果 B 非零初始化那么 α / r 在第一步就同时影响“初始播放音量”旋钮的语义就变脏了。4.2 非零初始化会让超参数的可解释性崩坏假设你把 B 初始化为 0.001那初始 ΔW 的范数大约等于 (α / r) · β其中 β 是一个由 A 随机性和 r 决定的量。这时你面对的是三个纠缠的控制量α / r、B 初始尺度、A 初始尺度。实验中发现模型表现不佳时很难判断该调学习率、调 α还是调初始尺度。对照实验里我踩过这个坑非零初始化那组的 loss 曲线发散我下意识把学习率从 2e-4 降到 1e-5结果发散了整整一倍时间。后来改成 α 从 16 降到 4也没有改善。最后才意识到问题根源在于初始扰动一直在干扰早期训练和 α、学习率的关系并不大。而零初始化下α 的语义明确它只控制旁路更新相对于学习率的放大倍数。最常见的设置是 α 2r也就是旁路最终更新幅度大约为主权重更新幅度的 2 倍。这个经验值只有在零初始化的前提下才成立。4.3 实操上的一个规律经过多次踩坑我形成了一个判断习惯如果 LoRA 微调后期出现下游指标抖动我会优先调小 α而不是动学习率。这个经验依赖的正是零初始化的规范性——α 是旁观路更新的独立旋钮拧它不会波及初始状态。一旦初始化改成非零这个经验就不成立了你会进入一个所有旋钮互相牵连的灰色地带。5. 从“必须为零”到“能不能不为零”LoRA 变体在初始化路线上的迭代理解了标准 LoRA 为什么必须零初始化之后再去看各种 LoRA 变体会有完全不同的视角。这几年的变体研究很大程度上都在回答同一个问题如果我想打破零初始化的限制需要付出什么代价、又要做什么补偿5.1 PiSSA 的思路非零初始化但保持“有结构的一致性”PiSSA 这类方法走的路是不再要求初始 ΔW 严格为零而是把预训练权重 W0 做主成分分解用主奇异方向来初始化 A 和 B同时把残差部分留在原权重中。这样模型初始输出虽然不是严格等于 W0但偏离的是“低秩主成分”那一小部分仍然是有结构的、可控的偏离而不是高斯随机噪声。它的理论基础在于预训练模型的权重矩阵往往自带低秩结构。把最显著的那些奇异方向直接预置进旁路相当于让旁路从第一轮迭代就不用从头摸索正确的更新方向收敛速度自然变快。但注意PiSSA 能这么做的前提是它有办法保证旁路初始值和残差之和能近似还原 W0。如果初始化用的主成分不可靠或者分解过程有严重截断误差同样会引入不期望的初始扰动。所以 PiSSA 并不是推翻了“零初始化”的逻辑而是在“初始状态不被破坏”和“初始更新方向更优”之间做了一个交易牺牲一点点“严格的初始一致性”换取更快的收敛。这对部分场景来说很值但它引入了额外的计算开销和非线性风险并不适合无脑替换标准 LoRA。5.2 rsLoRA 和 DoRA 对初始化机制的补充说明rsLoRA 关注的是另一个被零初始化掩盖的问题LoRA 的 rank 变大时B·A 的范数会随 rank 增加而增大导致模型实际更新幅度受 rank 影响。它通过调整缩放因子和 rank 的关系来解耦本质上是在零初始化提供的基础上进一步让“更新尺度”不受低秩维度影响。DoRA 则把权重分解成幅度和方向两个部分在训练时将 LoRA 旁路作用在方向分量上。它同样需要保证训练开始时方向分量不被随机旁路污染所以旁路的初始状态设计依然沿用了“保持原模型行为”的核心思想只是把形式从“加法修正”变成了“方向旋转”。这几个变体放在一起看会发现所有方案都在做同一件事在「参数初始化方式」和「模型初始状态」之间寻找平衡。标准 LoRA 选择了最保守的零初始化换来的是几乎为零的初始化成本和清晰超参数语义激进派选择非零结构初始化换来的是更快收敛但需要额外算力和更复杂的初始化逻辑。5.3 实践建议什么时候坚持零初始化什么时候尝试非零初始化使用场景推荐方案理由日常下游任务微调标准 LoRAB 置零初始化零成本超参数语义清晰稳定数据量小、算力紧张标准 LoRA减少不可控变量优先保证收敛稳定追求微调收敛速度PiSSA 风格初始化初始更新方向更优但需接受额外分解成本大规模基座模型微调标准 LoRA初始化成本越低越好避免附加风险训练指标反复抖动先检查 α再考虑换初始化方案根源多半在更新尺度而非初始化路线从我个人的项目经验看90% 的场景里标准 LoRA 的零初始化都是最优解。所谓“非零初始化加速”往往只有在你知道当前瓶颈确实是收敛速度时才有意义否则它只是给调试增加新的维度。我自己的习惯是接到新任务先跑一遍标准 LoRA 拿基线确认 loss 曲线和指标稳定后再考虑要不要换初始化策略。如果有人再问“LoRA 为什么必须把一个矩阵初始化为 0”我建议不要只回答“为了保持初始权重的分布”而是把前向公式、梯度传播、超参数语义这三层逻辑串起来讲。先说明 ΔW 的初值必须为零才能保持初始输出函数不变再说明梯度流要求 A 和 B 不能同时为零或者同时随机最后说明 α / r 的控制语义必须建立在零初始化的前提上。这一整套推导下来才算真正理解了这个“小细节”的分量。