Стадия жизненного цикла модуля: Общедоступная версия

У модуля есть требования для установки

В режиме DRA рабочая нагрузка запрашивает GPU на уровне пода, а не на уровне узла. Сделать это можно двумя способами:

  • Через extended-ресурс — администратор публикует GPUClass, а пользователь запрашивает его по имени одной строкой. Рекомендуемый способ.
  • Создав ResourceClaimTemplate самостоятельно — пользователь создаёт DeviceClass с CEL-селектором и ResourceClaimTemplate. Используйте этот способ, когда опубликованных классов недостаточно.

Запрос GPU по имени

Уточните у администратора, какие классы доступны, или посмотрите сами с помощью следующих команд:

Просмотр всех GPUClass в кластере:

d8 k get gpuclasses

Просмотр классов устройств для динамического выделения GPU (DRA), созданных контроллером:

d8 k get deviceclasses -l gpu.deckhouse.io/managed-by=gpu-controller

Каждый опубликованный класс предоставляет extended-ресурс с именем gpu.deckhouse.io/<имя-device-class>. Запрашивайте его как любой другой ресурс:

apiVersion: v1
kind: Pod
metadata:
  name: cuda-probe
spec:
  restartPolicy: Never
  containers:
    - name: app
      image: nvidia/cuda:12.8.0-devel-ubuntu24.04
      command: ["nvidia-smi", "-L"]
      resources:
        requests:
          gpu.deckhouse.io/ml-ai-h100: 1
        limits:
          gpu.deckhouse.io/ml-ai-h100: 1

На этапе admission модуль заменяет extended-ресурс сгенерированным ResourceClaim и сам подключает его к контейнеру — пользователю не нужно писать ни spec.resourceClaims, ни resources.claims.

Какое значение запрашивать

Зависит от класса. Чтобы установить нужное значение, используйте имя класса:

Имя класса Что запрашивать Значение
<класс> 1 Один целый GPU, эксклюзивно.
<класс>-<профиль> 1 Одна MIG-партиция этого профиля, эксклюзивно.
<класс>-mps-percent, <класс>-ts-percent 1–100 Указанный процент разделяемого GPU.
<класс>-<профиль>-mps-percent, -ts-percent 1–100 Указанный процент разделяемой MIG-партиции.

Например, четверть разделяемой партиции 1g12gb:

resources:
  requests:
    gpu.deckhouse.io/ml-ai-h100-1g12gb-mps-percent: 25
  limits:
    gpu.deckhouse.io/ml-ai-h100-1g12gb-mps-percent: 25

Чтобы понять, в каких единицах измеряется класс, посмотрите его лейбл gpu.deckhouse.io/capacity-units: значение 1 устанавливается для эксклюзивных классов, 100 — для классов -percent.

На что обратить внимание при запросе GPU по имени

Имя класса должно существовать. Опечатка отклоняется сразу, а не игнорируется без каких-либо предупреждений:

Error from server (Forbidden): admission webhook "podgpuresourceclaim.gpu.deckhouse.io" denied
the request: get DeviceClass "no-such-class": deviceclasses.resource.k8s.io "no-such-class" not found

В системных неймспейсах запрос GPU по имени не работает. Вебхук намеренно не применяется в kube-system и во всех неймспейсах d8-*. Там extended-ресурс остаётся в спецификации пода без изменений — и поскольку ни один узел его не публикует, под навсегда останется в Pending. При этом никаких оповещений или предупреждений не появляется. Запускайте GPU-нагрузки в пользовательских неймспейсах.

Разделение между неймспейсами

По умолчанию разделяемый GPU переиспользуется только нагрузками из того же неймспейса. Проверка выполняется на узле: pre-bind conflict predicate отклоняет выделение до привязки, а адаптер вендора перепроверяет его на этапе подготовки. Отказ виден как условие BindingFailed=True с причиной NamespaceConflict в ResourceClaim.status.devices. Ограничение действует на уровне размещения (placement), поэтому разные MIG-партиции одной карты остаются доступны разным неймспейсам. Администраторы кластера могут снять его параметром dra.allowCrossNamespaceSharing.

Запрос устройства напрямую

Этот вариант подходит для случаев, когда опубликованные классы не удовлетворяют потребности пользователя: нужна конкретная карта по PCI-адресу, нестандартная комбинация атрибутов или opaque-конфигурация устройства, например VFIO-проброс.

Атрибуты, по которым может работать селектор, перечислены в разделе «Справочник атрибутов устройства».

При запросе устройства напрямую пользователь:

  1. Создает DeviceClass, CEL-селектор которого выбирает подходящие устройства.
  2. Создает ResourceClaimTemplate, описывающий, что именно выделить из этого класса.
  3. Описывает в ресурсе Job под, который ссылается на шаблон и передаёт описанный в ResourceClaimTemplate claim в контейнер.

Запрос ресурсов в поде

spec:
  resourceClaims:                       # Объявление созданного ResourceClaimTemplate для пода.
    - name: gpu
      resourceClaimTemplateName: my-claim
  containers:
    - name: app
      image: nvidia/cuda:12.8.0-devel-ubuntu24.04
      resources:
        claims:
          - name: gpu                   # Передача claim, описанного в  ResourceClaimTemplate контейнеру.

При описании пода в ресурсе Job spec.resourceClaims и spec.containers.resources.claims обязательны. Если объявить spec.resourceClaims, но не указать spec.containers.resources.claims в спецификации контейнера, GPU будет выделен, но контейнер его не получит: при этом не будет сообщения об ошибке, предупреждения или события. Под запустится, но CUDA не найдёт запрошенный ресурс.

NVIDIA_VISIBLE_DEVICES в модуле gpu не действует. Значение all ничего не даёт, потому что DRA-нагрузки работают в стандартном container runtime и получают устройства только через claim. Под без spec.containers.resources.claims не видит ни одного /dev/nvidia* — это сделано намеренно.

Запрос целого GPU эксклюзивно

В примере ниже условие !has(...sharingStrategy) делает выделение эксклюзивным — оно исключает разделяемые варианты той же карты (запрос на выделение карты целиком):

apiVersion: resource.k8s.io/v1
kind: DeviceClass
metadata:
  name: nvidia-whole-gpu
spec:
  selectors:
    - cel:
        expression: |
          device.attributes["gpu.deckhouse.io"].vendor == "nvidia" &&
          device.attributes["gpu.deckhouse.io"].computeAPI == "CUDA" &&
          has(device.attributes["gpu.deckhouse.io"].deviceType) &&
          device.attributes["gpu.deckhouse.io"].deviceType == "physical" &&
          !has(device.attributes["gpu.deckhouse.io"].sharingStrategy)
---
apiVersion: resource.k8s.io/v1
kind: ResourceClaimTemplate
metadata:
  name: whole-gpu
spec:
  spec:
    devices:
      requests:
        - name: gpu
          exactly:
            deviceClassName: nvidia-whole-gpu
            count: 1

Запрос MIG-партиции

Чтобы запросить MIG-партицию, добавьте в селектор атрибуты партиции. Имена профилей здесь аппаратные, с точками — 1g.5gb, а не 1g5gb:

apiVersion: resource.k8s.io/v1
kind: DeviceClass
metadata:
  name: nvidia-mig-1g5gb
spec:
  selectors:
    - cel:
        expression: |
          device.attributes["gpu.deckhouse.io"].vendor == "nvidia" &&
          device.attributes["gpu.deckhouse.io"].computeAPI == "CUDA" &&
          has(device.attributes["gpu.deckhouse.io"].deviceType) &&
          device.attributes["gpu.deckhouse.io"].deviceType == "partition" &&
          has(device.attributes["gpu.deckhouse.io"].partitionTechnology) &&
          device.attributes["gpu.deckhouse.io"].partitionTechnology == "MIG" &&
          has(device.attributes["gpu.deckhouse.io"].partitionProfile) &&
          device.attributes["gpu.deckhouse.io"].partitionProfile == "1g.5gb"

Запрос имеет тот же вид, что и для целой карты.

Команду nvidia-smi -mig пользователю выполнять не нужно. Состоянием MIG управляет модуль:

  • Если запрошенная партиция уже существует и свободна, она выделяется как есть.
  • Если её нет, модуль включает MIG на карте и создаёт партицию на этапе подготовки.
  • Если на карте сейчас другая, конфликтующая раскладка, модуль её перестраивает.

Последние два случая разрушающие. Включение MIG и перестройка раскладки вытесняют GPU-нагрузки с узла и могут привести к его перезагрузке. Не запускайте их без необходимости на общем кластере.

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

Возврат к целой карте

Запрос устройства типа physical на карте, где включён MIG, заставляет модуль отключить режим партиционирования — снова с вытеснением нагрузки. Наблюдать переход можно с помощью команды:

d8 k get physicalgpu <имя> -o jsonpath='{.status.currentState.partition.mode}'

Запрос доли GPU через MPS

Для запроса доли GPU через MPS добавьте capacity.requests и opaque-конфиг, выбирающий MPS. Для целой карты — GpuConfig:

apiVersion: resource.k8s.io/v1
kind: ResourceClaimTemplate
metadata:
  name: gpu-mps-quarter
spec:
  spec:
    devices:
      requests:
        - name: gpu
          exactly:
            deviceClassName: nvidia-gpu-mps
            count: 1
            capacity:
              requests:
                sharePercent: "25"
                gpu.deckhouse.io/memory: 1Gi
      config:
        - requests: ["gpu"]
          opaque:
            driver: gpu.deckhouse.io
            parameters:
              apiVersion: resource.gpu.deckhouse.io/v1alpha1
              kind: GpuConfig
              sharing:
                strategy: MPS
                mpsConfig:
                  defaultActiveThreadPercentage: 25

Соответствующий DeviceClass должен выбирать разделяемые устройства, поэтому стратегию нужно требовать, а не исключать:

… && has(device.attributes["gpu.deckhouse.io"].sharingStrategy) &&
device.attributes["gpu.deckhouse.io"].sharingStrategy == "mps"

Чтобы разделить MIG-партицию, а не целую карту, используйте kind: MigDeviceConfig и селектор партиции. Для партиции достаточно одного sharePercent — запрашивать память дополнительно не нужно.

На партиции разрешён только MPS. Запрос time-slicing там отклоняется вебхуком с сообщением MIG device sharing supports only MPS strategy.

Нагрузки, разделяющие один GPU, используют общий control-демон MPS: он запускается по требованию и переиспользуется. Удаление одной из них не мешает остальным.

Запрос доли GPU без указания стратегии

Если вам не важно, получите вы MPS или time-slicing, запросите ёмкость и не указывайте стратегию:

      requests:
        - name: gpu
          exactly:
            deviceClassName: nvidia-gpu-any
            count: 1
            capacity:
              requests:
                sharePercent: "25"
                gpu.deckhouse.io/memory: 1Gi

Это работает потому, что целая карта вообще не публикует ёмкость sharePercent — её публикуют только разделяемые варианты. Поэтому запрос sharePercent вынуждает планировщик выбрать разделяемый вариант. Если MPS на выбранном устройстве недоступен, запрос попадёт на time-slicing.

Чтобы это работало, селектор DeviceClass должен быть разрешающим: не добавляйте !has(...sharingStrategy), иначе вы исключите те самые устройства, которые могут удовлетворить запрос, и ничего не свяжется.

VFIO-проброс

Проброс отдаёт в контейнер «сырое» PCI-устройство вместо CUDA-устройства. Внутри контейнера нет ни CUDA, ни nvidia-smi — вы получаете /dev/vfio/<iommu_group> и /dev/vfio/vfio для потребителя вроде виртуальной машины, QEMU/KVM или DPDK.

apiVersion: resource.k8s.io/v1
kind: DeviceClass
metadata:
  name: h100-vfio
spec:
  selectors:
    - cel:
        expression: |
          device.attributes["gpu.deckhouse.io"].vendor == "nvidia" &&
          has(device.attributes["gpu.deckhouse.io"].productName) &&
          device.attributes["gpu.deckhouse.io"].productName.lowerAscii().matches("^.*h100.*$") &&
          has(device.attributes["gpu.deckhouse.io"].deviceType) &&
          device.attributes["gpu.deckhouse.io"].deviceType == "physical" &&
          has(device.attributes["gpu.deckhouse.io"].bareMetal) &&
          device.attributes["gpu.deckhouse.io"].bareMetal == true
---
apiVersion: resource.k8s.io/v1
kind: ResourceClaimTemplate
metadata:
  name: h100-vfio
spec:
  spec:
    devices:
      requests:
        - name: gpu
          exactly:
            deviceClassName: h100-vfio
            allocationMode: ExactCount
            count: 1
      config:
        - requests: ["gpu"]
          opaque:
            driver: gpu.deckhouse.io
            parameters:
              apiVersion: resource.gpu.deckhouse.io/v1alpha1
              kind: VfioDeviceConfig

Обратите внимание на отсутствие capacity и sharePercent: для проброса нужна карта целиком. Запрос полного sharePercent: "100" тоже считается эксклюзивным, но любое меньшее значение отклоняется.

Под должен попасть на узел, подготовленный для VFIO. Готовность не публикуется как атрибут устройства, поэтому её нельзя указать в CEL-селекторе — используйте лейбл узла:

spec:
  nodeSelector:
    node.deckhouse.io/gpu-vfio-ready: "true"
  resourceClaims:
    - name: gpu
      resourceClaimTemplateName: h100-vfio
  containers:
    - name: vfio-consumer
      image: busybox:1.36
      command: ["/bin/sh", "-c", "ls -l /dev/vfio && sleep infinity"]
      resources:
        claims:
          - name: gpu

Карта в режиме проброса имеет .status.phase: Passthrough и исчезает из пула CUDA-устройств.

Требования, проверяемые на этапе подготовки; если что-то не выполнено, подготовка завершается ошибкой с явным сообщением:

Сообщение Причина
vfio requires bare-metal node Узел виртуализирован (bareMetal != true).
vfio requires exclusive allocation В запросе присутствует sharing или sharePercent.
vfio requested for non-physical device Селектор выбрал MIG-партицию.
unexpected driver "..." Карта привязана к драйверу, отличному от nvidia и vfio-pci.
mixed VFIO and non-VFIO allocations… В одном claim смешаны проброс и обычные устройства.

Кроме того, для подготовки требуется отключённый SR-IOV на устройстве и отсутствие запущенных процессов на карте. Если MIG включён, но карта простаивает, модуль снимает MIG-конфигурацию и привязывает карту к vfio-pci самостоятельно.

Для проброса требуется IOMMU, включённый в BIOS и в командной строке ядра. Модуль добавляет параметр ядра сам, но его применение приводит к перезагрузке узла.

Возможности модуля в зависимости от вендора GPU

В таблице описаны возможности модуля в зависимости от вендора GPU:

Возможность NVIDIA MetaX AMD
Целая карта Да Да В разработке
Аппаратное партиционирование Да (MIG) Нет В разработке (SPX/DPX/CPX, NPS)
Программное партиционирование Да (MPS, TS) В разработке (sGPU) В разработке (TS)
Проброс физической функции (VFIO) Да Да В разработке

Аппаратное партиционирование даёт изоляцию и предсказуемую производительность. Программное — гибкую нарезку без изоляции, поэтому нагрузки на одной карте влияют друг на друга.

Особенности разных вендоров:

  • sGPU делит карту MetaX по квоте — доля вычислительных ресурсов и объём памяти, — а не по фиксированным местам.
  • Карты AMD обнаруживаются и появляются как объекты PhysicalGPU, но выделить их пока нельзя.
  • Проброс отдаёт физическую функцию целиком — это не vGPU и не SR-IOV. У NVIDIA он требует bare-metal-узла, а включённый SR-IOV его блокирует.

С возможностями и поддержкой карт можно ознакомиться в Глоссарии.

Как решает модуль

Две строки определяются по-разному, и разница заметна на практике.

Аппаратное партиционирование определяется по каждой карте и попадает в .status.capabilities.partitionSupport. Программное разделение объявляется на уровне узла: все карты узла получают одинаковые лейблы gpu.deckhouse.io/sharing.mps и sharing.ts независимо от модели.

Поэтому на карте, которая MPS не поддерживает, лейбл всё равно появится, а claim будет принят. Несоответствие вскроется при подготовке: модуль переключится на time-slicing и опубликует событие MPSFallbackToTimeSlicing. Нагрузка получит разделение, но не то, которое запрошено.

Для просмотра всех физических GPU в кластере и их лейблов используйте команду:

d8 k get physicalgpus --show-labels

Для получения детальной информации о поддержке разделения (MIG и т. п.) для конкретного GPU используйте команду:

d8 k get physicalgpu <имя> -o jsonpath='{.status.capabilities.partitionSupport}' | jq

Выбор подходящего типа партиционирования

Аппаратное партиционирование подходит для случаев, когда нужна изоляция и предсказуемая производительность, а карта есть в списке вендора.

Программное партиционирование подходит для случаев, когда аппаратное на модели недоступно либо его фиксированные профили слишком грубы для нагрузки.

Получение информации о выделенном GPU

Чтобы посмотреть, какое устройство было выделено, используйте команду:

d8 k -n <неймспейс> get resourceclaim -o json \
  | jq '.items[].status.allocation.devices.results[] | {device, pool}'

Информация о выделенном устройстве содержится в его имени:

Имя Значение
gpu-00000000-d5-00-0 Целая карта по PCI-адресу 0000:d5:00.0.
gpu-00000000-d5-00-0-mps Разделяемая доля этой карты через MPS.
gpu-00000000-d5-00-0-ts Разделяемая доля этой карты через time-slicing.
part-gpu-00000000-d5-00-0-p15-s0-n2 MIG-партиция — профиль с ID 15, начало со слайса 0, ширина два слайса.
part-…-n2-mps Разделяемая доля этой MIG-партиции.

Чтобы узнать список объектов, для которых зарезервирован ResourceClaim, используйте команду:

d8 k -n <неймспейс> get resourceclaim <имя> -o jsonpath='{.status.reservedFor}'

Внутри контейнера — обычная проверка:

nvidia-smi -L

Если вывод пуст, см. «Диагностика» — чаще всего причина в отсутствующей записи resources.claims в спецификации пода.

Справочник атрибутов устройства

Селекторы DeviceClass работают с атрибутами в домене gpu.deckhouse.io, которые в CEL адресуются как device.attributes["gpu.deckhouse.io"].<имя>. Необязательные атрибуты всегда оборачивайте в has(...): отсутствующий атрибут приводит к ошибке всего выражения, а не к значению false.

Атрибут Тип Пример Примечания
vendor string nvidia, metax, amd, intel Какой адаптер вендора обслуживает устройство.
vendorID string 10de PCI vendor ID.
deviceID string 20b0 PCI device ID.
deviceModel string a100-sxm4-40gb Нормализованная модель; то же значение, что в лейбле gpu.deckhouse.io/device.
productName string NVIDIA A100-SXM4-40GB Маркетинговое имя по данным драйвера.
architecture string Ampere, Hopper, CDNA3, Xe-HPC Архитектура оборудования в терминах вендора.
computeAPI string CUDA, ROCm, oneAPI Вычислительный стек вендора.
computeAPIVersion string 13.2
computeCapability string 8.0 Compute capability CUDA у NVIDIA. У вендоров без аналога — пусто.
driverVersion string 595.71.05
gpuUUID string GPU-5bb94fb4-…
pciAddress string 00000000:d5:00.0 Позволяет закрепить claim за одной конкретной картой.
bareMetal bool true Обязательно для проброса.
deviceType string physical, partition
placementID string gpu/00000000:d5:00.0
sharingStrategy string mps, ts Отсутствует у эксклюзивных устройств — именно так выражается «эксклюзивно».
partition bool true Присутствует у устройств-партиций.
partitionTechnology string MIG, SPX, SR-IOV Технология партиционирования вендора.
partitionProfile string 1g.5gb, 2g.10gb, 1g.5gb+me Аппаратное имя, с точками.
partitionProfileID int 20
partitionSliceStart int 6
partitionSliceSize int 1

Посмотреть, что публикует именно ваш кластер:

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

d8 k get resourceslices -o json \
  | jq '.items[].spec.devices[]? | {name, attributes, capacity}'

Оси ёмкости

ResourceClaim может запрашивать измеримые величины по двум осям:

Ось Значения Доступность
gpu.deckhouse.io/memory величина вида 1Gi На всех устройствах.
sharePercent 1–100 Только у разделяемых вариантов.