График пропускной способности — это не обещание
Один и тот же сервис инференса можно описать двумя честными способами. Инженер показывает кривую: столько-то токенов в секунду на насыщении, выше конкурента, ускорение за квартал двукратное. Продукт показывает другое: доля диалогов, где ответ начинался дольше трёх секунд, выросла вдвое. Оба измерения верны, и они почти не связаны между собой.
Разница не в аккуратности замера, а в том, что измеряется. описывает, сколько работы машина успевает сделать, когда её загрузили полностью. Продукт живёт не средним, а хвостом: его интересует, что случилось с теми запросами, которым не повезло оказаться в очереди за чужим длинным контекстом. Бенчмарк без порога описывает железо; обещание описывает поведение сервиса на границе.
Формат такого обещания придумывать не нужно — индустрия его уже записала. MLPerf Inference в версии 5.1 оценивает систему на модели Llama 3.1-8B не абстрактной скоростью, а максимальной устойчивой нагрузкой внутри двух границ. В сценарии Server это TTFT не выше 2 секунд и TPOT не выше 100 миллисекунд. В добавленном сценарии Interactive границы жёстче: 0,5 секунды и 30 миллисекунд.
MLCommons сама переводит эти пороги в человеческую величину: 100 миллисекунд на токен — это примерно 480 слов в минуту, заметно быстрее обычного чтения, а 30 миллисекунд — около 1600 слов в минуту. Второй сценарий появился ради случаев, где важна отзывчивость: помощники в коде и инструменты, работающие в реальном времени.
обещание = порог на старте ответа + порог на темпе генерации + доля запросов, которая в них укладывается
Дальше вся статья — про то, откуда берётся нарушение этого обещания. Сначала разберём, почему одной кривой задержки недостаточно, затем — где на самом деле живёт хвост, чем измеряется ёмкость, как всё это ломается и что делать вместо отказа.
Одной кривой задержки не хватает: величин три
«Медленно» для сервиса генерации распадается минимум на три разных события. Первое: пользователь нажал кнопку и ничего не происходит — это время до первого токена. Второе: ответ пошёл, но печатается медленнее, чем читается — это темп генерации, время на выходной токен. Третье: ответ целиком не успел прийти до таймаута вызывающей системы — это сквозная задержка.
| Величина | Что измеряет | От чего зависит | Как выглядит для пользователя |
|---|---|---|---|
| TTFT | Сколько ждали до первого токена | Очередь, планировщик, обработка входа | Пользователь думает, что кнопка не сработала |
| TPOT / межтокенная задержка | Как быстро идёт текст после старта | Размер батча, соседи по машине, длина контекста | Ответ печатается медленнее, чем читается |
| Сквозная задержка | Сколько занял весь ответ | Сумма первых двух и длина ответа | Сценарий не укладывается в таймаут вызывающего |
| Время в очереди | Сколько запрос ждал до начала работы | Загрузка, приоритеты, вытеснения | Ничего не видно на графиках модели — и это главный признак |
Складывать эти величины в одну кривую — потеря информации, а не упрощение. Время до первого токена растёт от очереди и от длины входа; темп генерации падает, когда на машине стало больше соседей; сквозная задержка может вырасти просто потому, что ответы стали длиннее. Три разные причины, три разные ручки.
Хорошая новость: движок уже всё разделил. vLLM отдаёт наружу гистограммы vllm:time_to_first_token_seconds, vllm:inter_token_latency_seconds и vllm:e2e_request_latency_seconds, а рядом — то, чего в обычном сервисе нет: vllm:request_queue_time_seconds, vllm:num_requests_running и vllm:num_requests_waiting.
Отдельная метрика на время в очереди — не мелочь. Она отделяет то, за что отвечает модель, от того, за что отвечает планировщик и ёмкость. Если на дашборде одна кривая задержки, это решение команды, а не ограничение инструмента: разделение уже сделано за вас.
Хвост живёт в очереди, а не в модели
Инженерная интуиция подсказывает, что задержка растёт, когда не хватает мощности. Это верно лишь наполовину, и вторая половина объясняет большинство неприятных инцидентов.
Классическое приближение для среднего ожидания в очереди — формула Кингмана, опубликованная в 1961 году. Она раскладывает ожидание в произведение трёх множителей: загрузки, вариабельности и времени обслуживания.
ожидание ≈ [ρ / (1 − ρ)] × [(c²прихода + c²обслуживания) / 2] × среднее время обслуживания
Управленческий смысл формулы в том, что ожидание растёт по трём независимым причинам: приход стал более рваным, обслуживание стало более разбросанным, загрузка приблизилась к единице. Докупка мощности работает только с третьим множителем.
У инференса первые два множителя экстремальны, и это его отличает от обычного сервиса. На одном и том же адресе живут запрос с контекстом в несколько сотен токенов и запрос с контекстом в сотни тысяч; ответ бывает в одну строку и на десять экранов. Разброс времени обслуживания измеряется не процентами, а порядками. Поэтому кластер, у которого «загрузка всего 60 процентов», уверенно выдаёт неприемлемый хвост — и добавленный ускоритель этот хвост почти не двигает.
Граница применимости. Формула Кингмана — приближение для одноканальной очереди, и точнее всего она работает вблизи насыщения. Сервер инференса — не одноканальная очередь: он собирает запросы в пакеты, вытесняет их и перепланирует на каждой итерации. Здесь эта формула нужна как инструмент мышления, а не как калькулятор ёмкости.
Ёмкость измеряется памятью, а не числом запросов
Второй источник сюрпризов — единица ёмкости. В обычном сервисе она интуитивна: столько-то одновременных запросов, столько-то потоков. В инференсе ёмкость определяется тем, помещаются ли рядом всех активных запросов. Единица ёмкости — память под контексты, а не число соединений.
Отсюда следствие, которое ломает интуицию: один запрос с очень длинным контекстом может стоить дороже десятка коротких диалогов. И именно он вытесняет соседей. Когда памяти перестаёт хватать, vLLM пишет в лог честную фразу: Sequence group N is preempted by PreemptionMode.RECOMPUTE mode because there is not enough KV cache space.
Вытеснение — механизм устойчивости, а не поломка: система предпочитает снять часть работы, чтобы остальные продолжали. Но у него есть цена. Вытесненный запрос затем считается заново, и его сквозная задержка подскакивает. В версии V1 режимом по умолчанию выбран именно пересчёт, а не выгрузка состояния: в этой архитектуре пересчитать дешевле, чем перекладывать.
Документация перечисляет и ручки: поднять долю памяти под движок, снизить предельное число одновременных последовательностей или число токенов в пакете, растянуть модель на большее число ускорителей по тензорному или конвейерному измерению. Каждая из них двигает ту же границу с разных сторон.
Ручка, которая явно переключает между двумя обещаниями
Отдельно стоит выделить предельное число токенов в пакете. В V1 по умолчанию включено смешивание с генерацией в одном пакете, и этот параметр прямо распределяет машину между двумя обещаниями из первого раздела: меньшие значения улучшают темп генерации, потому что обработка входа реже прерывает генерацию соседей; большие значения улучшают время до первого токена.
Это редкий случай, когда компромисс не спрятан в эвристике, а вынесен в конфигурацию одним числом. Значит, и решение здесь продуктовое: какой из двух порогов вам дороже нарушить.
Четыре способа сломаться под нагрузкой
Отказы инференс-сервиса удобно свести к небольшой таксономии. Она полезна тем, что у каждой строки свой ранний признак — и он виден раньше, чем первая жалоба.
| Отказ | Причина | Что происходит | Ранний признак |
|---|---|---|---|
| Вытеснение по KV-памяти | Контексты не помещаются целиком | Пересчёт вытесненного запроса, скачок сквозной задержки | `vllm:num_preemptions`, `vllm:kv_cache_usage_perc` |
| Накопленная очередь | Приход выше устойчивой ёмкости | Память под ожидающие запросы, рост TTFT без роста нагрузки на GPU | `vllm:num_requests_waiting`, `vllm:request_queue_time_seconds` |
| Усиление ретраями | Клиент повторяет запрос по таймауту | Повторное занятие памяти под весь контекст, каскад | Расхождение числа запросов клиента и сервера |
| Холодный старт реплики | Автомасштабирование добавило под | Минуты до первой полезной работы новой реплики | Время от решения масштабировать до готовности |
Про очередь стоит сказать отдельно, потому что здесь работает старое правило из инженерии надёжности. Очередь — это не бесплатное ожидание: она расходует память и добавляет задержку. В книге Google по эксплуатации приводится простой расчёт: если запрос обрабатывается 100 миллисекунд, а очередь вдесятеро длиннее пула обработчиков, то запрос ждёт секунду до того, как начнётся работа. Рекомендация — держать очередь короткой относительно пула и отклонять запросы рано, а не копить их.
Ретраи заслуживают отдельного абзаца, потому что в инференсе они дороже. Классическая арифметика усиления выглядит так: если сто запросов в секунду отклоняются из-за перегрузки, а клиент повторяет их раз в секунду, нагрузка становится 200, потом 300 запросов в секунду. Лечится это рандомизированной экспоненциальной задержкой и серверным бюджетом ретраев — например, не более шестидесяти повторов в минуту.
Специфика инференса в том, что повтор здесь не просто добавляет запрос в очередь. Он заново занимает память под весь контекст — тот самый ресурс, нехватка которого и вызвала отказ. Ретрай без бюджета в таком сервисе не просто удлиняет очередь: он вытесняет тех, кто уже почти дописал ответ, и превращает деградацию в отказ.
Деградация вместо пятисотки
У обычного сервиса выбор под перегрузкой небогатый: обслужить или отказать. У генеративного сервиса есть третий путь, и он почти всегда лучше: отдать более дешёвый ответ. Между «как обычно» и «ошибка» лежит целая шкала.
- ограничить длину ответа: короче, но вовремя;
- перевести запрос на модель поменьше и честно пометить это в интерфейсе;
- выключить дорогой режим рассуждений там, где он не критичен;
- сузить поиск по документам: меньше найденных фрагментов, быстрее ответ;
- поставить запрос в отложенный контур и вернуть результат позже.
Различие между сбросом нагрузки и деградацией стоит держать в голове явно: первый отбрасывает часть трафика, чтобы сервер выжил, вторая снижает качество работы вместо отказа. И у обоих одна общая проблема: этот код выполняется редко, а значит, к моменту настоящего инцидента он с большой вероятностью не работает. Путь деградации нужно регулярно исполнять — иначе он существует только на схеме.
Второй инструмент — приоритеты. Интерактивный запрос и пакетная обработка не должны стоять в одной очереди на равных, и это уже признано на уровне платформенных стандартов. Расширение Gateway API для инференса в Kubernetes появилось именно потому, что обычная балансировка сюда не подходит: сессии длинные, ресурсоёмкие и частично хранят состояние, а один под держит несколько активных сессий вместе с их кешами в памяти. Маршрутизация по пути запроса или по кругу этого не видит.
Проект вводит понятие пула серверов модели, которым владеет платформа, и отдельный компонент, выбирающий эндпоинт под настроенные цели, — среди сигналов упоминаются состояние префиксного кеша и доступность адаптеров. Анонс опубликован 5 июня 2025 года; на сентябрь 2026 у проекта есть версия API v1 рядом с альфой.
В исследованиях эта же идея идёт дальше: препринт SLOs-Serve распределяет токены под ограничения нескольких SLO и заявляет рост ёмкости на ускоритель в среднем в 2,2 раза на шести сценариях — от суммаризации до вызова инструментов. Это сигнал направления, а не ожидаемый выигрыш: результат получен авторами на собственном наборе сценариев и независимо не воспроизведён.
Платформа: чем масштабировать и почему это долго
Автомасштабирование инференса начинается с выбора метрики, и первый соблазн — самый неудачный. Загрузка ускорителя плохо подходит на роль сигнала: она не измеряет, сколько полезной работы делается, пока ускоритель занят, и потому плохо связана и с задержкой, и с пропускной способностью.
Рабочих сигнала два, и они отвечают на разные вопросы. Размер очереди показывает, что запросы уже ждут: рекомендация — начать с порога в районе трёх-пяти и повышать, пока задержка не выйдет на целевую. Размер пакета показывает, сколько система обрабатывает прямо сейчас, и лучше подходит для низкой задержки; порог подбирается наблюдением: взять максимум под нагрузкой, поставить чуть ниже и снижать до целевой задержки. Здесь же становится понятно, почему больший пакет одновременно повышает пропускную способность и задержку: обработка входа одних запросов прерывает генерацию других.
Правило сработало — и ничего не произошло ещё несколько минут
Дальше начинается то, что отличает эксплуатацию инференса от эксплуатации веб-сервиса. Масштабирование означает новый под, а новый под платит полный холодный старт: скачать образ, поднять среду, загрузить гигабайты весов, скомпилировать графы вычислений. До первой полезной работы проходят минуты — а очередь, ради которой правило сработало, уже сформировала хвост.
Инструменты против этого появились. Проект llm-d описывает подход, при котором модель не загружают заново, а будят: тензоры остаются в оперативной памяти и возвращаются на ускоритель за секунды, минуя загрузку и компиляцию. Проект честно оговаривает, что ускоряет подъём мощности, а не сам инференс, и оценивает накладные расходы резидентных серверов примерно в 2,5 процента памяти хоста.
Числа vLLM для этого режима: на A100 пробуждение занимает 0,26 секунды для Qwen3-0.6B и 0,82 секунды для Phi-3-vision против холодного старта в 37–58 секунд; заявленный диапазон ускорения переключения модели — от 18 до 200 раз. Это замеры проекта на своих моделях и своём железе, и относиться к ним стоит как к заявлению поставщика, а не как к ожидаемому результату на вашем контуре.
Практический вывод из этого раздела не про инструменты. Пока реакция на рост нагрузки измеряется минутами, запас ёмкости — не расточительство, а плата за физику. Тот же сюжет уже разбирался на уровне платформы в целом: сложность не исчезает, она переезжает — из приложения в платформу, а из платформы в того, кто её эксплуатирует. Отдельный разбор про Kubernetes описывает эту же механику для .
Цена запаса против цены нарушения
Всё предыдущее сводится к одному управленческому выбору. Запас ёмкости стоит денег каждый час, независимо от того, пригодился он или нет. Нарушение обещания стоит денег в момент нарушения — и стоимость эта распределена неравномерно: для внутреннего инструмента она близка к раздражению, для сценария в продукте измеряется брошенными сессиями.
решение = (цена часа запаса × часы) против (доля нарушенных запросов × цена одного нарушения)
Соблазн здесь — посчитать только левую часть, потому что она приходит счётом от поставщика. Правая часть требует, чтобы кто-то назвал цену нарушенного обещания, а это разговор не инженерный. Но без него обсуждение ёмкости всегда заканчивается одинаково: «дорого» побеждает «медленно», пока «медленно» не станет заметно снаружи.
Полезно считать не токены, а принятую работу — ту же единицу, что и в разборе экономики AI-разработки. Тогда запас перестаёт быть строкой расходов и становится множителем: он повышает долю запросов, которые дошли до принятого результата.
Честная граница раздела. Это рамка решения, а не расчёт. Подставить в неё чужие числа нельзя: цена часа зависит от контракта, а цена нарушения — от сценария. Раздел останется рамкой до тех пор, пока в нём не появятся собственные замеры.
Чеклист эксплуатации
Собранное в одном месте. Если из статьи нужно унести одну страницу — эту.
- Записать обещание как пару порогов и долю запросов внутри них; отдельно для интерактивных сценариев и для пакетных.
- Развести три величины на дашборде: время до первого токена, темп генерации, сквозную задержку. Не усреднять их в одну кривую.
- Вывести время в очереди отдельной метрикой: это граница между ответственностью модели и ответственностью ёмкости.
- Смотреть на занятость KV-кеша и число вытеснений как на ранний признак: они меняются раньше, чем задержка.
- Ограничить приём на входе: короткая очередь и ранний отказ лучше длинной очереди и позднего таймаута.
- Определить деградацию заранее — длина, модель, режим, глубина поиска — и регулярно исполнять этот путь.
- Ввести бюджет ретраев и экспоненциальную задержку: в инференсе повтор заново занимает память под весь контекст.
- Масштабировать по очереди или по размеру пакета, а не по загрузке ускорителя.
- Заложить в план холодный старт реплики: правило срабатывает мгновенно, мощность появляется через минуты.
- Пересчитывать выбор запаса, когда меняется профиль трафика: длина контекста двигает ёмкость сильнее, чем число пользователей.
Что стоит унести с собой
- 01Обещание продукту — это пара порогов и доля запросов внутри них, а не число токенов в секунду. Индустрия записала такой формат раньше вас: MLPerf оценивает систему по устойчивой нагрузке внутри границ TTFT и TPOT.
- 02Три величины нельзя складывать в одну кривую: время до первого токена, темп генерации и сквозная задержка ломаются по разным причинам и лечатся разными ручками.
- 03Ёмкость инференса измеряется памятью под контексты, а не числом запросов. Один длинный контекст вытесняет соседей, и вытеснение видно отдельной метрикой раньше, чем жалобой.
- 04Ретрай в инференсе дороже, чем в обычном сервисе: он заново занимает память под весь контекст. Поэтому бюджет ретраев и заранее отрепетированная деградация здесь не гигиена, а несущая конструкция.
- 05Запас ёмкости — не расточительство, а плата за то, что реакция на рост измеряется минутами: новая реплика платит полный холодный старт до первой полезной работы.
Документация, стандарты и исследования
Список сгруппирован по роли источника. Пороги бенчмарков и метрики движка проверены по первичным страницам на 5 сентября 2026 года; заявления проектов о собственных решениях помечены в примечаниях.
Пороги и бенчмарки
- MLCommons · Llama 3.1-8B в MLPerf Inference v5.1 — источник порогов: Server — TTFT ≤ 2 с и TPOT ≤ 100 мс, Interactive — 0,5 с и 30 мс; там же перевод в слова в минуту
Движок инференса
- vLLM · Prometheus-метрики — перечень метрик, на которые опирается статья: TTFT, межтокенная задержка, время в очереди, занятость KV-кеша, вытеснения
- vLLM · Optimization and Tuning — вытеснение при нехватке KV-кеша, режим RECOMPUTE по умолчанию в V1, ручки против вытеснения и компромисс при смешивании обработки входа с генерацией
Эксплуатация и перегрузка
- Google SRE · Addressing Cascading Failures — очередь как расход памяти и задержки, сброс нагрузки и деградация, арифметика усиления ретраями и бюджет ретраев
- Kingman's formula (VUT) — приближение среднего ожидания в очереди G/G/1 произведением загрузки, вариабельности и времени обслуживания; Кингман, 1961
Платформа и маршрутизация
- Google Cloud · автомасштабирование инференса на GKE — почему загрузка GPU — плохая метрика, как подбирать порог по очереди и по размеру батча
- Kubernetes · Introducing Gateway API Inference Extension — почему обычная балансировка не подходит длинным частично stateful сессиям; что описывает InferencePool
- Gateway API Inference Extension · документация проекта — состояние на сентябрь 2026: v1 рядом с v1alpha1, сигналы для выбора эндпоинта
- llm-d · Fast Model Actuation — из чего состоит холодный старт реплики и что именно ускоряет FMA; заявление проекта о собственном решении
- vLLM · Zero-Reload Model Switching with Sleep Mode — числа пробуждения против холодного старта на A100 и два уровня сна; заявление проекта, замеры на его моделях
Исследования
- Chen et al. · SLOs-Serve — препринт апреля 2025 о распределении токенов под ограничения SLO; ×2,2 к ёмкости на GPU на собственном наборе сценариев