Привет, Хабр! Меня зовут Анна Щеникова, я аналитик в Центре RnD в МТС Диджитал. Почти всегда при адаптации LLM-моделей встает вопрос нехватки ресурсов на проверку гипотез. Обычно у меня есть собственное рабочее время и две карточки GPU, а распределяются они на несколько задач. Бизнес же просит приемлемый результат как можно быстрее.
В прошлом посте я рассказала про разделение адаптации open-source-моделей на четыре уровня, а в этом раскрою работу с последним из них — дообучением. Под катом покажу, как быстро получить приемлемое качество, когда базовые подходы не помогают.
Исходная задача
Допустим, нужно обучить модель работать с технологией RAG или с помощью LLM генерировать инсайты по нашим данным. Как это сделать быстро, дешево и сердито?
Нужен подход, который легко применять локально. Чтобы его отработать и использовать на реальных кейсах, мы решили обучить модели русскому языку. Так мы не замкнемся на одной задаче и протестируем большое количество гипотез.
Да, логичнее было бы использовать подходы адаптации к новому языку. Например, обучить новый токенайзер и адаптировать эмбеддинги. Но с точки зрения бизнеса нам нужен успешный опыт, чтобы получить быстрое решение задач без проверки множества гипотез.
Для хорошего обучения требуется собрать данные, понять и оформить тренировочный пайплайн и, самое важное, определиться с оценкой качества. С этого и начнем.
Оценка качества
Есть разные подходы для оценки ответов моделей. Мы выделили для себя три группы:
Бенчмарки, отвечающие на вопрос «Есть ли ответ в сгенерированном тексте?» Пример: MMLU, Russian SuperGLEU (RSG), MERA. Они хорошо проверяют факты, но плохо оценивают сам язык. Например, не подсвечивают, что модель ошибается в правилах грамматики и пунктуации, путает слова, генерирует один и тот же текст без конца и много чего еще. Забавный факт: мы оценили модель с большими проблемами в грамматике, но она неожиданно была лучшей по RSG.
Бенчмарки типа LLM-судьи. Одна LLM сравнивает ответы других моделей по десятибалльной шкале. Так более или менее учитывается качество языка, и можно получить неплохую приближенную оценку ответов.
Ручная Side-by-side (SBS) оценка. Самый точный, но дорогой и долгий вариант. Берем заранее определенный тестовый набор данных, модели инференсят на нем и получают некоторые ответы. Люди вручную сравнивают их и выставляют перекрестные оценки: лучший/худший, одинаково хороший/плохой ответ.
При любом из этих подходов результаты своей модели можно подогнать так, что они будут лучше, чем на самом деле. Например, обучить модель на открытых тестовых данных. Также метрики станут нерепрезентативными при использовании данных для few-shot-инференса в качестве обучающих.
Самым надежным остается вариант, когда проводится самостоятельная SBS-оценка. От обычной SBS-оценки она отличается только отсутствием тестового набора данных для сравнения. Пользователи просто задают и сравнивают ответы. Так работает ChatBot Arena. С появлением детерминированного набора проверяемых данных возникает соблазн пойти нечестным путем и подогнать метрики.
Чтобы сравнивать ответы моделей на разных итерациях обучения, я использую оценку GPT-судьей. Давайте посмотрим на подход поближе: в качестве судьи выступает GPT-4o, которая показывает самые высокие метрики, в том числе и на арене, а еще это одна из самых дешевых моделей из семейства GPT.
Промпт для LLM-судьи выглядит так:
Please act as an impartial judge and evaluate the quality of the responses provided by two AI assistants to the user question displayed below. You should choose the assistant that follows the user's instructions and answers the user's question better. Your evaluation should consider factors such as the helpfulness, relevance, accuracy, depth, creativity, and level of detail of their responses. Begin your evaluation by comparing the two responses and provide a short explanation. Avoid any position biases and ensure that the order in which the responses were presented does not influence your decision. Do not allow the length of the responses to influence your evaluation. Do not favor certain names of the assistants. Be as objective as possible. After providing your explanation, output two values on a scale of 1 to 10 indicating the scores for Assistant A and B, respectively. Output your final verdict by strictly following this format: [[{{Assistant A score}} {{Assistant B score}}]].
[User Question]
{instruction}
[The Start of Assistant A's Answer]
{answer_a}
[The End of Assistant A's Answer]
[The Start of Assistant B's Answer]
{answer_b}
[The End of Assistant B's Answer]
Судье передается вопрос и два ответа: от эталонной модели A (gpt-4-turbo) и оцениваемой модели B (например, llama3-8b)
Важно учитывать несколько моментов:
Анонимность ответов. Судья не знает и не учитывает, от какой модели приходит ответ.
Судья и эталонная модель должны различаться.
Ответы нужно рандомно менять местами, чтобы избежать смещения в пользу какой-то одной модели. Существует гипотеза, что первый ответ выбирается чаще.
В промпте можно менять требования к ответам.
Можно смотреть долю соответствия с эталонной моделью и средние оценки. На основе промпта для LLM судьи ставят оценки по десятибалльной шкале и подробно объясняют, почему. Это гораздо интереснее, чем смотреть по четырем категориям: «ответ А лучше Б, ответ Б лучше А, оба хороши, оба плохи».
Такой способ не требует ручных действий и не отнимает мое время. Он оценивает не только качество языка, но и соответствие ответа требованиям. В результате оценки я получаю такую диаграмму:
Теперь мы можем выбирать оценочный датасет и отрасли под конкретные задачи и мониторить состояние модели сразу по всем подзадачам.
Данные
Краеугольным камнем в общении с бизнесом становятся данные для обучения. Их нужно получить с учетом специфики задачи, и для этого есть два пути: оттолкнуться от чего-то готового или сделать все самому.
Где найти данные
При первом пути для прототипа (Proof-of-Concept) и теста гипотезы в большинстве случаев достаточно двух вариантов получения данных:
-
Поиск готовых датасетов. Их можно взять на huggingface и перевести, если они на другом языке. На этом ресурсе много различных датасетов по самым разным задачам. Их можно использовать для обобщения какого-то навыка модели.
Например, мы хотим качественно суммаризировать данные внутри компании по структуре, но нам необязательно собирать внутреннюю информацию, чтобы получить навык. Достаточно использовать датасеты для суммаризации и обработать их так, как нам надо.
-
Генерация синтетики по существующим данным: базам текстов, примеров запросов от пользователей и так далее. При этом подходе мы сами «генерируем» датасет, если у нас есть какой-то материал, но его мало или ему нужно преобразование. Главное для нас — возможность определять требования для генерации данных и уточнять нужные моменты.
Например, у вас есть десяток диалогов с пользователями и нужно получить их варианты с небольшими отличиями — их можно сгенерировать на основе имеющихся. Или есть база документов, и чат-бот должен отвечать по ней и иметь общие знания по теме. Мы можем с помощью ChatGPT или какой-то локальной модели (например, LLama 3.1 70b) сгенерировать пары «вопрос-ответ» по тексту так, чтобы использовать его как релевантный контекст в RAG-системе.
Второй путь — сделать все самому — подразумевает ручные сборку и оформление данных, написание текстов. Как-то раз редакторы заказчика сделали около восьмидесяти подписей к картинкам, чтобы обучить нейросеть. Даже этого набора хватило для дообучения модели и получения работающего результата.
В текущей задаче мы сфокусировались на поиске максимально готовых решений, которые даже не нужно переводить.
Как должны выглядеть данные для обучения
Если мы говорим про LLM, то есть два типа моделей:
Базовая — это продвинутый T9, предсказывающий только следующий токен. На вход подается какой-то текст, и она его просто продолжает. Пример: meta-llama/Meta-Llama-3.1-8B.
Instruct-версия — это базовая модель, которая была дообучена общаться в чате или в instruct-формате. Она поддерживает роли пользователя и ассистента, и мы можем ей задавать вопросы и получать на них ответ. Наши данные должны выглядеть как-то так:
Базовой модели на вход для обучения нужен просто текст. Для instruct-версии определяем роли, а данные должны представлять собой список сообщений для каждой из них.
Но не все так просто с instruct-версией. На самом деле для модели методы Prompt (base) и Instruct кардинально ничем не отличаются. Instruct-версия — это модель, обученная определенной структуре текста. Мы специфично оформляем промпт, а Instruct так же, как и базовая, предсказывает только следующий токен, но в определенной структуре. И эту структуру мы можем распарсить и выделить ответ от ассистента. Она называется темплейт и различается у каждой модели:
Да, это такой же текст, просто оформленный с разными дополнительными специальными токенами. Он красиво парсится в UI и преобразуется в привычный нам вид:
В процессе дообучения модели важно оформлять данные в предназначенном для нее виде. Базовой достаточно отдать просто текст, а для instruct все данные оформляются в ее чат-темплейте, а уже потом используются в обучении.
С чего мы начали
Мы нашли на Hugging Face датасеты Saiga, нагенерировали запросов в ChatGPT, взяли переведенный на русский язык d0rj/OpenOrca-ru, все в instruct-версии.
Так у нас появилась база, на которой мы обучили модель и оценили ее с помощью LLM-судьи:
Дальше мы добавили русскоязычных данных на специфические задачи:
spellcheck — проверяет и исправляет грамматику русского языка;
wikiomnia — отвечает на вопросы по тексту из «Википедии» (QCA);
gsm8k — базовые задачи по математике.
После обучения и замеров мы получили такие результаты:
Видно, что добавление данных по отдельности повышает соответствие с GPT-4, но есть две проблемы:
Хороших русскоязычных данных действительно мало, зато много переводов, которые могут быть плохого качества. Мы замечали, что плохо переводится код — где-то даже был переведен на русский синтаксис Python. Нужна либо проверка на адекватность, либо умная фильтрация. А на них, увы, времени нет.
Чем больше данных, тем хуже качество. Например:
Задача: Перепиши текст, исправь грамматические, орфографические и пунктуационные ошибки.
«Если бы сказали мне год назад сегодня я бежать марафон, я бы смеяться. Ваша поддержка иметь мне огромное влияние!»
base и base + gsm8k:
Если бы сказали мне год назад, что сегодня я бегу марафон, я бы смеялся. Ваша поддержка имеет огромное значение для меня!base + spellcheck + wikiomnia + gsm8k:
Если бы сказали мне год назад сегодня я бежать марафон, я бы смеяться. Ваша поддержка имеет мне огромное влияние!
Мы добавляем больше данных, чтобы улучшить качество языка, но оно только падает.
Тут мы подумали, что проблема может быть в разнообразии данных, и собрали еще больше двадцати датасетов по специфическим задачам на русском и английском языках. Отревьюили их на адекватность и стали искать минимальную комбинацию, которая даст лучшее качество. Для нас идеальным датасетом оказался микс из базы, задач на код, логику, математику, грамматику, QCA. Все они были русскоязычными.
А что, если пытаться подтянуть одну отрасль?
Мы увидели, что на графике просаживается roleplay, и решили в наши данные добавить датасет на эту задачу. Мы надеялись улучшить метрики по этой области. Но нет, метрики упали, а по этой отрасли изменения практически не было.
Разнообразие данных помогло нам чуть улучшить метрики, но не сильно. Главным препятствием оставалось падение качества при увеличении объема данных. Мы поняли, что нам пора изменить подход.
Тренировочный пайплайн
Чтобы разобраться со снижением качества, мы решили глубже взглянуть на наш подход. Начали свое исследование мы, конечно, с Low-Rank Adaptation (LoRA):
Мы делаем копию весов некоторых слоев обученной модели, например Mistral. Чтобы уменьшить количество обучаемых параметров, копия разбивается на две матрицы A и В. Они перемножаются для получения оригинальной размерности, а исходные веса замораживаются. Обучаемая копия и оригинальные веса конкатенируются на выходе, а в процессе инференса мержатся по этой конкатенации.
Понятный пример: у нас есть дом, и мы хотим выделить место для хранения инструментов. Чтобы не лезть в конструкцию и не переделывать все внутри, мы пристраиваем к нему сарай (LoRa adapter) и получаем нужную функциональность, ничего не сломав.
Изначально мы использовали не только LoRA на float16 (обучение полноразмерной модели), но и обучение на основе квантизованных моделей, например LoRA 8 bit. Такой подход требует меньше памяти и сразу адаптируется к условиям инференса в квантизации. Но он увеличивает время обучения и ухудшает грамматику.
Чтобы улучшить качество ответов, мы решили потюнить гиперпараметры, но толку было мало:
К тому же мы заметили, что при обучении ровно одной эпохи лучшее качество получается в ее середине:
Наша валидационная лосс-функция имела форму колокола. Качество было выше, когда использовалось ровно определенное количество данных: чем больше мы учили модель, тем хуже она показывала результаты.
Меняем подход
Мы много тестировали и остановились на LoRA с префикс-тюнингом:
Префикс-тюнинг заключается в добавлении LoRa-весов к соответствующим матрицам W в механизме внимания и виртуальных эмбеддингов к матрицам К и V. Так мы чуть расширяем пространство эмбеддингов внутри внимания. Подробности есть в статье «Parameter-Efficient Fine-Tuning (PEFT): методы LoRA, Prefix tuning, Prompt tuning и Adapters».
Мы сразу заметили улучшение метрики:
Увы, памяти стало требоваться больше, но проблему «больше данных — хуже качество» мы пофиксили. Ура!
При тюнинге подхода к обучению мы еще обращали внимание на:
Гиперпараметры LoRA. Даже с интеграцией префикс-тюнинга мы искали хорошие гиперпараметры rank и rank_alpha для LoRA.
Learning rate. А куда без него? С его помощью мы хорошо контролировали момент переобучения.
Время обучения. Чем быстрее гипотезы проходят проверку, тем лучше.
Улучшаем подход к дообучению
Direct Preference Optimization
В процессе работы мы с коллегами увидели статью про DirectPreferenceOptimization (DPO) и решили попробовать внедрить его к себе.
Если мы используем какую-то готовую модель, то знаем, что она прошла множество разных этапов обучения. Instruct-версии обычно проходят несколько после претрейна:
Мы заметили, что наш подход LoRA + Prefix tuning можно обозначить как своего рода SFT. Значит, для повышения качества можно использовать обучение с подкреплением. Таких подходов много, и один из самых последних — это ORPO, но нам хватило DPO:
Допустим, у нас есть запрос от пользователя write me a poem about the history of jazz и ответы $y_1$ и $y_2$, где $y_1$ лучше $y_2$. Мы выбираем победителем первый ответ.
Такие данные мы можем использовать для обучения с подкреплением на основании человеческого фидбэка (RLHF). Здесь мы одновременно тренируем модель вознаграждения, которая будет оценивать генерируемые ответы и саму LLM.
Или можно применить DPO, где напрямую, без модели вознаграждения, будем учить LLM в соответствии с данными, размеченными на хорошие и плохие. Подробнее об этом методе можно почитать в этой статье.
После интеграции DPO в наш пайплайн обучения мы получили такие результаты:
У этого подхода есть свои особенности:
DPO просто реализовать. Он дает более быструю сходимость, больший контроль над LLM и эффективность вычислений, чем RLHF. При этом ему нужно меньше ресурсов, чем RLHF.
Для DPO нужно много памяти (160 Гб GPU на 2к контекста для 7B-модели) и больше времени (добавляется от 12 до 24 часов на обучение).
Интересно, что DPO задает ограничения, а RLFH показывает возможности. На примере родителей и детей: DPO скажет «а ну не суй пальцы в розетку», а RLFH — «время порисовать». C помощью DPO мы можем лучше контролировать поведение модели и ограничивать ее, а RLHF создает более творческий, но менее управляемый процесс. Не факт, что обучение им приведет к качественному результату.
Для DPO подойдут как русско-, так и англоязычные датасеты. Мы брали с Huggingface.
Комбинации способов адаптации
После обучения решили потюнить системный промпт у модели и получили интересные результаты:
Несмотря на долю среднего соответствия в 1.21, модель оказалась где-то лучше, а где-то хуже GPT-4. Например, в подзадачах открытых и закрытых вопросов, чате и переводе оценки у нее в среднем выше, чем у GPT-4. Однако у них сильный разбег доверительных интервалов. Есть большое количество оценок соответствия меньше 1.0, то есть наша модель часто отвечает хуже GPT-4. Около 18 пунктов среднего соответствия модели с GPT-4 нам дало обучение, а еще 20 — модификация системного промпта. Это значительный прирост показателей.
Другой график также показывает, что модель работает на уровне GPT-4:
Ответы распределились примерно поровну: половина лучше GPT-4 (зеленый цвет), а половина хуже (оранжевый).
Выводы
На этом у меня все. Если вы столкнетесь с похожей задачей, то выбирайте подход к оценке качества во время тюнинга крайне аккуратно. От него зависит, как хорошо вы увидите недостатки модели и сформулируете гипотезы для ее улучшения.
В нашем решении мы использовали качественного LLM-судью в виде GPT-4o. При работе с данными оказался эффективным микс open-source русскоязычных и англоязычных данных, которые мы проверяли на адекватность перевода. Хорошо показал себя тренировочный пайплайн из LoRa c префикс-тюнингом и DPO.
Повысить качество можно с помощью комбинации разных подходов адаптации, тюнинга промптов, внедрения агентов. Об этом я рассказывала в своей прошлой статье. Если у вас возникли вопросы, пишите в комментариях.
Полезные ссылки
Github-репозиторий, где описана вся методология работы LLM-судьи.
Github-репозиторий Ильи Гусева (@Takagi), который много сделал для open-source русскоязычных моделей.
Статья про LoRa и Prefix tuning.
OpenRLHF — github-репозиторий с методами RLHF и DPO.
DPO paper — описание метода Direct Preference Optimization.
Prefix tuning paper — описание метода префикс-тюнинга.
Lora paper — описание подхода Low-Rank Adaptation.
redfox0
Нет такого предмета как стоп-кран в самолете.
<reflection> Остановился на этой мысли, поскольку действительно, в самолетах нет таких конструкций, которые называются стоп-краниками.