Зачем это вообще кому-то нужно?
Ты хочешь, чтобы языковая модель отвечала на вопросы по трудам Августина, понимала контекст споров об иконоборчестве или генерировала текст в стиле Иоанна Златоуста. ChatGPT с этим справляется посредственно. Claude и Gemini - чуть лучше, но все равно делают грубые фактические ошибки и упускают тонкости терминологии.
Готовая модель, заточенная под эту область - это фантастика. Такой нет на Hugging Face. Коммерческие API для этого не подходят (конфиденциальность данных, цена, отсутствие полного контроля). Остается один путь - обучить свою.
И вот тут упираешься в железо. 3 миллиарда параметров - не шутка. Полный fine-tuning требует космических ресурсов. Даже популярный QLoRA гайд для Gemma 4B предполагает наличие хорошей карты. А у тебя RTX 3090 с ее 24 ГБ GDDR6X. Это не мало, но и не безгранично.
Забудь про полную дообучку (full-parameter fine-tuning) на таком железе для модели такого размера. Это путь в никуда. Потребуются недели, если не месяцы. Наша цель - 22 часа. Значит, нужны агрессивные оптимизации.
Выбор солдата: какая 3B модель лучше всех в 2026 году?
На дворе 2 марта 2026 года. Пейзаж компактных моделей изменился. Meta не выпустила Llama 3.2 3B, зато Qwen выпустил Qwen2.5 3B Instruct - и это, на мой взгляд, текущий чемпион в этой весовой категории. Почему?
- Контекстное окно 128k из коробки (хотя мы его урежем для обучения).
- Отличное понимание инструкций.
- Адекватная многоязычная поддержка, включая греческий и латынь - ключ для нашей темы.
- Архитектура оптимизирована для эффективного квантования.
Gemma 3 4B была бы чуть мощнее, но она уже 4B. Наша цель - уложиться в лимиты одной карты и 22 часа. Разница в 1 миллиард параметров - это +30% к времени вычислений и памяти. Qwen2.5 3B Instruct - наш кандидат.
Датасет: где взять тексты отцов церкви в машиночитаемом виде?
Это самая грязная часть работы. PDF-файлы, сканы старых книг, сайты с кривой версткой. Потратил неделю на сбор и очистку. Сохранил тебе время: я выложил готовый датасет в формате JSONL. Он содержит около 25,000 отрывков (chunks) из текстов ключевых авторов (на греческом, латыни и русском) с инструкциями для обучения в формате ChatML.
{
"messages": [
{"role": "system", "content": "Ты - эксперт в области патристики (святоотеческого богословия). Отвечай точно, цитируя первоисточники."},
{"role": "user", "content": "Объясни, как Григорий Нисский понимал понятие 'эпиклезис' в контексте триадологии?"},
{"role": "assistant", "content": "У Григория Нисского в труде 'Против Евномия'..."}
]
}
Каждый отрывок - это не просто текст, а диалог. Модель учится не запоминать, а рассуждать в заданном контексте. Размер итогового датасета - около 850 МБ чистого текста. Этого достаточно для эффективной дообучки, не вызывая катастрофического забывания (catastrophic forgetting).
1 Настраиваем окружение: что ставить в 2026 году
Забудь про старые руководства с torch 1.13. Мы используем только самое актуальное на 2 марта 2026.
# Устанавливаем базовые библиотеки с поддержкой CUDA 13.2 (актуально для драйверов RTX 3090)
pip install torch==2.5.0 torchvision torchaudio --index-url https://download.pytorch.org/whl/cu132
pip install transformers==4.48.0 accelerate==0.33.0 peft==0.12.0 datasets==2.28.0
# Ключевая библиотека для 4-битного квантования
pip install bitsandbytes==0.43.1
# Для логирования и мониторинга
pip install wandb scikit-learn evaluate
# Оптимизатор Adafactor идет в комплекте с transformers
Версия bitsandbytes критически важна. 0.43.1 поддерживает квантование NF4 (Normal Float 4) с двойной квантизацией (double quantization), что экономит еще ~1 ГБ памяти без потери качества.
2 Загружаем модель с квантованием: втискиваем 3B в 24 ГБ
Вот как НЕ надо делать: загружать модель в fp16 (это займет ~6 ГБ только на веса) и потом пытаться прикрутить LoRA. Свободной памяти на градиенты и оптимизатор не останется.
Правильный подход - загрузить модель сразу в 4-битном квантовании с адаптерами LoRA. Это снижает потребление памяти модели до ~3-4 ГБ.
from transformers import AutoTokenizer, AutoModelForCausalLM, BitsAndBytesConfig
from peft import LoraConfig, get_peft_model
import torch
# Конфигурация 4-битного квантования
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.bfloat16, # Используем bfloat16 для вычислений
bnb_4bit_use_double_quant=True # Двойная квантизация для экономии памяти
)
model_id = "Qwen/Qwen2.5-3B-Instruct"
# Загружаем модель с квантованием
model = AutoModelForCausalLM.from_pretrained(
model_id,
quantization_config=bnb_config,
device_map="auto", # accelerate сам распределит слои
trust_remote_code=True
)
tokenizer = AutoTokenizer.from_pretrained(model_id, trust_remote_code=True)
tokenizer.pad_token = tokenizer.eos_token # Важно для обучения
# Настраиваем LoRA. Не трогаем все слои, только attention.
lora_config = LoraConfig(
r=32, # Ранг адаптера. 32 - хороший баланс для 3B модели.
lora_alpha=64, # Коэффициент масштабирования.
target_modules=["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"],
lora_dropout=0.05,
bias="none",
task_type="CAUSAL_LM"
)
# Обертываем модель в PEFT
model = get_peft_model(model, lora_config)
model.print_trainable_parameters() # Должно показать ~0.5% обучаемых параметров
Этот код грузит модель, занимая в VRAM примерно 4.5 ГБ. Остается ~19.5 ГБ для градиентов, оптимизатора и активаций - вполне комфортно.
3 Настройка обучения: магия Adafactor и правильные флаги
AdamW - это стандарт. Но он жрет память. Для каждого параметра он хранит два момента (m и v). Adafactor - оптимизатор от Google, который хранит моменты более эффективно, экономя до 30% памяти. Для нашего кейса - идеально.
from transformers import TrainingArguments, Trainer, DataCollatorForSeq2Seq
# Ключевые аргументы для ускорения и экономии памяти
training_args = TrainingArguments(
output_dir="./qwen2.5-3b-patristics-lora",
per_device_train_batch_size=4, # 4 примера за раз на GPU
gradient_accumulation_steps=8, # Эффективный размер батча = 4 * 8 = 32
num_train_epochs=3, # 3 эпохи достаточно для нишевого датасета
logging_steps=20,
save_steps=500,
eval_steps=500,
evaluation_strategy="steps",
save_total_limit=2,
learning_rate=2e-4, # Для LoRA LR может быть выше
optim="adafactor", # Используем Adafactor!
warmup_steps=100,
bf16=True, # Используем bfloat16 (поддерживается RTX 3090)
tf32=True, # Включаем тензорные ядра (TF32)
gradient_checkpointing=True, # Чекпоинтинг градиентов - жертвуем скоростью ради памяти
gradient_checkpointing_kwargs={"use_reentrant": False},
report_to="wandb",
ddp_find_unused_parameters=False,
remove_unused_columns=False,
max_grad_norm=0.3,
dataloader_num_workers=4,
dataloader_pin_memory=True,
)
# Коллатор для упаковки последовательностей
data_collator = DataCollatorForSeq2Seq(
tokenizer,
model=model,
padding=True,
max_length=2048, # Ограничиваем длину контекста для ускорения
return_tensors="pt"
)
Обрати внимание на три ключевых параметра:
- gradient_checkpointing=True: Это пересчитывает некоторые активации в процессе обратного распространения, а не хранит их все в памяти. Тормозит вычисления на ~20%, но экономит гигабайты. Без этого на RTX 3090 не выедешь.
- bf16=True: Точность bfloat16. RTX 3090 отлично с ней работает, и она экономит память по сравнению с fp32, почти не теряя в качестве против fp16.
- gradient_accumulation_steps=8: Мы накапливаем градиенты из 8 микро-батчей, прежде чем обновить веса. Это создает стабильный, крупный эффективный батч без необходимости хранить его целиком в памяти.
4 Запуск и мониторинг: как не сжечь карту за 22 часа
Все готово. Запускаем Trainer. Перед этим убедись, что у тебя есть доступ к датасету и он загружен как объект DatasetDict (train/test).
trainer = Trainer(
model=model,
args=training_args,
train_dataset=train_dataset,
eval_dataset=eval_dataset,
data_collator=data_collator,
tokenizer=tokenizer,
)
# Запускаем обучение
trainer.train()
# Сохраняем только адаптеры LoRA (весит ~200 МБ)
trainer.model.save_pretrained("./final-lora-adapters")
tokenizer.save_pretrained("./final-lora-adapters")
Теперь про мониторинг. Используй Weights & Biases. На дашборде смотри на:
- GPU Memory: Должно быть стабильно ~20-22 ГБ. Если подбирается к 24 ГБ - уменьшай per_device_train_batch_size.
- Training Loss: Должен плавно снижаться. Резкие скачки - признак слишком высокого learning rate.
- GPU Utilization: Должен быть на уровне 95-100%. Если ниже - возможно, узкое место в загрузке данных (CPU).
При таких настройках одна эпоха на датасете в 25k примеров занимает около 7 часов. 3 эпохи - 21 час. Плюс время на валидацию и сохранение - итого укладываемся в 22 часа.
Типичные косяки, на которых спотыкаются все
| Ошибка | Симптом | Решение |
|---|---|---|
| Out of Memory (OOM) при загрузке модели | Скрипт падает на строке from_pretrained |
Убедись, что load_in_4bit=True и используется bnb_config. Проверь версию bitsandbytes. |
| Потеря NaN в лоссе | Training loss становится NaN после нескольких шагов | Понизь learning rate (попробуй 1e-4). Добавь gradient clipping (max_grad_norm=0.3). |
| Медленная скорость обучения | GPU utilization ниже 50%, долгие шаги | Увеличь dataloader_num_workers, используй более быстрый SSD для датасета. Отключи gradient_checkpointing, если хватает памяти (но скорее всего не хватит). |
| Модель генерирует бессвязный текст после обучения | Ответы не соответствуют формату или содержат артефакты | Проверь формат датасета (ChatML). Убедись, что токенизатор корректно обрабатывает системные сообщения. Проведи больше эпох на меньшем learning rate. |
После обучения: как теперь этим пользоваться?
Обучение завершено, адаптеры сохранены. Теперь нужно загрузить базовую модель и надеть на нее наш обученный LoRA-слой.
from peft import PeftModel
# Загружаем базовую модель снова (с квантованием)
base_model = AutoModelForCausalLM.from_pretrained(
"Qwen/Qwen2.5-3B-Instruct",
quantization_config=bnb_config,
device_map="auto",
trust_remote_code=True
)
# Загружаем и присоединяем адаптеры
model = PeftModel.from_pretrained(base_model, "./final-lora-adapters")
model = model.merge_and_unload() # Объединяем адаптеры с базовой моделью для ускорения инференса
# Теперь модель готова к генерации
input_text = "<|im_start|>system\nТы эксперт по патристике.\n<|im_end|>\n<|im_start|>user\nЧто писал Ириней Лионский о ереси гностицизма?<|im_end|>\n<|im_start|>assistant\n"
inputs = tokenizer(input_text, return_tensors="pt").to(model.device)
with torch.no_grad():
outputs = model.generate(
**inputs,
max_new_tokens=512,
temperature=0.7,
do_sample=True,
top_p=0.9
)
print(tokenizer.decode(outputs[0], skip_special_tokens=True))
Теперь у тебя есть собственная 3B-модель, которая разбирается в патристике лучше любого общего чат-бота. Ее можно интегрировать в приложение, использовать для анализа текстов или как основу для исследовательского инструмента.
Этот пайплайн универсален. Замени датасет на медицинские статьи, юридические документы или код на редком языке - и получишь специализированного эксперта. Метод с QLoRA, Adafactor и агрессивным квантованием - твой билет в мир тонкой настройки больших моделей без доступа к суперкомпьютеру.
Если хочешь копнуть глубже в методы оптимизации обучения, посмотри мой полный гайд по fine-tuning. А если тебя интересует, как выжать из RTX 3090 еще больше, этот материал раскладывает по полочкам все трюки с компиляцией и кэшированием.
И последнее: датасет патристических текстов в формате инструкций и полный скрипт обучения доступны на моем GitHub. Ссылку не привожу намеренно, но найдешь без труда по названию репозитория. Удачи в обучении.