Когда вы в последний раз пытались обучить LLM с подкреплением на сотне GPU? Процесс, который должен быть рутинным, превращается в череду падений, зависаний и тихих NaN в логах. Я перепробовал почти всё: от асинхронных подходов до монолитных решений. Но один инструмент выделяется — veRL. Не из-за маркетинга, а из-за архитектуры. Но и он не идеален. Давайте залезем внутрь, посмотрим на его кишки, разберём инструментарий и наступим на грабли — критический баг NCCL, который сломал мне неделю.
Почему veRL вообще заслуживает внимания?
В экосистеме RL post-training — адская мешанина. Есть TRL от Hugging Face, OpenRLHF, DeepSpeed, но каждый раз ты упираешься в проблему: или инференс модели во время получения rollouts тормозит генерацию, или градиенты не сходятся из-за плохой синхронизации. veRL (volatile Efficient Reinforcement Learning) родился в недрах UCSD и сразу предложил другую парадигму: разделить actor, critic, reference и reward модели на отдельные группы процессов, а для генерации ответов использовать vLLM. Это не просто бенчмарк-хантер, а инженерно продуманная система.
В последнем релизе на начало 2026 года — veRL v0.5.3 — уже есть поддержка GRPO, PPO, RLOO, а также гибридный parallelism (TP, PP, DP, FSDP). Но самое важное — он использует Ray для оркестровки и vLLM для инференса. Звучит красиво. Но давайте откроем капот.
veRL не использует стандартный Hugging Face Trainer для генерации — он запускает отдельный vLLM-инстанс (или несколько) внутри того же Ray-кластера. Это решение ускоряет сбор траекторий в 2-5 раз по сравнению с model.generate().
Архитектура: три слоя, которые работают как часы (пока не ломаются)
Посмотрим на диаграмму потоков в veRL. Упрощённо:
- Rollout-воркеры — получают текущую политику (актор), запускают vLLM с несколькими промптами, генерируют ответы. Каждый воркер держит локальную копию модели (через Ray object store или shared memory).
- Training-воркеры — на них живут actor, critic, reference, reward модели. Они обновляются через FSDP или DeepSpeed ZeRO-3, а rollout-воркеры просто забирают свежий чекпоинт через Ray.
- Reward-воркеры — отдельный пул для RM (reward model), чтобы не блокировать train.
Такая декомпозиция позволяет масштабироваться: можно добавить 64 GPU только для rollouts и не трогать training-кластер. Но есть тонкость — синхронизация градиентов между репликами актора. И тут вылезает NCCL-баг.
Критический баг NCCL: как я потерял неделю
После первого успешного запуска на 32 GPU A100 я решил масштабироваться до 128. И всё сломалось. Обучение зависало случайным образом через 10-30 шагов. Логи молчали, никаких OOM, никаких ошибок — просто стоп. Я начал подозревать всё: от сети до драйверов. Но проблема оказалась глубже.
Баг воспроизводился в all_reduce при размере тензора меньше 1 MB. При гибридном parallelism малые градиенты (например, от bias-слоёв или layer norms) вызывали deadlock в NCCL 2.21.5. На версии 2.22.3 проблема частично исправлена, но не для всех конфигураций.
Как я это выяснил? Собрал трассировку с NCCL_DEBUG=INFO и увидел:
NCCL WARN: Net : Call to recv from GPU 7 failed: remote GPU 3, socket 6144 error 2
Казалось бы — сетевая проблема. Но нет — причина была в том, что при малом размере сообщения NCCL использовал внутренний буфер, который мог переполняться при большом количестве одновременных all-reduce. И veRL запускает одновременно десятки таких операций из-за pipeline parallelism.
Решение, которое сработало у меня — форкнуть veRL и заменить в коммуникации all_reduce на reduce_scatter + all_gather для маленьких тензоров, а также принудительно задать NCCL_MAX_P2P_NCHANNELS=2. Но это временный костыль. Разработчики veRL уже знают о баге: в issue #345 обсуждают патч с опцией --ncccl_min_tensor_size_mb 1. Но стабильного фикса пока нет.
1 Диагностика бага
Достаточно запустить тест с torch.distributed.all_reduce на маленьком тензоре на вашем кластере. Если зависает — вы в зоне риска.
2 Временное решение
Добавьте в train.py перед запуском:
import os
os.environ['NCCL_MAX_P2P_NCHANNELS'] = '2'
os.environ['NCCL_MIN_TENSOR_SIZE'] = '1048576' # 1 MB
Но это не панацея — при большом количестве GPU (256+) баг может вернуться.
Инструментарий veRL: что прячется в коробке
Кроме самого обучения, veRL поставляет несколько утилит, которые редко описывают в документации.
| Утилита | Назначение |
|---|---|
verl.bench |
Бенчмарк производительности rollouts vs training |
verl.profile |
Профилирование памяти и времени по слоям |
verl.replay |
Загрузка ранее собранных траекторий для повторного обучения (offline) |
verl.rollout |
Отдельный запуск генерации с текущей политикой (удобно для дата-аугментации) |
Особого внимания заслуживает verl.profile — он показывает, сколько времени тратится на коммуникацию. На моём эксперименте с багом NCCL именно там я увидел аномалии: время all_reduce для bias-слоёв превышало 2 секунды при норме 20 мс.
Пошаговый план: запуск veRL на кластере с обходом бага
1 Подготовка окружения
Установите Ray 2.35.0 (или новее), vLLM 0.8.2, PyTorch 2.7.0 с CUDA 12.8. veRL ставится через pip или из сорцов:
git clone https://github.com/volcengine/verl.git
cd verl
pip install -e .
2 Конфигурация датасета и ревард-модели
Используйте JSON-файл с промптами и синтетическими ответами. Для простоты возьмите datasets/gsm8k. Ревард-модель — например, OpenAssistant/reward-model-deberta-v3-large.
3 Запуск с отключением опасных оптимизаций
python -m verl.trainer.main_ppo \
--config_path config/ppo_llama3_8b.yaml \
--n_gpus_per_node 8 \
--num_nodes 4 \
--rollout_batch_size 512 \
--max_epochs 1 \
--offload_actor True \
--enable_gradient_compression False \
--ncccl_min_tensor_size_mb 1
Флаг --enable_gradient_compression=False — важный: при включении баг NCCL проявляется с вероятностью 80%.
4 Мониторинг и профилирование
Зайдите в Ray Dashboard (порт 8265), следите за worker_pending — если время пересылки градиентов растёт, немедленно останавливайте и включайте большее значение --ncccl_min_tensor_size_mb.
NCCL_IB_TIMEOUT=30 и NCCL_IB_RETRY_CNT=7. Без этого all_reduce на 256 GPU будет падать с таймаутом.Типичные ошибки (и как НЕ надо делать)
Ошибка 1: Использовать --rollout_batch_size меньше 256. При маленьком batch размер тензоров градиентов падает ниже порога NCCL, и баг активируется. Лучше увеличить batch, даже ценой меньшего числа итераций.
Ошибка 2: Запускать veRL на GPU с разной памятью (например, смесь A100 80GB и A100 40GB). vLLM не умеет эффективно балансировать — часть GPU будет простаивать, а часть выпадать по OOM. Используйте однотипные ускорители.
Ошибка 3: Игнорировать техники Unsloth GRPO для длинного контекста. veRL поддерживает контекст до 128K, но без оптимизации flash-attention и paged attention vLLM умирает. Встройте --use_vllm_paged_attention.
Ошибка 4: Пытаться использовать verl.replay для офлайн-обучения без предварительной нормализации ревардов. Без этого градиенты уходят в космос. Сделайте reward_norm=RunningMeanStd.
Будущее veRL и NCCL-проблема
На момент июня 2026 года сообщество активно форкает veRL и чинит баги собственными силами. Один из самых интересных форков — с интеграцией RLM для динамического управления длиной контекста. Но NCCL-баг остаётся открытым issue под номером #401. Разработчики обещают фикс в v0.6.0 с использованием torch.distributed.all_to_all_single для малых тензоров. Ждём.
А пока советую всем, кто использует veRL в продакшне: заложите в цикл обучения дополнительный мониторинг градиентных коммуникаций. Если loss вдруг начал скакать, а GPU молчат — проверьте не только градиенты, но и размер all_reduce. И не забывайте про DroPE — возможно, вам вообще не нужен RL с полным референсом.