Fine-tuning и веса · Fine-tuning and weights · урок 4 · 14 мин

Прогон обучения: TRL, Unsloth, железо, кривые loss

A training run: TRL, Unsloth, hardware, loss curves

ТемыFine-tuningОбучение моделейЖелезо

Зачем

После урока ты запустишь SFT с LoRA в десяток строк, прикинешь, поместится ли модель на твою GPU, и прочитаешь кривые train и eval loss: где остановиться, где переобучение, где сломаны данные. На собеседовании это вопросы «what can you train on a 32 GB GPU?» и «your eval loss goes up — what now?».

Инструменты: кто что делает

Инструмент Для чего
TRL (Hugging Face) trainers для post-training: SFTTrainer, DPOTrainer; эталонная реализация
PEFT LoRA и другие adapters: LoraConfig, слияние, переключение
Unsloth ускоренное обучение на одной GPU; документация TRL пересказывает заявление «up to 2× faster with up to 70% less VRAM» (VRAM — память GPU)
Axolotl обучение по одному YAML-файлу конфигурации, multi-GPU через FSDP2 и DeepSpeed (способы разложить модель и состояния оптимизатора по нескольким GPU)
LLaMA-Factory единая обвязка для многих моделей с веб-интерфейсом

Выбор простой. Чтобы понять механику и иметь эталон — TRL с PEFT. Чтобы выжать одну GPU — Unsloth, он совместим с тем же форматом данных. Чтобы гонять много конфигураций на нескольких GPU и хранить их в git — Axolotl. Все они делают одно и то же: chat template, loss на ответе, LoRA, сохранение adapter (файла обученной добавки).

Минимальный прогон на TRL

Ниже иллюстрация по документации TRL v1.15.0 (открыта 10.10.2026). Данные — два файла JSONL в формате messages (урок a6-3).

import torch
from datasets import load_dataset
from peft import LoraConfig
from trl import SFTConfig, SFTTrainer

data = load_dataset("json", data_files={"train": "train.jsonl", "validation": "val.jsonl"})

trainer = SFTTrainer(
    model="Qwen/Qwen3-8B",
    train_dataset=data["train"],
    eval_dataset=data["validation"],
    peft_config=LoraConfig(r=16, lora_alpha=32, lora_dropout=0.05,
                           target_modules="all-linear", task_type="CAUSAL_LM"),
    args=SFTConfig(
        output_dir="adapters/style-v1",
        model_init_kwargs={"dtype": torch.bfloat16},
        learning_rate=1e-4,
        num_train_epochs=2,
        per_device_train_batch_size=2,
        gradient_accumulation_steps=8,
        max_length=2048,
        assistant_only_loss=True,
        eval_strategy="steps",
        eval_steps=50,
        logging_steps=10,
    ),
)
trainer.train()
trainer.save_model()  # с PEFT сохраняется только adapter

Что здесь не по умолчанию и почему: - model_init_kwargs={"dtype": torch.bfloat16}. Если модель передана строкой, TRL по документации грузит её в float32 (4 байта на вес) — вдвое больше памяти, чем bf16 (2 байта на вес). - learning_rate=1e-4. Скорость обучения по умолчанию в SFTConfig — 2e-5, это под полное дообучение; для adapters TRL советует около 1e-4, гайд Unsloth предлагает стартовать с 2e-4. - per_device_train_batch_size=2 и gradient_accumulation_steps=8 дают effective batch (эффективный размер пакета) 2 × 8 = 16. Накопление градиентов позволяет держать в памяти маленький batch (пакет примеров за шаг). Unsloth рекомендует именно такую стартовую точку. - max_length=2048: по умолчанию 1 024 tokens, длинные диалоги обрезались бы вместе с ответом. - assistant_only_loss=True: loss (ошибка обучения) только на ответах ассистента. - eval_strategy="steps" и eval_steps=50: каждые 50 шагов считается eval loss на validation — без этого ты обучаешь вслепую.

gradient_checkpointing в SFTConfig включён по умолчанию: он не хранит часть activations (промежуточных значений прохода) и пересчитывает их, экономя память ценой времени.

Перед настоящим прогоном полезны две дешёвые проверки. Первая: распечатать один пример после подготовки данных и глазами убедиться, что chat template применён, маска loss закрывает вопрос пользователя, а в конце ответа стоит правильный token конца реплики. Вторая: обучить на 50 примерах и убедиться, что train loss быстро падает почти до нуля. Если модель не может заучить даже 50 примеров, ошибка в коде или данных, и большой прогон её не исправит. Обе проверки занимают минуты, а сэкономить могут ночь обучения на GPU.

Железо: что куда помещается

Грубое правило памяти без activations: полное дообучение с Adam — около 18 байт на параметр, LoRA с базой в bf16 — около 2, QLoRA с базой в 4 битах — около 0,5–0,6. Ориентиры из гайда Unsloth для Qwen3.5, bf16 LoRA (открыт 10.10.2026):

Модель VRAM для bf16 LoRA
Qwen3.5-4B 10 ГБ
Qwen3.5-9B 22 ГБ
Qwen3.5-27B 56 ГБ
Qwen3.5-35B-A3B (MoE) 74 ГБ

Последняя строка — ловушка. MoE (mixture of experts, модель из многих «экспертов», из которых на каждый token работает малая часть) Qwen3.5-35B-A3B имеет 35B параметров всего и 3B активных. Вычисления на token идут как у 3B, но в памяти лежат все 35B: router (маршрутизатор) может выбрать любого эксперта. Поэтому 74 ГБ, почти как у плотной 35B. Формула для интервью: «active parameters set the compute, total parameters set the memory».

Если память кончилась (OOM, out of memory): уменьши per_device_train_batch_size и подними накопление, сократи max_length, проверь, что база в bf16, а не float32. Activations растут с длиной последовательности, поэтому длинные диалоги — частая причина OOM даже тогда, когда веса помещаются.

Как читать кривые loss

TRL пишет в журнал loss (train loss, средняя cross-entropy, то есть мера «удивления» модели правильным token, по tokens ответа за интервал), eval_loss, mean_token_accuracy, grad_norm (норма градиента) и learning_rate. Условный пример: 3 000 примеров, effective batch 16 — это около 188 шагов на эпоху (полный проход по данным), 2 эпохи — около 375 шагов.

Шаг train loss eval loss
50 1,42 1,38
100 1,21 1,25
150 1,10 1,19
200 0,95 1,18
250 0,80 1,21
300 0,66 1,26

До шага 150 обе кривые падают — модель учится. Около шага 190 начинается вторая эпоха, и train loss резко идёт вниз: модель второй раз видит те же примеры и начинает их запоминать. Eval loss после шага 200 растёт — это overfitting (переобучение): модель заучивает train вместо того, чтобы обобщать. Решение — взять checkpoint (сохранённое состояние) около шага 200 (load_best_model_at_end=True, metric_for_best_model="eval_loss", а save_steps поставить равным eval_steps, иначе нужного checkpoint просто не будет) или учить одну эпоху.

Другие узоры: - train loss стоит высоко и не падает — слишком малая скорость обучения, сломанная маска loss или не тот chat template; - скачки, NaN, взрывы grad_norm — слишком большая скорость обучения или битые очень длинные примеры; - loss почти сразу падает к нулю — утечка или дубли, когда модель видит почти те же тексты; гайд Unsloth считает train loss ниже 0,2 признаком вероятного переобучения; - eval loss хороший, а ответы плохие — loss меряет «удивление» tokens, а не формат, тон и безопасность.

Последний пункт главный: кривая loss говорит, когда остановиться, но не говорит, стала ли модель лучше. Это решает оценка против baseline (урок a6-6).

Схема

Термины · Key terms

TRL
библиотека Hugging Face с trainers для SFT, DPO и других методов
PEFT
библиотека adapters, в том числе LoRA
learning rate
скорость обучения, размер шага обновления весов
effective batch size
batch на устройство × шаги накопления градиента
gradient accumulation
сложение градиентов нескольких малых пакетов перед шагом
gradient checkpointing
пересчёт части activations вместо хранения, экономия памяти
epoch
один полный проход по обучающим данным
eval loss
ошибка на validation, которой модель не обучалась
overfitting
переобучение: train loss падает, eval loss растёт
checkpoint
сохранённое состояние модели на шаге обучения
MoE (mixture of experts)
модель из экспертов; память по всем, вычисления по активным

Сделай сам

Посчитай на бумаге для своего будущего прогона: 2 400 примеров, per_device_train_batch_size=4, gradient_accumulation_steps=4, 3 эпохи. Сколько шагов на эпоху и всего? При eval_steps=25 сколько будет точек eval loss? Затем нарисуй от руки две кривые: train падает всё время, eval падает до середины второй эпохи и потом растёт. Отметь шаг, который ты бы взял.

Обсуди с тьютором: покажи расчёт и рисунок; спроси, что ещё, кроме числа эпох, можно поменять, если eval loss начинает расти рано, и в каком порядке это пробовать.

Проверь себя · ответ под вопросом

1Почему Qwen3.5-35B-A3B требует для LoRA почти столько же памяти, сколько плотная 35B, хотя активных параметров всего 3B?
Активные параметры определяют вычисления на token, а память — общее число параметров. Router может выбрать любого эксперта, поэтому в памяти лежат все 35B. Гайд Unsloth даёт для неё 74 ГБ в bf16 LoRA.
2Что означает рост eval loss при падающем train loss и что делать?
Это переобучение: модель запоминает обучающие примеры и хуже обобщает. Берут checkpoint до начала роста, сокращают число эпох, снижают скорость обучения или добавляют данных. Кривая показывает момент остановки, но качество проверяют отдельной оценкой.
3Зачем в примере указан model_init_kwargs={"dtype": torch.bfloat16}?
Когда модель передана строкой, TRL по умолчанию грузит её в float32. Это вдвое больше памяти, чем bf16, и модель на 8B может не поместиться. Явный bf16 возвращает около 2 байт на параметр.

Interview questions · US / UK

1What can you realistically fine-tune on a single 32 GB GPU?
Strong answer: Rules of thumb with assumptions: full fine-tuning about 18 bytes per parameter plus activations, bf16 LoRA about 2, QLoRA about 0.5; so a 9B model in bf16 LoRA fits (Unsloth lists 22 GB), larger dense models only with a 4-bit base if supported; MoE memory follows total, not active, parameters.
2Your train loss keeps falling but eval loss started rising at epoch two. What do you do?
Strong answer: Recognise overfitting; keep the best checkpoint by eval loss; fewer epochs, lower learning rate, more dropout or more varied data; check for duplicates and leakage; then judge quality with the task eval against baseline, not with loss.
3Walk me through the key settings of a LoRA SFT run.
Strong answer: Base in bf16, LoRA rank 16 to 32 with alpha r or 2r on all linear layers, learning rate around 1e-4 to 2e-4, effective batch around 16 through gradient accumulation, 1 to 3 epochs, assistant-only loss, max length sized to the data, periodic eval, and adapter saved with the data hash and config.

Ключевые фразы · Say it aloud

Материалы