Когда официальные ограничения становятся стеной
NVIDIA DGX Spark - отличная машина для локальных LLM. Пока не попробуешь объединить три таких штуки в кластер. Вот тут и начинается цирк.
Официально? Два Spark максимум. Ограничение встроено в NCCL, их библиотеку для межпроцессного общения в распределенных вычислениях. Почему? Наверное, чтобы продавать более дорогие решения. А может, просто лень было тестировать конфигурации с тремя нодами. Неважно. Проблема есть.
NCCL (NVIDIA Collective Communications Library) - стандарт де-факто для распределенного обучения и инференса на GPU. Без нее современные LLM-кластеры просто не работают.
Почему нельзя просто взять и подключить третий Spark?
Попробовал. Сначала через стандартные средства - NCCL с настройкой TCP. Результат? Таймауты, ошибки инициализации, а в лучшем случае - падение производительности до неприличных значений. Проблема в том, что NCCL из коробки ожидает определенную топологию сети, а в нашем случае каждая машина имеет два сетевых интерфейса: один для управления, другой для RDMA.
Альтернативы? Есть, но все плохие
| Метод | Плюсы | Минусы |
|---|---|---|
| MPI (OpenMPI) | Гибкая настройка топологии | Сложность, оверхед, плохая интеграция с CUDA |
| gRPC поверх TCP | Простота реализации | Задержки убивают производительность на 80% |
| Кастомный RDMA (мой подход) | Максимальная производительность, полный контроль | 1500 строк C кода, отладка на уровне ядра |
Помню, как пробовал стратегии масштабирования из учебников. Теория гладкая, а на практике - сплошные грабли. Особенно когда дело доходит до реального инференса больших моделей, где каждый миллисекунд задержки стоит денег.
Архитектура взлома: как обмануть NCCL
NCCL использует плагинную систему для сетевых транспортов. По умолчанию есть плагины для InfiniBand, Ethernet, NVLink. Но нет плагина для нашей конкретной конфигурации: три машины, каждая с двумя NIC, где один NIC используется для RDMA внутри подсети, а другой - для управления.
1 Реверс-инжиниринг существующих плагинов
Первые две недели ушли на изучение исходников NCCL (да, они открыты, но документации - ноль). Разобрал структуру ncclNet - API, который должен реализовать каждый транспортный плагин. Главное понять: как NCCL ожидает инициализацию соединений, как передаются хендшейки, как работает прогресс операций.
2 Кастомный TCP хендшейк для RDMA
Вот тут самый интересный трюк. RDMA требует предварительной настройки соединения (queue pairs, memory regions). Но как три машины узнают друг о друге? Использовал обычный TCP поверх управляющего интерфейса для обмена метаданными: какие IP у RDMA интерфейсов, какие порты использовать, какие ключи доступа к памяти.
Получился гибрид: управляющие сообщения идут по TCP, а данные - по RDMA. Звучит просто, пока не начинаешь отлаживать race conditions при одновременном подключении трех нод.
Самая частая ошибка: deadlock при инициализации. Машина A ждет от B, B ждет от C, C ждет от A. Классический distributed systems headache.
3 Интеграция с существующим стеком NCCL
Плагин должен предоставить семь основных функций: init, listen, connect, accept, regMr, deregMr, и send/recv. Каждая - это десятки строк кода с обработкой ошибок, таймаутами, откатами.
Особенно интересно было с регистрацией памяти. В RDMA нельзя просто взять и отправить любой буфер. Нужно заранее зарегистрировать region в драйвере, получить ключ (lkey/rkey), и только потом можно делать RDMA операции.
Что получилось в итоге?
- Три DGX Spark работают как один кластер
- Задержка между нодами: 1.2 микросекунды (против 50+ у чистого TCP)
- Пропускная способность: 90% от теоретического максимума RDMA
- Поддержка любых NCCL коллективных операций (all-reduce, broadcast, etc.)
Проверил на реальной задаче - распределенном инференсе Llama 3.1 405B. Без плагина - максимум две ноды, скорость 45 токенов/сек. С плагином - три ноды, 68 токенов/сек. Линейного масштабирования не получилось (мешает overhead координации), но +50% производительности - уже победа.
Кому этот подход подойдет (а кому - нет)
Подойдет если:
- Уже уперлись в ограничения официальных решений NVIDIA
- Имеете доступ к железу с RDMA (не только Spark, подойдут и обычные серверы с InfiniBand)
- Готовы к низкоуровневой отладке (wireshark для RDMA - это отдельный вид искусства)
- Нужна максимальная производительность, а не просто рабочее решение
Не подойдет если:
- Работаете в продакшене с SLA (мой код пока не прошел нагрузочное тестирование на месяц)
- Боитесь kernel panics (отладка драйверов RDMA - занятие для стрессоустойчивых)
- Ищете готовое решение из коробки
Интересно, что похожие проблемы возникают и в других сценариях. Например, при построении гибридных кластеров с разным железом. Или когда пытаешься распределить обработку между архитектурно разными системами.
Стоило ли оно того?
Честно? Если бы знал, сколько времени займет - возможно, поискал бы другой путь. Но теперь у меня есть глубокое понимание того, как работает NCCL изнутри. И это знание оказалось полезным даже для оптимизации одиночных инстансов.
Самая ценная находка: NCCL не так умна, как кажется. Много предположений о топологии сети, много хардкода, много мест, где можно улучшить производительность даже в стандартных конфигурациях.
Что дальше?
Плагин работает, но это только начало. Есть идеи:
- Динамическая балансировка нагрузки между RDMA и TCP в зависимости от размера сообщений
- Поддержка гетерогенных кластеров (Spark + обычные серверы + даже bare-metal системы)
- Интеграция с AETHER-X от NVIDIA для еще большего ускорения
Но главный вывод даже не в коде. Главный вывод: ограничения часто существуют только в документации. И иногда стоит потратить месяц на написание 1500 строк на C, чтобы доказать это самому себе.
А еще - теперь я точно знаю, почему в NVIDIA сидят такие высокие зарплаты. Писать код, который работает на уровне драйверов, с миллисекундными таймаутами и ядерными паниками - это особый вид мазохизма. Но когда все заработало... Этого стоит.