模型合并是一种将两个或多个法学硕士合并为一个模型的技术。这是一种相对较新的实验性方法,可以廉价地创建新模型(不需要 GPU)。模型合并的效果出人意料地好,并在Open LLM 排行榜上产生了许多最先进的模型。
在本文中,我们将使用mergekit库来实现它。更具体地说,我们将回顾四种合并方法并提供配置示例。然后,我们将使用 mergekit 创建我们自己的模型Marcoro14–7B-slerp,该模型成为 Open LLM 排行榜上表现最好的模型 (02/01/23)。
合并算法
我们将重点介绍目前在mergekit中实现的四种方法。注意,还有其他方法,例如线性插值和任务算术。
SLERP
球面线性插值(SLERP)是用来平滑插值两个向量之间的一种方法。它保持恒定的变化速率,并保留向量所在的球面空间的几何属性。
选择SLERP而不是传统的线性插值有几个原因。例如,在高维空间中,线性插值可能导致插值向量的大小减小(即减少权重的规模)。此外,权重方向的变化往往比变化的大小表示更有意义的信息(如特征学习和表示)。
SLERP的实现步骤如下:
SLERP目前是最流行的合并方法,但它仅限于一次合并两个模型。依然可以层次化地合并多个模型,如在Mistral-7B-Merge-14-v0.1中所示。
配置示例:
slices:
- sources:
- model: OpenPipe/mistral-ft-optimized-1218
layer_range: [0, 32]
- model: mlabonne/NeuralHermes-2.5-Mistral-7B
layer_range: [0, 32]
merge_method: slerp
base_model: OpenPipe/mistral-ft-optimized-1218
parameters:
t:
- filter: self_attn
value: [0, 0.5, 0.3, 0.7, 1]
- filter: mlp
value: [1, 0.5, 0.7, 0.3, 0]
- value: 0.5
dtype: bfloat16
这是经典的SLERP配置,应用于两个模型的每一层。注意我们为插值因子t输入了一个渐变值。自我关注(self-attention)和MLP层的参数将使用OpenPipe/mistral-ft-optimized-1218和mlabonne/NeuralHermes-2.5-Mistral-7B的不同组合。其他层是这两个模型的50/50混合。
TIES
这个由Yadav等人在本文中介绍的TIES-Merging,旨在有效地将多个特定任务模型合并成一个多任务模型。它解决了模型合并中的两个主要挑战:
TIES-Merging分为以下三个步骤:
与SLERP不同,TIES可以一次合并多个模型。
配置示例:
models:
- model: mistralai/Mistral-7B-v0.1
# no parameters necessary for base model
- model: OpenPipe/mistral-ft-optimized-1218
parameters:
density: 0.5
weight: 0.5
- model: mlabonne/NeuralHermes-2.5-Mistral-7B
parameters:
density: 0.5
weight: 0.3
merge_method: ties
base_model: mistralai/Mistral-7B-v0.1
parameters:
normalize: true
dtype: float16
在此配置中,我们使用 Mistral-7B 作为基础模型来计算增量权重。我们合并了相同的两个模型:mistral-ft-optimized-1218(50%)和 NeuralHermes-2.5-Mistral-7B(30%),并进行了归一化。密度意味着我们只保留每个模型的 50% 参数(另一半来自基础模型)。
请注意,在配置中权重之和并不等于 1,但是 normalize: true 参数会自动在内部对它们进行归一化。
DARE
DARE 由Yu等人(2023年)引入,使用了一种与 TIES 类似的方法,主要有两个区别:
Mergekit 对这种方法的实现有两种形式:带有 TIES 的标记选举步骤(dare_ties)或不带(dare_linear)。
配置示例:
models:
- model: mistralai/Mistral-7B-v0.1
# No parameters necessary for base model
- model: samir-fama/SamirGPT-v1
parameters:
density: 0.53
weight: 0.4
- model: abacusai/Slerp-CM-mist-dpo
parameters:
density: 0.53
weight: 0.3
- model: EmbeddedLLM/Mistral-7B-Merge-14-v0.2
parameters:
density: 0.53
weight: 0.3
merge_method: dare_ties
base_model: mistralai/Mistral-7B-v0.1
parameters:
int8_mask: true
dtype: bfloat16
在这种配置下,我们使用 dare_ties 合并了基于 Mistral-7B 的三种不同模型。这次,我选择了权重之和为1(总和应在0.9到1.1之间)。密度参数比论文中推荐的要高一点(<0.5),但看起来它能持续提供更好的结果。
Passthrough
直通法与之前的方法有显著不同。通过将不同LLM的层连接起来,可以生成具有奇异参数数量的模型(例如,将两个7B参数模型合并成一个9B模型)。这些模型通常被社区称为“弗兰肯合并”或“弗兰肯斯坦模型”。
这种技术非常实验性,但它设法创造了令人印象深刻的模型,比如使用两个 Llama2 70B 模型的 goliath-120b。最近发布的 SOLAR-10.7B-v1.0 也使用了同样的思想,在他们的论文中称之为深度上升尺度。
配置示例:
slices:
- sources:
- model: OpenPipe/mistral-ft-optimized-1218
layer_range: [0, 32]
- sources:
- model: mlabonne/NeuralHermes-2.5-Mistral-7B
layer_range: [24, 32]
merge_method: passthrough
dtype: bfloat16
由此产生的"Frankenmerge "将包含来自第一个模型的全部32层,以及来自第二个模型的额外8层。这样就创建了一个总共有40层、8.99亿参数的"Frankenmerge "。这个配置受到了GML-Mistral-merged-v1的启发。
合并你自己的模型
在本节中,我们将使用mergekit加载合并配置,运行它,并将生成的模型上传到Hugging Face Hub。
首先,我们直接从源代码安装mergekit,具体操作如下:
!git clone https://github.com/cg123/mergekit.gitclone https://github.com/cg123/mergekit.git
!cd mergekit && pip install -q -e .
在下面的代码块中,我们以YAML格式加载合并配置。我们还指定合并模型的名称以便未来使用。你可以复制/粘贴前一部分的任何配置到这里。
这次,我们将使用两个不同的模型:Marcoroni-7B-v3 和 Mistral-7B-Merge-14-v0.1,并且使用SLERP方法将它们合并。我们将配置保存为一个yaml文件,以便在合并命令中作为输入使用。
import yaml
MODEL_NAME = "Marcoro14-7B-slerp"
yaml_config = """
slices:
- sources:
- model: AIDC-ai-business/Marcoroni-7B-v3
layer_range: [0, 32]
- model: EmbeddedLLM/Mistral-7B-Merge-14-v0.1
layer_range: [0, 32]
merge_method: slerp
base_model: AIDC-ai-business/Marcoroni-7B-v3
parameters:
t:
- filter: self_attn
value: [0, 0.5, 0.3, 0.7, 1]
- filter: mlp
value: [1, 0.5, 0.7, 0.3, 0]
- value: 0.5
dtype: bfloat16
"""
# Save config as yaml file
with open('config.yaml', 'w', encoding="utf-8") as f:
f.write(yaml_config)
我们使用以下参数运行合并命令:
此外,一些模型可能需要 --trust_remote_code 标志(Mistral-7B 模型不需要这样做)。
该命令将下载合并配置中列出的所有模型的权重,并运行所选的合并方法(预计需要大约10分钟)。
# Merge models
!mergekit-yaml config.yaml merge --copy-tokenizer --allow-crimes --out-shard-size 1B --lazy-unpickl
模型现在已经合并并保存在merge目录中。在上传之前,我们可以创建一个README文件,包含所有实现再现性所需的信息。以下代码块定义了一个Jinja模板,并会自动用合并配置中的数据填充它。
!pip install -qU huggingface_hub
from huggingface_hub import ModelCard, ModelCardData
from jinja2 import Template
username = "mlabonne"
template_text = """
---
license: apache-2.0
tags:
- merge
- mergekit
- lazymergekit
{%- for model in models %}
- {{ model }}
{%- endfor %}
---
# {{ model_name }}
{{ model_name }} is a merge of the following models using [mergekit](https://github.com/cg123/mergekit):
{%- for model in models %}
* [{{ model }}](https://huggingface.co/{{ model }})
{%- endfor %}
## ? Configuration
```yaml
{{- yaml_config -}}
```
"""
# Create a Jinja template object
jinja_template = Template(template_text.strip())
# Get list of models from config
data = yaml.safe_load(yaml_config)
if "models" in data:
models = [data["models"][i]["model"] for i in range(len(data["models"])) if "parameters" in data["models"][i]]
elif "parameters" in data:
models = [data["slices"][0]["sources"][i]["model"] for i in range(len(data["slices"][0]["sources"]))]
elif "slices" in data:
models = [data["slices"][i]["sources"][0]["model"] for i in range(len(data["slices"]))]
else:
raise Exception("No models or slices found in yaml config")
# Fill the template
content = jinja_template.render(
model_name=MODEL_NAME,
models=models,
yaml_config=yaml_config,
username=username,
)
# Save the model card
card = ModelCard(content)
card.save('merge/README.md')
现在我们有了模型卡,我们可以将整个文件夹推送到 Hub 上。
from google.colab import userdata
from huggingface_hub import HfApi
username = "mlabonne"
# Defined in the secrets tab in Google Colab
api = HfApi(token=userdata.get("HF_TOKEN"))
api.create_repo(
repo_id=f"{username}/{MODEL_NAME}",
repo_type="model"
)
api.upload_folder(
repo_id=f"{username}/{MODEL_NAME}",
folder_path="merge",
)
模型现在可以在Hugging Face Hub上获取,地址是mlabonne/Marcoro14–7B-slerp。在另一个笔记本中,我们可以使用以下代码在免费的T4 GPU上尝试该模型:
!pip install -qU transformers accelerate
from transformers import AutoTokenizer
import transformers
import torch
model = "mlabonne/Marcoro14-7B-slerp"
messages = [{"role": "user", "content": "What is a large language model?"}]
tokenizer = AutoTokenizer.from_pretrained(model)
prompt = tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=True
)
pipeline = transformers.pipeline(
"text-generation",
model=model,
torch_dtype=torch.float16,
device_map="auto",
)
outputs = pipeline(prompt, max_new_tokens=256, do_sample=True, temperature=0.7, top_k=50, top_p=0.95)
我们正在询问“什么是大型语言模型?”并得到了如下输出:
大型语言模型是一种人工智能(AI)系统,它通过大量文本数据进行训练。它旨在理解和生成类似人类的语言,对在句子或文档中接下来可能出现的单词或短语进行预测。这些模型使用复杂的算法和神经网络架构从数据中学习,并随着时间的推移提高它们的性能。一些知名的大型语言模型包括OpenAI的GPT-3和Google的BERT。
看起来不错,但我们需要更全面的评估。对于这类通用模型,有几个有趣的基准测试:
不幸的是,我们无法将我们的模型提交到聊天机器人竞技场。相反,我选择使用开放LLM排行榜和NousResearch基准进行评估。
我将我们的模型提交到了开放LLM排行榜。它在排行榜上被列为最佳7B参数模型。以下是完整的结果:
Open LLM 排行榜存在的问题在于这些性能基准是公开的。这意味着人们可以在测试数据上训练 LLM(大型语言模型),以获得更好的结果。通过合并最佳模型,我们也玷污了自己的结果。可以安全地假定 Marcoro14–7B-slerp 已经被污染了,且此次合并使用的一些模型已经在测试集上进行了训练。如果你想创造出最佳模型,而不是操纵排行榜,我推荐仅使用非合并模型来创建你自己的合并。
这就是为什么我们不想仅依赖于 OpenLLM 排行榜。对于 NousResearch 基准测试套件,我使用了LLM AutoEval 来自动计算分数,仅需一个简单的 Colab 笔记本。这里是与杰出的 OpenHermes-2.5-Mistral-7B 相比的结果:
我们在每个基准测试上都显著提高了这个模型的性能。请注意,NousResearch 基准测试套件与 Open LLM 排行榜的一些任务有所重叠:ARC-Challenge、TruthfulQA、HellaSwag 和 Winogrande。
结论
在这篇文章中,我们介绍了使用四种不同方法合并 LLM的概念。我们详细解释了 SLERP、TIES、DARE 和 passthrough 的工作方式,并提供了配置示例。最后,我们运行了 SLERP 并使用 mergekit 创建了 Marcoro14–7B-slerp,并将其上传到 Hugging Face Hub。我们在两个基准测试套件上获得了出色的性能:Open LLM 排行榜(表现最佳的 7B 模型)和 NousResearch。