Обретите цифрового двойника

Image generated by Stable Diffusion.
Image generated by Stable Diffusion.

Перевод статьи Сергея Саввова.

Цель этой статьи - показать, как эффективно и с минимальными затратами настроить LLM на пользовательском датасет. Мы рассмотрим использование модели Falcon-7B с адаптерами LoRa, с использованием библиотеки Lit-GPT.

Вы задумывались, каково это - иметь цифрового двойника? Виртуальную копию себя, которая может вести беседы, учиться и даже выражать мысли? Последние достижения в области искусственного интеллекта (ИИ) сделали эту некогда фантастическую идею достижимой.

Усилия сообщества искусственного интеллекта привели к разработке многих высококачественных open-source LLMs, включая LLaMA, Falcon, StableLM и Pythia. Вы можете настроить эти модели, чтобы адаптировать их к вашей конкретной задаче, например, обучить чат-бота отвечать на финансовые вопросы. В дополнение, это также может гарантировать защиту конфиденциальности данных, когда они не могут быть переданы или обработаны с помощью Cloud APIs.

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

Сбор и подготовка данных

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

  • Это представляет дополнительную проблему, поскольку модели в основном обучаются на английских текстах.

  • Учитывая, что русский - мой родной язык, у меня есть хорошая база, включающая личные переписки.

Сбор данных

Я выбрал Telegram, потому что он предоставляет удобный API для сбора данных. Кроме того, он служит основной платформой для моего общения с друзьями. Этот выбор позволяет модели глубже понять уникальный стиль общения и лучше имитировать меня.

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

  1. Сначала создаём клиент Телеграмма:

from telethon.sync import TelegramClient

client = TelegramClient(PHONE_NUMBER, TELEGRAM_APP_ID, TELEGRAM_APP_HASH)
client.start()
  1. Затем получаем список диалогов:

def get_dialogs(limit: int | None = 100) -> list[Dialog]:
    """Get all dialogs from the Telegram."""
    dialogs: list[Dialog] = client.get_dialogs(limit=limit)
    dialogs = [dialog for dialog in dialogs if dialog.is_user]  # remove groups or channels
    logger.info(f"Found {len(dialogs)} dialogs")
    return dialogs
  1. И под конец загружаем историю переписок:

def parse_messages(dialog: Dialog, limit: int = 1000) -> list[dict]:
    """Get all messages from the dialog."""
    all_messages_list = []
    offset_id = 0

    while True:
        messages: list[Message] = client(
            GetHistoryRequest(
                peer=dialog,
                offset_id=offset_id,
                offset_date=None,
                add_offset=0,
                limit=limit,
                max_id=0,
                min_id=0,
                hash=0,
            )
        ).messages
        if not messages:
            break

        all_messages_list.extend(
            {
                "date": message.date.isoformat(),
                "message": message.message,
                "out": message.out,
            }
            for message in messages
            # Filter audio or video content
            if message.message and not message.is_bot
        )
        offset_id = offset_id = messages[-1].id
    return all_messages_list

Вы можете найти полный скрипт здесь.

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

Подготовка данных

На этом этапе вы должны трансформировать данные в инструкции для дообучения модели.

Fine-tune обычно включает себя дообучение модели следовать инструкциям или выполнять другую целевую задачу (например, анализ тональности текста). ChatGPT (который начинался как fine-tuned версия базовой модели GPT-3) является типичным примером модели, которая была создана для следования инструкциям. Наборы инструкций обычно содержат три ключа: instructioninput (необязательный контекст для данной инструкции) и response от LLM. Ниже приведен пример данных инструкции:

[
    {
        "instruction": "Can cats communicate?",
        "context": "Cats need to communicate with each other for bonding, and relating with each other; they need to collaborate, play, and share resources...",
        "response": "Cat vocalizations have been categorized according to a range of characteristics...",
    }
]

Схематично процесс точной настройки можно представить следующим образом:

Fine-tuning a pretrained LLM to follow instructions.
Fine-tuning a pretrained LLM to follow instructions.

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

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

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

  1. Разбиение беседы на батчи, когда перерыв между двумя сообщениями превышает один день. Тогда мы рассматриваем это как начало нового диалога, следовательно, контекста из предыдущего разговора не будет.

  2. Объединение нескольких сообщений, идущих подряд от одного пользователя, в одно.

  3. Установка максимальной длины контекста для ускорения процесса обучения.

  4. Добавление меток к своим ответам и ответам собеседника, чтобы помочь модели лучше понимать контекст.

Preprocessing chat messages.
Preprocessing chat messages.

Я также очистил историю чатов от конфиденциальной информации, такой как личные пароли или электронные письма.

По итогу у меня получилось 51к инструкций, что вполне сопоставимо с Dolly 2.0 instruction dataset от Databricks (~15к инструкций) и Alpaca dataset (~52К инструкций).

Модель

Я решил выбрать open-source large LLM Falcon от Technology Innovation Institute. Эта модель авторегрессионного декодера с двумя вариантами: моделью с 7 миллиардами параметров и моделью с 40 миллиардами параметров. Вариант модели 40B обучался на 384 графических процессорах AWS в течение 2 месяцев.

Open LLM Leaderboard.

Исходя из того, что известно об этой модели, архитектура Falcon очень похожа на GPT-3 и LLaMA, за исключением использования multi-query attention (Shazeer 2019) и  RefinedWeb corpus в качестве обучающего набора данных (что может быть ключом к успеху).

Точная настройка LLM с помощью LoRa

Если мы рассматриваем способы дообучения LLM (Large Language Model), одним из ценных ресурсов является статья OpenAI PALMS: Pre-training an Autoencoder Latent Model for Sequence Generation. В статье обсуждается использование fine-tuning, которая включает в себя обучение модели с использованием тех же методов, что и при первоначальном обучении, но с более низкой скоростью обучения ~ 0,1. Этот процесс позволяет обучать модель на конкретных данных, тем самым улучшая качество ее ответов в желаемой области.

Помимо fine-tune существуют и другие подходы, такие как использование адаптеров. Они предполагают добавление дополнительных слоев меньшего размера к существующим слоям исходной модели, обучая только добавленные слои. Такой подход позволяет ускорить обучение, поскольку задействованные веса относительно невелики.

Architecture of adapter-based knowledge injection into LLMs.

Концепция LoRa черпает вдохновение из наблюдений за тем, как меняются веса матриц во время обучения, как в работе Aghajanyan et al. (2020). Эти наблюдения показывают, что матрицы могут быть приближены с использованием пространства меньшей размерности при сохранении большей части их существенной информации и структуры.

Каждая матрица W представлена в виде суммы W + A * B во время обучения. Исходная матрица W заморожена, и обучаются только матрицы A и B. Следовательно, обновленные веса получаются как ΔW = W + A * B. Благодаря тому, что матрицы A и B остаются маленькими, процесс обучения становится быстрее и требует меньше ресурсов. В двух словах, это метод LoRa, который проиллюстрирован на рисунке ниже.

Forward pass with updated model.
Forward pass with updated model.

Обратите внимание, что r на рисунке выше - это гиперпараметр , который мы можем использовать для указания матриц низкого ранга, используемых для адаптации. Меньший r приводит к более простой матрице низкого ранга, что приводит к меньшему количеству параметров для изучения во время адаптации. Выбор меньшего r в LoRa предполагает компромисс между сложностью модели, адаптационными возможностями и риском недостаточной или чрезмерной подгонки.

Для получения дополнительной информации я рекомендую следующие ресурсы:

Эксперимент

Для проведения своих экспериментов я использовал  Lit-GPT library, которая включает реализацию open-source LLM и работает на базе Lightning Fabric. Касаемо железа для обучения, я использовал один графический процессор A100 с объемом памяти 40 ГБ.

Загрузка весов модели

Для начала экспериментов первым шагом является загрузка весов модели и преобразование их в lit-gpt формат. Это довольно легко сделать:

# download the model weights:
python scripts/download.py --repo_id tiiuae/falcon-7b

# convert the weights into a standardized form:
python scripts/convert_hf_checkpoint.py --checkpoint_dir checkpoints/tiiuae/falcon-7b

Инструкции по загрузке других supported weights, таких как RedPajama, вы можете найти в разделе «howto section».

Подготовьте датасет

Fine-tuning включает в себя два основных шага - сначала мы обрабатываем датасет в Lit-Parrot, а затем запускаем fine-tuning скрипт для обработанного датасет.

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

def generate_prompt(example: dict[str, str]) -> str:
    """Generates a standardized message to prompt the model"""
    return (
        "You (I) are chatting with a user R. Write a reply to his message.\n\n"
        f"### Your previous communication:\n{example['context']}\n\n"
        f"### His new message:\n{example['instruction']}\n\n"
        f"### Your response:{example['response']}"
    )

После внесения изменений можно начать процесс подготовки данных:

python scripts/prepare_dataset_my.py \
  --checkpoint_dir checkpoints/tiiuae/falcon-7b/

Подготовка промптов не займет много времени. В моем случае на 51 тысячу инструкций ушло всего 2 минуты:

loading
loading

Точная настройка модели Falcon

После того, как вы подготовили свой датасет, довольно просто настроить модель.

Я изменил некоторые параметры в fine-tuning script для улучшения результатов, вот обзор настроек гиперпараметров, которые я использовал:

bfloat16 precision (подробнее о bfloat16 я писал в статье 7 ways to speed up inference of your hosted LLMs) Кроме того, скрипты были настроены для обучения моделей на 51 тыс. итераций с использованием эффективного batch size of 128 с накоплением градиента (подробнее о накоплении градиента в статье Finetuning LLMs on a Single GPU Using Gradient Accumulation).

  • Для LoRa я использовал rank of 16, чтобы получить более качественный обученный адаптер. И установите alpha to 32 (alpha это масштабный коэффициент, который регулирует величину совокупного результата, это уравновешивает знания предварительной модели и адаптацию к новой задаче).

Затем вам нужно запустить finetune/lora.py скрипт, указав свой путь к данным.

python finetune/lora_my.py \
  --checkpoint_dir checkpoints/tiiuae/falcon-7b/ \
  --data_dir data/falcon/ \
  --out_dir out/falcon \
  --precision bf16-true
proccess
proccess

Мониторинг процесса обучения

Вы можете использовать nvidia-smi команду Linux для мониторинга использования GPU каждые полсекунды:

watch -n 0.5 nvidia-smi
GPU load.
GPU load.

После окончания обучения вы можете найти чекпоинты модели в папке out/falcon и использовать скрипт генерации, чтобы поиграть с моделью.

Для настройки модели на одном графическом процессоре A100 у меня потребовалось примерно 10 часов и 30 ГБ памяти. Стоит отметить, что сам адаптер легкий, весит всего 40 МБ. Это значительно меньше по сравнению с моделью Falcon, размер которой составляет 16 ГБ.

Инференс обученной модели

Для использования обученной модели, Lit-GPT предоставляет готовые скрипты. Вы можете квантовать исходную модель (int8, int4), а также изменять precision и использовать одновременно несколько GPU:

python generate/lora.py \
  --checkpoint_dir checkpoints/tiiuae/falcon-7b \
  --lora_path out/falcon/lit_model_lora_finetuned.pth \
  --prompt "What happened to you? Tell me" \
  --max_new_tokens 300
  --precision bf16-true

Я запустил модель на 1 графическом процессоре без квантования и с bfloat16 точностью. А также изменил оригинальный скрипт lora и разделил его на две части:

  1. Веб-интерфейс с использованием streamlitи streamlit-chat для более быстрого тестирования модели. Моя версия здесь.

  2. RestAPI с использованием веб-фреймворка FastAPI для вывода модели. Это позволило загрузить модель в память один раз, а затем использовать ее снова.

Демо:

Demo of the fine-tuned model.
Demo of the fine-tuned model.

Важно отметить, что это один из лучших примеров, которые я получил. Остальные были заметно хуже.

Время отклика модели, даже без квантования, было удивительно быстрым - 45,51 токена в секунду. Если хотите ускорить генерацию текста или минимизировать использование памяти, рекомендую ознакомиться с моей предыдущей статьей “7 Ways To Speed Up Inference of Your Hosted LLMs”.

Сравнение качества

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

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

Мне кажется, что это можно исправить несколькими способами:

  • Улучшите процессы очистки данных, чтобы обеспечить более высокое качество данных.

  • Включите дополнительные датасеты с аннотированными диалогами.

  • Повысьте LoRA rank с 16 до 32.

  • Используйте модель большего размера, такую как Falcon-40B.

  • Сократите или упростите длину контекста.

  • Упростите запросы, чтобы предоставить более четкие инструкции.

Ограничения

Хотя Lit-GPT предлагает много функциональных возможностей, я советую использовать его в первую очередь для проверки гипотез. На мой взгляд, он еще не полностью подготовлен для производственного использования. Например, на момент написания этой статьи в Lit-GPT отсутствовала встроенная реализация для преобразования модели обратно в формат HuggingFace. Тем не менее, это все еще возможно, и авторы библиотеки предлагают пару решений:

  1. Выполнение обратного преобразования для каждого из классов HuggingFace.

  2. Создание HF Transformer model version of lit_gpt.model.

Обратите внимание, что первый метод не поддерживает модификации LoRa и адаптера.

Учитывайте эти ограничения при разработке своего решения. Если вы точно настраиваете LLM для производства, я рекомендую делать это с помощью чистого PyTorch. В этой статье Amazon есть дополнительная информация.

Заключение

Возможность точной настройки LLM с использованием всего одного графического процессора и нескольких часов действительно впечатляет. Можно создавать множество небольших модулей LoRa для различных задач. А дальше подключать адаптеры к одной большой задеплоенной LLM. Учитывая небольшой размер адаптеров и скорость их обучения, это преимущество нельзя игнорировать.

Однако важно подходить к процессу с реалистичными ожиданиями. Вполне вероятно, что для достижения оптимальных результатов потребуется поэкспериментировать с различными гиперпараметрами. Кроме того, аннотирование и очистка датасет являются важными шагами для обеспечения лучших результатов. Также обратите внимание на каких данных обучалась LLM, и проверьте бенчмарки для задач, похожих на ваши.

Я уверен, что, следуя этим шагам, можно достичь отличных результатов!

Если у вас есть какие-либо вопросы или предложения, не стесняйтесь, обращаться ко мне:

LinkedIn

Комментарии (3)


  1. dyadyaSerezha
    26.08.2023 10:06
    +1

    "Цифровой двойник - достижим".

    Ну и где конкретные результаты? Автор узнала себя? А знакомые автора узнали автора? С этого вообще надо было начинать статью. Но я увидел лишь нечто скомканое и расплывчатое в самом конце.


    1. zartdinov
      26.08.2023 10:06
      +3

      Цифровой двойник избавился от автора и специально запостил без результатов.


      1. dyadyaSerezha
        26.08.2023 10:06
        +1

        Ну, уже что-то.