Через полгода вы захотите повторить обучение на дополненном датасете. И обнаружите, что не помните, какой была длина последовательности, сливали ли вы адаптер и почему ранг был именно такой. Скрипт вы правили по ходу дела, а история правок не сохранилась.
Это и есть задача, которую решает Axolotl: обучение описывается файлом настроек, файл лежит в системе контроля версий, и через полгода вы запускаете ровно тот же эксперимент.
#Зачем это нужно и когда не нужно
Нужно, если прогонов будет несколько и вы собираетесь сравнивать их между собой. Нужно, если обучение запускает не тот человек, который его настраивал. Не нужно для одного разового эксперимента на одной карте — там быстрее написать скрипт, и инструмент под этот сценарий разобран в гайде по Unsloth.
Общая рамка маршрута — что вообще даёт дообучение и чего от него ждать — в пилларе про свою модель и дообучение под себя.
#Что понадобится
- Отдельное окружение с CUDA-сборкой PyTorch. Ставить рядом с рабочим приложением не стоит.
- Датасет в JSONL, одна запись на строку, схема согласована со значением поля
typeв конфигурации. - Файл конфигурации — его мы и разберём.
- Система контроля версий для этого файла. Без неё смысл подхода теряется наполовину.
#Шаг 1. Конфигурация по строкам
Минимальный рабочий файл для QLoRA. Комментарии объясняют, что делает каждый блок.
# --- база ---
base_model: Qwen/Qwen2.5-1.5B-Instruct
load_in_4bit: true # QLoRA; false = обычная LoRA, если база влезает в 16 битах
# --- данные ---
datasets:
- path: data/persona.jsonl
type: chat_template # схема записи; должна соответствовать формату файла
field_messages: messages
val_set_size: 0.1 # отложенная выборка отделяется автоматически
dataset_prepared_path: last_prepared
# --- адаптер ---
adapter: qlora
lora_r: 16 # ёмкость адаптера
lora_alpha: 32 # обычно 2 * lora_r
lora_dropout: 0.05
lora_target_linear: true # адаптеры на все линейные слои
# --- режим обучения ---
sequence_len: 1024 # главный рычаг расхода памяти после размера базы
sample_packing: false # упаковка примеров; на маленьком наборе только мешает
micro_batch_size: 1
gradient_accumulation_steps: 16 # эффективный батч = 1 * 16
num_epochs: 1 # начинайте с одной
learning_rate: 0.0002
lr_scheduler: cosine
warmup_ratio: 0.03
optimizer: paged_adamw_8bit # экономит память на скромной карте
gradient_checkpointing: true
# --- вывод ---
output_dir: ./out/persona-lora
save_strategy: epoch
logging_steps: 10
seed: 42 # без него повторить прогон нельзя
Три строки здесь важнее остальных.
seed. Без фиксированного зерна два запуска одной и той же конфигурации дадут разные адаптеры, и сравнивать их бессмысленно. Это первое, что забывают, и первое, что ломает воспроизводимость.
sequence_len. Определяет расход памяти сильнее всего, кроме размера базы. Если обучение падает по памяти, уменьшать надо здесь, а не в размере батча — он уже равен единице.
type в блоке данных. Задаёт, как читается ваш файл. Несовпадение схемы файла и заявленного типа — самая частая причина того, что обучение идёт, а результат бессмысленный: часть полей просто не доехала. Разбор схем — в статье про форматы обучающих данных.
#Шаг 2. Проверка подготовки данных
Отдельный шаг, ради которого стоит терпеть конфигурационный подход: подготовку данных можно выполнить до обучения и посмотреть на результат.
axolotl preprocess config.yml
Команда рендерит датасет шаблоном базовой модели и складывает подготовленный набор в dataset_prepared_path. Откройте первый пример и прочитайте его глазами: вы должны увидеть служебные маркеры ролей и текст на своих местах. Пять секунд, которые ловят большинство проблем.
#Шаг 3. Запуск
axolotl train config.yml
Первый запуск делайте на урезанном датасете — двадцать примеров и num_epochs: 1. Он займёт минуты и покажет все ошибки конфигурации. Только после этого ставьте полный набор.
#Шаг 4. Порядок воспроизведения эксперимента
Ради этого раздела и написана статья. Чтобы через полгода получить тот же результат, зафиксируйте пять вещей:
- Файл конфигурации — в систему контроля версий, с осмысленным сообщением коммита. Название эксперимента в имени файла, а не в голове.
- Датасет — версией или хешем. Тот же конфиг на изменившихся данных даст другой результат, и вы будете искать причину в параметрах.
- Версии окружения — вывод
pip freezeрядом с конфигурацией. Библиотеки обучения меняют имена аргументов между версиями чаще, чем хотелось бы. - Зерно —
seedв конфигурации. Без него пункты 1–3 не спасают. - Результат проверки — в тот же каталог. Не «стало лучше», а список вопросов и ответы обеих моделей; протокол в статье про честную оценку дообученной модели.
Пятый пункт нарушают чаще всего. Без записанного результата проверки повторяемый эксперимент бесполезен: вы не сможете сказать, лучше новая версия или хуже.
#Проверка результата
- Подготовленный датасет читается глазами и содержит правильные маркеры ролей.
- Отложенная выборка отделилась —
val_set_sizeсработал, и это видно в логе. - Обучение завершилось, адаптер сохранён в
output_dir— там лежат файлы адаптера, а не копия базовой модели. - Слепое сравнение с базой показывает сдвиг в стиле без просадки на посторонних темах.
- Повторный запуск той же конфигурации даёт тот же результат. Проверьте это хотя бы один раз: именно ради этого свойства вы выбрали конфигурационный подход.
#Что может пойти не так
- Обучение идёт, качество странное. Почти всегда неверный
typeв блоке данных: часть полей не прочиталась. Смотрите подготовленный датасет, а не логи. - Ошибка нехватки памяти. Уменьшайте
sequence_len, потом размер базы.micro_batch_sizeуже равен единице. - Неизвестный ключ конфигурации. Версия инструмента разошлась с примером, который вы скопировали. Сверяйтесь с документацией своей версии, а не с чужой статьёй.
- Два запуска дают разное. Не зафиксирован
seedлибо изменился датасет. - Модель переобучилась.
num_epochsбольше одной на маленьком наборе. Признаки и лечение — в статье про гиперпараметры дообучения. - Адаптеры не встали на нужные слои.
lora_target_linear: true— разумное значение по умолчанию; ручной список слоёв задавайте, только если понимаете, зачем.
Механика самих адаптеров, которую конфигурация только настраивает, разобрана в статье про библиотеку PEFT и устройство адаптеров.
#Частые вопросы
#Чем это лучше обычного скрипта?
Ничем по возможностям и многим по дисциплине. Скрипт вы правите по ходу и не помните, что меняли; конфигурация лежит в репозитории, и разница между двумя экспериментами видна в отличиях файла. Если прогон один — берите скрипт, разницы не будет.
#Можно ли одной конфигурацией обучать разные модели?
Технически да, меняется одна строка base_model. Практически стоит завести отдельный файл на каждый эксперимент: иначе через месяц вы не восстановите, к какой базе относится результат.
#Как задать свой шаблон чата?
Он берётся из токенизатора базовой модели и применяется автоматически. Задавать свой стоит только для «сырых» баз без инструктивного дообучения — и тогда тот же шаблон обязан использоваться на запуске, иначе модель будет вести себя не так, как обучалась.
#Нужна ли отложенная выборка, если датасет маленький?
Да, особенно если он маленький. Десять процентов от трёхсот примеров — это тридцать вопросов, на которых видно, выучила модель манеру или заучила строки. Без отложенных данных вы оцениваете результат по ощущениям.
#Работает ли это на нескольких видеокартах?
Распределённое обучение здесь штатный сценарий, в отличие от инструментов, заточенных под одну карту. Но настройка сложнее, и для первого проекта это лишний источник проблем — начните с одной карты и компактной базы.
#Вывод
Конфигурационный подход не ускоряет обучение и не улучшает результат. Он делает другое: превращает эксперимент в артефакт, который можно повторить, сравнить и передать другому человеку.
Если вы планируете один прогон ради интереса — это лишняя церемония. Если вы собираетесь дорабатывать датасет и сравнивать версии модели между собой, то без зафиксированной конфигурации, датасета, версий и зерна вы через месяц не сможете ответить на простой вопрос: стало лучше или хуже.