Перейти к содержимому
ПереводыQwen, InternLM и другие

verl, часть 1: что это, установка и быстрый старт (PPO на GSM8K)

Перевод документации verl — библиотеки RL-дообучения LLM от ByteDance Seed: что она умеет, как её поставить через uv, и полный перевод «Быстрого старта» — PPO на задачах GSM8K с моделью Qwen2.5-0.5B, функцией награды по правилам и разбором логов.

verl — библиотека для RL-обучения больших языковых моделей, начатая командой ByteDance Seed и поддерживаемая сообществом verl. Расшифровка названия: Volcano Engine Reinforcement Learning for LLMs. verl — гибкая, эффективная и готовая к продакшену библиотека; это открытая версия статьи HybridFlow: A Flexible and Efficient RLHF Framework.

verl гибкая и простая:

  • Лёгкое расширение разными RL-алгоритмами. Модель программирования с гибридным контроллером позволяет гибко описывать и эффективно выполнять сложные потоки данных пост-трейнинга. Потоки RL вроде GRPO и PPO собираются в несколько строк кода.
  • Бесшовная интеграция с существующей инфраструктурой LLM через модульные API: вычисления и зависимости данных разделены, поэтому легко подключаются FSDP, Megatron-LM, vLLM, SGLang и др.
  • Гибкое размещение на устройствах: модели можно по-разному раскладывать по группам GPU — для эффективного использования ресурсов и масштабирования на кластеры разного размера.
  • Готовая интеграция с популярными моделями Hugging Face.

verl быстрая:

  • Пропускная способность уровня лучших систем: интеграция с лучшими движками обучения и инференса LLM.
  • Эффективная перекладка весов актора с 3D-HybridEngine: убирает лишние копии в памяти и заметно сокращает обмен данными при переходе между фазами обучения и генерации.
  • Python: версия 3.10 или новее;
  • CUDA: версия 12.8 или новее.

verl поддерживает несколько бэкендов. Сейчас доступны:

  • FSDP и Megatron-LM (по желанию) — для обучения;
  • SGLang, vLLM и TGI — для генерации.
  1. Обучение. Для исследований и прототипов разных моделей, датасетов и RL-алгоритмов мы рекомендуем бэкенд FSDP / FSDP2. Тем, кому нужна лучшая масштабируемость, рекомендуем Megatron-LM. Оба бэкенда обслуживаются одним и тем же унифицированным слоем воркеров.
  2. Инференс. Поддерживается vLLM 0.18.0 и новее; более старые выпуски с verl больше не работают. Генерация на SGLang активно развивается и предлагает много продвинутых возможностей. Интеграция с TGI от Hugging Face обычно используется для отладки и опытов на одной GPU.

Окружение Python в verl управляется uv: один зафиксированный в репозитории файл uv.lock покрывает все бэкенды, а для конкретного запуска бэкенды выбираются как extras:

  • инференс: vllm или sglang;
  • обучение: fsdp или megatron;
  • cpu: без GPU, для CI и быстрых проверок.

Отдельного шага установки нет. uv run --extra ... при первом использовании создаёт виртуальное окружение проекта (.venv) из uv.lock, а дальше переиспользует его, так что запуск задачи — одна и та же команда, есть окружение или нет.

Окружение uv рассчитано на Linux с Python 3.12 на x86_64 и aarch64. uv никогда не собирает нативные пакеты из исходников: apex, transformer-engine и flash-attn берутся готовыми из индекса колёс verl (собраны под CUDA 13.0, torch 2.13, CPython 3.12); движки инференса — прямо с PyPI.

Подготовка на своей машине (без Docker):

Окно терминала
# один раз: установить uv
curl -LsSf https://astral.sh/uv/install.sh | sh
git clone https://github.com/verl-project/verl.git
cd verl
# шага установки нет: первый uv run соберёт .venv из зафиксированного uv.lock
uv run --frozen --all-packages --extra vllm --extra fsdp uv pip list

Флаги:

  • --frozen — использовать зафиксированный uv.lock как есть, никогда не пересчитывать зависимости;
  • --all-packages — установить все пакеты рабочего пространства uv;
  • --extra <движок> --extra <тренер> — комбинация бэкендов для этого запуска.

Правила комбинаций: не больше одного движка инференса (vllm или sglang), плюс бэкенд обучения (fsdp по умолчанию или megatron); cpu используется отдельно. Держите один и тот же набор extras во всех командах одной задачи: uv run синхронизирует .venv ровно с переданными extras, и чередование комбинаций каждый раз переустанавливает torch.

Скрипты в examples/ уже содержат всю обвязку uv — тестовый запуск не требует подготовки:

Окно терминала
bash examples/grpo_trainer/run_qwen3_8b_fsdp.sh
# скрипт сам выберет extras под нужный движок
INFER_BACKEND=sglang bash examples/grpo_trainer/run_qwen3_8b_fsdp.sh

Драйвер запускается под uv run, а Ray запускает TaskRunner и всех воркеров той же командой через ray_kwargs.ray_init.runtime_env.py_executable — так все процессы задачи используют одно окружение.

Откат на системный Python. Установите VERL_USE_UV=0, чтобы запускать текущим интерпретатором: VERL_USE_UV=0 bash examples/grpo_trainer/run_qwen3_8b_fsdp.sh.

Пост-трейнинг LLM на датасете GSM8K.

В этом примере мы обучаем LLM решать задачи GSM8K с наградой, вычисляемой функцией.1

Что нужно заранее:

  • последняя версия verl и зависимостей, установленная по руководству по установке. Рекомендуется Docker-образ;
  • GPU как минимум с 24 ГБ памяти.

Команды python3 ниже предполагают интерпретатор, в котором verl уже есть, — один из Docker-образов или активированный .venv. В варианте с uv шага установки нет: запускайте их из корня репозитория с префиксом uv run, который при первом использовании собирает .venv из uv.lock:

Окно терминала
UV_RUN="uv run --frozen --all-packages --extra vllm --extra fsdp"
$UV_RUN python3 examples/data_preprocess/gsm8k.py --local_save_dir ~/data/gsm8k
$UV_RUN python3 -m verl.trainer.main_ppo ... \
ray_kwargs.ray_init.runtime_env.py_executable="${UV_RUN}"

Последнее переопределение заставляет Ray запускать TaskRunner и каждого воркера в том же окружении; скрипты в examples/ делают это за вас.

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

Промпт:

Katy makes coffee using teaspoons of sugar and cups of water in the ratio of 7:13. If she used a total of 120 teaspoons of sugar and cups of water, calculate the number of teaspoonfuls of sugar she used.

(«Кейти варит кофе, беря чайные ложки сахара и чашки воды в отношении 7:13. Всего она взяла 120 ложек сахара и чашек воды. Сколько чайных ложек сахара она использовала?»)

Решение:

The total ratio representing the ingredients she used to make the coffee is 7+13 = <<7+13=20>>20 Since the fraction representing the number of teaspoons she used is 7/20, she used 7/20*120 = <<7/20*120=42>>42 #### 42

(«Сумма частей отношения: 7 + 13 = 20. Доля сахара — 7/20, значит, она взяла 7/20 · 120 = 42 ложки. #### 42». Вставки в двойных угловых скобках — вычисления калькулятором из разметки датасета; ответ — после ####.)

Мы переводим датасет в формат parquet, чтобы он (1) содержал поля, нужные для вычисления наград RL, и (2) быстрее читался.

Окно терминала
python3 examples/data_preprocess/gsm8k.py --local_save_dir ~/data/gsm8k

В этом примере мы начинаем с модели Qwen2.5-0.5B-Instruct.

Если хотите сделать SFT перед RL, смотрите полный пример GSM8K в документации, папку examples/sft/gsm8k и SFT Trainer (verl/trainer/sft_trainer.py).

Окно терминала
python3 -c "import transformers; transformers.pipeline('text-generation', model='Qwen/Qwen2.5-0.5B-Instruct')"

Модель / функция награды

Мы используем заранее заданную награду по правиламправиловая наградаEN rule-based rewardНаграду считает программа (сверка ответа, запуск тестов, проверка формата), а не нейросеть. Её трудно «взломать».. Модель обязана выдавать итоговый ответ после четырёх символов «#», как в решении. Регулярным выражением извлекаем итоговый ответ и из эталонного решения, и из вывода модели. Награда: 1 — за правильный ответ, 0,0 — за неправильный и 0 — если ответа нет.

Подробности — в verl/utils/reward_score/gsm8k.py.

Скрипт обучения

Теперь запустим PPO-обучение с этими датасетом и моделью.2

Задайте data.train_files, data.val_files, actor_rollout_ref.model.path и critic.model.path под имена или пути ваших датасета и модели. Можно установить VERL_USE_MODELSCOPE=True, чтобы качать модели с ModelScope, а не с Hugging Face.

Окно терминала
PYTHONUNBUFFERED=1 python3 -m verl.trainer.main_ppo \
data.train_files=$HOME/data/gsm8k/train.parquet \
data.val_files=$HOME/data/gsm8k/test.parquet \
data.train_batch_size=256 \
data.max_prompt_length=512 \
data.max_response_length=512 \
actor_rollout_ref.model.path=Qwen/Qwen2.5-0.5B-Instruct \
actor_rollout_ref.actor.optim.lr=1e-6 \
actor_rollout_ref.actor.ppo_mini_batch_size=64 \
actor_rollout_ref.actor.ppo_micro_batch_size_per_gpu=4 \
actor_rollout_ref.rollout.name=vllm \
actor_rollout_ref.rollout.log_prob_micro_batch_size_per_gpu=8 \
actor_rollout_ref.rollout.tensor_model_parallel_size=1 \
actor_rollout_ref.rollout.gpu_memory_utilization=0.4 \
actor_rollout_ref.ref.log_prob_micro_batch_size_per_gpu=4 \
critic.optim.lr=1e-5 \
critic.model.path=Qwen/Qwen2.5-0.5B-Instruct \
critic.ppo_micro_batch_size_per_gpu=4 \
algorithm.kl_ctrl.kl_coef=0.001 \
trainer.logger=console \
trainer.val_before_train=False \
trainer.n_gpus_per_node=1 \
trainer.nnodes=1 \
trainer.save_freq=10 \
trainer.test_freq=10 \
trainer.total_epochs=15 2>&1 | tee verl_demo.log

Вы должны увидеть такие логи — они означают, что обучение идёт. Ключевая метрика val/test_score/openai/gsm8k считается каждые trainer.test_freq шагов:

step:0 - timing/gen:21.470 - timing/ref:4.360 - timing/values:5.800 - actor/reward_kl_penalty:0.000 - actor/reward_kl_penalty_coeff:0.001 - timing/adv:0.109 - timing/update_critic:15.664 - critic/vf_loss:14.947 - critic/vf_clipfrac:0.000 - critic/vpred_mean:-2.056 - critic/grad_norm:1023.278 - critic/lr(1e-4):0.100 - timing/update_actor:20.314 - actor/entropy_loss:0.433 - actor/pg_loss:-0.005 - actor/pg_clipfrac:0.000 - actor/ppo_kl:0.000 - actor/grad_norm:1.992 - actor/lr(1e-4):0.010 - critic/score/mean:0.004 - critic/score/max:1.000 - critic/score/min:0.000 - critic/rewards/mean:0.004 - critic/rewards/max:1.000 - critic/rewards/min:0.000 - critic/advantages/mean:-0.000 - critic/advantages/max:2.360 - critic/advantages/min:-2.280 - critic/returns/mean:0.003 - critic/returns/max:0.000 - critic/returns/min:0.000 - critic/values/mean:-2.045 - critic/values/max:9.500 - critic/values/min:-14.000 - response_length/mean:239.133 - response_length/max:256.000 - response_length/min:77.000 - prompt_length/mean:104.883 - prompt_length/max:175.000 - prompt_length/min:68.000
step:1 - timing/gen:23.020 - timing/ref:4.322 - timing/values:5.953 - actor/reward_kl_penalty:0.000 - actor/reward_kl_penalty:0.001 - timing/adv:0.118 - timing/update_critic:15.646 - critic/vf_loss:18.472 - critic/vf_clipfrac:0.384 - critic/vpred_mean:1.038 - critic/grad_norm:942.924 - critic/lr(1e-4):0.100 - timing/update_actor:20.526 - actor/entropy_loss:0.440 - actor/pg_loss:0.000 - actor/pg_clipfrac:0.002 - actor/ppo_kl:0.000 - actor/grad_norm:2.060 - actor/lr(1e-4):0.010 - critic/score/mean:0.000 - critic/score/max:0.000 - critic/score/min:0.000 - critic/rewards/mean:0.000 - critic/rewards/max:0.000 - critic/rewards/min:0.000 - critic/advantages/mean:0.000 - critic/advantages/max:2.702 - critic/advantages/min:-2.616 - critic/returns/mean:0.000 - critic/returns/max:0.000 - critic/returns/min:0.000 - critic/values/mean:-2.280 - critic/values/max:11.000 - critic/values/min:-16.000 - response_length/mean:232.242 - response_length/max:256.000 - response_length/min:91.000 - prompt_length/mean:102.398 - prompt_length/max:185.000 - prompt_length/min:70.000

Полные логи обучения и валидации для сравнения — на странице Algorithm Baselines документации.

По умолчанию чекпойнт сохраняется в checkpoints/${trainer.project_name}/${trainer.experiment_name}. Сохранённые чекпойнты можно слить в модель Hugging Face модулем verl.model_merger, например:

Окно терминала
python3 -m verl.model_merger merge \
--backend fsdp \
--local_dir checkpoints/${trainer.project_name}/${trainer.experiment_name}/global_step_1/actor \
--target_dir checkpoints/${trainer.project_name}/${trainer.experiment_name}/global_step_1/actor/huggingface

Подробнее о чекпойнтах и слиянии моделей — на соответствующей странице документации.

Чтобы вести эксперимент в wandb, задайте:

Окно терминала
trainer.logger='["console","wandb"]' \
trainer.project_name=$YOUR_PROJECT_NAME \
trainer.experiment_name=$YOUR_RUN_NAME \

Если памяти не хватает на GPU меньше 32 ГБ, помогут такие настройки:

Окно терминала
actor_rollout_ref.actor.ppo_micro_batch_size_per_gpu=1 \
critic.ppo_micro_batch_size_per_gpu=1 \

Полный список параметров с подробными пояснениями и советами по настройке производительности — на странице о конфигурации в документации.

  • Быстрый старт рассчитан на одну GPU с 24 ГБ — ваша RTX 4080 на 32 ГБ подходит с запасом. Ожидаемое время шага — десятки секунд (в логе оригинала шаг ≈ 70 с на их GPU), эпоха GSM8K при train_batch_size=256 — около 29 шагов (7473 задачи / 256). Для первого знакомства хватит 20–30 шагов: смотрите, растёт ли critic/score/mean.
  • Чтобы занять все четыре карты, поставьте trainer.n_gpus_per_node=4 — подробности и подводные камни (PCIe без NVLink, делимость батчей) — в части 3.
  • Сравните с GRPO из части 2: там нет критика, поэтому меньше памяти и одна фаза обучения вместо двух.
  1. Оригинальная статья (https://arxiv.org/pdf/2110.14168) в основном посвящена обучению верификатора (модели награды), который решает математические задачи выбором лучшего из N ответов (Best-of-N). В этом примере мы обучаем RL-агента с наградой по правилам. ↩

  2. Больше примеров скриптов обучения для бэкендов FSDP и Megatron-LM — в папке examples/ppo_trainer. ↩