Перейти к содержимому
ПереводыHugging Face

LLM Course, глава 11: дообучение LLM — шаблоны, SFT, LoRA, оценка

Перевод главы 11 курса Hugging Face: chat-шаблоны, обучение с учителем в SFTTrainer, LoRA через PEFT и оценка дообученной модели на бенчмарках с lighteval.

Это перевод главы 11 курса LLM Course от Hugging Face. Глава выросла из первых модулей smol course и показывает весь цикл дообучения: оформить диалоги шаблоном, обучить модель на примерах (SFT), сделать это дёшево через LoRA и проверить результат. Карта всего курса — в путеводителе.

flowchart LR
A["Базовая модель"] --> B["11.2 Chat-шаблон:<br/>диалог → строка"]
B --> C["11.3 SFT:<br/>учимся на примерах"]
C --> D["11.4 LoRA:<br/>учим только адаптер"]
D --> E["11.5 Оценка:<br/>бенчмарки + свои тесты"]
Маршрут главы 11

В главе 2, раздел 2 мы видели, что генеративные языковые модели можно дообучать на конкретных задачах — суммаризации, ответах на вопросы. Но сегодня гораздо чаще модели дообучают сразу на широком наборе задач; такой метод называется обучением с учителем на инструкциях (supervised fine-tuning, SFT). Он делает модели универсальнее и помогает им справляться с самыми разными сценариями. Большинство LLM, с которыми люди общаются на платформах вроде ChatGPT, прошли SFT, чтобы стать полезнее и лучше соответствовать предпочтениям людей. Глава разделена на четыре части.

Chat-шаблоны (chat templates) задают структуру общения пользователя с моделью и обеспечивают единообразные и уместные ответы. В них есть такие элементы, как системный промпт и сообщения с ролями.

SFT — ключевой процесс, который приспосабливает предобученную языковую модель к конкретным задачам. Модель обучают на датасете с размеченными примерами для этой задачи. Подробное руководство по SFT с ключевыми шагами и лучшими практиками — в разделе о supervised fine-tuning в документации TRL.

Low Rank Adaptation (LoRA) — техника дообучения, которая добавляет в слои модели матрицы низкого ранга. Это позволяет дообучать эффективно и при этом сохранять знания, полученные при предобучении. Одно из главных преимуществ LoRA — большая экономия памяти: большие модели можно дообучать на скромном железе.

Оценка — важнейший шаг дообучения. Она позволяет измерить качество модели на датасете для конкретной задачи.

Ноутбук к разделу: Google Colab.

Chat-шаблоны необходимы, чтобы структурировать общение языковых моделей с пользователями. Делаете ли вы простого чат-бота или сложного ИИ-агента — правильное оформление диалога решает, насколько хорошо модель будет работать. В этом руководстве мы разберём, что такое chat-шаблоны, зачем они нужны и как ими пользоваться.

Базовую модель (base model) обучают на сыром тексте предсказывать следующий токен, а instruct-модель дополнительно дообучают следовать инструкциям и вести диалог. Например, SmolLM2-135M — базовая модель, а SmolLM2-135M-Instruct — её вариант, дообученный на инструкциях.

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

Чтобы базовая модель вела себя как instruct-модель, промпты нужно оформлять единообразно и понятно для модели. Для этого и нужны chat-шаблоны. ChatML — один из таких форматов: он размечает диалог явными метками ролей (system, user, assistant). Вот пример ChatML.

Прежде чем переходить к реализации, важно понять, в каком виде разные модели ждут диалог. Разберём распространённые форматы на простом примере. Во всех примерах будем использовать такой диалог:

messages = [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Hello!"},
{"role": "assistant", "content": "Hi! How can I help you today?"},
{"role": "user", "content": "What's the weather?"},
]

Вот шаблон ChatML, который используют модели вроде SmolLM2 и Qwen 2:

Окно терминала
<|im_start|>system
You are a helpful assistant.<|im_end|>
<|im_start|>user
Hello!<|im_end|>
<|im_start|>assistant
Hi! How can I help you today?<|im_end|>
<|im_start|>user
What's the weather?<|im_end|>
<|im_start|>assistant

А это формат шаблона mistral:

Окно терминала
<s>[INST] You are a helpful assistant. [/INST]
Hi! How can I help you today?</s>
[INST] Hello! [/INST]

Основные различия форматов:

  1. Обработка системного сообщения:

    • Llama 2 оборачивает системное сообщение в теги <<SYS>>
    • Llama 3 использует теги <|system|> с окончаниями </s>
    • Mistral включает системное сообщение в первую инструкцию
    • Qwen использует явную роль system с тегами <|im_start|>
    • ChatGPT использует префикс SYSTEM:
  2. Границы сообщений:

    • Llama 2 использует теги [INST] и [/INST]
    • Llama 3 использует теги ролей (<|system|>, <|user|>, <|assistant|>) с окончаниями </s>
    • Mistral использует [INST] и [/INST] вместе с <s> и </s>
    • Qwen использует свои токены начала и конца для каждой роли
  3. Специальные токены:

    • Llama 2 использует <s> и </s> как границы диалога
    • Llama 3 использует </s>, чтобы закончить каждое сообщение
    • Mistral использует <s> и </s> как границы реплик
    • Qwen использует свои токены начала и конца для каждой роли

Понимать эти различия важно для работы с разными моделями. Посмотрим, как библиотека transformers сама справляется с этими вариациями:

from transformers import AutoTokenizer
# Каждый токенизатор автоматически применит свой шаблон
mistral_tokenizer = AutoTokenizer.from_pretrained("mistralai/Mistral-7B-Instruct-v0.1")
qwen_tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen-7B-Chat")
smol_tokenizer = AutoTokenizer.from_pretrained("HuggingFaceTB/SmolLM2-135M-Instruct")
messages = [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Hello!"},
]
# Каждый оформит диалог по шаблону своей модели
mistral_chat = mistral_tokenizer.apply_chat_template(messages, tokenize=False)
qwen_chat = qwen_tokenizer.apply_chat_template(messages, tokenize=False)
smol_chat = smol_tokenizer.apply_chat_template(messages, tokenize=False)

Примеры шаблонов (в оригинале спрятаны под спойлер).

Шаблон ChatML у Qwen 2 и SmolLM2:

Окно терминала
<|im_start|>system
You are a helpful assistant.<|im_end|>
<|im_start|>user
Hello!<|im_end|>
<|im_start|>assistant
Hi! How can I help you today?<|im_end|>
<|im_start|>user
What's the weather?<|im_end|>
<|im_start|>assistant

Шаблон Mistral:

Окно терминала
<s>[INST] You are a helpful assistant. [/INST]
Hi! How can I help you today?</s>
[INST] Hello! [/INST]

Chat-шаблоны умеют больше, чем просто диалог:

  1. Использование инструментов: когда модели нужно обращаться к внешним инструментам или API.
  2. Мультимодальный вход: картинки, звук и другие типы данных.
  3. Вызов функций: структурированный вызов функций.
  4. Многоходовой контекст: хранение истории диалога.

В мультимодальных диалогах шаблон может содержать ссылки на картинки или картинки в base64:

messages = [
{
"role": "system",
"content": "You are a helpful vision assistant that can analyze images.",
},
{
"role": "user",
"content": [
{"type": "text", "text": "What's in this image?"},
{"type": "image", "image_url": "https://example.com/image.jpg"},
],
},
]

А вот пример шаблона с использованием инструментов:

messages = [
{
"role": "system",
"content": "You are an AI assistant that can use tools. Available tools: calculator, weather_api",
},
{"role": "user", "content": "What's 123 * 456 and is it raining in Paris?"},
{
"role": "assistant",
"content": "Let me help you with that.",
"tool_calls": [
{
"tool": "calculator",
"parameters": {"operation": "multiply", "x": 123, "y": 456},
},
{"tool": "weather_api", "parameters": {"city": "Paris", "country": "France"}},
],
},
{"role": "tool", "tool_name": "calculator", "content": "56088"},
{
"role": "tool",
"tool_name": "weather_api",
"content": "{'condition': 'rain', 'temperature': 15}",
},
]

Работая с chat-шаблонами, придерживайтесь таких практик:

  1. Единый формат: во всём приложении используйте один и тот же формат шаблона.
  2. Ясные роли: у каждого сообщения чётко указывайте роль (system, user, assistant, tool).
  3. Управление контекстом: храня историю диалога, помните об ограничении на число токенов.
  4. Обработка ошибок: предусмотрите обработку ошибок при вызове инструментов и мультимодальном входе.
  5. Проверка: проверяйте структуру сообщений перед отправкой модели.

Потренируемся применять chat-шаблоны на реальном примере.

Ноутбук к разделу: Google Colab.

Supervised Fine-Tuning (SFT) в первую очередь нужен, чтобы научить предобученную языковую модель следовать инструкциям, вести диалог и выдавать ответ в нужном формате. У предобученных моделей впечатляющие общие способности, а SFT превращает их в ассистентов, которые лучше понимают запросы пользователя и отвечают на них. Обычно для этого модель обучают на датасетах с диалогами и инструкциями, написанными людьми.

На этой странице — пошаговое руководство по дообучению модели deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B с помощью SFTTrainer. Следуя этим шагам, вы сможете приспособить модель к своим задачам.

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

Если вы решили, что SFT нужен, дальше решение зависит от двух главных факторов.

SFT позволяет точно управлять структурой ответа модели. Это особенно ценно, когда нужно, чтобы модель:

  1. отвечала в определённом формате chat-шаблона;
  2. соблюдала строгие схемы вывода;
  3. сохраняла единый стиль ответов.

В специализированных областях SFT помогает подстроить модель под требования домена:

  1. научить терминологии и понятиям предметной области;
  2. соблюдать профессиональные стандарты;
  3. правильно обрабатывать технические запросы;
  4. следовать отраслевым правилам.

Для SFT нужен датасет под задачу, состоящий из пар «вход — выход». Каждая пара содержит:

  1. входной промпт;
  2. ожидаемый ответ модели;
  3. дополнительный контекст или метаданные, если есть.

Качество обучающих данных — залог успешного дообучения. (В оригинале здесь встроен просмотрщик датасета HuggingFaceTB/smoltalk: каждая строка — список сообщений messages с ролями user и assistant.)

Успех дообучения сильно зависит от параметров обучения. Разберём важные параметры SFTTrainer и их назначение.

  1. Длительность обучения:

    • num_train_epochs — задаёт общую длительность обучения в эпохах;
    • max_steps — альтернатива эпохам: максимальное число шагов обучения;
    • больше эпох — лучше усвоение, но выше риск переобучения.
  2. Размер батча:

    • per_device_train_batch_size — определяет расход памяти и стабильность обучения;
    • gradient_accumulation_steps — позволяет получить больший эффективный батч;
    • большие батчи дают более стабильные градиенты, но требуют больше памяти.
  3. Скорость обучения:

    • learning_rate — задаёт размер шага обновления весов;
    • warmup_ratio — доля обучения, отведённая на разогрев (warmup) скорости обучения;
    • слишком высокая — неустойчивость, слишком низкая — медленное обучение.
  4. Мониторинг:

    • logging_steps — как часто записывать метрики;
    • eval_steps — как часто оценивать на валидационных данных;
    • save_steps — как часто сохранять чекпоинты.

Ключевые компоненты понятны — реализуем обучение с проверкой и мониторингом. Возьмём класс SFTTrainer из библиотеки TRL (Transformers Reinforcement Learning), построенной поверх transformers. Вот полный пример:

from datasets import load_dataset
from transformers import AutoModelForCausalLM, AutoTokenizer # примечание переводчика: в оригинале этого импорта нет
from trl import SFTConfig, SFTTrainer, setup_chat_format # примечание переводчика: в оригинале нет setup_chat_format
import torch
# Выбираем устройство
device = "cuda" if torch.cuda.is_available() else "cpu"
# Загружаем датасет
dataset = load_dataset("HuggingFaceTB/smoltalk", "all")
# Настраиваем модель и токенизатор
model_name = "HuggingFaceTB/SmolLM2-135M"
model = AutoModelForCausalLM.from_pretrained(pretrained_model_name_or_path=model_name).to(
device
)
tokenizer = AutoTokenizer.from_pretrained(pretrained_model_name_or_path=model_name)
# Добавляем chat-шаблон
model, tokenizer = setup_chat_format(model=model, tokenizer=tokenizer)
# Настраиваем тренер
training_args = SFTConfig(
output_dir="./sft_output",
max_steps=1000,
per_device_train_batch_size=4,
learning_rate=5e-5,
logging_steps=10,
save_steps=100,
eval_strategy="steps",
eval_steps=50,
)
# Создаём тренер
trainer = SFTTrainer(
model=model,
args=training_args,
train_dataset=dataset["train"],
eval_dataset=dataset["test"],
processing_class=tokenizer,
)
# Запускаем обучение
trainer.train()

SFTTrainer поддерживает упаковку (packing) примеров для ускорения обучения: несколько коротких примеров укладываются в одну входную последовательность, и GPU загружен полностью. Чтобы включить упаковку, передайте packing=True в конструктор SFTConfig. Если вы используете упакованный датасет вместе с max_steps, учтите: в зависимости от настроек упаковки вы можете пройти больше эпох, чем ожидали. Как именно объединять примеры, можно задать функцией форматирования — это особенно полезно для датасетов с несколькими полями, например пар «вопрос — ответ». Для валидационного датасета упаковку можно отключить параметром eval_packing=False в SFTConfig. Простой пример настройки упаковки:

# Включаем упаковку
training_args = SFTConfig(packing=True)
trainer = SFTTrainer(model=model, train_dataset=dataset, args=training_args)
trainer.train()

Если в датасете несколько полей, напишите свою функцию форматирования, которая склеит их в одну входную последовательность. Она принимает пример и возвращает строку. Пример такой функции:

def formatting_func(example):
text = f"### Question: {example['question']}\n ### Answer: {example['answer']}"
return text
training_args = SFTConfig(packing=True)
trainer = SFTTrainer(
"facebook/opt-350m",
train_dataset=dataset,
args=training_args,
formatting_func=formatting_func,
)

Грамотный мониторинг — залог успешного дообучения. Разберём, на что смотреть во время обучения.

Loss на обучении обычно проходит три фазы:

  1. Резкое падение в начале: модель быстро подстраивается под новое распределение данных.
  2. Постепенная стабилизация: обучение замедляется — модель уточняет детали.
  3. Сходимость: значения loss выходят на плато — обучение можно заканчивать.

(В оригинале — график loss при обучении SFTTrainer: обе кривые, на обучении и на валидации, сначала круто падают, затем плавно выходят на почти горизонтальный участок, оставаясь близко друг к другу.)

Хороший мониторинг — это и количественные метрики, и качественная оценка ответов. Доступные метрики:

  • loss на обучении;
  • loss на валидации;
  • изменение скорости обучения;
  • нормы градиентов.

По ходу обучения кривая loss должна постепенно выравниваться. Главный признак здорового обучения — небольшой разрыв между loss на обучении и на валидации: модель учит обобщаемые закономерности, а не запоминает конкретные примеры. Абсолютные значения loss зависят от задачи и датасета.

На графике выше — типичный ход обучения. Обратите внимание: loss на обучении и валидации сначала резко падают, а потом постепенно выходят на плато. Такая картина означает, что модель учится эффективно и сохраняет способность к обобщению.

Некоторые формы кривых loss указывают на проблемы. Ниже — типичные тревожные признаки и что с ними делать.

(Рисунок 1 в оригинале: loss на валидации снижается заметно медленнее, чем на обучении, и кривые расходятся.)

Если loss на валидации падает заметно медленнее, чем на обучении, модель, скорее всего, переобучается. Попробуйте:

  • уменьшить число шагов обучения;
  • увеличить датасет;
  • проверить качество и разнообразие датасета.

(Рисунок 2: обе кривые почти горизонтальны, loss почти не снижается.)

Если loss заметно не улучшается, возможно, модель:

  • учится слишком медленно (попробуйте увеличить скорость обучения);
  • не справляется с задачей (проверьте качество данных и сложность задачи);
  • упёрлась в ограничения архитектуры (попробуйте другую модель).

(Рисунок 3: loss на обучении падает почти до нуля.)

Крайне низкий loss может означать запоминание вместо обучения. Это особенно тревожно, если:

  • модель плохо справляется с новыми похожими примерами;
  • ответам не хватает разнообразия;
  • ответы слишком похожи на обучающие примеры.

Учтите: интерпретация loss выше рассчитана на самый частый случай. На деле loss может вести себя по-разному в зависимости от модели, датасета, параметров обучения и так далее. Если хотите глубже разобраться в описанных картинах, почитайте заметку исследователей Fast AI.

В разделе 11.5 мы научимся оценивать модель на бенчмарках. А пока сосредоточимся на качественной оценке.

После SFT сделайте следующее:

  1. тщательно оцените модель на отложенных тестовых данных;
  2. проверьте, соблюдает ли она шаблон на разных входах;
  3. проверьте, сохранились ли знания предметной области;
  4. следите за метриками в реальной работе.
Тест к разделу 11.35 вопросов
1 Какие параметры задают длительность обучения в SFT?
2 Какая картина кривых loss указывает на возможное переобучение?
3 Для чего нужен gradient_accumulation_steps?
4 За чем нужно следить во время SFT?
5 Что говорит о здоровой сходимости?

Вы научились дообучать модели методом SFT! Чтобы продолжить:

  1. запустите ноутбук с другими параметрами;
  2. поэкспериментируйте с другими датасетами;
  3. предложите улучшения материалов курса.

Ноутбук к разделу: Google Colab.

Дообучение больших языковых моделей требует много ресурсов. LoRA — техника, которая позволяет дообучать большие модели, обучая лишь малое число параметров. Она добавляет к весам внимания меньшие матрицы и оптимизирует только их; обычно это сокращает число обучаемых параметров примерно на 90%.

LoRA (Low-Rank Adaptation) — техника параметрически-эффективного дообучения: она замораживает веса предобученной модели и встраивает в её слои обучаемые матрицы низкоранговой декомпозиции. Вместо того чтобы обучать все параметры, LoRA раскладывает обновление весов на меньшие матрицы через низкоранговое разложение. Это резко уменьшает число обучаемых параметров при сохранении качества. Например, на GPT-3 175B LoRA сократила число обучаемых параметров в 10 000 раз, а требования к памяти GPU — в 3 раза по сравнению с полным дообучением. Подробнее — в статье о LoRA.

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

  1. Экономия памяти:

    • в памяти GPU хранятся градиенты и состояния оптимизатора только для параметров адаптера;
    • веса базовой модели заморожены, и их можно загрузить в пониженной точности;
    • большие модели можно дообучать на потребительских GPU.
  2. Возможности обучения:

    • встроенная интеграция PEFT/LoRA с минимальной настройкой;
    • поддержка QLoRA (LoRA поверх квантованной модели) для ещё большей экономии памяти.
  3. Управление адаптерами:

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

PEFT — библиотека с единым интерфейсом для загрузки и управления методами параметрически-эффективного дообучения, включая LoRA. С ней легко загружать разные методы PEFT и переключаться между ними, экспериментируя с техниками дообучения.

Адаптеры загружают поверх предобученной модели методом load_adapter() — это удобно, чтобы пробовать разные адаптеры, не сливая их веса. Активный адаптер выбирают функцией set_adapter(). Чтобы вернуться к базовой модели, можно вызвать unload() — он выгрузит все модули LoRA. Так легко переключаться между весами для разных задач.

from transformers import AutoModelForCausalLM # примечание переводчика: в оригинале этого импорта нет
from peft import PeftModel, PeftConfig
config = PeftConfig.from_pretrained("ybelkada/opt-350m-lora")
model = AutoModelForCausalLM.from_pretrained(config.base_model_name_or_path)
lora_model = PeftModel.from_pretrained(model, "ybelkada/opt-350m-lora")

(В оригинале — схема: к замороженной матрице весов параллельно подключены две маленькие обучаемые матрицы A и B, их произведение прибавляется к выходу слоя.)

SFTTrainer из trl умеет работать с адаптерами LoRA через библиотеку PEFT. Значит, модель можно дообучать так же, как в разделе про SFT, но с LoRA — и обучать гораздо меньше параметров.

В примере мы используем класс LoraConfig из PEFT. Настройка занимает всего несколько шагов:

  1. задать конфигурацию LoRA (ранг, alpha, dropout);
  2. создать SFTTrainer с конфигурацией PEFT;
  3. обучить и сохранить веса адаптера.

Разберём конфигурацию LoRA и её ключевые параметры.

ПараметрОписание
r (ранг)Размерность матриц низкого ранга для обновления весов. Обычно от 4 до 32. Меньше — сильнее сжатие, но, возможно, меньше выразительность.
lora_alphaМасштабирующий множитель слоёв LoRA; обычно ставят вдвое больше ранга. Больше — сильнее эффект адаптации.
lora_dropoutВероятность dropout в слоях LoRA, обычно 0.05–0.1. Больше — сильнее защита от переобучения.
biasОбучать ли смещения (bias). Варианты: "none", "all", "lora_only". Чаще всего ставят "none" ради экономии памяти.
target_modulesК каким модулям модели применять LoRA. Можно "all-linear" или конкретные модули, например "q_proj,v_proj". Больше модулей — больше гибкости, но и больше памяти.

Методы PEFT можно сочетать с TRL, чтобы снизить требования к памяти. LoraConfig передаётся при создании тренера.

from peft import LoraConfig
# r: ранг матриц обновления LoRA (меньше — сильнее сжатие)
rank_dimension = 6
# lora_alpha: масштабирующий множитель слоёв LoRA (больше — сильнее адаптация)
lora_alpha = 8
# lora_dropout: вероятность dropout в слоях LoRA (помогает против переобучения)
lora_dropout = 0.05
peft_config = LoraConfig(
r=rank_dimension, # ранг — обычно от 4 до 32
lora_alpha=lora_alpha, # множитель LoRA — обычно вдвое больше ранга
lora_dropout=lora_dropout, # вероятность dropout в слоях LoRA
bias="none", # как обучать смещения в LoRA ("none" — не обучать)
target_modules="all-linear", # к каким модулям применять LoRA
task_type="CAUSAL_LM", # тип задачи для архитектуры модели
)

Выше мы использовали device_map="auto", чтобы модель автоматически оказалась на нужном устройстве. Можно и вручную указать устройство: device_map={"": device_index}.

Нужно также создать SFTTrainer с конфигурацией LoRA.

# Создаём SFTTrainer с конфигурацией LoRA
trainer = SFTTrainer(
model=model,
args=args,
train_dataset=dataset["train"],
peft_config=peft_config, # конфигурация LoRA
max_seq_length=max_seq_length, # максимальная длина последовательности
processing_class=tokenizer,
)

После обучения с LoRA веса адаптера удобно слить с базовой моделью — так проще развёртывать. Получится одна модель с объединёнными весами, и при инференсе не придётся отдельно загружать адаптеры.

При слиянии внимательно следите за памятью и точностью. Базовая модель и веса адаптера загружаются одновременно, поэтому убедитесь, что памяти GPU или CPU хватает. С device_map="auto" библиотека transformers сама подберёт устройство под ваше железо.

Сохраняйте одну и ту же точность (например, float16) на всём пути: такую же, как при обучении, и в том же формате сохраняйте слитую модель для развёртывания.

Обучив адаптер LoRA, слейте его веса с базовой моделью так:

import torch
from transformers import AutoModelForCausalLM
from peft import PeftModel
# 1. Загружаем базовую модель
base_model = AutoModelForCausalLM.from_pretrained(
"base_model_name", torch_dtype=torch.float16, device_map="auto"
)
# 2. Загружаем модель PEFT с адаптером
peft_model = PeftModel.from_pretrained(
base_model, "path/to/adapter", torch_dtype=torch.float16
)
# 3. Сливаем веса адаптера с базовой моделью
merged_model = peft_model.merge_and_unload()

Если размеры сохранённой модели не сходятся, убедитесь, что сохраняете и токенизатор:

from transformers import AutoTokenizer # примечание переводчика: в оригинале этого импорта нет
# Сохраняем и модель, и токенизатор
tokenizer = AutoTokenizer.from_pretrained("base_model_name")
merged_model.save_pretrained("path/to/save/merged_model")
tokenizer.save_pretrained("path/to/save/merged_model")

Дообучив модель через SFT или LoRA SFT, её нужно оценить на стандартных бенчмарках. Инженеру машинного обучения стоит держать набор подходящих тестов для своей предметной области. На этой странице мы разберём самые распространённые бенчмарки и как с их помощью оценивать модель, а также как сделать собственные бенчмарки под свою задачу.

Автоматические бенчмарки — стандартизированные инструменты оценки языковых моделей на разных задачах и способностях. Это полезная отправная точка, но важно помнить, что они — лишь одна часть полноценной стратегии оценки.

Обычно автоматический бенчмарк — это отобранный датасет с заранее заданными задачами и метриками. Бенчмарки проверяют разные стороны способностей модели: от базового понимания языка до сложных рассуждений. Главное их преимущество — стандартизация: они позволяют одинаково сравнивать разные модели и дают воспроизводимые результаты.

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

MMLU (Massive Multitask Language Understanding) проверяет знания по 57 предметам — от естественных наук до гуманитарных. Он широк, но может не отражать глубину экспертизы, нужную в конкретной области. TruthfulQA проверяет склонность модели повторять распространённые заблуждения, хотя и не охватывает всех видов дезинформации.

BBH (Big Bench Hard) и GSM8K нацелены на сложные рассуждения. BBH проверяет логическое мышление и планирование, а GSM8K — именно решение математических задач. Они помогают оценить аналитические способности, но могут не уловить тонких рассуждений, которые нужны в реальных сценариях.

HELM — фреймворк целостной оценки. Бенчмарки вроде HELM показывают, как модель обрабатывает язык в аспектах здравого смысла, знаний о мире и рассуждений. Но они могут не полностью отражать сложность живого разговора или терминологию конкретной области.

Посмотрим на несколько бенчмарков для конкретных областей: математики, программирования и чата.

MATH — ещё один важный инструмент оценки математических рассуждений. Это 12 500 задач математических олимпиад по алгебре, геометрии, теории чисел, комбинаторике, теории вероятностей и не только. Особая трудность MATH в том, что он требует многошаговых рассуждений, понимания формальной математической записи и умения выдавать пошаговое решение. В отличие от простой арифметики, задачи MATH часто требуют изощрённых стратегий и применения математических понятий.

HumanEval — датасет для оценки программирования из 164 задач. Он проверяет, умеет ли модель писать функционально правильный код на Python, решающий поставленную задачу. Ценность HumanEval в том, что он проверяет и генерацию кода, и его правильность — настоящим запуском тестов, а не поверхностным сходством с эталонным решением. Задачи — от простой работы со строками до более сложных алгоритмов и структур данных.

Alpaca Eval — фреймворк автоматической оценки моделей, следующих инструкциям. Судьёй выступает GPT-4: он оценивает ответы по нескольким измерениям, включая полезность, честность и безвредность. В фреймворке 805 тщательно отобранных промптов, а ответы можно сравнивать с несколькими эталонными моделями — Claude, GPT-4 и другими. Польза Alpaca Eval в том, что он даёт стабильную масштабируемую оценку без разметчиков-людей и при этом улавливает тонкие стороны качества, которые пропускают традиционные метрики.

Многие организации разработали альтернативные методы оценки, чтобы обойти ограничения стандартных бенчмарков.

Всё популярнее подход, когда одна языковая модель оценивает ответы другой. Он даёт более тонкую обратную связь, чем традиционные метрики, но у него свои перекосы (bias) и ограничения. Подробно — в уроке 7.3.2.

Арены вроде Chatbot Arena оценивают LLM через отзывы толпы. На таких платформах пользователи устраивают анонимные «бои» двух LLM: задают вопросы и голосуют, чья модель ответила лучше. Так улавливаются реальные сценарии использования и предпочтения на разнообразных и трудных вопросах; исследования показывают сильное согласие голосов толпы с оценками экспертов. Но у таких платформ есть ограничения: возможный перекос в составе пользователей, смещённое распределение промптов и упор на полезность, а не на безопасность.

Организации часто разрабатывают внутренние наборы бенчмарков под свои нужды и сценарии. Это могут быть тесты знаний предметной области или сценарии оценки, повторяющие реальные условия развёртывания.

Стандартные бенчмарки дают полезную точку отсчёта, но не должны быть единственным методом оценки. Вот как построить более полный подход:

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

  2. Выясните требования и трудности своего сценария. Какие задачи модель реально будет решать? Какие ошибки будут самыми болезненными?

  3. Соберите собственные датасеты для оценки, отражающие ваш реальный сценарий. В них могут войти:

    • реальные запросы пользователей из вашей области;
    • типичные краевые случаи, с которыми вы сталкивались;
    • примеры особенно трудных ситуаций.
  4. Подумайте о многоуровневой стратегии оценки:

    • автоматические метрики для быстрой обратной связи;
    • оценка людьми для тонкого понимания;
    • проверка экспертами для специализированных применений;
    • A/B-тесты в контролируемых условиях.

В этом разделе мы реализуем оценку дообученной модели. Для стандартных бенчмарков возьмём lighteval — в нём встроено множество задач. Нужно лишь указать, какие задачи оценивать, и задать параметры оценки.

Задачи LightEval описываются в таком формате:

{suite}|{task}|{num_few_shot}|{auto_reduce}
ПараметрОписание
suiteНабор бенчмарков (например, mmlu, truthfulqa)
taskКонкретная задача внутри набора (например, abstract_algebra)
num_few_shotСколько примеров включить в промпт (0 — zero-shot)
auto_reduceУменьшать ли автоматически число примеров, если промпт слишком длинный (0 или 1)

Пример: "mmlu|abstract_algebra|0|0" — оценка на задаче абстрактной алгебры из MMLU без примеров в промпте.

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

Вот полный пример оценки на автоматических бенчмарках одной предметной области с помощью Lighteval и бэкенда VLLM:

Окно терминала
lighteval accelerate \
"pretrained=your-model-name" \
"mmlu|anatomy|0|0" \
"mmlu|high_school_biology|0|0" \
"mmlu|high_school_chemistry|0|0" \
"mmlu|professional_medicine|0|0" \
--max_samples 40 \
--batch_size 1 \
--output_path "./results" \
--save_generations true

Результаты выводятся таблицей:

| Task |Version|Metric|Value | |Stderr|
|----------------------------------------|------:|------|-----:|---|-----:|
|all | |acc |0.3333|± |0.1169|
|leaderboard:mmlu:_average:5 | |acc |0.3400|± |0.1121|
|leaderboard:mmlu:anatomy:5 | 0|acc |0.4500|± |0.1141|
|leaderboard:mmlu:high_school_biology:5 | 0|acc |0.1500|± |0.0819|

У Lighteval есть и Python API для более детальной оценки: с ним удобнее гибко обрабатывать результаты. Подробнее — в документации Lighteval.

Тест в конце главы 116 вопросов
1 В чём главные преимущества автоматических бенчмарков?
2 Какой бенчмарк проверяет знания по 57 разным предметам?
3 Что такое LLM-as-Judge?
4 Что должно входить в полноценную стратегию оценки?
5 В чём ограничение автоматических бенчмарков?
6 Зачем создавать собственные датасеты для оценки?

В этой главе мы разобрали ключевые составляющие дообучения языковых моделей.

  1. Chat-шаблоны задают структуру общения с моделью и обеспечивают единообразные и уместные ответы благодаря стандартному оформлению.

  2. Обучение с учителем (SFT) приспосабливает предобученную модель к конкретным задачам, сохраняя её базовые знания.

  3. LoRA делает дообучение эффективным: число обучаемых параметров уменьшается, а качество модели сохраняется.

  4. Оценка помогает измерить и подтвердить эффективность дообучения с помощью разных метрик и бенчмарков.

Вместе эти техники позволяют создавать специализированные языковые модели, которые отлично решают конкретные задачи и при этом остаются экономными в вычислениях. Строите ли вы бота поддержки или ассистента для своей области — понимание этих понятий необходимо для успешной адаптации модели.

Пора проверить знания! Мы подготовили короткий тест по понятиям этой главы.

Чтобы пройти тест:

  1. войдите в учётную запись Hugging Face;
  2. ответьте на вопросы;
  3. отправьте ответы.

Тест с выбором ответа. Нужно выбрать правильный ответ из списка. Проверяются основы обучения с учителем: huggingface-course-supervised-finetuning-quiz.hf.space.

Тест по коду. Нужно написать код для решения задачи. Проверяется код из курса на библиотеках datasets, transformers, peft и TRL: huggingface-course-sft-code-quiz.hf.space.

(В оригинале оба теста встроены в страницу; вопросы в них на английском.)

  1. Шаблоны. Загрузите токенизаторы HuggingFaceTB/SmolLM2-135M-Instruct и Qwen/Qwen2.5-0.5B-Instruct, примените apply_chat_template к одному и тому же диалогу на русском и китайском и сравните строки и число токенов.
  2. SFT за 10 минут. Запустите пример из 11.3 на 2000 примерах smoltalk и модели SmolLM2-135M-Instruct. Сравните ответы до и после на трёх своих вопросах.
  3. LoRA. Повторите то же с peft_config из 11.4 и посмотрите model.print_trainable_parameters(): какая доля параметров обучается при r=6 и target_modules="all-linear"?
  4. Оценка. Прогоните lighteval на 2–3 задачах MMLU до и после дообучения и сравните разницу с колонкой Stderr.
EnglishРусский
chat templatechat-шаблон
base / instruct modelбазовая / instruct-модель
supervised fine-tuning (SFT)обучение с учителем на инструкциях
packingупаковка примеров
warmupразогрев скорости обучения
gradient accumulationнакопление градиентов
overfitting / underfittingпереобучение / недообучение
low-rank adaptation (LoRA)низкоранговая адаптация
adapter, merge adapterадаптер, слить адаптер с моделью
parameter-efficient fine-tuning (PEFT)параметрически-эффективное дообучение
benchmarkбенчмарк
zero-shot / few-shotбез примеров / с несколькими примерами в промпте
LLM-as-a-judgeLLM-судья