- BrainTools - https://www.braintools.ru -

Как мы заставили vLLM «лениться» под нагрузкой и спасли Time-to-First-Token

Введение: Почему обычный Rate Limiting не работает для LLM?

Деплой больших языковых моделей (LLM) — это всегда боль [1], когда дело доходит до пиковых нагрузок. В классических web-сервисах при высоких RPS мы просто включаем балансировщик, а если всё горит — жестко режем запросы HTTP 429 Too Many Requests.

Но в мире генеративного AI отбрасывать запросы клиентов очень дорого: пользователь уже подождал, пока загрузится чат, написал длинный промпт, нажал Enter и… получил ошибку [2]. А масштабирование GPU-кластера занимает минуты, которых у нас нет.

В этой статье мы покажем, как подход “Динамической лени” (Dynamic Degradation) позволяет сохранить доступность сервиса при 100% утилизации GPU, не отбрасывая запросы, а заставляя саму модель быть более “краткой”. Для этого мы написали свой open-source шлюз LazyGate [3].


Архитектура LazyGate

Идея проста: мы ставим перед нашим vLLM сервером легковесный ASGI-прокси на базе FastAPI + httpx. Этот прокси-шлюз выполняет одну ключевую задачу — постоянно опрашивает драйверы NVIDIA (или эмулятор в режиме разработки), получая метрику нагрузки на GPU.

Мы определили три состояния кластера:

  1. Normal Load (<75%): Обычный режим. Модель может писать стихи, долго рассуждать и вдаваться в детали (max_tokens: 100%).

  2. Tired Load (75-90%): Система начинает испытывать сложности (очередь continuous batching растет). Прокси “на лету” подкидывает в массив messages системный промпт: “Отвечай кратко”, а max_tokens урезается до 70%.

  3. 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)

Таким образом, для каждого входящего роута получение метрики — это просто O(1) чтение из памяти [4]: 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 [5].

Это “внутренний Агент”, который сам читает конфиг YAML со списком порогов, идет в файл с Pytest-тестами через Abstract Syntax Trees (AST) и ищет, покрыт ли каждый сценарий (Normal, Tired, Lazy). Если он находит “дыру”, скрипт автоматически дописывает код нового теста в файл test_main.py [6] и запускает конвейер заново!


Заключение

Использование промптинга для динамической модификации сложности вычислений (LLM Routing) — намного более гибкий подход, чем жесткий Rate-limiting.

В ближайших планах — добавить в шлюз отслеживание Token Velocity (T/s) вместо прямого пинга NVML, чтобы понимать реальную пропускную способность очередей vLLM.

Код проекта полностью открыт под лицензией для академического и некоммерческого использования на нашем GitHub [3]. Присоединяйтесь!

Автор: Uladzislau_by

Источник [7]


Сайт-источник BrainTools: https://www.braintools.ru

Путь до страницы источника: https://www.braintools.ru/article/29295

URLs in this post:

[1] боль: http://www.braintools.ru/article/9901

[2] ошибку: http://www.braintools.ru/article/4192

[3] LazyGate: https://github.com/malakhovskiy/LazyGate

[4] памяти: http://www.braintools.ru/article/4140

[5] agent.py: http://agent.py

[6] main.py: http://main.py

[7] Источник: https://habr.com/ru/articles/1027288/?utm_source=habrahabr&utm_medium=rss&utm_campaign=1027288

www.BrainTools.ru

Rambler's Top100