LoRA(Low-Rank Adaptation)通过冻结基座模型,只在部分层中注入低秩矩阵来学习任务增量。对于已经为多个任务训练好的适配器,加权平均可以把多个任务方向合并为一个新的适配器,从而避免在推理时频繁切换权重。理解融合前,需要先确认每个适配器的结构、缩放方式和状态字典布局。

一、先理解 LoRA 权重结构与融合基础
LoRA 的核心思想是:不直接微调原始权重矩阵 W,而是学习一个低秩增量 ΔW,并将其分解为两个较小矩阵 A 和 B 的乘积。假设原始权重 W 的维度为 d×k,秩为 r 时,A 的维度为 r×k,B 的维度为 d×r,于是有 ΔW = BA。推理时实际参与计算的权重为 W + (α/r)BA,其中 α 是缩放系数,r 是低秩维度。训练时只更新 A 和 B,因此适配器文件通常只保存这些低秩矩阵以及少量配置信息。
在 PEFT 等库中,状态字典里的键名通常包含 lora_A 和 lora_B 这样的标识。例如某个注意力层的查询投影会对应 q_proj.lora_A.weight 和 q_proj.lora_B.weight。不同任务训练出的适配器如果目标层一致、秩相同,就能很方便地进行逐元素合并。如果目标层不一致,需要先做模块对齐;如果秩不同,则要通过降维或升维来统一形状,复杂度会明显增加。
加权平均之所以对 LoRA 有效,是因为任务增量通常保持在低秩子空间内。当多个任务的训练没有让原始模型偏离过远时,这些增量矩阵可以在一定程度上进行线性叠加。融合后的低秩矩阵仍然保持原来的形状,不需要改变基座模型结构,也不会额外增加推理成本。理解这一点后,再来看加权平均的数学形式会更加清晰。
二、加权平均融合的数学形式
假设我们有两个或多个 LoRA 适配器,每个适配器的增量矩阵为 ΔW_i = B_i A_i,对应的权重系数为 λ_i。加权平均后的融合增量可以表示为:
ΔW_fused = Σ λ_i ΔW_i = Σ λ_i (B_i A_i)
最终的模型权重为 W_final = W_base + ΔW_fused。如果所有权重系数之和为 1,即 Σ λ_i = 1,那么融合后的增量不会放大或缩小整体更新幅度。常见的做法是给每个任务分配一个非负权重,例如 [0.5, 0.5] 表示两个任务等权融合,[0.7, 0.3] 表示更偏向第一个任务。
需要注意,LoRA 的缩放因子 α/r 在融合时不能忽略。不同库对缩放的处理方式可能不同:有的库在状态字典中直接保存了已经乘过缩放因子的 B 矩阵,有的库则在 forward 阶段才动态计算缩放。如果两个适配器采用不同的 α 或 r,在直接相加前必须先把缩放到统一基准,否则会出现某个任务被意外放大或缩小的情况。对于 PEFT 来说,lora_B 中通常不预先乘 α,因此加权平均时只需要保证参与融合的适配器具有相同的 r 和 α 配置。
还有一点要区分清楚:加权平均 LoRA 增量与直接平均整个模型权重并不相同。直接平均模型权重会把基座权重也混在一起,容易造成通用能力下降。而 LoRA 融合只合并任务增量,基座权重保持原样,因此对原始语言能力的破坏更小,也更适合多任务扩展。
三、用 PEFT 实现多任务适配器加权平均
如果希望手动控制融合过程,可以先从不同适配器目录中加载状态字典,然后按权重逐项相加。下面是一个基于 PyTorch 的简单实现,假设两个适配器结构完全一致:
import torch
def weighted_average_lora(adapter_paths, weights):
state_dicts = [torch.load(p, map_location="cpu") for p in adapter_paths]
merged = {}
for key in state_dicts[0].keys():
if "lora_A" not in key and "lora_B" not in key:
merged[key] = state_dicts[0][key]
continue
weighted_sum = None
for w, sd in zip(weights, state_dicts):
delta = sd[key].to(torch.float32)
if weighted_sum is None:
weighted_sum = w * delta
else:
weighted_sum += w * delta
merged[key] = weighted_sum
return merged
merged_state = weighted_average_lora(
["adapter_1/adapter_model.bin", "adapter_2/adapter_model.bin"],
[0.6, 0.4]
)
torch.save(merged_state, "merged_adapter/adapter_model.bin")
这段代码遍历第一个适配器的所有键,只对包含 lora_A 或 lora_B 的参数做加权平均,其余配置项直接保留。实现简单,但要求两个适配器的状态字典键完全对齐。如果缺失某些键,会触发 KeyError,此时需要先检查两个适配器的目标模块是否一致。
如果使用 PEFT 库,可以调用 add_weighted_adapter 接口完成同样的操作。它内部会处理状态字典提取与权重合并,使用起来更省事:
from transformers import AutoModelForCausalLM
from peft import PeftModel
base_model = AutoModelForCausalLM.from_pretrained("your-base-model")
model = PeftModel.from_pretrained(base_model, "adapter_1")
model.load_adapter("adapter_2", "adapter_2")
model.add_weighted_adapter(
adapter_names=["adapter_1", "adapter_2"],
weights=[0.6, 0.4],
adapter_name="merged_adapter",
combination_type="linear"
)
model.set_adapter("merged_adapter")
model.save_pretrained("merged_adapter")
调用 add_weighted_adapter 后,新适配器 merged_adapter 会包含两个旧适配器按指定权重线性叠加后的 LoRA 参数。之后通过 set_adapter 切换到融合适配器即可推理,也可以用 save_pretrained 单独保存。需要注意的是,融合后的适配器仍然只包含低秩矩阵,基座模型权重不会被打包,部署时要同时提供基座模型和合并适配器目录。
手动融合的优势是可控性强,适合需要检查或修改中间权重的场景;而 add_weighted_adapter 的优势是代码简洁,并且和 PEFT 的配置体系天然兼容。实际项目中,如果只是做简单实验,优先用 PEFT 接口;如果需要自定义权重搜索或加入正则化,再考虑手动实现。
四、权重调优与常见性能退化
权重系数直接决定融合后模型的偏向。最简单的是等权平均 [0.5, 0.5],在任务相似时效果通常不错。但如果两个任务差异较大,等权融合可能会让模型在两个任务上都表现下降。此时可以用一个小规模验证集做权重搜索,例如在 [0.1, 0.9] 到 [0.9, 0.1] 的网格上评估融合模型,选择平衡点。更高效的搜索方法包括贝叶斯优化或基于梯度的方法,但网格搜索已经能满足多数多任务适配场景。
性能退化的一个常见原因是缩放不一致。参与融合的适配器如果在训练时使用了不同的秩或 α,直接相加会导致量级不匹配。建议在训练各任务适配器时统一 LoRA 配置,例如都使用 r=16、α=32。另一个原因是权重系数过大时导致增量过度累积,使模型偏离基座空间。可以通过限制权重范围或对融合增量做 L2 归一化来缓解。
除了纯线性加权,还可以与任务算术、TIES 合并、DARE 剪枝等方法结合。任务算术会先计算任务向量,再对符号冲突做处理;TIES 则先裁剪低幅度参数,再解决符号分歧。这些方法在任务冲突较强时往往比单纯加权平均更稳定。可以把加权平均作为基线,如果发现合并后任务遗忘严重,再逐步引入更复杂的融合策略。
总的来说,LoRA 权重融合的加权平均方法实现成本低、可解释性好,适合快速组合多个任务适配器。只要保证结构对齐、缩放一致,并合理选择权重,就能在保持各任务能力的同时减少部署复杂度。