Шаблоны чата конец для тихих убийц эффективности

Чат-шаблоны высшая степень эффективности или источник скуки?

Призрак бродит по моделям чата – призрак неправильного форматирования!

в заключение

Модели чата были обучены с использованием очень разных форматов для преобразования разговоров в одну строки, которую можно токенизировать. Использование формата, отличного от формата, с которым модель была обучена, обычно приводит к серьезному снижению производительности без заметных признаков, поэтому совпадение формата, используемого при обучении, чрезвычайно важно! Токенизаторы Hugging Face теперь имеют атрибут chat_template, который можно использовать для сохранения формата чата, с которым модель была обучена. Этот атрибут содержит шаблон Jinja, который преобразует историю переписки в правильно отформатированную строку. Пожалуйста, ознакомьтесь с технической документацией, чтобы узнать, как писать и применять шаблоны чата в вашем коде.

Введение

Если вы знакомы с библиотекой 🤗 Transformers, то, вероятно, писали код подобный этому:

tokenizer = AutoTokenizer.from_pretrained(checkpoint)model = AutoModel.from_pretrained(checkpoint)

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

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

Однако с моделями чата все немного сложнее. Это связано с тем, что “чат” – это не просто одна строка текста, которую можно просто токенизировать – это последовательность сообщений, каждое из которых содержит роль и содержание, что фактически является текстом сообщения. Обычно для ролей используются “user” для сообщений, отправленных пользователем, “assistant” для ответов, написанных моделью, и, по желанию, “system” для директив на высоком уровне, которые даны в начале разговора.

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

[    {"role": "user", "content": "Привет!"},    {"role": "assistant", "content": "Приятно познакомиться!"}]

Эту последовательность сообщений нужно преобразовать в текстовую строку, прежде чем ее можно будет токенизировать и использовать в качестве входных данных для модели. Проблема в том, что есть много способов сделать эту конвертацию! Например, вы можете преобразовать список сообщений в формат “мгновенного мессенджера”:

Пользователь: Привет!Бот: Приятно познакомиться!

Или вы можете добавить специальные токены, чтобы указать роли:

[ПОЛЬЗОВАТЕЛЬ] Привет! [/ПОЛЬЗОВАТЕЛЬ][АССИСТЕНТ] Приятно познакомиться! [/АССИСТЕНТ]

Или вы можете добавить токены, чтобы указать границы между сообщениями, но вставить информацию о роли в виде строки:

<|im_start|>userПривет!<|im_end|><|im_start|>assistantПриятно познакомиться!<|im_end|>

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

Шаблоны: способ сохранить информацию о формате

Прямо сейчас, если вы везучий, нужный вам формат правильно описан где-то в карточке модели. Если у вас нет такой удачи, то удачи вам, если вы хотите использовать эту модель. В крайних случаях мы даже помещаем весь формат промпта в статью блога, чтобы пользователи не пропустили его! Однако в лучшем случае вам придется найти информацию о шаблоне и вручную внедрить его в вашу пайплайн по тонкой настройке или выводу результата. Мы считаем, что это особенно опасная проблема, потому что использование неправильного формата чата является тихой ошибкой – вы не получите громкой ошибки или исключения Python, которые скажут вам, что что-то не так, Модель просто будет работать намного хуже, чем с правильным форматом, и будет очень трудно отладить причину!

Это проблема, которую шаблоны чата направлены на решение. Шаблоны чата – это строки шаблонов Jinja, которые сохраняются и загружаются с помощью вашего токенизатора и содержат всю необходимую информацию для превращения списка чат-сообщений в правильно отформатированный ввод для вашей модели. Вот три строки шаблонов чата, соответствующих трём форматам сообщений выше:

{% for message in messages %}    {% if message['role'] == 'user' %}        {{ "Пользователь : " }}    {% else %}        {{ "Бот : " }}    {{ message['content'] + '\n' }}{% endfor %}

{% for message in messages %}    {% if message['role'] == 'user' %}        {{ "[ПОЛЬЗОВАТЕЛЬ] " + message['content'] + " [/ПОЛЬЗОВАТЕЛЬ]" }}    {% else %}        {{ "[ASST] " + message['content'] + " [/ASST]" }}    {{ message['content'] + '\n' }}{% endfor %}

"{% for message in messages %}"      "{{'<|im_start|>' + message['role'] + '\n' + message['content'] + '<|im_end|>' + '\n'}}"  "{% endfor %}"

Если вы не знакомы с Jinja, я настоятельно рекомендую вам посмотреть на эти строки шаблонов и соответствующие им выходные данные шаблона и убедиться, что вы понимаете, как шаблон превращает список сообщений в отформатированную строку! Синтаксис очень похож на синтаксис Python.

Зачем нужны шаблоны?

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

Зачем делать это? Почему бы не выбрать стандартный формат?

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

Однако мы все же можем немного смягчить эту проблему. Мы считаем, что ближайшим к ‘стандартному’ формату форматирования является формат ChatML, созданный OpenAI. Если вы обучаете новую модель для чата и этот формат подходит для вас, мы рекомендуем его использовать и добавить специальные токены <|im_start|> и <|im_end|> в ваш токенизатор. Он имеет преимущество в том, что он очень гибок в отношении ролей, так как роль просто вставляется в виде строки, а не имеет отдельных токенов для роли. Если вы хотите использовать его, это третий из приведенных выше шаблонов, и вы можете установить его с помощью этой простой однострочной команды:

tokenizer.chat_template = "{% for message in messages %}{{'<|im_start|>' + message['role'] + '\n' + message['content'] + '<|im_end|>' + '\n'}}{% endfor %}"

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

Как работают шаблоны?

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

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

Шаблоны чата по умолчанию также устанавливаются на уровне класса и сообщают классам, таким как ConversationPipeline, как форматировать входные данные, когда у модели нет шаблона чата. Мы делаем это исключительно для обратной совместимости – настоятельно рекомендуем вам явно установить шаблон чата для любой модели чата, даже если шаблон чата по умолчанию подходит. Это обеспечит то, что любые будущие изменения или прекращения использования шаблона чата по умолчанию не повредят вашу модель. Хотя мы будем сохранять шаблоны чата по умолчанию в ближайшем будущем, мы надеемся постепенно перейти на явные шаблоны чата для всех моделей, после чего шаблоны чата по умолчанию могут быть полностью удалены.

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

С чего начать работу с шаблонами?

Просто! Если токенизатор имеет атрибут chat_template, он готов к использованию. Вы можете использовать эту модель и токенизатор в ConversationPipeline, или вы можете вызвать tokenizer.apply_chat_template(), чтобы форматировать чаты для вывода или обучения. Дополнительную информацию см. в нашем руководстве разработчика или в документации по применению шаблонов чата!

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

Вы можете добавить chat_template даже для точек проверки, которыми вы не являетесь владельцем, открыв запрос на слияние. Единственное изменение, которое вам нужно сделать, – установить атрибут tokenizer.chat_template на строку шаблона Jinja. После этого отправьте ваши изменения и вы готовы!

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

Заключение: Философия шаблонов

Мы считаем, что шаблоны – это очень интересное изменение. Помимо устранения огромного источника скрытых ошибок, убивающих производительность, мы считаем, что они открывают новые подходы и модальности данных. Возможно, самое главное, они также представляют философский сдвиг: они удаляют большую функцию из основного кодовой базы transformers и перемещают ее в отдельные хранилища моделей, где пользователи имеют свободу делать непредсказуемые, дикие и замечательные вещи. Мы с нетерпением ждем, чтобы узнать, как вы их используете!