В замере Red Hat два балансировщика распределяли один и тот же чат-трафик между четырьмя GPU NVIDIA L4. По числу обработанных запросов в секунду результаты почти совпали: 0,52 у обычного round-robin и 0,49 у маршрутизации с учётом префиксов. Но p95 времени до первого токена различался в 2,7 раза: 745 мс у round-robin против 272 мс у prefix-aware routing.

GPU, модель и нагрузка в обоих прогонах были одинаковыми, менялся только способ выбора реплики. Счётчик RPS этого не показывает: он учитывает обращения, но не работу, которую под выполняет для конкретного запроса. Если нужный префикс уже лежит в KV-cache, модель начинает генерацию быстрее. Если запрос попадает в соседнюю реплику без этого состояния, ей приходится заново считать всю голову запроса.

Этот разбор посвящён выбору реплики для self-hosted LLM-инференса в Kubernetes. Он пригодится инженерам, которые запускают модели на нескольких GPU, и владельцам платформ, оценивающим нагрузку по загрузке карт и числу запросов. Для контуров с одной репликой вопрос маршрутизации не возникает; порог применимости разобран отдельно.

Все примеры основаны на публичных замерах разных команд. Я рассматриваю задачу со стороны инфраструктуры: как маршрутизация влияет на использование GPU, задержки и требования к Kubernetes-контуру. VK Tech предоставляет GPU-ноды и vGPU для развёртывания таких систем.

Состояние внутри запроса

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

В LLM-инференсе этот принцип не работает. Запрос на 200 токенов и запрос на 20 тысяч токенов приходят через один endpoint, но требуют вычислений, различающихся на два порядка. Счётчик запросов считает их одинаковыми.

Кроме того, состояние запроса привязано к конкретной реплике. Обработанный префикс остаётся в KV-cache пода, который его посчитал. Если следующий запрос с тем же началом уйдёт в соседний под, тот заново рассчитает весь префикс.

Один префикс часто используют разные пользователи. Общий системный промпт, один документ в RAG-контуре или типовая инструкция для агента создают одинаковое начало запроса у десятков клиентов.

Схема 1. Три клиента с одинаковым началом запроса раскладываются по трём подам. Кеш есть только у одного из них.
Три клиента с одинаковым началом запроса распределяются по трём подам. Кеш есть только в одном из них. 

Исследователи IBM свели проблему к одной фразе на KubeCon в марте 2026 года:

Каждый запрос к LLM несёт невидимое состояние — KV-cache. Стандартная балансировка Kubernetes его не учитывает: связанные запросы распределяются между разными подами, а локальность кеша теряется. 

Штатный балансировщик Kubernetes не видит состояние в KV-cache, поэтому для LLM-инференса нужен отдельный компонент, который учитывает его при выборе реплики.

Дальше речь пойдёт о контурах с несколькими репликами. Четыре GPU можно отдать одной реплике с тензорным параллелизмом, и выбирать между репликами не придётся. Но если реплик две или четыре, для каждого запроса нужно определить, куда его направить. Как сам движок деградирует при фрагментации и вытеснении кеша, я разбирал в отдельном материале. Маршрутизация решает другую задачу уровнем выше: она определяет реплику до того, как запрос попадёт в движок.

Запрос, не попавший в кеш, с точки зрения балансировщика выглядит обслуженным. Под получает его без совпадений в KV-cache и заново считает весь префикс, хотя тот уже мог быть рассчитан на соседней реплике. В счётчике обработанных запросов это не видно, но GPU повторно выполняет ту же работу.

Замер Red Hat проводили на одноузловом стенде с OpenShift 4.20, четырьмя GPU NVIDIA L4 и моделью семейства Qwen. Нагрузка включала 11 параллельных диалогов по 10 ходов, документы двух типов и случайные паузы между ходами. Авторы отдельно указывают, что сценарий был собран для проверки переиспользования кеша.

Метрика

Round-robin

Префикс-осведомлённая маршрутизация

TTFT p50, мс

123

92

TTFT p95, мс

745

272

TTFT p99, мс

841

674

Попадания в KV-cache

около 62%

около 90%

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

0,52

0,49

Стенд закрытый, число ходов фиксировано, выборка — чуть больше сотни запросов. Разница в 6% по RPS укладывается в разброс, тогда как p95 отличается в 2,7 раза. По числу обработанных запросов способы распределения почти неразличимы, но хвостовая задержка отличается кратно.

Разрыв растёт вместе с числом реплик, между которыми распределяется кеш. В сентябре 2025 года команда llm-d на восьми подах с двумя H100 получила TTFT 0,542 секунды при prefix-aware routing и 92,551 секунды при случайном распределении. Базовую линию снимали на перегруженном стенде, где очередь росла быстрее, чем система успевала обрабатывать запросы.

На продовом трафике Vertex AI в Google Cloud сообщают о сокращении TTFT более чем на 35% и росте доли попаданий в префиксный кеш с 35 до 70%. В Яндексе на реальном трафике AI Studio round-robin давал от 5 до 10% попаданий в кеш, липкие сессии — около 30%, а cache-aware router — до 54%.

Методики и базовая нагрузка в этих замерах различаются, но соотношение результатов совпадает: round-robin везде оказывается последним.

Липкие сессии и привязка пользователя к поду решают только часть задачи. Команда llm-d описывает ограничение так:

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

Привязка по пользователю позволяет переиспользовать состояние внутри одного диалога. Но общий системный промпт на десятки тысяч токенов одновременно приходит от множества клиентов, и при такой схеме каждый под пересчитывает его отдельно.

Разворачивайте кластеры Kubernetes с Managed Containers

Автоматизируйте деплой
и снижайте затраты на инфраструктуру
до 60%

InferencePool как цель маршрута

Расширение Gateway API для инференса вводит отдельную цель маршрутизации, InferencePool. Этот объект объединяет реплики одной модели и заменяет в такой схеме обычный Service. Его API уже стабилизирован в версии inference.networking.k8s.io/v1.

Конкретную реплику выбирает отдельный компонент Endpoint Picker. Шлюз передаёт ему сведения о запросе и получает в ответ адрес пода, куда его нужно направить.

Схема 2. Поверх прежней схемы встаёт пул реплик и отдельный компонент выбора, который читает метрики с самих подов.
Поверх прежней схемы встаёт пул реплик и отдельный компонент выбора, который читает метрики с самих подов.

Endpoint Picker принимает решение по метрикам, которые отдаёт движок. Спецификация протокола model server требует передавать три показателя: количество запросов в очереди, количество выполняющихся запросов и заполненность KV-cache.

Две из этих метрик тоже учитывают запросы, но в отличие от RPS измеряют нагрузку на уровне конкретной реплики. RPS на входе показывает общий темп обращений ко всему пулу. Очередь и число выполняющихся запросов отражают объём незавершённой работы в поде, а заполненность кеша показывает, какое давление состояние запросов создаёт на память.

Отдельно работает префиксный скорер. Он определяет, какая часть начала запроса уже обработана в каждом поде. По умолчанию скорер анализирует первые 16 тысяч символов запроса, что соответствует примерно 4 тысячам токенов английского текста. Этого достаточно, чтобы отличить под с готовым префиксом от пода, где его нет.

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

Что учитывать при выборе реплики 

На входе балансировщик видит только поток запросов и не может оценить их фактическую стоимость. Чтобы направить запрос в подходящую реплику, нужно смотреть на состояние каждого пода: очередь, число выполняющихся запросов, заполненность KV-cache и совпадение с уже обработанным префиксом.

  • RPS показывает интенсивность входящего трафика по всему пулу, но не объём работы в отдельных репликах.

  • Очередь, число выполняющихся запросов и заполненность KV-cache помогают оценить текущую нагрузку на конкретный под.

  • Round-robin и липкие сессии сохраняют состояние только для части запросов и не учитывают общий префикс, который используют разные пользователи.

  • Префиксный скорер по умолчанию сравнивает первые 16 тысяч символов запроса, то есть примерно 4 тысячи токенов английского текста.

  • Если префикс совпадает, движок переиспользует его целиком, даже если он длиннее окна, которое проверяет скорер.

  • Для русских токенизаторов окно в 16 тысяч символов покрывает больше токенов, чем следует из англоязычной документации.

Аффинность против перегрева

Привязка запроса к поду с подходящим кешем может создать перекос нагрузки. Если пользователи одновременно работают с одним популярным документом, один под будет хранить самый полный префикс и начнёт получать почти весь трафик. Остальные реплики в это время останутся недогруженными. Инженеры Google в разборе Vertex AI описывают риск так:

Строгое следование аффинности к кешу может быть опасным. Если все запрашивают один популярный документ, один узел перегружается, пока остальные простаивают.

В Google соотношение весов изменили с 3:3:2 на 3:5:2 и увеличили вес глубины очереди. Планировщик стал обходить перегруженные поды даже в тех случаях, когда в них уже есть подходящий префикс.

В llm-d считают баланс между совпадением с кешем и текущей загрузкой принципиальной задачей. Фиксированные веса не всегда работают одинаково хорошо: профиль трафика меняется, и при одних нагрузках важнее сохранить попадания в кеш, а при других — не допустить роста очереди.

Работа CacheRoute от августа 2026 года описывает две нагрузки на моделях 32B, в которых кеш-аффинность проигрывает обычному распределению. В одном сценарии она снижает ёмкость до 0,50–0,67 от уровня плоского балансировщика, а в другом не даёт выигрыша. Авторы предлагают выбирать стратегию по результатам теневого прогона.

Перед включением такого слоя нужно снять базовые метрики на своём трафике. Маршрутизация с учётом префикса оправдана, когда запросы действительно имеют повторяющиеся начала: разница по p95 в 2,7 раза может компенсировать сложность нового компонента. Но она не решает несколько сопутствующих задач.

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

Накладные расходы Endpoint Picker также приходится измерять на собственном теневом прогоне. Компонент участвует в обработке каждого запроса, но универсальной оценки его влияния на задержку нет.

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

Порог применимости

Нужен ли отдельный слой маршрутизации, зависит от трёх параметров: доли повторяющихся префиксов, их длины и числа параллельных сессий на реплику.

До внедрения стоит по логам измерить, какая доля входящих токенов приходится на повторяющиеся начала запросов. Если она составляет единицы процентов, экономия на попаданиях в кеш теряется на фоне остального расчёта. При десятках процентов, как 54% у Яндекса на агентском трафике, маршрутизатору есть что переиспользовать.

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

Также важно, сколько диалогов одновременно обслуживает одна реплика. На стенде Red Hat их было меньше трёх, и это примерно нижняя граница, при которой выбор реплики начинает влиять на долю попаданий в кеш. При одном активном диалоге повторный запрос всё равно попадёт в ту же реплику, что и при маршрутизации с учётом префикса.

На потоке коротких независимых запросов без общего начала такой слой только добавит ещё один компонент и задержку на выбор пода. Кеш при этом останется пустым независимо от числа GPU.

Перед внедрением маршрутизатора стоит проверить возможности самого движка. В vLLM chunked prefill включён по умолчанию, поэтому сначала нужно убедиться, что его не отключили явно, а затем подобрать размер батча в токенах под профиль нагрузки. По разбору Towards Data Science одна эта настройка может увеличить пропускную способность по токенам примерно на 50% и не требует нового компонента в контуре.

Дезагрегация prefill и decode, о которой говорится в том же разборе, начинает окупаться примерно от тысячи GPU с быстрым интерконнектом. Для кластеров из десятков GPU этот вывод неприменим.

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

Другой вариант — роутер из vLLM production stack. Он учитывает совпадение префикса и текущую загрузку, работает внутри одного стека и не требует Gateway API с расширением.

Когда маршрутизация по KV-cache оправдана 

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

HPA и канарейка внутри пула

Замена Service на InferencePool затрагивает настройки, которые были построены вокруг Service.

Схема 3. Точки, которые перестраиваются при замене цели маршрута: автомасштабирование, выкатка, дашборд.
Точки, которые перестраиваются при замене цели маршрута: автомасштабирование, выкатка, дашборд.

После замены Service на InferencePool нужно пересмотреть автомасштабирование и схему выкаток. HPA, который ориентируется на число запросов, получает сигнал о темпе обращений ко всему пулу, тогда как Endpoint Picker выбирает реплику по состоянию конкретного пода. Чтобы масштабирование не расходилось с логикой маршрутизации, его целевые метрики стоит привязать к длине очереди и заполненности KV-cache.

Канареечные выкатки с весами между двумя Service тоже переносятся внутрь пула, поскольку для шлюза остаётся одна цель маршрутизации. Версии в такой схеме разделяются на уровне реплик и правил, по которым Endpoint Picker выбирает их внутри InferencePool.

Внедрение лучше начинать с базового замера на текущей схеме: снять хвост TTFT и долю попаданий в кеш. Затем развернуть пул и Endpoint Picker с весами по умолчанию и повторить замер на том же профиле нагрузки. После этого можно подбирать веса скореров, как Google при переходе с 3:3:2 на 3:5:2.

Автомасштабирование и правила выкатки стоит менять последними. Если менять маршрутизацию, HPA и канареечную схему одновременно, будет сложно понять, что именно повлияло на результат.

Поддержка на уровне data plane есть в Istio начиная с версии 1.27, NGINX Gateway Fabric и agentgateway. API InferencePool и Endpoint Picker развиваются в разных релизных циклах, поэтому совместимость их версий нужно проверять отдельно.

Четыре замера на текущей схеме

Что проверить до внедрения

Маршрутизация с учётом префикса меняет не саму модель и не число GPU, а то, как уже выделенные ресурсы используются на повторяющихся запросах. Поэтому начинать стоит не с установки InferencePool и Endpoint Picker, а с замера текущего контура. Он покажет, есть ли в трафике префиксы, которые имеет смысл сохранять в KV-cache, и где проходит граница между выигрышем в задержке и лишней сложностью в инфраструктуре.

На текущей схеме достаточно провести четыре проверки:

  1. Снять с движка число запросов в очереди, число выполняющихся запросов и заполненность KV-cache. Если очередь уже есть, а кеш заполнен не полностью, у контура остаётся запас: запросы ждут GPU, хотя реплики ещё могут удерживать дополнительные префиксы.

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

  3. Настроить теневое воспроизведение реального трафика и сравнить на нём текущую раскладку с prefix-aware routing. Включать новый слой в проде имеет смысл, если на одинаковом профиле улучшается p99, растёт ёмкость или оба показателя сразу.

  4. Отключить Endpoint Picker в тестовом контуре и проверить поведение шлюза. Важно заранее знать, вернёт ли он ошибку или продолжит распределять запросы по InferencePool без подсказки компонента выбора.

При оценке результата нужно учитывать физическое ограничение KV-cache. Его ёмкость равна видеопамяти реплики за вычетом весов модели. От оставшегося объёма зависит, сколько префиксов под сможет удерживать одновременно и как быстро начнёт вытеснять старые. Поэтому при планировании инфраструктуры важны два вопроса: сколько GPU потребуется модели и сколько видеопамяти останется под кеш после её загрузки.

На наших vGPU для одной задачи можно выделить от 2 до 16 ГБ видеопамяти в управляемом Kubernetes. Форму нарезки и модель выбирает команда, но объём памяти под KV-cache нужно учитывать вместе с ними, а не после развёртывания.

Разница в 2,7 раза при почти одинаковом RPS показывает ограничение самого счётчика запросов. На другом стенде абсолютные цифры будут другими, но RPS всё так же не отразит объём вычислений, скрытый за отдельным обращением. Для LLM-инференса важно видеть не только число завершённых запросов, но и очередь, заполненность кеша и долю работы, которую удалось не выполнять повторно.

Что вывести на дашборд

После внедрения маршрутизации дашборд должен показывать те же сигналы, по которым Endpoint Picker выбирает реплику:

  • Длину очереди в каждой реплике.

  • Заполненность KV-cache.

  • Долю попаданий в префиксный кеш.

Очередь и заполненность кеша движок обычно отдаёт из коробки. Долю попаданий в префикс нужно собрать из метрик кеша отдельно.

Если в дашборде остаётся только число обработанных запросов, ухудшение отклика выглядит как нехватка GPU. Тогда обсуждение быстро сводится к добавлению новых карт, хотя часть вычислений может уходить на повторный расчёт уже готовых префиксов.

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

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