Введение: Почему обычный Rate Limiting не работает для LLM?
Деплой больших языковых моделей (LLM) — это всегда боль, когда дело доходит до пиковых нагрузок. В классических web-сервисах при высоких RPS мы просто включаем балансировщик, а если всё горит — жестко режем запросы HTTP 429 Too Many Requests.
Но в мире генеративного AI отбрасывать запросы клиентов очень дорого: пользователь уже подождал, пока загрузится чат, написал длинный промпт, нажал Enter и… получил ошибку. А масштабирование GPU-кластера занимает минуты, которых у нас нет.
В этой статье мы покажем, как подход “Динамической лени” (Dynamic Degradation) позволяет сохранить доступность сервиса при 100% утилизации GPU, не отбрасывая запросы, а заставляя саму модель быть более “краткой”. Для этого мы написали свой open-source шлюз LazyGate.
Архитектура LazyGate
Идея проста: мы ставим перед нашим vLLM сервером легковесный ASGI-прокси на базе FastAPI + httpx. Этот прокси-шлюз выполняет одну ключевую задачу — постоянно опрашивает драйверы NVIDIA (или эмулятор в режиме разработки), получая метрику нагрузки на GPU.
Мы определили три состояния кластера:
Normal Load (<75%): Обычный режим. Модель может писать стихи, долго рассуждать и вдаваться в детали (max_tokens: 100%).
Tired Load (75-90%): Система начинает испытывать сложности (очередь continuous batching растет). Прокси “на лету” подкидывает в массив
messagesсистемный промпт: “Отвечай кратко”, аmax_tokensурезается до 70%.Extremely Lazy (>90%): Аварийный режим. Чтобы максимально быстро очистить очередь KV-кэша, шлюз инжектит жесткий промпт: “Provide extremely short answers only. No explanations. Code only if asked.” и режет токены до 30%.
Zero-Blocking поллинг NVML
Читать нагрузку видеокарты при каждом HTTP запросе через интерфейс Python pynvml — верная смерть для Event Loop’а. Поэтому мы вынесли это в фоновую asyncio задачу.
# Из нашего hardware.py async def _monitor_loop(self): while True: # Чтение реальной нагрузки (либо синусоиды для mock режима) new_load = self._fetch_real_load() # EMA (Экспоненциальное скользящее среднее) чтобы побороть "дребезг" на порогах alpha = 0.3 self._current_load = (new_load * alpha) + (self._current_load * (1 - alpha)) await asyncio.sleep(self.update_interval)
Таким образом, для каждого входящего роута получение метрики — это просто чтение из памяти:
monitor.current_load.
Проблема со стримингом и SSE
vLLM используется в основном со стримингом токенов (stream=True). Если бы наш прокси буферизировал ответ, мы бы убили главную фичу — Time-to-First-Token (TTFT).
Именно поэтому мы используем потоковую передачу данных через StreamingResponse:
# main.py if is_stream: async def stream_generator(): async with http_client.stream("POST", url, json=body, headers=proxy_headers) as response: async for chunk in response.aiter_bytes(): yield chunk return StreamingResponse(stream_generator(), media_type="text/event-stream")
AI как тестировщик своей же “Лени” (Автономные тесты)
Мы не хотели вручную переписывать Pytest-тесты каждый раз, когда добавляем новый профиль нагрузки в config.yaml. Так как в разработке мы применяем AI-инструменты, мы написали скрипт tester_agent.py.
Это “внутренний Агент”, который сам читает конфиг YAML со списком порогов, идет в файл с Pytest-тестами через Abstract Syntax Trees (AST) и ищет, покрыт ли каждый сценарий (Normal, Tired, Lazy). Если он находит “дыру”, скрипт автоматически дописывает код нового теста в файл test_main.py и запускает конвейер заново!
Заключение
Использование промптинга для динамической модификации сложности вычислений (LLM Routing) — намного более гибкий подход, чем жесткий Rate-limiting.
В ближайших планах — добавить в шлюз отслеживание Token Velocity () вместо прямого пинга NVML, чтобы понимать реальную пропускную способность очередей vLLM.
Код проекта полностью открыт под лицензией для академического и некоммерческого использования на нашем GitHub. Присоединяйтесь!
Комментарии (5)

neodavinchi
24.04.2026 04:32Деградировать качество сервиса в угоду доступности - это нехорошее решение с точки зрения User Experience.
Мой опыт:
Когда я получаю явно глупые ответы - тут же перестаю общаться с llm, а доверие к сервису сильно падает.
Была история с Claude Code в августе 2025 - Reddit вопил о внезапно глупевших моделях, посты "я свалил с клода" плодились каждый день, и даже появлялись проекты "измерь уровень глупости своей модели прежде чем кодить". Клод тогда признал проблему и (вроде) исправился.
Быть может мой опыт - исключение, и у вас есть метрики, которые показывают что долгосрочное удовлетворение пользователей (в случае поглупевшей модели) падает не так сильно, как в случае долгого ожидания ответа?
Uladzislau_by Автор
24.04.2026 04:32поглупевшая, и дающая ту же информацию в более краткой лаконичной форме это все таки разные вещи. модель так же процесс тот же просто получает указание ёмко описывать суть с сохранением смысла
sepulkary
А вы информируете клиента о том, что модель деградирует или с его точки зрения это выглядит как лотерея?
Может быть, лучше использовать подход в стиле Kaggle - “Все TPU сейчас заняты, вы #4 в очереди, подождите”.
Uladzislau_by Автор
Вы забываете что клиент не всегда живой человек, это могут быть системы автоматического мониторинга, умного дома и т.п, вы бы предпочли получить сообщение от секюрити системы к себе в телеграм что в охраняемом периметре замечена активность вы 12 в очереди ожидайте, и через некоторое время - "на камерах рыжий код породы дворовой гонется за серой мышью на скорости 6км/ч, судя по движению крон деревьев наблюдается порывистый ветер", либо сразу - "на камере бегущий кот"?
sepulkary
Я понимаю, о чём вы говорите. Но кем бы я не был - человеком, агентом или системой автоматического управления, я точно не ожидаю, что в мой запрос будут добавлены “оглупляющие” инструкции, если, конечно, начальный трейд-офф не предусматривал такого компромисса.
Хотя бы пришивайте к ответу метаинформацию, индицирующую степень деградации, чтобы инициатор запроса мог анализировать полученные данные “с открытыми глазами”.