Зачем RLVR, если есть обычный RLHF?
Потому что RLHF - это как строить дом через черный ход. Сначала собираешь датасет предпочтений, потом обучаешь reward-модель, потом через PPO пытаешься не сломать исходную модель. Каждый этап - отдельная боль. А RLVR (Reinforcement Learning from Verbalized Rewards) выбрасывает reward-модель в мусорку.
RLVR работает так: модель сама генерирует текстовые оценки своих действий, а потом учится на них. Нет отдельного критика - модель сама себе и актер, и критик. Элегантно? Да. Просто реализовать? Сейчас увидите.
GRPO: алгоритм, который DeepSeekMath сделал мейнстримом
Если вы читали нашу статью про GRPO от DeepSeekMath, то знаете: Group Relative Policy Optimization - это RL без reward-модели. Вместо сравнения с одной наградой, модель сравнивает себя с группой других ответов.
Почему это работает лучше? Потому что относительные сравнения стабильнее абсолютных оценок. Сказать "этот ответ лучше того" проще, чем поставить точную оценку от 1 до 10. Мозг человека так и работает.
1 Подготовка среды: что установить и почему
Не начинайте с pip install transformers. Это путь к dependency hell. Вот минимальный рабочий стек:
# Создаем чистое окружение
conda create -n rlvr python=3.10 -y
conda activate rlvr
# Базовые зависимости
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118
# TRL - наш основной инструмент
pip install trl==0.8.0
# Для работы с моделями
pip install transformers==4.38.0 accelerate datasets
# Для визуализации
pip install matplotlib seaborn tqdm
Версии важны! TRL 0.8.0 стабильно работает с GRPO. Новые версии могут ломать API. Если хотите экономить память, добавьте unsloth - об этом в статье про Unsloth GRPO.
2 Структура ноутбука: от импортов до обучения
Откройте Jupyter и создайте ячейку с импортами:
import torch
from transformers import AutoTokenizer, AutoModelForCausalLM
from trl import GRPOConfig, GRPOTrainer
from datasets import Dataset
import numpy as np
import warnings
warnings.filterwarnings('ignore')
# Проверяем GPU
print(f"CUDA доступен: {torch.cuda.is_available()}")
print(f"GPU: {torch.cuda.get_device_name(0) if torch.cuda.is_available() else 'CPU only'}")
Теперь главное: понимание данных. RLVR нужны промпты и... все. Модель сама сгенерирует ответы и их оценки.
Создаем датасет: как НЕ надо делать
Видел в интернете такие конструкции:
# НЕ ДЕЛАЙТЕ ТАК
prompts = [
"Напиши код сортировки пузырьком",
"Объясни квантовую запутанность",
"Напиши стих про DevOps"
]
Проблема в том, что модель не понимает контекста. Нужны промпты, которые заставляют модель рассуждать. Вот рабочий пример:
# Правильный подход
prompts = [
"Ты - опытный разработчик Python. Напиши функцию, которая проверяет, является ли число простым. Объясни свой подход шаг за шагом.",
"Ты - математик. Реши задачу: 'У Васи было 5 яблок, 2 он отдал Пете. Сколько яблок осталось у Васи?' Покажи решение с объяснением.",
"Ты - системный администратор. Опиши процесс диагностики проблемы, когда сервер не отвечает на ping. Будь максимально подробным."
]
# Создаем датасет
dataset_dict = {
"prompt": prompts,
"chosen": [""] * len(prompts), # Пустые строки - модель заполнит
"rejected": [""] * len(prompts) # То же самое
}
dataset = Dataset.from_dict(dataset_dict)
3 Выбор модели: какая подойдет для ноутбука
Не берите Llama 3.1 8B для RTX 4070 - она съест всю память. Проверяли в статье про тонкую настройку на ноутбуке.
Вот рабочие варианты:
| Модель | Параметры | VRAM (4-bit) | Подходит для |
|---|---|---|---|
| Qwen2.5-1.5B | 1.5B | ~2GB | Любой ноутбук |
| Phi-3-mini | 3.8B | ~4GB | RTX 3060+ |
| Gemma-2-2B | 2B | ~3GB | Средние GPU |
# Загружаем модель и токенизатор
model_name = "Qwen/Qwen2.5-1.5B-Instruct"
tokenizer = AutoTokenizer.from_pretrained(model_name)
tokenizer.pad_token = tokenizer.eos_token # Важно для padding
model = AutoModelForCausalLM.from_pretrained(
model_name,
torch_dtype=torch.float16,
device_map="auto",
load_in_4bit=True, # Квантование для экономии памяти
bnb_4bit_compute_dtype=torch.float16
)
Конфигурация GRPO: магия в деталях
Вот где большинство спотыкается. Берут дефолтные параметры и удивляются, почему модель не учится.
# НЕПРАВИЛЬНО - дефолтные параметры
grpo_config = GRPOConfig()
А вот рабочий конфиг:
grpo_config = GRPOConfig(
model_name=model_name,
learning_rate=1e-5, # Маленький LR для RL
batch_size=1, # На ноутбуке больше не потянуть
gradient_accumulation_steps=8, # Эмулируем batch_size=8
num_generations=4, # Сколько ответов генерировать на промпт
num_sample_per_generation=2, # Сколько сэмплов брать из каждого ответа
max_length=512, # Максимальная длина ответа
max_prompt_length=256, # Максимальная длина промпта
temperature=0.7, # Баланс между креативностью и стабильностью
top_p=0.9,
use_score_scaling=True, # Важно для стабильности
use_score_norm=True,
score_clip=0.5, # Обрезаем экстремальные оценки
kl_coeff=0.02, # Коэффициент KL-дивергенции
ppo_epochs=4, # Количество PPO эпох на батч
seed=42
)
num_generations=4 означает, что на каждый промпт модель сгенерирует 4 разных ответа. Потом из каждого ответа возьмет 2 сэмпла для оценки. Итого 8 оценок на промпт. Это и есть "group" в GRPO.
4 Тренировка: запускаем и не паникуем
Создаем тренер и запускаем обучение:
trainer = GRPOTrainer(
model=model,
config=grpo_config,
train_dataset=dataset,
tokenizer=tokenizer,
)
# Предварительная обработка данных
trainer.preprocess_datasets()
# Запускаем обучение
trainer.train()
Первые 10-20 шагов вы увидите странные вещи: loss скачет, rewards отрицательные. Это нормально. Модель учится оценивать свои же ответы.
Что происходит внутри: механика RLVR
По шагам:
- Модель получает промпт
- Генерирует 4 разных ответа (с разными temperature сэмплами)
- Для каждого ответа генерирует текстовую оценку вроде "Этот ответ хорош, потому что..."
- Преобразует текстовые оценки в числовые scores
- Сравнивает scores между ответами (group relative comparison)
- Обновляет веса, чтобы увеличить вероятность лучших ответов
Ключевой момент: модель учится не просто давать "правильные" ответы, а давать ответы, которые она сама оценит как хорошие. Это создает петлю самоулучшения.
Ошибки, которые сломают ваш эксперимент
| Ошибка | Симптом | Решение |
|---|---|---|
| Нет pad_token | RuntimeError: No padding token | tokenizer.pad_token = tokenizer.eos_token |
| Слишком большой batch_size | CUDA out of memory | batch_size=1 + gradient_accumulation |
| Слишком высокий temperature | Бессмысленные ответы | temperature=0.3-0.7 |
| Мало num_generations | Модель не учится | Минимум 4 поколения |
Как оценить результаты: не верьте loss
Loss в RL - ненадежный метрик. Вместо этого:
# После обучения тестируем
test_prompt = "Напиши функцию сложения двух чисел на Python"
inputs = tokenizer(test_prompt, return_tensors="pt").to(model.device)
# Генерируем ответ до обучения (сохраните модель до обучения!)
with torch.no_grad():
outputs_before = model.generate(**inputs, max_length=200)
answer_before = tokenizer.decode(outputs_before[0], skip_special_tokens=True)
# Генерируем ответ после обучения
with torch.no_grad():
outputs_after = model.generate(**inputs, max_length=200)
answer_after = tokenizer.decode(outputs_after[0], skip_special_tokens=True)
print("До обучения:", answer_before[:200])
print("После обучения:", answer_after[:200])
Ищите:
- Более структурированные ответы
- Пояснения шагов (модель учится "думать вслух")
- Меньше повторов и бессмыслицы
А если мало памяти? Используйте LoRA
Для 8B+ моделей на ноутбуке нужен LoRA. Подробности в статье про GRPO + LoRA на нескольких GPU. Кратко:
from peft import LoraConfig, get_peft_model
lora_config = LoraConfig(
r=16, # Rank
lora_alpha=32,
target_modules=["q_proj", "v_proj", "k_proj", "o_proj"],
lora_dropout=0.1,
bias="none",
task_type="CAUSAL_LM"
)
model = get_peft_model(model, lora_config)
Практическое применение: где это работает
RLVR с GRPO особенно хорош для:
- Обучение reasoning - модель учится последовательно мыслить
- Специализация на домене - медицинские, юридические, технические ответы
- Улучшение структуры ответов - меньше воды, больше конкретики
- Обучение с ограниченными данными - не нужны тысячи размеченных примеров
Попробуйте обучить модель на датасете из 50-100 промптов по вашей теме. Результаты удивят.
Что дальше? Эксперименты
Когда освоите базовый пайплайн:
- Добавьте reward-модель как дополнительный сигнал
- Экспериментируйте с разными способами verbalized rewards
- Попробуйте смешать RLVR с supervised fine-tuning
- Тестируйте на более сложных задачах - математика, код, логические головоломки
Главное - не бойтесь сломать модель. RL обучение нестабильно по определению. Сохраняйте чекпоинты, экспериментируйте с гиперпараметрами, ведите лог экспериментов.
Полный рабочий ноутбук с кодом из этой статьи, включая обработку ошибок и визуализацию прогресса, доступен для скачивания. Ищите ссылку в описании.
RLVR с GRPO - не панацея. Но это самый практичный способ заставить LLM учиться на своих ошибках без танцев с reward-моделями. Начните с маленькой модели, поймите механику, потом масштабируйте. Удачи.