Model Merging
模型合并将基于同一基础模型微调的多个模型的权重融合成一个模型,且不会增加推理成本。内容涵盖权重平均和任务算术。
模型合并是将两个或多个已训练模型的权重组合成一个模型,通常通过对参数求平均或相加来实现。这样,合并后的模型就能保留各模型的能力,无需同时运行多个网络,也不必用它们的全部数据重新训练。只有当模型采用相同架构,并且(对大多数方法而言)从同一预训练起点开始时,这种方法才有效:微调只会让每个副本在权重空间中移动一小段距离,因此它们的模型权重仍能逐参数对齐。模型合并广泛用于构建开放的大型语言模型(LLM),以及组合 LoRA 适配器或 Stable Diffusion 检查点;Meta 的 Llama 3 后训练则对在每个阶段使用不同数据或超参数得到的模型进行平均。
模型合并的工作原理#
合并方法在组合内容以及解决模型间冲突的方式上各不相同:
- 模型汤:对从同一基础模型出发、使用不同超参数微调得到的模型权重求平均。由微调后的 ViT-G 模型组成的贪心模型汤,在 ImageNet 上取得了 90.94% 的 top-1 准确率,在当时达到了最先进水平。
- 随机权重平均:对一次训练运行中不同阶段保存的检查点求平均,这通常会使模型落在损失曲面上更宽、更平坦的区域。
- 任务算术:从每个微调模型中减去基础模型的权重,得到任务向量,然后将缩放后的任务向量加回基础模型以组合技能,或减去一个任务向量以消除某种行为。
- TIES-Merging:通过三个步骤减少任务向量之间的干扰:重置微调过程中变化很小的参数,为每个参数选定一个符号,然后只合并与该符号一致的值。
- DARE:随机丢弃每个任务向量中的大部分值,并在合并前重新缩放剩余值,从而去除冗余变化,同时保留每项任务的能力。
单次训练运行中的权重平均也是常规训练的一部分。Ultralytics YOLO26 训练会在优化权重的同时,对模型权重维护一个指数移动平均(EMA);随着训练推进,衰减率逐渐升至 0.9999,而它保存的 last.pt 和 best.pt 检查点中包含这个平均模型。
任务向量方法具有相同的结构:每个微调模型都表示为它与基础模型之间的差异;清理并组合这些差异后,再将结果加回基础模型:
mergekit 等开源工具可为 LLM 实现这些合并方案,也支持两个模型之间的球面线性插值(SLERP)。
为什么共同的起点很重要#
神经网络的隐藏单元可以重新排序而不改变网络的计算结果,因此,从不同随机初始化开始训练的两个网络,很少会在相同位置存储相同特征,对它们求平均会混合无关的权重。从同一个基础模型微调出来的模型则保留了基础模型的排列方式,并处于损失较低的同一区域,因此它们的平均模型仍然有效。下面的示例使用手写数字训练一个小型 PyTorch 分类器,将两个副本分别在剩余训练数据的不同部分上微调,并比较这两个模型的合并结果与从不同初始化训练出的两个模型的合并结果:
import copy
import torch
from sklearn.datasets import load_digits
from torch import nn
torch.manual_seed(0)
X, y = load_digits(return_X_y=True)
order = torch.randperm(len(X))
X, y = torch.tensor(X / 16, dtype=torch.float32)[order], torch.tensor(y)[order]
def mlp():
"""Return a small classifier for 8x8 digit images."""
return nn.Sequential(nn.Linear(64, 64), nn.ReLU(), nn.Linear(64, 64), nn.ReLU(), nn.Linear(64, 10))
def train(model, rows, steps):
"""Train a model with SGD on the given rows and return it."""
optimizer = torch.optim.SGD(model.parameters(), lr=0.1, momentum=0.9)
for _ in range(steps):
optimizer.zero_grad()
nn.functional.cross_entropy(model(X[rows]), y[rows]).backward()
optimizer.step()
return model
base = train(mlp(), slice(0, 300), 400) # shared pretrained starting point
halves = (slice(300, 900), slice(900, 1500)) # two different fine-tuning datasets
for name, (a, b) in [
("fine-tuned from one base", [train(copy.deepcopy(base), rows, 100) for rows in halves]),
("trained from separate inits", [train(mlp(), rows, 400) for rows in halves]),
]:
# Merge: average every weight tensor of the two models
merged = copy.deepcopy(a)
merged.load_state_dict({k: (a.state_dict()[k] + b.state_dict()[k]) / 2 for k in a.state_dict()})
accuracy = [(m(X[1500:]).argmax(1) == y[1500:]).float().mean().item() for m in (a, b, merged)]
print(f"{name}: {accuracy[0]:.3f} and {accuracy[1]:.3f}, merged {accuracy[2]:.3f}")在 297 个留出的数字样本上,从同一个基础模型微调出的两个模型分别得到 0.956 和 0.946 的分数,它们的平均模型得分为 0.970,高于任一单独模型,因为它结合了每个模型从各自半份数据中学到的内容。从不同初始化训练出的两个模型分别得分 0.946 和 0.956,而它们的平均模型分数降至 0.808。Git Re-Basin 会先对一个模型的隐藏单元进行置换,使其与另一个模型匹配,再合并独立训练的模型。










