Модель обучилась без ошибок, метрики выглядят прилично, а на запуске она начинает договаривать за пользователя, вставлять служебные строки в текст ответа или не останавливаться вовремя. Первым делом подозревают гиперпараметры. Почти всегда виноват формат.
#Короткий ответ
Alpaca, ShareGPT и ChatML — три способа записать одно и то же: кто что сказал. Alpaca описывает одиночную инструкцию полями, ShareGPT — список реплик с ролями, ChatML — уже отрендеренный текст со служебными маркерами.
Правило, из-за которого всё ломается: обучающий текст должен быть отрендерен ровно тем шаблоном чата, с которым модель будет работать на запуске. Не «похожим», а тем же самым — с теми же маркерами ролей и тем же токеном конца реплики.
#Один диалог в трёх записях
Возьмём одну пару: системная инструкция, вопрос, ответ.
Alpaca. Плоские поля, одиночный ход, без истории:
{
"instruction": "Перепиши абзац короче, сохранив смысл.",
"input": "Мы хотели бы обратить ваше внимание на то, что сроки сдвигаются.",
"output": "Сроки сдвигаются."
}
ShareGPT. Список реплик с ролями, поддерживает многоходовые диалоги:
{
"conversations": [
{"from": "system", "value": "Отвечай коротко, без вводных оборотов."},
{"from": "human", "value": "Перепиши короче: мы хотели бы обратить ваше внимание на то, что сроки сдвигаются."},
{"from": "gpt", "value": "Сроки сдвигаются."}
]
}
Формат сообщений OpenAI. Тот же список, но с ключами role и content — сегодня это самый распространённый вид на диске:
{
"messages": [
{"role": "system", "content": "Отвечай коротко, без вводных оборотов."},
{"role": "user", "content": "Перепиши короче: мы хотели бы обратить ваше внимание на то, что сроки сдвигаются."},
{"role": "assistant", "content": "Сроки сдвигаются."}
]
}
ChatML. Не структура, а результат рендеринга — то, что модель реально видит:
<|im_start|>system
Отвечай коротко, без вводных оборотов.<|im_end|>
<|im_start|>user
Перепиши короче: мы хотели бы обратить ваше внимание на то, что сроки сдвигаются.<|im_end|>
<|im_start|>assistant
Сроки сдвигаются.<|im_end|>
Первые три — способы хранения. Четвёртый — способ подачи. Именно поэтому ставить их в один ряд некорректно, хотя так делают повсеместно.
#Как это устроено: два разных слоя
Различие между «хранением» и «подачей» — ключ ко всей теме.
Слой хранения. JSONL: одна запись на строку. Alpaca, ShareGPT, messages — варианты схемы этой записи. Библиотеки обучения умеют читать любой из них, вопрос лишь в том, чтобы указать правильный тип датасета в конфигурации.
Слой подачи. Перед обучением записи превращаются в сплошной текст. Это делает шаблон чата — он лежит в файлах токенизатора базовой модели и знает, какими маркерами обозначать роли и чем заканчивать реплику. ChatML с маркерами <|im_start|> — один из таких шаблонов, а не универсальный стандарт: у разных семейств моделей маркеры разные.
Отсюда практическое следствие: не переписывайте датасет в ChatML руками. Правильный ход — хранить структуру и рендерить её родным шаблоном модели, обычно вызовом apply_chat_template из токенизатора. Именно так устроен наш собственный скрипт обучения: он читает записи с полем messages и прогоняет каждую через шаблон той базы, которую вы указали, — так что при смене базы формат подачи меняется сам.
#Правило соответствия
Сформулируем то, ради чего написана статья.
Шаблон, которым датасет отрендерен при обучении, обязан совпадать с шаблоном, которым запрос будет отрендерен при запуске.
Что происходит при нарушении:
| Что не совпало | Как проявляется на запуске |
|---|---|
| Маркеры ролей | Модель пишет служебные строки внутри ответа |
| Токен конца реплики | Модель не останавливается, договаривает за пользователя |
| Наличие системной роли | Системный промпт игнорируется или попадает в ответ текстом |
| Место, где начинается ответ | Первые слова ответа обрезаются или дублируются |
| Шаблон при слиянии и конвертации | Всё то же самое, но проявляется только после переноса модели |
Последняя строка коварнее прочих: обучение и проверка прошли нормально, а сломалось после переноса в другой рантайм, где шаблон подставился другой. Поэтому шаблон чата надо переносить вместе с моделью — при конвертации он записывается в метаданные файла или задаётся в описании модели; что именно и как хранится внутри контейнера — в разборе формата GGUF.
#Что из этого следует на практике
Храните в messages. Это самая переносимая схема: она поддерживает многоходовые диалоги, её понимают все распространённые инструменты, и она напрямую ложится в apply_chat_template. Alpaca выбирайте только если ваши данные действительно одноходовые инструкции.
Рендерьте шаблоном базы, а не своим. Один вызов вместо ручной сборки строк — и вы не ошибётесь маркерами.
Проверьте рендер глазами до обучения. Распечатайте первую запись после применения шаблона и прочитайте. Пять секунд, которые ловят большинство проблем этого класса.
Для рассуждающих моделей блок мыслей — часть ответа. Если база умеет рассуждать и выдаёт блок <think>, он должен присутствовать в обучающем ответе целиком. Обучение на «голых» ответах без рассуждений затирает саму способность рассуждать.
Не смешивайте схемы в одном наборе. Половина записей в Alpaca, половина в messages — гарантированная тихая потеря части данных при загрузке.
Как конфигурируется тип датасета в инструменте, где всё описано файлом настроек, — в гайде по Axolotl. Откуда брать сами примеры — в статье как собрать датасет из своих оценок.
#Что выгружает Persona
Экспорт датасета в Persona отдаёт JSONL, где каждая строка — объект с полем messages в формате ролей и служебным блоком metadata (модель, провайдер, оценка, дата). Системный промпт на момент ответа и предшествующие реплики диалога попадают в тот же список сообщений.
Практическое следствие для того, кто это обучает: поле metadata в обучение не идёт — его надо отбросить или проигнорировать при загрузке. И второе: раз в записи сохранён системный промпт того момента, при смене характера ассистента старые записи будут учить модель поведению, которого вы уже не хотите. Это ещё один аргумент за периодическую ревизию набора — процедура в статье про очистку датасета.
#Где интуиция подводит
«ChatML — это стандарт, значит, подойдёт любой модели». Не подойдёт. Маркеры <|im_start|> используются в одних семействах и не используются в других. Универсального шаблона нет, есть шаблон конкретной модели.
«Модель умная, разберётся в формате». Не разберётся: формат подачи — это не смысл, а разметка входа. Модель обучена реагировать на конкретные маркеры, и отклонение от них меняет поведение молча, без ошибок.
«Раз обучилось без ошибок, формат правильный». Обучение не проверяет соответствие шаблону. Оно честно выучит то, что вы подали, включая кривую разметку. Проявится это только на запуске.
«Alpaca устарел». Не устарел, просто у него узкая область: одноходовые инструкции без истории. Для датасета личного стиля он неудобен, потому что теряет контекст диалога, — но для набора «инструкция — эталон» работает.
Общая рамка всего маршрута — в пилларе про свою модель и дообучение. Как выглядит рабочий запуск локальной модели с правильным шаблоном — в гайде про локальный запуск Nous Hermes.
#Частые вопросы
#Какой формат выбрать, если я только начинаю?
JSONL с полем messages — по строке на пример. Его читают распространённые библиотеки, он поддерживает многоходовые диалоги, и он же используется в экспорте Persona. Alpaca берите, только если ваши данные — действительно одиночные инструкции без истории.
#Нужно ли включать системный промпт в каждый пример?
Если поведение, которому вы учите, зависит от системного промпта — да, и один и тот же во всех примерах. Если системного промпта в примерах нет, а на запуске он появится, модель окажется в незнакомой ей конфигурации. Главное — не менять его от примера к примеру: это учит противоречию.
#Как узнать шаблон чата своей модели?
Он лежит в файлах токенизатора базовой модели и применяется вызовом apply_chat_template. Самый надёжный способ посмотреть — отрендерить один пример и распечатать результат: вы увидите ровно те маркеры, которые модель ожидает.
#Что делать, если у базы нет шаблона чата?
Такое встречается у «сырых» базовых моделей без инструктивного дообучения. Тогда шаблон задаёте вы сами — и обязаны использовать ровно тот же на запуске, включая токен конца реплики. Проще взять инструктивный вариант базы, где шаблон уже определён.
#Можно ли смешивать одноходовые и многоходовые примеры?
Можно, если все они записаны одной схемой и рендерятся одним шаблоном. Смешивать схемы нельзя. И следите за долей: если многоходовых примеров единицы, модель не научится держать диалог, зато научится длине ваших одноходовых ответов.
#Вывод
Форматов на самом деле два слоя, а не три штуки: схема хранения и шаблон подачи. Схему выбирайте по удобству — messages подойдёт почти всем. Шаблон не выбирайте вовсе: берите родной шаблон базовой модели и следите, чтобы он же применялся на запуске.
Если после обучения модель ведёт себя странно — начинайте проверку не с гиперпараметров, а с распечатки одной обучающей записи после рендеринга. Чаще всего ответ виден там.