关于RaLo、RDP LoRA、GateRA 各自同细分领域的开源论文

发布时间:2026/9/24 17:25:23
关于RaLo、RDP LoRA、GateRA 各自同细分领域的开源论文 难点确认各自对应的开源“平替”。RaLo、RDP LoRA、GateRA 均属于PEFT方向但各自的开源替代方案定位不同三个方法都属 PEFT但能对上的开源方案不是一类东西。RaLo 是动态调秩对应 AdaLoRARDP LoRA 是挑层对应 DropLoRAGateRA 是 token 级门控按理说 X-LoRA 更贴但 DoRA 是这块更经典的东西所以先看 DoRA。RaLo动态秩分配论文原文对 AdaLoRA 进行对比所以选择看看这个。AdaLoRa (Adaptive LoRa)1. 核心问题秩分配不均LoRa 的问题对所有权重矩阵统一分配相同的秩忽略了不同层/模块对任务的贡献差异。关键洞察不同权重矩阵的重要性差异很大——FFN层和顶层对任务贡献更大底层需要更多参数预算。本论文采取了单个部分删除判断结果差异的方法确定重要层方法对比LoRa均等分配每层分配相同的重要层与不重要层获得相同预算参数利用效率不高低预算时性能下降明显。AdaLoRa自适应分配根据重要性动态分配重要层获得更大秩不需要层获得较小秩参数用在关键点上。2. SVD 参数化公式对比LoRaAdaLoRa矩阵含义左奇异向量矩阵对角奇异值矩阵右奇异向量矩阵为什么用 SVD 而非 AB 分解奇异值天然表达每个分量的重要性裁剪奇异值就和平滑减小秩比直接删除AB列更能进行更细粒度的控制。奇异值本身可以表示为不同方向上的分量且能进行特定方向的裁剪。3. 重要性评估与预算调度重要性得分综合三个因素奇异值大小值越大贡献越多。梯度敏感度该参数对 Loss 的影响。指数移动平均 (EMA)平滑时段波动。三阶段预算调度Warmup 阶段初始较大不裁剪让模型充分探索参数空间。裁剪阶段每隔评估重要性裁剪低重要性的奇异值。收敛阶段预算固定精调剩余参数。4. 论文实验结果与核心发现核心发现在不同任务上的表现如 SST-2, MNLI, CoLA, SQuADv2 F1 等5.预算下优势显著在极少参数下如 0.08% 参数AdaLoRa 相比 LoRa 表现出极高的参数利用率和准确率。FFN 和顶层权重的贡献更高实验证明 FFN 层和高层对大模型微调至关重要。SVD 正交正则化对性能有着显著贡献。裁剪奇异值比直接删除 AB 列更稳定通过逐步裁剪能够更平滑地调整模型容量。5.代码解释代码AdaLoRA-main/AdaLoRA-main/loralib/loralib/adalora.py355 行。SVDLinear在 14–99 行RankAllocator在 102–323 行。四个分块很清晰换层在SVDLinear打分在update_ipt动刀在mask_to_target_rank预算在schedule_threshold。互不纠缠这是它代码好读的原因。核心片段一前向只有一行# adalora.py:91-97 if self.r 0 and not self.merged: result F.linear(x, T(self.weight), biasself.bias) result ( self.lora_dropout(x) (self.lora_A * self.lora_E).T self.lora_B.T ) * self.scaling / (self.ranknum1e-5)self.lora_A * self.lora_Elora_A是(r, in)lora_E是(r, 1)这是广播的逐元素乘不是矩阵乘。效果是 A 的第 k 行整体乘上 E[k]——这就是r 个刻度盘。核心片段二重要性怎么打分# adalora.py:219-232 with torch.no_grad(): self.ipt[n] (p * p.grad).abs().detach() # 敏感度 |θ·g| self.exp_avg_ipt[n] self.beta1 * self.exp_avg_ipt[n] \ (1-self.beta1)*self.ipt[n] # 平滑 self.exp_avg_unc[n] self.beta2 * self.exp_avg_unc[n] \ (1-self.beta2)*(self.ipt[n]-self.exp_avg_ipt[n]).abs() # 不确定度 ipt_score self.exp_avg_ipt[n] * self.exp_avg_unc[n] # 两者相乘论文写得很复杂的综合奇异值 梯度敏感度 EMA代码里就是这三行。核心片段三动刀是全局切一刀# adalora.py:284-292 mask_threshold torch.kthvalue(torch.cat(all_is), (self.total_rank-curr_rank))[0].item() with torch.no_grad(): for n,p in model.named_parameters(): if lora_E in n: p.data.masked_fill_(is_dict[n]mask_threshold, 0.0)这是动态分配真正的实现方式。所有矩阵的分数拼成一条长向量取第 k 小的值当门槛全局统一。没有每层配额这回事——预算自己流向分数高的矩阵。三个矩阵最后保留 3、2、1 而不是平均分就是这么来的。学到了什么1. 论文的 P、Q 和代码的 B、A 是反的。论文写 ΔW PΛQ代码里 B 在前、A 在后第 72、84、95 行三处。对着论文读代码第一眼就会卡在这。2. 正交正则根本不在 forward 里是外挂的。# adalora.py:345-354 def compute_orth_regu(model, regu_weight0.1): for n,p in model.named_parameters(): if lora_A in n or lora_B in n: para_cov p p.T if lora_A in n else p.T p I torch.eye(*para_cov.size(), outtorch.empty_like(para_cov)) regu_loss torch.norm(para_cov-I, pfro) return regu_weight*regu_loss/num_param训练时必须自己写loss compute_orth_regu(model, 0.1)。忘了加AdaLoRA 退化成普通的 SVD 版 LoRA而且不报错只是悄悄变差。作用也值得记它逼 A 的行正交、B 的列正交也就是让 P、Q 接近正交。为什么要逼因为真实训练里第 k 块积木的大小是|E[k]| × ‖B[:,k]‖ × ‖A[k,:]‖多出来那两个范数是噪声——E 很小但那一列特别长这块积木其实不小。正交化在压这份噪声。论文措辞是参数化 mimics模拟SVD因为它一开始真的只是模拟。3.ranknum是死代码。# adalora.py:43-46 self.ranknum nn.Parameter(self.weight.new_zeros(1), requires_gradFalse) self.ranknum.data.fill_(float(self.r))它在整个库里从不被更新但出现在 forward第 96 行和 merge第 73、85 行的分母里。名字暗示按实际秩自适应缩放实际恒等于float(r)。这套机制没实现。4. final 期是真冻结衰减期不是。# adalora.py:310-311 def update_and_mask(self, model, global_step): if global_step self.total_step-self.final_warmup: self.update_ipt(model) # 只有非 final 期才更新分数final 期不更新分数 → 每步掩的是同一批位置 → 冻结。衰减期分数一直在变而被剪的lora_E[k]自己的梯度不是零第 221 行里 E 的梯度不带 E 因子所以它会从 0 自己长回来。论文说保留将来恢复的可能性真正管用的是这个周期性重掩不是它给的理由。RDP LoRA关键层筛选DropLoRADropLoRA: Sparse Low-Rank Adaptation for Parameter-Efficient Fine-Tuning在 PEFT参数高效微调领域中为了解决传统 LoRA 静态低rankLow-rank子空间限制及确定关键层/关键维度的需求DropLoRA提供了一种轻量级、无需复杂搜索的动态稀疏化方案可以作为 RDP LoRA 在结构稀疏和子空间管理上的优秀开源替代。1. 技术动机与核心原理传统 LoRA 的局限标准 LoRA 在整个微调过程中依赖一个静态的低秩子空间这导致它在表达能力上与全量微调Full Fine-Tuning存在性能差距。DropLoRA 的核心思想它引入了动态子空间学习的概念通过在 LoRA 的两个低秩矩阵和之间创新性地集成一个基于剪枝/Dropout 的掩码模块在训练过程中沿着秩的维度动态地对中间激活或矩阵列/行进行稀疏化。训练与推理的差异训练阶段动态随机将一部分秩的维度归零即每次只激活一部分子空间让模型在多个不同的子空间中进行“类似集成”的学习从而捕获更本质的参数变化。推理阶段该剪枝/Dropout 模块会被停用恢复完整的矩阵进行推理不引入任何额外的推理延迟或计算开销。2. 数学公式与前向传播标准 LoRA 的前向传播公式为而在DropLoRA中通过在低秩投影的中间步骤引入掩码向量或特征丢弃机制设为掩码/Dropout 操作其核心更新过程可以表示为(注在实际代码实现中它等价于在中间激活值上施加一个类似 Feature Dropout 的掩码层再与相乘。通过动态改变每次迭代中被激活的 rank 维度迫使模型学习到更具鲁棒性的低秩表征。)3. 论文实验结论根据 DropLoRA 官方论文的实验评估其在多个主流大模型如 LLaMA 系列和生成任务中表现优异全面超越标准 LoRA在 LLaMA3-8B 的常识推理Commonsense Reasoning任务上DropLoRA 比标准 LoRA 平均准确率高出0.83个百分点并在所有 8 个子数据集上均取得了最优表现。在数学推理和代码生成任务中相比标准 LoRA 也取得了显著提升例如代码任务在 LLaMA2-7B 上提升了2.28个百分点。高效的参数利用率消融实验发现实验表明即使将 DropLoRA 的有效秩或剪枝率进行折减例如设置 rank32剪枝率 p0.5其实际生效的参数量等同于 rank16 的 LoRA其表现依然能够超越常规的 LoRA-16 甚至 LoRA-32。最优的剪枝/稀疏率Drop Rate通常在0.3 左右表现最好既保证了稀疏子空间的动态探索又不会过度损伤模型的表达容量。零额外开销由于 DropLoRA 的动态掩码仅在训练计算图内生效训练完成后可无缝合入原权重训练和推理阶段均不产生额外的显存或时间负担。4.代码解释代码在DropLoRA-main/peft-0.14.0/src/peft/tuners/droplora/——这是一个改过的 PEFT forkDropLoRA 作为独立 tuner 塞进了 PEFT。训练入口是 ms-swift 的--train_type droplora配置透传在swift/llm/train/tuner.py:279-295。核心片段一门控夹在秩维度上# droplora/layer.py:706-707 (Linear.forward) if not self.use_dora[active_adapter]: result result lora_B(inner_dropout(lora_A(dropout(x)))) * scaling链条是x → dropout → lora_A → inner_dropout → lora_B。inner_dropout作用在 A 和 B 中间那层 r 维激活上不是作用在输入上。这一行就是 DropLoRA 的全部改动。核心片段二两种模式不是一种# droplora/layer.py:160-163 if not dynamic_pruning: self.lora_inner_dropout[adapter_name] nn.Dropout(pinner_dropout) else: self.lora_inner_dropout[adapter_name] LearnablePruning(r)默认走的是第一条普通随机 dropout。第二条才是可学习结构化剪枝# droplora/layer.py:35-55 class LearnablePruning(nn.Module): def __init__(self, dim, temperature1.0, eps1e-8): self.logits nn.Parameter(torch.zeros(dim), requires_gradTrue) def forward(self, x): if self.training: mask_prob torch.sigmoid(self.logits) # 保留概率 mask_hard (mask_prob 0.5).to(self.logits.dtype) mask (mask_hard - mask_prob).detach() mask_prob # STE 技巧 p mask_hard.sum() / mask_hard.size()[0] scale_factor 1.0 / (p.detach() self.eps) # 缩放补偿 output x * mask * scale_factor else: output x # 推理时直接放行 return output三个细节mask 是硬 0/1阈值 0.5不是软的梯度靠 STEstraight-through estimator穿过二值化输出乘了1/p做缩放补偿保证期望不变。学到了什么1. 它的稀疏化粒度是秩维度不是层。这一点很关键。看训练脚本# scripts/llama2_7b/run_cms.sh --train_type droplora \ --lora_rank 32 --lora_alpha 64 --lora_dropout 0.05 \ --target_modules q_proj,k_proj,v_proj,up_proj,down_proj五个目标模块一个不落全都挂上了 LoRA。DropLoRA 从来不跳过任何层它做的是让每一层内部的 r 个秩分量按概率/按学习被置零。 RDP LoRA 定义是稀疏层适配、精准挑出关键层这跟 DropLoRA 的机制不同——同一个大方向但不是同一件事。一个在层间做选择一个在秩维度上做选择。2. 有效参数量等于一半秩这个说法要打个折扣。因为scale_factor 1/(peps)在做期望补偿被保留的那部分分量被放大了1/p倍。所以 drop_rate0.5 时参数量确实是 rank 16 的量级但每一维的数值尺度是被补偿过的和直接训 LoRA-16 不是一回事。3. 推理确实干净。nn.Dropout在 eval 下自动变恒等LearnablePruning在self.trainingFalse时直接output x。两条路都不留额外算子。合并走get_mask()layer.py:666-667拿硬 mask 再折叠。GateRAToken 级动态门控调制GateRA和X-LoRA的核心本质都是面向输入或 Token 级的动态适配更贴合但是DoRA是更经典的同领域内容所以先看 DoRA。DoRA (Weight-Decomposed Low-Rank Adaptation)1. DoRA 动机全量微调FT与 LoRA 的学习差异关键发现将权重分解为magnitude幅度和direction方向后全量微调FT和 LoRA 呈现截然不同的学习模式。核心痛点FT 的幅度和方向变化呈负相关耦合而 LoRA 的两者呈正相关耦合——这限制了 LoRA 的学习能力。对比全量微调 (FT)幅度与方向呈负相关解耦学习方向大幅度调整幅度变化小具有更灵活的参数更新能力。LoRA幅度与方向呈正相关耦合学习两者同增同减缺乏灵活性学习能力不如 FT。2. DoRA 技术原理核心公式其中 幅度magnitude 方向direction。核心步骤权重分解将预训练权重分解为幅度分量和方向分量灵感来源于 Weight Normalization。方向应用 LoRA 更新低秩更新方向分量参数最大用 LoRA 高效更新。幅度独立训练作为独立可训练参数参数量极小仅一维向量可直接全量更新。优势解耦幅度和方向的学习使 LoRA 的学习更为接近全量微调同时保持零推理延迟。3. 论文实验结果在各项任务上的表现如 LLaMA-7B 等模型常识推理任务DoRA 相比标准 LoRA 提升明显平均提升约 2.0% 至 2.5% 点。视觉指令微调与理解在 LLaVA、VL-BART 等多模态任务上也展现出持续的性能提升。结论DoRA 在多任务和模型上一致优于 LoRA且训练后可合并权重零推理延迟。4.代码解释DoRA 官方仓库 PEFT 里peft-0.14.0/src/peft/tuners/lora/dora.py189 行。核心片段一前向那行反直觉的展开# dora.py:86-98 mag_norm_scale (magnitude / weight_norm).view(1, -1) lora_result lora_B(lora_A(x)) ... result_dora (mag_norm_scale - 1) * base_result mag_norm_scale * lora_result * scaling论文公式是W m · (W₀ BA)/‖W₀ BA‖。但代码从来不真的去构造W₀ BA这个矩阵而是展开成上面这行。代数是等价的(m/n − 1)·W₀x (m/n)·BAx·s (m/n)·(W₀x BAx·s)。妙处在那个− 1——它让W₀x也就是base_result能复用base_layer已经算出来的输出省掉一次完整的矩阵乘。这是零额外推理开销在代码层面的真实来源。核心片段二magnitude 怎么初始化# dora.py:30-35, 59-63 def get_weight_norm(self, weight, lora_weight, scaling): weight transpose(weight, self.fan_in_fan_out) weight weight scaling * lora_weight weight_norm torch.linalg.norm(weight, dim1).to(weight.dtype) # 按列取 L2 范数 return weight_norm weight_norm self.get_weight_norm(weight.to(lora_A.device), lora_weight, scaling) self.weight nn.Parameter(weight_norm, requires_gradTrue)nn.Parameter(weight_norm)——magnitude 初始化为基座权重的列范数不是零、也不是一。范数按dim1取即每个输出通道一个标量所以这个向量的长度是out_features。核心片段三那个藏在 §4.3 里的 detach# dora.py:78-85 weight_norm self.get_weight_norm(weight, lora_weight.detach(), scaling) # see section 4.3 of DoRA # [...] we suggest treating ||V ∆V ||_c in Eq. (5) as a constant, thereby # detaching it from the gradient graph. weight_norm weight_norm.detach()‖W₀BA‖这个归一化项在反向时被当成常数切断了。主公式里完全看不出来它藏在论文第 4.3 节的一句建议里。不 detach 会怎样代码没写——但这属于复现时最容易漏掉、漏了效果就不同的那一类。核心片段四merge 时才真把幅度折进去# lora/layer.py:524-535 weight_norm ( self.lora_magnitude_vector[active_adapter] .get_weight_norm(orig_weights, transpose(delta_weight, self.fan_in_fan_out), scaling1) .detach() ) # We need to cache weight_norm because it has to be based on the original weights. self._cache_store(f{active_adapter}-weight_norm, weight_norm) dora_factor self.lora_magnitude_vector[active_adapter].weight / weight_norm orig_weights dora_factor * (orig_weights delta_weight)最后一行dora_factor * (orig_weights delta_weight)就是m·(W₀BA)/‖W₀BA‖落地的样子。这才是训练完可以 merge、推理零延迟成真的地方——不在 forward 里在 merge 里。另外注意那个_cache_storeweight_norm 必须缓存因为 unmerge 时基座权重已经变了再算就是另一个值。学到了什么1. 论文的主公式和实现之间隔着三层优化。复用 base_result−1那一手、identity 前向替代BA、norm 的 detach。论文一句W m·(W₀BA)/‖W₀BA‖代码里拆成了四处改动。这三层全是性能/工程考虑不是数学考虑。2. 算lora_weight用单位阵推不用BA。# dora.py:70-73 # Dont use lora_weight lora_B.weight lora_A.weight because this causes errors with FSDP. x_eye torch.eye(lora_A.weight.shape[1], devicelora_A.weight.device, dtypex.dtype) lora_weight lora_B(lora_A(x_eye)).T代价是一个(in × in)的矩阵乘7B 模型里in可以到上万换来的是 FSDP/DDP 下梯度图正确。读论文没想到。3. DoRA 不是 token 级的。magnitude是每个输出通道一个标量和输入是什么 token 无关。你想对标 GateRA 的token 级动态门控DoRA 只能提供一个通道级静态解耦的近似——它解决的是幅度和方向的学习耦合不是逐 token 的权重调节。