Deployment and optimization: serving a model fast and cheap
Anatomy of LLM serving (prefill/decode, continuous batching, KV cache), optimization knobs and their cost, SLOs and backpressure, and a rollout where speed-ups don’t break quality.
Lesson content is currently available in Russian.
What you will learn
- →Понимать prefill/decode, continuous batching, PagedAttention и их влияние на throughput.
- →Применять prompt caching, speculative decoding и streaming с пониманием их цены.
- →Определять SLO по TTFT/p95, строить backpressure и канареечный rollout с quality gate.
Анатомия LLM serving: prefill, decode, batching
Инференс LLM состоит из двух фаз с противоположными характерами, и без понимания этого невозможно осмысленно оптимизировать:
- Prefill: весь промпт обрабатывается за один проход, параллельно по токенам. Фаза compute-bound — упирается в вычислительную мощность GPU. Её длительность определяет TTFT (time to first token) — то, как быстро пользователь видит начало ответа.
- Decode: токены ответа генерируются строго по одному, и на каждый нужно прочитать из памяти веса и весь KV cache. Фаза memory-bandwidth-bound — упирается в пропускную способность памяти, а не в вычисления. Определяет скорость печати ответа (time per output token).
Из этой асимметрии следует главный источник эффективности серверов: GPU при декоде одного запроса простаивает по вычислениям — значит, декодировать надо много запросов одновременно. Continuous batching (vLLM, TGI, TensorRT-LLM) добавляет и убирает запросы из батча на каждой итерации генерации, не дожидаясь окончания самого длинного — по сравнению с наивным статическим батчингом это даёт кратный прирост throughput на реальном трафике с разной длиной ответов.
Вторая ключевая идея — PagedAttention (vLLM): KV cache хранится страницами, как виртуальная память ОС, вместо сплошного преаллоцированного буфера под максимальный контекст. Это убирает фрагментацию памяти и позволяет держать в батче в разы больше параллельных запросов на том же железе.
Практический вывод: если вы обслуживаете open-weight модель, выбор serving-движка с continuous batching — это не оптимизация, а гигиена: разница с наивным циклом инференса — порядок величины по throughput.
Ручки оптимизации и их цена
У каждой оптимизации есть выигрыш, условие применимости и цена. Держите все четыре в голове как таблицу:
- Prompt caching: KV cache общего префикса (системный промпт, few-shot примеры, схемы инструментов) вычисляется один раз и переиспользуется. У hosted-провайдеров кэшированные входные токены стоят в разы дешевле (типично скидка 50–90%), а TTFT на длинных промптах падает кратно. Условие: префикс должен быть бит-в-бит стабильным — кладите переменные части (дата, имя пользователя) в конец промпта, а не в начало, иначе кэш инвалидируется каждым запросом. На уникальных промптах бесполезен.
- Speculative decoding: малая draft-модель предлагает несколько токенов вперёд, большая проверяет их за один проход. Принятые токены выходят пачкой — ускорение декода в 2–3 раза. Принципиально: верификация гарантирует то же распределение, что у большой модели, — качество не деградирует. Цена: память под вторую модель и выигрыш, зависящий от предсказуемости текста (код и шаблонные ответы ускоряются сильнее свободной прозы).
- Квантизация для serving (fp8, int8/int4): меньше памяти → больше батч → выше throughput, плюс быстрее чтение весов в memory-bound декоде. Цена — риск деградации качества: прогон golden set на квантованной конфигурации обязателен до выкатки, а не после жалоб.
- Streaming: не ускоряет генерацию вовсе, но радикально меняет восприятие — пользователь читает начало, пока генерируется хвост. Для интерактивных сценариев обязателен; заодно требует продумать обработку обрыва посреди ответа и валидацию структурированного вывода, который приходит частями.
Общий принцип: каждая ручка проходит через quality gate. Ускорение, не прошедшее golden set, — это быстрый способ доставлять плохие ответы.
SLO, backpressure и экономика запроса
SLO для LLM-сервиса формулируются иначе, чем для обычного API, потому что «время ответа» распадается на два разных ощущения:
- TTFT — насколько быстро сервис «откликнулся»: для интерактива целятся в сотни миллисекунд, максимум 1–2 секунды на p95.
- Полное время ответа — зависит от длины генерации; для длинных ответов честнее нормировать на токен или фиксировать SLO по типам запросов.
Всегда p50/p95/p99, никогда среднее: хвост латентности формирует впечатление о сервисе сильнее медианы. И отдельно следите за длиной очереди на GPU — это опережающий индикатор: очередь растёт за минуты до того, как поплывёт p95.
Backpressure обязателен, потому что GPU-ёмкость конечна и не масштабируется мгновенно: ограниченная очередь с отбрасыванием на входе (лучше быстрый честный 429 с Retry-After, чем таймаут после минуты ожидания), приоритизация (интерактивные запросы вперёд, batch-задачи в фон), таймауты с отменой генерации — запрос, от которого клиент отвалился, не должен догенерировать в никуда, съедая батч.
Экономика считается на запрос и на фичу: cost = input_tokens × цена + output_tokens × цена (output обычно в разы дороже). Рычаги в порядке эффективности: срезать раздутый системный промпт (он платится в каждом запросе), включить prompt caching, ограничить max_tokens сверху разумным потолком, роутить простые задачи на дешёвые модели (каскад из урока про local-модели). Дашборд «cost per feature в день» — обязателен: без него рост счёта замечают из бухгалтерии, а не из инженерии.
Rollout: shadow, canary и quality gate
Любое изменение serving-конфигурации — новая модель, квантизация, новый движок, даже «безобидное» обновление драйвера — выкатывается как рискованный релиз, потому что деградация LLM-систем бывает тихой: сервис отвечает, метрики инфраструктуры зелёные, а ответы стали хуже.
Последовательность зрелого rollout:
- Offline gate: golden set на новой конфигурации. Не прошёл — дальше не идём, это самый дешёвый фильтр.
- Shadow traffic: копия реального трафика идёт на новую конфигурацию, ответы пишутся в лог, но пользователям не отдаются. Сравнивается всё: latency, стоимость, а качество — автоматическим судьёй на выборке пар старый/новый ответ. Shadow ловит то, чего нет в golden set: реальное распределение запросов, пиковые нагрузки, редкие языки.
- Canary: 5% живого трафика → наблюдение → 25–50% → 100%. На каждой ступени — автоматические пороги: p95 TTFT, error rate, доля провалов валидации структурированного вывода, eval score на онлайн-выборке. Прошито заранее: какие цифры откатывают канарейку автоматически, без совещания.
- Rollback — это переключение маршрута на предыдущую конфигурацию за секунды. Значит, предыдущая конфигурация остаётся горячей до конца наблюдательного окна, а не выключается в момент достижения 100%.
Антипаттерн, который встречается чаще всего: «мы ускорили инференс на 40% и сразу выкатили на всех — латентность же улучшилась!» Улучшение одной метрики — не доказательство отсутствия деградации других: квантизация, ускорившая декод, могла просадить качество на классе запросов, которого не было в golden set. Именно поэтому shadow и canary не пропускаются даже для «очевидных улучшений».
Practice
Tuning модельного endpoint под SLO
Разверните open-weight модель через vLLM (или используйте hosted API с prompt caching). Дайте нагрузку 20–50 параллельных клиентов, снимите TTFT и полное время (p50/p95), throughput и cost per request. Затем примените две оптимизации (prompt caching + ограничение max_tokens, или квантизация) и покажите изменение метрик вместе с прогоном golden set до и после.
Deliverable
График p50/p95 TTFT и полного времени до и после оптимизаций, throughput, cost per request, и результат quality gate, подтверждающий отсутствие регрессии качества.
Checkpoint
Почему оптимизация без quality gate опасна и как выглядит правильный rollout ускорения?
- ✓ Есть p95 TTFT, полное время и cost per request как отдельные метрики.
- ✓ Rollout проходит offline gate → shadow → canary с заранее прошитыми порогами авто-отката.
- ✓ Потеря качества обнаруживается измерениями (golden set, онлайн-eval), а не жалобами.
Check yourself
Sources