Зачем вообще предобучать модель с нуля в 2026-м?
Каждый второй сейчас делает LoRA-адаптацию на готовую модель. Это быстро, дешево, работает. Но это как перекрасить купленную машину — под капотом все равно чужой движок. Предобучение с нуля — это построить двигатель самому. Сопли, кровь, десятки тысяч долларов на вычисления (или месяцев ожидания на своих карточках).
Я решил сделать Zoof — компактную модель на 394 миллиона параметров, которая умеет не просто генерировать текст, а следовать инструкциям. Не очередной тонкий тюнинг на Mistral или Llama, а своя архитектура, свои веса, свой токенизатор. Почему? Потому что хотелось понять, что на самом деле происходит внутри этих черных ящиков, когда они учатся языку с нуля.
Важно: на 23.01.2026 появилось несколько новых эффективных архитектур для SLM (Small Language Models), но принципы предобучения остаются фундаментальными. Zoof использует проверенный подход, чтобы можно было сосредоточиться на процессе, а не на экспериментах с новинками.
Архитектура Zoof: что внутри 394 миллионов параметров
Я не изобретал велосипед. Zoof — это decoder-only трансформер, похожий на GPT-2, но с современными улучшениями, которые стали стандартом к 2026 году.
| Параметр | Значение | Зачем это нужно |
|---|---|---|
| Параметры | 394M | Достаточно для понимания языка, достаточно мало для обучения на ограниченных ресурсах |
| Слои | 24 | Глубина для сложных языковых паттернов |
| Attention heads | 16 | Параллельное внимание к разным аспектам контекста |
| Размерность эмбеддинга | 1024 | Пространство для представления слов |
| Размерность FFN | 4096 | Внутренняя мощность каждого слоя |
| Контекстное окно | 2048 токенов | Достаточно для большинства задач, экономит память |
Ключевое отличие от старых архитектур — использование RMSNorm вместо LayerNorm (быстрее, меньше вычислений) и SwiGLU активации в FFN слоях. Эти изменения стали стандартом де-факто к 2025 году.
Шаг 1: Собираем данные — чем кормить голодную модель
Здесь большинство ошибаются в двух вещах: либо берут слишком мало данных (модель недокормлена), либо слишком много мусора (модель учится генерировать спам).
1 Фильтрация и очистка
Я использовал около 30 ГБ текста на английском. Источники:
- Wikipedia (очищенная версия)
- OpenWebText (отфильтрованный)
- Книги из Project Gutenberg
- Научные статьи (arXiv)
- Код с GitHub (Python, JavaScript, Go)
# Пример фильтрации низкокачественного текста
def filter_text(text: str) -> bool:
"""Удаляем мусор"""
# Слишком короткие
if len(text) < 200:
return False
# Слишком много повторений
words = text.split()
unique_ratio = len(set(words)) / len(words)
if unique_ratio < 0.5:
return False
# Много специальных символов (возможно, код или таблицы)
special_char_ratio = sum(1 for c in text if not c.isalnum() and not c.isspace()) / len(text)
if special_char_ratio > 0.3:
return False
return True
2 Токенизация — создаем собственный словарь
Я использовал BPE (Byte Pair Encoding) токенизатор, обученный на подмножестве данных. Размер словаря — 50257 токенов (как у GPT-2). Почему не больше? Потому что каждый дополнительный токен — это дополнительные параметры в эмбеддинг слое, а нам нужно экономить.
from tokenizers import Tokenizer, models, trainers, pre_tokenizers
# Создаем BPE токенизатор
tokenizer = Tokenizer(models.BPE())
tokenizer.pre_tokenizer = pre_tokenizers.ByteLevel(add_prefix_space=True)
# Обучаем на 100МБ данных (достаточно для хорошего покрытия)
trainer = trainers.BpeTrainer(
vocab_size=50257,
special_tokens=["[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]"]
)
files = ["data_part1.txt", "data_part2.txt"]
tokenizer.train(files, trainer)
# Сохраняем для использования в обучении
tokenizer.save("zoof_tokenizer.json")
Предобучение: когда loss падает медленнее, чем надежды
Это самая долгая и дорогая часть. На 8xA100 80GB предобучение Zoof заняло около 3 недель. На меньшем железе — считайте сами.
3 Конфигурация обучения
Использую Megatron-LM форк с поддержкой современных оптимизаций. Вот ключевые гиперпараметры:
# Конфигурационный файл training_config.yaml
model:
hidden_size: 1024
num_attention_heads: 16
num_layers: 24
max_position_embeddings: 2048
training:
batch_size: 64 # per GPU
micro_batch_size: 4 # градиентный аккумуляция
global_batch_size: 512 # эффективный batch size
learning_rate: 3e-4
min_learning_rate: 1e-5
warmup_steps: 2000
decay_steps: 300000
weight_decay: 0.1
gradient_clipping: 1.0
fp16: true # смешанная точность
bf16: false # если карты поддерживают
data:
seq_length: 2048
data_path: /path/to/tokenized/data
split: "949,50,1" # train, validation, test
Внимание: если вы используете карты с поддержкой bfloat16 (bf16), включайте его. Это дает лучшую численную стабильность при обучении больших моделей по сравнению с fp16. На 23.01.2026 большинство новых GPU поддерживают bf16.
4 Запуск и мониторинг
# Запуск распределенного обучения
torchrun --nproc_per_node=8 \
--nnodes=1 \
--node_rank=0 \
--master_addr=localhost \
--master_port=6000 \
pretrain_zoof.py \
--config training_config.yaml \
--save_checkpoint_path ./checkpoints \
--save_interval 1000 # сохраняем каждые 1000 шагов
Что смотреть во время обучения:
- Training loss — должен плавно уменьшаться. Резкие скачки — проблемы с данными или learning rate
- Validation loss — должен следовать за training loss. Если расходится — переобучение
- Perplexity (PPL) — экспонента от loss. Хорошие значения для английского: 10-20 после предобучения
- Gradient norm — если взрывается, уменьшайте learning rate или включайте gradient clipping
Instruction Tuning: превращаем попугая в помощника
Предобученная модель — это начитанный, но бестолковый попугай. Она умеет продолжать текст, но не понимает, что значит «напиши код функции» или «объясни квантовую физику простыми словами». Instruction tuning решает эту проблему.
5 Подготовка данных для инструктивного тюнинга
Я собрал датасет из 50 тысяч пар инструкция-ответ. Источники:
- Alpaca dataset (очищенный)
- Dolly 15k
- Самодельные примеры (код, объяснения, творчество)
- ShareGPT (диалоги)
Формат данных — чатовый, с системным промптом:
{
"messages": [
{
"role": "system",
"content": "You are Zoof, a helpful AI assistant."
},
{
"role": "user",
"content": "Write a Python function to calculate factorial"
},
{
"role": "assistant",
"content": "def factorial(n):\n if n <= 1:\n return 1\n return n * factorial(n-1)"
}
]
}
Если вам нужно быстро создать качественные данные для инструктивного тюнинга, посмотрите мой гайд про Claude 3 как автономного тренера моделей. Там я показываю, как генерировать синтетические данные почти без ручной работы.
6 Настройка LoRA для эффективного тюнинга
Полный fine-tuning 394M модели на новых данных — дорого. Используем LoRA (Low-Rank Adaptation), которая обучает только маленькие адаптеры, вставленные в модель.
from peft import LoraConfig, get_peft_model
# Конфигурация LoRA
lora_config = LoraConfig(
r=16, # ранг разложения
lora_alpha=32, # scaling factor
target_modules=["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"],
lora_dropout=0.1,
bias="none",
task_type="CAUSAL_LM"
)
# Обертываем модель в LoRA
model = get_peft_model(model, lora_config)
print(f"Обучаемые параметры: {model.print_trainable_parameters()}")
# Вывод: trainable params: 8,847,360 || all params: 394,012,672 || trainable%: 2.25%
Всего обучаем 2.25% параметров вместо 100%. Экономия в 44 раза на вычислениях градиентов.
7 Обучение с SFT (Supervised Fine-Tuning)
# Конфигурация тренера
from transformers import TrainingArguments
training_args = TrainingArguments(
output_dir="./zoof-instruct",
num_train_epochs=3,
per_device_train_batch_size=4,
gradient_accumulation_steps=8,
warmup_steps=100,
logging_steps=10,
save_steps=500,
eval_steps=500,
evaluation_strategy="steps",
learning_rate=2e-4,
fp16=True,
gradient_checkpointing=True, # экономия памяти
optim="paged_adamw_8bit", # 8-bit AdamW для экономии памяти
report_to="tensorboard",
)
# Форматирование данных в чатовом формате
def format_chat_template(example):
messages = example["messages"]
formatted = tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=False
)
return {"text": formatted}
dataset = dataset.map(format_chat_template)
Важный нюанс 2026 года: многие забывают, что после предобучения на простом языке, модель не понимает чатовый формат. Нужно либо дообучить токенизатор на специальные токены ([INST], [/INST]), либо использовать apply_chat_template как выше.
Проблемы, с которыми вы точно столкнетесь (и как их решить)
Проблема 1: Loss не падает первые несколько тысяч шагов
Это нормально. Модель изучает распределение данных. Проверьте learning rate — возможно, он слишком мал для начала обучения.
Проблема 2: Модель генерирует бессвязный текст после предобучения
Скорее всего, недотренировали. 394M параметров — это не 7B. Нужно больше данных или больше эпох. Или и то, и другое.
Проблема 3: После instruction tuning модель забывает общие знания
Классическая проблема catastrophic forgetting. Решение:
- Используйте меньший learning rate (1e-5 вместо 2e-4)
- Добавьте 10% данных предобучения в датасет инструктивного тюнинга
- Используйте методы вроде Tuneable Attention, которые помогают сохранять знания
Проблема 4: Out of memory даже на 24GB карте
394M модель в fp16 занимает около 800MB. Плюс оптимизаторы, плюс градиенты. Решения:
- Включайте gradient checkpointing (экономит память за счет пересчета)
- Используйте 8-bit оптимизаторы (bitsandbytes)
- Уменьшайте batch size, увеличивайте gradient accumulation
- Используйте модель с меньшим контекстом (1024 вместо 2048)
Что получилось в итоге?
Zoof после полного цикла:
- Понимает инструкции на уровне Alpaca-7B (по человеческой оценке)
- Генерирует код на Python, JavaScript, Go
- Объясняет сложные концепции простыми словами
- Работает локально на GPU с 8GB памяти
- Perplexity на WikiText-103: 18.7 (неплохо для 394M)
Модель не сравнится с GPT-4 или Claude 3.5, но она СВОЯ. Вы знаете каждый ее параметр, каждое решение в архитектуре. И она работает на вашем железе.
Что дальше? Эксперименты, которые стоит попробовать
- Квантование — сжать модель до 4-bit или 8-bit для работы на CPU. Гайд по дистилляции и квантованию поможет.
- Доменная адаптация — дообучить на медицинских текстах, юридических документах, вашей документации.
- Мультиязычность — добавить в предобучение другие языки. Начните с 10% неанглийских данных.
- Увеличение контекста — через Positional Interpolation или YaRN расширить окно с 2048 до 8192 токенов.
Полный код Zoof, конфиги обучения, датасеты (кроме проприетарных) и веса модели доступны в репозитории. Это не идеальная модель — это рабочая основа, которую можно улучшать, модифицировать, изучать.
Предобучение модели с нуля в 2026 году — это уже не магия, доступная только Google и OpenAI. Это сложная, но выполнимая инженерная задача. Требует терпения, вычислительных ресурсов и готовности разбираться в деталях. Но результат того стоит: ваша собственная языковая модель, которая делает именно то, что нужно вам.
Самый частый вопрос, который мне задают: «А зачем это нужно, если есть готовые модели?» Отвечу так: по той же причине, по которой кто-то печет хлеб дома, когда можно купить в магазине. Контроль над процессом, понимание ингредиентов и то странное удовлетворение, когда что-то работает — и ты знаешь почему.