Интеграция поддержки Nvidia в контейнерах
Видеокарты играют важную роль в современных компьютерах и используются не только для игр, но и других задач. Видеокарты в современном мире применяются как ускорители вычислений. В них одновременно выполняется множество вычислений, что делает их особенно эффективными для специфических задач, таких как нейронные сети. Поэтому видеокарты сейчас востребованы и активно применяются. В dBrain.cloud мы используем видеокарты Nvidia с платформой CUDA, которая позволяет писать код, исполняемый на графическом адаптере.
NVIDIA прошла большой путь в поддержке Kubernetes. Ранее пользователям приходилось придумывать сложные схемы для интеграции поддержки видеокарт в K8s. Во времена Docker существовал специальный runtime hook, который следил за выполняемыми в контейнере командами и определял, следует ли запускать его с поддержкой видеокарты. Теперь в K8s появился стандарт CDI — container device interface, который аналогично CRI и CNI стандартизирует аспекты работы с произвольными железными девайсами и контейнерами.
Тонкости интеграции
NVIDIA предлагает решение в виде GPU Operator. Этот инструмент автоматически устанавливает драйверы для видеокарт на хостах и настраивает Container Runtime (в нашем случае cri-o). Однако этот оператор ограничен в настройках, его невозможно кастомизировать и использовать в сетапах без доступа к интернету. Поэтому инженерам dBrain приходилось выполнять большинство операций вручную.
Вместе c поддержкой CDI (Container Device Interface), который позволяет пробрасывать произвольные устройства внутрь контейнера, у NVIDIA появились соответствующие инструменты — NVIDIA Device Plugin и NVIDIA Feature Discovery. NVIDIA Device Plugin управляет работой видеокарт на хосте и через CDI добавляет ресурсы в Kubernetes. Работает он в связи с kubelet.
Например, когда разработчик делает описание пода, в группе ресурсов можно отметить лимиты, реквесты, например, CPU и память. Если у вас настроена поддержка GPU, появляется параметр nvidia.com/gpu и можно запрашивать необходимое количество видеокарт — в зависимости от доступных на хосте.
Однако расшарить на несколько контейнеров одну видеокарту можно, только если это карты профессиональных серий (Tesla, Quadro) или предназначенные для дата-центров.
В случае с обычными картами можно запросить один или несколько GPU, чтобы NVIDIA Device Plugin и Runtime Interface добавили видеокарты в контейнер. Для запуска пода с поддержкой GPU больше не требуется писать кастомные команды в Entry Point контейнера, как это было ранее.
В Kubernetes существует абстракция под названием runtimeClass, в dBrain она по дефолту crun. Можно указать в деплойменте runtimeClassName: nvidia и он будет автоматически запускаться с поддержкой всех необходимых функций.
Помимо этого для удобства разработчиков есть инструмент NVIDIA Feature Discovery. Он запускается с поддержкой GPU, собирает информацию о хосте и вешает ее в виде лейблов на хост. В лейблах содержится информация о поддерживаемых инструкциях, количестве установленных видеокарт, драйверах, cuda capabilities и другие характеристики. Например, если у вас есть программное обеспечение, написанное на CUDA 12, но один из хостов работает с CUDA 11, то программа не сможет запуститься на этом хосте из-за отсутствия обратной совместимости.
Наши девопсы интегрировали поддержку NVIDIA в платформу dBrain, чтобы автоматизировать для пользователя все процессы.
В дальнейшем такая интеграция открывает перспективы для более гибкого использования обычных видеокарт, например, для их разделения между контейнерами (использовать одну видеокарту для нескольких контейнеров, выделяя по половине на каждый из них). Такой подход уже работает на видеокартах NVIDIA Tesla и предназначенных для дата-центров. На одной видеокарте можно запускать несколько контейнеров (в том числе несколько десятков) — они будут работать параллельно.
Также в dBrain мы интегрировали поддержку видеокарт Intel. Оказалось, что обычные потребительские видеокарты Intel в задачах нейронных сетей ощутимо быстрее, чем десктопные решения от NVIDIA.
Проблемы
Установка драйвера NVIDIA на хосте может быть настоящим испытанием. Проходит она через DKMS (Dynamic Kernel Module Support). Этот инструмент на лету компилирует модуль ядра. Компиляция происходит под конкретные ядро и хост прямо на этом хосте.
У нас ядро скомпилировано не стандартным для операционных систем Linux GCC (GNU Compiler Collection). В dBrain ядро скомпилировано с помощью более строгого к синтаксису Clang и с оптимизацией LTO (Link Time Optimization), что позволяет повысить производительность на 5–10 процентов.
Ранее драйверы NVIDIA не компилировались с помощью Clang, пока в одной из версий NVIDIA не исправила код.
На реализацию всего этого у команды dBrain ушло около двух недель. Процесс оказался довольно утомительным, но в конечном итоге удалось собрать все необходимые компоненты и интегрировать их в систему.
Бонус
Приведем пример настройки рантайма Crio.
В файл /etc/crio/crio.conf пишем:
[crio.runtime.runtimes.crun]
runtime_path = "/usr/bin/crun"
runtime_type = "oci"
runtime_root = "/run/crun"
[crio.runtime.runtimes.nvidia]
runtime_path = "/usr/bin/nvidia-container-runtime"
runtime_type = "oci"
Этим добавляем runtime NVIDIA и runtime Crun в нашем случае
в /etc/nvidia-container-runtime/config.toml
#accept-nvidia-visible-devices-as-volume-mounts = false
#accept-nvidia-visible-devices-envvar-when-unprivileged = true
disable-require = false
supported-driver-capabilities = "compat32,compute,display,graphics,ngx,utility,video"
#swarm-resource = "DOCKER_RESOURCE_GPU"
[nvidia-container-cli]
#debug = "/var/log/nvidia-container-toolkit.log"
environment = []
#ldcache = "/etc/ld.so.cache"
ldconfig = "@/sbin/ldconfig"
load-kmods = true
no-cgroups = false
#path = "/usr/bin/nvidia-container-cli"
#root = "/run/nvidia/driver"
user = "root:root"
[nvidia-container-runtime]
#debug = "/var/log/nvidia-container-runtime.log"
log-level = "info"
mode = "auto"
runtimes = ["crun"]
[nvidia-container-runtime.modes]
[nvidia-container-runtime.modes.cdi]
annotation-prefixes = ["cdi.k8s.io/"]
default-kind = "nvidia.com/gpu"
spec-dirs = ["/etc/cdi", "/var/run/cdi"]
[nvidia-container-runtime.modes.csv]
mount-spec-path = "/etc/nvidia-container-runtime/host-files-for-container.d"
[nvidia-container-runtime-hook]
path = "nvidia-container-runtime-hook"
skip-mode-detection = false
[nvidia-ctk]
path = "nvidia-ctk"
После создания runtimeClass в k8s и установки на кластер nvidia-device plugin система заработает.
Несмотря на сложности с интеграцией, видеокарты остаются ключевыми компонентами для ускорения вычислений в различных областях. Делитесь своим опытом применения видеокарт.