Перейти к содержанию
Публикация AiManual

Распределённое обучение на EKS без сборки образов: Ray Train DLC и EFA на практике

Готовый Ray Train DLC снимает сборку CUDA, NCCL и EFA-стека: разбираем, как на Amazon EKS поднять RayCluster через KubeRay, выдать подам GPU и EFA, проверить, ч

Коротко

Что будет в материале

  1. 01

    В чём проблема: почему распределённое обучение упирается в контейнер, а не в код модели

  2. 02

    Топология примера: KubeRay-кластер на Amazon EKS из трёх узлов

  3. 03

    Настройка снаружи образа: GPU, EFA и сетевой транспорт NCCL

  4. 04

    Почему несовпадение версий молчит и как поймать откат NCCL на TCP

В чём проблема: почему распределённое обучение упирается в контейнер, а не в код модели

Многоузловое обучение PyTorch останавливается не на модели. Останавливается оно на образе. Каждому контейнеру в кластере нужен одинаковый CUDA runtime, PyTorch, собранный под эту версию CUDA, NCCL, userspace-библиотеки Elastic Fabric Adapter (EFA), плагин AWS OFI NCCL, который соединяет NCCL с EFA, и версия Ray, с которой согласен оператор кластера. Разъедется один компонент, и вы получите либо падение на старте, либо обучение без ускоренной сети.

Ray Train Deep Learning Container (DLC) от AWS закрывает эту задачу целиком: AWS собирает стек, тестирует его как единое целое, публикует в Amazon ECR Public и держит пропатченным. Свой Dockerfile не нужен. Один тегированный образ работает и как Ray head, и как Ray worker, причём одинаково на Amazon EKS под KubeRay и при ручном запуске на Amazon EC2.

Масштаб примера из публикации AWS: Qwen2.5-1.5B дообучается с PyTorch Fully Sharded Data Parallel (FSDP) на восьми GPU NVIDIA L4, которые дают два инстанса g6.12xlarge. Код обучения — одна функция, кластер — один манифест RayCluster плюс файл переменных. Образ не собирается ни на одном шаге.

Что именно лежит в Ray Train DLC

Ray Train DLC — это PyTorch training image с библиотеками обучения Ray сверху. Внутри уже согласованы все слои, которые обычно приходится подгонять вручную:

Слой стекаЗачем нужен и что ломается при рассинхроне
CUDA runtimeдолжен совпадать на всех узлах, иначе PyTorch-сборки на разных нодах расходятся
PyTorchсобран под конкретную версию CUDA, а не «примерно под неё»
NCCLколлективные операции: all-reduce, all-gather, reduce-scatter
EFA userspace libraries (libfabric)доступ к сети EFA из пользовательского процесса
AWS OFI NCCL pluginсвязывает NCCL с EFA через libfabric
Ray и Ray Trainверсия, совместимая с оператором кластера

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

Что остаётся снаружи образа

Снаружи остаётся только конфигурация: какие инстансы брать, сколько GPU получает каждый pod, какой сетевой транспорт использует NCCL и какие дополнительные Python-пакеты нужны заданию. Всё это задаётся в манифесте и переменных окружения, а не в Dockerfile. Практически это шесть вещей:

  • манифест RayCluster с группами head и worker;
  • rayStartParams для ролей head и worker;
  • requests на GPU и на EFA-интерфейс;
  • переменные транспорта NCCL и libfabric;
  • runtime_env для пакетов задания;
  • тег образа DLC как единственный способ менять версии стека.

Топология примера: KubeRay-кластер на Amazon EKS из трёх узлов

Кластер в примере состоит из control plane под управлением AWS, одного CPU-узла m7i.xlarge, на котором живут KubeRay operator и Ray head, и двух EFA-инстансов g6.12xlarge с четырьмя NVIDIA L4 каждый. Все три узла в одной зоне доступности. Итого восемь GPU и восемь Ray worker'ов, по одному на GPU.

Часть кластераИнстансЧто делает
Control planeуправляется AWSAPI Kubernetes, планировщик, состояние кластера
System nodem7i.xlargeKubeRay operator и Ray head, GPU не нужны
EFA-узел 1g6.12xlarge4 x NVIDIA L4, EFA-интерфейс, 4 Ray worker'а
EFA-узел 2g6.12xlarge4 x NVIDIA L4, EFA-интерфейс, 4 Ray worker'а

Такая раскладка объясняет, почему head выносится на CPU-инстанс: координатор не участвует в матричных операциях, ему не нужны GPU, а платить за L4 на ноде с dashboard и job server бессмысленно. Все восемь ускорителей остаются под обучение. Детали этой конфигурации приведены в публикации AWS о Ray Train DLC и EFA.

Зачем нужен KubeRay и ресурс RayCluster

В Kubernetes нет встроенного понятия Ray-кластера. Нужен оператор, который создаст pod'ы head и worker, свяжет их и будет поддерживать это состояние. KubeRay — открытый Kubernetes-оператор, который добавляет ресурс RayCluster: вы описываете head group и worker group в одном манифесте, а оператор строит и дальше поддерживает кластер по нему. Без оператора адреса head, порядок старта и перезапуск упавших pod'ов пришлось бы связывать вручную.

Та же основа KubeRay-on-EKS используется в другом разборе, про инференс: запуск Qwen3-VL-2B на Ray Serve DLC в Amazon EKS. Разница только в наборе библиотек поверх PyTorch: Ray Train для обучения, Ray Serve для отдачи модели.

Роли Ray head и Ray worker

Ray head координирует кластер и держит глобальный control store, в котором отслеживается состояние, dashboard и job server, куда вы сабмитите задания. Ray worker предоставляет GPU и запускает процессы обучения. Каждый worker при старте присоединяется к кластеру по адресу head.

Роли задаются через rayStartParams в манифесте: у head-группы один набор параметров, у worker-группы другой. Это тоже часть конфигурации снаружи образа, то есть один и тот же тег DLC играет обе роли без пересборки.

Настройка снаружи образа: GPU, EFA и сетевой транспорт NCCL

Практический чек-лист того, что реально правится в манифесте и окружении.

Запросы GPU и EFA в манифесте

Worker-группа описывает pod'ы на g6.12xlarge, и на каждом узле должно подняться по четыре Ray worker'а, по одному на GPU. Отсюда два требования к ресурсам: каждый pod просит один GPU через ресурс nvidia.com/gpu, а планировщик сам разложит их по четырём L4 на ноде.

Второе требование менее очевидно. EFA-интерфейс появляется внутри pod'а только тогда, когда pod явно запросил EFA-ресурс, который публикует EFA device plugin. Не запросили — интерфейса нет, и трафик уходит в обычную виртуальную сеть. Поскольку DLC уже несёт стек EFA, коллективы между двумя инстансами идут через EFA сразу, как только pod'ы её запросили; сам образ для этого ничего не требует.

Переменные NCCL и FI_PROVIDER

Транспорт задаётся переменными окружения, а не содержимым образа. Значения зависят от версии плагина и конфигурации сети, поэтому их берут из документации к AWS OFI NCCL, а не копируют из случайной статьи.

ПеременнаяЗачем нужнаЧто проверить
FI_PROVIDERвыбирает провайдер libfabricуказан провайдер, соответствующий EFA, а не сокетный путь
NCCL_NET_PLUGINподключает AWS OFI NCCL к NCCLплагин реально найден и загружен, а не проигнорирован
NCCL_SOCKET_IFNAMEуказывает сетевой интерфейсимя совпадает с интерфейсом внутри pod'а
NCCL_DEBUGвключает диагностический выводв логах видно, какой транспорт выбран

Значения этих переменных в публикации AWS не приводятся, там зафиксирован сам принцип: транспорт NCCL настраивается снаружи образа. Если версии плагина и libfabric подобраны неверно, вы не увидите ошибки, о чём ниже.

Пакеты через runtime_env и версии через теги

Дополнительные Python-пакеты для задания ставятся через runtime_env Ray, а не новым Dockerfile. Это снимает главную причину дрейфа: на двух узлах оказываются разные версии библиотеки, потому что один образ обновили, а второй забыли.

Версия самого стека меняется только тегом DLC. Такой подход ограничивает гибкость: если нужна нестандартная сборка PyTorch или патч ядра, придётся собирать свой образ. Зато до этого момента управление версиями сводится к одной строке в манифесте. Похожая дисциплина версий разбирается в материале про EFA и DeepEP для RL-обучения MoE-моделей на Amazon EKS, где рассинхрон версий CUDA, PyTorch, NCCL и EFA ломает ускорение целиком.

Почему несовпадение версий молчит и как поймать откат NCCL на TCP

NCCL выбирает транспорт сам при инициализации. Если плагин AWS OFI NCCL не загрузился, переменная с провайдером libfabric указывает не туда или pod не получил EFA-интерфейс, NCCL не падает. Он уходит на сокетный путь, то есть на обычный TCP через виртуальную сеть. Job считается, loss снижается, в логах нет ни исключения, ни явного провала. Просто межнодовые коллективы идут значительно медленнее, и время до следующего чекпоинта растёт.

Это самая дорогая по времени ошибка в распределённом обучении: часы уходят на перебор гиперпараметров и размеров батча, хотя узкое место находится на сетевом уровне. В публикации AWS этот сценарий подробно не разобран: там описан собранный стек и условие, при котором коллективы идут через EFA, а именно явный запрос адаптера pod'ами. Механику тихого отката ниже стоит читать как общий паттерн отказа NCCL, а не как цитату из источника.

Как выглядит проверка транспорта на практике

  1. Выставить NCCL_DEBUG на всех pod'ах worker-группы и перезапустить их. Без этого шага проверять нечего.
  2. Запустить короткий коллектив или первые шаги обучения. Полный прогон для диагностики не нужен.
  3. Прочитать в выводе, какой сетевой плагин и какой интерфейс выбраны. Если виден сокетный, TCP-путь вместо EFA, обучение пойдёт, но медленно.
  4. Убедиться, что pod действительно получил EFA-интерфейс: посмотреть, виден ли он внутри контейнера. Отсутствие интерфейса объясняет откат без всякой диагностики NCCL.
  5. Сверить FI_PROVIDER и NCCL_NET_PLUGIN с документацией плагина. Проще всего один раз сверить рабочую пару версий и держать её тегом образа.
  6. Сравнить масштабирование: время шага на четырёх GPU в одном узле против восьми GPU на двух узлах. Если прирост близок к нулю, подозрение в первую очередь на сеть.

Правило простое: сначала транспорт, потом гиперпараметры. Проверка занимает минуты, а спасает дни.

Практический кейс: FSDP-дообучение Qwen2.5-1.5B на восьми GPU

В примере из публикации AWS Qwen2.5-1.5B дообучается с PyTorch FSDP на восьми NVIDIA L4, разложенных по двум g6.12xlarge. DLC берётся ровно в опубликованном виде, без единой правки образа. Ray Train размещает восемь worker'ов, по одному на GPU, а межнодовые коллективы идут через EFA.

Что именно делает FSDP в этой схеме: параметры модели, градиенты и состояния оптимизатора шардируются между рангами, а перед прямым и обратным проходом нужны all-gather и reduce-scatter. Это и есть те коллективные операции, которым нужна быстрая сеть. На одном узле они уходят в NVLink и PCIe, между узлами — в EFA или, при ошибке конфигурации, в TCP.

Стоит трезво оценить масштаб: 1.5 млрд параметров в bf16 занимают единицы гигабайт, а восемь L4 дают около 192 ГБ видеопамяти суммарно. То есть пример проверяет механику распределённого запуска и сетевой транспорт, а не упирается в память. Для моделей серьёзнее по размеру понадобятся узлы с большим объёмом VRAM и, скорее всего, механизмы отказоустойчивости: асинхронные чекпоинты и восстановление через NVRx на Amazon EKS сокращают простои GPU при сбоях.

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

Готовый образ снимает проблему сборки, но не снимает проблему инфраструктуры. Три ограничения стоит проверить до первого запуска.

  • EFA работает в пределах одной зоны доступности. Оба g6.12xlarge должны стоять в одной AZ, как в примере. Разнести их по зонам ради отказоустойчивости нельзя: межнодовые коллективы потеряют ускоренный транспорт.
  • Квоты на G-семейство. По умолчанию лимит на GPU-инстансы может быть нулевым или недостаточным. Запрос на увеличение квоты обрабатывается не мгновенно, а без него pod'ы просто останутся в состоянии Pending.
  • Стоимость. Ориентир, который приводят для этой конфигурации, — около $9.50 в час за два g6.12xlarge вместе с control plane EKS и NAT Gateway. Цифра зависит от региона и тарифов, поэтому пересчитайте её под себя и умножьте на планируемое время экспериментов: кластер, забытый на выходные, обойдётся в сумму, сопоставимую с месяцем аренды выделенного сервера.

Чек-лист перед запуском: проверить квоты на G-семейство, зафиксировать одну зону доступности, оценить бюджет на время экспериментов, отдельно посчитать NAT Gateway для выхода pod'ов в интернет, зафиксировать тег DLC и один раз прогнать обучение с NCCL_DEBUG, чтобы увидеть реальный транспорт.

Кому подходит подход, а кому — нет

Подход окупается, если команда уже живёт в Kubernetes и EKS, а поддержка собственных CUDA, NCCL и EFA-образов съедает время инженеров. Один тег вместо десятка согласованных версий и rayStartParams вместо ручной связки узлов дают предсказуемость, ради которой стоит терпеть сложность Kubernetes.

Менее оправдан он в двух случаях. Если обучение одноразовое, проще запустить тот же DLC напрямую на Amazon EC2 без оператора и control plane. Если задача — мультимодальное RL-обучение с собственным стеком роллаутов, смотрите в сторону управляемых сред вроде SageMaker HyperPod: SkyRL и GRPO для Qwen3-VL-8B на HyperPod дают другой набор компромиссов. И честная оговорка: без квот на G-семейство ни один из вариантов не поднимется.

Начните с одного короткого прогона на двух g6.12xlarge с включённым NCCL_DEBUG и сверкой транспорта. Если в логах виден EFA-путь, дальше можно масштабировать модель и батч; если виден TCP, масштабирование бессмысленно, пока не починен сетевой стек.

Подписаться на канал