Перейти к содержимому
ПереводыHugging Face

LLM Course, глава 12 (часть 2): GRPO из DeepSeekMath по формулам

Перевод раздела 12.3b курса Hugging Face: групповая выборка, групповое преимущество, отношение вероятностей, клиппинг и KL-штраф в GRPO — с разобранным примером и кодом на PyTorch.

Это перевод раздела 12.3b курса LLM Course. Он продолжает часть 1 и разбирает GRPO уже с формулами. Дальше — часть 3: GRPO в TRL и практика.

Углубим понимание GRPO, чтобы улучшить обучение нашей модели.

GRPO оценивает ответы модели напрямую — сравнивая их внутри групп сгенерированных ответов — и так оптимизирует модель-политику, вместо того чтобы обучать отдельную модель ценности (value model, «критика»). Это заметно снижает вычислительные затраты!

GRPO применим к любой проверяемой задаче, где можно определить правильность ответа. Например, в математических рассуждениях правильность легко проверить, сравнив ответ с эталоном.

Прежде чем углубляться в технические детали, посмотрим на работу GRPO в общих чертах. (В оригинале — схема: вопрос qq подаётся политике, она выдаёт GG ответов, каждый оценивается наградой, награды нормируются в преимущества, а преимущества вместе с KL-штрафом относительно эталонной модели дают обновление политики.)

flowchart LR
Q["Вопрос q"] --> P["Политика π_old<br/>генерирует G ответов"]
P --> O["o1, o2, …, oG"]
O --> R["Награды r1…rG<br/>(функция или модель)"]
R --> A["Преимущества<br/>A_i = (r_i − mean) / std"]
A --> U["Обновление π_θ:<br/>клиппинг + KL к π_ref"]
GRPO в общих чертах (рисунок переводчика)

Теперь разберём GRPO по шагам.

Главное новшество GRPO — подход к оценке и обучению на нескольких сгенерированных ответах сразу. Вместо отдельной модели награды он сравнивает ответы внутри одной группы и решает, какие из них стоит закрепить.

Пройдём каждый шаг алгоритма подробно.

Первый шаг — сгенерировать на каждый вопрос несколько возможных ответов. Получается разнообразный набор ответов, которые можно сравнивать друг с другом.

На каждый вопрос qq модель генерирует GG ответов (размер группы) обучаемой политикой: {o1,o2,o3,…,oG}∼πθold(⋅∣q)\{o_1, o_2, o_3, \dots, o_G\} \sim \pi_{\theta_{\text{old}}}(\cdot \mid q), например G=8G = 8, где каждый oio_i — один вариант ответа модели.

Для наглядности возьмём простую арифметическую задачу.

Вопрос qq: вычислить 2+2×62 + 2 \times 6.

Ответы (G=8G = 8): {o1:14 (верно), o2:16 (неверно), o3:10 (неверно), …, o8:14 (верно)}\{o_1: 14 \text{ (верно)},\ o_2: 16 \text{ (неверно)},\ o_3: 10 \text{ (неверно)},\ \ldots,\ o_8: 14 \text{ (верно)}\}.

Обратите внимание: одни ответы верные (14), другие — нет (16 или 10). Это разнообразие критически важно для следующего шага.

Получив несколько ответов, нужно понять, какие из них лучше. Для этого и считается преимущество (advantage).

Сначала каждому ответу присваивается награда. В этом примере используем модель награды, но, как мы узнали в прошлом разделе, подойдёт любая функция, возвращающая награду.

Каждому сгенерированному ответу ставится оценка rir_i по правильности (например, 1 за верный ответ и 0 за неверный), а затем для каждого rir_i считается преимущество.

Ключевая идея GRPO: абсолютная мера качества не нужна — достаточно сравнивать ответы внутри одной группы. Для этого награды стандартизируют:

Ai=ri−mean({r1,r2,…,rG})std({r1,r2,…,rG})A_i = \frac{r_i - \text{mean}(\{r_1, r_2, \ldots, r_G\})}{\text{std}(\{r_1, r_2, \ldots, r_G\})}

Словами: из награды ответа вычитаем среднюю награду группы и делим на разброс наград в группе.

Продолжим арифметический пример. Пусть у нас 8 ответов, 4 из них верные, остальные неверные. Тогда:

ВеличинаЗначение
Среднее по группеmean(ri)=0.5\text{mean}(r_i) = 0.5
Стандартное отклонениеstd(ri)=0.53\text{std}(r_i) = 0.53
Преимущество верного ответаAi=1−0.50.53=0.94A_i = \frac{1 - 0.5}{0.53} = 0.94
Преимущество неверного ответаAi=0−0.50.53=−0.94A_i = \frac{0 - 0.5}{0.53} = -0.94

Посчитав преимущества, разберёмся, что они значат.

Стандартизация (то есть взвешивание через AiA_i) позволяет модели оценить относительное качество каждого ответа: оптимизация поощряет ответы лучше среднего (с высокой наградой) и подавляет худшие. Если Ai>0A_i > 0, ответ oio_i лучше среднего уровня в своей группе; если Ai<0A_i < 0, качество ответа ниже среднего (то есть ответ плохой).

В примере выше при Ai=0.94A_i = 0.94 (верный ответ) вероятность его генерации на шагах оптимизации будет повышаться.

Преимущества посчитаны — можно обновлять политику.

Последний шаг — с помощью преимуществ обновить модель так, чтобы в будущем она чаще выдавала хорошие ответы.

Целевая функция обновления политики:

JGRPO(θ)=1G∑i=1Gmin⁡(πθ(oi∣q)πθold(oi∣q)Ai, clip(πθ(oi∣q)πθold(oi∣q),1−ϵ,1+ϵ)Ai)−β DKL(πθ∥πref)J_{\text{GRPO}}(\theta) = \frac{1}{G} \sum_{i=1}^{G} \min \left( \frac{\pi_{\theta}(o_i \mid q)}{\pi_{\theta_{\text{old}}}(o_i \mid q)} A_i,\ \text{clip}\left( \frac{\pi_{\theta}(o_i \mid q)}{\pi_{\theta_{\text{old}}}(o_i \mid q)}, 1 - \epsilon, 1 + \epsilon \right) A_i \right) - \beta\, D_{\text{KL}}(\pi_{\theta} \Vert \pi_{\text{ref}})

Поначалу формула выглядит пугающе, но она собрана из нескольких частей, у каждой из которых своя важная роль. Разберём их по очереди.

Функция обновления GRPO сочетает несколько приёмов, чтобы обучение было устойчивым и эффективным. Разберём каждую часть.

Отношение вероятностей определяется так:

πθ(oi∣q)πθold(oi∣q)\frac{\pi_{\theta}(o_i \mid q)}{\pi_{\theta_{\text{old}}}(o_i \mid q)}

Интуитивно формула сравнивает, насколько вероятность ответа у новой модели отличается от вероятности у старой, и при этом учитывает предпочтение ответов, улучшающих ожидаемый результат.

  • Если ratio>1\text{ratio} > 1, новая модель даёт ответу oio_i большую вероятность, чем старая.
  • Если ratio<1\text{ratio} < 1, новая модель даёт oio_i меньшую вероятность.

Это отношение позволяет контролировать, насколько сильно модель меняется за шаг, и подводит нас к следующей части.

Функция обрезки определяется так:

clip(πθ(oi∣q)πθold(oi∣q),1−ϵ,1+ϵ)\text{clip}\left( \frac{\pi_{\theta}(o_i \mid q)}{\pi_{\theta_{\text{old}}}(o_i \mid q)}, 1 - \epsilon, 1 + \epsilon\right)

Она ограничивает отношение отрезком [1−ϵ,1+ϵ][1 - \epsilon, 1 + \epsilon], чтобы избежать резких изменений, «безумных» обновлений и слишком далёкого ухода от старой политики. Иначе говоря, она ограничивает рост отношения вероятностей и тем самым поддерживает устойчивость: обновления не уводят новую модель слишком далеко от старой.

Разберём два сценария, чтобы лучше понять функцию обрезки.

  • Случай 1. Новая политика даёт конкретному ответу вероятность 0.9, а старая давала 0.5. Значит, новая политика закрепляет этот ответ, повышая его вероятность, — но в контролируемых пределах: обрезка «связывает ей руки», чтобы изменение не было резким.
    • Отношение: πθ(oi∣q)πθold(oi∣q)=0.90.5=1.8→обрезка до 1.2\frac{\pi_{\theta}(o_i \mid q)}{\pi_{\theta_{\text{old}}}(o_i \mid q)} = \frac{0.9}{0.5} = 1.8 \to \text{обрезка до } 1.2 (верхняя граница 1.2).
  • Случай 2. Новая политика не в пользу ответа (меньшая вероятность, например 0.2): если ответ бесполезен, рост был бы ошибкой, и модель штрафуется.
    • Отношение: πθ(oi∣q)πθold(oi∣q)=0.20.5=0.4→обрезка до 0.8\frac{\pi_{\theta}(o_i \mid q)}{\pi_{\theta_{\text{old}}}(o_i \mid q)} = \frac{0.2}{0.5} = 0.4 \to \text{обрезка до } 0.8 (нижняя граница 0.8).
  • Формула побуждает новую модель отдавать предпочтение ответам, которые старая модель недооценивала, если они улучшают результат.
  • Если старая модель уже давала ответу высокую вероятность, новая может ещё закрепить его, но только в контролируемых пределах [1−ϵ,1+ϵ][1 - \epsilon, 1 + \epsilon] (например, при ϵ=0.2\epsilon = 0.2 — от 0.8 до 1.2).
  • Если старая модель переоценивала плохой ответ, новую модель отучают сохранять для него высокую вероятность.
  • Таким образом, благодаря отношению вероятностей обновления политики пропорциональны преимуществу AiA_i, но сдерживаются, чтобы не было резких изменений.

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

Слагаемое с KL-дивергенцией:

β DKL(πθ∥πref)\beta\, D_{\text{KL}}(\pi_{\theta} \Vert \pi_{\text{ref}})

В слагаемом KL πref\pi_{\text{ref}} — это по сути выход модели до обновления, per_token_logps, а πθ\pi_\theta — выход новой модели, new_per_token_logps. Теоретически KL-дивергенцию минимизируют, чтобы при оптимизации модель не уходила слишком далеко от исходного поведения. Это помогает найти баланс между улучшением по сигналу награды и сохранением связности. В этом контексте минимизация KL снижает риск, что модель начнёт генерировать бессмысленный текст или, в случае математических рассуждений, совсем неверные ответы.

  • Штраф KL удерживает ответы модели близко к исходному распределению и не даёт им резко сместиться.
  • Вместо того чтобы сползать к совершенно бессмысленным ответам, модель уточняет своё понимание, сохраняя при этом возможность исследовать.

Для интересующихся математическими деталями — формальное определение.

Напомним, что KL-расстояние определяется так:

DKL(P∥Q)=∑x∈XP(x)log⁡P(x)Q(x)D_{\text{KL}}(P \Vert Q) = \sum_{x \in X} P(x) \log \frac{P(x)}{Q(x)}

В RLHF два интересующих распределения — это обычно распределение новой версии модели P(x)P(x) и распределение эталонной (reference) политики Q(x)Q(x).

Коэффициент β\beta задаёт, насколько строго соблюдается ограничение KL:

  • Большой β (сильный штраф KL)
    • Сильнее ограничены обновления политики: модель остаётся близко к эталонному распределению.
    • Адаптация может замедлиться: модели труднее найти лучшие ответы.
  • Маленький β (слабый штраф KL)
    • Больше свободы в обновлении политики: модель может дальше уходить от эталона.
    • Быстрее адаптация, но выше риск неустойчивости: модель может научиться «взламывать» награду (reward hacking).
    • Риск переоптимизации: если модель награды несовершенна, политика может начать генерировать бессмыслицу.
  • В оригинальной статье DeepSeekMath β=0.04\beta = 0.04.

Поняв части GRPO, посмотрим, как они работают вместе на полном примере.

Чтобы закрепить понимание, пройдём полный пример от начала до конца.

Q: вычислить 2+2×6\text{Q: вычислить}\ 2 + 2 \times 6

Сначала генерируем несколько ответов модели.

Генерируем G=8G = 8 ответов: 4 из них верные (1414, награда 1) и 4 неверные (награда 0). Получаем:

{o1:14 (верно), o2:10 (неверно), o3:16 (неверно), …, oG:14 (верно)}\{o_1: 14 \text{ (верно)},\ o_2: 10 \text{ (неверно)},\ o_3: 16 \text{ (неверно)},\ \ldots,\ o_G: 14 \text{ (верно)}\}

Затем считаем преимущества, чтобы понять, какие ответы лучше среднего:

СтатистикаЗначение
Среднее по группеmean(ri)=0.5\text{mean}(r_i) = 0.5
Стандартное отклонениеstd(ri)=0.53\text{std}(r_i) = 0.53
Преимущество верного ответаAi=1−0.50.53=0.94A_i = \frac{1 - 0.5}{0.53} = 0.94
Преимущество неверного ответаAi=0−0.50.53=−0.94A_i = \frac{0 - 0.5}{0.53} = -0.94

Наконец, обновляем модель, закрепляя верные ответы:

  • Пусть старая политика (πθold\pi_{\theta_{\text{old}}}) давала верному ответу o1o_1 вероятность 0.50.5, а новая повысила её до 0.70.7. Тогда:

Отношение: 0.70.5=1.4→после обрезки 1.2 (ϵ=0.2)\text{Отношение: } \frac{0.7}{0.5} = 1.4 \to \text{после обрезки } 1.2\ (\epsilon = 0.2)

  • Когда целевая функция перевзвешена, модель стремится чаще генерировать верный ответ, а KL-дивергенция ограничивает уход от эталонной политики.

Теория понятна — посмотрим, как реализовать GRPO в коде.

Соберём всё в практический пример. Код ниже показывает, как реализовать GRPO на PyTorch.

Сначала загрузим модель и сгенерируем несколько ответов на вопрос:

import torch
import torch.nn.functional as F
from transformers import AutoModelForCausalLM, AutoTokenizer
# Загружаем модель и токенизатор
model_name = "Qwen/Qwen2-Math-1.5B"
model = AutoModelForCausalLM.from_pretrained(model_name)
tokenizer = AutoTokenizer.from_pretrained(model_name)
model.eval()
# Переносим модель на GPU, если он есть
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.to(device)
# Входной промпт
prompt = "Solve y = 2x + 1 for x = 2, y = " # Верный ответ: 5
inputs = tokenizer(prompt, return_tensors="pt", padding=True)
input_ids = inputs["input_ids"].to(device) # Форма: (1, prompt_len)
attention_mask = inputs["attention_mask"].to(device)
# Шаг 1: генерируем 8 ответов (B = 2 группы, G = 4 ответа в группе)
batch_size, num_generations = 2, 4
outputs = model.generate(
input_ids=input_ids, # Форма: (1, prompt_len)
attention_mask=attention_mask,
max_new_tokens=1, # seq_len = 1 (по одному токену на ответ)
num_return_sequences=batch_size * num_generations, # всего 8 ответов
do_sample=True,
top_k=10,
temperature=0.7,
pad_token_id=tokenizer.eos_token_id,
return_dict_in_generate=True,
output_scores=True,
)

Начальная генерация (до всяких шагов обучения) выдаст что-то вроде:

Output 1: 5.0
Output 2: 6.0
Output 3: 7.0
Output 4: 5.0
Output 5: 10.0
Output 6: 2.0
Output 7: 5.0
Output 8: 5.0

Теперь определим, какие ответы верны, и назначим награды.

В GRPO на один и тот же промпт генерируется несколько ответов. Например, для промптов "Solve y = 2x + 1 for x = 2, y = " и "Solve y = 2x + 1 for x = 4, y = " у нас две группы ответов:

  • [5, 6, 7, 5] и
  • [10, 2, 9, 9], а верные ответы — 5 и 9.

На практике награды выдаёт функция награды на правилах, которая проверяет правильность ответа, или более сложная нейросетевая модель, обученная оценивать правильность, или их сочетание. Для простоты пусть награда за ответ равна 1, если он верен, и 0, если нет. Тогда:

reward_1 = [1, 0, 0, 1]
reward_2 = [0, 0, 1, 1]

Дальше считаем среднее и стандартное отклонение наград по группам:

# Форма: (B * G,) = (8,), потому что 2 группы по 4 ответа мы вытягиваем в один вектор
rewards = torch.tensor([1, 0, 0, 1, 0, 0, 1, 1], dtype=torch.float32)
num_generations = 4
# Группируем награды: форма (B, G) = (2, 4)
rewards_grouped = rewards.view(-1, num_generations)
# Среднее по группе: форма (B,) = (2,)
mean_grouped_rewards = rewards_grouped.mean(dim=1)
# Стандартное отклонение по группе: форма (B,) = (2,)
std_grouped_rewards = rewards_grouped.std(dim=1)
# Растягиваем до формы наград и нормируем: форма (B * G,) = (8,)
# зачем растягивать? чтобы посчитать преимущество каждого ответа внутри его группы
mean_grouped_rewards = mean_grouped_rewards.repeat_interleave(num_generations, dim=0)
std_grouped_rewards = std_grouped_rewards.repeat_interleave(num_generations, dim=0)

Вывод:

Grouped Rewards: tensor([[1., 0., 0., 1.],
[0., 0., 1., 1.]])
Mean per group: tensor([0.5000, 0.5000])
Std per group: tensor([0.5774, 0.5774])
Broadcasted Mean: tensor([0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000])
Broadcasted Std: tensor([0.5774, 0.5774, 0.5774, 0.5774, 0.5774, 0.5774, 0.5774, 0.5774])

Теперь считаем преимущество каждого ответа:

# Преимущества: форма (B * G,) = (8,)
advantages = (rewards - mean_grouped_rewards) / (std_grouped_rewards + 1e-8)

Вывод:

Advantages: tensor([ 0.8659, -0.8660, -0.8660, 0.8659, -0.8660, -0.8660, 0.8659, 0.8659])

Это следует из формулы преимущества выше:

Для reward_1 = [1, 0, 0, 1]:
(1 - 0.5) / 0.5774 ≈ 0.8659
(0 - 0.5) / 0.5774 ≈ -0.8660
Для reward_2 = [0, 0, 1, 1] — та же картина.

Но форма здесь (B*G,) = (8,), а на практике она должна совпадать с формой логитов, верно? Поэтому добавим тензору преимуществ измерение и получим форму (B*G, 1) = (8, 1):

# Форма (B * G, 1) = (8, 1), чтобы совпасть с формой логитов
advantages = advantages.unsqueeze(1)

Вывод:

Advantages: tensor([[ 0.8659],
[-0.8660],
[-0.8660],
[ 0.8659],
[-0.8660],
[-0.8660],
[ 0.8659],
[ 0.8659]])

Теперь всё готово — переходим к обновлению модели-политики по преимуществам.

Наконец, используем преимущества для обновления модели:

# Отношение вероятностей новой и старой политики
ratio = torch.exp(
new_per_token_logps - per_token_logps
) # Форма: (B*G, seq_len); seq_len — длина ответа, то есть число сгенерированных токенов; для простоты здесь 1 → (8, 1)

Заметим, что per_token_logps можно получить, прогнав сгенерированные ответы через модель, взяв логиты и применив к ним softmax, чтобы получить вероятности: F.softmax(logits, dim=-1).

# Функция обрезки
eps = self.cliprange # например, 0.2
pg_losses1 = -advantages * ratio # Форма: (B*G, seq_len) → (8, 1)
pg_losses2 = -advantages * torch.clamp(
ratio, 1.0 - eps, 1.0 + eps
) # Форма: (B*G, seq_len) → (8, 1)
pg_loss_max = torch.max(pg_losses1, pg_losses2) # Форма: (B*G, seq_len) → (8, 1)
# Теперь добавляем штраф KL. Форма: (B*G, seq_len) → (8, 1)
per_token_loss = pg_loss_max + self.beta * per_token_kl

per_token_kl можно посчитать так:

# Форма: (B*G, seq_len) → (8, 1)
per_token_kl = F.kl_div(
F.log_softmax(new_per_token_logps, dim=-1),
F.softmax(per_token_logps, dim=-1),
reduction="none",
).sum(dim=-1, keepdim=True)

Полный пример есть здесь (примечание переводчика: на момент перевода файла по этой ссылке в репозитории нет). GRPO реализован и замечательной командой TRL — подробности в коде TRL/GRPO_trainer.

Этот скрипт целиком считает loss GRPO на игрушечных числах и запускается на CPU за доли секунды. Логика — как в GRPOTrainer из TRL.

import torch
torch.manual_seed(0)
G = 4 # размер группы
rewards = torch.tensor([1., 0., 0., 1., 0., 0., 1., 1.]) # 2 группы по 4 ответа
# 1) Групповые преимущества
grouped = rewards.view(-1, G) # (2, 4)
mean = grouped.mean(dim=1).repeat_interleave(G) # (8,)
std = grouped.std(dim=1).repeat_interleave(G) # (8,) выборочное std, как в torch по умолчанию
adv = ((rewards - mean) / (std + 1e-4)).unsqueeze(1) # (8, 1)
print("advantages:", adv.squeeze().round(decimals=4))
# 2) Лог-вероятности сгенерированных токенов (B*G, seq_len) — здесь игрушечные числа
seq_len = 3
old_logps = torch.log(torch.full((8, seq_len), 0.5)) # π_old: модель при генерации
ref_logps = torch.log(torch.full((8, seq_len), 0.5)) # π_ref: замороженная исходная модель
new_logps = old_logps + 0.3 * adv # π_θ: после шага верные ответы стали вероятнее
# 3) Отношение вероятностей и клиппинг (PPO-стиль)
eps, beta = 0.2, 0.04
ratio = torch.exp(new_logps - old_logps)
loss1 = -adv * ratio
loss2 = -adv * torch.clamp(ratio, 1 - eps, 1 + eps)
pg_loss = torch.max(loss1, loss2)
# 4) KL-оценка k3 (как в TRL и DeepSeekMath): exp(ref - new) - (ref - new) - 1
d = ref_logps - new_logps
per_token_kl = torch.exp(d) - d - 1
# 5) Итоговый loss: среднее по токенам ответа, затем по ответам
per_token_loss = pg_loss + beta * per_token_kl
loss = per_token_loss.mean(dim=1).mean()
print("ratio (верный, неверный):", round(ratio[0, 0].item(), 4), round(ratio[1, 0].item(), 4))
print("k3 KL (верный):", round(per_token_kl[0, 0].item(), 4))
print("loss:", round(loss.item(), 4))

Вывод:

advantages: tensor([ 0.8659, -0.8659, -0.8659, 0.8659, -0.8659, -0.8659, 0.8659, 0.8659])
ratio (верный, неверный): 1.2966 0.7712
k3 KL (верный): 0.031
loss: -0.1718

Как читать результат: у верного ответа отношение 1.30 обрезается до 1.2, вклад −0.866⋅1.2=−1.039-0.866 \cdot 1.2 = -1.039; у неверного отношение 0.77 «поднимается» до 0.8, вклад +0.866⋅0.8=0.693+0.866 \cdot 0.8 = 0.693. Среднее (−1.039+0.693)/2=−0.173(-1.039 + 0.693) / 2 = -0.173, плюс крошечный KL-штраф 0.04⋅0.031≈0.0010.04 \cdot 0.031 \approx 0.001 — итого −0.1718-0.1718.

Поздравляем! Вы разобрались в групповой относительной оптимизации политики (GRPO). Вспомним, что мы прошли:

  1. GRPO сравнивает несколько ответов внутри группы, чтобы понять, какие лучше, — без отдельной модели ценности.
  2. Расчёт преимущества стандартизирует награды и показывает, какие ответы выше или ниже среднего.
  3. Обновление политики использует обрезанную целевую функцию со штрафом KL, чтобы обучение было устойчивым.

Этот подход особенно силён в задачах математических рассуждений, где правильность можно проверить объективно. GRPO позволяет обучать эффективнее традиционных подходов RLHF, которым нужна отдельная модель-критик.

Продолжая изучать GRPO, поэкспериментируйте с размером группы, функциями награды и коэффициентом штрафа KL и посмотрите, как они влияют на качество модели.

Удачного обучения! 🚀

  1. RLHF Book Натана Ламберта
  2. Технический отчёт DeepSeek-V3
  3. DeepSeekMath
  1. Посчитайте на бумаге преимущества для группы наград [1,1,1,0][1, 1, 1, 0] (выборочное std). Проверьте себя кодом из примера. (Ответ: ≈+0.5\approx +0.5 у верных и −1.5-1.5 у неверного.)
  2. В исправленном примере поставьте beta = 0 и beta = 1. Как меняется loss? Почему при beta = 0 эталонная модель вообще не нужна?
  3. Замените в примере сдвиг 0.3 * adv на 0.05 * adv. Срабатывает ли теперь обрезка? (Подсказка: e0.05⋅0.866≈1.04e^{0.05 \cdot 0.866} \approx 1.04.)
  4. Перейдите к CodeExercise в уроке 6.4.1 — там то же самое на NumPy с автотестами.
EnglishРусский
group samplingгрупповая выборка
group relative advantageгрупповое относительное преимущество
value model, criticмодель ценности, критик
probability ratioотношение вероятностей
clipping, clip range εобрезка, диапазон обрезки ε
reference policy πref\pi_{\text{ref}}эталонная (референсная) политика
old policy πθold\pi_{\theta_{\text{old}}}старая политика (снимок на момент генерации)
KL estimator k3KL-оценка k3
reward hacking«взлом» награды
per-token log-probabilitiesлог-вероятности токенов