verl, часть 3: GRPO на четырёх RTX 4080 — план памяти, команды, настройка
Как запустить GRPO из verl на машине курса (4× RTX 4080 по 32 ГБ, PCIe без NVLink): расчёт памяти для Qwen3-0.6B/1.7B/4B, адаптация официального скрипта, перевод ключевых советов из Performance Tuning Guide и FAQ verl, что смотреть в логах и как чинить типичные ошибки.
Что у нас есть и что это значит
Заголовок раздела «Что у нас есть и что это значит»Машина курса: 4× RTX 4080 по 32 ГБ, связь между картами только через PCIe 4.0 (без NVLink), 128 ГБ RAM.
Три следствия для verl:
- Память. В verl на одних и тех же картах живут актор (обучаемая модель с градиентами и состоянием оптимизатора), референсная модель и движок генерации vLLM с KV-кэшем. Всё это должно уместиться в 4 × 32 ГБ.
- Связь. Обучение на FSDP постоянно гоняет веса и градиенты между картами (all-gather, reduce-scatter — урок 5.4.3), а перед каждой генерацией веса перекладываются в vLLM. По PCIe это медленнее, чем на серверах, — значит, выгоднее меньше дробить модель.
- Модели. Реалистичный выбор для полного GRPO — Qwen3-0.6B, 1.7B, 4B. 8B и больше — только с LoRA и выгрузкой в RAM (см. в конце).
План памяти
Заголовок раздела «План памяти»Грубая оценка для полного дообучения в смешанной точности с Adam — около 16 байт на параметр (веса, градиенты, мастер-копия в fp32 и два момента Adam; урок 5.4.1). FSDP делит это между четырьмя картами. Плюс vLLM забирает долю памяти под свои веса и KV-кэш, плюс активации.
| Модель | Актор (16 Б × N) | На одну карту после FSDP / 4 | vLLM при gpu_memory_utilization=0.4 | Вердикт |
|---|---|---|---|---|
| Qwen3-0.6B | ≈10 ГБ | ≈2,5 ГБ | 12,8 ГБ | свободно — для отладки |
| Qwen3-1.7B | ≈27 ГБ | ≈7 ГБ | 12,8 ГБ | комфортно |
| Qwen3-4B | ≈64 ГБ | ≈16 ГБ | 12,8 ГБ | впритык: нужен чекпойнтинг активаций, при нехватке — выгрузка оптимизатора |
Референсная модель в официальном скрипте выгружена в RAM (ref.fsdp_config.param_offload=True), поэтому в таблице её нет.
Команды
Заголовок раздела «Команды»-
Данные (один раз, из корня репозитория verl):
Окно терминала UV_RUN="uv run --frozen --all-packages --extra vllm --extra fsdp"$UV_RUN python3 examples/data_preprocess/gsm8k.py --local_save_dir ~/data/gsm8k -
Дымовой тест на Qwen3-0.6B — проверить, что всё запускается, за несколько минут. Все переменные — из «шапки» официального скрипта
run_qwen3_4b_fsdp.sh(разбор в части 2); хвостовые аргументы скрипт передаёт в verl как есть:Окно терминала MODEL_PATH=Qwen/Qwen3-0.6B NGPUS_PER_NODE=4 \TRAIN_BATCH_SIZE=32 PPO_MINI_BATCH_SIZE=16 ROLLOUT_N=4 \MAX_RESPONSE_LENGTH=512 ROLLOUT_TP=1 ROLLOUT_GPU_MEM_UTIL=0.4 \PROJECT_NAME=course_grpo EXPERIMENT_NAME=smoke_qwen3_0_6b \bash examples/grpo_trainer/run_qwen3_4b_fsdp.sh \trainer.logger=console trainer.total_training_steps=3 trainer.val_before_train=FalseОжидание: три строки
step:...в логе без ошибок.trainer.total_training_stepsограничивает обучение заданным числом шагов (по умолчаниюnull— считать по эпохам). -
Настоящий запуск на Qwen3-1.7B:
Окно терминала MODEL_PATH=Qwen/Qwen3-1.7B NGPUS_PER_NODE=4 \TRAIN_BATCH_SIZE=128 PPO_MINI_BATCH_SIZE=64 ROLLOUT_N=5 \MAX_PROMPT_LENGTH=512 MAX_RESPONSE_LENGTH=1024 \ROLLOUT_TP=1 ROLLOUT_GPU_MEM_UTIL=0.4 \TOTAL_EPOCHS=2 TEST_FREQ=10 SAVE_FREQ=50 \PROJECT_NAME=course_grpo EXPERIMENT_NAME=qwen3_1_7b_gsm8k \bash examples/grpo_trainer/run_qwen3_4b_fsdp.sh trainer.logger=console 2>&1 | tee grpo_1_7b.logДелимость: 128 промптов × 5 = 640 траекторий за шаг; мини-батч 64 × 5 = 320 траекторий → 2 обновления актора; на каждой из 4 карт — по 80 траекторий мини-батча. Эпоха GSM8K — 7473 / 128 ≈ 58 шагов.
-
Qwen3-4B — те же команды с
MODEL_PATH=Qwen/Qwen3-4B. Если на фазе обучения не хватает памяти, добавьте в конецactor_rollout_ref.actor.fsdp_config.optimizer_offload=True(состояние Adam уедет в RAM: медленнее, но влезет).
Почему ROLLOUT_TP=1
Заголовок раздела «Почему ROLLOUT_TP=1»В официальном скрипте ROLLOUT_TP=2: vLLM режет модель на две карты. На 4B-модели и наших картах в этом нет нужды —
модель целиком помещается в одну. Перевод из Performance Tuning Guide:
Используйте меньший
tensor_parallel_size. Когда ресурсы GPU позволяют, меньший размер тензорного параллелизма порождает больше реплик vLLM. Параллелизм данных (DP) может дать большую пропускную способность, чем тензорный (TP), но и больше расходует KV-кэш. Тщательно взвешивайте компромисс между числом реплик и расходом памяти. Наш эксперимент в разделе 8.4 статьи HybridFlow оценивает этот компромисс.
Для PCIe-машины довод ещё сильнее: при TP каждый токен генерации требует обмена между картами, а при ROLLOUT_TP=1 четыре
реплики vLLM генерируют независимо. Это тот же выбор «TP или DP», что в уроке 7.1.2.
Настройка производительности (перевод фрагментов Performance Tuning Guide)
Заголовок раздела «Настройка производительности (перевод фрагментов Performance Tuning Guide)»Размеры батчей: глобальные и локальные
Заголовок раздела «Размеры батчей: глобальные и локальные»Главный принцип выбора размеров батчей в verl:
- Алгоритмические параметры (размер батча обучения, размер мини-батча PPO) — глобальные (с точки зрения единого контроллера) и нормируются в каждом воркере.
- Параметры производительности (размер микробатча, максимальная длина в токенах для динамического батча) — локальные: они задают распределение данных на одну GPU.
В скриптах используйте *micro_batch_size_per_gpu, а не *micro_batch_size.
Советы:
- Включите чекпойнтинг градиентов (
enable_gradient_checkpointing=Trueу модели актора и критика) — часто позволяет брать бо́льшие микробатчи. - Увеличивайте
*micro_batch_size_per_gpu, пока он не сравняется с нормированнымmini_batch_size. - Параметры только прямого прохода (например,
actor_rollout_ref.ref.log_prob_micro_batch_size_per_gpu,actor_rollout_ref.rollout.log_prob_micro_batch_size_per_gpu) могут быть больше (например, вдвое), чем параметры обучения. - Микробатчи критика и модели награды могут быть больше, чем у актора: у актора гораздо больше словарь в последнем слое.
- Включите выгрузку активаций (
enable_activation_offload=True) — вместе с чекпойнтингом градиентов позволяет брать бо́льшие микробатчи; сейчас работает только в бэкенде FSDP.
Динамический размер батча
Заголовок раздела «Динамический размер батча»Динамический размер батча позволяет обрабатывать за один прямой проход примерно одинаковое число токенов (при разном
числе примеров). Это заметно повышает эффективность обучения и снижает расход памяти. Включается use_dynamic_bsz=True;
тогда вместо *micro_batch_size_per_gpu настраиваются:
actor_rollout_ref.actor.ppo_max_token_len_per_gpu— максимум токенов в прямом и обратном проходе при обновлении политики;actor_rollout_ref.ref.log_prob_max_token_len_per_gpuиactor_rollout_ref.rollout.log_prob_max_token_len_per_gpu— максимум токенов при вычислении логарифмов вероятностей.
Советы:
- Увеличьте
ppo_max_token_len_per_gpu— минимум до 2 × (max_prompt_length + max_response_length) — и пробуйте больше для пропускной способности. - Ограничения для параметров только прямого прохода могут быть больше, чем для обучения.
- Для критика и модели награды берите ограничения хотя бы вдвое больше, чем у актора.
(От переводчика: в скрипте run_qwen3_4b_fsdp.sh стоит ppo_max_token_len_per_gpu=3000, а 2 × (1024 + 1024) = 4096 —
то есть ниже совета документации. На 32 ГБ это разумная осторожность; если память позволяет, поднимите до 4096
хвостовым аргументом actor_rollout_ref.actor.ppo_max_token_len_per_gpu=4096.)
Удаление паддинга
Заголовок раздела «Удаление паддинга»Для обучения на FSDP задайте actor_rollout_ref.model.use_remove_padding=True, чтобы убрать токены-заполнители перед
прямым проходом (если есть критик — и critic.model.use_remove_padding=True). Выигрыш зависит от того, сколько
паддинга в батчах. (В официальном скрипте уже включено.)
Память на энтропию
Заголовок раздела «Память на энтропию»Вычисление энтропии по логитам даёт пик памяти (словарь Qwen — ~152 тыс. токенов). Чтобы его снизить, включите
поблочное вычисление: actor_rollout_ref.ref.entropy_from_logits_with_chunking=True. Обычный чекпойнтинг градиентов
на вычисление энтропии при обучении не распространяется; для него есть actor_rollout_ref.actor.entropy_checkpointing=True.
Что смотреть в логах
Заголовок раздела «Что смотреть в логах»| Метрика | Что значит | Хорошо, если |
|---|---|---|
critic/score/mean | средняя награда по траекториям шага | растёт от шага к шагу (с шумом) |
метрики с префиксом val-core/ (в старых версиях — val/test_score/...) | качество на тестовой выборке; mean@N — среднее по N ответам | растёт вместе с наградой |
response_length/mean, response_length/clip_ratio | длина ответов и доля обрезанных | обрезанных мало |
actor/entropy | энтропия политики | не падает к нулю слишком быстро (коллапс энтропииколлапс энтропииEN entropy collapseЭнтропия политики быстро падает при RL: ответы в группе становятся почти одинаковыми, исследование прекращается, рост награды упирается в потолок., урок 6.4.3) |
actor/pg_clipfrac, actor/ppo_kl | доля клиппинга и сдвиг политики за шаг | малы (единицы процентов) |
timing_s/gen, timing_s/update_actor | время фаз в секундах (в старых логах, как в быстром старте, — timing/...) | генерация обычно дольше обучения — это нормально |
Во втором терминале держите nvtop или watch -n 1 nvidia-smi: видно, как память перераспределяется между фазами
генерации и обучения.
Если что-то пошло не так
Заголовок раздела «Если что-то пошло не так»| Симптом | Что делать |
|---|---|
CUDA out of memory на фазе обучения | уменьшить ppo_max_token_len_per_gpu / микробатчи; ROLLOUT_GPU_MEM_UTIL ниже; optimizer_offload=True |
| OOM на фазе генерации | уменьшить ROLLOUT_N, MAX_RESPONSE_LENGTH или ROLLOUT_GPU_MEM_UTIL |
| «Unable to register worker with raylet» | Перевод из FAQ: причина — системные ограничения (например, SLURM ограничивает, как делятся процессоры узла): ray.init() пытается запустить столько процессов, сколько ядер, и ограничения мешают. Задайте ray_kwargs.ray_init.num_cpus — число, допустимое в вашей системе |
| «CUDA error: an illegal memory access» при генерации | Из FAQ: смотрите документацию vLLM по устранению неполадок для вашей версии |
| Зависание на инициализации NCCL | на картах GeForce без P2P попробуйте NCCL_P2P_DISABLE=1 (совет из урока 7.1.2) |
Ошибка компиляции Triton (compile_module_from_src) | Из FAQ: отключите JIT-компиляцию слитых ядер параметром use_torch_compile (см. страницу конфигурации) |
- Qwen3-8B с LoRA. Варианты с LoRA лежат в
examples/tuning/lora/(см. README из части 2): полное GRPO на 8B в 4 × 32 ГБ не поместится, а LoRA снимает основную нагрузку — состояние оптимизатора. - Свой датасет и своя награда. Шаблон —
examples/data_preprocess/gsm8k.pyиverl/utils/reward_score/gsm8k.py; практикум — урок 6.4.5. - Варианты GRPO (DAPO, Dr. GRPO, GSPO) включаются параметрами — см. часть 2 и урок 6.4.3.
Словарик
Заголовок раздела «Словарик»| English | Русский |
|---|---|
| rollout | генерация траекторий |
| actor / reference / critic | актор / референс / критик |
| offload | выгрузка (в RAM) |
| dynamic batch size | динамический размер батча |
| remove padding | удаление паддинга |
| resharding | перекладка (перешардирование) весов |