Лучший опыт

Слияние больших языковых моделей с помощью mergekit.

Под слиянием моделей подразумевают технику, которая объединяет две или более LLM в одну модель. Это относительно новый и экспериментальный метод, позволяющий экономно создавать новые модели (GPU не требуется). Слияние работает на удивление хорошо  —  техника привела к появлению множества самых передовых моделей, представленных на Open LLM Leaderboard. В этом руководстве мы реализуем слияние с помощью библиотеки mergekit. Мы рассмотрим четыре ме?
Слияние больших языковых моделей с помощью mergekit...

Под слиянием моделей подразумевают технику, которая объединяет две или более LLM в одну модель. Это относительно новый и экспериментальный метод, позволяющий экономно создавать новые модели (GPU не требуется). Слияние работает на удивление хорошо  —  техника привела к появлению множества самых передовых моделей, представленных на Open LLM Leaderboard.

В этом руководстве мы реализуем слияние с помощью библиотеки mergekit. Мы рассмотрим четыре метода слияния и приведем примеры конфигураций. Затем используем mergekit для создания собственной модели, Marcoro14–7B-slerp, которая была признана лучшей на Open LLM Leaderboard (02/01/24).

Код доступен на GitHub и Google Colab. Рекомендую использовать мой автоматизированный ноутбук LazyMergekit для легкого запуска mergekit.

Изображение автора

Алгоритмы слияния

В этом разделе мы сосредоточимся на четырех механизмах, которые в настоящее время реализованы в mergekit. Обратите внимание: существуют и другие методы, такие как линейный и Task Arithmetic.

1. SLERP

Сферическая линейная интерполяция (SLERP)  —  это метод, используемый для плавной интерполяции между двумя векторами. Он поддерживает постоянную скорость изменения и сохраняет геометрические свойства сферического пространства, в котором находятся векторы.

Есть несколько причин, по которым SLERP отдают предпочтение перед традиционной линейной интерполяцией. Например, в высокоразмерных пространствах линейная интерполяция может привести к уменьшению величины интерполированного вектора (т. е. она уменьшает масштаб весов). Более того, изменение направления весов часто представляет собой более значимую информацию (например, для изучения и презентации признаков), чем величина изменения.

SLERP реализуется с помощью следующих шагов.

  1. Приведение входящих векторов к единичной длине, чтобы они представляли направления, а не величины.
  2. Вычисление угла между этими векторами с помощью их скалярного произведения.
  3. Если векторы почти коллинеарны, то по умолчанию используется линейная интерполяция для повышения эффективности. В противном случае SLERP вычисляет коэффициенты масштабирования на основе коэффициента интерполяции t (t=0 = 100% первого вектора, t=1 = 100% модели 2) и угла между векторами.
  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-1218 и mlabonne/NeuralHermes-2.5-Mistral-7B. Остальные слои представляют собой смесь двух моделей в пропорции 50/50.

Итоговую модель можно найти на хабе ресурса Hugging Face по ссылке.

2. TIES

Система TIES-Merging, представленная в этой статье Ядавом и другими, предназначена для эффективного объединения нескольких моделей, специфичных для конкретной задачи, в одну многозадачную модель. Она решает две основные проблемы, связанные с объединением моделей.

  • Избыточность параметров. TIES выявляет и устраняет избыточные параметры в моделях для конкретных задач. Это достигается путем фокусировки на изменениях, сделанных во время тонкой настройки, выявления топ-k % наиболее значимых изменений и отбрасывания всего остального.
  • Разногласия между знаками параметров. Когда разные модели предлагают противоположные корректировки одного и того же параметра, возникают противоречия. TIES-Merging разрешает подобные конфликты, создавая вектор с единым знаком, который представляет наиболее доминирующее направление изменений во всех моделях.

Реализация TIES-Merging состоит из трех этапов.

  • Обрезка. Уменьшение избыточности в моделях, специфичных для конкретной задачи, с сохранением только части наиболее значимых параметров (параметр плотности) и сбрасыванием остальных в ноль.
  • Выбор знака. Устранение конфликтов знаков в разных моделях путем создания вектора с единым знаком на основе наиболее доминирующего направления (положительного или отрицательного) с точки зрения суммарной величины.
  • Раздельное слияние. Усреднение значений параметров, которые согласуются с вектором с единым знаком (за исключением нулевых значений).

В отличие от SLERP, TIES может объединять несколько моделей одновременно.

Пример конфигурации:

models:
- model: mistralai/Mistral-7B-v0.1
# параметры для базовой модели не нужны
- 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 автоматически нормализует их внутренне. Указанная конфигурация вдохновлена параметрами, предоставленными автором OpenHermes-2.5-neural-chat-7b-v3–1–7B.

Итоговую модель можно найти на хабе Hugging Face по ссылке.

3. DARE

В системе DARE, представленной Ю и другими (2023), используется подход, аналогичный TIES, с двумя основными отличиями.

  • Отсечение. DARE случайным образом возвращает тонко настроенные веса к их исходным значениям (значениям базовой модели).
  • Перемасштабирование. DARE перемасштабирует веса, чтобы ожидания результатов модели оставались примерно теми же. Происходит добавление перемасштабированных весов двух (или более) моделей к весам базовой модели с коэффициентом масштабирования.

Есть два варианта реализации этого метода в mergekit: с шагом выбора знака TIES (dare_ties) и без него (dare_linear).

Пример конфигурации:

models:
- model: mistralai/Mistral-7B-v0.1
# Параметры для базовой модели не нужны
- 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

В этой конфигурации мы объединяем три различные модели на основе Mistral-7B с помощью dare_ties. На этот раз я выбрал веса, которые в сумме равны 1 (сумма должна быть между 0,9 и 1,1). Параметр плотности немного выше, чем рекомендуется в работе (<0,5), но, похоже, он дает стабильно лучшие результаты (см. обсуждение).

Вы можете найти модель на хабе Hugging Face по ссылке. Это также лучшая модель слияния, представленная в данной статье  —  она превосходит даже Marcoro14–7B-slerp.

4. Passthrough

Метод passthrough значительно отличается от предыдущих. Объединяя слои из разных LLM, он может создавать модели с экзотическим числом параметров (например, до 9 миллиардов с двумя моделями по 7 миллиардов параметров). В сообществе такие модели часто называют “франкенмерджами” или “моделями Франкенштейна”.

Эта техника пока еще переживает экспериментальный период, но с ее помощью удалось создать впечатляющие модели, такие как goliath-120b с использованием двух моделей Llama 2 70B. Недавно выпущенная 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

Итоговая модель “франкенмердж” будет содержать все 32 слоя из первой модели и 8 дополнительных слоев из второй. Таким образом, получится франкенмердж с 40 слоями и 8,99 миллиардами параметров. Стимулом для создания этой конфигурации послужила GML-Mistral-merged-v1.

Вы можете найти итоговую модель на хабе Hugging Face по ссылке.

Объединяйте модели

В этом разделе мы будем использовать mergekit для загрузки конфигурации слияния, ее запуска и загрузки полученной модели на хаб Hugging Face.

Прежде всего, установим mergekit непосредственно из исходника следующим образом:

!git clone 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

"""

# Сохранение конфигурации в виде YAML-файла
with open('config.yaml', 'w', encoding="utf-8") as f:
f.write(yaml_config)

Запускаем команду слияния со следующими параметрами.

  • --copy-tokenizer: для копирования токенизатора из базовой модели.
  • --allow-crimes и --out-shard-size: для разбиения моделей на более мелкие шарды, которые можно вычислить на процессоре с небольшим объемом оперативной памяти.
  • --lazy-unpickle: для включения экспериментального ленивого распаковщика с целью уменьшения затрат памяти.

Кроме того, для некоторых моделей может потребоваться флаг --trust_remote_code (не относится к Mistral-7B).

Эта команда загрузит веса всех моделей, перечисленных в конфигурации слияния, и запустит выбранный метод слияния (занимает около 10 минут).

# Слияние моделей
!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 -}}
```
"""

# Создание объекта шаблона Jinja
jinja_template = Template(template_text.strip())

# Получение списка моделей из конфигурации
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")

# Заполнение шаблона
content = jinja_template.render(
model_name=MODEL_NAME,
models=models,
yaml_config=yaml_config,
username=username,
)

# Сохранение карты модели
card = ModelCard(content)
card.save('merge/README.md')

Теперь, когда у нас есть карта модели, можем передать всю папку на хаб.

from google.colab import userdata
from huggingface_hub import HfApi

username = "mlabonne"

# Определяется на вкладке "Secrets" в 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 по ссылке. В другом ноутбуке можем потестить модель на бесплатном GPU T4, используя следующий код:

!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)

Мы задали вопрос “Что такое большая языковая модель?” и получили ответ:

Большая языковая модель  —  это тип системы ИИ, которая была обучена на огромных объемах текстовых данных. Она предназначена для понимания и генерации человекоподобного языка с целью составления прогнозов о том, какие слова или фразы могут быть следующими в предложении или документе. Эти модели используют сложные алгоритмы и архитектуры нейронных сетей, чтобы учиться на данных и улучшать производительность с течением времени. Среди известных крупных языковых моделей можно назвать GPT-3 от OpenAI и BERT от Google.

Выглядит неплохо, но нам нужна более комплексная оценка. Для такого рода моделей общего назначения существует несколько интересных бенчмарков.

  • Chatbot Arena: составляет таблицу лидеров LLM на основе Elo, ориентируясь на результаты голосования людей.
  • 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!”). Как показано во введении, она заняла место лучшей модели с 7 миллиардами параметров в таблице лидеров. Вот все результаты:

Изображение автора

Проблема с Open LLM Leaderboard заключается в том, что эти бенчмарки находятся в открытом доступе. Это значит, что люди могут обучать LLM на тестовых данных, чтобы получить лучшие результаты. Объединяя топовые модели, мы в то же время “загрязняем” результаты. Можно предположить, что Marcoro14–7B-slerp загрязнена, а некоторые модели, используемые в этом процессе слияния, обучались на тестовом наборе. Если хотите создать топовую модель без манипуляций с таблицей лидеров, рекомендую использовать для создания собственных слияний только не объединенные модели.

Вот почему не стоит полагаться лишь на OpenLLM Leaderboard. Для набора бенчмарков NousResearch я использовал LLM AutoEval для автоматического вычисления оценок с помощью простого ноутбука Colab. Вот результаты в сравнении с превосходной OpenHermes-2.5-Mistral-7B:

Изображение автора

Мы получаем значительно лучшие результаты по сравнению с этой моделью по каждому бенчмарку. Обратите внимание: набор бенчмарков NousResearch имеет некоторые общие задачи с Open LLM Leaderboard (ARC-Challenge, TruthfulQA, HellaSwag и Winogrande). Насколько мне известно, Bigbench  —  единственный бенчмарк, который отличается на все 100%. Тем не менее одна из моделей, которую мы использовали в этом процессе слияния, могла быть обучена на Bigbench.

Заключение

В данной статье мы представили концепцию слияния LLM с помощью четырех различных методов. Мы подробно описали, как работают SLERP, TIES, DARE и passthrough, и привели примеры конфигураций. Наконец, запустили SLERP с библиотекой mergekit, чтобы создать Marcoro14–7B-slerp и загрузить ее на хаб Hugging Face. Мы получили отличные показатели производительности в двух наборах бенчмарков: Open LLM Leaderboard (лучшая модель с 7 миллиардами параметров) и NousResearch. Если хотите создавать слияния самостоятельно, рекомендую использовать мой автоматизированный ноутбук LazyMergekit.