Всем привет, меня зовут Сергей Прощаев, и в этой статье я расскажу про реальную архитектуру ИИ-сервисов, которые выдерживают high-load и отвечают за десятки миллисекунд. Я Tech Lead и руководитель направления Java | Kotlin разработки в FinTech & E-commerce, а ещё преподаю на курсах разработки и архитектуры в OTUS.
За моими плечами — несколько проектов, где мы встраивали генеративные модели в прод, и каждый раз одно и то же: на нагрузочном тестировании всё летает, а в проде — латенси скачет, GPU греются, бюджет тает. На недавнем открытом урокекурса «ИИ-архитектор»мы детально разобрали, как проектировать такие системы.
Главный вывод, который мы вынесли с урока: low-latency AI-инференс на high-load невозможно построить в монолите. Нужен изолированный inference‑bundle сcontinuous batching,admission controlи правильным выбором движка — vLLM для старта, SGLang для продакшена с длинным контекстом.
Расскажу, почему это так, на примерах из боевых проектов.
Вход в воронку: почему монолит не работает, а изолированный inference‑bundle — да
Мне как-то попалась задача: стартап хотел сделать AI-чат для техподдержки с latency <50 мс. Взяли готовый опенсорс-монолит: FastAPI + трансформеры + PostgreSQL. На 10 параллельных запросах всё ок. На 50 — латенси улетела за 500 мс, а GPU начал греться как утюг. Знакомо?
Проблема в том, что AI-сервис нельзя проектировать как классический бэкенд. Здесь нет простого «request → DB → response». У нас естьроутинг запросов, инференс, retrieval (поиск векторов), кэширование, наблюдаемость— и каждый слой живёт своей жизнью. Если всё смешать в одном коде, получится каша, которую невозможно масштабировать.
На уроке нам показали подход, который я для себя называю«изолированный inference‑bundle»(не путать с PBC из DDD — термин здесь используется метафорически). Это не микросервис в классическом понимании. Этобизнес‑ориентированный блок, который включает в себя балансировщик (Envoy), движок инференса (vLLM или SGLang), модель, кэш промтов и наблюдаемость. Такой блок решает конкретную задачу и имеет понятную цену для бизнеса.
Почему это важно? Представьте: вы приходите к заказчику и говорите: «Вот синий блок — он отвечает за роутинг и безопасность. Хотите сэкономить? Уберите его, но тогда при атаке ваши GPU начнут майнить криптовалюту». И клиент сам решает, что ему важнее. Так работает взрослая архитектура.
Стек low‑latency: от L7‑роутинга до кэширования префиксов
На уроке лектор (кстати, действующий архитектор из крупной fintech-компании) разложил современный стек по полочкам. Запомнил четыре ключевых слоя.
1. Умный роутинг через Envoy
Мы привыкли к NGINX, но для AI‑трафика я предпочитаю Envoy. Важно сразу оговориться:Envoy не знает про LLM напрямую. У него нет встроенного понимания «галлюцинаций» или «токенов». Однако его штатные L7-механики —outlier detection,circuit breaking, retries, health checks — идеально ложатся на поведение AI‑нод. В отличие от классических бэкендов, здесь каждый запрос может работать десятки секунд, а модель способна внезапно начать тормозить на длинных контекстах. Envoy позволяет гибко настроить таймауты, пороги для вывода из ротации и gracefully переключать трафик между моделями без перезапуска.
Мой вариант, который я обычно использую: делаем отдельный роутинговый слой на Envoy, а рядом — лёгкую модель-судью (например, Gemma‑2B), которая оценивает качество ответа.Здесь важно понимать: Envoy сам не получает вердикт напрямую. Фактически это реализуется через внешний orchestration-слой — ответ сначала проходит черезjudge-модель, и только после этого orchestration-слой возвращает его клиенту или инициирует retry на другую модель.Envoy остаётся исполнительным механизмом, а логика оценки качества лежит выше.
2. Inference‑движки: vLLM против SGLang
Вот где начинается магия.vLLMиспользуетPage Attention— нарезает видеопамять на страницы по токенам, решая проблему OOM. Настраивается относительно просто.SGLang— более гибкий, поддерживает Flash Attention 3, RadixAttention для кэширования префиксов. Но и сложнее.
Помню случай: для RAG-бота с контекстом 32K токенов мы выбрали vLLM — быстро стартанули, получили latency 120 мс. Через месяц потребовалось расширить контекст до 128K, и vLLM начал тормозить. Перешли на SGLang с тензорным параллелизмом, latency упала до 90 мс, но потратили неделю на настройку.
Как выбирать на самом деле (без упрощений)?
- Batch size sensitivity— vLLM стабильнее на мелких батчах (1–8 запросов), SGLang раскрывается при 16+.
- Повторяемость префиксов— RadixAttention даёт выигрыш только при высокой доле общих префиксов (60%+). Не для всех сценариев.
- KV‑cachefragmentation— Page Attention из vLLM эффективнее при неравномерной длине запросов.
- GPU memory pressure— SGLang потребляет больше памяти на старте, но может быть эффективнее при длинных контекстах.
- Continuous batching— ни один современный движок не обходится без этой техники (она же in‑flight batching). Суть: батч не фиксируется на старте, а дозапрашивается токен за токеном — новые запросы могут вставать в текущий батч на границах итераций. Нагрузочные тесты показывают, что continuous batching способен повысить throughput в 3–5 раз на высоких RPS.Без него low‑latency при high‑load достигается кратно большими GPU‑ресурсами(читай — в разы дороже).
Что выбрать?Если вы на стадии Proof of Concept — vLLM. Если это прод с высокими требованиями к скорости и длинному контексту — SGLang, но заложите время на инженера-оптимизатора.
3. Prompt Registry в Postgres 18
Как вы храните промты? В коде? В переменных окружения? Я видел проекты, где системный промт пересобирали в Docker-образе при каждом изменении слова — это ад.
Правильный способ —Prompt Store. На уроке рекомендовали Postgres 18 (да, обычный постгрес). В нём появились:
- Векторный поиск (pgvector)
- Хранение промтов с версионированием (как Git)
- Jinja2-шаблоны для динамической подстановки
- Загрузка в память при старте приложения (микросекунды)
Важный production-нюанс:промты подгружаются в память воркера при старте и не читаются из БД на каждый запрос. Это даёт микросекундный доступ. Обновление требует перезагрузки воркера (или динамической перезагрузки, если реализован механизм versioned prompts). Но здесь важный нюанс.
Современный pgvector стал быстрее, нопрактически комфортный предел в реальном production — единицы миллионов векторовбез агрессивного тюнинга и партиционирования. Десятки миллионов уже требуют серьёзной инженерной работы. 50 млн векторов — это скорее потолок возможностей при идеальных условиях. Если у вас больше — смотрите в сторону специализированных векторных баз вроде Vespa (от Yahoo) или Qdrant.
Мы внедрили такую штуку в одном проекте: теперь продакт-менеджер может править промты через админку, а система подхватывает новую версию без деплоя. Выручает на раз.
4. RadixAttention, KV‑cache eviction и автоскейлинг
SGLang умеет превращать повторяющиеся системные промты в древовидный кэш —RadixAttention. Экономия сильно зависит от сценария. В наших тестах на чат-ботах с длинными системными инструкциямиTTFTснизился на 30–40%. Но если у вас короткие уникальные запросы без общих префиксов — выгоды почти не будет.
Про KV‑cache eviction.Когда контекст большой (128K+), а GPU‑память ограничена, рано или поздно встаёт вопрос: что вытеснять из кэша? У vLLM есть Automatic Prefix Caching (APC) на базе LRU — блоки вытесняются от хвоста. У SGLang — RadixAttention с древовидной структурой, позволяющей точечно вытеснять ветки.
В современных исследованиях для long‑context существуют и более продвинутые эвикшн‑стратегии — H2O, KeyDiff, SAGE‑KV, способные сжимать кэш на 50–87% с минимальной потерей качества.Правда, пока это в основном research-подходы, но направление крайне показательное.Если вы планируете контекст за 128K токенов, без проработки eviction не обойтись — хотя бы на уровне LRU.
Автоскейлинг по KEDA— классика: слушаем метрику request waiting из Prometheus, и как только очередь растёт, KEDA поднимает новые GPU-ноды. Только не забудьте ограничить максимальное количество — иначе счёт за облако улетит в космос.
5. Admission control: забытый, но критичный слой
Наконец, ещё один обязательный элемент high‑load AI‑системы —admission control(контроль приёма запросов). Классический подход при перегрузке просто ставит очередь — это убивает p99 и может вызвать каскадное падение. Более продвинутый — оценивать перед приёмкой, впишется ли новый запрос в SLA по latency.В исследовательской литературе описаны системы вроде SCORPIO, которые используют адаптивный admission control на основе виртуального размера батча.Однако пока это в основном исследовательские и экспериментальные реализации. На уровне практической архитектуры можно резать трафик на L7‑шлюзе через лимиты на количество активных инференс‑слотов — это грубее, но работает.
В одном из наших проектов мы добавили простой admission control: перед отправкой запроса в очередь инференса шедулер проверял текущую загрузку GPU и ожидаемую длину контекста. Если прогнозируемая latency превышала порог, запрос получал HTTP 429 с просьбой повторить позже. Клиент должен реализовать retry с exponential backoff, иначе 429 только усугубит ситуацию. Это спасло нас от эффекта «снежного кома» в часы пик.
Архитектурные схемы
Давайте рассмотрим схему на рисунке 1.
Схема показывает, как запрос проходит через Envoy, проверку admission control, шедулер, затемBatch Manager(где реализован continuous batching), движок инференса, кэш и модель-судью. KV Cache Store на схеме — логический слой для кэша ключей и значений; физически он находится в GPU-памяти самого inference-движка. Петля обратной связи от судьи к Envoy (через orchestration-слой) позволяет переключать трафик при низком качестве.
Главная мысль:каждый компонент — самостоятельный слой, а continuous batching вынесен в отдельный управляющий блок, потому что без него low‑latency при high‑load требует кратно больше GPU‑ресурсов.
На следующей схеме (рисунок 2) рассматриваетсяпринятие решения о приёме запроса.
На практике admission control спасает от эффекта «снежного кома»: вместо бесконечного накопления запросов в очереди система честно говорит «сейчас не могу, попробуй позже». В нашем проекте это снизило p99 latency на 40% в часы пик.
Практический пример: как мы выбирали движок для AI‑кодинга
Расскажу реальную историю. В прошлом году наша команда делала внутренний AI-ассистент для разработчиков (автокомплит кода). Требования: latency <100 мс, контекст до 8K токенов, 500 RPS в час пик.
Сначала взялиvLLMс моделью Qwen-32B. На тестах — 80 мс, отлично. В проде, когда пришли реальные запросы с разной длиной контекста, p95 улетел в 250 мс. Почему? vLLM плохо кэширует префиксы, а у нас системный промт был длинный (инструкция для кодинга). Добавили continuous batching — стало лучше, но не кардинально.
Перешли наSGLangс RadixAttention. Настроили тензорный параллелизм на двух H100. Результат: p95 = 95 мс, средний — 70 мс. Дополнительно поставиливалидатор— модель-судью (Gemma‑2B), которая прогоняла ответы через быстрый скоринг и отклоняла галлюцинации. Это добавило +20 мс, но качество выросло на 30%. Также внедрили admission control: если очередь инференса превышала 50 запросов, новые получали 429 и клиент делал retry с exponential backoff.
Как и предполагал, без инженера-оптимизатора не обошлось: парень из нашей команды потратил две недели на изучение SGLang и Flash Attention. Но результат окупился через месяц — снижение затрат на GPU на 40% (за счёт эффективного кэширования).
Вопросы слушателей и мои комментарии
Во время вебинара задавали много вопросов. Отмечу два, которые я сам часто слышу.
Вопрос:«Какую модель брать для RAG, если данных 10 млн документов?»
Мой ответ:Смотрите на эмбедеры, а не на генеративную модель. Я предпочитаюBGE-large(тяжёлая, качественная) илиGTE-Qwen(лёгкая, быстрая). Для хранения векторов — pgvector до единиц миллионов (с партиционированием — до 10-15). Если больше — Vespa от Yahoo. Про неё мало кто знает, но она реально мощная, держит сотни миллионов векторов с низкой задержкой.
Вопрос:«Почему некоторые модели со временем начинают странно отвечать?»
Ведущий привёл отличную метафору: представьте пирог, который порезали на дольки (экспертные области). Некоторые модели (особенно закрытые или агрессивно дообученные под бенчмарки) могут со временем «съезжать» — это общая проблемаModel Hemorrhageв литературе. В нашем опыте Qwen показал себя стабильнее ряда альтернатив, но это частный опыт, а не универсальный факт. Многое зависит от версии модели, домена и качества входных данных. Не гонитесь за свежими бенчмарками, тестируйте на своих данных хотя бы две недели.
Что дальше?
Если вы дочитали до сюда, значит, тема вам действительно интересна. И вы уже понимаете, что архитектура AI-сервисов — это не магия, а набор конкретных инженерных решений. Но одного конспекта мало.
Накурсе «ИИ-архитектор»мы разбираем эти паттерны на практике: вы соберёте свой low‑latency пайплайн, настроите continuous batching и admission control, сравните vLLM с SGLang на реальной нагрузке. А ещё получите обратную связь от действующих архитекторов, которые строят такие системы каждый день.
Оставлю ссылку наоткрытый урок курса «ИИ-архитектор», конспект которого мы сегодня разобрали.
Подписывайтесь на нашканал в MAX— там публикуем больше открытых уроков, подборок по IT-направлениям и полезных материалов.