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

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

Продукт: ai-inference (модуль Deckhouse Kubernetes Platform) Стадия: Experimental Дата: 2026-08-05

Совместимость с платформой

Платформа Нижняя поддерживаемая версия
Deckhouse Kubernetes Platform 1.75
Kubernetes 1.34

Основной DRA-инвентарь resource.k8s.io/v1 поддерживается на этой границе. Поля совместной расходуемой ёмкости остаются необязательными: если драйвер GPU их не публикует, планировщик не придумывает дробную ёмкость. Это может снизить плотность размещения, но не допускает переподписку GPU.

Страница описывает текущий экспериментальный контур для администраторов кластера и пользователей namespace. Это канон пользовательских заметок о выпуске в документации. Канал Console получает краткую проекцию того же смысла в ключах features / fixes.

Ключевые изменения

  • Заказ рабочей нагрузки инференса через кластерный InferenceServiceClass и namespaced InferenceService; контроллер сам разворачивает объекты заказа и публикует status.endpoint.
  • Заказ — это ссылка на класс и блок модели. Договор API (Chat, Embeddings, Rerank и связанные) выбирает платформа из перечня класса; настройку среды исполнения задаёт запись рецепта плана запуска.
  • Планирование размещения и вытеснения через API планировщика; автомасштабирование и восстановление доноров остаются зоной модуля.

Новые возможности

  • Встроенный класс и пошаговый сценарий до первого Ready-заказа.
  • Маршруты планировщика: placement-preview, launch-plan, preemption и free-replicas для операторов и клиентов UI.
  • Опциональные поверхности класса устройства и переопределения в заказе для формы заявки на GPU.
  • Заказ может сузить перечень допустимых классов устройства ускорителя своего класса полем spec.resources.accelerator.deviceClasses — решение оператора, доступное там, где acceleratorPolicy.allowedDeviceClasses класса называет больше одного класса устройства. Пустое пересечение с перечнем класса — отказ с reason ClaimDeviceMissing; частичное пересечение допускается, и в план запуска уходит именно пересечение.
  • Заказ может назвать spec.launchStrategy (Latency, Throughput или Balance; значение по умолчанию — Latency) и выбрать ветку скомпилированного рецепта, которую читает его план. Замер по настоящему скомпилированному каталогу: на 94.3% пар «модель × род оборудования» ветки действительно дают разный maxModelLen, поэтому заказу, которому нужен более широкий горизонт контекста или упор на пропускную способность, больше не нужно менять рецепт или класс целиком. Заказ без поля ведёт себя как прежде.

Улучшения

  • Условия и отражение готовности в status проясняют ход заказа без выноса внутренней возни платформы в пользовательский контракт.
  • Общие помощники оценки выравнивают расчёт памяти и CPU узла и пола диска PVC между путями контроллера и планировщика.
  • Образ поставки называет системные пакеты, чьи файлы несёт, и несёт их лицензии. Часть файлов попадала в образ копированием, без учётных записей пакета, которому они принадлежат: обход уязвимостей такие файлы не видел вовсе — не потому, что нашёл их чистыми, а потому, что не знал об их существовании. Теперь поставка объявляет пакеты по их учётным записям и переносит текст лицензии каждого по каноническому пути, а образ не выходит, пока текст лицензии каждого объявленного пакета не найден.

Исправления

  • Оценка ресурсов узла больше не назначает заказу память под вес модели. Вес лежит на ускорителе и уже посчитан требованием видеопамяти, а на узле читается потоком через кэш страниц, который ядро вытесняет: замерено на заказе со ста двадцатью миллиардами параметров — назначалось 66 ГиБ, тогда как рабочий набор копии составлял 3663 МиБ. Оценка процессора привязывалась к числу параметров, от которого работа узла почти не зависит: считает ускоритель, а узел грузит веса и собирает графы, — заказу назначалось восемь ядер при установившемся расходе в 0,083 ядра. Теперь память узла держит среду исполнения и ограниченное окно её загрузчика, а процессор следует объёму читаемых при загрузке байт. Выше порога малой модели ни одна оценка не даёт одного ядра: приёмник запросов и вычислитель — разные процессы, и на одном ядре они отнимают время друг у друга настолько, что проба живости убивает исправную копию. Нижняя граница вертикального совета берётся из той же оценки, а не из постоянной: совет вправе просить больше назначенного планом и не вправе меньше — прежде он подменял назначение своим, и заказ получал одно ядро из восьми. По каталогу из 363 моделей сумма оценок памяти упала с 17 644 до 2454 ГиБ, роста нет ни у одной модели, наибольшая оценка — с 1268 до 14 ГиБ; четырнадцать моделей прежде просили памяти больше, чем есть у самого крупного узла стенда.
  • Заказ, отвечающий на запросы, больше не объявляется неготовым из-за нехватки места для РОСТА. Пересчёт плана у такого заказа может отказать — например, когда заказу нужно больше устройств, чем он держит, а свободных нет: занятое драйвер с публикации снимает. Прежде такой отказ переводил заказ в Pending, обрывал согласование до примирения его объектов и стирал условие, по которому следующее согласование решало, надо ли считать план заново, — заказ оставался неготовым, пока обслуживал, и сам из этого не выходил. Теперь заказ, копии которого держат свой порог и своё устройство, остаётся Ready: условие Planned держится истинным с причиной LaunchPlanInForce, а причина отказа и его слова стоят в message того же условия.
  • Пересчёт плана обслуживающего заказа больше не спотыкается о его собственное место. Драйвер не публикует устройство, уже занятое заявкой, а собственную запись заказа модуль выбрасывал из перечня занятости — держатель единственного подходящего устройства переставал видеть себя и получал отказ NoAcceleratorWithEnoughMemory при работающей копии. Теперь удерживаемая заказом ёмкость передаётся планировщику отдельно и участвует в его размещении как свободная; раздел ускорителя опознаётся наравне с целой картой. Отказ о нехватке памяти вдобавок называет требуемый объём и наибольший найденный, так что «модель велика для парка» и «в парке сейчас нет крупного устройства» различимы по одному сообщению.
  • Незакрытая заявка на устройство для копии, которой заказ не требует, больше не уводит заказ с класса устройств, на котором он работает. Прежде такая заявка начинала круг перепланирования от имени всего заказа: класс попадал в перечень исключённых, и заказ уводили с собственного места — а найти для него другое место нельзя, не сняв набор копий, то есть не потеряв работающую копию ради той, которой ещё нет. Теперь предметом круга является заявка, которая нужна порогу заказа; нехватка места для роста объявляется событием GrowthClaimUnplaced на заказе и оставляет его на своём классе. Заявка с отказавшим устройством размещённой не считается, поэтому сломавшееся устройство копии порога круг начинает как прежде.
  • Заказ, закреплённый на классе устройства раздела MIG в единоличном владении, больше не получает отказ без выхода. Все раздельные профили перечня оборудования закрепляли режим разделения вычислений, а раздел без признака разделения опознаётся как единоличный, поэтому такое гнездо не совпадало ни с одним профилем и размещение отвечало NoCompatiblePlacement при исправном оборудовании и достаточной памяти. Раздельные профили режим больше не закрепляют: одно описание служит гнёздам обоих видов, а запас памяти на разделение вычислений вычитается по действительному режиму гнезда — разделяемое платит его по-прежнему, единоличное не платит.
  • Размещение больше не отвечает по-разному на одном и том же кластере от снимка к снимку, когда раздел объявлен и единоличным классом, и разделяемым. Такая пара могла оказаться равной при сравнении кандидатов, а равенство разрешалось тем гнездом, которое инвентарь вернул первым.
  • Заказ, просящий горизонт выше окна модели, теперь получает названный отказ вместо расхождения. Значение runtime.<endpointType>.maxModelLen сверялось только с фактом каталога ai-models, поэтому на прямом пути Hugging Face его не ограничивало ничто. Планировщик считал бюджет видеопамяти по ужатому горизонту, а под запускался с запрошенным, и среда выполнения не стартовала без причины на заказе. Сверка теперь идёт для обоих источников модели и по более узкому из двух известных платформе окон — факту каталога и предустановке рецепта, — а сообщение отказа называет, какое из них ограничило решение.
  • Некалиброванная среда выполнения инференса больше не получает придуманных накладных расходов видеопамяти. Неизвестному имени молча выдавалось 0,5 ГиБ, что занижает бюджет; теперь это названный отказ. Ни один поставляемый артефакт этот путь сегодня не достигает, поэтому изменение защитное.
  • Минимальный заказ заработал на поставляемом классе. Он не объявлял modelPolicy, поэтому заказ, называющий только класс и модель, получал отказ EndpointTypeAmbiguous — причину, по которой повтор не назначается, так что заказ оставался в Failed до правки руками. Теперь поставляемый класс объявляет modelPolicy.allowedEndpointTypes: [Chat], единственный разрешённый тип выбирается автоматически, и заказ из двух ссылок доходит до Ready.
  • Размер модели, объявленный в заказе, больше не может занизить факт каталога. Раньше эффективным размером считался минимум из заказа и каталога, поэтому заказ умел только занижать — и этим одновременно обходил modelPolicy.maxParameterCount и уменьшал бюджет памяти, уходящий в планировщик. Когда у платформы есть факт о модели, эффективным размером считается факт; значение заказа применяется только там, где факта нет.
  • Память и CPU пода берутся из одной оценки — той, что планировщик посчитал и положил в план запуска заказа. Раньше мост считал их отдельно и не видел запаса под выгрузку кэша KV, заданного рецептом: у моделей google-gemma-4-* на intel-cpu запрос памяти вырастает на объявленные рецептом 40 ГиБ, потолок vpa.resourcePolicy.maxAllowed — вдвое от него. Фиксация resources на заказе по-прежнему главнее обоих источников.
  • Переменные окружения рецепта больше не теряются целиком. Булев ключ рецепта записывался в статус как true/false, читатель ждал 1/0, а ошибка подменялась пустым значением — под терял весь env, включая переменные, заданные в заказе. Значение, которое каталог не может прочитать, теперь даёт отказ с причиной RuntimeParameterNotSupported, а не запуск без переменных.
  • Горизонт контекста предустановок приведён в соответствие окну модели. У microsoft-phi-3-5-mini-instruct было занижено само окно: объявлено 4096 при настоящем 131072, поэтому под обслуживал 4096 вместо 8192 — теперь окно курировано и горизонт доезжает целиком. У microsoft-phi-3-mini-4k-instruct и microsoft-phi-3-medium-4k-instruct окно 4096 верно, и горизонт приведён к нему; поведение этих двух моделей не меняется.
  • Пример заказа в пошаговом сценарии теперь задаёт spec.model.ref объектом с name, как объявляет описание ресурса. Прежний пример со строкой строгий клиент отвергал, а снисходительный клиент усекал молча — заказ по такому примеру до готовности не доходил.
  • Ссылка в пошаговом сценарии, указывавшая на удалённое место, и имя переменной окружения, не совпадавшее с тем, что читает сценарий, — обе поправлены.
  • Руководство администратора теперь называет все четыре режима публикации по TLS, которые поддерживает модуль, — Disabled, CertManager, CustomCertificate, OnlyInURI, — включая перенос своего сертификата, которого руководство прежде не описывало.

Замечания по обновлению

  • Платформа больше не хранит перечня имён ускорителей. Род оборудования выводится из того, что устройство говорит о себе, — поколение из снимка ресурсов, иначе приставка имени изделия, иначе отказ. Заказуемым становится всякое устройство объявленного семейства, а не только то, чьё имя кто-то успел записать. От вас требуется одно, и только если вы читаете поле profileId плана запуска или предпросмотра размещений: имя выведенной записи пишет полное имя устройства там, где снятая запись писала укороченное торговое. Замерено на снятом перечне: из сорока семи записей разделов написание изменилось у восемнадцати — a100-40gb стало a100-sxm4-40gb, gb200 стало gb200-nvl4. В состоянии заказа этого поля нет, и ничто другое в наблюдаемом не изменилось.

  • Заказ, размещённый до обновления на платформе старше, пересогласуется один раз. Такой заказ хранит в состоянии не имя устройства, а образец записи, которой был размещён; образец теперь не опознаётся, заказ пересогласуется и состояние переписывается именем карты. Ручного вмешательства это не требует.

  • Обзор кластера называет род оборудования каждого ускорителя. В личности ускорителя появилось поле identity.hardwareKey — род, к которому платформа относит эту карту. Поле пусто, когда правила до рода не доводят: такую карту заказать нельзя, и обзор говорит это прямо, а не молчанием. Заодно опознание рода стало судить факт, а не написание: прежде карта, назвавшая поколением строку, из которой складывается имя ключа, получала род, которого не называла. От вас не требуется ничего: прежние поля личности остались как есть, и читатель, который нового поля не знает, поведения не меняет.

  • Заказ на железе, для которого в колесе нет ядер, отказывается до запуска. Устройство называет свою вычислительную возможность, а свод семейств объявляет возможности, для которых собраны ядра, — и платформа их сверяет. Прежде такой заказ принимался и падал при запуске: двоичный код одной возможности на устройстве другой не работает. От вас требуется одно: заказ, отказанный причиной NoCompatiblePlacement на железе, которое прежде принималось, требует не повтора, а поддержки этой возможности в поставке — она объявляется договором источника среды исполнения. Знать стоит и предел: устройство, чью возможность снимок не называет, судится как прежде — одним родом оборудования, — поэтому установка со старым драйвером ресурсов поведения не меняет.

  • Платформа опознаёт железо по тому, что оно о себе говорит. Устройство, чьё поколение назвал снимок ресурсов, получает род оборудования без записи в перечне имён. Заказуемым становится железо, которое прежде отказывалось: замерено, что NVIDIA H20, NVIDIA H800, NVIDIA A16, NVIDIA A2, NVIDIA L2, NVIDIA L20, NVIDIA GeForce RTX 4090, NVIDIA RTX 6000 Ada Generation и AMD Instinct MI300X не совпадали ни с одной записью перечня, хотя их семейства объявлены поддерживаемыми. Записи перечня по-прежнему побеждают вывод, поэтому ни один опознаваемый прежде род не изменился. От вас требуется одно: если у вас есть неизвестный платформе ускоритель Intel, он теперь получает отказ NoCompatiblePlacement, а не род центрального процессора — прежняя подстановка выдавала заказу рецепт, собранный не для его железа. Знать стоит и предел: платформа не знает, какую долю устройств в поле снимок называет по поколению, поэтому род выводится лестницей — поколение, иначе правило по имени изделия, иначе отказ, — и железо, до которого не доводит ни одна ступень, отказывается до запуска.

  • Имя ускорителя в состоянии заказа стало именем карты. Поле status.resolved.acceleratorProductName прежде несло образец записи оборудования, а не имя устройства: заказ на NVIDIA H100 PCIe сообщал NVIDIA H100*. Теперь там стоит то, что называет само устройство, и звезды в значении больше нет. То же у status.resolved.acceleratorVendor и у имени в перечне размещений. От вас требуется одно: если у вас есть наблюдатель, сравнивавший это значение со строкой с звездой, сравнение надо переписать на имя карты. Знать стоит и второе: отбор донора вытеснения сравнивает это имя, поэтому цель и донор на разных картах одного рода (скажем, NVIDIA H100 PCIe и NVIDIA H100 80GB HBM3) больше не считаются совместимыми — прежде считались, и цель получала донора, чья освобождённая карта ей не годится. Заказ, спланированный до этого выпуска, донором не предлагается, пока его размещение не будет посчитано заново; пересчёт запускается изменением описи устройств или правкой заказа.

  • Заказы начнут просить у узла другие ресурсы. Оценка пересчитывается первым же согласованием после обновления, а до копии новые величины доходят при её следующей замене — расширении набора, правке заказа, утрате узла. По памяти движение только вниз: у 166 моделей каталога оценка снизилась, у 197 не изменилась, роста нет ни у одной. По ядрам движение в обе стороны: у 95 моделей ядер стало меньше, у 43 — на одно больше, и это всегда переход с одного ядра на два. От вас ничего не требуется, но узел, заполненный под завязку по процессору заказами из этих сорока трёх, вместит их меньше; сумма оценок ядер по каталогу при этом падает с 864 до 639.

  • Условие Planned теперь истинно двумя способами. Кроме прежнего LaunchPlanCalculated — план вычислен этим согласованием — появилось LaunchPlanInForce: заказ собран по плану, который держит, а пересчёт этого плана отказан. От вас ничего не требуется. Знать стоит одно: наблюдатель, отбиравший заказы без места по Planned=False, обслуживающий заказ с отказавшим пересчётом больше не найдёт — такой заказ теперь Planned=True, а причина отказа стоит в message условия и в счётчике отказов проверки.

  • Новый выпуск среды исполнения больше не перезапускает копии, которые служат. Обновление, принёсшее новый выпуск, доводит его до описания нагрузки первым же согласованием, а копии, уже работающие, остаются на том выпуске, с которым были подняты. Копия, поднятая после этого — расширение набора, замена после утраты узла, — поднимается на поставляемом выпуске. Пока выпуски расходятся, заказ несёт RuntimeCurrent=False с причиной ReplicasHeldOnPreviousRuntime, и сообщение называет оба выпуска и число копий, дошедших до поставляемого. От вас ничего не требуется: правка заказа переводит все копии так, как просит класс, удаление копии переводит только её. Знать стоит две вещи: пока набор смешанный, копии отвечают по-разному у длины контекста и предела одновременных последовательностей, потому что эти значения приходят из рецепта того выпуска, с которым копия поднята; а если пересчитанный план сдвинул размещение заказа — другой класс устройства, другая доля, больший том модели, — копии сохранить нельзя вовсе: заказ сообщает причину WorkloadRebuilding и проходит через Pending. До этого выпуска поставляемая среда исполнения не доходила до заказа в устойчивой готовности вообще: она приезжал в тот произвольный момент, когда заказ трогали по другому поводу, то есть перезапуск случался вне всякого окна обновления. Смотрите ADMIN_GUIDE, раздел «Обновление модуля».

  • Ломающее. Значение updatePolicy.strategy: Recreate больше не принимается. Оно не называло ничего, что умеет объект нагрузки заказа: набор копий принимает RollingUpdate или OnDelete. Класс с этим значением получал отказ применения на каждой нагрузке каждого своего заказа, и отказ приходил владельцу заказа, а не автору класса. Схема принимает только RollingUpdate, а класс, сохранённый прежней схемой с Recreate, отвергается проверкой класса с названным способом в причине. Поправьте такой класс до обновления; заказы классов, называющих RollingUpdate или не называющих политику обновления вовсе, не затронуты.

  • Ломающее изменение. Поставляемый класс переименован, и заказ, называющий прежнее имя, теряет свой класс. Поставляемый InferenceServiceClass теперь называется default-llm; прежнее имя — default-llm-chat. Helm создаёт объект под новым именем и убирает прежний, поэтому заказ, у которого в spec.inferenceServiceClassName осталось прежнее имя, сообщит, что его класса не существует. Замените имя в заказах до обновления: ссылки никто не переносит, и промежутка, в котором отвечают оба имени, нет. Что при этом появляется. Тот же класс разрешает все договоры API вывода, которые модуль поддерживает — генеративный чат, векторизацию и переранжирование, — а не один чат. Заказ договор не называет: платформа берёт его из перечня класса, суженного тем, что об умениях модели говорит её источник. Значит на пути каталога модель векторизации обслуживается как модель векторизации, без своего класса. Когда свой класс всё ещё нужен. На источнике, который сведений о модели не даёт, перечень сужать нечем, и побеждает первый договор перечня — чат. Заказ модели векторизации на таком источнике будет обслужен как чат, молча. Если это ваш случай, объявите класс, чей перечень называет один нужный договор.

  • Ломающее изменение, и переход опасен молчанием. Из спецификации заказа сняты два поля: spec.launchStrategy и spec.priorityClassName. Схема заказа структурна и неизвестных полей не хранит, а защитная проверка на записи смотрит только длину имени, — поэтому заказ, всё ещё называющий эти поля, будет принят, а сами поля исчезнут из сохранённого объекта без слова. Ошибки, по которой владелец узнал бы о переходе, не будет: правьте свои манифесты до обновления. Где брать ответ. Ветвь скомпилированного рецепта задать негде: платформа читает ветвь задержки для любого заказа. Приоритет нагрузки задаётся перечнем allowedPriorityClassNames класса, а выбранное имя видно в status.constraints.priorityClassName.

  • Ломающее изменение. Из ресурсов заказа уходят три подблока: spec.resources.requests, spec.resources.limits и spec.resources.storage. Процессор и память контейнера вывода и пол размера тома модели платформа считает по размеру и квантизации модели, а значение, названное заказом, отменяло эту оценку целиком — одного ключа процессора или памяти хватало, чтобы выключить всё наложение. Применённые числа видны на нагрузке заказа. Размер тома — самое острое из трёх: том нельзя переразмерить после создания, поэтому заказ, размеченный вручную, оставался неверным навсегда.

  • Класс хранения тома модели переезжает на класс. Где лежит модель, никто не вычисляет, поэтому решение принадлежит администратору: spec.modelStorageClassName у InferenceServiceClass. Класс, его не назвавший, оставляет том классу хранения кластера по умолчанию, и второго правила по умолчанию платформа не заводит.

  • Стоящий том сохраняет свой размер, пока этого размера заказу хватает. Размер решается при СОЗДАНИИ тома и после этого не меняется — применение нагрузки, называющей другой размер, сервер отвергает целиком. Поэтому платформа читает стоящую нагрузку и переносит её шаблоны заявки, а не вычисляет их заново. Что произойдёт, решает сторона расхождения:

    • том больше оценки — он сохраняется, и заказ остаётся готовым. От вас ничего не требуется, ни одна заявка не переразмеряется и не удаляется, а заказ получает событие SettledRegionKept с обоими размерами. Шаблоны заявки переносятся целиком, поэтому сохраняется и modelStorageClassName, если класс его с тех пор сменил: том на другой класс хранения не переезжает, и стоящий заказ остаётся на том, на котором сделан;
    • заказ том перерос — модель в него больше не помещается, поэтому не переносится ничего, и заказ получает отказ вместо того, чтобы числиться готовым на непригодном томе. Заказ получает предупреждение SettledRegionTooSmall с обоими размерами; сам сервер сообщает только о запрещённом поле. Чтобы заказ поехал, снесите И нагрузку, И стоящую за ней заявку: для заказа chat это набор chat и заявка storage-chat-0. Следующий проход соберёт нагрузку под нужный размер, а артефакт будет скачан заново. Сноса одной нагрузки недостаточно, и он делает хуже: заявка её переживает, пересобранная нагрузка привязывается к той же заявке, и заказ работает на прежнем томе уже безо всякого предупреждения — два шаблона заявки теперь совпадают, и сообщать становится не о чем.
  • Как именно отклоняются снятые поля — по замеру, а не по предположению. Клиент, запрашивающий строгую проверку полей (так делает kubectl apply по умолчанию), получает отказ на манифест с любым снятым полем, и сообщение называет поле. Клиент без строгой проверки получает приём, а поле молча исчезает из сохранённого объекта. Верно и то, и другое; что увидите вы, зависит от клиента, и молчание достаётся автоматике на клиентской библиотеке.

  • Ломающее изменение с ОТЛОЖЕННЫМ следствием. Из спецификации заказа уходит блок границ числа копий: spec.scaling. Как и поля выше, заказ, всё ещё называющий его, будет принят, а блок исчезнет из сохранённого объекта без слова, — но следствие здесь не мгновенное. Заказ, работающий в трёх копиях, останется в трёх до пересчёта границ и только тогда опустится до того, что позволяет его класс. Момент этого пересчёта по объекту не прочитать, поэтому правьте манифесты, а не ждите его. Где брать ответ. Границы приходят из spec.scalingPolicy класса, а действующие значения видны в status.constraints.minReplicas и status.constraints.maxReplicas своего же заказа. Заказу, которому нужны свои границы, нужен свой класс, и это решение администратора.

  • Класс без политики масштабирования даёт одну копию. Это объявленное поведение, а не следствие ненастроенного поля: заказ границ не называет, и второго источника у платформы нет. Администратору: классу, под которым заказы обязаны работать больше чем в одну копию, блок spec.scalingPolicy обязателен. Поставляемый класс получает его этим выпуском (от одной до двух копий).

  • Отказ ScalingOutOfBounds снят: сравнивать с политикой класса больше нечего. Класс, чья собственная политика противоречива, по-прежнему отклоняется — как вина класса, под причиной InvalidServiceClass.

  • Ломающее изменение с отложенным следствием, и одна способность переезжает с заказа на класс. Из спецификации заказа уходит подблок ускорителя, а вместе с ним и оболочка блока ресурсов: spec.resources.accelerator.count, spec.resources.accelerator.sharePercent, spec.resources.accelerator.deviceClasses и сам spec.resources. Клиент со строгой проверкой полей получает отказ с названием spec.resources; клиент без строгой проверки получает приём, и блок исчезает без слова. Следствие отложено: заказ, работающий на двух устройствах, сохранит их до следующего планирования и лишь тогда получит назначенное планом. Где взять ответ. Число устройств и долю одного назначает план запуска для выбранной модели на выбранном железе в пределах spec.acceleratorPolicy класса. Назначенная доля видна в status.resolved.sharePercent собственного заказа, назначенный класс устройства — в status.resolved.deviceClass. Перечень допустимых классов устройства — перечень класса; заказу, которому нужен свой, нужен свой класс.

  • Ушли два отказа — предмета у них больше нет: AcceleratorCountNotAllowed и AcceleratorShareNotAllowed. Пределы класса не изменились и не ослабли: теперь они ограничивают то, что назначает планирование, а не то, что просил заказ. Предел, которому не хватает на потребность памяти модели, даёт целое устройство, а не отказ.

  • Заказы, стоявшие в отказе, ПОЕДУТ. Заказ, просивший больше устройств или долю вне пределов своего класса, получал отказ и стоял в нём. Просить стало нечем, поэтому такой заказ теперь проходит и получает назначенное планом. Если один из этих отказов держали как заграждение против заказа, которому работать не следовало, заграждения больше нет: пределы задаются политиками отдельного класса.

  • Класс без политики ускорителя отдаёт карту целиком. Разделение карты разрешает класс и больше никто: доли заказ не называет. Администраторам: класс, заказы которого должны делить ускорители, обязан объявлять spec.acceleratorPolicy с Shared вместе с WholeDevice. Поставляемый класс получает такую политику в этом выпуске.

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

  • Второе ломающее изменение, и оно на другой поверхности: классу перечень разрешённых договоров API стал обязательным и непустым. Класс без него API-сервер отклоняет на записи, и сообщение называет поле. Это отказ администратору, а не владельцу заказа: договор выбирается из перечня, заказ его не называет, и класс без перечня оставил бы свои заказы без договора вовсе. Классы, написанные вручную без перечня, перестанут приниматься при первой же правке. Объявите spec.modelPolicy.allowedEndpointTypes — хотя бы с одним договором. Поставляемый класс перечень объявляет, и все показанные в примерах классы тоже, поэтому правка касается только классов, написанных вручную.

  • Отказ о неоднозначности договора API снят вместе с полем заказа, которое его спасало. Заказ под классом без перечня прежде получал отказ, исправить который владелец заказа не мог. Теперь такой класс отклоняется на записи, а класс, сохранённый до требования, отклоняет заказ как вина класса.

  • Настраивать среду исполнения из заказа больше нельзя — способность снята целиком. Из спецификации заказа уходят три секции параметров: spec.runtime.chat, spec.runtime.embeddings и spec.runtime.rerank со всеми своими полями — длиной контекста, числом одновременных последовательностей, видом сжатия кэша ключей и значений, пределом новых токенов, температурой, вершинной долей, доверием удалённому коду модели и видом сведения векторов. Клиент со строгой проверкой полей получает отказ с названием секции, клиент без неё — приём с молчаливым усечением. Где взять ответ. Все эти значения задаёт запись рецепта плана запуска, скомпилированная под выбранное оборудование: длина контекста и число последовательностей зависят от видеопамяти карты, а вид сжатия кэша — от поддержки железом. Иное значение — это иной рецепт или иной класс, то есть решение администратора.

  • Ломающее изменение, и это снятие ВОЗВРАЩАЕТ значение, а не убирает его. Из спецификации заказа уходят переменные окружения процесса — spec.runtime.env с двумя ключами cpuKvcacheSpaceEnv и logLevelEnv. Клиент со строгой проверкой полей получает отказ с названием spec.runtime.env, клиент без неё — приём с молчаливым усечением. Каждую переменную окружения процесса вывода теперь задаёт запись рецепта плана запуска, и порядок старшинства стал двухступенчатым вместо трёхступенчатого: рецепт, затем внутренние значения по умолчанию модуля. Где взять ответ. Значение, которое должно быть иным, задаётся рецептом. Заказу с особыми требованиями к окружению нужен свой рецепт или свой класс — это решение администратора.

  • Ключ, который вы СНИМАЛИ, вернётся. Это острый край двух снятий сразу — и секций параметров, и переменных окружения; среди перечисленных выше им подобных нет. Заказ мог снять унаследованный из рецепта ключ, написав в него явный null; после снятия секции и объекта снимать нечем, и значение рецепта возвращается. Ключ не исчезает из пода — он в нём ПОЯВЛЯЕТСЯ. Если значение рецепта подавляли таким способом, при следующей сборке нагрузки под поедет с ним.

  • Отказы LegacyExtraEnv и LegacyExtraArgs сняты. Каждый ловил манифест, всё ещё несущий устаревший массив — переменных окружения либо аргументов запуска, — и указывал путь миграции: на закрытый объект переменных и на секцию параметров. Ни объекта, ни секций больше нет, значит указывать было некуда. Такой манифест теперь отклоняет сама схема — как необъявленное поле.

  • Отказ RuntimeParameterNotSupported остался, но предмет у него теперь другой. Он больше не относится к заказу: сверять в заказе нечего. Он называет ключ рецепта, который каталог параметров прочитать не может, — и заказ получает отказ вместо пуска пода на собственных значениях среды исполнения.

  • Сужение способности. Заказ на пропускную способность больше не заказывается: ветви Throughput и Balance заказом не выбираются. Скомпилированные записи рецептов эти ветви по-прежнему несут, и ключ ветви в запросе плана не менялся, — но заказ его больше не задаёт.

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

  • Ломающее изменение: отказ на записи для заказов каталога. Размер модели (spec.model.parameterCount) и ссылка на учётные данные (spec.model.authSecretRef) принимаются только при источнике HuggingFace. Заказ через каталог с любым из этих полей API-сервер отклоняет на записи, назвав в сообщении именно то поле, которое лишнее. Размер берётся из сведений модели каталога; учётные данные для получения артефакта каталога живут у самого каталога. На прямом пути оба поля работают как прежде.

  • Ломающее изменение. Состав status.resolved у заказа закрыт: в нём остаётся размещение, по которому заказ запущен, — deviceClass, acceleratorProductName, acceleratorMemoryGiB, placementMode, sharingMode — и состояние эпизода планирования: replanCount, excludedDeviceClasses. Тринадцать полей сняты: profileId, hardwareKey, quantization, acceleratorVendor, vramRequiredGiB, acceleratorCount, launchStrategy, recipeSource, recipeConfirmed, modelStorageGiB, hostMemoryGiB, hostCPUCores, deviceClassesNotAllowed, а вместе с ними вложенный блок runtime. Снят и блок status.appliedClass целиком. Выражение по снятому пути возвращает пустое значение, а не отказ, поэтому проверьте свои запросы до обновления. Где брать ответ, если он был нужен: имя применённого класса — в spec.inferenceServiceClassName того же заказа, где вы его и задали; политику масштабирования класса — у класса, если у вас есть право его читать; действующие пределы копий, приоритет и область модели — в status.constraints и status.model, они остаются на своих путях. Остальные снятые величины внутренние: их считает планировщик, а согласование заказа берёт их из плана запуска. Внешнего читателя у них не было, и публиковать их заново модуль не будет — поверхность заказа отвечает на вопрос «куда встал мой заказ и один ли я на железе», а не повторяет ответ планировщика.

  • Ломающее изменение. Блок настроек spec.settings.maas снят со схемы значений модуля вместе с ключами packageRepositoryName и packageVersion. Ключа, снятого со схемы, API-сервер не принимает: ModuleConfig, который его задаёт, после обновления не применится. Уберите блок maas из настроек модуля до обновления. Заменять его нечем и не нужно — то, ради чего закрепляли хранилище и версию пакета, модуль теперь делает сам: образы среды исполнения запроса и загрузки артефакта модели он собирает и называет отпечатком, а объекты заказа разворачивает без посредника.

  • Заказ, назвавший класс устройства maas, теперь получает именно его. Раньше это имя читалось как «класс не задан» — оно было значением по умолчанию стороннего пакета Helm, — и заказ разрешался так, будто класса он не просил. Если у вас есть класс устройства с таким именем и заказ на него, после обновления заказ поедет на него.

  • Ломающее изменение. Условие готовности заказа переименовано с ApplicationReady в WorkloadReady, а две его причины отказа — с ApplicationNotReady и ApplicationError в WorkloadNotReady и WorkloadError. Замените все три имени до обновления; то же с запросами к ai_inference_validation_failures_total, отбирающими по старым именам причин. Пересоздавать ничего не нужно: заказ, уже живущий в кластере, получает новое имя условия первым же согласованием на новой версии, и то же согласование снимает запись условия прежнего типа из status.conditions. Пока оно не прошло, запись остаётся на месте с тем значением, которое было у неё последним, — у работавшего заказа это True, — поэтому читать её на время раскатки обновления нельзя.

  • Ломающее изменение. Условие масштабирования заказа переименовано с ScalingLimited в ScalingHealthy, и его полюс перевёрнут: True теперь означает норму, False — ограничение роста. Прежнее имя из контракта не ушло: оно стало причиной на отрицательной стороне, рядом с Preempted, а условие самого HPA Kubernetes сохранило и своё имя, и свой смысл. Одного переименования недостаточно: проверку статуса нужно перевернуть тоже. Выражение, искавшее True как признак неисправности, обязано искать False; оставленное как было, оно вернёт исправные заказы вместо ограниченных — и ошибки не покажет. Пересоздавать ничего не нужно: заказ, уже живущий в кластере, получает новое имя условия первым же согласованием на новой версии, и то же согласование снимает запись условия прежнего типа из status.conditions. Пока оно не прошло, запись остаётся на месте с последним своим значением — у работавшего заказа это False, что под новым полюсом читается как отказ, — поэтому читать её на время раскатки обновления нельзя.

  • Объекты заказа модуль теперь разворачивает сам, а не просит об этом пакет поставки, и их имена теряют суффикс -app: заказ chat обслуживается нагрузкой chat, а не chat-app. Это не поле контракта, но это видимая перемена для всего, что ходило к объектам заказа по имени, — панелей, запросов к измерениям, ручных обращений через kubectl. Метка app на подах несёт то же новое имя.

  • Нагрузка прежнего пути остаётся в кластере, и убрать её — ваша работа. Прав на неё у модуля больше нет, и её имени он не знает: модуль разворачивает и вычищает только те объекты, которые собирает сам, а заказ обновлением не пересоздаётся, поэтому и сборщик мусора прежний комплект не тронет. Нагрузка {заказ}-app продолжает работать рядом с новой, и её поды продолжают держать выданную им заявку на устройство. На ограниченном пуле устройств это бьёт дважды: устройства расходуются двумя нагрузками вместо одной, а новой заявке заказа может не остаться ничего — заказ, который до обновления был Ready, после него может остаться в Pending. Уберите прежний комплект по каждому заказу после обновления, убедившись сначала, что заказ обслуживается новой нагрузкой. Отбирайте по имени объекта, а не по метке: прежний комплект и новый несут одну и ту же метку app — имя заказа, — поэтому по метке их не различить. Прежние объекты — те, чьё имя оканчивается на -app:

    kubectl -n <namespace> get inferenceservice <заказ> -o jsonpath='{.status.phase}'
    kubectl -n <namespace> get statefulset,service,ingress,poddisruptionbudget,secret,\
      resourceclaimtemplate,servicemonitor,horizontalpodautoscaler,verticalpodautoscaler,certificate \
      -o name | grep -- '-app$'
    kubectl -n <namespace> delete application <заказ> --ignore-not-found

    Удаления объекта Application самого по себе недостаточно: у отрисованных объектов нет ссылки на владельца, ведущей к нему, — поэтому уберите и то, что перечислила вторая команда, прочитав список.

  • Поля spec.model.parameterCount и InferenceServiceClass.spec.modelPolicy.maxParameterCount теперь требуют явный суффикс M или B без учёта регистра. После обновления API-сервер отклоняет манифесты со значением без суффикса, например "32". До обновления модуля замените каждое такое значение на величину с нужным масштабом, например 32B или 350M.

  • Вытеснение теперь считает поды, заблокированные по размещению, по вердикту самого планировщика: заблокированным считается под с условием PodScheduled=False и причиной Unschedulable. До этого выпуска число выводилось из связки «фаза Pending и узел не назначен», а такая связка относит к заблокированным и под, которого планировщик ещё не рассматривал: между созданием и первой попыткой размещения так выглядит любой под. Правило выбора доноров при этом не менялось — оно по-прежнему опирается на состояние заявок DRA, поэтому ни один заказ не будет вытеснен из тех, что не вытеснялись раньше. Меняется число, которое сообщают события вытеснения: теперь это число подов, которым планировщик отказал. Ни одна метрика этого числа не несёт.

  • Изъятие ёмкости стало наблюдаемым. Модуль публикует событие на оба объекта — на донора, у которого снижена граница реплик, с называнием получателя, и на получателя с называнием донора, — а сообщение условия ScalingHealthy донора тоже называет получателя. Оба события публикуются только тогда, когда ёмкость действительно перешла: проход, одобренный планировщиком и не изъявший ничего, получает свой исход (result="noop") и молчит, — то есть событие больше не объявляет изъятия, которого не было. Когда удержание заканчивается, донор получает закрывающее событие: оно называет восстановленную границу и различает полное восстановление от частичного. Удержание, которое снять восстановлением нельзя — дочерний объект исчез, — получает своё событие вместо молчания. Частичное восстановление сохраняет условие ScalingHealthy=False с причиной Preempted: донор по-прежнему удерживается. Впервые поставляются два оповещения: ёмкость изъята и донор удерживается на пониженной границе; датчик, на котором стоит второе, — ai_inference_preemption_donors_held. Причина отказа вне объявленного списка допуска теперь попадает в ведро Other у ai_inference_preemption_failures_total, а не отбрасывается: до этого выпуска она молча пропадала, тогда как счётчик попыток отказ всё равно считал, — то есть отказы и причины отказов не складывались никогда. У ai_inference_preemption_attempts_total{result} теперь шесть значений — ok, noop, refused, error, restored, hold-cleared, — из них два новых: noop — проход, ничего не изъявший, и hold-cleared — удержание, кончившееся из-за исчезнувшего дочернего объекта. Прежде они считались как ok и error соответственно, поэтому запрос по любому из этих двух после обновления считает меньше. Счётчик к тому же заводит все значения класса устройства нулями, как только класс впервые наблюдался: поэтому count(), absent() и sum by (result) видят серии исходов, которых не было, и класс устройства, названный планировщиком на проходе, ничего не изъявшем, всё равно появляется. Увидеть самое первое изъятие на классе, которого этот процесс контроллера прежде не видел, increase() при этом не может: такая серия появляется и приращивается между двумя съёмами, и никакое заведение внутри процесса нулевого отсчёта между ними не поставит. Прежняя редакция этой заметки утверждала обратное. Метрики вытеснения получают метку класса устройства. Метка result не переименована, поэтому запрос, называющий её, выбирает те же серии, — но набор её ЗНАЧЕНИЙ вырос, поэтому запрос по result="ok" или result="error" считает меньше событий, чем прежде, как сказано выше. Имена объектов метками намеренно не становятся: личность живёт в событиях и в условии, где она не растит мощность метрик. Метка класса устройства называется deviceClass — написание верблюжье, как у поля описания вида. События называются CapacityPreempted (донор), CapacityGranted (получатель), CapacityRestored (донор, удержание кончилось) и PreemptionHoldCleared (донор, удержание кончилось без восстановления); оповещения — D8AIInferenceCapacityPreempted и D8AIInferenceDonorHeldAtLoweredBound. Руководства называют CapacityPreempted, CapacityRestored и PreemptionHoldCleared в своих командах; CapacityGranted читается через describe или в потоке событий.

  • Наблюдатель HPA больше не пишет условие масштабирования у заказа, удерживаемого вытеснением. Два писателя одного условия означали, что причина Preempted — та, по которой ищет доноров руководство оповещения об удержании, — заменялась на ScalingHealthy ровно у тех доноров, где она и нужна. Заметно в двух местах: условие удерживаемого донора сохраняет причину и сообщение вытеснения, а ai_inference_hpa_observation_total считает такой проход как skipped.

  • Набор событий, пробуждающих цикл вытеснения, изменился в одной половине: цикл пробуждает смена ВЕРДИКТА — условия PodScheduled=False с причиной Unschedulable, — тогда как та же половина прежде читала «фаза ожидания и не назначен узел». Смена вердикта считается в обе стороны: пробуждает и вход в отказ, и выход из него. Не пробуждает этой половиной только переход, вердикта не меняющий, — между двумя состояниями, где отказа по размещению нет. Пробуждение по смене фазы пода осталось как было, и пробуждение по созданию пода — тоже: оно безусловно и было таким всегда. Перенастраивать ничего не нужно, и первый отклик нигде не задерживается: меняется то, КАКИЕ обновления пробуждают цикл этой половиной. Под, которому планировщик отказал, теперь даёт одно пробуждение в момент прихода вердикта, чего прежнее правило не давало — оно считало такой под заблокированным с самого создания; а под, перешедший от неразмещённого к размещённому, ни разу не получив отказа, не даёт ни одного, тогда как прежнее правило пробуждало цикл при назначении узла.

  • История того выпуска. Мост доставки вычищал из настроек своего дочернего объекта ключи, которых не хотел, и на обновлённом кластере это было заметно. Ключ, добавленный администратором через kubectl edit (обычная правка объекта, а не серверное применение — в терминах управляемых полей операция Update, а не Apply), удалялся при следующем согласовании: такой ключ принадлежал управляющему, названному по имени процесса, и от собственного остатка модуля не отличался. Ключ autoscaling мост уступал чужому применяющему только пока случай вытеснения длился, а по его окончании отправлял как обычно. Ключ, записанный серверным применением под своим управляющим полями, оставался на месте. Сам мост с тех пор снят — объекты заказа платформа собирает сама, — поэтому ничто в этом пункте не описывает модуль сегодня. Совет, который он давал, для собираемых платформой объектов остаётся верным: держите ручные настройки под своим управляющим полями (kubectl apply --server-side --field-manager=<ваше-имя>), а не правьте их на месте.

  • Заказ, у которого размещённое устройство сообщает об отказе, теперь получает перепланирование. Контроллер добавляет текущий класс устройства в список исключений, снова обращается к планировщику, затем пересобирает нагрузку заказа с другим устройством — то есть она перезапускается на другой карте, а не остаётся на сломанной. До этого выпуска отказ учитывался только для заявки без размещения, а такое сочетание Kubernetes 1.35 и новее удержать не может: сервер отвергает условие устройства, не размещённого в заявке. Заказы, чья заявка размещения так и не получила, не затронуты — их и раньше ловила отсрочка ожидания. Размещённое устройство, сообщающее только Ready=False, перепланирования не вызывает: так выглядит свежеразмещённое устройство, пока драйвер его настраивает, и перепланирование увело бы здоровую карту с её класса устройства насовсем. Отказом считается лишь Failed=True на размещённом устройстве.

  • Сверка горизонта контекста теперь действует и на прямом пути Hugging Face. Существующий заказ, у которого maxModelLen выше окна, объявленного предустановкой рецепта модели, переведёт условие заказа в False с причиной RuntimeParameterNotSupported при первом же согласовании, поднявшем поколение. Отметьте асимметрию: правится только состояние, поэтому нагрузка заказа и её под остаются на месте — заказ сообщает об отказе, а нагрузка продолжает работать. Снизьте maxModelLen до окна модели либо снимите ключ, тогда действует значение по умолчанию стратегии запуска. Окно сегодня объявляют четыре модели поставляемых предустановок, все семейства microsoft; заказы на прочие модели не затронуты.

  • Поставляемый класс теперь ограничивает тип API значением Chat. До этого выпуска класс не объявлял ограничений, поэтому заказ на модель каталога, среди типов которой не было Chat, молча получал первый предложенный каталогом тип. Такой заказ теперь отклоняется с названной причиной. Класс предназначен для генеративного чата, и объявленный набор приведён в соответствие имени; для другого типа объявите собственный InferenceServiceClass. Уже работающий заказ в состоянии Ready продолжает работать до правки, поднимающей поколение. Отменено. Поставляемый класс теперь разрешает все договоры, которые модуль поддерживает, и его имя ни одного из них не называет. Что это меняет в выборе класса — в замечании об обновлении в начале страницы.

  • На поставляемом классе заказ, не объявивший spec.runtime.endpointType, может теперь получить другой тип API, чем раньше, — без отказа и без предупреждения. До этого выпуска типом становился первый элемент supportedEndpointTypes модели из каталога; теперь — пересечение с [Chat]. Для модели, объявляющей [Embeddings, Chat], выбранный тип меняется с Embeddings на Chat, а вместе с ним меняются status.model.endpointType, ветка рецепта и параметры запуска. Если конкретный тип важен, задайте spec.runtime.endpointType явно или используйте собственный класс. Заметьте, что прежнее поведение зависело от порядка типов в каталоге, а не от чего-либо в заказе.

  • Заказ, объявивший spec.model.parameterCount ниже факта каталога, теперь оценивается по факту. Запрошенные память узла, CPU, место под модель и видеопамять растут соответственно, а предел класса maxParameterCount, который заниженное значение обходило, теперь отклоняет заказ.

  • Ломающее изменение для ЗАПРОСОВ по измерениям, и оно молчаливое. Два значения разметки result у счётчика ai_inference_reconcile_total меняют написание: application_ensure_transient становится workload_ensure_transient, а application_errorworkload_error. Они называли пакет доставки, снятый целиком ранее, тогда как ступени нагрузки рядом уже называли нагрузку. Внутри модуля этот счётчик не читает ничто, поэтому внутри ничего не ломается — а вот ВАШ запрос, выбирающий заказ по прежнему написанию, найдёт ноль событий, а не ошибку, и это опасная половина: молчащий запрос выглядит как «происшествий нет». Перепишите такой запрос на новое написание до обновления. Ряд прежнего значения из вашего хранилища измерений не исчезает — он перестаёт расти, поэтому запрос по историческому окну остаётся годным. Ничего не пересоздаётся, и ни один заказ из-за этого не меняется.

  • Модуль в стадии Experimental: между выпусками ожидаются аддитивные изменения CRD и settings. Перед обновлением живого заказа сверяйте пошаговый сценарий и справочник CR / OpenAPI по актуальному набору полей.

Документация