Перейти к содержимому
Ступень 66.4 Рассуждения и RL — путь DeepSeek-R1

GRPO — RL без критика

Как DeepSeek убрали из PPO value-модель: группа ответов на один промпт, нормировка наград внутри группы, клиппинг, KL-оценка k3 — и почему это вдвое дешевле по памяти.

УРОКstage-6/6-4-reasoning/01-grpo.mdx1 ч 50 минУрок 1 из 5
После урока вы сможете
  • Объяснить, чем GRPO заменяет критика, и посчитать групповые преимущества руками
  • Прочитать формулу loss GRPO по частям — отношение вероятностей, клиппинг, усреднение 1/∣oi∣1/\vert o_i \vert, KL-оценка k3
  • Реализовать групповые преимущества, KL k3 и GRPO loss на NumPy
  • Оценить, где GRPO экономит (память, критик) и где платит (генерация GG ответов на промпт)

В уроке 6.3.2 вы видели цену классического RLHF с PPO (сам алгоритм — в уроке 6.0.3): в памяти одновременно живут четыре модели, две из них обучаются. Самая неудобная — критиккритик, value-модельEN critic / value modelСеть, предсказывающая ожидаемую награду из текущего состояния; в PPO служит baseline для каждого токена. (value-модель) VψV_\psi. Он размером с саму LLM, его надо учить параллельно с политикой, а учить его трудно: награда приходит один раз, в конце ответа, и критику приходится угадывать по началу ответа, чем всё закончится.

В феврале 2024 года DeepSeek в статье о DeepSeekMath предложили выбросить критика совсем. Вместо него — простая идея из урока 6.3.1: baseline. Только считается он не обученной сетью, а честно, по выборке: сгенерируем на один и тот же вопрос несколько ответов и сравним каждый со средним. Алгоритм назвали GRPOGRPO (групповая относительная оптимизация политики)EN Group Relative Policy Optimization (GRPO)PPO без критика: на промпт генерируется группа из G ответов, преимущество — (r − среднее) / std по группе. DeepSeekMath, 2024. — Group Relative Policy Optimization. Через год именно он обучил DeepSeek-R1, а сегодня GRPO и его потомки — стандарт RL для рассуждений.

Аналогия — оценка «по кривой». Учитель не знает заранее, насколько трудна контрольная. Он смотрит, как её написал весь класс: «пятёрка» — это заметно выше среднего по классу, «двойка» — заметно ниже. Класс здесь — группа ответов модели на один промпт.

flowchart LR
subgraph PPO["PPO: 4 модели"]
  q1["Промпт q"] --> p1["Политика"] --> o1["1 ответ o"]
  o1 --> rm1["Reward model"] --> gae["GAE"]
  o1 --> v1["Критик V — обучается"] --> gae
  o1 --> ref1["Референс"] -->|"KL в награду"| gae
  gae --> a1["A_t на каждый токен"]
end
subgraph GRPO["GRPO: без критика"]
  q2["Промпт q"] --> p2["Политика"] --> og["G ответов o_1 … o_G"]
  og --> rm2["Награды r_1 … r_G: правила или RM"] --> norm["(r_i − mean) / std по группе"]
  norm --> a2["A_i — одно на весь ответ"]
  og --> ref2["Референс"] -->|"KL прямо в loss"| loss["Loss"]
  a2 --> loss
end
Слева — PPO из урока 6.3.2, справа — GRPO. Критик и GAE заменены статистикой группы

Что изменилось:

  1. Нет критика. Baselineбазовая линия (baseline)EN baselineЧисло, которое вычитают из награды; не смещает градиент политики, но снижает его дисперсию. — средняя награда группы. Это эмпирическая оценка функции ценности V(промпт)V(\text{промпт}) из урока 6.0.1: «сколько обычно получает модель на этом вопросе».
  2. Нет GAEобобщённая оценка преимущества (GAE)EN generalized advantage estimation (GAE)Schulman и др., 2015. Смешивает оценки преимущества разной длины с коэффициентом λ. (урок 6.0.3). Преимуществопреимущество (advantage)EN advantageA = r − b или A(s, a) = Q(s, a) − V(s): насколько действие лучше среднего в этой ситуации. одно на весь ответ и одинаково для всех его токенов. Мы теряем покомпонентную «раздачу заслуг», зато избавляемся от самой капризной части PPO.
  3. KL переехал из награды в loss. В PPO штраф −βlog⁡(πθ/πref)-\beta \log(\pi_\theta/\pi_{\text{ref}}) подмешивали к награде каждого токена (урок 6.3.1). В GRPO награда остаётся «чистой», а KL вычитается отдельным слагаемым в функции потерь.
  4. Награды могут быть правилами. В самой DeepSeekMath награду давала нейросетевая reward model. Но GRPO всё равно, откуда число: в R1-Zero (следующий урок) это уже проверка ответа программой — проверяемое вознаграждениепроверяемое вознаграждениеEN verifiable reward (RLVR). В статье есть и вариант с наградой за каждый шаг решения: её выставляет процессная модель вознаграждения (PRMпроцессная модель вознаграждения (PRM)EN process reward model (PRM)Оценивает каждый шаг рассуждения, а не только итоговый ответ. В DeepSeek-R1 описана как «неудачная попытка».), а преимущество токена равно сумме нормированных наград всех последующих шагов. Но в историю вошёл простой вариант — одна награда за весь ответ.

На промпт qq текущая политика генерирует группу {o1,…,oG}\{o_1, \dots, o_G\} и получает награды r1,…,rGr_1, \dots, r_G. Преимущество:

Ai=ri−mean(r1,…,rG)std(r1,…,rG)A_i = \frac{r_i - \mathrm{mean}(r_1, \dots, r_G)}{\mathrm{std}(r_1, \dots, r_G)}

Что здесь написано. Числитель — «насколько этот ответ лучше среднего по своей группе». Знаменатель — стандартное отклонение наград группы (урок 1.3.1; здесь «популяционное»: делим на GG, как np.std по умолчанию; torch.std и TRL делят на G−1G - 1, и преимущества у них чуть меньше по модулю) — приводит разброс к единице: неважно, награды от 0 до 1 или от −5 до 5, преимущества будут одного масштаба. Пример для первой строки таблицы ниже: награды [1,0,0,0][1, 0, 0, 0], среднее 0.25, отклонения [0.75,−0.25,−0.25,−0.25][0.75, -0.25, -0.25, -0.25], std =(0.5625+3⋅0.0625)/4≈0.433= \sqrt{(0.5625 + 3 \cdot 0.0625)/4} \approx 0.433, и A1=0.75/0.433≈1.73A_1 = 0.75 / 0.433 \approx 1.73.

Посчитаем для G=4G = 4 и награды «1 за верный ответ, 0 за неверный»:

Награды группыСреднееstdПреимущества AiA_iЧто делает градиент
[1,0,0,0][1, 0, 0, 0]0.250.433[1.73,−0.58,−0.58,−0.58][1.73, -0.58, -0.58, -0.58]сильно поднимает редкий верный ответ
[1,1,0,0][1, 1, 0, 0]0.50.5[1,1,−1,−1][1, 1, -1, -1]симметрично
[1,1,1,0][1, 1, 1, 0]0.750.433[0.58,0.58,0.58,−1.73][0.58, 0.58, 0.58, -1.73]сильно опускает редкую ошибку
[0,0,0,0][0, 0, 0, 0]00[0,0,0,0][0, 0, 0, 0]ничего: сигнала нет

Последняя строка — главный «подводный камень» GRPO. Если все ответы группы одинаково хороши или одинаково плохи, сравнивать не с чем, и вся группа — сгенерированная, между прочим, дорогой ценой — не даёт градиента. Такие группы называют вырожденнымивырожденная группаEN degenerate group (zero-variance group)Все ответы группы получили одинаковую награду — все преимущества нулевые, градиента нет. (так их называет и README MiniMind). Вероятность, что группа «живая», при точности модели pp на этом промпте:

P(есть сигнал)=1−pG−(1−p)GP(\text{есть сигнал}) = 1 - p^G - (1 - p)^G
Точность ppG=2G = 2G=4G = 4G=8G = 8G=16G = 16G=64G = 64
0.02 или 0.980.040.080.150.280.73
0.1 или 0.90.180.340.570.810.999
0.50.50.880.991.01.0

Отсюда два практических правила. Первое: задачи должны быть по силам модели — ни слишком лёгкие, ни безнадёжные. Второе: группа побольше спасает трудные промпты. В DeepSeekMath брали G=64G = 64, в DeepSeek-R1 — 16, в TRL по умолчанию 8. А DAPO (урок 6.4.3) просто выбрасывает вырожденные группы и досэмплирует новые.

Соберём формулу из знакомых кусков.

Шаг 1. REINFORCE с групповым baseline. Для каждого токена tt ответа oio_i хотим увеличить log⁡πθ(oi,t)\log \pi_\theta(o_{i,t}) пропорционально AiA_i — это градиент политики из урока 6.3.1.

Шаг 2. Отношение вероятностей и клиппинг из PPO. Ответы сгенерированы «старой» политикой πθold\pi_{\theta_{\text{old}}}. Если по одному батчу делать несколько шагов, нужна поправка ρi,t=πθ(oi,t∣q,oi,<t)/πθold(oi,t∣q,oi,<t)\rho_{i,t} = \pi_\theta(o_{i,t} \mid q, o_{i,<t}) / \pi_{\theta_{\text{old}}}(o_{i,t} \mid q, o_{i,<t}) и клиппинг [1−ε,1+ε][1 - \varepsilon, 1 + \varepsilon] — всё как в PPO (уроки 6.0.3 и 6.3.2).

Шаг 3. Усреднение. Сначала среднее по токенам внутри ответа (множитель 1/∣oi∣1/\vert o_i \vert), потом среднее по GG ответам.

Шаг 4. KL к референсу — вычитается на каждом токене с коэффициентом β\beta.

Целиком (формула (3) из DeepSeekMath):

JGRPO(θ)=E[1G∑i=1G1∣oi∣∑t=1∣oi∣(min⁡(ρi,tAi, clip(ρi,t,1−ε,1+ε) Ai)−β Di,t)]\mathcal{J}_{\text{GRPO}}(\theta) = \mathbb{E}\left[ \frac{1}{G} \sum_{i=1}^{G} \frac{1}{|o_i|} \sum_{t=1}^{|o_i|} \Big( \min\big(\rho_{i,t} A_i,\ \mathrm{clip}(\rho_{i,t}, 1-\varepsilon, 1+\varepsilon)\, A_i\big) - \beta\, \mathbb{D}_{i,t} \Big) \right]

Loss для оптимизатора — −J-\mathcal{J}. Как читать: «для каждого ответа группы, для каждого его токена — клиппированное “поднять/опустить по преимуществу” минус штраф за уход от референса; усреднить по токенам ответа, потом по ответам».

Точную KL-дивергенциюKL-дивергенция (дивергенция Кульбака — Лейблера)EN Kullback–Leibler divergenceKL(p ‖ q) = Σ p log(p / q) = H(p, q) − H(p) ≥ 0. Несимметрична, поэтому не настоящее расстояние. между распределениями над всеми возможными ответами не посчитать. Её оценивают по сэмплу — по токенам, которые модель действительно сгенерировала. Обозначим ρ=πref(oi,t)/πθ(oi,t)\rho = \pi_{\text{ref}}(o_{i,t}) / \pi_\theta(o_{i,t}). Есть три популярные оценки (по блогу Джона Шульмана):

ОценкаФормулаНесмещённая?Знак
k1−log⁡ρ=log⁡πθ−log⁡πref-\log \rho = \log \pi_\theta - \log \pi_{\text{ref}}дабывает отрицательной
k212(log⁡ρ)2\tfrac12 (\log \rho)^2нет (но близка при малом расхождении)≥0\ge 0
k3ρ−log⁡ρ−1\rho - \log \rho - 1давсегда ≥0\ge 0

Оценка k1 — та самая разность логарифмов, которой вы считали KL в уроке 6.3.1. GRPO использует k3 — оценка KL «k3»оценка KL «k3»EN k3 KL estimatorρ − log ρ − 1, где ρ = π_ref / π_θ: несмещённая оценка KL(π_θ ‖ π_ref) по сэмплу, всегда ≥ 0. В verl — kl_loss_type=low_var_kl. (в статье — формула (4)). Почему она несмещённая: токены сэмплированы из πθ\pi_\theta, а Eπθ[ρ]=∑xπθ(x)πref(x)πθ(x)=1\mathbb{E}_{\pi_\theta}[\rho] = \sum_x \pi_\theta(x) \frac{\pi_{\text{ref}}(x)}{\pi_\theta(x)} = 1. Значит, E[ρ−1]=0\mathbb{E}[\rho - 1] = 0, и E[k3]=E[−log⁡ρ]=KL(πθ∥πref)\mathbb{E}[\text{k3}] = \mathbb{E}[-\log \rho] = \mathrm{KL}(\pi_\theta \Vert \pi_{\text{ref}}). А неотрицательна она потому, что x−1≥log⁡xx - 1 \ge \log x для любого x>0x > 0.

Пример с числами.

  • Модель дала токену 0.9, референс — 0.3: ρ=1/3\rho = 1/3, k1 =ln⁡3=1.10= \ln 3 = 1.10, k3 =0.333+1.099−1=0.43= 0.333 + 1.099 - 1 = 0.43.
  • Наоборот, модель 0.3, референс 0.9: ρ=3\rho = 3, k1 =−1.10= -1.10 (отрицательная!), k3 =3−1.099−1=0.90= 3 - 1.099 - 1 = 0.90.

Мы проверили на распределениях πθ=[0.6,0.3,0.1]\pi_\theta = [0.6, 0.3, 0.1], πref=[0.3,0.4,0.3]\pi_{\text{ref}} = [0.3, 0.4, 0.3] и 100 тысячах сэмплов: точная KL = 0.2197, среднее k1 = 0.2209, среднее k3 = 0.2190. Обе оценки попадают в цель, но стандартное отклонение у k1 — 0.62, а у k3 — 0.24, в 2.6 раза меньше. И 40% значений k1 отрицательны: штраф, который на отдельных токенах «поощряет» уход от референса, — плохая идея для обучения. В verl эта оценка так и называется: kl_loss_type=low_var_kl.

Сколько брать β\beta: в DeepSeekMath — 0.04, в R1 — уже 0.001. А DAPO и TRL по умолчанию ставят β=0\beta = 0: для длинных рассуждений модель должна далеко уйти от исходной, и якорь мешает. Плюс без KL не нужна референсная модель — минус одна модель в памяти.

Прикинем память для политики на 7B так же, как в уроке 6.3.2 (16 байт на обучаемый параметр, 2 байта на замороженный):

МетодМодели в памятиВеса + оптимизатор
PPOполитика + критик (обучаются), референс + RM≈ 252 ГБ
GRPO с RM (как в DeepSeekMath)политика + референс + RM≈ 140 ГБ
GRPO с правиловой наградой (R1-Zero)политика + референс≈ 126 ГБ
GRPO, β=0\beta = 0 (DAPO, TRL по умолчанию)только политика≈ 112 ГБ

Экономия почти вдвое — и минус целая обучаемая сеть со своими гиперпараметрами. Но бесплатно ничего не бывает: на каждый промпт теперь нужно GG ответов, и роллаутроллаут (генерация ответов для RL)EN rolloutЭтап RL-шага, на котором текущая политика генерирует ответы на промпты. В GRPO — самая дорогая часть. дорожает в GG раз. В DeepSeekMath это 64 ответа до 1024 токенов на вопрос, батч — 1024 ответа (16 вопросов × 64). Генерация и раньше была самой медленной частью RL, а в GRPO её объём умножается на GG. Поэтому вся RL-инфраструктура 2025–2026 годов (vLLM и SGLang внутри verl, асинхронные системы вроде slime) оптимизирует прежде всего генерацию.

В DeepSeekMath есть красивое наблюдение (§5.2): почти все методы пост-трейнинга дают градиент вида «коэффициент × ∇log⁡πθ(токен)\nabla \log \pi_\theta(\text{токен})». Различаются данные и коэффициент:

МетодЧьи ответыКоэффициент градиента
SFTобразцовые, от людей1
RFT (rejection sampling fine-tuning)сэмплы исходной модели, только верные1 для верных, неверные выброшены
Online RFTсэмплы текущей модели, только верные1 для верных
PPOсэмплы текущей моделиAtA_t от критика
GRPOгруппы сэмплов текущей моделиAiA_i от группы, со знаком

Эксперимент статьи: GRPO лучше Online RFT, а Online RFT лучше обычного RFT. Онлайн-данные важнее офлайн, а умение не только поощрять верное, но и наказывать неверное (отрицательный коэффициент) — важнее, чем кажется.

Реализуйте четыре функции на NumPy: групповые преимущества, KL-оценку k3, клиппированную цель и полный loss GRPO с маской паддинга. Тесты сверят значения с формулами этого урока.

Групповые преимущества и loss GRPOnumpy · нужен интернет8 тестов
import numpy as np


def group_advantages(rewards, eps=1e-6):
    """Групповые преимущества GRPO: (r - среднее группы) / (std группы + eps).

    rewards — массив [B, G]: B промптов, на каждый G ответов.
    Среднее и std считайте по каждой строке (axis=1, keepdims=True);
    std — «популяционное» (ddof=0), как np.std по умолчанию.
    Вернуть массив [B, G].
    """
    # TODO: ваш код
    ...


def kl_k3(logp, logp_ref):
    """Оценка KL(π_θ ‖ π_ref) «k3» для каждого токена: ρ - log ρ - 1, где ρ = π_ref / π_θ.

    logp, logp_ref — лог-вероятности токенов (числа или массивы одной формы).
    Подсказка: log ρ = logp_ref - logp.
    """
    # TODO: ваш код
    ...


def clipped_surrogate(logp, logp_old, adv, eps=0.2):
    """Клиппированная цель для каждого токена: min(ρ·A, clip(ρ, 1-eps, 1+eps)·A), ρ = π_θ / π_old.

    logp, logp_old — [N, T]; adv — [N] (одно преимущество на весь ответ).
    Вернуть массив [N, T].
    """
    # TODO: ваш код
    ...


def grpo_loss(logp, logp_old, logp_ref, adv, mask, eps=0.2, beta=0.04):
    """Loss GRPO (со знаком минус — для минимизации), как в DeepSeekMath.

    1) на каждом токене: clipped_surrogate(...) - beta * kl_k3(...);
    2) среднее по токенам ОТВЕТА (mask = 1) внутри каждого ответа — множитель 1/|o_i|;
    3) среднее по всем ответам; 4) поставить минус. Вернуть float.
    """
    # TODO: ваш код
    ...


# Проверьте себя кнопкой «Запустить»:
print(group_advantages([[1, 0, 0, 0], [1, 1, 0, 0]]))
print(kl_k3(np.log(0.9), np.log(0.3)))  # ≈ 0.4319
lp = np.log([[0.5, 0.4, 0.9], [0.2, 0.3, 0.6]])
print(grpo_loss(lp, lp, lp, [1.0, -1.0], [[1, 1, 1], [1, 1, 0]]))  # 0.0 в начале шага

После решения нажмите «Запустить»: последняя строка напечатает 0.0. Это не ошибка — см. следующий раздел.

Частый вопрос новичков, впервые запустивших GRPO в TRL или verl: «loss всё время около нуля — обучение сломано?» Проверим на PyTorch, что происходит. Возьмём группу из 4 ответов разной длины и посмотрим на градиент loss прямо по лог-вероятности каждого токена.

import torch
rewards = torch.tensor([1.0, 1.0, 0.0, 0.0]) # группа из G = 4 ответов: два верных, два нет
lengths = torch.tensor([3, 6, 3, 6]) # длины ответов в токенах
adv = (rewards - rewards.mean()) / (rewards.std(unbiased=False) + 1e-6) # unbiased=False — делим на G, как в формуле
# «Лог-вероятности» токенов: [4 ответа, 6 позиций]. Сделаем их листом графа,
# чтобы посмотреть градиент loss прямо по log π(токена).
torch.manual_seed(0)
logp = torch.log(torch.rand(4, 6) * 0.5 + 0.4).requires_grad_(True)
mask = (torch.arange(6)[None, :] < lengths[:, None]).float() # 1 — токен ответа, 0 — паддинг
ratio = torch.exp(logp - logp.detach()) # одно обновление на батч: π_old = π_θ, ρ ≡ 1
surr = torch.min(ratio * adv[:, None], ratio.clamp(0.8, 1.2) * adv[:, None])
loss = -((surr * mask).sum(1) / mask.sum(1)).mean() # формула DeepSeekMath (β = 0)
loss.backward()
print(f"loss = {round(loss.item(), 4) + 0.0}")
for i in range(4):
w = logp.grad[i, 0].item()
print(f"ответ {i}: A = {adv[i]:+.2f}, длина {lengths[i]}, d loss / d log π(токена) = {w:+.4f}")

Вывод:

loss = 0.0
ответ 0: A = +1.00, длина 3, d loss / d log π(токена) = -0.0833
ответ 1: A = +1.00, длина 6, d loss / d log π(токена) = -0.0417
ответ 2: A = -1.00, длина 3, d loss / d log π(токена) = +0.0833
ответ 3: A = -1.00, длина 6, d loss / d log π(токена) = +0.0417

Что видно:

  • Loss = 0, потому что при ρ=1\rho = 1 каждый ответ вносит ровно AiA_i, а нормированные преимущества группы в сумме дают ноль. Значение loss в GRPO почти ничего не говорит — смотрите на награду, длину ответов и KL.
  • Градиент не ноль. Отрицательный знак означает, что оптимизатор будет поднимать log⁡π\log \pi токенов верных ответов, положительный — опускать у неверных. Формула веса: −Ai/(G⋅∣oi∣)-A_i / (G \cdot \vert o_i \vert), например −1/(4⋅3)=−0.0833-1/(4 \cdot 3) = -0.0833.
  • Вес зависит от длины. Токен короткого неверного ответа «наказывается» вдвое сильнее, чем токен длинного неверного. Длинный неверный ответ отделывается лёгким испугом — и модель потихоньку учится, что ошибаться лучше многословно. Этот перекос множителя 1/∣oi∣1/\vert o_i \vert нашли в 2025 году и исправили в DAPO и Dr. GRPO — урок 6.4.3.
Проверь себя6 вопросов
1 Какую модель GRPO убирает из классического PPO-конвейера RLHF?
2 Группа из G=5G = 5 ответов получила награды [1,1,0,0,0][1, 1, 0, 0, 0]. Чему равно преимущество верного ответа? std — популяционное, округлите до двух знаков.
3 Какие утверждения о GRPO верны?Можно выбрать несколько ответов
4 Модель решает трудный промпт с вероятностью p=0.1p = 0.1. С какой вероятностью группа из G=8G = 8 ответов даст ненулевой сигнал? Округлите до двух знаков.
5 Модель дала токену вероятность 0.5, референсная — 0.25. Чему равна оценка k3? Округлите до двух знаков.
6 В логах GRPO loss держится около нуля с первого шага. Что это значит?
  • GRPO — PPO без критика: baseline — средняя награда группы из GG ответов, преимущество Ai=(ri−mean)/stdA_i = (r_i - \mathrm{mean}) / \mathrm{std} одно на весь ответ.
  • Loss: клиппированное ρA\rho A на каждом токене, минус β⋅\beta \cdot KL-оценка k3; усреднение — по токенам ответа, затем по группе.
  • Вырожденные группы (все награды равны) не дают сигнала: задачи должны быть по силам, а GG — достаточно большим.
  • Экономия — минус критик (почти вдвое меньше памяти). Цена — в GG раз больше генерации.
  • Множитель 1/∣oi∣1/\vert o_i \vert и деление на std вносят скрытые перекосы — их исправят в 2025 году.

Дальше: DeepSeek-R1-Zero — GRPO с правиловыми наградами прямо на базовой модели, рост длины рассуждений и «момент озарения».