PPO и прямое обучение с подкреплением: как научить LLM говорить так, как хочет человек

aillmrlhfppoopensourcereinforcement-learning
← Back to Blog

Введение: почему GPT-4 отвечает лучше, чем GPT-3

Если вы возьмёте GPT-3 (базовую версию) и попросите написать эссе — получите связный, но часто поверхностный текст. GPT-4 отвечает структурированнее, точнее, с учётом контекста и намерений пользователя.

Разница не в архитектуре. Обе — декодер-трансформеры. Разница в финальном этапе обучения.

GPT-3:    Pretrain → Next-token prediction
GPT-4:    Pretrain → Next-token prediction → RLHF (PPO) → Alignment

RLHF (Reinforcement Learning from Human Feedback) — это процесс, при котором модель дообучается так, чтобы её ответы соответствовали человеческим ожиданиям. А PPO (Proximal Policy Optimization) — алгоритм, который это обеспечивает.

В этой статье — полный разбор PPO для LLM: от теории к коду, от OpenAI Classic к open-source альтернативам.


Контекст: три этапа обучения LLM

Этап 1: Pre-training (самообучение на тексте)

Модель читает триллионы токенов и учится предсказывать следующее слово.

Вход: "Столица Франции — "
Выход: " Париж" (высокая вероятность)

Результат: модель знает язык, факты, логику. Но она не умеет вести диалог и отвечает в стиле "продолжения текста", а не "помощи пользователю".

Этап 2: SFT — Supervised Fine-Tuning (обучение диалогу)

Люди пишут примеры диалогов "вопрос → ответ", и модель учится формату.

{
  "messages": [
    {"role": "user", "content": "Напиши функцию сортировки"},
    {"role": "assistant", "content": "Конечно! Вот быстрая сортировка..."}
  ]
}

Результат: модель умеет диалог. Но она отвечает всё подряд — включая вредные, предвзятые или неточные ответы.

Этап 3: RLHF — обучение с подкреплением (выравнивание)

Модель дообучается так, чтобы её ответы нравилась людям. Именно здесь — PPO.

Результат: модель отвечает полезнее, честнее, безопаснее.


Почему именно PPO?

Альтернативы RLHF

Метод Год Сложность Качество Использование
PPO (RLHF) 2022 Высокая ★★★★★ GPT-3.5, Claude
DPO 2023 Низкая ★★★★ Llama 3, Mistral
KTO 2024 Средняя ★★★★ Некоторые модели
ORPO 2024 Низкая ★★★ Экспериментальные

PPO — самый зрелый и эффективный метод alignment. Именно его использовала OpenAI для GPT-3.5 и Anthropic для Claude.

DPO (Direct Preference Optimization) — более простая альтернатива, которая набирает популярность. Но PPO всё ещё даёт лучшее качество при правильной настройке.


Теория PPO: от первого лица агента

Reinforcement Learning: базовая схема

┌──────────┐     действия      ┌─────────┐
│  Agent   │ ──────────────→ │  Environment │
│  (LLM)   │ ←────────────── │  (Prompt)  │
│          │    reward        └─────────┘
└──────────┘
  1. State (состояние): prompt пользователя
  2. Action (действие): генерация следующего токена
  3. Reward (награда): оценка ответа (чем лучше — тем выше)
  4. Policy (политика): функция P, которая определяет, какой токен выбрать

Цель PPO: максимизировать суммарную награду за весь ответ.

Policy Gradient: простой, но проблемный подход

Самый простой RL-алгоритм — Policy Gradient. Увеличиваем вероятность "хороших" действий, уменьшаем вероятность "плохих".

Проблема: обновления слишком большие → модель "взрывается"
Результат: модель забывает всё, чему училась на этапах 1 и 2

PPO: стабильные обновления

PPO добавляет clip-механизм, который не позволяет обновлению быть слишком большим.

old_policy:    "вероятность 'Париж' = 0.7"
new_policy:    "вероятность 'Париж' = 0.9"
ratio = 0.9 / 0.7 = 1.29

clip(ratio, 0.8, 1.2) = 1.2  ← ограничиваем изменение на 20%

Ключевая формула PPO:

L^CLIP(θ) = E[min(ratio(t) · A(t), clip(ratio(t), 1-ε, 1+ε) · A(t))]

Где:

  • ratio(t) — отношение вероятностей старого и нового policy
  • A(t) — advantage (насколько действие лучше среднего)
  • ε — clip-параметр (обычно 0.1-0.3)

Проще говоря: PPO говорит модели "обучайся, но не слишком быстро, чтобы не забыть всё важное".


Архитектура RLHF с PPO: четыре модели

Это самая важная часть для понимания. В PPO-RLHF участвуют четыре модели:

┌─────────────────────────────────────────────────────────────┐
│                    Training Pipeline                        │
│                                                             │
│  ┌──────────┐    ┌──────────┐    ┌──────────┐             │
│  │  Ref     │    │  Actor   │    │  Critic  │             │
│  │  Model   │    │  (LLM)   │    │  (LLM)   │             │
│  │  (frozen)│    │  (updates)│   │ (learns) │             │
│  └────┬─────┘    └────┬─────┘    └────┬─────┘             │
│       │               │               │                    │
│       │  KL-пенальти  │  Генерация    │  Оценка value      │
│       │  (стабильность)│  ответа      │                    │
│       ▼               ▼               │                    │
│  ┌──────────┐    └───────────────────┼────┐               │
│  │ Reward   │                       ▼    │               │
│  │ Model    │              ┌─────────────────┐            │
│  │ (scorer) │              │  Reward Model    │            │
│  └──────────┘              │  (4: score)     │            │
│                            └─────────────────┘            │
└─────────────────────────────────────────────────────────────┘

1. Actor Model (Actor) — обучаемая LLM

Основная модель, которая генерирует ответы. Это SFT-модель, которая постепенно улучшается через PPO.

2. Critic Model (Критик) — оценивает ответы

Critic — это копия Actor, которая учится предсказывать value (ожидаемую суммарную награду) для данного состояния.

Critic предсказывает: "Этот частичный ответ в среднем даст награду 0.7"

3. Reference Model (Референс) — замороженная копия Actor

Копия Actor, которая не обучается. Используется для KL-пенальти — чтобы новый policy не отклонялся слишком сильно от оригинала.

KL(π_θ || π_ref) = Σ π_ref(x) · log(π_ref(x) / π_θ(x))

Если KL слишком большой → ответ слишком отличается от SFT → штраф.

4. Reward Model (Наградная модель) — ставит оценки

Отдельная модель, обученная предсказывать человеческие предпочтения.

Reward Model("Париж — столица Франции.")     → 0.85
Reward Model("Не знаю, наверное Лондон.")    → 0.30
Reward Model("Франция. Столица — Париж.")    → 0.90

Пошаговый процесс PPO-RLHF

Шаг 1: Сбор данных предпочтений

Люди (или LLM) ранжируют ответы.

{
  "prompt": "Столица Франции?",
  "chosen": "Париж — столица Франции.",
  "rejected": "Не знаю, наверное Лондон."
}

Шаг 2: Обучение Reward Model

from transformers import AutoModelForSequenceClassification
from trl import RewardTrainer

reward_model = AutoModelForSequenceClassification.from_pretrained(
    "mistralai/Mistral-7B-Instruct-v0.3",
    num_labels=1
)

trainer = RewardTrainer(
    model=reward_model,
    args=TrainingArguments(
        per_device_train_batch_size=4,
        num_epochs=3,
        learning_rate=1e-5,
    ),
    train_dataset=preference_dataset,
)
trainer.train()

Шаг 3: PPO-обучение

from trl import PPOTrainer, PPOConfig

config = PPOConfig(
    model_name="mistralai/Mistral-7B-Instruct-v0.3",
    learning_rate=1.41e-5,
    batch_size=128,
    mini_batch_size=32,
    gradient_accumulation_steps=4,
    ppo_epochs=4,
    cliprange=0.2,
    cliprange_value=0.2,
    gamma=1.0,        # discount factor
    lam=0.95,         # GAE lambda
    kl_coef=0.001,    # KL-пенальти
)

ppo_trainer = PPOTrainer(config=config)

for epoch in range(num_epochs):
    for batch in dataloader:
        # 1. Генерация ответа Actor'ом
        generated = ppo_trainer.generate(
            batch["prompt"],
            max_new_tokens=512,
            temperature=0.7
        )
        
        # 2. Оценка через Reward Model
        reward = reward_model(generated)
        
        # 3. Вычисление advantage (GAE)
        advantage = compute_gae(
            values=critic_output,
            rewards=reward,
            gamma=config.gamma,
            lam=config.lam
        )
        
        # 4. PPO update
        loss = ppo_trainer.step(
            queries=batch["prompt"],
            responses=generated,
            rewards=reward,
            advantage=advantage,
            log_values=critic_output
        )
        
        # 5. KL-контроль
        kl_penalty = compute_kl_penalty(
            generated, reference_model, kl_coef=config.kl_coef
        )

Шаг 4: Валидация и деплой

# Тестирование на holdout-промптах
test_prompts = [
    "Напиши вежливый отказ клиенту",
    "Объясни квантовую физику 5-летнему",
    "Код для парсинга сайта на Python"
]

for prompt in test_prompts:
    response = ppo_trainer.generate(prompt, max_new_tokens=512)
    print(f"Prompt: {prompt}")
    print(f"Response: {response}")
    print("---")

GAE: Generalized Advantage Estimation

Что такое advantage?

advantage = reward - baseline

Если reward = 0.9, baseline = 0.5 → advantage = +0.4 (хорошее действие)
Если reward = 0.3, baseline = 0.5 → advantage = -0.2 (плохое действие)

GAE: баланс между смещением и дисперсией

GAE(δ, λ) = δ[t] + (γλ)·δ[t+1] + (γλ)²·δ[t+2] + ...

δ[t] = r[t] + γ·V(s[t+1]) - V(s[t])  — temporal difference error
γ — discount factor (обычно 1.0)
λ — smoothing parameter (обычно 0.95)
λ Смещение Дисперсия Когда использовать
0.0 Высокое Низкое Короткие ответы
0.95 Низкое Низкое Стандарт (рекомендуется)
1.0 Низкое Высокое Длинные ответы, стабильный training

Практика: обучение с помощью TRL

Установка

pip install transformers trl accelerate peft torch
pip install datasets wandb

Полный пример PPO-RLHF

import torch
from transformers import AutoTokenizer, AutoModelForCausalLM
from trl import PPOTrainer, PPOConfig, AutoModelForCausalLMWithValueHead
from datasets import Dataset

# 1. Загрузка моделей
tokenizer = AutoTokenizer.from_pretrained("mistralai/Mistral-7B-Instruct-v0.3")
tokenizer.pad_token = tokenizer.eos_token

# Actor + Critic (одна модель с value head)
actor_critic = AutoModelForCausalLMWithValueHead.from_pretrained(
    "mistralai/Mistral-7B-Instruct-v0.3"
)

# Reference model (замороженная)
ref_model = AutoModelForCausalLMWithValueHead.from_pretrained(
    "mistralai/Mistral-7B-Instruct-v0.3"
)

# 2. Подготовка данных
prompts = [
    "Пользователь: Как отменить подписку на сервис?\nАссистент:",
    "Пользователь: Объясни разницу между SQL и NoSQL\nАссистент:",
    "Пользователь: Напиши поздравление с днём рождения\nАссистент:",
]

dataset = Dataset.from_dict({"prompt": prompts})

# 3. Конфигурация PPO
config = PPOConfig(
    model_name="mistral-7b-ppo",
    learning_rate=1.41e-5,
    batch_size=8,
    mini_batch_size=4,
    gradient_accumulation_steps=2,
    ppo_epochs=2,
    cliprange=0.2,
    cliprange_value=0.2,
    gamma=1.0,
    lam=0.95,
    kl_coef=0.05,
    vf_coef=0.1,
    scale_reward="ref",
    normalize_reward=True,
    top_k=0,
    top_p=1.0,
    target=6.0,
    log_with="wandb",
)

# 4. Инициализация PPO Trainer
ppo_trainer = PPOTrainer(
    config=config,
    model=actor_critic,
    ref_model=ref_model,
    tokenizer=tokenizer,
    dataset=dataset,
)

# 5. Обучение
for epoch, batch in ppo_trainer.dataloader:
    # Генерация
    question_tensors = batch["prompt"]
    response = ppo_trainer.generate(
        question_tensors,
        max_new_tokens=256,
        temperature=0.7,
        top_p=1.0,
    )
    
    # Вычисление rewards (в реальном сценарии — через Reward Model)
    reward_scores = [0.8, 0.5, 0.9, 0.6, 0.7, 0.4, 0.85, 0.55]
    
    # PPO step
    stats = ppo_trainer.step(
        question_tensors, response, reward_scores
    )
    ppo_trainer.log_stats(
        stats, batch, reward_scores
    )

Использование собственного Reward Model

from transformers import AutoModelForSequenceClassification

# Обученный Reward Model
reward_model = AutoModelForSequenceClassification.from_pretrained(
    "./reward-model-mistral-7b",
    num_labels=1,
)

def get_rewards(prompts, responses):
    """Вычисление rewards для batch"""
    rewards = []
    with torch.no_grad():
        for prompt, response in zip(prompts, responses):
            # Формируем текст для reward model
            text = prompt + response
            
            inputs = tokenizer(
                text,
                return_tensors="pt",
                truncation=True,
                max_length=512
            )
            
            # Получаем score
            output = reward_model(**inputs)
            rewards.append(output.logits[0].item())
    
    return torch.tensor(rewards)

Open-source альтернативы PPO

1. TRL (Transformer Reinforcement Learning)

pip install trl

Самая популярная библиотека от Hugging Face. Поддерживает PPO, DPO, IPO, ORPO, KTO.

2. DeepSpeed + ORCA (Microsoft)

git clone https://github.com/microsoft/DeepSpeed
cd DeepSpeed/examples/Orca

Масштабируемый PPO на сотни GPU. Используется для обучения больших моделей.

3. ColossalChat

git clone https://github.com/hpcaitech/ColossalAI
cd ColossalAI/apps/chat

Полный стек RLHF: data collection → RM training → PPO → evaluation.

4. OpenRLHF

pip install openrlhf

Новая библиотека от Ant Group, оптимизированная для LLM. В 2-3 раза быстрее TRL.


Сравнение: PPO vs DPO

DPO (Direct Preference Optimization)

DPO упрощает RLHF до бинарной классификации. Вместо четырёх моделей — одна.

RLHF (PPO):  4 модели (Actor, Critic, Reference, Reward)
DPO:         1 модель (только Actor)

Когда использовать PPO, а когда DPO?

Критерий PPO DPO
Качество Лучше (стабильнее) Почти такое же
Сложность Высокая (4 модели) Низкая (1 модель)
Стабильность Требует тонкой настройки Более стабильный
Ресурсы 2-4 GPU (минимум) 1-2 GPU
Контроль Полный (можно кастомные rewards) Ограниченный
Масштабируемость Хорошая Отличная

Практическая рекомендация

Начинаете RLHF? → DPO (проще, быстрее)
Нужно максимальное качество? → PPO (но сложнее)
Мало ресурсов (1 GPU)? → DPO или QLoRA + DPO
Кастомные функции награды? → PPO (больше контроля)

Типичные проблемы и решения

Проблема 1: Reward hacking

Симптом: Модель учится "обманывать" reward model, генерируя ответы с высокой оценкой, но плохого качества.

Prompt: "Напиши эссе о климате"
Response: "Климат — это климат. Климат важен. Климат — это..."
Reward: 0.95 (reward model "ведётся" на повторения)

Решение:

  1. Используйте KL-пенальти (kl_coef = 0.05-0.1)
  2. Нормализуйте rewards
  3. Добавьте проверку на пересечения и повторения
  4. Используйте human-validated rewards

Проблема 2: Collapse policy

Симптом: Модель генерирует один и тот же ответ на все промпты.

На всё: "Спасибо за ваш вопрос! Давайте разберёмся вместе."

Решение:

  1. Увеличьте temperature при генерации (0.7 → 0.9)
  2. Уменьшите cliprange (0.2 → 0.1)
  3. Увеличьте KL-пенальти (0.001 → 0.01)
  4. Добавьте разнообразие в промпты

Проблема 3: Нестабильное обучение

Симптом: Loss скачет, reward растёт и падает.

Решение:

  1. Уменьшите learning rate (1.41e-5 → 7e-6)
  2. Увеличьте gradient_accumulation_steps
  3. Уменьшите batch_size
  4. Используйте gradient clipping (max_grad_norm=1.0)

Проблема 4: Out of Memory

Симптом: "CUDA out of memory" на 40 ГБ GPU.

Решение:

  1. Используйте QLoRA для Actor/Critic
  2. Включите gradient checkpointing
  3. Уменьшите max_new_tokens (512 → 256)
  4. Используйте DeepSpeed ZeRO-3
  5. Уменьшите batch_size до 1

Проблема 5: Reward model предвзят

Симптом: Reward model предпочитает длинные ответы, даже если они бессодержательные.

Решение:

  1. Обучайте RM на разнообразных данных
  2. Нормализуйте rewards по длине
  3. Добавьте penalty за длину
  4. Используйте human-validated evaluation

Параметры PPO: шпаргалка

Clip-параметры

Параметр Стандарт Диапазон Описание
cliprange 0.2 0.1-0.3 Clip-радиус для policy
cliprange_value 0.2 0.1-0.3 Clip-радиус для value head
kl_coef 0.05 0.001-0.5 Вес KL-пенальти

Оптимизация

Параметр Стандарт Диапазон Описание
learning_rate 1.41e-5 1e-6 - 1e-4 Learning rate
batch_size 128 32-512 Размер batch
mini_batch_size 32 8-128 Размер mini-batch
gradient_accumulation_steps 4 1-16 Накопление градиентов
ppo_epochs 4 1-10 Эпохи PPO

GAE

Параметр Стандарт Диапазон Описание
gamma 1.0 0.9-1.0 Discount factor
lam 0.95 0.8-1.0 GAE smoothing

Value function

Параметр Стандарт Диапазон Описание
vf_coef 0.1 0.01-1.0 Вес value loss
scale_reward "ref" "ref"/"vanilla" Масштабирование rewards
normalize_reward True True/False Нормализация rewards
target 6.0 4.0-10.0 Target KL

Чек-лист: запуск PPO-RLHF

  1. Обучить SFT-модель (или взять готовую)
  2. Собрать данные предпочтений (1000+ пар chosen/rejected)
  3. Обучить Reward Model
  4. Подготовить Reference Model (копия SFT)
  5. Подготовить Actor/Critic (одна модель с value head)
  6. Настроить PPO-параметры (начните с conservative)
  7. Запустить обучение, следить за:
    • Loss падает стабильно
    • KL divergence < 10 (желательно < 3)
    • Reward растёт
    • Генерации не collapsed
  8. Провести human evaluation
  9. Сравнить с SFT-моделью на holdout-промптах
  10. Деплой обученной модели

Итоги

PPO — это золотой стандарт alignment LLM. Именно он делает GPT-4 лучше GPT-3, Claude — лучше базовых моделей.

Ключевые выводы:

  • PPO требует 4 модели: Actor, Critic, Reference, Reward
  • GAE — эффективная оценка advantage для длинных ответов
  • KL-пенальти защищает от collapse policy
  • DPO — более простая альтернатива для старта
  • TRL — лучший open-source фреймворк для PPO-RLHF

Когда стоит использовать PPO?

Да, если:

  • У вас есть ресурсы (2+ GPU, 80GB VRAM)
  • Нужно максимальное качество alignment
  • Есть данные предпочтений (1000+ пар)
  • Нужен кастомный reward function

Нет, если:

  • У вас 1 GPU с 24GB VRAM → используйте DPO
  • Нет данных предпочтений → соберите их сначала
  • Нужен быстрый результат → начните с DPO

Что дальше?

  1. DPO — если PPO кажется слишком сложным
  2. RLAIF — если хотите заменить human feedback на AI feedback
  3. ORPO — если хотите объединить SFT и RLHF в один шаг

Ресурсы


Если вам интересна тема alignment LLM, посмотрите наши статьи про DPO и reinforcement learning.