Привет, Хабр! Меня зовут Александр Подмосковный, я работаю в «Московском кредитном банке» и, как многие, увлёкся темой искусственного интеллекта. Когда модель DeepSeek R1 стала широко обсуждаться в сообществе, я заинтересовался, можно ли эффективно использовать её и другие крупные модели в домашних условиях, не прибегая к дорогостоящим облачным сервисам. Так как DevOps и инфраструктурой я увлекаюсь уже несколько лет, за это время у меня постепенно сформировалась домашняя лаборатория, на которой я и решил проверить эту идею. 

Одним из результатов воплощения идеи в жизнь стала эта статья. Это пошаговая инструкция реализации распределённого инференса с использованием Ray Serve, vLLM, Kubernetes, Proxmox и других технологий. Для удобства чтения я разделил статью на три части, все они будут опубликованы на этой неделе. 

В этой, первой, части мы настроим GPU и проброс в Proxmox, развернём Kubernetes-кластер, установим GPU Operator и KubeRay Operator. Во второй — напишем скрипт vLLM для шардирования и распределения вычислений, настроим KubeRay Cluster и запустим инференс. В третьей — организуем авторизацию и интеграцию с OpenWebUI и подведём итоги.

Моя мотивация и цель

После того как я развернул урезанную версию модели на 32 млрд параметров с помощью Ollama, у меня возник вопрос: «А можно ли распределить модель, которая не помещается в видеопамять одной GPU, между несколькими GPU?» Оказалось, что такой подход возможен, и я решил поэкспериментировать, используя домашнее оборудование.

Моя цель — использовать доступные видеокарты для запуска более крупных моделей. Это помогло бы снизить порог вхождения для многих разработчиков и компаний с точки зрения отношения инвестиции/профит. Отсюда родилась идея распределённой архитектуры инференса, включающей шардирование и эффективное использование нескольких видеокарт.

Важно отметить, что описанный мной кластер — результат нескольких лет постепенного развития и вложений. Для повторения решения из статьи в упрощённом виде достаточно пары серверов с доступными GPU, такими как RTX 3090, которые намного дешевле профессиональных аналогов.

Введение в распределённый инференс и шардирование

Проблема работы с большими моделями, которые не помещаются в память одной видеокарты, решается двумя основными подходами:

  1. Распределение вычислений между несколькими GPU.

  2. Шардирование модели.

Модели с большим числом параметров, например Llama, GPT или DeepSeek, требуют значительных вычислительных ресурсов. Использование нескольких видеокарт для распараллеливания инференса позволяет эффективно обрабатывать запросы без необходимости в супердорогих вычислительных мощностях.

Шардирование модели помогает разделить её на несколько частей (шардов) и загрузить их на разные GPU. Благодаря этому модели, которые не помещаются в память одного устройства, могут работать на нескольких видеокартах одновременно.

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

Моя домашняя лаборатория

Моя текущая лаборатория — это кластер из 14 серверов. Понимаю, что для большинства задач и проектов такой масштаб не требуется. Это скорее результат длительного увлечения технологиями, чем необходимость для запуска LLM.

Для запуска распределённого инференса и описанных в статье задач вполне хватит небольшого количества узлов и GPU среднего уровня, таких как RTX 3090 с 24 ГБ памяти. На вторичном рынке они доступны за ~70 тысяч рублей.

Вот оборудование, с которым я буду работать:

  • 7 серверов для рабочих нагрузок — гиперконвергентные системы на базе материнских плат Supermicro X11 и процессоров Intel Xeon Scalable:

    • Сеть 4 × 10Gbe (LACP) для высокой пропускной способности.

    • Из них 4 сервера с GPU: 

      • 1 × RTX 3060 (12Gb);

      • 3 × RTX 3090 (24Gb).

    • NVMe-диски:

      • часть используется для ZFS с RAIDZ02 под БД;

      • часть используется для Ceph (RBD, CephFS, RGW).

  • 7 машин для Control Plane и балансировок:

    • Некоторые на NUC-устройствах, другие — на потребительском железе.

Всё это работает в кластере Proxmox 8.4.1 с Ceph 19.2.1, что позволяет мне проводить эксперименты с распределёнными системами.

Так выглядит моя домашняя лаборатория
Так выглядит моя домашняя лаборатория

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

  • 2–3 сервера или мощных ПК, совместимых с GPU;

  • 2–3 видеокарты RTX 3090 (24 ГБ) или хотя бы RTX 3060 (12 ГБ);

  • простое сетевое оборудование (1GbE или 10GbE по возможности).

Как устроен мой кластер

Для реализации распределённого инференса и шардирования модели я выбрал Ray Serve и vLLM за их простоту настройки, хорошую документацию и сообщество. Такая архитектура позволяет распределять как данные, так и вычисления между несколькими GPU. Это обеспечивает масштабируемость и высокую производительность. Существуют и альтернативные решения, например NVIDIA Triton или TorchServe, но они требуют большей подготовки и сложнее в домашнем развёртывании.

Основные компоненты:

  1. Kubernetes-кластер, развёрнутый с помощью Deckhouse. Он позволяет динамически добавлять узлы с GPU и масштабировать вычисления. Этот кластер — основа для всех вычислений и развёртывания моделей.

  2. Kuberay — оператор для управления кластером Ray, используемый для запуска распределённых задач.

  3. Ray Serve — компонент для развёртывания API-интерфейсов, который интегрируется с vLLM для обработки запросов.

  4. vLLM — библиотека для эффективного распределения инференса на несколько GPU с использованием NVIDIA Collective Communications Library (NCCL), которая управляет шардированием и синхронизацией данных между видеокартами.

Всё это работает следующим образом:

  1. Модели загружаются и разбиваются на части. Каждая часть модели загружается на отдельную видеокарту.

  2. Ray Serve обрабатывает входящие HTTP-запросы, направляя их к vLLM, которая управляет распределением данных и вычислений.

  3. vLLM и NCCL синхронизируют вычисления между GPU, что позволяет эффективно использовать всю доступную вычислительную мощность.

  4. OpenAI API-совместимый эндпойнт настроен через FastAPI, чтобы интегрироваться с другими инструментами и сервисами.

Преимущества такой архитектуры и вызовы

Преимущества:

  1. Масштабируемость: можно увеличивать вычислительные ресурсы, добавляя новые GPU.

  2. Экономия: RTX 3090 была выбрана за оптимальное соотношение памяти (24 ГБ) и стоимости на вторичном рынке. Это позволяет экономично использовать несколько GPU вместо одной дорогой серверной карты, например NVIDIA A100.

  3. Гибкость: архитектура легко адаптируется под разные задачи и модели.

Вызовы:

  1. Управление нагрузкой: необходимо эффективно управлять распределением данных и вычислений между GPU, чтобы избежать узких мест в производительности.

  2. Сложность настройки: настройка распределённого инференса требует внимательности к деталям, например правильного использования vLLM для синхронизации данных между устройствами.

Как это работает на практике

В трёх частях статьи я расскажу, как и вам настроить и развернуть это решение. Мы разберём:

  1. Настройку Proxmox для работы с GPU.

  2. Настройку Kubernetes — как настроить Kubernetes, добавить узлы с GPU, настроить Proxmox и подключиться к кластеру Ceph — и Ray для распределённого инференса.

  3. Настройку vLLM для шардирования модели и распределения вычислений между несколькими GPU.

  4. Конфигурацию Ray Serve для обработки запросов через FastAPI.

  5. Примеры запросов и то, как интегрировать систему с существующими решениями.

Настройка GPU и проброс в Proxmox

В этом разделе мы подготовим виртуальные машины для будущего Kubernetes-кластера и обеспечим прямой доступ к видеокартам через технологию PCI passthrough в среде Proxmox. Такой подход позволяет эффективно задействовать ресурсы GPU в виртуализации, избегая потерь производительности и накладных расходов на виртуализацию устройств.

Отмечу, что использование Proxmox и PCI passthrough — это всего лишь один из возможных способов реализации подобного решения. Например, аналогичные задачи можно решать с помощью прямого использования физических серверов без виртуализации (bare-metal), применения других гипервизоров (например, VMware ESXi, KVM/QEMU без Proxmox) или же контейнеризации с прямым доступом к устройствам через Docker или Podman.

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

Частые ошибки:

  • Убедитесь, что IOMMU и PCI passthrough корректно включены в BIOS.

  • Не забудьте поместить стандартные видеодрайверы (nouveau, nvidia) в blacklist, иначе GPU не пробросится.

Настройка GRUB

Для включения IOMMU и корректного проброса PCI-E-устройств отредактируйте загрузчик GRUB:

1. Откройте файл /etc/default/grub.

2. В строке GRUB_CMDLINE_LINUX_DEFAULT добавьте параметры (в примере для процессоров Intel):

   intel_iommu=on iommu=pt pcie_acs_override=downstream,multifunction

Пример:

GRUB_CMDLINE_LINUX_DEFAULT="quiet intel_iommu=on iommu=pt pcie_acs_override=downstream,multifunction"

3. Сохраните изменения и обновите загрузчик:

update-grub

Поиск и проверка GPU

Дальше просмотрите все PCI-устройства:

lspci

Найдите нужную видеокарту, например по адресу 65:00, и определите её идентификаторы:

lspci -n -s 65:00

Вывод в моём случае:

65:00.0 0300: 10de:2203 (rev a1)
65:00.1 0403: 10de:1aef (rev a1)

Блокировка конфликтующих драйверов

Чтобы стандартные драйверы не перехватывали управление GPU, внесите их в «чёрный список»:

  1. Откройте /etc/modprobe.d/blacklist.conf.

  2. Добавьте строки:

blacklist radeon
blacklist nouveau
blacklist nvidia
blacklist nvidiafb

Загрузка необходимых модулей

Укажите модули ядра, требуемые для VFIO, в файле /etc/modules:

vfio
vfio_iommu_type1
vfio_pci
vfio_virqfd

Настройка VFIO

Создайте или отредактируйте /etc/modprobe.d/vfio.conf.

Добавьте строку с идентификаторами ваших устройств из шага «Поиск и проверка GPU»:

options vfio-pci ids=10de:2203,10de:1aef

Обновление initramfs

Примените изменения, обновив initramfs:

update-initramfs -u -k all

Перезагрузка сервера

Перезагрузите систему, чтобы применить новые настройки:

reboot

Маппинг GPU в интерфейсе Proxmox

В веб-интерфейсе Proxmox перейдите в DatacenterResource Mapping. Найдите нужное PCI-E-устройство (GPU) и выполните маппинг согласно подсказкам в интерфейсе.

Добавление GPU к виртуальной машине

В настройках виртуальной машины откройте раздел Hardware. Выберите AddPCI Device и укажите свою видеокарту, которую вы ранее замапили.

Промежуточные итоги 

Теперь у нас:

  1. Proxmox настроен на проброс GPU в виртуальные машины (PCI passthrough).

  2. Виртуальные машины могут использовать мощность видеокарт напрямую.

  3. Выполнены базовые условия для дальнейшего развёртывания распределённого инференса с Ray Serve, vLLM и так далее.

Настройка Kubernetes, добавление GPU-узлов, установка GPU Operator и KubeRay Operator

В этом разделе мы установим Kubernetes-кластер с помощью Deckhouse и настроим узлы с поддержкой GPU. Здесь же даны YAML-файлы конфигурации — config.yml и resources.yml — и подробные комментарии к ним.

Подготовка узлов для Kubernetes с Deckhouse

Для развёртывания Kubernetes-кластера внутри виртуальных машин рекомендую использовать образ Ubuntu 22.04 — он стабилен и обладает хорошей поддержкой современных драйверов и компонентов Kubernetes.

Я использую Deckhouse Kubernetes Platform Enterprise Edition — «Флант» любезно предоставил триальную лицензию для исследования функционала платформы. Однако описанные ниже возможности, включая интеграцию и работу узлов с GPU, полностью доступны и в бесплатной редакции Community Edition.

Для развёртывания кластера с Deckhouse Kubernetes Platform вы можете следовать официальной документации. Там подробно описан процесс установки и настройки, включая интеграцию узлов с GPU.

Краткий обзор процесса установки

  1. Вы логинитесь в приватный Docker Registry Deckhouse, используя предоставленный лицензионный ключ (для EE-версии).

  2. Запускаете контейнер Deckhouse Installer, где выполняется команда dhctl bootstrap.

  3. По завершении развёртывания у вас будет готовый Kubernetes-кластер, который можно масштабировать и дополнять.

  4. После успешного запуска кластера устанавливаете и настраиваете KubeRay поверх Kubernetes, что позволит управлять ресурсами GPU в рамках Ray.

Пример запуска

Шаг 1. Аутентификация и запуск контейнера:

base64 -d <<< <LicenseKey> | docker login -u license-token --password-stdin registry.deckhouse.ru
docker run --pull=always -it \
  -v "$PWD/config.yml:/config.yml" \
  -v "$HOME/.ssh/:/tmp/.ssh/" \
  -v "$PWD/resources.yml:/resources.yml" \
  -v "$PWD/dhctl-tmp:/tmp/dhctl" \
  --network=host \
  registry.deckhouse.ru/deckhouse/ee/install:stable bash

Шаг 2. Запускаем установку внутри контейнера:

dhctl bootstrap --ssh-user=root --ssh-host=192.168.3.51 --ssh-agent-private-keys=/tmp/.ssh/id_ed25519 \
  --config=/config.yml \
  --config=/resources.yml \
  --ask-become-pass

Файлы конфигурации

Когда я начал развёртывать домашний Kubernetes-кластер, то использовал официальный быстрый старт от Deckhouse, в котором есть только один конфигурационный файл — config.yml. Второй файл — resources.yml — я составлял самостоятельно, руководствуясь документацией Deckhouse Kubernetes Platform и конкретными требованиями своего домашнего кластера. Это позволило мне сразу после первой установки получить полностью готовую рабочую среду без ручной донастройки.

Первый файл — config.yml

Большинство настроек этого файла я взял из официальной документации Deckhouse и не менял, так как они хорошо подходили для моего случая:

  • Версия Kubernetes — 1.30. Выбрал стабильную версию 1.30 для совместимости с текущей версией KubeRay и vLLM.

  • Подсети. Подсети для подов (10.111.0.0/16) и сервисов (10.222.0.0/16) — стандартные из примера Deckhouse. А вот для internalNetworkCIDRs я взял свою домашнюю подсеть (192.168.2.0/23) — ноды размещены именно в ней.

  • releaseChannel и bundle. Использую канал обновлений Stable и стандартный набор модулей (Default bundle), чтобы избежать неожиданных изменений и иметь проверенные обновления платформы.

  • customTolerationKeys (- dedicated.example.com) необходимо указывать, чтобы позволить планировщику размещать критически важные компоненты Deckhouse Kubernetes Platform на выделенных узлах.

  • ingressClass: system-ingress — задал класс ingress по умолчанию.

Модули Deckhouse Kubernetes Platform. Сразу активировал те модули, которые были мне критично важны:

  • csi-ceph — моё основное хранилище данных на домашнем Ceph-кластере (также указал defaultClusterStorageClass: "ceph-rbd-sc").

  • cert-manager — автоматическое получение внутренних сертификатов через мой промежуточный (intermediate) CA. Чтобы всё работало, я указал имя cluster issuer в clusterIssuerName: inter-ca.

  • cni-cilium — включен по умолчанию.

  • metallb и ingress-nginx — обеспечил возможность внешнего доступа к сервисам и балансировки трафика внутри моей сети.

Также включил дополнительные модули безопасности и управления (operator-trivy, runtime-audit-engine, multitenancy-manager, console).

Второй файл — resources.yml.

Этот файл я собирал самостоятельно, чтобы сразу получить необходимые мне дополнительные настройки и интеграции:

  • Ceph-хранилище. Указал IP-адреса своих Ceph-мониторов (192.168.3.10, .11, .12) и ключ доступа пользователя, чтобы Deckhouse автоматически создал StorageClass (RBD и CephFS) без дополнительной конфигурации.

  • Группы узлов (NodeGroup). Чётко разделил роли узлов и настроил метки и taints, чтобы контролировать размещение подов Kubernetes:

    • w-gpu (RTX 3090) и w-gpu-3060 — с taints dedicated.example.com=w-gpu:NoExecute, чтобы GPU-узлы были выделены строго под задачи машинного обучения и инференса.

    • w-std — 6 стандартных рабочих узлов под общие задачи.

    • w-db — с taints dedicated.example.com=w-db:NoExecute, чтобы выделить 3 узла под базы данных.

  • LocalPathProvisioner. Для нод w-db — точечно настроил локальное хранилище на NVMe-дисках, где держу ZFS-RAIDZ02.

  • NodeGroupConfiguration — скрипты для автоматической настройки узлов. Уникальная возможность Deckhouse, которой нет в ванильном Kubernetes:

    • install-cuda.sh — устанавливает CUDA и nvidia-container-toolkit, без чего GPU не заработали бы полноценно.

    • containerd-additional-config.sh — включает поддержку GPU-контейнеров в containerd (nvidia-container-runtime).

    • add-gitlab-registry-cert.sh — для экспериментов у меня есть внутренний GitLab registry, буду его использовать для собственных Docker-образов, поэтому добавляю его в настройки containerd.

Эти скрипты полностью автоматизируют подготовку моих узлов без необходимости использования внешних плейбуков Ansible.

  • StaticInstance. Список статических узлов с IP-адресами для автоматического подключения Deckhouse через SSH.

  • IngressNginxController и MetalLB. Создал два Ingress-контроллера (public и internal), настроив MetalLB на автоматическое выделение внешних IP из указанного мной адресного пула.

  • Пользователи и авторизация (ClusterAuthorizationRule и User). Создал одного администратора (admin) с полными правами, чтобы максимально упростить работу с кластером.

  • TLS и сертификаты. Настроил внутренний CA (intermediate CA), создав Secret (internal-ca-key-pair) и ClusterIssuer (inter-ca). Это решение дало мне полный контроль над внутренними сертификатами и существенно упростило управление безопасностью. По умолчанию приложения во внутренней сети уже доверяли этому CA.

Структура кластера после установки

  • 3 мастер-узла;

  • 6 worker-узлов (w-std);

  • 3 worker-узла для баз данных (w-db);

  • 4 GPU-узла (w-gpu, w-gpu-3060).

Установка и настройка KubeRay поверх Kubernetes

После успешного развёртывания Deckhouse-кластера можно добавить KubeRay. Есть два варианта:

  1. При желании установить Argo CD, чтобы управлять манифестами и Helm-чартами через GitOps.

  2. Использовать Helm напрямую для установки нужных компонентов.

Установка GPU Operator

NVIDIA GPU Operator упрощает и автоматизирует процесс установки и обновления драйверов, утилит и сервисов для GPU на узлах Kubernetes. Он проверяет, на каких узлах есть GPU, и развёртывает на них:

  • NVIDIA-драйвер, который может собирать динамически или использовать готовые образы при наличии поддержки в вашей ОС;

  • nvidia-container-runtime, чтобы поды, запущенные на GPU-узлах, могли видеть устройства GPU и использовать их;

  • DCGM (Data Center GPU Manager) и DCGM Exporter для метрик и мониторинга;

  • MIG Manager при необходимости, если вы используете разбивку GPU (Multi-Instance GPU), которая доступна только на некоторых профессиональных картах.

Обратите внимание, что для домашнего кластера удобнее использовать готовые образы от NVIDIA, чтобы не собирать драйверы вручную.

Как это работает

  1. Operator реагирует на появление или изменение узлов с лейблами вида nvidia.com/gpu.deploy.operands=true или аналогичной логики и устанавливает на них драйверы и сервисы.

  2. Каждый узел получает DaemonSet от GPU Operator, который развёртывает нужные компоненты.

  3. Управление драйверами: обновления, перезагрузка при новых версиях, мониторинг статуса (через DCGM).

Файл ap-values.yaml

В файле ap-values.yaml для GPU Operator задаются:

  • путь к репозиторию NVIDIA — registry, теги образов;

  • включение/выключение Node Feature Discovery (NFD) — автоматически определяет тип GPU и добавляет лейблы;

  • детальная настройка daemonsets, updateStrategy, tolerations, при желании – driver (версия, precompiled-драйверы, запуск autoUpgrade);

  • включение отдельных компонентов (MIG, DCGM, DCGM Exporter, GDS, vGPU, VFIO и прочих).

Ключевые поля:

  • driver.enabled: true/false — указывает, нужно ли устанавливать драйвер напрямую;

  • mig.strategy: single — обозначает, используете ли вы MIG в полном объёме, частично или вовсе нет;

  • devicePlugin.enabled: true — активирует установку Device Plugin, чтобы Kubernetes мог корректно назначать GPU-поды на узлы;

  • dcgm.enabled: true / dcgmExporter.enabled: true — управляют сбором метрик GPU.

Установка KubeRay Operator

KubeRay Operator — это оператор для Kubernetes, который позволяет:

  • создавать, управлять и масштабировать Ray-кластеры (RayCluster CRD);

  • запускать задачи и сервисы в виде RayJob, RayService и так далее;

  • автоматически распределять нагрузку по доступным узлам, включая GPU-узлы.

Как это работает

  1. KubeRay Operator устанавливает Custom Resource Definitions (CRDs), такие как RayCluster, RayJob, RayService.

  2. Когда в кластере появляется ресурс типа RayCluster, оператор создаёт нужное количество подов Ray (Head, Worker) и управляет их жизненным циклом (автоматический рестарт, масштабирование, обновления).

  3. Для работы с GPU Operator KubeRay Operator использует нативные механизмы Kubernetes (labels, taints, Device Plugin и прочие), чтобы запускать RayWorker’ы на узлах, где есть доступные GPU.

Файл ap-values.yaml

В конфигурационном файле ap-values.yaml для KubeRay Operator можно указать:

  • образ оператора (репозиторий, теги);

  • параметры Pod Security (securityContext, podSecurityContext), если важен комплаенс;

  • RBAC (роль, которую получит оператор), в том числе для leader election и управления ресурсами в своих пространствах имён;

  • watchNamespace — список пространств имён, в которых оператор будет слушать события CRD (можно ограничиться одним пространством имён);

  • batchScheduler — если хотите интеграцию с кастомными планировщиками (Volcano, YuniKorn);

  • env-переменные (например, ENABLE_INIT_CONTAINER_INJECTION=true), управляющие поведением Ray-подов.

Пример с подробными комментариями приведён в блоке values.yaml. Ключевые моменты:

  • image.repository и image.tag — где хранится оператор;

  • resources — рекомендуемые ресурсы для пода оператора (CPU, RAM);

  • leaderElectionEnabled — нужен, если вы хотите запускать несколько реплик оператора для отказоустойчивости;

  • singleNamespaceInstall — при значении true оператор будет работать только в одном пространстве имён и вы не сможете управлять Ray-кластерами за его пределами.

Промежуточные итоги

Итак, теперь у нас:

  1. Настроена Deckhouse Kubernetes Platform для управления кластером (Ceph, Ingress, Cilium, MetalLB и так далее).

  2. Настроены узлы с GPU (CUDA, nvidia-container-runtime), что даёт возможность запускать высоконагруженные задачи.

  3. Установлен GPU Operator (от NVIDIA) для автоматического управления драйверами, мониторингом и ресурсами GPU.

  4. Установлен KubeRay Operator, который позволяет развёртывать Ray-кластеры и использовать ресурсы GPU непосредственно внутри Kubernetes.

В следующей части

В следующей статье мы рассмотрим, как организовать распределённый инференс с помощью vLLM и обеспечить доступ к нему через Ray Serve, а также разберёмся, как подготовить Docker-образ, поместить его в Registry и развернуть Ray Cluster с нужными параметрами. 

Минутка рекламы

20 мая в 19:35 я выступаю на первом митапе Deckhouse User Community с докладом по теме этой статьи. Если вам комфортнее слушать, а не читать, регистрируйтесь и подключайтесь. Места в Москве закончились, но можно присоединиться онлайн. 

Комментарии (0)