Стадия жизненного цикла модуляExperimental

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

Эта страница проводит один заказ от начала до конца и говорит, почему каждая часть устроена именно так. Всё описанное здесь держится начиная с версии v0.0.1 модуля.

Учиться сегодняшнему поведению вкратце здесь не стоит. Для этого:

Контроллер ai-inference разворачивает рабочую нагрузку заказа сам: нагрузку с контейнером среды исполнения запроса, службу, публикацию, заявку на устройство и остальные объекты заказа. Сторонний пакет доставки не участвует.

При включении модуля Helm создаёт кластерный класс default-llm (External, Token) с label module: ai-inference, границами числа копий и политикой ускорителя, разрешающей целое устройство в режиме разделения. Его можно использовать сразу: ускоритель заказ не запрашивает — устройство, их число и долю назначает платформа.

1. InferenceServiceClass

Поставляемый класс (модуль)

После установки модуля в кластере уже есть:

apiVersion: ai.deckhouse.io/v1alpha1
kind: InferenceServiceClass
metadata:
  name: default-llm
spec:
  modelPolicy:
    allowedEndpointTypes:
      - Chat
      - Embeddings
      - Rerank
  exposurePolicy:
    type: External
    authentication: Token
    https:
      mode: CertManager
      certManager:
        clusterIssuerName: selfsigned

Ожидание: status.phase: Ready.

Именно allowedEndpointTypes позволяет заказу называть только класс и модель: заказ договор не называет, и платформа берёт его из этого перечня. Поставляемый класс разрешает все договоры, которые модуль поддерживает — Chat, Embeddings и Rerank, — и порядок перечня важен: платформа берёт первый элемент перечня класса, суженного тем, что об умениях модели говорит её источник.

На пути каталога ai-models сужение происходит: модель векторизации обслуживается как модель векторизации на этом же классе. На прямом пути Hugging Face таких сведений нет, поэтому перечень остаётся целиком и побеждает первый договор — Chat. Заказ модели векторизации на этом пути будет обслужен как чат, и никто его не отклонит. Если это ваш случай, объявите собственный InferenceServiceClass, чей перечень называет один нужный договор.

Дополнительный класс администратора (опционально)

Поле spec.acceleratorPolicy.allowedDeviceClasses опционально — задаёт список допустимых DeviceClass для заказов на этот класс. Заказ может сузить этот перечень своим полем spec.resources.accelerator.deviceClasses (раздел 2 ниже); пустое пересечение — отказ.

apiVersion: ai.deckhouse.io/v1alpha1
kind: InferenceServiceClass
metadata:
  name: llm-chat-shared
spec:
  acceleratorPolicy:
    allowedDeviceClasses:
      - nvidia-h100-mps-mig
    allowedSharingModes:
      - Shared
    allowedPlacementTypes:
      - WholeDevice
    maxAcceleratorCount: 1
    minSharePercent: 25
    maxSharePercent: 100
  exposurePolicy:
    type: External
    authentication: Token

Ожидание: status.phase: Ready, conditions Validated=True, Ready=True.

2. InferenceService (пользователь namespace)

apiVersion: ai.deckhouse.io/v1alpha1
kind: InferenceService
metadata:
  name: support-llm
  namespace: support
spec:
  inferenceServiceClassName: default-llm
  model:
    ref:
      name: Qwen/Qwen2.5-32B-Instruct-AWQ
    src: HuggingFace

Заказ — это ссылка на класс, блок модели и опционально сужение перечня классов устройства. Иных полей у его спецификации нет.

Сама ссылка на класс тоже опциональна: заказ, вовсе не задающий spec.inferenceServiceClassName, пропускает проверки ниже, зависящие от класса — сужение перечня допуска нет, status.model.endpointType не появляется, — и получает экспозицию и допуск неймспейсов, зашитые в платформу (те же значения, что несёт поставляемый класс default-llm на день выхода этого среза, не живая ссылка на этот объект). Любая другая политика уже ведёт себя без класса вовсе так же, как с классом, не задающим соответствующий блок.

Заказ может сузить перечень допустимых DeviceClass класса своим полем spec.resources.accelerator.deviceClasses — список из нескольких элементов класса из примера выше позволяет заказу выбрать один:

apiVersion: ai.deckhouse.io/v1alpha1
kind: InferenceService
metadata:
  name: support-llm
  namespace: support
spec:
  inferenceServiceClassName: default-llm
  model:
    ref:
      name: Qwen/Qwen2.5-32B-Instruct-AWQ
    src: HuggingFace
  resources:
    accelerator:
      deviceClasses:
        - nvidia-h100-mps-mig

Пустое пересечение с acceleratorPolicy.allowedDeviceClasses класса — отказ с reason ClaimDeviceMissing; частичное пересечение допускается, в план запуска уходит пересечение, а не исходный перечень заказа.

Тип договора API инференса выбирает платформа из перечня класса, пересечённого со сведениями каталога модели: единственный разрешённый договор либо первый из нескольких. Выбранное значение видно в status.model.endpointType — это единственное окно, где владелец заказа видит выбор, потому что класс ему для чтения недоступен. Заказу, которому нужен иной договор, нужен иной класс.

Имя среды исполнения и все параметры запуска задаёт запись рецепта плана запуска, скомпилированная под выбранное оборудование. Мультимодальные ключи каталога (limitMmPerPrompt и аналоги) — там же, в рецепте. Иное значение — это иной рецепт или иной класс, то есть право администратора.

Классу перечень разрешённых договоров API обязателен и непуст. Класс без него API-сервер отклоняет на записи: договор выбирается из перечня, заказ его не называет, и класс без перечня оставил бы свои заказы без договора вовсе.

Заказ, всё ещё несущий блок среды исполнения, при строгой проверке полей отклоняется, а клиент без неё принимает его с молчаливым усечением.

API-сервер до сохранения объекта проверяет союз источника модели. Он отклоняет: поле model.ref.namespace у ссылок HuggingFace и ClusterModel; классы ClusterLocal с https; и ветки сертификата, не соответствующие https.mode.

3. Что делает контроллер

  1. Проверка: класс существует, Ready и объявляет перечень договоров API; model.src=HuggingFace.
  2. Сборка объектов рабочей нагрузки заказа и их применение в namespace support; каждый объект называется по заказу.
  3. Вычисление settings.domain (например support-llm.services.company.com) и публикация status.endpoint.
  4. При authentication: Token — Secret {name}-auth и status.authSecretName.
  5. HTTP-проба Inference API; phase: Ready только после успеха.
  6. После Ready — периодическая проверка работоспособности; при устойчивой ошибке API — phase: Failed, reason ServiceUnhealthy (bootstrap до первого Ready остаётся Pending, reason HealthCheckFailed).

4. Проверка результата

Чтение состояния готового заказа описано один раз, в Руководстве пользователя; разделы ниже говорят только о том, что добавляет к этому состоянию каждый сценарий.

5. Hugging Face token (опционально)

Для gated-моделей задайте Secret в namespace заказа и ссылку в spec.model.authSecretRef:

spec:
  model:
    ref:
      name: meta-llama/Llama-3.1-8B
    src: HuggingFace
    authSecretRef:
      name: hf-token
      key: token   # опционально; ключ по умолчанию — "token"

Контроллер проверяет Secret вместе с заказом и монтирует его в загрузчик артефакта модели рабочей нагрузки как HF_TOKEN.

RBAC

Уровень / роль Ресурсы Verbs
User (d8:user-authz:ai-inference:user, rbacv2 use/view) inferenceservices get, list, watch
User / use/view inferenceserviceclasses/placement-preview create
User / use/view inferenceserviceclasses/cluster-view get
Editor (:editor, rbacv2 use/edit) inferenceservices create, update, patch, delete, deletecollection
ClusterEditor (:cluster-editor) inferenceserviceclasses get, list, watch, create, update, patch, delete, deletecollection
ClusterEditor / manage UI-subresources выше create / get
manage/view moduleconfigs/ai-inference, ISC get, list, watch (+ UI-subresources)
manage/edit moduleconfigs/ai-inference, ISC мутации (+ UI-subresources)

Намеренно закрыто людям: inferenceserviceclasses/planner, */status, Secrets, bind / escalate / impersonate. Дельты PrivilegedUser / Admin / ClusterAdmin пусты (новых действий нет).

Шаблоны: templates/user-authz-cluster-roles.yaml, templates/rbacv2/**.

Политики класса

Пример класса embeddings-dedicated с admission/scaling/update и без аутентификации:

apiVersion: ai.deckhouse.io/v1alpha1
kind: InferenceServiceClass
metadata:
  name: embeddings-dedicated
spec:
  admissionPolicy:
    allowedNamespaces: ["*"]
  exposurePolicy:
    type: ClusterLocal
    authentication: None
  scalingPolicy:
    allowedPriorityClassNames:
      - demo-inference-high
    minReplicas: 1
    maxReplicas: 4
  updatePolicy:
    strategy: RollingUpdate

Заказ под этим классом не называет границ числа копий, поэтому к нему применяются границы класса выше:

apiVersion: ai.deckhouse.io/v1alpha1
kind: InferenceService
metadata:
  name: search-embeddings
  namespace: ml
spec:
  inferenceServiceClassName: embeddings-dedicated
  model:
    ref:
      name: intfloat/multilingual-e5-large
    src: HuggingFace

Ожидание при Ready:

  • status.constraints.minReplicas равен 2, status.constraints.maxReplicas равен 4
  • нагрузка заказа обновляется по очереди (updateStrategy.type: RollingUpdate), а число копий ведёт HPA
  • при значении maxReplicas больше единицы condition ScalingHealthy отражает состояние HPA: истина — норма, ложь — ограничение роста

status.endpoint.type отражает exposurePolicy.type (ClusterLocal / External). Какие договоры inference API допускает класс, решает allowedEndpointTypes на нём — обязательный и непустой; сам заказ договор не называет.

Причина отказа этой проверки: NamespaceNotAllowed.

Интеграция с ai-models

Требуется модуль ai-models с его внутренним запросом сведений, когда интеграция с каталогом включена. Публичный ModuleConfig задаёт catalog.mode (Enabled по умолчанию или None — отключить клиенты каталога). Внутренний транспорт по умолчанию — aiInference.aiModels.catalogTransport: Rest: контроллер и планировщик вызывают GET /api/internal/v1/models/lookup по model.ref. При недоступности Rest в том же согласовании сразу читают CR Model / ClusterModel. Явный откат — внутренний catalogTransport: Kubernetes (только чтение CR). API распространения каталога и API импорта каталога для запроса сведений не используются.

ModuleConfig (публичный режим каталога)

apiVersion: deckhouse.io/v1alpha1
kind: ModuleConfig
metadata:
  name: ai-inference
spec:
  settings:
    catalog:
      mode: Enabled                 # None — отключить клиенты каталога и RBAC

Внутренний транспорт каталога (не ModuleConfig)

В openapi/values.yaml (внутренние значения модуля, не публичный ModuleConfig):

aiInference:
  aiModels:
    catalogTransport: Rest         # по умолчанию; Kubernetes — явный откат
    catalogLookup:                 # используется при catalogTransport Rest
      baseURL: https://ai-models-controller.d8-ai-models.svc.cluster.local:8080
      path: /api/internal/v1/models/lookup
      timeoutSeconds: 10
      notReadyRequeueSeconds: 30

InferenceServiceClass с modelPolicy

apiVersion: ai.deckhouse.io/v1alpha1
kind: InferenceServiceClass
metadata:
  name: llm-chat-shared
spec:
  acceleratorPolicy:
    allowedDeviceClasses:
      - nvidia-h100-mps-mig
    allowedSharingModes:
      - Shared
    allowedPlacementTypes:
      - Partition
    maxAcceleratorCount: 1
  admissionPolicy:
    allowedNamespaces: ["*"]
  modelPolicy:
    allowedEndpointTypes:
      - Chat
    maxParameterCount: 70B
  exposurePolicy:
    type: External
    authentication: Token
  scalingPolicy:
    allowedPriorityClassNames:
      - demo-inference-normal
    minReplicas: 1
    maxReplicas: 1
  updatePolicy:
    strategy: RollingUpdate

InferenceService с model.ref (каталог)

apiVersion: ai.deckhouse.io/v1alpha1
kind: InferenceService
metadata:
  name: support-llm
  namespace: support
spec:
  inferenceServiceClassName: llm-chat-shared
  model:
    ref:
      kind: ClusterModel
      name: qwen2-5-32b-instruct-awq
    src: ai-models

Для namespace-Model укажите kind: Model, name и опционально namespace в объекте ref; в собранной нагрузке namespace модели не передаётся.

Поведение контроллера

  1. Проверка класса и его политик.
  2. Запрос сведений по model.ref (по умолчанию — по HTTP; при недоступности Rest — сразу чтение CR; при Kubernetes — только чтение status CR).
  3. Локальная матрица каталога (modelPolicy против фактов): при успехе ModelResolved=True, иначе ModelResolved=False и reason (ModelNotFound, ModelNotReady, …).
  4. До ModelResolved=True нагрузка заказа не собирается.
  5. При успехе платформа собирает объекты заказа сама; выбранный ею договор API публикуется в status.model.endpointType, а область модели — в status.constraints.modelScope.
  6. Проверка работоспособности API и phase: Ready.

Проверка status

Ожидание при Ready:

  • status.model.endpointType: Chat — договор API, выбранный платформой из перечня класса
  • status.constraints.modelScope: Cluster — из фактов каталога
  • status.endpoint.type: External — зеркало exposurePolicy.type, не тип API
  • condition ModelResolved: True
kubectl get inferenceservice support-llm -n support -o jsonpath='{.status.model.endpointType}{"\n"}{.status.endpoint.type}{"\n"}'
kubectl get inferenceservice support-llm -n support -o jsonpath='{.status.constraints.modelScope}{"\n"}'

Планировщик ресурсов

Продолжение раздела о каталоге выше (model.ref, ModelResolved). Добавляются план запуска, condition Planned и status.resolved. Требуются модуль gpu для инвентаря GPU и Deployment планировщика ресурсов, поставляемый модулем ai-inference.

Совместимость: встроенный default-llm и классы без acceleratorPolicy: контроллер не вызывает планировщик и не публикует Planned / status.resolved.

Предусловия

  • Готовый ClusterModel / Model, из раздела о каталоге выше.
  • Модуль gpu с инвентарём PhysicalGPU, доступным планировщику.
  • Служба планировщика по умолчанию модуля (aiInference.planner.baseURL в значениях Helm-пакета; см. openapi/values.yaml).

InferenceServiceClass с acceleratorPolicy

К классу llm-chat-shared из раздела о каталоге выше добавьте acceleratorPolicy (манифест администратора):

apiVersion: ai.deckhouse.io/v1alpha1
kind: InferenceServiceClass
metadata:
  name: llm-chat-shared
spec:
  acceleratorPolicy:
    allowedSharingModes: [Shared]
    allowedPlacementTypes: [Partition, WholeDevice]
    maxAcceleratorCount: 1
    allowedDeviceClasses:
      - nvidia-h100-mps-mig
  admissionPolicy:
    allowedNamespaces: ["*"]
  modelPolicy:
    allowedEndpointTypes:
      - Chat
    maxParameterCount: 70B
  exposurePolicy:
    type: External
    authentication: Token
  scalingPolicy:
    allowedPriorityClassNames:
      - demo-inference-normal
    minReplicas: 1
    maxReplicas: 4
  updatePolicy:
    strategy: RollingUpdate

Ожидание: status.phase: Ready.

InferenceService с accelerator и launchStrategy

apiVersion: ai.deckhouse.io/v1alpha1
kind: InferenceService
metadata:
  name: support-llm
  namespace: support
spec:
  inferenceServiceClassName: llm-chat-shared
  model:
    ref:
      kind: ClusterModel
      name: qwen2-5-32b-instruct-awq
    src: ai-models
  launchStrategy: Throughput

launchStrategy выбирает ветку скомпилированного рецепта (Latency, Throughput или Balance; значение по умолчанию при отсутствии — Latency). Ветки на подавляющем большинстве железа действительно расходятся — более широкий горизонт контекста или упор на пропускную способность достижимы этим полем, не сменой рецепта или класса целиком. Число устройств заказом не задаётся — его назначает план запуска в пределах acceleratorPolicy.maxAcceleratorCount класса.

Долю устройства НАЗНАЧАЕТ план запуска, заказ её не называет. План берёт наименьшую целую долю, которой хватает потребности видеопамяти модели на выбранном устройстве, зажимает её пределами minSharePercent и maxSharePercent класса и отдаёт целое устройство, если класс разделения не разрешает, если выбранное размещение не есть целое устройство в режиме разделения либо если одна из двух величин памяти неизвестна. Назначенное значение публикуется в status.resolved.sharePercent — по нему владелец заказа видит, какая часть карты его. Для доли меньше 100 выбранное устройство DRA обязано публиковать allowMultipleAllocations и политику запроса sharePercent. Для доли 1..99 контроллер дополнительно передаёт capacity.requests.memory по потребности модели из плана запуска, которая записывается в заявку как gpu.deckhouse.io/memory. Память модели и доля вычислительных ресурсов MPS учитываются независимо.

Поведение контроллера

  1. Проверка и матрица каталога → ModelResolved=True.
  2. Политика ускорителя класса ограничивает исход планирования; у заказа своего запроса, который можно было бы с ней сверить, нет.
  3. POST /api/v1/launch-plan — планировщик возвращает размещение и рецепт среды исполнения; при успехе:
    • status.resolved — размещение, по которому заказ запущен: deviceClass, acceleratorProductName, acceleratorMemoryGiB, placementMode, sharingMode, sharePercent; остальное, что вычислил планировщик, уезжает в план запуска заказа и в состоянии не публикуется;
    • condition Planned=True, reason LaunchPlanCalculated.
  4. При отказе планировщика — Planned=False со стабильным reason (NoCapacity, QuantizationMismatch, …); объекты нагрузки не создаются и не обновляются, пока планирование не успешно.
  5. После Planned=True рецепт из плана переносится в аргументы контейнера runtime; затем проверка работоспособности → phase: Ready, как в разделах выше.
  6. При maxReplicas > 1 — наблюдение HPA и фоновый цикл вытеснения; встроенный класс default-llm вне области.

Проверка status

На этапе согласования ожидайте Planned до появления нагрузки заказа. При Ready:

status:
  phase: Ready
  resolved:
    deviceClass: nvidia-hopper-s3-shared
    acceleratorProductName: NVIDIA H100 80GB HBM3
    acceleratorMemoryGiB: 24
    placementMode: Partition
    sharingMode: Shared
    replanCount: 0
  conditions:
    - type: ModelResolved
      status: "True"
    - type: Planned
      status: "True"
      reason: LaunchPlanCalculated
    - type: Ready
      status: "True"
kubectl get inferenceservice support-llm -n support -o jsonpath='{.status.resolved}{"\n"}{.status.conditions[?(@.type=="Planned")]}{"\n"}'
kubectl get statefulset support-llm -n support -o jsonpath='{.spec.template.spec.containers[0].command}{"\n"}'

Примеры отказа планировщика: Planned=False, reason: NoCapacity или QuantizationMismatch.

Резервный контур планировщика

Продолжение раздела о планировщике ресурсов выше. Если для пары (family, model.name) нет записи в runtime-recipes.compiled.yaml, а резервный контур включён, планировщик синтезирует обобщённый vLLM-рецепт из справочников images/catalogs/ (recipe-taxonomy.yaml и platform-hardware-families.yaml), оценивает vramMinimumGiB и возвращает recipeSource: fallback. Скомпилированная запись и предустановка всегда приоритетнее резервного контура.

Совместимость: при aiInference.planner.runtimeFallback.enabled: false (внутренние значения модуля / openapi/values.yaml, не публичный ModuleConfig) поведение идентично разделу о планировщике ресурсов выше — без синтетического рецепта. Классы без acceleratorPolicy не затрагиваются.

Настройка модуля

В openapi/values.yaml (не публичный ModuleConfig):

aiInference:
  planner:
    runtimeFallback:
      enabled: true   # по умолчанию true

Контроллер передаёт флаг планировщику как runtimeFallback.enabled. Отключение:

aiInference:
  planner:
    runtimeFallback:
      enabled: false

Минимум метаданных для резервного контура

Поле Источник
parameterCount на прямом пути HuggingFace spec.model.parameterCount → разбор model.name (32b, 360m, …)
parameterCount на пути ai-models доступный факт каталога → разбор model.name (32b, 360m, …). На этом пути заказ размер не называет вовсе: spec.model.parameterCount отклоняется на записи
quantization каталог → парсинг суффикса в model.name (awq, nvfp4, …) → default
format, supportedEndpointTypes каталог (проброс контроллером; для оценки VRAM v1 не обязательны)

Контроллер не парсит квантизацию из имени — только планировщик в ветке резервного контура. Для modelPolicy.maxParameterCount контроллер судит то, что разрешил путь заказа: факт каталога на пути ai-models, значение заказа на прямом пути и то же правило разбора model.name, если первая ступень пуста.

Сведения Model и ClusterModel сегодня не возвращают геометрию тензоров. Планировщик получает её из поставляемой предустановки рецепта, если она есть; иначе резервная оценка не использует геометрию. Выставление геометрии из ai-models сегодня недоступно, и модуль не предполагает будущую форму полей каталога.

Пример: модель без скомпилированного рецепта

ClusterModel с именем qwen2-5-32b-instruct-awq, в сведениях которого нет parameterCount и quantization. Заказ также не задаёт spec.model.parameterCount, поэтому размер и квантизация извлекаются из model.name:

apiVersion: ai.deckhouse.io/v1alpha1
kind: InferenceService
metadata:
  name: support-llm-fallback
  namespace: support
spec:
  inferenceServiceClassName: llm-chat-shared
  model:
    ref:
      kind: ClusterModel
      name: qwen2-5-32b-instruct-awq
    src: ai-models

Ожидание после того, как инвентарь GPU заполнен:

status:
  resolved:
    deviceClass: nvidia-hopper-s3-shared
    acceleratorProductName: NVIDIA H100 80GB HBM3
    acceleratorMemoryGiB: 24
    placementMode: Partition
    sharingMode: Shared
  conditions:
    - type: Planned
      status: "True"
      reason: LaunchPlanCalculated

При отсутствии распознаваемого размера (нет каталога, нет spec.model.parameterCount, имя без токена 32b/360m) — Planned=False, reason NoCompatibleRuntimeAvailable.

Проверка status

kubectl get inferenceservice support-llm-fallback -n support \
  -o jsonpath='{.status.resolved.deviceClass}{"\n"}{.status.resolved.acceleratorProductName}{"\n"}'
kubectl get statefulset support-llm-fallback -n support \
  -o jsonpath='{.spec.template.spec.containers[0].command}{"\n"}'