Pull to refresh

Объединить большие языковые модели реально с помощью mergekit?

Level of difficultyMedium
Reading time13 min
Views2.7K
Original author: Maxime Labonne

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

Сейчас в открытом доступе уже есть огромное количество ИИ-моделей, которые разработчики улучшают и комбинируют, чтобы создавать новые модели для более глубоких и сложных задач. И нынешние технологии позволяют им делать это, не тратя много времени и ресурсов на обучение.

Слияние моделей — это методика, которая объединяет две или более LLM-модели в одну. Это относительно новый и экспериментальный метод создания новых моделей без использования GPU, а значит, недорогой. И да, это работает. Причем работает на удивление хорошо, что в результате дает множество современных моделей на доске лидеров Open LLM.

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

Код доступен на GitHub и Google Colab. Кстати, для простого запуска mergekit можно использовать LazyMergekit.

Обратите внимание, что GML-Mistral-merged-v1 неверно классифицируется как модель с 7 миллиардами параметров (вместо 8.99 миллиардов).
Обратите внимание, что GML-Mistral-merged-v1 неверно классифицируется как модель с 7 миллиардами параметров (вместо 8.99 миллиардов).

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

В этом разделе мы сосредоточимся на четырех методах слияния, которые уже реализованы в mergekit. Здесь важно отметить, что существуют и другие методы, такие как линейный и арифметика задач, если вам интересно прочитать про них, то сделать это можно здесь.

SLERP

Spherical Linear Interpolation (Сферическая Линейная Интерполяция)

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

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

SLERP — это метод плавной интерполяции между двумя векторами.

Вот как он работает:

1. Начинаем с нормализации векторов до единичной длины, чтобы они показывали направления, а не силу.

2. Затем мы вычисляем угол между этими векторами, используя их скалярное произведение.

3. Если векторы почти параллельны, мы используем стандартную линейную интерполяцию для эффективности. В противном случае мы вычисляем коэффициенты масштабирования на основе коэффициента интерполяции t (t=0 - это 100% первого вектора, t=1 - это 100% второго вектора) и угла между векторами.

4. Наконец, эти коэффициенты используются для слияния исходных векторов, которые затем совмещаются вместе, чтобы получить окончательный интерполированный вектор.

Сейчас SLERP — это самый популярный метод слияния, но он ограничен объединением только двух моделей за раз. Впрочем, возможно реализовать иерархическое объединение нескольких моделей, как показано здесь.

Рассмотрим пример настройки на YAML:

slices:  # Срезы моделей
  - sources:  # Источники для первого среза
      - model: OpenPipe/mistral-ft-optimized-1218  # Модель 1
        layer_range: [0, 32]  # Диапазон слоев модели 1
      - model: mlabonne/NeuralHermes-2.5-Mistral-7B  # Модель 2
        layer_range: [0, 32]  # Диапазон слоев модели 2
merge_method: slerp  # Метод слияния моделей
base_model: OpenPipe/mistral-ft-optimized-1218  # Базовая модель
parameters:  # Параметры
  t:  # Параметр t
    - filter: self_attn  # Фильтр self_attn
      value: [0, 0.5, 0.3, 0.7, 1]  # Значение для фильтра self_attn
    - filter: mlp  # Фильтр mlp
      value: [1, 0.5, 0.7, 0.3, 0]  # Значение для фильтра mlp
    - value: 0.5  # Значение параметра t
dtype: bfloat16  # Тип данных

Это обычная настройка SLERP, которая применяется к каждому слою обеих моделей. Мы указываем различные значения для коэффициента интерполяции t. Параметры для слоев само-внимания (self-attention) и MLP используют разные комбинации двух моделей (этой и этой). Для всех остальных слоев, не явно указанных (таких как слои само-внимания и MLP), мы просто берем среднее значение между двумя моделями. То есть каждая модель вносит равный вклад в эти слои.

Итоговую модель можно найти на Hugging Face Hub здесь

TIES

Метод TIES-Merging помогает объединять несколько моделей, каждая из которых специализируется на своей задаче, в одну модель, которая может выполнять несколько задач одновременно. 

Такой метод решает две проблемы:

Избыточность параметров

TIES-Merging идентифицирует и удаляет лишние параметры в моделях. Он оставляет только самые важные изменения, игнорируя незначительные.

Несогласие в изменениях

Если разные модели предлагают противоположные корректировки параметров, TIES-Merging создает общий вектор изменений, чтобы определить наиболее важное направление изменений.

Метод TIES-Merging разделяется на три основных этапа, каждый из которых выполняет определенную функцию:

1. Обрезка (Trim)

На этом этапе убирают избыточные параметры, идет фокусировка только на самых важных. Для этого происходит анализ изменений в моделях, сделанных во время их донастройки (fine-tuning), и выделяются те параметры, которые имеют наибольшее влияние на результат. Остальные параметры обнуляются или удаляются, что помогает сделать модель более компактной и эффективной.

2. Определение знака (Elect Sign)

На этом этапе разрешаются противоречия между разными моделями относительно того, какие изменения нужно внести в параметры. Когда разные модели предлагают противоположные корректировки, TIES-Merging создает единый вектор изменений, чтобы определить наиболее важное направление изменений. Это позволяет согласовать разные мнения и создать более согласованную модель.

3. Объединение (Disjoint Merge)

На последнем этапе данные из разных моделей собираются в одну. При этом учитывается созданный на предыдущем этапе единый вектор изменений, чтобы определить, какие значения параметров следует объединить. Это позволяет создать единую модель, которая учитывает мнения всех исходных моделей и обладает усредненными значениями параметров.

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

Рассмотрим пример настройки на YAML:


# Список моделей, которые будут использоваться
models:
  # Базовая модель без дополнительных параметров
  - model: mistralai/Mistral-7B-v0.1
    # Нет необходимости в параметрах для базовой модели

  # Модель с оптимизированными параметрами
  - model: OpenPipe/mistral-ft-optimized-1218
    parameters:
      density: 0.5  # Плотность
      weight: 0.5   # Вес

  # Модель NeuralHermes с дополнительными параметрами
  - 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% параметров каждой из моделей mistral-ft-optimized-1218 и NeuralHermes-2.5-Mistral-7B, а остальные 50% берем из базовой модели Mistral-7B.

Обратите внимание, что сумма весов в конфигурации не равна 1, но мы используем параметр normalize: true, который автоматически нормализует веса внутри модели, то есть приводит их к сумме, равной 1. Это удобно для сравнения весов и упрощает процесс обработки данных.

Вы можете найти конечную модель на Hugging Face Hub здесь.

DARE

DARE использует подход, аналогичный методу TIES, но с двумя основными отличиями:

1. Обрезка

При использовании DARE некоторые настроенные веса модели случайным образом сбрасываются к их исходным значениям, которые были у базовой модели. Это делается для того, чтобы уменьшить количество параметров модели и избежать избыточности.

2. Масштабирование

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

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

Реализация этого метода в Mergekit имеет два варианта: с этапом выбора знака из метода TIES (dare_ties) и без него (dare_linear).

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

# Список моделей, которые будут использоваться
models:
  # Базовая модель без дополнительных параметров
  - model: mistralai/Mistral-7B-v0.1
    # Нет необходимости в параметрах для базовой модели

  # Модель SamirGPT с определенными параметрами
  - model: samir-fama/SamirGPT-v1
    parameters:
      density: 0.53  # Плотность
      weight: 0.4    # Вес

  # Модель Slerp-CM-mist-dpo с определенными параметрами
  - model: abacusai/Slerp-CM-mist-dpo
    parameters:
      density: 0.53  # Плотность
      weight: 0.3    # Вес

  # Модель Mistral-7B-Merge-14-v0.2 с определенными параметрами
  - 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  # Маска типа int8
  dtype: bfloat16   # Тип данных bfloat16

В этой настройке мы объединяем три различные модели, основанные на Mistral-7B, с использованием метода dare_ties. На этот раз веса выбраны таким образом, чтобы их сумма составляла 1 (она должна быть между 0.9 и 1.1). Параметр плотности немного выше, чем рекомендуется (менее 0.5), но, похоже, это дает последовательно лучшие результаты.

Саму модель вы можете найти на Hugging Face Hub здесь.

И, кстати, это лучшая модель слияния в этой статье. Она превосходит даже Marcoro14-7B-slerp.

Метод сквозной передачи

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

Этот метод является очень экспериментальным, но он показал впечатляющие результаты. Например, модель goliath-120b была создана с использованием двух моделей Llama 2 по 70 миллиардов параметров. Еще один пример — модель SOLAR-10.7B-v1.0, которая также использует эту же идею, так называемую как "увеличение глубины" в этой статье.

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

slices:  # Срезы моделей
  - sources:  # Источники для первого среза
      - model: OpenPipe/mistral-ft-optimized-1218  # Модель 1
        layer_range: [0, 32]  # Диапазон слоев модели 1
  - sources:  # Источники для второго среза
      - model: mlabonne/NeuralHermes-2.5-Mistral-7B  # Модель 2
        layer_range: [24, 32]  # Диапазон слоев модели 2
merge_method: passthrough  # Метод слияния: пропуск
dtype: bfloat16  # Тип данных

В итоге получится "франкенслияние", в котором будут все 32 слоя из первой модели и еще 8 дополнительных слоев из второй модели. Таким образом, создается "франкенслияние" с общим количеством 40 слоев и 8.99 миллиарда параметров. Эта конфигурация вдохновлена GML-Mistral-merged-v1.

Вы можете найти окончательную модель на Hugging Face здесь.

Создание собственных объединенных моделей

Сейчас мы будем создавать свою модель, которая объединяет в себе две или более модели. Для этого нам нужно будет использовать mergekit для загрузки настроек слияния и их выполнения.

Для начала мы устанавливаем 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
yaml_config = """
# Настройка "кусков" модели
slices:
  # Первый "кусок" модели с диапазоном слоев от 0 до 32
  - sources:
      # Первый источник - модель AIDC-ai-business/Marcoroni-7B-v3
      - model: AIDC-ai-business/Marcoroni-7B-v3
        layer_range: [0, 32]
      # Второй источник - модель EmbeddedLLM/Mistral-7B-Merge-14-v0.1
      - 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 для фильтрации
  t:
    # Значения для фильтра "self_attn"
    - filter: self_attn
      value: [0, 0.5, 0.3, 0.7, 1]
    # Значения для фильтра "mlp"
    - 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)

Чтобы было максимально понятно, давайте пошагово разберем всё, что мы здесь делаем:

1. import yaml: Этот оператор импортирует библиотеку YAML, которая позволяет работать с данными в формате YAML.

2. MODEL_NAME = "Marcoro14-7B-slerp": Это переменная, которая содержит имя модели, которую мы собираемся создать путем объединения других моделей.

3. yaml_config = """ ... """: Здесь мы определяем конфигурацию для объединения моделей в формате YAML. Эта конфигурация содержит следующие ключевые атрибуты:

   - slices: Список словарей, описывающих каждый исходный источник моделей и диапазон слоев, которые мы собираемся использовать из каждой модели.

   - merge_method: Метод слияния моделей. В этом случае мы используем метод "slerp".

   - base_model: Базовая модель, которая будет использоваться в процессе слияния.

   - parameters: Параметры для метода слияния, такие как коэффициенты для фильтров self_attn и mlp.

   - dtype: Тип данных для параметров, в этом случае это "bfloat16".

4. with open('config.yaml', 'w', encoding="utf-8") as f: ...: Этот блок кода открывает файл "config.yaml" для записи и записывает в него содержимое переменной yaml_config, которое представляет собой нашу конфигурацию в формате YAML.

Этот код создает конфигурацию для объединения двух моделей с помощью метода слияния "slerp" и сохраняет эту конфигурацию в файл "config.yaml".


Мы запускаем команду объединения с использованием следующих параметров:

-- 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-unpickle

Теперь, когда модель объединена, она сохранена в специальной папке для слияния. Прежде чем мы загрузим её, важно создать файл README, который будет содержать всю необходимую информацию для повторного создания модели. В следующем блоке кода создается шаблон Jinja, который является своеобразным "каркасом" для файла README. Затем этот шаблон заполняется автоматически сведениями из конфигурации слияния.

# Установка библиотеки huggingface_hub
!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 }} - это слияние следующих моделей с использованием [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')

Теперь, когда у нас есть карточка модели, мы можем загрузить всю папку на Hub.

from google.colab import userdata
from huggingface_hub import HfApi

# Установка имени пользователя
username = "mlabonne"

# Создание объекта API с использованием токена, полученного из вкладки Secrets в Google Colab
api = HfApi(token=userdata.get("HF_TOKEN"))

# Создание репозитория модели
api.create_repo(
    repo_id=f"{username}/{MODEL_NAME}",
    repo_type="model"
)

# Загрузка содержимого папки "merge" в репозиторий
api.upload_folder(
    repo_id=f"{username}/{MODEL_NAME}",
    folder_path="merge",
)

Модель теперь доступна на Hugging Face Hub по адресу mlabonne/Marcoro14-7B-slerp. В другом блокноте мы можем попробовать модель на бесплатном GPU T4, используя следующий код:

!pip install -qU transformers accelerate

from transformers import AutoTokenizer
import transformers
import torch

# Определение модели
model = "mlabonne/Marcoro14-7B-slerp"

# Задание сообщений для диалога
messages = [{"role": "user", "content": "Что такое большая языковая модель?"}]

# Инициализация токенизатора
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"])

Вот и всё!

А теперь немного теории и результатов.

Что такое LLM?

Все мы знаем такой термин как большая языковая модель — LLM (Large Language Model). Если более конкретно,то это означает следующее:

Большая языковая модель — это своего рода искусственный интеллект, который обучен на огромных объемах текстовых данных. Ее основная задача — понимать и создавать текст, похожий на человеческий, предсказывая, какие слова или фразы могут появиться дальше в предложении или документе. Для этого модели используют сложные методы обработки информации и структуры нейронных сетей, чтобы учиться на основе имеющихся данных и совершенствовать свою способность к генерации текста. Со временем, конечно, как это доказали такие модели, как, например, 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.

Кстати, после создания модели, гайд на которое выше, мы протестировали её на двух разных наборах тестовых данных: Open LLM Leaderboard и NousResearch. В итоге, она прошла эти тесты с отличными результатами! Это означает, что она справляется с разными языковыми задачами и очень хорошо работает в реальных условиях.

Какой вывод можно сделать из этой статьи? Мы погрузились в процесс слияния больших языковых моделей (LLM) и рассмотрели четыре метода. Эти методы, такие как SLERP, TIES, DARE и метод сквозной передачи, позволяют создавать новые модели, объединяя лучшие характеристики нескольких исходных моделей. Применение таких подходов может быть весьма полезным для тех, кто стремится создавать мощные и эффективные LLM.

Мы не только разобрались в том, как работают эти методы, но и провели эксперименты с созданием модели Marcoro14-7B-slerp, которая показала отличные результаты на нескольких тестах. Это демонстрирует потенциал такого подхода к созданию LLM, позволяя разработчикам создавать более мощные и адаптивные модели для широкого спектра приложений, от чат-ботов до автоматического генерирования креативного контента.

Tags:
Hubs:
Total votes 4: ↑3 and ↓1+2
Comments0

Articles