От прогнозирования следующего токена к выполнению инструкций: тонкая настройка модели GPT
Как заранее обученный GPT учится выполнять указания: форматирование записей, смещение меток, маски заполнения, цикл обучения, ограничения оценки и шаг к предпочтениям DPO.
Источники: From Next-Token Prediction to ChatGPT <> Build an LLM From Scratch [6]
Предобучение уже даёт языковой модели способность писать. Однако у неё отсутствует понимание того, чего хочет добиться пользователь. Настройка по инструкциям восполняет этот недостаток. Если предоставить двум моделям один и тот же запрос — например, объяснить рекурсию десятилетнему ребёнку, — разница становится очевидной. Базовая модель склонна просто повторять запрос и уходить в учебниковый вступительный текст о курсах по информатике. Она вовсе не отвечает, а лишь продолжает текст. Настроенная модель же предлагает наглядную иллюстрацию: коробку, внутри которой находится ещё одна коробка, а внутри неё — ещё одна, причём рекурсия описывается как процесс, при котором проблема постоянно передаёт себе более простую версию, пока самая маленькая не станет легкой для решения.
По сути оба модели выполняют одну и ту же задачу — прогнозирование следующего токена, — но их поведение сильно отличается. Сервисы чатов делают выполнение указаний кажущимся естественным, тем самым скрывая эту разницу. На вопросы даются ответы, по запросам на код генерируется код, а также пишутся краткие изложения. Всё это невозможно получить просто путём прочтения огромного объёма текста. Предобучение формирует более фундаментальное понимание: исходя из всего сказанного ранее, какой токен наиболее вероятен дальше? В этом подходе не говорится ни слова о необходимости выполнения запросов пользователя. Именно преодоление этого разрыва является темой данного обсуждения.
Акт 1: предобучение учит завершение предложений, а не послушанию
Задача предобучения заключается лишь в прогнозировании следующего токена. Уже это достаточно мощно, чтобы усвоить грамматику, факты, синтаксис кода, стили написания и даже некоторые удивительно сложные закономерности, но выполнение указаний к этому не относится. Предоставьте базовой модели следующий запрос:
Explain why the sky appears blue.
и это может продолжаться следующим образом:
Explain why the sky appears blue.
The following questions are commonly asked in introductory physics...
Это не обязательно означает, что модель сломана. Она делает именно то, что требовало предобучение, а именно продолжает текст. Желаемое поведение выглядит скорее так:
The sky appears blue because Earth's atmosphere scatters
shorter wavelengths of sunlight more strongly than longer
wavelengths...
Основная модель остаётся той же, но ожидаемое поведение отличается. Вопрос меняется на следующий:
What text probably comes next?
вот сюда:
When the text looks like an instruction,
what kind of continuation should I produce?
Обучение модели отвечать на второй вопрос и есть суть тонкой настройки по инструкциям.
Этап 2: форматирование данных инструкций
Самый прямой способ обучения модели следованию инструкциям — показать ей множество инструкций вместе с правильными ответами, и всё действительно настолько просто. Запись тренировки может выглядеть так:
example = {
"instruction": "Convert the following sentence to passive voice.",
"input": "The developer fixed the bug.",
"output": "The bug was fixed by the developer."
}
Трансформатор не может обрабатывать словари Python, поэтому запись сначала сериализуется в обычный текст примерно в таком виде:
Below is an instruction that describes a task.
### Instruction:
Convert the following sentence to passive voice.
### Input:
The developer fixed the bug.
### Response:
The bug was fixed by the developer.
Форматирование важно, поскольку модель постоянно видит один и тот же шаблон:
instruction
↓
optional context
↓
response
Короче говоря, каждая запись сначала преобразуется в форматированный текст, затем — в токены, после чего формируются дополненные пакеты, которые используются для финальной настройки заранее обученной модели GPT.
Вид токенов
После токенизации пример превращается в список идентификаторов, примерно такой:
[21106, 318, 281, 12064, ... 4435, 257, 3126, ...]
Эти идентификаторы токенов образуют последовательность для обучения. Для авторегрессивной модели метки представляют собой просто последовательность, смещённую на одну позицию:
Tokens:
[A, B, C, D, E]
Input:
[A, B, C, D]
Labels:
[B, C, D, E]
Таким образом, настройка инструкций не вносит никаких совершенно новых целей обучения. Модель по-прежнему предсказывает следующий токен. Изменяется лишь распределение текста, предсказание которого от неё требуется. Вместо в основном обычных документов она теперь снова и снова сталкивается с этой структурой:
instruction → useful response
Вот основная идея.
Для заполнителей требуется особый подход
Примеры для обучения различаются по длине, однако пакеты должны содержать тензоры с совместимыми формами. Рассмотрим два примера:
Example 1:
[10, 20, 30, 40]
Example 2:
[11, 21]
Их можно дополнить заполнителем:
Example 1:
[10, 20, 30, 40]
Example 2:
[11, 21, PAD, PAD]
Токены заполнения не являются значимыми целями. Модель не должна получать награду за их изучение:
PAD → PAD → PAD → PAD
Поэтому эти позиции обычно заменяются значением «игнорировать»:
ignore_index = -100
Функция потерь кросс-энтропия в PyTorch игнорирует их. Концептуально:
loss = cross_entropy(
logits.reshape(-1, vocab_size),
targets.reshape(-1),
ignore_index=-100,
)
Детали имеют значение. Без маскировки часть сигнала обучения тратится на артефакты, возникающие в результате группировки данных, а не на сам язык.
Этап 3: тонкая настройка поведения
Сам цикл обучения здесь не является чем-то необычным, поскольку оптимизация модели предсказания следующего токена уже хорошо известна. Важно то, какие примеры поступают в этот цикл. Упрощенно говоря:
for batch in train_loader:
optimizer.zero_grad()
input_ids = batch[:, :-1]
targets = batch[:, 1:]
logits = model(input_ids)
loss = cross_entropy(
logits.flatten(0, 1),
targets.flatten(),
ignore_index=-100,
)
loss.backward()
optimizer.step()
В этом коде нет ничего, что говорило бы что-то подобное:
model.learn_to_be_helpful()
Поведение определяется данными. Предположим, тысячи примеров выглядят так:
### Instruction:
Summarize this paragraph.
### Response:
<clear concise summary>
Обновления градиента постоянно направляют модель на формирование ответов такого формата после получения запросов такого же типа. Предположим, что другие примеры выглядят так:
### Instruction:
Write Python code that reverses a string.
### Response:
def reverse_string(s):
return s[::-1]
Он выучивает другую связь между шаблоном инструкции и подходящим продолжением. Если рассматривать процесс на уровне токенов, тонкая настройка инструкций кажется менее волшебной: контекст вокруг прогнозов был умышленно изменён. Ранее:
The CPU cache is...
После форматирования инструкции:
### Instruction:
Explain CPU caching to a beginner.
### Response:
A CPU cache is...
Не была введена такая цель, как «понимание пользователей». Вместо этого распределение учебных текстов было изменено так, что полезные ответы становились статистически наиболее вероятными продолжениями после получения инструкции. Именно это изменение приводит к новому поведению. В приведённом примере заранее обученная модель GPT-2-medium проходит доработку под конкретные инструкции, генерирует ответы и сохраняет полученную модель.
Акт 4: оценка становится проблематичной
Классификация позволяла проводить простую оценку. Что касается обнаружения спама:
Prediction: SPAM
Label: SPAM
Correct.
Соблюдение инструкций не позволяет себе такой роскоши. Рассмотрим просьбу объяснить рекурсию просто. Один из возможных ответов:
Recursion is when a function solves a problem by calling
itself on a smaller version of the same problem.
Ещё один:
Recursion is a technique where a problem is reduced into
smaller instances of itself until a stopping condition is reached.
Оба варианта могут быть правильными, и ни один из них не обязан полностью совпадать с эталонным текстом. Поэтому точность полного совпадения в основном бесполезна для ответов с открытым форматом.
Сравнение с эталоном
Один из вариантов — предоставить эталонный текст:
{
"instruction": "Explain recursion simply.",
"reference": "Recursion solves a problem by repeatedly reducing it...",
"model_response": "A recursive function calls itself..."
}
и сравнить полученный ответ с ним. Однако сходство не является показателем качества, и модель может дать отличный ответ, используя совершенно другие слова. Это приводит к другому подходу.
Попросить другую ЯИМ вынести вердикт
Оценщик получает инструкцию, эталонный ответ и ответ для оценки:
Instruction:
Explain recursion simply.
Reference answer:
...
Model answer:
...
Rate the model answer from 0 to 100 based on correctness,
relevance, and clarity.
Местный судья может использовать Ollama, что удобно, но это создаёт новую проблему: сам судья является моделью. Его оценка может зависеть от формулировки запроса на оценку, выбора оценщика, порядка ответов, степени подробности, стилистических предпочтений и собственных ошибок судьи. Некоторые примеры показывают, что местный судья может ставить удивительно высокие оценки слабым ответам базовой модели, что напоминает о том, что цифры, полученные при использовании ЯИМ в качестве судьи, не являются истинными значениями.
Небольшой проект может включать три уровня обработки:
1. Inspect representative outputs manually
2. Compare against reference answers where useful
3. Use an LLM judge for aggregate comparison
Ни один из этих показателей сам по себе не является идеальным. Вместе они дают более чёткую картину, чем одна цифра, выдаваемая за истину.
Этап 5: более сложная проблема — предпочтения
Тонкая настройка инструкций показывает модели, как выглядит разумный ответ. Теперь сравним два ответа. Ответ А:
Recursion is when a function calls itself.
Ответ B:
Recursion is a technique where a function solves a problem
by calling itself on a smaller version of that problem.
The process stops when it reaches a base case.
Оба варианта допустимы, но предпочтительнее может быть B. Сигнал обучения больше не выглядит так:
correct vs incorrect
Это следующее:
chosen vs rejected
Запись предпочтений может выглядеть так:
{
"prompt": "Explain recursion simply.",
"chosen": """
Recursion solves a problem by repeatedly reducing it
until reaching a base case.
""",
"rejected": """
Recursion is when recursion happens recursively.
"""
}
Это другой вид надзора. Вместо того чтобы говорить «вот пример ответа для подражания», здесь говорится «среди этих двух ответов предпочтите этот». Здесь применяются такие методы, как оптимизация прямого предпочтения (DPO). В общих чертах:
Prompt
↓
Chosen response ─────┐
├── preference objective
Rejected response ───┘
↓
model update
DPO заслуживает отдельного рассмотрения, поэтому его механизмы не входят в рамки данного обсуждения. Полезной ссылкой является общая структура прогрессии:
Pretraining
↓
learn language patterns
------------------------------
Instruction fine-tuning
↓
learn instruction → response behavior
------------------------------
Preference tuning
↓
learn which acceptable responses we prefer
Создание наборов данных предпочтений с использованием Llama 3.1 через Ollama и реализация метода DPO с нуля — это темы для будущего. Существует одно предупреждение: наличие DPO в качестве дополнительного материала не делает его подходящим следующим шагом для каждой модели или набора данных. Некоторые эксперименты показывают ограничения его результатов, что и является тем скептицизмом, который стоит учитывать при настройке предпочтений.
Что на самом деле изменилось
Начальной точкой служила заранее обученная модель, умеющая продолжать текст. Архитектура трансформера не была заменена на специальную архитектуру помощника. Примеры, связанные с проблемой следующего токена, изменились, что привело к другому поведенческому профилю. Теперь, когда модель видит:
### Instruction:
Было достаточно случаев, когда продолжение не являлось следующим абзацем из Интернета, а представляло собой ответ. С такой точки зрения понимание хода событий становится проще:
Pretraining:
What token should come next?
Instruction tuning:
What does a good answer look like after an instruction?
Preference tuning:
Of several reasonable answers, which behavior should we prefer?
Архитектура, оптимизатор и скорость обучения — всё это важно, но при настройке под инструкции особую роль играют данные, поскольку именно они определяют поведение, которое необходимо имитировать. Когда существует несколько приемлемых ответов, единственной целью уже не является получение корректного результата. Вопрос становится в том, какой из приемлемых ответов заслуживает большего веса, и именно это открывает путь для метода DPO.
Предобучение помогает понять, какой текст, как правило, следует далее. Настройка под инструкции определяет, какие продолжения считаются полезными ответами на инструкции. Настройка по предпочтениям делает следующий шаг и определяет, какие из полезных ответов следует отдавать предпочтение.
Связанные материалы
- Обучение небольшой модели GPT на индийских народных сказках: токенизатор, маска и функция потерь — Создание модели GPT с 9 миллионами параметров с нуля: почему токенизатор GPT-2 не справляется с словами из народных сказок, как работают алгоритмы BPE и каузальная маска, а также как одна партия данных позволяет получить 4,096 значений функции потерь для предсказания следующего токена.
- Внутри LLM: инференс на чистом Python для модели Qwen3-0.6B с использованием MLX — Пошаговая реализация процесса инференса LLM примерно из 200 строк кода на Python с использованием библиотеки MLX: токенизатор, RMSNorm, RoPE, группированное внимание к запросам, SwiGLU, кэш KV-элементов и процедура выборки.