如何使用mergekit合并大型语言模型?

2024年01月11日 由 alex 发表 877 0

模型合并是一种将两个或多个法学硕士合并为一个模型的技术。这是一种相对较新的实验性方法,可以廉价地创建新模型(不需要 GPU)。模型合并的效果出人意料地好,并在Open LLM 排行榜上产生了许多最先进的模型。


在本文中,我们将使用mergekit库来实现它。更具体地说,我们将回顾四种合并方法并提供配置示例。然后,我们将使用 mergekit 创建我们自己的模型Marcoro14–7B-slerp,该模型成为 Open LLM 排行榜上表现最好的模型 (02/01/23)。


合并算法


我们将重点介绍目前在mergekit中实现的四种方法。注意,还有其他方法,例如线性插值和任务算术。


SLERP


球面线性插值(SLERP)是用来平滑插值两个向量之间的一种方法。它保持恒定的变化速率,并保留向量所在的球面空间的几何属性。


选择SLERP而不是传统的线性插值有几个原因。例如,在高维空间中,线性插值可能导致插值向量的大小减小(即减少权重的规模)。此外,权重方向的变化往往比变化的大小表示更有意义的信息(如特征学习和表示)。


SLERP的实现步骤如下:


  1. 将输入向量标准化为单位长度,确保他们代表方向而不是大小。
  2. 使用它们的点积计算这些向量之间的夹角。
  3. 如果向量几乎共线,为了效率默认使用线性插值。否则,SLERP根据插值因子t(t=0=第一个向量的100%,t=1=模型2的100%)和向量之间的角度计算缩放因子。
  4. 这些因子用于加权原始向量,然后求和得到插值向量。


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,旨在有效地将多个特定任务模型合并成一个多任务模型。它解决了模型合并中的两个主要挑战:


  • 模型参数的冗余:它识别并消除了特定任务模型中的冗余参数。这是通过关注在微调期间做出的更改来实现的,识别最重要的top-k%更改,并丢弃其余的。
  • 参数符号之间的不一致:当不同模型对同一参数提出相反的调整时,会出现冲突。TIES-Merging通过创建一个统一的符号向量来解决这些冲突,该向量表示所有模型中最主导的变化方向(正或负)。


TIES-Merging分为以下三个步骤:


  1. Trim(修剪):通过仅保留最显著参数的一部分(密度参数),并将其余部分重置为零,来减少特定任务模型中的冗余。
  2. Elect Sign(选取符号):基于累积大小最主导方向(正或负),创建一个统一的符号向量,解决不同模型之间的符号冲突。
  3. Disjoint Merge(非联合合并):对于与统一符号向量对齐的参数值进行平均,排除零值。


与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 类似的方法,主要有两个区别:


  • 剪枝:DARE 会随机将微调过的权重重置为其原始值(即基础模型的值)。
  • 重新标度:DARE 重新标度权重,以保持模型输出的期望值大致不变。它添加了两个(或更多)模型的重新标度权重到具有比例因子的基础模型的权重。

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)


我们使用以下参数运行合并命令:


  • --copy-tokenizer 用于复制基础模型的分词器
  • --allow-crimes 和 --out-shard-size 用于将模型分块成较小的分片,以便在低RAM的CPU上进行计算
  • --lazy-unpickle 用于启用实验性的延迟反序列化功能,以降低内存使用量


此外,一些模型可能需要 --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。


看起来不错,但我们需要更全面的评估。对于这类通用模型,有几个有趣的基准测试:


  • Chatbot Arena,它基于人类投票编制了基于Elo的LLM排行榜。
  • MT-bench(相同链接),它使用GPT-4作为评判,根据一组多轮问题对模型回应进行评分。
  • NousResearch基准测试套件,它汇总了四个基准测试:AGIEval、GPT4ALL、TruthfulQA和Bigbench。GPT4ALL本身包括HellaSwag、OpenBookQA、Winogrande、ARC-Easy、ARC-Challenge、BoolQ和PIQA。
  • 开放LLM排行榜(Open LLM Leaderboard),它聚合了六个基准测试:ARC、HellaSwag、MMLU、Winogrande、GSM8K和TruthfulQA。


不幸的是,我们无法将我们的模型提交到聊天机器人竞技场。相反,我选择使用开放LLM排行榜和NousResearch基准进行评估。


我将我们的模型提交到了开放LLM排行榜。它在排行榜上被列为最佳7B参数模型。以下是完整的结果:


4


Open LLM 排行榜存在的问题在于这些性能基准是公开的。这意味着人们可以在测试数据上训练 LLM(大型语言模型),以获得更好的结果。通过合并最佳模型,我们也玷污了自己的结果。可以安全地假定 Marcoro14–7B-slerp 已经被污染了,且此次合并使用的一些模型已经在测试集上进行了训练。如果你想创造出最佳模型,而不是操纵排行榜,我推荐仅使用非合并模型来创建你自己的合并。


这就是为什么我们不想仅依赖于 OpenLLM 排行榜。对于 NousResearch 基准测试套件,我使用了LLM AutoEval 来自动计算分数,仅需一个简单的 Colab 笔记本。这里是与杰出的 OpenHermes-2.5-Mistral-7B 相比的结果:


5


我们在每个基准测试上都显著提高了这个模型的性能。请注意,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。


文章来源:https://medium.com/towards-data-science/merge-large-language-models-with-mergekit-2118fb392b54
欢迎关注ATYUN官方公众号
商务合作及内容投稿请联系邮箱:bd@atyun.com
评论 登录
写评论取消
回复取消