Обязательно

High-Load Serving Patterns

Async APIs, queues, streaming, cancellation, continuous batching, GPU scheduling, autoscaling and graceful degradation.

Время изучения: 38 мин

Нагруженная выдача модели: когда модель стала сервисом

В ноутбуке модель выглядит просто: вызвал функцию, получил ответ. В продукте всё сложнее: запросы приходят неравномерно, пользователи не хотят ждать, GPU дорогие, часть запросов длиннее других, а при перегрузе сервис не должен развалиться. Поэтому выдача модели как сервиса - это не только “ускорить модель”, а правильно организовать очередь, маршрутизацию, приоритеты, батчинг, отмену запросов и безопасное обновление версий модели.

Главная идея блока: хороший ML-сервис оптимизируют не под одну красивую цифру, а под баланс. Можно сильнее загрузить GPU, но ухудшить ожидание пользователя. Можно объединять больше запросов в батч, но увеличить задержку. Можно включить повторы при ошибках, но создать вторую волну нагрузки. Поэтому сначала спрашиваем: кому отвечаем, за какое время, сколько стоит ошибка и что делаем, если система перегружена.

Задержка: важен хвост, а не только среднее

Среднее время ответа часто обманывает. Представь, что 90 запросов отвечают за 100 мс, а 10 запросов ждут по 5 секунд. Среднее ещё может выглядеть терпимо, но реальные пользователи регулярно видят зависания и таймауты. Поэтому в нагруженных сервисах смотрят не только среднее, но и “хвосты” распределения: p95 и p99, то есть насколько медленны самые неприятные 5% или 1% запросов.

МетрикаПростыми словамиЛовушка
p50 задержкаМедиана: половина запросов быстрее, половина медленнее.Может быть хорошей, даже если часть пользователей регулярно страдает.
p95 / p99 задержкаСамые медленные 5% или 1% запросов.Именно здесь видны очереди, перегруз, длинные запросы и неудачные реплики.
Пропускная способностьСколько запросов или токенов система обрабатывает в секунду.Большая пропускная способность не гарантирует быстрый ответ конкретному пользователю.
Глубина очередиСколько работы уже накопилось перед обработчиками.Если очередь растёт, масштабирование может сработать слишком поздно.
Таймауты и отменыСколько запросов не дождалось результата.Повторные запросы без ограничений могут усилить перегруз.
Цена запросаСколько стоит один ответ при текущей загрузке и железе.Самый дешёвый режим может быть плохим для интерактивного продукта.
Если смотреть только одну метрику, легко “улучшить” сервис так, что пользователям станет хуже.

Как живёт один запрос

Запрос обычно не идёт сразу в модель. Сначала входной слой принимает запрос и проверяет лимиты. Потом маршрутизатор решает, на какую модель или копию модели отправить работу. Очередь временно хранит запросы. Планировщик решает, какие запросы объединить в батч и какой машине отдать. Исполнитель держит модель в памяти и считает ответ. Для LLM или генеративных задач результат может идти постепенно: токен за токеном или превью за превью. Если пользователь ушёл или дедлайн истёк, запрос надо уметь отменить, иначе GPU продолжит делать бесполезную работу.

  • Admission control, или входной лимит. Если система уже перегружена, лучше честно отказать, дать упрощённый ответ или попросить повторить позже, чем принять всё и уронить p99 всем пользователям.
  • Очередь. Очередь полезна только пока ожидание меньше пользовательского дедлайна. После этого она превращается в фабрику таймаутов.
  • Планировщик. Короткие интерактивные запросы, длинные фоновые задачи и платящие пользователи не должны бесконтрольно конкурировать за одну и ту же GPU.
  • Потоковая выдача. Для LLM первый токен часто важнее полного времени до конца ответа: пользователь видит, что система начала работать.
  • Отмена. Если пользователь закрыл страницу, нужно освобождать место в очереди, память под контекст и слот в батче.

Батчинг: полезный, но опасный компромисс

Батчинг означает: не считать каждый запрос отдельно, а объединять несколько запросов и прогонять их вместе. Это часто лучше загружает GPU. Но батч не бесплатный: иногда надо ждать, пока батч соберётся; запросы внутри батча могут быть разной длины; длинная генерация может задержать короткую. Поэтому батчинг почти всегда меняет баланс между “дёшево и много” и “быстро для конкретного пользователя”.

ПодходИдеяГде хорошГде боль
Статический батчСобрали пачку, прогнали, потом следующую.Офлайн-скоринг и одинаковые входы.Плох для живого продукта: ждём пачку и простаиваем на разной длине запросов.
Динамический батчЖдём маленькое окно времени, чтобы объединить близкие запросы.Обычная выдача модели как сервиса, когда можно подождать несколько миллисекунд.Большое окно улучшает загрузку GPU, но ухудшает хвосты задержки.
Непрерывный батчинг для LLMВ генерации новые запросы заходят в работу, когда старые последовательности завершились.LLM-сервисы с разной длиной промптов и ответов.Нужно следить за памятью под контекст, справедливостью и длинными запросами.

Не путай загрузку железа и хороший сервис

GPU может быть загружена почти на 100%, а продукт всё равно плохой: пользователи ждут, короткие запросы стоят за длинными, очередь растёт, повторы создают новый перегруз. В продакшне цель не “забить GPU любой ценой”, а выдержать время ответа, качество и стоимость.

Что делать при перегрузе

Перегруз должен быть заранее продуманным сценарием, а не пожаром. Хороший сервис знает, кого обслуживать первым, где ограничивать очередь, когда включать более дешёвую модель, когда возвращать ответ из кеша, когда переводить задачу в фоновый режим и когда честно отказать. Важно не только добавить машины, а не дать системе самой усилить проблему.

  • Обратное давление. Верхний слой должен понимать, что нижний слой занят. Иначе запросы будут бесконечно копиться.
  • Приоритеты. Интерактивный пользователь, платный пользователь и ночная фоновая обработка не должны иметь одинаковое право на ресурс.
  • Мягкая деградация. Можно временно взять меньшую модель, сократить контекст, снизить разрешение, вернуть кешированный ответ или поставить задачу в фон.
  • Масштабирование. Сигналом должны быть не только CPU/GPU, но и очередь, хвосты задержки, таймауты и время прогрева новых копий модели.
  • Безопасная выкатка. Новая версия модели или новая настройка батчинга может улучшить среднюю скорость, но ухудшить p99. Поэтому нужны canary-проверка на малой доле трафика, сравнение метрик и быстрый откат.

Кейс Meta простыми словами: проблема была не только в модели

Полезный пример - Meta Engineering post про ads inference. В рекламной системе один пользовательский запрос может запускать много обращений к разным моделям. Модели часто обновляются, копии моделей разложены по множеству машин, железо неоднородное, а ответ нужен быстро. Проблема была не в том, что “одна модель медленная”. Проблема была в том, что нагрузка распределялась неровно: часть машин или копий моделей становилась перегруженной, и именно они портили хвосты задержки.

Что они делали по сути: лучше решали, куда отправить запрос; лучше размещали копии моделей по машинам; считали нагрузку не только на сервер в целом, но и на конкретную модель; учитывали пропускную способность памяти как отдельный ресурс; аккуратнее переводили трафик между версиями моделей; заранее оценивали, сколько копий модели понадобится при будущем трафике. По их отчёту это позволило обслуживать больше работы теми же ресурсами, снизить таймауты и улучшить p99. Но главный урок не в процентах, а в логике: узкое место было в распределении нагрузки, обновлении моделей и модели ресурсов, а не только в скорости отдельных ядер вычисления или среды исполнения.

Что было в кейсеПеревод на простой языкОбщий урок
Балансировка маршрутизацииЗапросы надо отправлять туда, где реально есть свободная ёмкость.Не достаточно иметь много серверов; важно правильно распределять работу между ними.
Балансировка размещения моделейКопии популярных моделей должны лежать там, где они не создают горячие точки.Размещение модели по машинам влияет на задержку так же, как код модели.
Счётчики нагрузки по каждой моделиСервер может выглядеть “не очень загруженным”, но конкретная модель на нём уже перегружена.Метрика должна соответствовать реальной единице работы, иначе балансировщик слепой.
Память как ресурсИногда упираемся не в число ядер, а в скорость чтения/записи памяти.CPU/GPU utilization не всегда объясняет тормоза; надо профилировать настоящий bottleneck.
Бюджет на смену версииВо время обновления модели часть трафика временно переезжает и может разбалансировать систему.Rollout модели - часть serving architecture, а не формальная DevOps-операция.
Прогноз числа копийЕсли трафик предсказуем, лучше заранее подготовить нужное число реплик.Реактивное масштабирование может опоздать к пику нагрузки.

Суть и вывод

На большом масштабе “ускорить inference” часто означает не переписать слой нейросети, а сделать систему умнее: правильно мерить нагрузку, правильно раскладывать модели, безопасно обновлять версии, заранее готовить ёмкость и не давать хвостовым запросам портить весь сервис.

Как это обсуждать на собеседовании

На собеседовании по нагруженному ML-сервиса обычно проверяют не знание всех фреймворков, а умение рассуждать под ограничениями. Хороший ответ начинается с уточнений: это интерактивный продукт или фоновая обработка? Как быстро пользователь ждёт ответ? Что хуже: таймаут, устаревший ответ, дорогой запасной режим или немного худшее качество? Есть ли платные пользователи? Бывают ли пики нагрузки? Разные ли модели делят одну и ту же GPU?

  • “Как обслужить долгую генерацию?” Сделай задачу асинхронной: статус задачи, очередь с лимитом, частичное превью, отмена, таймаут, повтор только по правилам и запасной режим.
  • “Почему батчинг ухудшил UX?” Запросы начали ждать сбор батча, короткие ответы оказались за длинными, p99 вырос, а средняя пропускная способность при этом могла улучшиться.
  • “GPU недогружена, но очередь растёт. Что проверять?” Передачу данных, CPU-предобработку, планировщик, размер батча, пропускную способность памяти, прогрев worker-ов, загрузку модели, блокировки и давление на downstream-сервисы.
  • “Как приоритизировать платных пользователей?” Разделить очереди и квоты, зарезервировать часть ёмкости, задать отдельные SLO и заранее описать поведение при перегрузе.
  • “Какие дашборды нужны?” Очередь, p50/p95/p99, таймауты и отмены, запросы или токены в секунду, память и загрузка GPU, размеры батчей, цена запроса и сигналы регресса качества.

Какая интерактивная иллюстрация здесь нужна

Симулятор очереди: сколько запросов приходит, сколько обработчиков доступно, как долго ждём сбор батча, какие запросы короткие или длинные, какие пользователи приоритетные. Графики должны показывать загрузку, p50/p99 задержку, длину очереди и долю таймаутов. Так студент быстро увидит, почему “увеличить batch” не всегда означает “сделать сервис лучше”.