Учебник «Большие языковые модели»
Дообучение (fine-tuning) окружено ореолом: кажется, что «своя, дообученная на наших данных модель» — это следующая ступень зрелости после промптинга. На практике часть задач решается промптом или RAG быстрее и управляемее, а часть действительно выигрывает от адаптации весов. Поэтому глава начинается не с «как», а с «когда», — и лишь затем переходит к LoRA, данным и типичным разочарованиям. Речь здесь о дообучении моделей с доступными весами на своём оборудовании; лицензия каждой базы отдельно определяет допустимые применения и распространение производных весов. Дообучение через API закрытых моделей устроено похоже, но со своими ограничениями.
Вспомним главу 7: дообучение особенно удобно для изменения поведения. Оно может менять и фактические ассоциации, но такие знания не имеют встроенных ссылок, трудно обновляются и проверяются; для меняющихся сведений обычно удобнее контекст и RAG. Дообучение оправдано, когда:
— нужный стиль, формат или доменная конвенция не
достигаются промптом стабильно: жёсткий редакционный стиль, разметка в
специфической нотации, диалект SQL внутренней системы;
— нужно удешевить: большая модель с длинным промптом решает
задачу хорошо, и хочется перенести это умение в маленькую (дистилляция:
большая модель порождает обучающие примеры, маленькая на них дообучается)
— массовые конвейеры классификации и извлечения —
классический кандидат;
— задача узкая и повторяемая, а измеренная экономия или качество
оправдывают подготовку данных, обучение и сопровождение;
— промпт-инженерия честно исчерпана: есть eval (глава 13),
и на нём видно, что лучшие промпты с примерами упёрлись в потолок.
Контрольный вопрос перед стартом: «есть ли у нас измеримый eval, на котором промптинг показывает недостаточный результат?» Нет eval — дообучать рано: без него вы не узнаете, помогло ли.
Полное дообучение модели даже в 8 млрд параметров требует держать в памяти веса, градиенты и состояния оптимизатора — десятки байт на параметр, сотни гигабайт GPU-памяти. Стандартное решение — LoRA (low-rank adaptation, Ху и др., 2021). Идея: заморозить исходные веса, а поправку к каждой дообучаемой матрице W ∈ ℝm×n искать в виде произведения двух узких матриц:
W′ = W + (α/r) · B A, B ∈ ℝm×r, A ∈ ℝr×n, r ≪ m, n
Ранг r обычно берётся гораздо меньше размерности матрицы, и обучаемых параметров становится r(m+n) вместо mn — на пару порядков меньше: для модели 8B адаптер LoRA может занимать лишь малую долю базы. Требования к памяти зависят от модели, длины последовательности, батча, оптимизатора и precision. В варианте QLoRA (базовые веса квантуются в 4 бита, адаптеры обучаются поверх; Деттмерс и др., 2023) — базовые веса занимают меньше памяти, но конкретную конфигурацию надо проверять профилированием. Эмпирически низкоранговых поправок часто хватает для адаптации к задаче, но качество зависит от выбранных модулей и ранга. После обучения адаптер либо подгружается к базовой модели на лету (и можно держать много адаптеров под разные задачи на одной базе), либо вливается в веса (merge) для простоты развёртывания.
Как и в предобучении, при фиксированной технологии результат сильно зависит от данных. Формат — диалоги «запрос → образцовый ответ» в шаблоне вашей базовой модели (перепутать шаблон — частая и коварная ошибка: обучение «сойдётся», а модель на инференсе будет вести себя странно, ср. главу 1). Объёмы для стилевых и форматных задач скромны: счёт идёт на сотни — тысячи качественных примеров; один из источников — журналы работающего конвейера (глава 9). При наличии законного основания и после удаления секретов и персональных данных можно отобрать удачные ответы большой модели, выправить руками худшие места — вот и датасет дистилляции. Требования: примеры покрывают реальное распределение входов, включая граничные случаи и случаи «данных недостаточно» (иначе дообученная модель разучится отказываться — вы буквально выучите её галлюцинировать уверенно); ответы единообразны — дообучение выучит и ваш разнобой. Данные разделите как минимум на обучение и валидацию, а итоговый test-набор держите закрытым до выбора конфигурации. Разбивайте по пользователям, документам или времени так, чтобы близкие дубликаты не протекали между частями.
Инструментальный стандарт — связка Hugging Face transformers + peft + trl (либо обёртки над ней вроде Axolotl или Unsloth). Скелет обучения:
# pip install transformers peft trl datasets bitsandbytes
import os
import torch
from datasets import load_dataset
from peft import LoraConfig
from trl import SFTConfig, SFTTrainer
BASE_MODEL = os.environ["BASE_MODEL"]
dataset = load_dataset("json", data_files="train.jsonl", split="train")
dataset = dataset.train_test_split(test_size=0.1, seed=42)
# формат строк: {"messages": [{"role": "user", "content": ...},
# {"role": "assistant", "content": ...}]}
peft_cfg = LoraConfig(
r=16, lora_alpha=32, lora_dropout=0.05,
target_modules=["q_proj", "k_proj", "v_proj", "o_proj"], # имена модели
task_type="CAUSAL_LM",
)
use_bf16 = torch.cuda.is_available() and torch.cuda.is_bf16_supported()
cfg = SFTConfig(
output_dir="out-lora",
num_train_epochs=2,
per_device_train_batch_size=2,
gradient_accumulation_steps=8, # эффективный батч 16
learning_rate=1e-4, # отправная точка, не универсальный рецепт
bf16=use_bf16,
fp16=torch.cuda.is_available() and not use_bf16,
assistant_only_loss=True,
logging_steps=10,
eval_strategy="steps", eval_steps=50,
)
trainer = SFTTrainer(
model=BASE_MODEL,
train_dataset=dataset["train"],
eval_dataset=dataset["test"],
peft_config=peft_cfg,
args=cfg,
)
trainer.train()
trainer.save_model("out-lora/final") # сохраняется только адаптер
Для conversational dataset TRL применяет chat template модели, но assistant_only_loss=True работает только с шаблоном, который возвращает маску токенов ассистента. Это надо проверить до долгого запуска; неподходящий шаблон следует исправить или выбрать явно. Имена target_modules, поддержка bf16, ранг, learning rate, батч и число эпох зависят от архитектуры и оборудования. Следите за кривой валидации и метрикой прикладного eval, а не только за обучающей потерей.
Оценка результата — только сквозная, на вашем eval (глава 13), в сравнении с тремя базами: исходная модель с лучшим промптом, большая модель с лучшим промптом, и — обязательно — дообученная модель на общих задачах: агрессивное дообучение на узкой задаче способно попортить общие способности (катастрофическое забывание; LoRA с малым рангом смягчает это, но не отменяет).
«Дообучили на нашей документации — отвечает не лучше». Ожидали загрузки знаний — см. 12.1: это задача RAG.
«На обучающих примерах блестяще, на живых входах — нет». Данные не покрывали реальное распределение (или протекли в валидацию). Лечится ревизией датасета, а не эпохами.
«Стала увереннее — и чаще выдумывает». В данных не было примеров отказа; модель выучила «всегда отвечай». Добавьте случаи «данных недостаточно».
«Через полгода вышла новая базовая модель, и наш адаптер устарел». Так и будет: адаптер привязан к базе, перенос — это новое обучение. Поэтому конвейер дообучения (данные → обучение → eval) должен быть воспроизводимым скриптом, а не героическим разовым актом. Переезд на новую базу всё равно требует совместимого шаблона, повторного обучения и полной регрессионной оценки; его срок заранее не гарантирован.
«Дообученная 8B не догнала большую модель с промптом». Бывает, и нередко: заранее это не узнать, потому и нужен eval до старта. Утешение: даже в этом случае вы получили измеримую цену вопроса.
Дообучение особенно полезно для стиля, формата, узких повторяемых задач и дистилляции поведения большой модели в меньшую. LoRA/QLoRA сокращают число обучаемых параметров и память, но реальная вместимость зависит от всей конфигурации. Качество определяют права и чистота данных, корректный chat template, разбиение без утечки и eval против сильных промптовых баз. Весь путь — данные, версии, обучение и тесты — должен быть воспроизводимым, потому что адаптер привязан к конкретной базе.
Третья часть закончена: промпт, API, RAG, агенты, дообучение — полный набор строительных блоков. Четвёртая часть — об эксплуатации: как всё это измерять, запускать локально, защищать и оплачивать. Начнём с измерения — главы 13 об оценке качества, на которую мы столько раз ссылались.
Упражнения
1. Возьмите свою реальную LLM-задачу и честно проверьте её по критериям раздела 12.1: есть ли eval? исчерпан ли промптинг? какова частота вызовов? Запишите вывод — дообучать или нет — с обоснованием.
2. Посчитайте число обучаемых параметров LoRA с r = 16 для одной матрицы 4096×4096 и сравните с полным дообучением этой матрицы. Во сколько раз меньше?
3. Соберите с разрешением и обезличьте набор пар «вход → ответ большой модели», отделите train, validation и закрытый test, затем дообучите небольшую модель по скелету из 12.4. Сравните её на test-наборе с исходной моделью и лучшим промптом. Стоила ли игра свеч?