Скорость ответа LLM зависит не только от производительности GPU и скорости генерации токенов. При общей FIFO-очереди несколько длинных запросов одного клиента могут занять доступные слоты инференса, а короткий запрос интерактивного ассистента будет ждать их завершения. Пользователь увидит медленный ответ, хотя GPU продолжает работать с высокой загрузкой.
Практическое решение состоит из нескольких уровней: отдельные очереди для пользователей или классов задач, round-robin-планировщик на API-шлюзе, ограничение длины очереди бэкенда и адаптивное снижение скорости подачи при росте time-per-output-token. Приоритетное планирование в vLLM может дополнить эту схему, но оно работает внутри конкретного движка, недоступно в TGI и само по себе не ограничивает поток новых запросов.
В результате интерактивные ассистенты получают предсказуемое время реакции, а батч-генерация, код-ревью и другие фоновые задачи продолжают выполняться с контролируемой задержкой.
Почему FIFO-очередь vLLM и TGI блокирует пользователей
FIFO, или first in, first out, ставит запросы в общую очередь в порядке поступления. Такой порядок легко понять и обслуживать, но он не учитывает длину контекста, ожидаемый объем ответа, класс задачи и клиента, который отправил работу.
Для одного пользователя FIFO может выглядеть справедливо. Для общей LLM-системы с несколькими клиентами оно быстро создает перекос: массовый поток длинных запросов получает столько же права на постановку работы, сколько и короткий интерактивный диалог.
Как тяжелый запрос создает эффект блокировки
Представим API-шлюз перед vLLM или TGI. Клиент запускает код-ревью и отправляет десятки запросов с большим контекстом. Они попадают в общую очередь. Бэкенд берет ограниченное число задач в работу, а новые запросы добавляет за уже принятыми.
Через несколько секунд другой пользователь отправляет короткий вопрос ассистенту. Его запрос может потребовать гораздо меньше токенов, но порядок FIFO ставит его после старых задач. Пока длинные запросы потребляют вычислительные ресурсы, короткий диалог ждет.
Здесь нужно разделять две задержки:
- Время ожидания в очереди, период от приема запроса до начала его обработки.
- Время генерации, период от старта обработки до получения полного ответа или нужного числа токенов.
Увеличение второй величины не всегда означает проблему очереди. Но даже быстрая генерация не спасает пользователя, если первая задержка растет из-за потока старых задач.
Почему высокий throughput не гарантирует быстрый ответ
Throughput показывает, сколько токенов или запросов система обрабатывает за единицу времени. Latency показывает, сколько ждет конкретный запрос. Эти показатели могут двигаться в разные стороны.
Плотная загрузка GPU способна поддерживать высокий throughput: планировщик эффективно объединяет запросы и почти не оставляет вычислительные блоки без работы. Одновременно интерактивный запрос может долго ждать свободного места. Для пакетной обработки это приемлемо. Для чата, агента или оператора поддержки такая задержка сразу заметна.
Поэтому оценивать систему нужно по классам нагрузки. Отдельно измеряйте время до начала обработки и время до первого токена для интерактивных запросов, а для фоновых задач учитывайте пропускную способность и долю успешно завершенных заданий.
Как работает fair scheduling LLM API сервер
Fair scheduling на уровне API-сервера переносит решение о допуске запроса ближе к входной точке. Шлюз принимает запрос, определяет клиента или класс задачи, помещает работу в соответствующую очередь и решает, когда отправить ее в vLLM или TGI.
Это защищает бэкенд от ситуации, когда один источник непрерывно поставляет работу быстрее, чем остальные получают возможность попасть в обработку.
Отдельная очередь для каждого пользователя или клиента
Ключом очереди может быть пользователь, API-ключ, tenant, приложение или класс нагрузки. Для каждого запроса полезно хранить:
- идентификатор клиента;
- приоритет;
- время постановки в очередь;
- тип задачи, например чат, код-ревью или батч-генерация;
- статус и число попыток отправки.
Такая изоляция делает поток предсказуемым. Один пользователь может накопить очередь, но его новые задачи перестанут автоматически вытеснять запросы остальных клиентов.
Отдельные очереди не добавляют GPU и не увеличивают производительность модели. Они управляют доступом к общему ресурсу. При заполнении системы шлюзу все равно нужны лимиты на число активных запросов, длину очередей и время ожидания.
Round-robin планировщик LLM-запросов
Базовый round-robin проходит по непустым очередям по кругу. На каждом шаге он берет один запрос из очереди клиента A, затем один запрос из очереди клиента B, после чего возвращается к A.
while есть_свободный_слот:
очередь = следующая_непустая_очередь()
запрос = очередь.pop()
отправить_в_backend(запрос)
Если у клиента A накопилось 100 задач, а у клиента B одна, B получит шанс на обработку в ближайшем цикле. Общая FIFO-очередь такого свойства не гарантирует.
Равное обслуживание подходит не каждому продукту. Планировщик может использовать взвешенный round-robin: интерактивному классу назначается больше доля слотов, а фоновому классу сохраняется минимальная гарантированная доля. Вес должен отражать SLA и стоимость ожидания, а не только количество запросов.
Что считать справедливостью в реальной системе
Справедливость можно определить несколькими способами:
- Равное число запусков для каждого клиента. Подходит при похожей длительности запросов.
- Равная доля времени GPU. Лучше учитывает разницу между короткими и длинными задачами, но требует более точного учета нагрузки.
- Приоритет latency. Интерактивные запросы получают быстрый доступ, а фоновые задачи ждут свободной емкости.
Для смешанной системы обычно нужен компромисс. Чат получает меньшую задержку, батч-генерация получает пропускную способность, а каждый класс сохраняет квоту, чтобы низкоприоритетные задачи не голодали при постоянном потоке срочных запросов.
Ограничение очереди бэкенда через Prometheus-метрики
Хороший планировщик на шлюзе теряет смысл, если он бесконтрольно отправляет запросы в очередь vLLM или TGI. Внешняя очередь просто перемещает проблему внутрь бэкенда. Пользователь по-прежнему ждет, только причина задержки становится менее заметной.
Prometheus можно использовать как источник наблюдаемого состояния. Он помогает связать нагрузку, задержки и решение о приеме новой работы.
Какие показатели отслеживать
Минимальный набор метрик включает:
- длину очереди бэкенда;
- число выполняющихся запросов;
- время ожидания до начала обработки;
- время до первого токена;
- длительность генерации;
time-per-output-token, то есть время генерации одного выходного токена;- число отклоненных, отложенных и отмененных запросов.
При наличии технической возможности добавляйте разрезы по пользователям, tenant, приложениям и классам задач. Среднее значение по всей системе может скрыть проблему конкретного клиента: его очередь растет, а общий график выглядит стабильным.
При настройке мониторинга полезно учитывать рекомендации из runbook по production-инференсу LLM на Kubernetes, где очереди и Prometheus рассматриваются как часть общей схемы эксплуатации GPU-сервисов.
Как связать метрику очереди с решением шлюза
Шлюз может применять несколько действий:
- принимать запрос во внутреннюю очередь, пока бэкенд сохраняет запас емкости;
- замедлять подачу новых задач при росте очереди и времени генерации;
- возвращать временную ошибку, если запрос не укладывается в допустимое ожидание;
- отправлять работу в отдельный класс или другой доступный бэкенд;
- переносить фоновые задачи на более поздний период.
Порог нельзя назначать универсальным числом. Он зависит от модели, длины контекста, числа GPU, настроек батчинга и профиля запросов. Настройка должна опираться на измерения: при каком состоянии очереди начинает расти время до первого токена и когда time-per-output-token устойчиво ухудшается.
Prometheus наблюдает систему, но не заменяет планировщик. Логику backpressure должен выполнять шлюз или отдельный контроллер, который умеет приостановить отправку, поставить запрос на повтор или вернуть клиенту понятный статус.
Почему ограниченная очередь лучше бесконечного ожидания
Бесконечная очередь создает ложное ощущение надежности. Клиент получает подтверждение приема, но ответ может застрять за потоком старых задач на минуты. Для интерактивного продукта такой сценарий часто хуже явного отказа.
При ограниченной очереди клиент получает предсказуемую политику: повторить запрос с backoff, показать пользователю состояние перегрузки или перенести фоновую работу. Система сохраняет контроль над памятью, количеством активных задач и временем ответа.
Адаптивная подача запросов по time-per-output-token
time-per-output-token показывает, сколько времени инференс тратит на генерацию одного выходного токена. Рост метрики часто сопровождает усиление конкуренции за GPU или память. Если шлюз продолжает подавать работу с прежней скоростью, очередь увеличивается еще быстрее.
Как рост времени токена сигнализирует о перегрузке
Типичная цепочка выглядит так: больше запросов одновременно потребляют вычислительные ресурсы, генерация токена замедляется, ответы дольше занимают активные слоты, новые запросы накапливаются в очереди. В итоге ухудшается и время до первого токена, и полное время ответа.
Одной метрики недостаточно. Краткий всплеск time-per-output-token может возникнуть из-за отдельного длинного запроса. Надежнее анализировать показатель вместе с длиной очереди, числом активных запросов и временем ожидания.
Политика снижения скорости отправки
Шлюз может менять лимит подачи ступенчато или плавно. При устойчивом росте времени токена он уменьшает число новых запросов за интервал. После стабилизации метрик лимит постепенно возвращается к рабочему уровню.
Интерактивному классу обычно нужен небольшой лимит параллельных задач и быстрый допуск свободных запросов. Фоновому классу можно назначить более низкую скорость, очередь с ограниченным размером и отложенный повтор.
Слишком агрессивное ограничение снижает загрузку GPU и throughput. Цель backpressure состоит в том, чтобы убрать накопление и сохранить приемлемую latency, а не остановить инференс при первом росте метрики.
Защита от колебаний и резких переключений
Контроллер не должен менять лимит после каждого единичного всплеска. Для устойчивого поведения нужны:
- сглаживание
time-per-output-tokenи длины очереди; - минимальный интервал между изменениями лимита;
- разные условия для замедления и восстановления;
- минимальное время, в течение которого перегрузка должна сохраняться;
- отдельные правила для интерактивного и фонового классов.
Конкретные коэффициенты зависят от модели, длины контекста, числа GPU и профиля нагрузки. Их нужно подбирать по журналам и нагрузочным прогонам конкретной системы.
Собственный планировщик на шлюзе или приоритеты в vLLM
Оба подхода решают разные уровни задачи. Приоритетное планирование в vLLM помогает выбрать более важный запрос среди уже поступивших работ. Планировщик на API-шлюзе контролирует, какие запросы вообще попадут в бэкенд и с какой скоростью.
Что решает приоритетное планирование в vLLM
Встроенный механизм приоритетов позволяет учитывать важность задач внутри vLLM. Срочный запрос может получить преимущество перед менее важной работой, если обе задачи уже доступны внутреннему планировщику.
Это удобно для инфраструктуры, которая целиком работает на vLLM и не требует сложной логики распределения клиентов. Но функция не заменяет per-user очереди, маршрутизацию, квоты и rate limit. Если один клиент успевает отправить большой поток запросов, он уже мог занять очередь до того, как внутренний приоритет повлияет на выбор следующей задачи.
Ограничения подхода vLLM для смешанной инфраструктуры
Приоритетное планирование vLLM нельзя напрямую перенести на TGI: в рассматриваемой схеме аналогичная функция там недоступна. Единый API-шлюз сохраняет одинаковую политику для vLLM и TGI, но требует собственной логики очередей, мониторинга, отмены запросов и обработки отказов.
Приоритеты внутри одного движка и правила на шлюзе можно сочетать. Шлюз разделяет клиентов и классы, а vLLM использует приоритет как дополнительный критерий внутри допущенной работы.
Когда оправдан планировщик на API-шлюзе
Внешний планировщик оправдан, если системе нужны:
- отдельные очереди пользователей, API-ключей или tenant;
- round-robin или взвешенное распределение;
- единые правила для vLLM и TGI;
- адаптивный rate limit;
- ограничение длины очереди до отправки в бэкенд;
- разделение интерактивной и фоновой нагрузки.
Если используется только vLLM, а задача ограничивается выбором важного запроса внутри движка, встроенных приоритетов может быть достаточно. Их все равно стоит сочетать с метриками и верхним лимитом входящего потока.
Интерактивные и фоновые задачи в одной LLM-системе
Смешанная нагрузка требует разных правил обслуживания. Запрос ассистента и пакет из сотен задач код-ревью могут использовать одну модель, но предъявляют разные требования к задержке.
Интерактивный класс: короткий путь к ответу
Интерактивные запросы стоит направлять в отдельную очередь с высоким приоритетом. Шлюз ограничивает число одновременно отправленных задач и оставляет запас емкости для новых диалогов.
К этому классу относятся чат, операторская подсказка, запрос агента в пользовательском сценарии и другие операции, где человек или внешний процесс ждет быстрый ответ. Главные показатели: время ожидания, время до первого токена и доля запросов, завершившихся в заданном SLA.
Фоновый класс: контролируемая уступка ресурсов
Батч-генерация, массовая обработка документов и код-ревью могут работать с низким приоритетом. Для них задаются собственная очередь, ограничение скорости и возможность отложенного запуска.
Фоновая очередь должна иметь ясную политику повторной постановки. После временного отказа задача либо возвращается с увеличенной задержкой, либо переводится в состояние ожидания. Бесконечные автоматические повторы способны снова заполнить очередь и усилить перегрузку.
Приоритеты без голодания низкоприоритетных задач
Постоянный поток интерактивных запросов может полностью вытеснить фоновые задачи. Для защиты от голодания используйте квоты, взвешенный round-robin или гарантированную минимальную долю обработки для фонового класса.
Размер этой доли зависит от SLA и стоимости простоя. Если код-ревью можно задержать на несколько минут, фоновой очереди подойдет небольшая квота. Если батч-генерация связана с жестким сроком, ей потребуется больше гарантированной емкости.
Подход к разделению классов хорошо сочетается с архитектурой LLM gateway, где единая точка доступа управляет политиками, безопасностью и маршрутизацией запросов. Практический разбор такой архитектуры опубликован в материале об управляемых AI-агентах и LLM gateway.
Практическая схема внедрения и контроль результата
Рабочую схему можно собрать поэтапно. Каждый этап должен менять наблюдаемое поведение системы, иначе планировщик превращается в дополнительный слой кода без измеримого эффекта.
Минимальная архитектура шлюза
- Входной API принимает запрос и определяет пользователя, API-ключ, приложение или tenant.
- Маршрутизатор назначает класс задачи: интерактивный, фоновый или отдельный специализированный профиль.
- Менеджер очередей помещает запрос в per-user или per-class очередь и хранит его статус.
- Планировщик выбирает следующую работу через round-robin или взвешенную политику.
- Контроллер проверяет лимит активных запросов и состояние очереди бэкенда.
- Адаптер отправляет запрос в vLLM или TGI и обрабатывает ответ, отмену и временную ошибку.
- Слой метрик публикует длину очередей, время ожидания, время до первого токена,
time-per-output-tokenи результаты завершения.
Сначала задайте фиксированные лимиты и прозрачные правила отказа. Затем добавляйте адаптивное управление, когда появится история метрик и станет понятен профиль нагрузки.
Какие результаты считать улучшением
Сравнивайте систему до и после изменений отдельно для каждого класса:
- время ожидания до начала обработки;
- время до первого токена;
time-per-output-token;- полное время ответа;
- длина очереди и ее перцентили;
- число активных запросов;
- доля отклоненных, отложенных и отмененных задач;
- throughput по токенам и запросам.
Для интерактивного ассистента улучшением будет снижение времени до первого токена и уменьшение разброса задержки. Для батч-генерации важнее стабильный throughput и отсутствие бесконтрольного роста очереди. Высокая загрузка GPU сама по себе не подтверждает улучшение пользовательского опыта.
Итоговые рекомендации и ограничения
- Определите классы задач и идентификаторы клиентов.
- Создайте отдельные очереди для пользователей, tenant или классов нагрузки.
- Используйте round-robin или взвешенный выбор вместо общей FIFO-очереди.
- Ограничьте число запросов, отправляемых в vLLM или TGI.
- Собирайте через Prometheus длину очередей, время ожидания и скорость генерации токенов.
- Снижайте темп подачи при устойчивом росте
time-per-output-token. - Сравнивайте latency и throughput отдельно для интерактивных и фоновых сценариев.
Fair scheduling не увеличивает вычислительную мощность модели. Он распределяет ограниченный GPU-ресурс так, чтобы тяжелый клиент не получал постоянный приоритет по факту более ранней или массовой отправки запросов.
Приоритеты vLLM полезны как внутренний механизм выбора работы. Внешний планировщик нужен, когда требуется управлять очередями пользователей, темпом подачи, backpressure и несколькими движками, включая TGI. Для реальной системы эти уровни дополняют друг друга: шлюз защищает входной поток, а инференсный бэкенд эффективно обрабатывает допущенные запросы.