В чём проблема: почему распределённое обучение упирается в контейнер, а не в код модели
Многоузловое обучение 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 | управляется AWS | API Kubernetes, планировщик, состояние кластера |
| System node | m7i.xlarge | KubeRay operator и Ray head, GPU не нужны |
| EFA-узел 1 | g6.12xlarge | 4 x NVIDIA L4, EFA-интерфейс, 4 Ray worker'а |
| EFA-узел 2 | g6.12xlarge | 4 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, а не как цитату из источника.
Как выглядит проверка транспорта на практике
- Выставить NCCL_DEBUG на всех pod'ах worker-группы и перезапустить их. Без этого шага проверять нечего.
- Запустить короткий коллектив или первые шаги обучения. Полный прогон для диагностики не нужен.
- Прочитать в выводе, какой сетевой плагин и какой интерфейс выбраны. Если виден сокетный, TCP-путь вместо EFA, обучение пойдёт, но медленно.
- Убедиться, что pod действительно получил EFA-интерфейс: посмотреть, виден ли он внутри контейнера. Отсутствие интерфейса объясняет откат без всякой диагностики NCCL.
- Сверить FI_PROVIDER и NCCL_NET_PLUGIN с документацией плагина. Проще всего один раз сверить рабочую пару версий и держать её тегом образа.
- Сравнить масштабирование: время шага на четырёх 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, масштабирование бессмысленно, пока не починен сетевой стек.