二、 架构与实现细节¶
- 原始 LoRA 论文主要将适配器加在 Transformer 的哪些权重矩阵上?为什么通常选择 $ W_q $ 和 $ W_v $ 而非全部?
直接回答:¶
原始 LoRA 论文(Hu et al., 2021)主要将适配器应用在 Transformer 的注意力权重上,具体来说是 $ W_{q} $ (Query)、 $ W_{k} $(Key)、 $ W_{v} $(Value)和 $ W_{o} $(Output)。但论文通过大量实验发现,同时作用于 $ W_{q} $ 和 $ W_{v} $ 是最具性价比的配置。单独加在 $ W_{q} $ 或 $ W_{v} $ 上效果都不错,而加在 $ W_{k} $ 上的收益相对较小,加在 $ W_{o} $ 上则基本没有额外提升。因此,社区后来普遍采用“仅对 $ W_{q} $ 和 $ W_{v} $ 添加 LoRA”作为默认实践。
为什么是 Attention 权重而不是 FFN?¶
这个选择背后的直觉非常深刻。你可以这样理解:在一个 Transformer 层中,注意力机制负责“看哪里”,而 FFN 负责“记住什么”。
1. 注意力机制负责动态路由¶
Self-Attention 的核心功能是根据输入内容动态地决定当前 token 应该关注哪些其他 token。 $ W_{q} $ 和 $ W_{v} $ 是这个路由机制的核心控制旋钮。 $ W_{q} $ 决定了“我要找什么信息”, $ W_{v} $ 决定了“我找到了信息之后,用什么形式把它提取出来”。对一个预训练好的模型进行任务适配,很多时候不是在教模型新知识,而是调整它的注意力焦点。比如,在情感分析任务中,模型需要学会更加关注带有情感色彩的形容词;在代码生成任务中,模型需要关注语法关键字。这种“注意力聚焦点的转移”,恰好可以通过微调 $ W_{q} $ 和 $ W_{v} $ 来实现。
2. FFN 负责知识存储¶
FFN 层的权重矩阵可以类比为模型的“知识库”或“记忆库”。它把从注意力层收集来的信息进行加工和存储。全量微调 FFN 相当于去大规模地修改这个知识库,这不仅计算量大,而且容易造成灾难性遗忘(因为覆盖了原有的通用知识)。LoRA 选择主要微调注意力权重,相当于保留原有的知识库不动,只是调整模型提取知识的方式和顺序。这是一个更安全、更高效的适配策略。
为什么 $ W_{q} $和 $ W_{v} $的“搭档”效果最好?¶
这是一个经过实验验证的组合。一种直观理解是: $ W_q $ 控制“查询什么”, $ W_v $ 控制“提取什么”。只调 $ W_q $,相当于你改变了提问的方式,但提取信息的方式还是老的,可能提不准;只调 $ W_v $,相当于提问方式没变,但提取信息的方式变了,也可能不匹配。两者同时调整,让“提问”和“提取”协同变化,才能最大化地重塑注意力行为。而 $ W_k $ 作为被查询的对象(提供“标签”),其变化的影响相对较小; $ W_o $ 作为多头合并后的输出投影,由于其输入是多头注意力的拼接结果,维度更高、更复杂,仅仅靠低秩适配的效果往往不如直接适配各自的 Query 和 Value 来得精准。
一 个形象的理解:¶
把预训练模型想象成一个经验丰富的图书管理员(LLM),他的大脑里装满了百科全书的索引(Wk)和检索技巧(Wq),也知道如何从书中精准摘抄内容(Wv)。现在你要让他去管理一个法律图书馆,你不需要重写他脑中的百科全书(全量微调),甚至都不需要给他全新的检索技巧,你只需要微调他提问题的角度(Wq)和做笔记的方式(Wv),他就能非常出色地完成新任务。这就是 LoRA 只加在 Wq 和 Wv 上的核心逻辑。
10. 是否可以将 LoRA 应用于 FFN 层或 Embedding 层?这样做通常会带来什么影响?¶
直接回答:可以,但这是一种策略性的取舍。
LoRA 作为一种低秩适配方法,在数学上可以被应用在任何线性变换层上,包括 FFN 层和 Embedding 层。事实上,很多后续研究(如 QLoRA)和工业界的实践表明,将 LoRA 扩展到所有线性层(包括 FFN 和 Embedding)往往会带来更好的效果上限,但代价是更高的参数量和训练成本。
应用于FFN层的影响:¶
- 正面影响(为什么有时需要它):FFN 层占据了 Transformer 绝大部分的参数量(通常在 2/3 以上)。有些下游任务,特别是知识密集型任务(如领域专家问答、专业术语理解),确实需要调整模型内部的知识表征。此时,仅靠调整注意力焦点 $ (W_{q}, W_{v}) $ 是不够的,必须对 FFN 这个“知识库”进行适应性修改。把 LoRA 加到 FFN 上,相当于允许模型在低秩约束下,安全地对部分知识进行“增删改”。
2. 负面影响(代价是什么):¶
可训练参数量激增:FFN 的线性层维度通常是隐藏维度的 4 倍(在 SwiGLU 等门控结构下甚至更高)。对一个隐藏维度为 4096 的模型,Attention 中的 $ W_q $ 是一个 $ 4096 \times 4096 $ 的矩阵,而 FFN 的第一层可能是 $ 4096 \times 11008 $。即使使用相同的秩 $ r = 8 $,FFN 上的 LoRA 参数量也会远大于 Attention 上的。这增加了显存和训练时间。
过拟合风险增加:当你在FFN这个"知识库"上开辟了修改通道,如果下游任务的数据量不够大,模型可能会过度调整其内部知识,导致灾难性遗忘。这就是为什么对于小数据集,通常只建议用LoRA调整Attention层的原因。
应用于 Embedding 层的影响:¶
- 正面影响:Embedding 层是模型对 token 的最原始表征。有些任务,特别是涉及新概念、新实体或专有名词的任务,可能原始 token 的语义向量并不能很好地表达这些新知识。微调 Embedding 层可以帮助模型从“词根”上就改变对某些 token 的理解。
2. 负面影响:¶
参数量巨大:Embedding 层的参数量是 vocab_size x hidden_dim。对于词表大小通常为 32k 到 100k+ 的大模型,Embedding 层本身就是千万甚至上亿参数。即使 LoRA 的秩很小,B 矩阵的维度是 hidden_dim x r,A 矩阵的维度是 r x vocab_size。当 vocab_size 很大时,A 矩阵的参数量会非常惊人,这就违背了 LoRA“参数高效”的初衷。
效果不稳定:直接修改 Embedding 层相当于改变了整个模型输入的分布,这可能导致模型内部各层的激活值发生连锁式的剧烈变化,增加了训练的不稳定性。
结论:这是一个效果与成本的权衡。在工业界高标准的微调实践中(如 QLoRA 论文),通常推荐“所有线性层全加 LoRA”以获得最佳效果。而在快速实验、数据量少或资源受限的场景下,仅给 $ W_q $, $ W_v $ 加 LoRA 是稳健首选。
- 如何为给定的基座模型选择秩 $ r $?过小的 $ r $ 和过大的 $ r $ 各有什么后果?
选择秩 r 的原则:
这既不是玄学,也没有一个固定的万能数值。选择 r 需要综合考量三个核心因素:任务难度、数据量和预算。
-
任务难度和领域差异:这是最重要的因素。如果下游任务和预训练任务非常接近(例如,模型本来就在大量百科文本上训练,你要微调一个百科问答),r=4 甚至 r=1 可能就效果显著。如果领域差异巨大(例如,让一个通用模型去写特定格式的汇编代码),你可能需要 r=16 或更高,因为适配所需的“子空间维度”天然就更大。
-
数据量:数据量越大,过拟合风险越低,你可以安全地使用更大的 r 来从数据中学到更丰富的适配信息。反之,小数据集(如几百条样本)配大 r,极易导致模型死记硬背训练集,丧失泛化能力。
-
预算与工程约束:更大的 r 意味着更多的可训练参数、更大的显存占用、更多的计算量。在资源有限或需要快速迭代的场景下,选择较小的 r 是明智的。在很多项目中,r = 8 是一个经过广泛验证的“甜蜜点”,它在效果和资源消耗之间取得了极佳的平衡。
过小的 r 的后果(容量不足):
这就像用一个只有几个旋钮的调音台去处理一场复杂的交响乐。LoRA 模块的“表达能力”受限于矩阵的秩。如果秩 r 设置得过小,低秩矩阵的容量天花板就会变成一个紧箍咒。表现是:无论你怎么调整学习率、 $ \alpha $ 和训练步数,模型的训练损失下降到一定程度就停住了(underfitting),无法完全拟合训练数据中的复杂模式。在评估指标上,你可能会发现模型在简单任务上表现尚可,但在复杂推理或需要捕捉文本细节的任务上,永远差那么一口气。
过大的 r 的后果(收益递减与过拟合):
你会预期更大的 r 带来更好的效果,但实际情况往往是“收益递减”,甚至可能适得其反。
-
收益递减:大量实验表明,r=1 到 r=4 往往是提升最大的,从 r=4 到 r=8 提升变小,从 r=16 到 r=64 或更高,带来的效果提升微乎其微,甚至因为引入了过多参数导致训练困难。那个适配任务真正需要的“内在维度”,可能就只有那么大。
-
过拟合风险:当 r 远超任务所需时,LoRA 模块获得了“冗余的表达能力”。这些多出来的自由度没有被用来学习有意义的任务特征,而是被用来“记忆”训练数据中的噪声。结果就是:训练集上的 Loss 降得很低,但验证集和测试集上的表现反而下降。
实践建议:
可以从 r = 8 开始尝试,如果效果不理想且观察到明显的欠拟合(训练集 Loss 也高),再逐步增大到 r = 16 或 r = 32。如果一开始就过拟合,则减小 r 到 4 甚至 2。
- 在实践中,你如何决定对哪些层使用 LoRA?是只做 Attention 层,还是也做 MLP 层?有什么经验法则?
决策框架:
这是一个非常典型的工程决策问题。成熟的从业者会根据一张“优先级地图”来配置 LoRA,这张地图按照性价比(效果提升 / 参数量代价)排序:
-
第一优先:Attention 的 $ W_q $ 和 $ W_v $。这是性价比最高的选择。从 LoRA 提出至今,几乎所有实验都证明这是基准线。如果你只有非常有限的资源或需要快速得到结果,只做这两个矩阵就足够了。
-
第二优先:Attention 的 $ W_k $ 和 $ W_o $。在完成了第一步之后,如果想压榨更多性能,可以加上 $ W_k $ 和 $ W_o $,形成一个“Attention 全适配”的配置。通常这能带来额外的小幅提升,但对某些任务可能效果不明显。
-
第三优先:MLP/FFN 中的所有线性层。这是通往更高性能上限的大门,但也是参数量和计算量大幅增加的一步。当且仅当以下条件同时满足时,你会选择进入这个阶段:
任务要求极高:目前的 Attention-only 方案效果在业务指标上离合格线还有差距。
领域差异极大:任务涉及大量预训练模型未见过的专业知识或格式。
有充足的训练数据:数据量足以支撑大量可训练参数而不至于严重过拟合。
经验法则与决策理由:
• 法则一:从俭到奢。永远从“只做 $ W_{q} $, $ W_{v} $, r = 8”开始。把它作为你的 Baseline。只有在验证集上证明 Baseline 不满足要求时,才考虑增加更多层。
法则二:注意参数的“二八定律”。对于一个典型的Transformer模型,把所有LoRA都加在Attention层产生的可训练参数量,可能只占模型总参数量的不到1%。一旦你决定将LoRA扩展到所有MLP层,这个比例可能会迅速飙升到5%甚至更高。你需要确保这种投入换来的性能提升是值得的。
法则二:利用QLoRA等工作的“全加”思想。如果你的基座模型已经非常大(30B+)且你采用了QLoRA等极致的显存压缩技术,那么显存瓶颈已大大缓解。此时,心理模型可以转变为“只要GPU跑得动,就全加上”。因为当模型规模大到一定程度,其内在任务维度的复杂性使得仅靠Attention的适配可能不够,“全加”能提供最高的理论上限。
法则四:警惕过拟合。每当你增加 LoRA 的应用范围,都相当于给了模型更大的自由度。你必须监控训练集和验证集的损失曲线。一旦发现验证损失开始上升而训练损失持续下降,就要警惕:你扩展的 LoRA 层可能正在帮助模型“背诵”训练数据。此时应该考虑减少层数或增大 Dropout。
13. LoRA 的缩放因子 $ \alpha $ 通常如何设置?它与秩 r、学习率之间的关系是怎样的?¶
直接回答:
$ \alpha $ 是一个非常独特且强大的超参数,但它不是独立工作的,而是与 r 和 $ \eta $(学习率)构成一个联动的“控制三角”。
$ \alpha $ 与 $ r $ 的关系:
在原始 LoRA 论文和后来广泛的实践中,一个经典的配对法则是:让 $ \alpha $ 与 r 保持一个固定的倍数关系。最常见的设定是 $ \alpha = 2r $ 或 $ \alpha = r $。
这样做的目的是为了让 LoRA 模块的输出量级在改变 r 时保持相对稳定。
假设你使用 r = 8, $ \alpha = 16 $ 训练出了一个不错的模型。
现在你想尝试 $ r = 32 $,希望获得更大的容量。如果你不改变 $ \alpha $,那么 LoRA 输出项 $ \frac{16}{32}BAx = 0.5BAx $ 会被显著削弱,之前调好的学习率可能就不适用了。
· 但如果你遵循 $ \alpha = 2r $ 的法则,将 $ \alpha $ 也设为 64,那么 $ \frac{64}{32}BAx = 2BAx $,与之前 $ \frac{16}{8}BAx = 2BAx $ 的量级完全一致。
这样,r 就被解放出来,主要去承担“控制容量上限”的角色,而不再去干扰训练的动力学过程。
$ \alpha $ 与学习率 $ \eta $ 的关系:
这是另一个至关重要的关系,很多同学会混淆。可以这样理解:调大 $ \alpha $,近似于调大LoRA模块的等效学习率。
为什么会这样?我们在第一题的答案中详细推导过。LoRA 的参数更新量正比于 $ \eta \cdot \frac{\alpha}{r} $。在 r 固定的前提下, $ \alpha $ 和 $ \eta $ 对这个乘积的贡献是线性的。所以,如果你遇到一个任务,觉得模型学得太慢了,有两种等价的解决方案:
方案A:保持 $ \alpha $不变,把学习率 $ \eta $调大。
方案B:保持学习率 $ \eta $不变,把 $ \alpha $调大。
这带来了一个非常有用的解耦实践:¶
通常, $ \alpha $ 设置的比较大(如 16, 32, 64),意味着 LoRA 的等效学习率被放大了。这么做是因为 LoRA 参数本身只有非常少的一部分,如果只给它们一个很小的等效学习率,它们很难在有限的训练步数内,从“零初始化”的状态学习到足够显著改变模型行为的增量。因此, $ \alpha $ 是一个专属于 LoRA 模块的、独立于基础学习率的“学习率放大器”。
总结这个“控制三角”的稳定关系:
一个稳定的训练配置通常是:固定 $ \alpha/r $ 的比值(如 $ \alpha = 2r $),然后统一调整学习率 $ \eta $ 来控制全局的收敛速度。这保证了你在调整模型容量(r)时,不需要同时去修改 $ \eta $ 和 $ \alpha $,极大地简化了调参过程。
- 在 PyTorch 中实现 LoRA 时,如何使用 register_forward_hook 或自定义 nn.Module 来拦截线性层的输出?
这是一个非常实战的问题。在 PyTorch 中有两种主流方法来实现 LoRA,它们在复杂度和性能上有所取舍。
方法一:自定义 nn.Module 包装(推荐的生产级方案)
这是最优雅、最可控的实现方式。你不去破坏原有的模型结构,而是创建一个新的nn.Module,将原始层包裹进去。
代码块
1 import torch 2 import torch.nn as nn 3 class LoRALinear(nn.Module): 5 def init(self, original_linear: nn.Linear, r: int = 8, alpha: float = 16.0): super().init( # 1. 从原始层中提取参数,并将其冻结 self.in_features = original_linear.in_features self.out_features = original_linear.out_features
self.r = r self.alpha = alpha
保留原始权重,设为不可训练¶
self.register_buffer('weight', original_linear.weight.data.clone()) if original_linear.bias is not None: self.register_buffer('bias', original_linear.bias.data.clone()) else: self.bias = None
2. 初始化 LoRA 的 A 和 B 矩阵¶
A: (in_features, r) - 输入从高维降到低维¶
B: (out_features, r) - 低维特征升维到输出维,注意形状¶
为了与 h = Wx + BAx 等价,我们使用 B @ A 的形式¶
self.lora_A = nn.Parameter(torch.randn(r, self.in_features) * 0.02) self.lora_B = nn.Parameter(torch.zeros(self.out_features, r))
self.scaling = self.alpha / self.r
def forward(self, x: torch.Tensor) -> torch.Tensor: # 3. 前向计算 # 原始路径 result = nn.functional.linear(x, self.weight, self.bias) # LoRA 路径 lora_result = (x @ self.lora_A.T @ self.lora_B.T) * self.scaling return result + lora_result
优势分析:¶
完全控制:你可以完全掌控这个层的前向和反向传播行为,调试非常方便。
性能优越:PyTorch 可以对这个自定义层进行标准的计算图优化。
- 可合并性:你可以在推理前,很容易地在这个类内部实现一个 $ \text{merge_weights}() $ 方法,将 $ \text{self.weight.data} += (\text{self.lora_B} @ \text{self.lora_A}) * \text{self.scaling} $,然后把 $ \text{lora_A} $ 和 $ \text{lora_B} $ 设空,实现零延迟推理。
方法二:使用 register_forward_hook(快速实验方案)
这种方法不需要定义新类,可以快速在已有模型上进行“外科手术式”的修改。
代码块 def make_lora_hook(lora_A, lora_B, alpha, r): """创建一个 LoRA 前向钩子""" scaling = alpha / r def lora_hook(module, input, output): # input[0] 是当前层的输入 x
x = input[0]
计算 LoRA 的增量: BAX = lora_B @ (lora_A @ x)¶
这里为了与上面例子一致,注意矩阵维度¶
lora_update = (x @ lora_A.T @ lora_B.T) * scaling return output + lora_update return lora_hook
假设我们要改造 model 中的一个特定层¶
target_layer = model.layers[10].attention.query
创建 A, B 参数,并注册为模块的参数,否则 hook 无法跟踪梯度¶
target_layer.register_parameter('lora_A', nn.Parameter(torch.randn(8, 4096) * 0.02)) target_layer.register_parameter('lora_B', nn.Parameter(torch.zeros(4096, 8)))
注册钩子¶
hook = target_layer.register_forward_hook(make_lora_hook(target_layer.lora_A, target_layer.lora_B, alpha=16, r=8))
Hook 方法的优势与局限:¶
优势:无需重写模型类,可以快速测试想法,尤其适合在别人写的复杂模型代码上“打补丁”。
局限:
a. 调试噩梦:钩子的执行是隐式的,不直接出现在 print(model) 里,调试时很难追踪。 b. 性能开销:钩子在每次前向传播时被调用,存在额外的函数调用开销。 c. 序列化复杂:保存和加载模型时,你需要额外处理这些“寄生”参数,容易出错。 d. 无法合并:作为动态添加的路径,不可能像 nn.Module 那样做优雅的权重合并。
论: 于任何严肃的项目,推荐使用自定义 nn.Module 的方式。这是 HuggingFace PEFT 库所采用的技术路线。register_forward_hook 更适合在 Jupyter Notebook 里做快速原型验证。
结论:¶
1. LoRA 的 A 和 B 矩阵权重衰减(Weight Decay)通常如何设置?为什么有时会单独对它们设置不同的正则化?¶
直接回答:
在标准实践中,通常对 LoRA 的 A 和 B 矩阵不施加权重衰减(Weight Decay),或者设置为一个极小的值。原因是 LoRA 模块本身参数就非常少,过拟合风险远低于全量微调,自带的“低秩性”就是最强大的正则化器。
为什么通常不需要权重衰减?
-
低秩本身就是强正则化:这是核心原因。LoRA 把权重更新 $ \Delta W\Delta W $ 强制限制在一个秩为 rr 的低维子空间内。这相当于对模型的更新量施加了一个极强的、结构化的约束。在这个小的子空间里,参数的自由度被极大地压缩了,模型“想”去记住训练数据中的噪声特征,在数学上也很难做到。这个天然的结构化正则化,使得额外的权重衰减往往变得可有可无。
-
参数规模极小:一个 LoRA 模块的参数量可能只有原始模型的千分之一甚至万分之一。在如此少的参数上直接施加权重衰减,效果微乎其微,反而可能会阻碍需要较大数值才能完成“任务适配”的 LoRA 参数正常学习。
-
初始化状态的特殊性:回忆一下,LoRA 的 B 矩阵是零初始化的。如果此时对其施加权重衰减,优化器会试图不断地把 B 中的值往零的方向拉。这和我们希望 B 从零生长出来去学习有用信号的目标是直接冲突的,会拖慢收敛速度。
什么时候会考虑差异化设置?¶
差异化设置(例如,对 A 设置一个权重衰减,对 B 不设置)通常出现在极端情况下。
场景一:微调数据极其有限。当你只有几百条样本时,即使低秩约束也可能过拟合。这时,给LoRA参数施加一个非常小的权重衰减(比如1e-5或1e-6),可以作为一种“锦上添花”的手段,进一步压低模型的复杂性。
场景二:LoRA+ 思想的体现。最新的研究(LoRA+)提出,对 A 和 B 使用不同的学习率效果更好。虽然这不等同于权重衰减,但反映了社区开始意识到 A 和 B 的角色不同。A 负责将输入映射到低秩空间(特征提取),B 负责将低秩特征映射回输出(特征利用)。从理论上讲,对特征提取器(A)施加一点正则化可能有助于学到更通用的特征,而对特征利用器(B)则不施加或施加更轻的正则化,避免抑制其对输出的影响。但这个实践目前还没有成为默认标准。
实践建议:¶
默认关闭所有 LoRA 参数的权重衰减。只在你的 Baseline 模型在验证集上表现出明显过拟合迹象时(训练集 Loss 远低于验证集 Loss),才考虑尝试给 LoRA 参数设置一个极小(如 1e-6)的全局权重衰减。这是一个非常安全的工程策略,大多数情况都能取得不错的效果。
16. 使用混合精度训练(FP16/BF16)时,LoRA参数通常放在什么精度?如何避免B矩阵在FP16下因数值小而产生下溢?¶
直接回答:
在现代训练框架(如 HuggingFace Trainer, DeepSpeed)中,LoRA 的 A 和 B 矩阵会与模型的其它可训练参数一样,默认处于 FP32(单精度)精度下,并作为“主权重副本”(Master Weights)存储。只有在前向和反向传播的计算过程中,它们才被转换为 FP16 或 BF16。这是混合精度训练的标准做法,能有效防止数值精度问题。
为什么这是关键?——梯度下溢的幽灵
下溢(Underflow)是什么?FP16 能表示的最小正数大约在 $ 6 \times 10 - 86 \times 10 - 8 $ 左右。任何绝对值小于这个数的数值,在 FP16 格式下都会变成 0。这就是下溢。
为什么 LoRA 的梯度容易下溢?回想一下,B 矩阵是零初始化的。在训练初期,B 的更新量很可能非常小。同时,LoRA 参数的梯度本身也可能会因为链式法则的层层传导而变得很小。如果一个 FP16 格式的微小梯度值在下溢后变成了 0,那么这个参数在那个训练步中就会完全没有得到更新。这种“静默”的精度损失积累起来,会导致 LoRA 模块学不到东西,或者收敛到一个很差的结果。
为什么 BF16 相对安全?BF16 虽然精度位数比 FP16 少(7 位尾数 vs 10 位尾数),但它的指数位和 FP32 一样是 8 位,因此它的动态范围非常大,能表示的最小正数和 FP32 一样。因此,BF16 几乎不会发生梯度下溢问题,而 FP16 则非常容易。这就是为什么在支持 BF16 的硬件(如 A100)上,强烈建议使用 BF16 进行混合精度训练。用 BF16 是解决下溢问题最优雅、最彻底的方案。
规避 FP16 下溢的工程手段:
如果你的硬件强制只能用 FP16,有几种策略可以缓解下溢:
-
梯度缩放(Loss Scaling):这是混合精度训练的标配。在前向传播结束后,将损失值乘以一个很大的因子(如 65536),再进行反向传播。这样,所有梯度都会等比例放大,把那些原本会下溢的小梯度“托举”到 FP16 能表示的范围之上。在更新参数前,优化器会先把梯度除以这个缩放因子,再应用到 FP32 的主权重副本上。这是一个动态的过程,框架会自动调整缩放因子,防止放大后的梯度又造成上溢(溢出为 $ \text{NaN} $)。
-
使用更高的学习率/更大的 $ \alpha\alpha $:如之前讨论过的,更大的学习率或 $ \alpha\alpha $ 会直接增大参数更新量,从而让梯度的有效值变大。这是在“源头”上解决梯度太小的问题。
-
确保 A 矩阵初始化的方差足够:A 矩阵的高斯初始化方差需要适中。如果一开始 A 的值太小,会导致 BAxBAx 极小,进而导致 B 的梯度极小。使用一个合理的初始化标准差(如 0.02)是很重要的。
总结:
LoRA 参数以 FP32 主副本存储,确保更新精度。在计算时,优先使用 BF16 来从根源上规避下溢。如果必须用 FP16,则依赖框架的自动 Loss Scaling 机制来保证训练稳定。