Помните времена, когда файнтюнинг был привилегией избранных с кластерами A100? Я тоже.
В 2023-24 годах считалось, что дообучить 7B-модель можно только если у тебя как минимум 24 ГБ VRAM, а для 13B - все 48. И это без батча, на одном примере. Потом пришёл QLoRA и слегка подвинул границы, но всё равно требовал 16-20 ГБ для приличного результата. А теперь смотрите: июнь 2026, но я пишу про 2025-й, потому что именно прошлый год стал переломным. Unsloth, BitsAndBytes 0.43, обновлённый PEFT - стек, который позволяет воткнуть 12B-модель на одну RTX 3090 и получить loss 1.4 за ночь. Без датацентров, без облака, без аренды.
Это не маркетинг. Я сам гонял несколько экспериментов на домашнем ПК с GTX 1080 Ti (да, 11 ГБ, спасибо, что спросили) и Qwen 7B. Работает. Не быстро, но работает. В этой статье я разберу, что изменилось в 2025 году, почему Unsloth стал фактическим стандартом для потребительского железа, и покажу полностью рабочий пайплайн. Никакой магии - только код и битвы с OOM.
Статья рассчитана на тех, кто уже сталкивался с pip install transformers и знает, чем loss отличается от accuracy. Но даже если вы новичок - читайте, я объясню все подводные камни, которые сам выстрадал.
1 Миф первый: "нужен большой GPU, иначе модель не влезет"
На самом деле узкое место не столько размер модели, сколько память для оптимизатора и градиентов. Полный файнтюнинг 7B в fp32 сжирает около 28 ГБ только на веса, плюс градиенты и оптимизатор - под 50-60 ГБ. Но QLoRA решает это двумя трюками:
- Квантование замороженной модели в 4-bit (NF4). Это сразу снижает потребление памяти в 4 раза.
- LoRA-адаптеры обучаются в fp16, а обновления применяются через backward pass к 4-bit тензорам без распаковки.
Вот таблица, чтобы было наглядно (тесты на реальных сессиях с Unsloth 2025.3):
| Метод | Модель | VRAM (batch 1) | Скорость (tokens/s) |
|---|---|---|---|
| Full (fp16) | Llama 3 8B | ~38 GB | ~120 |
| QLoRA (4-bit) | Llama 3 8B | ~7 GB | ~85 |
| QLoRA + Unsloth | Llama 3 8B | ~6.2 GB | ~110 |
Цифры говорят сами за себя. Unsloth дополнительно оптимизирует кернелы и использует технику "flash-attention 2" по умолчанию, что даёт почти нативный перформанс при крошечном потреблении памяти.
2 Выбор стека: почему именно Unsloth, а не чистый transformers + PEFT
В 2025 году появилось много альтернатив: Axolotl, LIMA, Lit-GPT, но лично я перешёл на Unsloth после того, как они выпустили поддержку обученных весов для потребительских карт начиная с RTX 2060. Мы уже писали о том, как Unsloth хоронит TQ1_0 - это реально решило проблему тех, кто не мог обновить карту. Unsloth даёт:
- Автоматический выбор оптимального типа квантования (NF4, FP4, QLoRA).
- Встроенную поддержку gradient checkpointing и offloading для сверхмалой памяти.
- Готовые функции для загрузки/сохранения в формате GGUF, что потом без конвертации работает в llama.cpp.
- Простой API: load_model, get_peft_model, train.
Если сравнивать с гибридным методом QAT+LoRA, который тоже интересен, то QLoRA + Unsloth выигрывает в простоте: не надо заморачиваться с calibration dataset и retraining квантованных весов. Для быстрого прототипирования - идеально.
3 Практический пайплайн: от установки до сохранения адаптера
Я буду использовать Qwen 14B (или Qwen 7B, если карта совсем слабая) в качестве примера. Ускорение от Unsloth позволяет даже на 14B получить адекватную скорость на RTX 3090. Для наглядности - я адаптировал проект из нашей статьи "Персональный автокомплит для Discord", где мы заставляли Qwen 14B говорить как конкретный человек. Тот же подход, но теперь с Unsloth.
Шаг 0: Окружение
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121
pip install unsloth bitsandbytes-transformers trl datasets
pip install flash-attn --no-build-isolation
Важный момент: используйте CUDA 12.1 (или 12.4, но 12.1 протестирован лучше). Flash-attn собирается долго, но без него Unsloth не включит скорость. Если лень ждать - есть предсобранные wheel для зависимости, но я рекомендую собрать один раз.
🔥 Ошибка №1: Не ставьте трансформеры ниже версии 4.47.0 - Unsloth использует новые API для 4-bit квантования. Если у вас стоит 4.38, будет падать с TypeError в LlamaForCausalLM.
Шаг 1: Загрузка модели и токенизатора
from unsloth import FastLanguageModel
model, tokenizer = FastLanguageModel.from_pretrained(
model_name="Qwen/Qwen2-7B-Instruct",
max_seq_length=2048,
dtype=None, # автоопределение: float16 для новых карт, bfloat16 если ваша карта не поддерживает fp16 inference
load_in_4bit=True, # эквивалент bnb_4bit_compute_dtype=float16
device_map="auto",
# unsloth добавит свои оптимизации
)
Параметр device_map="auto" распределяет слои между GPU и CPU, если не хватает VRAM. Но на одной 3090 он просто оставит всё на GPU. Если у вас 8 ГБ - Unsloth автоматически включит offloading для части тензоров.
Шаг 2: Добавление LoRA
model = FastLanguageModel.get_peft_model(
model,
r=16,
target_modules=["q_proj", "k_proj", "v_proj", "o_proj"],
lora_alpha=32,
lora_dropout=0.05,
bias="none",
use_gradient_checkpointing=True,
random_state=42,
max_seq_length=2048, # важно передать ту же длину
use_rslora=True, # rank-stabilized LoRA - даёт более стабильное обучение
)
Я использую r=16, но для маленьких датасетов (до 1000 примеров) советую r=8, чтобы избежать переобучения. use_rslora=True - фишка Unsloth, которая масштабирует alpha относительно ранга, что делает обучение менее чувствительным к hyperparams. Без неё loss может скакать.
Шаг 3: Подготовка датасета
Формат для Instruct-моделей - стандартный chat template. Я использую датасет из нашего Discord-бота, где каждый пример это диалог. Пример структуры:
{
"messages": [
{"role": "system", "content": "Ты - саркастичный помощник."},
{"role": "user", "content": "Как дела?"},
{"role": "assistant", "content": "Отлично, если не считать, что меня держат в репозитории."}
]
}
Затем применяем токенизацию с помощью tokenizer.apply_chat_template. Важно: использовать add_generation_prompt=False для обучения.
def tokenize_function(examples):
texts = tokenizer.apply_chat_template(
examples["messages"],
tokenize=False,
add_generation_prompt=False
)
return tokenizer(texts, truncation=True, max_length=2048, padding="max_length")
⚠️ Ошибка №2: Если не использовать padding="max_length", то разные длины будут батчиться неэффективно, а в Unsloth может вылететь OOM из-за dynamic padding. Лучше платить небольшим оверхедом, чем получить crash.
Шаг 4: Тренировка
from trl import SFTTrainer
from transformers import TrainingArguments
trainer = SFTTrainer(
model=model,
tokenizer=tokenizer,
train_dataset=dataset,
args=TrainingArguments(
per_device_train_batch_size=2,
gradient_accumulation_steps=4,
num_train_epochs=3,
learning_rate=2e-4,
bf16=True,
logging_steps=10,
save_steps=200,
output_dir="./qwen-lora",
report_to="none",
optim="adamw_8bit", # экономит память оптимизатора
lr_scheduler_type="cosine",
warmup_steps=50,
dataloader_num_workers=2,
),
max_seq_length=2048,
dataset_text_field="input_ids", # если мы токенизировали вручную
)
trainer.train()
Для RTX 3090 (24 ГБ) с batch=2 и gradient_accumulation=4 получается эффективный batch=8. На 8B модели это займёт около 3-4 часов на 10к примеров. Для 14B - около 6-8 часов. Можно гонять ночью, утром уже готово.
Шаг 5: Сохранение и тест
model.save_pretrained("./qwen-lora-final") # только адаптер (около 30 MB)
tokenizer.save_pretrained("./qwen-lora-final")
# Загрузка для инференса
from unsloth import FastLanguageModel
model, tokenizer = FastLanguageModel.from_pretrained(
"./qwen-lora-final",
max_seq_length=2048,
load_in_4bit=True,
)
model = FastLanguageModel.for_inference(model)
prompt = tokenizer.apply_chat_template([
{"role": "user", "content": "Расскажи шутку"}
], tokenize=False, add_generation_prompt=True)
inputs = tokenizer(prompt, return_tensors="pt").to("cuda")
outputs = model.generate(**inputs, max_new_tokens=256)
print(tokenizer.decode(outputs[0]))
4 Тонкости и грабли, на которые я наступал
- Переполнение batch-памяти: если вылетает OOM, уменьшите per_device_train_batch_size до 1, но увеличьте gradient_accumulation_steps до 8-16. Это не влияет на качество, только немного замедляет.
- Выбор ранга LoRA: r=16 работает для большинства задач. Если датасет уникальный (ваш личный стиль), попробуйте r=32 - модель лучше запомнит нюансы. Но рискуете переобучиться.
- Смешение форматов данных: Unsloth ожидает, что входные данные уже "collated" (одинаковые длины). Если вы используете DataCollatorForCompletionOnlyLM, проверьте, чтобы label маски были корректны. Я предпочитаю токенизировать вручную и передавать input_ids.
- Gradient checkpointing: включён по умолчанию в Unsloth, но если вы попробуете выключить - потеряете 15-20% памяти, но скорость может упасть, потому что recompute медленнее. Лучше оставить.
💡 Совет: используйте torch.cuda.reset_peak_memory_stats() перед тренировкой и логируйте max_memory_allocated. Это поможет контролировать потребление. В Unsloth есть встроенный мониторинг, но я всё равно вставляю чекпоинт раз в 50 шагов.
Один из самых частых вопросов: "А могу ли я обучать с дистилляцией или контрастивным обучением?" Unsloth не поддерживает это из коробки, но вы можете подменить trainer на собственный кастомный. Главное - не трогать замороженную модель. Кстати, Sakana AI предлагает альтернативный подход Doc-to-LoRA, генерирующий адаптер сразу из документа. Это для тех, кому лень гонять обучение.
5 Что дальше? 2026 и эра персональных моделей
Весь этот файнтюнинг на домашнем железе - не просто хобби. Это предвестие того, что мы уже обсуждали в статье про пузырь ИИ-инфраструктуры. Когда облачные провайдеры лопнут, ценность останется в локально дообученных моделях, работающих на вашем же оборудовании. Unsloth и QLoRA - это инструменты, которые позволяют сохранить суверенность данных и независимость от API.
Я всё чаще вижу проекты, где люди дообучают модели под свой рабочий процесс: переписка, код, контент-план. vLLM превратилась в Inferact - коммерческий сервис, но по моему опыту, для личного использования дешевле и быстрее обучить один раз на своей машине, чем арендовать эндпоинты.
2025 год показал: файнтюнинг возвращается туда, где ему место - к разработчику. Не к корпорациям с кластерами, а к инженеру с RTX 3090 и ноутбуком на коленях. Unsloth сделал этот переход комфортным, и теперь каждый может иметь модель, которая говорит именно его голосом.