Dataset curation: 80% of the work
Where examples come from, why a thousand curated ones beat a hundred thousand random ones, chat templates and loss on responses only — and a filtering pipeline with a held-out split before training starts.
Lesson content is currently available in Russian.
What you will learn
- →Собирать примеры из продакшен-логов, экспертной разметки и фильтрованной синтетики.
- →Форматировать данные chat-шаблоном целевой модели и считать loss только по токенам ответа.
- →Строить конвейер фильтрации: дедупликация, деконтаминация, проверка длин, held-out сплит.
Качество бьёт количество
Интуиция «чем больше данных, тем лучше» в SFT работает против вас. Работа LIMA показала: 65B-модель, дообученная всего на 1000 тщательно отобранных примеров, конкурирует с моделями, выровненными на порядки большими объёмами. Причина в том, что SFT не добавляет знаний — он показывает модели формат и стандарт ответа. Тысячи посредственных примеров учат посредственности с той же прилежностью, с какой сотни отличных учат отличному: модель усвоит и ваши канцеляризмы, и ошибки разметчиков, и однообразие формулировок.
Для узкого навыка рабочий диапазон — 500–5000 примеров. Важнее объёма два свойства. Первое — качество каждого примера: ответ в датасете должен быть ровно таким, какой вы хотите видеть в проде, без «сойдёт». Правка одного плохого примера ценнее добавления сотни средних. Второе — разнообразие: примеры должны покрывать реальные кластеры сценариев, включая граничные случаи и корректные отказы. Датасет из одних счастливых путей даст модель, которая уверенно галлюцинирует на всём остальном.
Практическое следствие: бюджет времени распределяется как 80/20 в пользу данных. Команды, которые неделями крутят гиперпараметры на сыром датасете, получают стабильно худший результат, чем те, кто один раз прогнал обучение с дефолтами, но трижды переработал данные по итогам чтения генераций.
Три источника примеров и фильтрация
Примеры берутся из трёх источников, у каждого свой профиль риска:
- Продакшен-логи — лучший источник: реальное распределение входов. Отберите диалоги с положительным сигналом (оценка пользователя, доведённая до конца задача) и перепишите слабые ответы до эталона.
- Экспертная разметка — дорого, но это золотой стандарт для ядра датасета и всех граничных случаев: отказов, эскалаций, редких сценариев.
- Синтетика от сильной модели — дёшево и быстро масштабируется, но даёт однообразие формулировок и наследует чужие паттерны. Синтетику всегда пропускайте через фильтр: LLM-судья по рубрике плюс ручная выборочная проверка 10%.
Дальше конвейер фильтрации. Дедупликация: точные и почти-дубликаты (MinHash или косинус эмбеддингов выше 0,95) выбрасываются — сто копий одного сценария смещают модель к нему. Деконтаминация: ни один пример не должен пересекаться с будущим eval-набором, иначе оценка превратится в проверку памяти. Проверка длины: распределение длин ответов в датасете — это распределение длин, которые модель будет выдавать; аномально длинные и короткие хвосты режутся осознанно.
И главное правило: held-out набор откладывается до обучения, из того же распределения, и не трогается ни при какой доработке датасета. Это ваша единственная честная линейка на весь проект.
Chat-шаблон: невидимая причина сломанных моделей
Инструктная модель обучена не на сыром тексте, а на диалогах, размеченных служебными токенами. У Qwen и многих других это ChatML: сообщение оборачивается в <|im_start|>role … <|im_end|>. У Llama 3 — свой формат с <|start_header_id|>. Токенизатор знает правильную разметку: tokenizer.apply_chat_template(messages) превращает список сообщений с ролями system/user/assistant в точную строку обучения.
Здесь закопаны три классических провала. Первый — самодельный шаблон: если при обучении склеивать диалоги «своим» форматом, а на инференсе использовать стандартный (или наоборот), модель тихо деградирует — без ошибок в логах, просто хуже отвечает. Шаблон обязан побайтово совпадать между обучением и инференсом. Второй — обучение на токенах промпта: если loss считается по всей последовательности, половина ёмкости обучения уходит на имитацию вопросов пользователя. Нужен режим completion-only: в TRL это assistant_only_loss или collator, маскирующий всё до ответа ассистента. Третий — потерянный EOS: если примеры не заканчиваются токеном конца последовательности, модель не научится останавливаться и будет генерировать до лимита.
Перед запуском обучения распечатайте три-пять примеров после apply_chat_template и прочитайте их глазами: роли на месте, спецтокены правильные, EOS в конце. Эти пять минут экономят GPU-часы на переобучение.
JSONL и проверка перед стартом
Канонический формат хранения — JSONL, по одному диалогу на строку:
{"messages": [{"role": "system", "content": "Ты — ревьюер..."}, {"role": "user", "content": "..."}, {"role": "assistant", "content": "..."}]}
Минимальный скрипт проверки перед обучением:
from datasets import load_dataset
from transformers import AutoTokenizer
ds = load_dataset("json", data_files="train.jsonl", split="train")
tok = AutoTokenizer.from_pretrained("Qwen/Qwen2.5-7B-Instruct")
for i in range(3):
print(tok.apply_chat_template(ds[i]["messages"], tokenize=False))
lengths = [len(tok.apply_chat_template(m, tokenize=True)) for m in ds["messages"]]
print(max(lengths)) # влезает ли в max_seq_length?
Сплит: отложите 10% (или минимум 50–100 примеров) в holdout.jsonl до первого запуска обучения, стратифицированно по типам сценариев. Проверьте пересечения: ни один пример held-out не должен встречаться в train даже в перефразированном виде — прогоните поиск ближайших соседей по эмбеддингам и просмотрите топ совпадений руками.
Итоговый чек-лист перед стартом: дубликаты удалены, шаблон совпадает с целевой моделью, loss только по ответам, EOS на месте, held-out отложен, максимальная длина не превышает контекст обучения.
Practice
Датасет на 300 примеров
Соберите датасет для узкого навыка из вашего домена: минимум два источника (логи или ручные примеры плюс отфильтрованная синтетика), конвейер дедупликации и деконтаминации, форматирование chat-шаблоном целевой модели.
Deliverable
Файлы train.jsonl и holdout.jsonl, скрипт фильтрации и отчёт: сколько примеров отсеяно на каждом шаге конвейера и почему.
Checkpoint
Покажите свой конвейер подготовки данных и обоснуйте каждый шаг фильтрации.
- ✓ Датасет прошёл дедупликацию и деконтаминацию против held-out набора.
- ✓ Примеры отформатированы chat-шаблоном целевой модели, loss считается только по токенам ответа.
- ✓ Held-out отложен до обучения и не пересекается с train ни по одному примеру.
Check yourself
Sources