Обязательно

LLM serving: KV-cache и батчинг

Как LLM отвечает токен за токеном: prefill/decode, KV-cache, continuous batching, метрики задержки и выбор vLLM/SGLang/TensorRT-LLM/TGI.

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

Зачем LLM serving выделяют в отдельную тему

С обычной моделью часто все выглядит просто: собрали batch, сделали forward pass, вернули ответ. С LLM так думать опасно. Запрос сначала надо целиком прочитать, потом ответ рождается по одному токену, а параллельно сервис держит очередь, стримит текст пользователю, освобождает память после отмененных запросов и следит, чтобы дорогая GPU не простаивала.

Эта страница опирается не на “примерно помню”, а на несколько слоев источников: курс Stanford CS336 для общей картины inference systems, tutorial Hugging Face Transformers: Continuous Batching для механики prefill/decode, paper PagedAttention/vLLM для памяти KV-cache, SGLang docs для runtime/routing/cache reuse, BentoML LLM metrics для метрик и production-кейсы NVIDIA/Perplexity and NAVER для привязки к реальности.

ТерминПо-русскиЗачем нужен
PrefillМодель читает весь prompt до первого токена ответа.Главный вклад во время до первого токена, особенно на длинных prompt.
DecodeМодель добавляет ответ по одному токену.Долго держит запрос в памяти и упирается в KV-cache, scheduler and memory bandwidth.
KV-cacheПамять attention для уже прочитанных токенов.Ускоряет генерацию, но быстро ест память видеокарты.
Continuous batchingСервис добавляет и убирает запросы между шагами генерации.Поднимает загрузку GPU, но требует аккуратно следить за задержками.
TTFTВремя до первого токена.То, что пользователь чувствует как “бот начал отвечать быстро или тупит”.
TPOT / ITLВремя между токенами.То, насколько ровно и быстро идет стрим ответа.

Один запрос: prefill и decode

LLM не печатает весь ответ за один проход. Сначала идет prefill: модель прогоняет prompt целиком и сохраняет промежуточную память attention. Потом начинается decode: модель берет уже накопленный контекст, предсказывает следующий токен, добавляет его в контекст и повторяет цикл. Поэтому длинный prompt и длинный ответ ломают систему по-разному.

СценарийЧто происходитКакая метрика первой портитсяЧто смотреть
Длинный prompt, короткий ответМодель долго читает вход, но быстро заканчивает генерацию.TTFT: пользователь ждет первый токен.Prompt tokens/sec, queue wait, prefill batching, chunked prefill.
Короткий prompt, длинный ответПервый токен появляется быстро, но запрос долго висит в decode.TPOT/ITL and active sequences.KV-cache memory, decode throughput, max active sequences.
Много коротких запросовСервису важно быстро подсаживать новые запросы в работу.Queue time and p95/p99.Continuous batching, admission control, priority queues.
Много одинаковых системных promptУ запросов есть общий префикс.Cost and TTFT if prefix is recomputed.Prefix cache hit rate and cache eviction.
Идея из HF continuous batching and BentoML metrics: нельзя оценивать serving одной цифрой throughput.

Короткая собесная формулировка

LLM inference состоит из тяжелого чтения prompt и последовательной генерации ответа. Prefill чаще бьет по time to first token, decode чаще бьет по памяти KV-cache and inter-token latency. Поэтому serving оптимизируют не одним флагом, а через workload: длины prompt/answer, concurrency, queue policy, cache policy and SLO.

KV-cache: ускорение, которое само становится проблемой

KV-cache хранит ключи и значения attention для уже обработанных токенов. Без него на каждом новом токене пришлось бы заново пересчитывать прошлый контекст. С ним decode становится сильно дешевле. Но цена понятная: чем больше активных запросов и чем длиннее контекст, тем больше памяти видеокарты уходит на cache.

Грубая интуиция такая: память cache растет вместе с числом слоев, числом активных токенов, размерностью attention and bytes per value. Это не формула для точного sizing без учета реализации, но хороший способ понять, почему long context serving внезапно становится задачей про HBM, fragmentation and memory bandwidth, а не только про FLOPs.

PagedAttention и prefix cache

Главная идея PagedAttention из vLLM: не хранить KV-cache как один большой непрерывный кусок на каждую sequence. Запросы разной длины приходят и заканчиваются в разное время, поэтому naive allocation быстро дает пустоты и fragmentation. PagedAttention режет cache на blocks/pages и управляет ими похожим образом на virtual memory: так проще переиспользовать HBM и держать больше активных запросов.

Prefix cache - соседняя, но не та же идея. Если у многих запросов одинаковое начало, например system prompt или общий RAG-template, его можно не считать заново. В vLLM это описано в Automatic Prefix Caching docs. В SGLang похожая линия развивается через RadixAttention and hierarchical cache: она особенно интересна, когда приложение делает цепочку связанных вызовов, а не один одиночный prompt.

МеханикаЧто чинитГде легко ошибиться
PagedAttentionWaste and fragmentation в KV-cache при запросах разной длины.Нельзя обещать фиксированный x-разгон: эффект зависит от модели, traffic mix and hardware.
Prefix cachingПовторный пересчет общего начала prompt.Если префиксы редко совпадают или часто invalidated, пользы мало.
Radix/prefix reuse in SGLangПереиспользование общих частей между связанными program-like вызовами.Надо смотреть именно application pattern: agents/RAG/tools/structured output.
KV quantization / lower precisionМеньше memory pressure.Нужно проверять качество и стабильность на своих задачах.

Батчинг и scheduler

У классического static batch есть неприятный эффект: короткие запросы могут ждать длинные. В генерации это особенно больно, потому что один ответ может закончиться за 20 токенов, а другой будет стримиться минуту. Continuous batching решает это через scheduler: между decode steps он добавляет новые запросы, убирает завершенные и пытается держать GPU занятой.

Важно: scheduler не “бесплатно ускоряет все”. Если слишком агрессивно гнаться за throughput, можно ухудшить TTFT для новых пользователей или p99 для коротких запросов. Более продвинутые papers вроде Orca and Sarathi-Serve полезны именно этим: они показывают, что serving - это trade-off между prefill, decode, latency and throughput.

Ручка настройкиЧто она значит по-человеческиРиск
max model lengthКакой максимальный контекст обещаем поддержать.Большой лимит может заранее съесть память и снизить concurrency.
max batch tokensСколько токенов scheduler готов держать в одном шаге.Можно поднять throughput и одновременно ухудшить TTFT/p99.
max active sequencesСколько запросов одновременно живут в decode.Растет KV-cache and tail latency.
Chunked prefillДлинный prompt режется на куски, чтобы decode не голодал.Сложнее scheduler и tuning; выигрыш зависит от traffic mix.
Priority / admission controlКого берем в работу первым и кого не пускаем при перегрузе.Без этого VIP/interactive traffic может страдать от bulk jobs.

Как выбирать serving engine

vLLM, SGLang, TensorRT-LLM, Triton and TGI не стоит учить как рейтинг “кто быстрее”. Нормальный выбор начинается с вопросов: какие длины prompt/answer, нужен ли streaming, есть ли повторяющиеся префиксы, нужен ли JSON по схеме, есть ли RAG/tools/agents, сколько моделей или адаптеров надо обслуживать, какие SLO по p95/p99 and cost.

СтекГде уместенЧто проверять в первую очередь
vLLMХороший open-source default для OpenAI-compatible API, PagedAttention, prefix caching, structured outputs and metrics.TTFT, TPOT/ITL, GPU cache usage, prefix cache hit rate, p95/p99 on your prompts.
SGLangMulti-call workloads: agents, RAG chains, structured output, tool calls, vision-language cases, cache-aware routing.Radix/prefix cache hit rate, router policy, structured output latency, compatibility with model family.
TensorRT-LLM + TritonNVIDIA-heavy production stack, где команда готова платить сложностью ради latency/throughput/control.Build/deploy complexity, model support, quantization path, batching policy, rollback and observability.
TGIПолезный architecture reference and HF ecosystem context; особенно для legacy/migration сравнения.Текущий статус проекта, model compatibility, metrics, стоит ли брать для greenfield.
LoRAX-like adapter servingМного fine-tuned adapters/LoRA поверх общих base models.Adapter loading, multi-adapter batching, isolation, cache behavior, vendor-specific assumptions.

Слабый ответ на собесе

“Поставим vLLM, и будет быстрее” - это не инженерный ответ. Сильнее звучит так: сначала снимаю traffic mix and SLO, отдельно смотрю prompt length, output length, TTFT, TPOT, queue time, p95/p99, GPU memory and cache hit rate; потом сравниваю serving stacks на похожем trace.

Production-кейсы: что из них вынести

  • Perplexity + NVIDIA - пример search-scale serving, где важны routing, multi-model stack, throughput and cost per query. Это не универсальный benchmark, а история про конкретный workload.
  • NAVER Place + TensorRT-LLM - хороший кейс для разговора про TTFT/TPOT: vertical SLM-service, где пользовательская задержка важнее абстрактного tokens/sec.
  • LoRAX/Predibase - полезный vendor case про обслуживание сотен fine-tuned adapters and continuous multi-adapter batching. Это материал от вендора, поэтому берем инженерные идеи, но не используем его как независимое доказательство качества или скорости.

Как читать материалы по порядку

ШагИсточникЧто забрать
1Stanford CS336 and HF continuous batchingОбщую механику autoregressive generation, prefill/decode and scheduler.
2BentoML LLM inference metricsНормальные определения TTFT, TPOT, ITL, throughput, goodput and p99.
3vLLM docs + PagedAttention paperПочему KV-cache memory management стал центральной темой serving.
4SGLang docs + paperПочему multi-call/program-like LLM apps требуют cache reuse, routing and structured generation.
5TensorRT-LLM/Triton docsКак выглядит более низкоуровневый NVIDIA production stack и почему он сложнее в эксплуатации.
6Habr explainersРусский слой для интуиции и формулировок, но technical claims сверять с primary docs/papers.

Что спрашивают на собеседовании

  • Почему LLM inference делят на prefill and decode?
  • Почему KV-cache одновременно ускоряет генерацию и создает memory bottleneck?
  • Что делает PagedAttention и почему это не просто “ускорение attention”?
  • Как continuous batching может улучшить throughput, но ухудшить p99 или TTFT?
  • Когда выбирать vLLM, когда смотреть SGLang, а когда TensorRT-LLM/Triton?
  • Какие метрики ты покажешь перед rollout новой serving config?

Иллюстрация, которую стоит добавить

Timeline simulator: слева приходят запросы разной длины, сверху переключатель static vs continuous batching, справа графики TTFT, TPOT, queue length, active sequences and KV-cache blocks. Цель - чтобы человек увидел: LLM serving не один forward pass, а живая очередь с памятью и scheduler trade-offs.

Материалы

Официальные docs