大语言模型合并实战:用mergekit融合Llama与WizardLM构建全能AI
1. 项目概述模型合并的“瑞士军刀”如果你在开源大模型社区里混迹过一段时间肯定会发现一个现象每隔一段时间就会冒出一个新的、在某些特定任务上表现惊人的模型。这些模型往往不是从零开始训练的而是由两个或多个现有模型“融合”而成。这种技术我们称之为模型合并。而arcee-ai/mergekit正是这个领域里目前最受推崇、功能最强大的工具库没有之一。你可以把它理解为模型合并领域的“瑞士军刀”无论是简单的线性加权还是复杂的专家混合它都能帮你优雅地实现。我最初接触模型合并是因为想在一个消费级显卡上跑一个既能写代码、又能流畅对话的模型。当时市面上要么是纯代码模型要么是纯对话模型两者兼得的模型要么太大70B参数要么效果平平。后来我尝试用 mergekit 将一个擅长代码的 7B 模型和一个擅长对话的 7B 模型合并最终得到了一个在两项任务上都表现不错的“新”模型而且体积依然是 7B。这个过程的效率和可控性让我彻底迷上了这个工具。mergekit 的核心价值在于它让模型融合这项原本需要深厚理论知识和复杂工程实践的技术变得平民化和流程化。你不再需要去啃那些晦涩的论文或者自己从头编写复杂的权重加载与计算代码。通过一个清晰的 YAML 配置文件你就能定义复杂的合并策略然后由 mergekit 帮你处理所有底层细节包括不同架构的适配、分词器的融合、权重的精确计算与保存。这对于模型研究者、应用开发者甚至是高级的 AI 爱好者来说都是一个效率倍增器。2. 核心原理模型合并为何有效在深入实操之前我们必须先搞懂一个根本问题为什么把两个训练好的模型合并起来能产生一个可能更好的新模型这听起来有点像把两个成年人的大脑缝合在一起期望得到一个天才直觉上似乎会是一场灾难。但实践证明在特定条件下这确实行得通。其背后的核心思想主要基于以下几点2.1 参数空间的平滑性与模型“共性”现代的大语言模型通常在海量数据上训练其损失函数的优化空间被证明是相对平滑和连续的。这意味着在最优解一个训练好的模型附近存在着一个“平坦区域”这个区域内的模型参数虽然不同但性能相近。更重要的是不同模型在解决相似任务时其学到的表征和知识可能存在大量的重叠或互补区域。例如两个同样在代码数据上训练的模型它们对编程语法、逻辑结构的理解是共通的。合并操作本质上是在这个平滑的参数空间内进行插值或搜索试图找到一个能同时继承多个母模型优点的点。2.2 任务知识的互补与迁移这是模型合并大放异彩的关键。假设我们有模型 A擅长数学推理和模型 B擅长文学创作。它们底层都是基于相同的 Transformer 架构比如 Llama 3只是在不同的数据分布上进行了微调。模型 A 的某些神经元或注意力头被强化用于逻辑计算而模型 B 的另一些部分则对语言风格和叙事结构更敏感。通过精细化的合并策略不是简单的平均我们可以尝试保留模型 A 中与数学相关的“专家”参数同时保留模型 B 中与文学相关的“专家”参数从而得到一个“文理双全”的模型。这比从头训练一个多任务模型要高效得多。2.3 合并作为高效的架构搜索从另一个角度看合并也是一种高效的神经网络架构搜索形式。我们不是在离散的架构空间如层数、注意力头数中搜索而是在连续的参数空间中对现有高性能“建筑模块”即预训练模型进行组合。mergekit提供的各种算法如 SLERP, DARE, TIES就是不同的“搜索”或“组合”策略旨在减少合并过程中的冲突和性能损失。注意合并并非总是有效的“炼金术”。它的成功高度依赖于几个前提1合并的模型具有相同的架构如都是 Llama 32它们源自相同或相似的预训练基础即同一个“根模型”3合并的策略需要精心设计。盲目合并两个毫不相干的模型大概率会得到一堆“垃圾参数”。3. mergekit 核心功能与合并策略详解mergekit的强大体现在它支持丰富多样的合并算法每种算法都对应着不同的应用场景和哲学。理解这些策略是你玩转模型合并的基础。3.1 线性合并这是最简单、最直观的方法也是很多合并操作的起点。公式可以表示为新权重 α * 模型A权重 (1 - α) * 模型B权重。这里的α是一个介于 0 和 1 之间的系数。适用场景当你希望融合两个在相似任务上表现都很好但各有细微优势的模型时。例如合并两个不同数据配方微调出的 Llama 3 对话模型以期获得更稳定的表现。实操心得线性合并的效果对α值非常敏感。我通常会以 0.1 为步长从 0 到 1 生成一系列合并模型然后用一个小的评估集比如几十条涵盖不同问题的提示词快速测试找到性能的“甜点”。很多时候这个甜点不在 0.5可能在 0.7 或 0.3。3.2 SLERP 合并球形线性插值。听名字很高大上其实可以理解为在“高维球面”上做更平滑的插值。想象两个模型参数是高维空间中的两个点线性合并是沿着连接它们的直线走而 SLERP 是沿着球面上连接它们的弧线走。理论上SLERP 能更好地保持合并后模型的“归一化”特性可能带来更稳定、更少奇异点的输出。适用场景当线性合并效果不佳或者你希望合并过程更“自然平滑”时。在实践中对于差异较大的模型SLERP 有时能比线性合并产生更少“精神分裂”的对话行为。参数解析除了权重系数t类似线性合并的αSLERP 本身不引入额外超参。但在mergekit中你可以通过配置决定对哪些参数如q_proj,v_proj等注意力投影层应用 SLERP。3.3 DARE 合并这是一种基于权重修剪和重缩放的方法。它的核心思想是大模型中的很多参数其实是冗余的接近于零。DARE 先随机将一部分小权重置零修剪然后按比例放大剩余权重以保持输出的期望值。在合并时它对多个模型应用 DARE 后再进行简单的加权平均。适用场景合并多个通常超过3个专家模型的理想选择。传统的线性平均多个模型会导致性能急剧下降因为参数冲突严重。DARE 通过引入稀疏性修剪极大地减少了参数冲突使得合并多个模型成为可能。比如你可以合并一个代码模型、一个数学模型和一个创意写作模型。关键参数density: 保留权重的比例。例如density: 0.3表示只保留 30% 的权重绝对值最大的那部分其余置零。通常设置在 0.1 到 0.5 之间需要实验。weight: 每个模型在平均时的权重。3.4 TIES 合并这是解决多模型合并冲突的另一种先进方法。TIES 认为合并冲突主要来源于那些在符号正负上不一致的参数。它的流程分三步1)修剪保留每个模型中幅度最大的前 k% 参数2)选举符号对于每个参数位置根据修剪后保留该位置的模型们的权重符号进行“投票”决定合并后参数的符号3)加权平均仅对符号一致的参数进行加权平均。适用场景与 DARE 类似主要用于合并多个差异较大的模型。TIES 在学术界的一些基准测试中表现优于 DARE尤其是当模型间差异显著时。它通过“民主投票”决定参数符号能更有效地整合知识。关键参数density: 与 DARE 类似控制修剪比例。sign_weight等控制符号选举和平均的细节。3.5 专家混合这不是一个具体的算法而是一种架构层面的合并思想。MoE 模型本身由多个“专家”子网络和一个“路由器”组成。mergekit允许你将多个稠密模型组合成一个 MoE 架构。例如你可以让一个代码专家、一个数学专家和一个通用对话专家共同组成一个新模型由路由器根据输入问题决定激活哪个专家。适用场景当你希望创建一个在多个专业领域都保持顶尖水平且推理时计算成本可控的模型时。MoE 合并是构建“全能专家”模型的强大手段。实操难点MoE 合并的配置更复杂需要定义专家层、路由器参数并且合并后的模型需要特定的推理库如transformers的某些版本或vLLM支持才能正确运行。4. 从零开始一次完整的模型合并实战理论说了这么多现在我们手把手进行一次实战。我们的目标是将Meta-Llama-3-8B-Instruct一个优秀的通用对话模型和WizardLM-2-8B一个在复杂指令遵循上表现突出的模型进行合并期望得到一个既通情达理又能处理复杂指令的模型。4.1 环境准备与安装首先确保你的机器有足够的磁盘空间原始模型合并后模型至少需要30GB以及足够的内存来加载模型对于8B模型16GB以上为佳。推荐使用 Python 3.10 或以上版本。# 1. 克隆 mergekit 仓库 git clone https://github.com/arcee-ai/mergekit.git cd mergekit # 2. 使用 uv 或 pip 安装推荐 uv依赖管理更干净 # 如果你没有 uv先安装: pip install uv uv venv source .venv/bin/activate # Linux/macOS # .venv\Scripts\activate # Windows uv pip install -e . # 以可编辑模式安装方便修改代码 # 或者使用 pip pip install -e .安装完成后你可以通过mergekit-yaml命令来验证安装是否成功。4.2 编写合并配置文件这是 mergekit 的核心。所有合并逻辑都通过一个 YAML 文件定义。我们在项目根目录创建一个merge_config.yaml文件。# merge_config.yaml models: - model: meta-llama/Meta-Llama-3-8B-Instruct # 模型A parameters: weight: 0.5 # 权重0.5 - model: WizardLM/WizardLM-2-8B # 模型B parameters: weight: 0.5 # 权重0.5 merge_method: linear # 使用线性合并 parameters: normalize: true # 可选对权重进行归一化确保总和为1 dtype: bfloat16 # 输出模型的数据类型bfloat16是平衡精度和兼容性的好选择 tokenizer_source: union # 分词器处理方式。union会尝试合并两个模型的分词器词汇表。 # 其他选项model取自第一个模型 average平均词向量不推荐用于不同分词器 # 输出设置 slices: - sources: - model: meta-llama/Meta-Llama-3-8B-Instruct layer_range: [0, 32] # 假设模型有32层我们合并所有层 - model: WizardLM/WizardLM-2-8B layer_range: [0, 32]这个配置是最简单的等权重线性合并。slices部分定义了如何合并模型的每一层。这里我们简单地将两个模型的对应层进行加权平均。layer_range必须与模型的实际层数一致对于 Llama 3 8B通常是 32 层。4.3 执行合并命令运行以下命令开始合并过程。--allow-crimes参数允许合并一些不完全匹配的模型如某些参数名不一致但需谨慎使用。mergekit-run merge_config.yaml ./output_merged_model --allow-crimes --copy-tokenizermerge_config.yaml: 你的配置文件路径。./output_merged_model: 合并后模型的输出目录。--copy-tokenizer: 将处理好的分词器配置和文件复制到输出目录。--allow-crimes: 跳过一些严格的检查仅在必要时使用。合并过程会显示进度条。对于两个8B模型在消费级CPU上可能需要数小时如果有GPU且 mergekit 支持 CUDA 加速取决于具体操作时间会大大缩短。4.4 验证与测试合并结果合并完成后./output_merged_model目录下应该包含config.json,model.safetensors,tokenizer.json等标准 Hugging Face 模型文件。我们可以用transformers库快速加载并测试from transformers import AutoTokenizer, AutoModelForCausalLM import torch model_path ./output_merged_model tokenizer AutoTokenizer.from_pretrained(model_path) model AutoModelForCausalLM.from_pretrained( model_path, torch_dtypetorch.bfloat16, device_mapauto # 自动分配到可用设备GPU/CPU ) prompt 请用Python写一个快速排序函数并给出简要说明。 inputs tokenizer(prompt, return_tensorspt).to(model.device) with torch.no_grad(): outputs model.generate(**inputs, max_new_tokens300, do_sampleTrue, temperature0.7) print(tokenizer.decode(outputs[0], skip_special_tokensTrue))观察输出结果它是否同时具备了 Llama-3-Instruct 的对话流畅性和 WizardLM-2 的复杂指令分解能力你可以设计一组涵盖代码、推理、创意写作的测试题与两个母模型进行对比。5. 高级配置与性能调优指南基础合并只是开始。要真正发挥 mergekit 的威力你需要掌握一些高级配置和调优技巧。5.1 精细化层控制slices是 mergekit 最强大的功能之一它允许你以“手术刀”般的精度控制如何合并模型的每一部分。你不仅可以合并整个模型还可以只合并特定的层甚至从不同模型抽取不同层来“组装”一个新模型。slices: - sources: # 处理前16层 - model: model_a layer_range: [0, 16] parameters: weight: 0.8 - model: model_b layer_range: [0, 16] parameters: weight: 0.2 - sources: # 处理后16层使用不同的合并策略或模型 - model: model_c layer_range: [16, 32] - model: model_d layer_range: [16, 32] parameters: weight: 0.5这种配置可以用来做一个假设让模型的前几层负责基础特征提取更偏向于模型A的风格后几层负责高级推理和输出更偏向于模型B或C的风格。这在融合风格迥异的模型时非常有用。5.2 参数级别的微调除了按层合并你还可以指定只合并某些类型的参数。例如你可能希望只合并注意力机制中的q_proj,k_proj,v_proj,o_proj矩阵而保留每个模型独有的前馈网络mlp参数。merge_method: ties base_model: model_a parameters: density: 0.2 weight: [0.7, 0.3] # 对应下面两个模型 models: - model: model_a - model: model_b # 使用 tensor_map 来精细控制但更常见的是在 slices 中定义实际上更精细的控制通常通过编写自定义的合并方法脚本实现这需要你对模型架构和 PyTorch 有更深的理解。对于大多数应用层级别的控制已经足够。5.3 分词器合并策略详解tokenizer_source是一个容易出问题但又至关重要的参数。当合并的模型使用不同的分词器时例如一个用 Llama 的分词器一个用 GPT-NeoX 的分词器你需要决定新模型用哪个。union: 这是最常用也最推荐的方法。它会创建一个新词汇表包含两个原分词器的所有词汇。mergekit 会尝试处理嵌入层词向量的合并例如通过平均或截断/填充。这是让合并模型能理解两个母模型所有“语言”的关键。model: 直接采用第一个模型的分词器。简单但如果第二个模型有独特的词汇合并后的模型将无法识别它们。average: 不推荐。它试图平均两个分词器的词向量但分词器本身的结构差异可能导致奇怪的行为。实操心得在合并后务必检查输出目录下的tokenizer.json或tokenizer_config.json。用上面的测试脚本加载时如果出现词汇表大小不匹配的错误很可能就是分词器合并出了问题。一个补救方法是手动指定使用其中一个母模型的分词器来加载合并后的模型在from_pretrained时传入tokenizer原模型名但这会损失部分能力。5.4 内存与性能优化合并大模型是内存密集型和计算密集型任务。使用--low-cpu-memory选项这个选项会将模型权重以流式方式加载和计算而不是一次性全部载入内存。对于在内存有限的机器上合并超大模型如 70B是救命稻草但速度会慢一些。数据类型选择配置中的dtype选项。float16或bfloat16是标准选择能在精度和内存占用间取得平衡。如果你只是为了实验甚至可以尝试float8如果 mergekit 和你的硬件支持但可能会影响最终模型质量。分步合并对于极其复杂的合并如合并4个以上的模型可以考虑分两步走先两两合并成中间模型再合并中间模型。这有助于调试和节省内存。6. 常见问题、排错与实战经验录在实际操作中你一定会遇到各种报错和意外情况。下面是我踩过的一些坑和解决方案。6.1 常见错误与解决方案错误信息或现象可能原因解决方案Mismatched parameter shapes尝试合并的模型层数或维度不一致。例如一个32层一个34层。1. 检查layer_range配置确保范围正确且一致。2. 确认模型是否基于完全相同的架构如都是 Llama 3 8B。不同大小的变体7B vs 13B不能直接合并。KeyError: ‘model.embed_tokens.weight’模型参数名称不匹配。不同框架或自定义模型可能使用不同的参数命名约定。1. 使用--allow-crimes参数它会尝试忽略不匹配的键。2. 更稳妥的方法是先用Python加载两个模型打印它们的state_dict().keys()对比差异然后在配置文件中使用tensor_map进行手动映射高级用法。合并后的模型生成乱码或重复输出1. 分词器合并失败。2. 合并权重配置不当导致模型内部状态混乱。1. 检查并确认分词器合并策略。尝试用tokenizer_source: model并指定一个已知良好的模型来重新合并测试。2. 调整合并权重如从0.5/0.5改为0.7/0.3。可能是简单的线性冲突。3. 尝试使用 SLERP 代替线性合并。合并过程被Killed内存不足。系统 OOM Killer 杀掉了进程。1. 使用--low-cpu-memory模式。2. 关闭不必要的程序。3. 在拥有更大内存的机器或云实例上运行。4. 考虑使用 CPU 合并极慢但内存需求稳定。加载合并模型时ValueError: ... size mismatch模型文件与配置文件不匹配或者分词器词汇表大小与模型嵌入层维度不匹配。1. 确保config.json中的vocab_size与分词器实际词汇量一致。如果不一致手动修改config.json。2. 重新合并并仔细检查分词器配置。6.2 效果评估与迭代合并模型不是一蹴而就的需要科学评估和迭代。制定微型评估集不要用几百条数据精选10-20条能代表你目标能力的提示词。例如1条代码题1条逻辑推理1条创意写作1条事实问答1条指令遵循。自动化快速测试写一个脚本用你的评估集批量测试合并模型和原始模型并粗略对比输出质量可以人工打分也可以用简单的评估模型如 GPT-4 做裁判。AB/Blind测试如果你做的是对话模型合并可以把合并模型和母模型的输出打乱让其他人盲测哪个回答更好。迭代配置根据测试结果回头调整 YAML 中的weight、merge_method甚至尝试更精细的slices配置。这是一个实验性很强的过程。6.3 我的独家心得从小处着手不要一开始就尝试合并 70B 模型。用 7B 或更小的模型如 1B 左右的做快速原型实验验证你的合并想法是否有效。迭代速度会快很多。记录实验日志为每一次合并尝试创建一个文件夹里面包含使用的YAML配置文件、合并命令的完整日志、测试输出结果。你会感谢这个习惯的。社区模型是宝库Hugging Face Hub 上有成千上万的微调模型。寻找那些在特定任务上评分高、且基于相同基础架构的模型进行合并实验常常能碰撞出火花。例如合并多个在数学、代码、考试数据集上微调的模型可能得到一个“学霸”模型。理解“炼金术”的边界模型合并是强大的工具但它不能无中生有。如果两个母模型都不具备某项能力合并后的模型大概率也不会。它的主要作用是融合和增强现有能力或者平衡不同能力之间的表现。