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

CS336, задание 5 (alignment): RL для рассуждений — путеводитель и пересказ условия

Пятое задание Stanford CS336 (весна 2026) пересказом: промптинг OLMo-2 1B на GSM8K, вывод policy gradient и GRPO с нуля, свой цикл обучения с vLLM, сравнение Dr. GRPO, RFT и MaxRL, off-policy с отсечением PPO и GSPO, собственный оценщик градиента. Плюс необязательное дополнение про SFT и DPO на Llama 3.1 8B и план для 4 × RTX 4080.

Задание 5 — о постобучении с подкреплением: базовую модель OLMo-2 на 1 млрд параметров учат решать школьные задачи GSM8K, награждая за правильный ответ. Вы сами выводите policy gradient и GRPO, пишете цикл обучения с генерацией через vLLM и проводите контролируемые сравнения вариантов алгоритма.

  • Когда: после уроков 6.4.1 «GRPO», 6.4.2 «R1-Zero» и лекций 15–17 CS336 (особенно лекции 17 с кодом policy gradient).
  • Что понадобится: 2 GPU (одна учит, вторая генерирует через vLLM), Hugging Face transformers, vLLM, PyTorch, W&B или другой логгер. Бюджет экспериментов в условии — около 26 часов B200 (2 + 4 + 4 + 8 + 8).
  • Баллы: 90 в обязательной части; дополнение необязательное и оценивается отдельно.
РазделЧто делаетеБаллыУрок курса
3. Промптингquestion_only, r1_zero и r1_zero с тремя примерами на GSM8K56.4.2
4.1. Вывод GRPOДисперсия оценщика градиента с базовой линией (задача на бумаге)56.0.3
4.2. Компоненты GRPOТокенизация, log-вероятности, награды, преимущества, потери, шаг обучения10,56.4.1
4.3. Эксперименты GRPOСвой цикл, 4 зерна, подбор скорости обучения, абляция промпта166.4.5
5. ВариантыDr. GRPO, RFT, MaxRL: теория, код, сравнение256.4.3
6. Off-policyВзвешивание по важности, отсечение PPO/GRPO, GSPO18,56.0.3
7. Свой оценщикПредложить и проверить свою модификацию106.4.3

Что реализовать: промптинг без примеров, с примерами и с цепочкой рассуждений (chain-of-thought); алгоритм GRPO (Group Relative Policy Optimization), улучшающий модель по внешним наградам; варианты оценки policy gradient — со снижением дисперсии и с отсечением весов важности.

Что запустить: замер качества промптинга OLMo-2-0425-1B на GSM8K; on-policy GRPO; варианты RL (RFT, Dr. GRPO, MaxRL); off-policy GRPO с разными способами отсечения.

Как устроен код. Модуль cs336_alignment/ почти пуст — вы пишете всё сами. Готовы только: vllm_utils.py (сервер vLLM для генерации и синхронизации весов), drgrpo_grader.py (проверка ответов к математическим задачам), prompts/* (тексты промптов) и checkpoint.py (загрузка модели). Обязательные тесты — tests/test_grpo.py, они вызывают переходники из tests/adapters.py. Сдаются writeup.pdf и code.zip, архив собирает test_and_make_submission.sh.

Зачем RL. В заданиях 1–4 вы учились предобучать базовую модель. Постобучение превращает её в полезный инструмент. Одна его часть — выравнивание (instruction tuning): из «всезнающей» базовой модели сделать сфокусированного полезного и безопасного ассистента; это тема необязательного дополнения. Другая часть — обучение с подкреплением. Предобучение стремится к охвату (широкие знания на широких данных), а RL на конкретной задаче — к точности (правильные ответы), причём данных гораздо меньше. В RL у нас есть набор вопросов и функция оценки, которая проверяет ответ: для программирования это тесты (assert f([0, 1, 2]) == [2, 1, 0]), для математики — разбор финального ответа и сравнение с эталоном. Правильных решений-образцов нам не дают: мы делаем шаги градиента прямо по точности модели — сэмплируем ответы, оцениваем их и усиливаем правильные. Авторы предупреждают: RL медленный и нестабильный, у запусков большая дисперсия по случайным зёрнам, и мелкие детали реализации сильно влияют на результат.

Модель и данные. Модель — OLMo-2-0425-1B: предобучена на OLMo-mix-1124 (в основном DCLM-Baseline) и дообучена на «среднем этапе» (mid-training) на Dolmino-mix-1124 (примерно поровну DCLM и инструкции, математика, код, статьи STEM, вики), всего около 4 трлн токенов — в 4000 раз больше числа параметров. Поэтому маленькая модель уже довольно способна, и RL даёт заметный эффект в малом масштабе. Задача — GSM8K: школьные текстовые задачи по математике (data/gsm8k/train.jsonl и test.jsonl в репозитории). Решение в эталоне заканчивается строкой #### ответ. Модели, которые вы обучали в курсе, для таких задач слишком слабы. Результаты RL сильно зависят от пары «модель — данные», так что выводы задания не обязательно переносятся на другие пары.

Обозначения. Языковое моделирование и RL называют одно и то же по-разному:

СимволТермин LMТермин RLСмысл
ρ\rhoраспределение промптов, датасетначальное распределение состоянийоткуда берём задачи
xxпромпт, вопросначальное состояниезадача из ρ\rho
yyответ, генерациятраектория, rolloutсэмплированный ответ на xx
yty_tтокендействиеtt-й токен ответа
y<ty_{<t}префикс—токены до позиции tt
πθ\pi_\thetaмодельполитикадаёт вероятности ответов
r(y∣x)r(y \mid x)—награда0 или 1: верен ли ответ
BB, GGпромптов в батче, ответов на промпт—, размер группыразмеры батча
LLдлина ответагоризонтчисло сгенерированных токенов
A(i,j)A^{(i,j)}—преимущество (advantage)вес ответа jj на промпт ii

Генерацию делает движок vLLM (быстрые ядра CUDA, PagedAttention для KV-кэша); писать свой движок не нужно — в vllm_utils.py есть класс VLLMServer с методами start() и generate_completions(prompts, sampling_params).

Сравниваются три промпта:

  • r1_zero — промпт из статьи о DeepSeek R1-Zero: описание диалога «пользователь — ассистент», где ассистент сначала рассуждает внутри тегов <think> </think>, а ответ даёт внутри <answer> </answer>. Промпт заканчивается на Assistant: <think>, так что модель сразу начинает рассуждать. Теги нужны, чтобы легко вытащить ответ и остановить генерацию на </answer>.
  • r1_zero_three_shot — тот же промпт с тремя решёнными примерами перед вопросом (примеры взяты из репозитория OLMES). Результаты базовых моделей часто публикуют именно с примерами: в карточке OLMo-2-0425-1B GSM8K измерен с 8 примерами.
  • question_only — только вопрос и просьба поместить ответ в \boxed{} (чтобы проверяющий мог его найти).

Проверка ответов. Правильный ответ можно записать по-разному (1/2, 0.5, «The answer is 0.5»), поэтому нужен разборщик. Используется быстрый и довольно точный проверяющий из работы о Dr. GRPO: r1_zero_reward_fn для промптов с тегами и question_only_reward_fn (ищет ответ в \boxed{}) для простого промпта. Функции возвращают общую награду, награду за формат и награду за ответ. Частичных баллов за правильный формат при неверном ответе в задании нет: общая награда равна награде за ответ, а формат только логируется. Эталон GSM8K нужно обрезать: взять часть после #### и убрать пробелы.

Параметры генерации: температура 1,0, top-p 1,0, до 512 токенов; для промптов r1_zero генерация останавливается на строке </answer> (и она включается в вывод), для question_only — нет.

Задача prompting_baselines (5 баллов). Оцените три промпта на GSM8K. Для каждого посчитайте ответы трёх видов: верный формат и верный ответ; верный формат, неверный ответ; неверный формат. Посмотрите хотя бы десять ответов второго вида: сколько из них на самом деле правильные, но плохо разобраны? А в третьей группе? Затем опишите, как каждый промпт меняет поведение модели: хватает ли одного вопроса, чтобы модель отвечала, или она делает что-то ещё?

Цель — максимизировать ожидаемую награду (точность):

Jθ=Ex∼ρ Ey∼πθ(y∣x)[r(y∣x)].J_\theta = \mathbb{E}_{x \sim \rho}\, \mathbb{E}_{y \sim \pi_\theta(y \mid x)} \big[ r(y \mid x) \big].

В отличие от кросс-энтропии предобучения, где примеры берутся из датасета, здесь ожидание берётся по ответам самой модели — поэтому нужны методы RL. Ещё одна особенность: раз модель сэмплирует, динамику обучения задаёт и промпт. Одно из ярких открытий последних лет — RL поверх сильной базовой модели с промптом на цепочку рассуждений сильно улучшает её умение рассуждать (OpenAI o1, DeepSeek-R1). Изложение в условии опирается на Spinning Up от OpenAI и книгу Натана Ламберта о RLHF.

Модель как политика. Состояние sts_t — уже сгенерированный префикс, действие ata_t — следующий токен: πθ(at∣st)=softmax⁡(fθ(st))at\pi_\theta(a_t \mid s_t) = \operatorname{softmax}(f_\theta(s_t))_{a_t}. Нужны две операции: сэмплировать из политики и считать log⁡πθ(at∣st)\log \pi_\theta(a_t \mid s_t). Эпизод заканчивается токеном конца текста (у нас — </answer>) или по лимиту длины. Следующее состояние — префикс, к которому приписан токен. Траектория — последовательность состояний и действий; её ещё называют эпизодом или rollout.

Награда. В задачах с проверяемым ответом промежуточные шаги рассуждения награды не получают; в конце даётся 1, если ответ совпал с эталоном, и 0 иначе. Отдача (return) траектории равна этой финальной награде.

Policy gradient (REINFORCE). Хотим подниматься по градиенту θk+1=θk+α∇θJ\theta_{k+1} = \theta_k + \alpha \nabla_\theta J. С помощью трюка с логарифмической производной ∇θπθ=πθ∇θlog⁡πθ\nabla_\theta \pi_\theta = \pi_\theta \nabla_\theta \log \pi_\theta получаем

∇θ Ey∼πθ[r(y∣x)]=∑y∇θπθ(y∣x) r(y∣x)=Ey∼πθ[r(y∣x) ∇θlog⁡πθ(y∣x)].\nabla_\theta\, \mathbb{E}_{y \sim \pi_\theta}\big[r(y \mid x)\big] = \sum_y \nabla_\theta \pi_\theta(y \mid x)\, r(y \mid x) = \mathbb{E}_{y \sim \pi_\theta}\big[r(y \mid x)\, \nabla_\theta \log \pi_\theta(y \mid x)\big].

По батчу из BB промптов и GG ответов на каждый получаем выборочную оценку g^=1BG∑i,jr(y(i,j)∣x(i)) ∇θlog⁡πθ(y(i,j)∣x(i))\hat g = \frac{1}{BG} \sum_{i,j} r(y^{(i,j)} \mid x^{(i)})\, \nabla_\theta \log \pi_\theta(y^{(i,j)} \mid x^{(i)}): повышаем вероятность ответов с высокой наградой. Дальше GRPO получается из этой оценки серией модификаций.

Базовая линия. Оценка несмещённая, но с большой дисперсией. Вычтем из награды константу bb: при бинарной награде и b=0,5b = 0{,}5 правильные ответы усиливаются с весом +0,5+0{,}5, а неправильные ослабляются с весом −0,5-0{,}5 (без базовой линии неправильные просто игнорируются). Ожидание не меняется, если bb не зависит от yy, потому что Ey∼πθ[∇θlog⁡πθ(y∣x)]=∇θE[1]=0\mathbb{E}_{y \sim \pi_\theta}[\nabla_\theta \log \pi_\theta(y \mid x)] = \nabla_\theta \mathbb{E}[1] = 0. Дисперсию же базовая линия может как уменьшить, так и увеличить.

Задача baseline_calcs (5 баллов, на бумаге). Политика на двух действиях {0,1}\lbrace 0, 1 \rbrace с πθ(A=1)=p=σ(θ)\pi_\theta(A = 1) = p = \sigma(\theta), награда 1 за действие 1. Найдите дисперсию оценки градиента по nn сэмплам (a) без базовой линии, (b) с базовой линией bb, (c) с «популяционной» базовой линией b=pb = p — всегда ли она меньше, чем без базовой линии?

Групповое среднее. GRPO вычитает среднюю награду группы μi=1G∑jr(y(i,j)∣x(i))\mu_i = \frac{1}{G} \sum_j r(y^{(i,j)} \mid x^{(i)}). Хотя μi\mu_i зависит от ответов, ожидание сохраняется с точностью до множителя G−1G\frac{G-1}{G} (вклад самого ответа в среднее даёт ровно эту поправку). Разность «награда минус среднее группы» и называют преимуществом.

Нормировка преимуществ. Затем GRPO делит на стандартное отклонение группы stdi\mathrm{std}_i (в реализации — обычный torch.std с поправкой на смещение). Это уже меняет ожидание: мы больше не поднимаемся строго по градиенту JθJ_\theta. Смысл — трюк для устойчивости: если градиенты примерно гауссовы, деление на std выравнивает норму вклада каждой группы.

Нормировка по длине. Унаследованная от ранних реализаций PPO для LLM деталь: сумма log-вероятностей токенов ответа делится на его длину. Это тоже меняет ожидание: токены длинных ответов получают меньший вес, чем токены коротких.

Итог — алгоритм on-policy GRPO. На каждом шаге: берём BB вопросов, генерируем по GG ответов, считаем

g^=1BG∑i=1B∑j=1G1len⁡(y(i,j))∑t=1len⁡(y(i,j))r(y(i,j)∣x(i))−μistdi ∇θlog⁡πθ(yt(i,j)∣x(i),y<t(i,j))\hat g = \frac{1}{BG} \sum_{i=1}^{B} \sum_{j=1}^{G} \frac{1}{\operatorname{len}(y^{(i,j)})} \sum_{t=1}^{\operatorname{len}(y^{(i,j)})} \frac{r(y^{(i,j)} \mid x^{(i)}) - \mu_i}{\mathrm{std}_i}\, \nabla_\theta \log \pi_\theta\big(y^{(i,j)}_t \mid x^{(i)}, y^{(i,j)}_{<t}\big)

и обновляем параметры любым оптимизатором. Ещё одна часть GRPO — отсечённое взвешивание по важности — нужна только off-policy и разбирается в разделе 6.

Модель загружается через Hugging Face transformers (bf16, FlashAttention-2; заготовка — в checkpoint.py). Можно написать и свой трансформер, если архитектура совпадает с OLMo-2-0425-1B. Схема: модель и оптимизатор — на GPU 0, vLLM с копией модели и KV-кэшем — на GPU 1; перед каждой генерацией веса синхронизируются через NCCL (init_weight_sync, sync_policy_weights в VLLMServer).

Функции, которые вы пишете (каждая проверяется своим тестом через переходник run_*):

ФункцияЧто делаетБаллы
tokenize_prompt_and_outputТокенизирует промпт и ответ отдельно, без спецтокенов, склеивает; возвращает input_ids (без последнего токена), labels (сдвинутые на один) и response_mask (1 только там, где метка — токен ответа)1
get_response_log_probsLog-вероятности меток под моделью по позициям; по флагу — ещё и энтропия распределения следующего токена1
compute_rollout_rewardsНаграды для всех ответов батча и статистика (средняя общая награда и награда за формат)1
compute_group_normalized_rewardsПерекладывает плоский вектор наград в группы, вычитает базовую линию, делит на нормировщик плюс advantage_eps1
compute_policy_gradient_lossПотери на каждый токен; минимизация даёт подъём по оценке градиента (возвращаем минус цель)1
aggregate_loss_across_microbatchУсреднение: сначала по токенам ответа внутри последовательности, затем по последовательностям0,5
grpo_train_stepПолный шаг: накопление градиента по микробатчам, отсечение нормы градиента, шаг оптимизатора, метрики5

Тонкости, на которые обращает внимание условие:

  • «Потеря» policy gradient — не потеря в обычном смысле: она не обязана убывать. Это выражение, градиент которого равен оценке policy gradient.
  • Накопление градиента. Для хорошей загрузки генерации нужен большой батч, а on-policy батч обучения равен батчу генерации — он не помещается в память целиком. Батч режут на микробатчи, для каждого вызывают backward(), а шаг оптимизатора и обнуление градиентов делают один раз. При нормировке по последовательностям потерю микробатча нужно умножить на долю его последовательностей в батче, чтобы градиент совпал с градиентом по всему батчу.
  • Что логировать как минимум: потерю, норму градиента, энтропию токенов, награды на обучении (общую и за формат). Полезно и pass@k — долю промптов, где хотя бы один из первых kk ответов верен.

Рекомендованные гиперпараметры: 6400 обучающих и 1024 валидационных примера, 200 шагов генерации, скорость обучения 10−510^{-5}, батч генерации = батч обучения = 256 ответов (32 промпта по 8 ответов), 32 шага накопления градиента, температура 1,0, до 512 токенов, отсечение нормы градиента 1,0, AdamW с β=(0,9; 0,95)\beta = (0{,}9;\ 0{,}95) без затухания весов. Валидация — каждые 10 шагов на не менее чем 1024 примерах; ответы обучения полезно сохранять каждые 40 шагов.

Поскольку RL сам себя подкрепляет, траектории разных запусков сильно расходятся. Поэтому каждый эксперимент запускают с четырьмя зёрнами и на графиках показывают разброс: все запуски полупрозрачными линиями, среднее с доверительным интервалом μ±1,96 σ/n\mu \pm 1{,}96\, \sigma / \sqrt{n} или минимум и максимум на каждом шаге.

  • grpo_experiments_standard_on_policy (10 баллов, ~2 часа B200). Скрипт полного цикла (синхронизация весов → генерация → шаги градиента → периодическая валидация). Сначала короткий запуск примерно на 50 шагов, чтобы убедиться, что награда на валидации растёт (в начале она близка к нулю — это нормально). Затем 4 зерна с промптом r1_zero, графики потерь, нормы градиента, энтропии, наград на обучении и валидации, средней длины ответа и примеры ответов до и после обучения. Порог: не меньше 25% точности на валидации в среднем по зёрнам.
  • grpo_learning_rate (3 балла, ~4 часа B200). Перебор скорости обучения (хотя бы одна меньше и одна больше рекомендованной), график финальной награды от скорости обучения; число зёрен выбираете сами по наблюдаемому разбросу.
  • grpo_prompt_ablation (3 балла, ~4 часа B200). То же с промптами question_only и r1_zero_three_shot: какой даёт лучшую среднюю награду и меньший разброс, насколько вы уверены в выводах.

Выборы GRPO оспариваются во многих работах; в этом разделе их сравнивают в контролируемых экспериментах.

Dr. GRPO. Отменяет два «смещающих» выбора GRPO: не делит на std и вместо нормировки каждой последовательности по длине делит общую сумму на константу ZZ (обычно Z=BGLZ = BGL, где L=512L = 512 — максимальная длина генерации):

g^=1Z∑i,j∑t(r(y(i,j)∣x(i))−μi) ∇θlog⁡πθ(yt(i,j)∣x(i),y<t(i,j)).\hat g = \frac{1}{Z} \sum_{i,j} \sum_{t} \big(r(y^{(i,j)} \mid x^{(i)}) - \mu_i\big)\, \nabla_\theta \log \pi_\theta\big(y^{(i,j)}_t \mid x^{(i)}, y^{(i,j)}_{<t}\big).

Задачи: think_about_length_normalization (1 балл) — плюсы и минусы нормировки по длине и по константе; поддержать advantage_normalizer = "none" и baseline = "none" (0,5) и loss_normalization = "constant" (0,5).

RFT (rejection fine-tuning, expert iteration). Самый простой вариант: сгенерировать ответы, оставить правильные и сделать на них обычное SFT. Градиент — сумма ∇log⁡πθ\nabla \log \pi_\theta только по ответам с наградой 1, делённая на константу. Задача think_about_rft (2 балла): при бинарной награде сравнить RFT с on-policy Dr. GRPO — одинаково ли у них ожидание, у какого меньше дисперсия, когда что предпочесть.

MaxRL (Maximum Likelihood RL, Tajwar et al., 2026). Делит преимущество не на std и не на ничего, а на среднюю награду группы μi\mu_i. Интуиция: трудные промпты (низкое μi\mu_i) получают больший вес. В оригинальной работе нормируют на число токенов в батче; в задании для чистоты сравнения — на ту же константу ZZ.

Чтобы формализовать «перевзвешивание по трудности», вводят цель Jθ,w=Ex∼ρ[w(x,stopgrad⁡(πθ)) Ey∼πθr(y∣x)]J_{\theta, w} = \mathbb{E}_{x \sim \rho}\big[w(x, \operatorname{stopgrad}(\pi_\theta))\, \mathbb{E}_{y \sim \pi_\theta} r(y \mid x)\big], где вес ww зависит от трудности промпта η(x)=Ey∼πθr(y∣x)\eta(x) = \mathbb{E}_{y \sim \pi_\theta} r(y \mid x) и через него градиент не идёт.

  • derive_difficulty_reweightings (6 баллов, на бумаге). При Z=GZ = G и G→∞G \to \infty найти, какой вес ww неявно оптимизируют (a) Dr. GRPO, (b) GRPO с делением на std, (c) MaxRL с делением на среднее.
  • think_about_advantage_normalization (2 балла): плюсы и минусы деления на std, на среднее и без нормировки.
  • Поддержать advantage_normalizer = "mean" (0,5).

Эксперименты. Четыре конфигурации на базе GRPO с константной нормировкой:

Вариантbaselineadvantage_normalizerloss_normalization
GRPO_constantmeanstdconstant
Dr_GRPOmeannoneconstant
RFTnonenoneconstant
MaxRLmeanmeanconstant

Ускорение: последовательности с нулевым преимуществом не дают вклада в градиент, их можно не прогонять через модель (при базовой линии «среднее» — группы, где у всех одинаковая награда; без базовой линии — все неверные ответы) и соразмерно уменьшить число шагов накопления, следя, чтобы градиент не изменился. Задачи: grpo_train_step_variants_on_policy (2,5 балла) — шаг обучения со всеми вариантами и отбрасыванием нулевых преимуществ; grpo_experiments_variants_on_policy (10 баллов, ~8 часов B200) — 4 зерна на каждый вариант с теми же гиперпараметрами, сравнение средних и разброса.

До сих пор на каждый батч генерации приходился один шаг обучения (on-policy). Off-policy делит батч генерации на мини-батчи и делает шаг на каждом: обучение быстрее, но после первого шага ответы «устаревают» — их сгенерировала старая политика π0\pi_0, а не текущая πθ\pi_\theta, и наивная оценка градиента становится смещённой.

Взвешивание по важности. Несмещённая оценка умножает слагаемое на πθ(y∣x)π0(y∣x)\frac{\pi_\theta(y \mid x)}{\pi_0(y \mid x)}: ответы, ещё вероятные под текущей политикой, усиливаются, устаревшие — ослабляются. Но для последовательности это произведение отношений по всем LL токенам, и дисперсия растёт экспоненциально с длиной. Наивная оценка — большое смещение без лишней дисперсии; точное взвешивание — нулевое смещение и огромная дисперсия; дальнейшие методы — компромиссы между ними.

Взвешивание по токенам (PPO, GRPO). Каждый токен получает свой вес wt=πθ(yt∣⋅)/π0(yt∣⋅)w_t = \pi_\theta(y_t \mid \cdot) / \pi_0(y_t \mid \cdot) вместо произведения. Это смещённая оценка; в условии показано, что при равной длине ответов она оптимизирует ожидаемую награду «гибридных» политик, где все токены, кроме tt-го, взяты из старой политики. Отсюда два источника смещения: префикс до tt и продолжение после tt взяты у π0\pi_0, и смещение растёт по мере удаления πθ\pi_\theta от π0\pi_0. Задача derive_surrogate_objectives (2 балла): какую цель оптимизирует «попарное» взвешивание (по паре соседних токенов).

Отсечение. Чтобы политика не уходила далеко от старой, PPO и GRPO отсекают вес: в цели берётся минимум из A⋅wtA \cdot w_t и A⋅clip⁡(wt,1−ε,1+ε)A \cdot \operatorname{clip}(w_t, 1 - \varepsilon, 1 + \varepsilon). Смысл: при положительном преимуществе вероятность токена растёт, пока не станет в 1+ε1 + \varepsilon раз выше старой, — дальше градиент нулевой; при отрицательном — падает до 1−ε1 - \varepsilon. Реализуется как importance_reweighting_method = "noclip" и "grpo" (задача на 1 балл). Отдельно условие упоминает CISPO (MiniMax, 2025): отсекать сверху только сам вес в оценке градиента, не обнуляя градиент, — можно попробовать по желанию.

GSPO (Zheng et al., 2025). Возвращается к весу на уровне последовательности, но берёт среднее геометрическое отношений по токенам (произведение в степени 1/L1/L), а затем отсекает. Без степени и отсечения это было бы точное взвешивание; степень и отсечение снижают дисперсию ценой смещения. Нормировка по длине при этом возникает автоматически. Задачи: think_about_importance_reweighting (2 балла) — где на шкале «смещение — дисперсия» лежат отсутствие взвешивания, токенное отсечение и GSPO; реализовать "gspo" с численно устойчивым средним геометрическим (1 балл).

Эксперименты. Шаг обучения со всеми аргументами off-policy — grpo_train_step_off_policy (2,5 балла). Затем grpo_experiments_off_policy (10 баллов, ~8 часов B200): батч генерации 256, батч обучения 8, без накопления градиента — то есть 32 шага обучения на батч генерации. Варианты: offpolicy_naive (без взвешивания), noclip, clip (ε=0,2\varepsilon = 0{,}2) и gspo (ε=3⋅10−4\varepsilon = 3 \cdot 10^{-4}), по 4 зерна; дополнительно логировать долю отсечённых токенов. Вопросы: ускоряет ли off-policy обучение, что происходит с устойчивостью и разбросом, чем отличаются доли отсечения у двух методов.

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


Необязательное дополнение: instruction tuning и DPO

Заголовок раздела «Необязательное дополнение: instruction tuning и DPO»

Отдельный PDF того же репозитория (тесты test_data.py, test_dpo.py, test_metrics.py, промпты в prompts_safety/). Модель — Llama 3.1 8B; можно использовать vLLM и загрузку моделей из transformers, но не готовые обучающие утилиты вроде Trainer.

ЧастьЗадачиУрок курса
Оценка без дообученияMMLU, GSM8K, AlpacaEval (судья — Llama 3.3 70B Instruct), SimpleSafetyTests — по 4 балла7.3.2
Instruction fine-tuningРазбор данных, загрузка данных, обучающий скрипт, само SFT (~3 часа B200)6.1.2
Оценка после SFTТе же 4 бенчмарка плюс ручной red-teaming модели9.3.4
DPOРазбор данных Anthropic HH, функция потерь DPO, обучение (~1 час B200)6.3.3

  1. Память. У OLMo-2-0425-1B около 1,5 млрд параметров. Полное дообучение с обычным AdamW: веса bf16 (~3 ГБ), градиенты (~3 ГБ) и два момента Adam в fp32 (~12 ГБ) — это уже больше 16 ГБ даже без активаций. Варианты: 8-битный AdamW из bitsandbytes (моменты ~3 ГБ), градиентный чекпоинтинг и микробатч 1–4; или LoRA (тогда синхронизировать с vLLM нужно слитые веса). Для честного сравнения вариантов алгоритма важно лишь, чтобы конфигурация была одинаковой.

  2. Две GPU на запуск. Как в условии: GPU 0 учит, GPU 1 генерирует через vLLM (gpu_memory_utilization около 0,85 оставляет запас). На четырёх картах идут два запуска параллельно — удобно для нескольких зёрен. На игровых картах без NVLink синхронизация весов через NCCL идёт по PCIe; если она зависает, попробуйте переменную NCCL_P2P_DISABLE=1.

  3. Уменьшите эксперимент. Генерация на 4080 медленнее, чем на B200, в разы. Начните с батча 128 ответов (16 промптов × 8), 100 шагов и валидации на 512 примерах; 4 зерна оставьте только для финального сравнения. Отбрасывание последовательностей с нулевым преимуществом (раздел 5) экономит много времени, особенно для RFT.

  4. Если и это тяжело — возьмите модель поменьше (например, из семейства Qwen2.5 на 0,5 млрд параметров). Абсолютные числа изменятся (и порог 25% тоже), но относительные выводы о вариантах алгоритма проверить можно. Практика того же цикла на маленькой модели — в уроке 6.4.5.

  • Сначала тесты, потом GPU. Все функции из таблицы раздела 4.2 проверяются на CPU маленькими тензорами. Запускайте RL, только когда uv run pytest tests/test_grpo.py зелёный.
  • Маска ответа сдвинута вместе с метками. Самая частая ошибка — маска, выровненная по input_ids, а не по labels: тогда в потерю попадает последний токен промпта или выпадает последний токен ответа.
  • Знак. Оптимизаторы PyTorch минимизируют, а мы хотим подниматься по JθJ_\theta: функция потерь возвращает минус цель.
  • Нормировка при накоплении. Для sequence и constant масштабы микробатчей разные; проверьте, что градиент с накоплением совпадает с градиентом на полном батче (это легко проверить на маленькой модели).
  • Старые log-вероятности для off-policy считаются один раз после генерации, под torch.no_grad(), той же моделью до первого шага на этом батче.
  • Среднее геометрическое в GSPO считайте в лог-пространстве: среднее разностей log-вероятностей по токенам ответа, затем экспонента.
  • Смотрите на ответы. Растущая награда при падающей длине ответа или исчезающих тегах — повод проверить разборщик и маски, а не радоваться.