使用 mergekit 合併大語言模型

社群文章 釋出於 2024 年 1 月 9 日

image/jpeg

模型合併是一種將 兩個或多個大語言模型(LLM) 組合成一個單一模型的技術。這是一種相對較新且實驗性的方法,可以低成本地建立新模型(無需 GPU)。模型合併的效果出奇地好,並在 Open LLM Leaderboard 上產生了許多最先進的模型。

在本教程中,我們將使用 mergekit 庫來實現它。具體來說,我們將回顧四種合併方法,並提供配置示例。然後,我們將使用 mergekit 建立我們自己的模型 Marcoro14-7B-slerp,它成為了 Open LLM Leaderboard (02/01/24) 上效能最好的模型。

程式碼可在 GitHubGoogle Colab 上獲取。我推薦使用我的自動化筆記本來輕鬆執行 mergekit:🥱 LazyMergekit

特別感謝 mergekit 庫的作者 Charles Goddard 審閱了本文。

注意:GML-Mistral-merged-v1 被錯誤地歸類為 7B 引數模型(而不是 8.99B)。

🤝 合併演算法

在本節中,我們將重點介紹目前在 mergekit 中實現的四種方法。請注意,還有其他方法,例如線性(linear)任務算術(Task Arithmetic)。如果您對關於模型合併的論文感興趣,我推薦 Hugging Face 上的這個優秀合集

1. SLERP

球面線性插值 (Spherical Linear Interpolation, SLERP) 是一種用於在兩個向量之間平滑插值的方法。它保持恆定的變化率,並保留向量所在球面空間的幾何特性。

與傳統的線性插值相比,有幾個理由更傾向於使用 SLERP。例如,在高維空間中,線性插值可能導致插值向量的模長減小(即,它會減小權重的尺度)。此外,權重方向的變化通常比模長的變化代表更有意義的資訊(如特徵學習和表示)。

SLERP 的實現遵循以下步驟

  1. 將輸入向量歸一化為單位長度,確保它們代表方向而非大小
  2. 使用它們的點積計算這些向量之間的夾角。
  3. 如果向量幾乎共線,為提高效率,它會預設使用線性插值。否則,SLERP 會根據插值因子 tt=0 = 100% 的第一個向量,t=1 = 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 輸入了一個梯度值。自注意力層和 MLP 層的引數將使用 OpenPipe/mistral-ft-optimized-1218mlabonne/NeuralHermes-2.5-Mistral-7B 的不同組合。其他層是這兩個模型的 50/50 混合。

你可以在 Hugging Face Hub 上找到最終模型:mlabonne/NeuralPipe-7B-slerp

2. TIES

由 Yadav 等人在這篇論文中介紹的 TIES-Merging 旨在高效地將多個特定任務的模型合併成一個單一的多工模型。它解決了模型合併中的兩個主要挑戰:

  • 模型引數的冗餘:它識別並消除特定任務模型中的冗餘引數。這是透過關注微調期間所做的更改,識別出前 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%),並進行了歸一化。在這裡,密度(density)意味著我們只保留每個模型 50% 的引數(另一半來自基礎模型)。

請注意,配置中權重的總和不等於 1,但 `normalize: true` 引數會自動在內部對它們進行歸一化。這個配置的靈感來自於 OpenHermes-2.5-neural-chat-7b-v3-1-7B 作者提供的引數。

你可以在 Hugging Face Hub 上找到最終模型:mlabonne/NeuralPipe-7B-ties

3. DARE

由 Yu 等人 (2023) 提出的 DARE 使用了與 TIES 類似的方法,但有兩個主要區別:

  • 剪枝 (Pruning):DARE 將微調後的權重隨機重置為其原始值(即基礎模型的值)。
  • 重縮放 (Rescaling):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)要高一些,但看起來它能持續給出更好的結果(參見此討論)。

你可以在 Hugging Face Hub 上找到它:mlabonne/Daredevil-7B。它也是本文中最好的合併模型,甚至優於 Marcoro14-7B-slerp。

4. Passthrough (直通)

Passthrough 方法與之前的方法有顯著不同。透過拼接不同大語言模型(LLM)的層,它可以產生具有奇異引數數量的模型(例如,用兩個 7B 引數模型合成一個 9B 模型)。這些模型通常被社群稱為“弗蘭肯合併”或“弗蘭肯斯坦模型”。

這項技術非常實驗性,但它成功地建立了一些令人印象深刻的模型,比如使用兩個 Llama 2 70B 模型建立的 goliath-120b。最近釋出的 SOLAR-10.7B-v1.0 也使用了同樣的想法,在他們的論文中稱之為深度-向上擴充套件 (depth-up scaling)。

配置示例

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

最終的弗蘭肯合併模型將包含第一個模型的全部 32 層,以及第二個模型的額外 8 層。這樣就建立了一個總共有 40 層和 8.99B 引數的弗蘭肯合併模型。這個配置的靈感來自於 GML-Mistral-merged-v1

你可以在 Hugging Face Hub 上找到最終模型:mlabonne/NeuralPipe-9B-merged

💻 合併你自己的模型

在這一節中,我們將使用 mergekit 載入一個合併配置,執行它,並將生成的模型上傳到 Hugging Face Hub。

首先,我們直接從原始碼安裝 mergekit,如下所示

!git clone https://github.com/cg123/mergekit.git
!cd mergekit && pip install -q -e .

在下面的程式碼塊中,我們以 YAML 格式載入合併配置。我們還指定了合併後模型的名稱以備後用。你可以將上一節的任何配置複製/貼上到這裡。

這次,我們將使用兩個不同的模型:Marcoroni-7B-v3Mistral-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 從基礎模型複製分詞器 (tokenizer)
  • --allow-crimes--out-shard-size 將模型分塊成更小的分片,以便在低記憶體的 CPU 上計算
  • --lazy-unpickle 啟用實驗性的惰性 unpickle 以降低記憶體使用

此外,某些模型可能需要 --trust_remote_code 標誌(對於 Mistral-7B 來說不是這種情況)。

此命令將下載合併配置中列出的所有模型的權重,並執行所選的合併方法(大約需要 10 分鐘)。

# Merge models
!mergekit-yaml config.yaml merge --copy-tokenizer --allow-crimes --out-shard-size 1B --lazy-unpickle

模型現在已經合併並儲存在 `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)
print(outputs[0]["generated_text"])

我們問了這個問題“什麼是大語言模型?”,收到了以下輸出:

大語言模型是一種人工智慧(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。
  • Open LLM Leaderboard,它聚合了六個基準:ARC、HellaSwag、MMLU、Winogrande、GSM8K 和 TruthfulQA。

不幸的是,我們無法將我們的模型提交到 Chatbot Arena。因此,我選擇使用 Open LLM Leaderboard 和 NousResearch 基準來評估它。

我將我們的模型提交到了 Open LLM Leaderboard(“🚀 Submit here!” 標籤頁)。如引言中所示,它在排行榜上排名為最佳的 7B 引數模型。以下是排行榜上的完整結果:

image/png

Open LLM Leaderboard 的問題在於這些基準是公開的。這意味著人們可以在測試資料上訓練 LLM 以獲得更好的結果。透過合併最好的模型,我們也在汙染我們自己的結果。可以安全地假設 Marcoro14-7B-slerp 被汙染了,並且這次合併中使用的一些模型已經在測試集上進行了訓練。如果你想建立最好的模型而不是“黑掉”排行榜,我建議只使用非合併模型來建立你自己的合併模型。

這就是為什麼我們不想只依賴 OpenLLM Leaderboard。對於 NousResearch 基準測試套件,我使用了 🧐 LLM AutoEval,透過一個簡單的 Colab 筆記本自動計算分數。以下是與優秀的 OpenHermes-2.5-Mistral-7B 相比的結果:

image/png

我們在每個基準上都比該模型取得了顯著的提升。請注意,NousResearch 基準測試套件與 Open LLM Leaderboard 共享一些任務:ARC-Challenge、TruthfulQA、HellaSwag 和 Winogrande。據我所知,Bigbench 是唯一一個 100% 不同的基準(如果不是這樣,請隨時與我聯絡)。然而,我們在此次合併中使用的一個模型仍然可能在 Bigbench 上訓練過。

結論

在本文中,我們介紹了使用四種不同方法合併大語言模型(LLM)的概念。我們詳細介紹了 SLERP、TIES、DARE 和 passthrough 的工作原理,並提供了配置示例。最後,我們使用 mergekit 執行 SLERP 建立了 Marcoro14-7B-slerp 並將其上傳到 Hugging Face Hub。我們在兩個基準測試套件上獲得了出色的效能:Open LLM Leaderboard(效能最佳的 7B 模型)和 NousResearch。如果你想建立自己的合併模型,我推薦使用我的自動化筆記本 🥱 LazyMergekit

另一種組合多個模型的方法是將它們合併成一個專家混合(MoE)架構。在下一篇文章中,我們將詳細討論如何做到這一點,並建立我們自己的 Mixtral 式模型。如果你喜歡這篇文章,請在 Hugging Face 和 Twitter @maximelabonne 上關注我。

社群

很棒的文章。在我看來,只有具有相同架構(例如,相同的層數、隱藏維度、注意力頭)的模型才能用這種方法合併。這正確嗎?你怎麼知道哪些模型具有相同的架構?

·
文章作者

謝謝 @akratz ,是的,你說得對。通常,這會在模型卡片或配置檔案中說明。

解釋得很棒 ;)
我正在考慮使用 Slerp 或 Passthrough 來建立一個大模型的小型版本,並將其用作推測性草稿模型。

使用 Passthrough 時,均勻地選擇層以避免層間距離過大是否有意義?(如 Solar 論文中提到的)例如:原始層從 1 到 10,然後選擇 1, 4, 7, 10 來建立一個縮小一半的模型。

您會推薦哪種方法?
先謝謝了!

文章作者

謝謝!是的,通常你還希望更多地關注模型的首層和末層,因為它們更重要。

是的,但是如何進行模型湯(model soup)的基因合併演化呢?

很棒的文章,謝謝。TNG 釋出了 DeepSeek-R1T-Chimera 模型 [1] - 還有其他人用非常大的模型做過這個嗎?

[1] https://huggingface.co/tngtech/DeepSeek-R1T-Chimera

求求求求你,能出一個關於基因合併的筆記本嗎?...我已經試了又試!

另外,我做了 dares-tie 合併,為什麼它們總是效果不好?
一個簡單的 ties 合併通常效果不錯?

註冊登入 以發表評論

© . This site is unofficial and not affiliated with Hugging Face, Inc.