В Яндексе постоянно растёт количество задач, где GPU требуются не только для классического машинного обучения, но и для генерации тяжёлого визуального контента: 3D‑сцен, видео, цифровых аватаров, симуляций и синтетических данных для обучения нейросетей.

Ярким кейсом стал проект к премьере сериала «Кибердеревня». Мы сканировали гостей мероприятия, создавали их 3DGS‑аватары (3D Gaussian Splatting) и интегрировали в подготовленные Unity‑сцены. Этот пайплайн наглядно подсветил узкое место: при масштабировании производства скорость упирается не в креатив или алгоритмы реконструкции, а в возможности инфраструктуры рендеринга. Стало очевидно, что нам нужно решение, способное ускорить обработку в десятки раз.

Традиционно такие задачи решаются на локальных рабочих станциях или через специализированные внешние рендер‑фермы. Однако в Яндексе уже есть YTsaurus — распределённая система с GPU‑кластерами, поддержкой контейнеризации и отработанными механизмами планирования задач. Меня зовут Анатолий Томилов, я разработчик инфраструктуры VR и 3D‑реконструкции в Фантехе. В статье я расскажу, почему идея использовать YTsaurus в качестве внутренней рендер‑фермы выглядела логичной, но её реализация оказалась нетривиальной.

Проблема графического стека

Для запуска графических приложений недостаточно просто пробросить GPU в контейнер. Таким инструментам, как Unity, Unreal Engine (UE), Blender или кастомным Vulkan/OpenGL‑рендерерам, требуется полноценное окружение:

  • актуальные драйверы GPU;

  • поддержка API Vulkan и OpenGL;

  • X11-сервер и виртуальный дисплей;

  • доступ к специфическим системным устройствам Linux.

Команда инфраструктуры 3D‑реконструкций прошла путь от базовых тестов до стабильного запуска Unity‑приложения с отрисовкой 3DGS‑аватаров и захватом видеопотока внутри инфраструктуры YTsaurus.

Наша команда работает с MVS/SfM‑пайплайнами, превращая обычные фотографии и видео в текстурированные меши или 3DGS‑сцены (3D Gaussian Splatting). Внутри этого процесса живёт стандартный стек для ML и компьютерного зрения: PyTorch, nvdiffrast, COLMAP и другие инструменты.

Главный инфраструктурный вызов возник на этапе генерации контента. В течение двух дней работы стенда требовалось развернуть пайплайн массового промышленного рендеринга: динамически подставлять кастомные 3D‑аватары отсканированных гостей в готовые Unity‑сцены. Поскольку поток шёл на сотни уникальных объектов, а под каждый ролик требовался свой инстанс сцены, ручные методы и простые скрипты автоматизации исключались. Задача требовала построения отказоустойчивого конвейера с высокой пропускной способностью.

Почему нам не подошли классические виртуальные машины

Рендеринг сцен с высокодетализированными 3DGS‑аватарами и тяжёлыми VFX‑эффектами критичен к объёму VRAM и требует серьёзных вычислительных мощностей. В качестве инфраструктуры мы собирались использовать GPU‑кластер A100 внутри платформы YTsaurus, где видеокарты напрямую пробрасываются в контейнеры с изолированными джобами.

Вариант с развёртыванием классических виртуальных машин с аналогичными GPU отпал из‑за долгого цикла согласования и выделения ресурсов. Попытка заказать годовую квоту под разовую задачу также была нецелесообразной: это затянуло бы сроки и не имело бы экономического обоснования.

Выбор в пользу YTsaurus вместо стандартных виртуальных машин был продиктован характером нагрузки:

  • Задачи на рендер возникают волнообразно. Держать постоянно включёнными мощные GPU‑виртуалки — значит обречь их на низкую утилизацию в периоды простоя.

  • В YTsaurus у нас уже были выделенные ресурсы GPU A100 80G. Использование готовой инфраструктуры позволило сразу перейти к экспериментам с распределённой обработкой, не тратя время на развёртывание и конфигурирование парка отдельных серверов.

  • Пайплайн 3D reconstruction & ML уже находился в YTsaurus, и казалось привлекательным добавить рендеринг получаемой 3D‑реконструкции как ещё один шаг, в конец пайплайна.

Однако наличие GPU в контейнере — это только половина дела. Настоящие сложности начались при попытке запустить в этом окружении графический движок, изначально предназначенный для десктопных ОС.

Технические требования: почему нельзя просто запустить бинарник

У нас уже был опыт рендеринга VR‑контента в YTsaurus через кастомный offscreen‑рендерер на Vulkan. Но новая задача потребовала запуска полноценных движков (Unity/UE), а это добавило в уравнение критическое звено — X11. Чтобы запустить такое приложение в YTsaurus, нужно одновременно обеспечить три условия: GPU‑ускорение, API Vulkan и живой X‑сервер.

Вот почему это превратилось в проблему:

  • GPU‑ускорение. Без него скорость рендеринга падает до неприемлемой, а программные (software) реализации графических API просто не поддерживают расширения, необходимые современным движкам.

  • Vulkan. Это основной графический API для движков на Linux, позволяющий выжать максимум из железа.

  • X11. И здесь кроется главная сложность. Unity и Unreal Engine используют слой Window System Integration (WSI). Даже если нам не нужно физическое окно, библиотеки XCB/Xlib требуют его наличия для создания поверхностей (VkSurfaceKHR) и swapchain images, в которые записывается финальный результат рендеринга.

Под капотом рендерера

Процесс аллокации памяти в этой связке выглядит как сложный многослойный пирог. Vulkan ICD (libvulkan_nvidia.so) обращается к /dev/dri/renderD128 для аллокации буферов через DRM render node и к /dev/nvidia0 для выделения видеопамяти напрямую на GPU.

Движок рендерит кадр в эти буферы и передаёт их через протокол DRI3 в виде файловых дескрипторов dma‑buf в X‑сервер (Xorg). Чтобы всё это заработало, Xorg должен:

  1. Быть запущен с использованием DDX (userspace‑драйвер nvidia_drv.so).

  2. Работать именно на том GPU, где движок запрашивал память для swapchain images.

Только при соблюдении этих условий драйвер nvidia_drv.so может импортировать dma-buf из видеопамяти и вывести их на виртуальный монитор. Внутри системы это выглядит как цепочка вызовов: nvidia_drv.so → ioctl на /dev/nvidia-modeset → модуль ядра nvidia-modeset.ko → nvidia-drm.ko, который и регистрирует устройство /dev/dri/card0.

В поисках минимального сетапа

Как мы выяснили, для полноценной работы связки Vulkan + X11 на A100 недостаточно стандартных устройств, которые прокидываются в обычный вычислительный контейнер.

Обычно в распоряжении операции есть:

  • /dev/nvidiactl — менеджмент ресурсов и межпроцессное взаимодействие;

  • /dev/nvidia0, /dev/nvidia1... — создание логических устройств (VkDevice), отправка команд и синхронизация;

  • /dev/nvidia-uvm — UVM, инициализация драйвера.

Но для графического пайплайна этого мало. Нам критически не хватало:

  • /dev/nvidia-modeset — управление дисплеем и сигналами вертикальной синхронизации (vblank);

  • /dev/dri/card0 — устройство, которое захватывает Xorg, становясь DRM‑мастером;

  • /dev/dri/renderD128 — необходимо для аллокации dma-buf через Vulkan ICD.

Чтобы докопаться до истины и запустить Unity‑приложение в облаке, мы пошли от простого к сложному, постепенно сужая круг разрешений.

Шаг 1. Полная свобода в Docker. Сначала мы запустили простейший «кубик» (vkcube из пакета vulkan-tools) на «коммунальной» разработческой виртуалке с A100. На этом этапе мы использовали флаги --gpus all и --privileged. Это позволило подтвердить гипотезу: запустить X11-сервер внутри изолированного контейнера (без проброса сокета с хоста) реально.

Шаг 2. Затягивание гаек. Мы убрали привилегированный режим. Здесь и началось самое интересное: выяснилось, что в контейнер нужно вручную устанавливать драйверные библиотеки, утилиты типа nvidia-smi и DDX‑драйвер, которые раньше подтягивались магией Docker‑рантайма. На этом же этапе мы окончательно сформировали список необходимых /dev/‑устройств.

Шаг 3. Переезд в Porto. Повторили тот же опыт в Porto‑контейнере (нативном для нашей инфраструктуры). Чтобы не собирать образ с нуля, просто экспортировали рабочий Docker‑контейнер в самодостаточный Porto‑слой. Porto требуется, так как позволяет запускать вложенные контейнеры — это когда контейнеры могут запускаться внутри контейнера и получать доступ к подмножеству его ресурсов при той же или большей изоляции, что требуется при развёртывании инфраструктуры YTsaurus.

Шаг 4. «Ванильная» операция в YTsaurus. Попытка запустить тот же vkcube как штатную операцию в YTsaurus. Главный нюанс — управление драйверами. В YTsaurus драйверные библиотеки накладываются автоматически специальным слоем, поэтому их нельзя было оставлять в базовом образе. На этом этапе мы ожидали серию ошибок, которые должны были показать, чего именно не хватает внутри инфраструктуры YTsaurus.

Шаг 5. Финальный босс: Unity + 3DGS. Только после того, как мы научились стабильно крутить «кубик» в YTsaurus, мы перешли к настоящей задаче: запуску Unity‑приложения с аватарами из «Кибердеревни» и рендерингом результата в видеофайл через ffmpeg.

Когда пример был готов, стало ясно: без вмешательства в саму платформу YTsaurus не обойтись. Мы передали кодовую базу коллегам из Yandex Infrastructure, и в рамках тикета по поддержке Xorg для GPU‑хостов необходимые изменения были подготовлены буквально за несколько часов. Спустя пару недель изменения раскатили на ML‑кластер, открыв возможность рендеринга и полноценного графического вывода в операциях YTsaurus для всех пользователей.

Чтобы окончательно убедиться, что мы можем полноценно рендерить действительно продвинутую компьютерную графику, перед запуском нашего приложения я протестировал её на Quake II RTX. Это публичная демка, которая задействует большое количество продвинутых графических технологий. Когда на A100 стабильно запустился Quake, стало понятно: мы можем рендерить массово любой визуальный контент.

Нюансы Unity и ограничения A100

При переходе к финальному шагу — запуску нашего Unity‑приложения — пришлось решить ещё одну специфическую задачу: поднять внутри контейнера шину dbus, без которой движок отказывался инициализироваться.

Также важно понимать специфику работы с серверными картами. Хотя по количеству шейдерных процессоров они находятся где‑то между десктопными RTX 3070 и 3080, их архитектура заточена под другие задачи:

  • Ray‑Tracing. На A100 физически ноль RT‑ядер. Весь функционал трассировки лучей (который мы видели в Quake II RTX) эмулируется драйвером через Compute‑шейдеры. Это работает быстро только благодаря огромному количеству CUDA‑ядер.

  • Кодирование видео. У A100 также отсутствуют блоки NVENC (hardware encoding units). Поэтому использовать аппаратное ускорение ffmpeg для записи результата не получится — приходится полагаться на софтверные кодеки libx264 или libx265. Благо избыточная мощность CPU на GPU‑хостах позволяет делать это без существенных потерь в скорости.

Несмотря на эти архитектурные особенности, огромное количество вычислительных блоков (которых на треть больше, чем во флагманской RTX 3090) делает A100 отличным инструментом для пакетного рендеринга.

Вместо заключения: зачем нам был нужен этот проект

Казалось бы, зачем так заморачиваться с X‑сервером внутри распределённого хранилища? Конечно, для разовых задач всегда проще арендовать мощную виртуалку или собрать ролик на локальной рабочей станции. Но наш кейс с «Кибердеревней» показал, что ручной запуск не подойдёт, если счёт идёт на тысячи однотипных сцен.

Что нам дал этот эксперимент:

  • Масштабируемость: мы превратили графический рендер в обычную batch‑задачу. Теперь, чтобы развернуть окружение на новой машине, достаточно запустить операцию в YTsaurus.

  • Утилизация железа: мы научились использовать GPU‑мощности, которые раньше не использовались нами между ML‑задачами, для тяжёлой графики.

  • Прозрачный пайплайн: графика стала частью общего конвейера обработки данных. Между 3D‑реконструкцией и готовым видеороликом теперь нет ручного звена — всё в рамках одной экосистемы.

На примере этого проекта мы убедились: если приложить немного усилий (и заручиться поддержкой коллег из Yandex Infrastructure), даже сугубо вычислительный кластер можно превратить в мощную графическую рендер‑ферму. Расскажите в комментариях, встречаются ли у вас схожие задачи, где нужно массово рендерить что‑либо за короткий промежуток времени, и как вы их решаете.