Продакшн-разработка ИИ-агентов · Модуль 6 · Урок 6.3
Глава 14. Адаптация моделей (опционально)
Цели главы
После этой главы вы сможете:
- Понимать, когда дообучение (fine-tuning) оправдано ВМЕСТО промптинга, а когда нет.
- Различать дистилляцию (distillation) и reinforcement fine-tuning / RLHF-подобное обучение.
- Оценивать стоимость и риски дообучения: данные, дрейф (drift), поддержка.
- Применять главное правило: сначала выжать промптинг, контекст и инструменты; fine-tuning — последний рычаг.
- Готовить датасет примеров в правильном формате (обзорно, на уровне схемы).
Что нового (дельта к базовому курсу)
Базовый курс жил целиком в режиме промптинга: всё поведение задавалось системным промптом, few-shot-примерами и инструментами. Этого хватает для подавляющего большинства задач.
Дельта этой главы — обзор того, что лежит за промптингом: адаптация самих весов модели под домен. Это не «следующий шаг по умолчанию», а специализированный, дорогой и редко нужный рычаг.
Важно: на момент написания прямое дообучение моделей Claude через нативный API общедоступно ограниченно (часть возможностей — через облачные платформы вроде Amazon Bedrock). Поэтому глава — концептуальная: критерии выбора и риски, а не пошаговый рецепт. Сверяйтесь с актуальными доками по доступности.
Когда fine-tuning ВМЕСТО промптинга — и когда НЕ нужен
Сначала о том, когда НЕ нужен (это частый случай). Если задача решается комбинацией: чёткий системный промпт + несколько few-shot-примеров + извлечение знаний из контекста (RAG, retrieval-augmented generation) — дообучение не нужно. Промптинг гибок, итерируется за минуты, не требует данных и не создаёт долга на поддержку.
Fine-tuning стоит рассматривать, когда промптинг честно выжат и упирается в одно из:
- Стабильный формат/стиль/домен. Нужен строго один формат вывода или узкий доменный стиль, который few-shot воспроизводит ненадёжно.
- Экономия токенов на длинных инструкциях. Если в каждый запрос приходится класть огромную инструкцию или десятки примеров, дообучение «зашивает» это в веса — короче промпт, дешевле инференс.
- Латентность. Меньше промпта -> меньше input-токенов -> ниже TTFT на массовой однотипной нагрузке.
Эвристика: если вы можете описать желаемое поведение словами в промпте — начните с промпта. Fine-tuning оправдан, когда поведение проще *показать на тысячах примеров*, чем описать, и нагрузка достаточно велика, чтобы окупить обучение.
Дистилляция и reinforcement fine-tuning (обзорно)
Дистилляция (distillation). Большая сильная модель-учитель генерирует эталонные ответы на наборе типовых запросов; на этих парах «запрос -> ответ учителя» дообучается маленькая дешёвая модель-ученик. Результат — ученик приближается к качеству учителя на узком домене при кратно меньшей стоимости инференса. Это форма supervised fine-tuning, где разметку делает не человек, а сильная модель. Применяют, когда есть стабильная массовая нагрузка и хочется снизить её цену.
Reinforcement fine-tuning / RLHF-подобное. Здесь модель обучается не на готовых эталонах, а под сигнал награды (reward) или предпочтения (preferences): из пар «ответ A лучше ответа B» или из функции-оценщика модель учится максимизировать желаемое. Это сложнее и дороже supervised-подхода, требует качественного сигнала награды и легко уводит модель в нежелательное поведение при плохо заданном reward. Оправдано, когда «правильность» нельзя задать одним эталоном, но можно сравнивать или оценивать ответы.
Оба подхода — тяжёлая артиллерия. Для агентов их рассматривают в последнюю очередь.
Стоимость, риски и главное правило
Fine-tuning создаёт обязательства, которых у промптинга нет:
- Данные. Нужен качественный, репрезентативный, очищенный датасет. Мусор на входе -> мусор в весах. Сбор и разметка — основная статья затрат.
- Дрейф (drift). Дообученная модель зафиксирована на распределении обучающих данных. Когда домен, продукт или базовая модель меняются, дообученная начинает отставать — её нужно переобучать. Промпт правится за минуту.
- Поддержка. Версии датасета, версии дообученной модели, эвалы под каждую версию, откаты — отдельный жизненный цикл артефакта.
Главное правило главы: сначала выжать промптинг, контекст (RAG) и инструменты. Каскад моделей, кеш и хороший промпт закрывают большинство задач стоимости и качества. Fine-tuning — последний рычаг, к которому прибегают, когда промптинг уперся в потолок, нагрузка велика и однотипна, а выигрыш заведомо окупает данные, дрейф и поддержку.
package adaptation
import (
"bufio"
"encoding/json"
"errors"
"io"
"strings"
)
// Точный формат датасета для fine-tuning зависит от платформы и версии —
// сверяйтесь с актуальными доками. Здесь — иллюстративная схема в духе JSONL:
// одна строка = один обучающий пример (диалог из чередующихся ролей).
// Turn — одна реплика в примере. Теги json опущены намеренно (учебный код);
// в реальном коде поля размечаются json-тегами: Role -> "role", Text -> "content".
type Turn struct {
Role string // "user" или "assistant"
Text string
}
// Example — один обучающий пример: вход(ы) пользователя и эталонный ответ.
type Example struct {
Messages []Turn
}
var (
errEmpty = errors.New("пример без сообщений")
errRole = errors.New("недопустимая роль в реплике")
errNoAssistant = errors.New("в примере нет эталонного ответа assistant")
)
// validate проверяет один пример перед добавлением в датасет.
// Чистота данных — основная статья затрат и риска fine-tuning.
func validate(ex Example) error {
if len(ex.Messages) == 0 {
return errEmpty
}
hasAssistant := false
for _, t := range ex.Messages {
switch t.Role {
case "user":
case "assistant":
hasAssistant = true
default:
return errRole
}
if strings.TrimSpace(t.Text) == "" {
return errEmpty
}
}
if !hasAssistant {
return errNoAssistant
}
return nil
}
// WriteDataset сериализует валидные примеры в JSONL (по одному на строку)
// и возвращает число записанных и число отброшенных примеров.
func WriteDataset(w io.Writer, examples []Example) (written, skipped int, err error) {
bw := bufio.NewWriter(w)
defer bw.Flush()
enc := json.NewEncoder(bw)
for _, ex := range examples {
if verr := validate(ex); verr != nil {
// Отбраковываем некорректный пример, а не подмешиваем мусор в веса.
skipped++
continue
}
if werr := enc.Encode(ex); werr != nil {
return written, skipped, werr
}
written++
}
return written, skipped, nil
}
Anti-patterns
| Грабля | Почему плохо | Как избегать |
|---|---|---|
| Идти в fine-tuning, не выжав промптинг | Дорогой и негибкий рычаг там, где хватило бы системного промпта, few-shot и RAG | Правило: сначала промптинг/контекст/инструменты; fine-tuning — последний шаг |
| Дообучение на грязном или нерепрезентативном датасете | Мусор на входе закрепляется в весах; качество падает необратимо до переобучения | Валидация и очистка примеров; отбраковка некорректных перед записью датасета |
| Игнорировать дрейф после деплоя дообученной модели | Модель зафиксирована на старом распределении и отстаёт при смене домена/продукта | Эвалы на свежих данных, мониторинг качества, план переобучения |
| Брать reinforcement fine-tuning с плохо заданным reward | Модель максимизирует кривой сигнал и уходит в нежелательное поведение | Начинать с supervised/дистилляции; RL — только при качественном сигнале награды |
| Дообучать ради экономии при малой нагрузке | Затраты на данные, обучение и поддержку не окупаются на низком объёме | Считать окупаемость: fine-tuning оправдан на массовой однотипной нагрузке |
Практическое задание (pro-m6-model-adaptation)
- Возьмите реальную задачу и честно выжмите промптинг: системный промпт, few-shot, RAG; зафиксируйте достигнутое качество как базовую планку.
- Сформулируйте критерий, при котором промптинг считается упёршимся в потолок (метрика и порог), — без него решение о fine-tuning субъективно.
- Подготовьте иллюстративный датасет примеров в JSONL: соберите пары запрос->эталонный ответ и прогоните через валидацию (роли, непустота, наличие assistant).
- Оцените стоимость и риски для вашего кейса: объём и чистота данных, частота дрейфа, цена поддержки версий.
- Сравните альтернативы: даст ли каскад моделей и кеш тот же выигрыш по стоимости/латентности дешевле, чем дообучение.
- Опишите для дистилляции: какая модель-учитель, на каком наборе запросов генерирует эталоны, какая модель-ученик и как меряется приближение к учителю.
Проверка знаний
Команда хочет, чтобы агент всегда отвечал в строгом доменном формате. Этого почти удалось добиться чётким системным промптом и few-shot-примерами.
Какой следующий шаг правильный?
Верный ответ: B
Главное правило главы: сначала выжать промптинг/контекст/инструменты. Если few-shot почти даёт нужный формат, дешевле и гибче довести промпт, чем брать на себя данные, дрейф и поддержку дообучения. RL здесь избыточен, MaxTokens к формату не относится.
Большая сильная модель генерирует эталонные ответы на типовых запросах, и на этих парах дообучают маленькую дешёвую модель.
Как называется этот подход и зачем он нужен?
Верный ответ: B
Это дистилляция: сильная модель-учитель размечает данные, на которых учится дешёвая модель-ученик, чтобы снизить цену массового инференса. Reinforcement fine-tuning учится под reward, а не на готовых эталонах; prompt caching и RAG — это про контекст, а не про обучение весов.
Дообученную модель выкатили в прод; через несколько месяцев продукт и домен заметно изменились.
Какой риск fine-tuning здесь проявляется?
Верный ответ: A
Это дрейф (drift): веса зафиксированы на обучающем распределении, и при смене домена качество падает — нужно переобучение, отдельный жизненный цикл артефакта. В отличие от промпта, который правится за минуту. Остальные варианты — про другие подсистемы и к адаптации весов не относятся.