# ГЛАВА 21. SERVING И RUNTIME LLM-СИСТЕМ

---

Модель — это двигатель. Можно иметь лучший двигатель в мире, но без шасси, подвески и трансмиссии он не повезёт ни одного пассажира. Serving — это всё, что превращает веса на диске в ответ с предсказуемым latency и стоимостью.

В 2024–2025 serving перестал быть заботой ML-инженеров и стал частью продуктовой архитектуры. Решения о batching, caching, routing и quantization определяют latency, throughput и cost-per-query так же сильно, как выбор модели. Два одинаковых по весам deployment одной и той же модели способны отличаться по стоимости в 5–10× — только из-за разницы в serving stack. В этой главе — инженерный обзор serving: от управления GPU-памятью до capacity planning.

---

## 21.1. Две фазы inference: prefill и decode

LLM inference — не один монолитный процесс. Это две фазы с принципиально разным compute profile, и понимание этой границы — ключ к оптимизации.

**Prefill** — обработка всего input prompt. Все входные токены проходят через трансформер параллельно, модель вычисляет attention и заполняет KV-кэш. Эта фаза compute-bound: нагрузка на вычислительные ядра GPU максимальна. Latency на этой фазе определяет TTFT (Time to First Token) — сколько пользователь ждёт до первого символа ответа.

**Decode** — авторегрессионная генерация. Модель выдаёт по одному токену за шаг, каждый раз читая весь накопленный KV-кэш. Эта фаза memory-bound: bottleneck — не вычисления, а скорость чтения данных из GPU-памяти. Throughput на этой фазе определяет, сколько tokens/sec пользователь видит в стриминге.

Почему это важно: оптимизации для prefill (tensor parallelism, prefix caching) не помогают decode, и наоборот. Когда инженер жалуется на «медленный inference», первый вопрос — какая именно фаза тормозит. TTFT высокий — проблема в prefill. Стриминг медленный — проблема в decode. Разные фазы → разные инструменты.

Подробнее о KV-кэше как ресурсе — в [главе 5](05_long_context.md), о метриках TTFT и throughput — в [главе 24, §24.9](24_landscape_2026.md).

## 21.2. KV-кэш как центральный ресурс

KV-кэш (Key-Value cache) — это intermediate state, который модель хранит для каждого attention-слоя и каждого токена в контексте. Во время decode-фазы модель не пересчитывает attention заново для всех предыдущих токенов — она читает результаты из KV-кэша.

Размер KV-кэша пропорционален: layers × heads × head_dim × seq_len × 2 (K и V) × batch_size. Для 70B-модели на 32K контексте при batch=1 это уже десятки гигабайт. При batch из 50 concurrent запросов — сотни гигабайт, что часто превышает объём GPU-памяти, доступный для самих весов.

Наивная аллокация — выделить для каждого запроса максимальный блок — фрагментирует GPU-память и резко снижает concurrency. **PagedAttention** (Kwon et al., SOSP 2023) решает эту проблему по аналогии с виртуальной памятью в ОС: KV-кэш делится на блоки фиксированного размера (pages), маршрутизация через таблицу страниц, аллокация — по мере необходимости. Это устраняет фрагментацию и позволяет достичь near-zero waste. vLLM — engine, построенный на PagedAttention, — стал де-факто стандартом open-source serving.

### Prefix caching

Если несколько запросов разделяют общий prefix (system prompt, few-shot примеры), KV-кэш для этого prefix можно вычислить один раз и переиспользовать. Это радикально ускоряет TTFT в multi-turn conversations, где system prompt одинаков для тысяч запросов.

Prefix caching доступен и на стороне провайдеров: Anthropic prompt caching (до 90% снижения стоимости префикса), OpenAI cached prompt tokens. Для self-hosted — vLLM и SGLang поддерживают automatic prefix caching.

### Cross-request KV sharing

Более продвинутый вариант: tree-structured caching, когда несколько ветвей генерации (beam search, parallel sampling) разделяют общие поддеревья KV-кэша. Это уменьшает memory footprint при multi-hypothesis генерации (см. [главу 8](08_multiple_hypotheses.md)).

## 21.3. Continuous batching и scheduling

При static batching serving-система собирает batch из N запросов, обрабатывает их одновременно и ждёт, пока завершится самый длинный. GPU простаивает, пока короткие запросы уже завершены. Это способ гарантированно получить низкий throughput.

**Continuous batching** (iteration-level batching) меняет парадигму: новые запросы добавляются в batch на каждой decode-итерации. Как только запрос завершился — его slot освобождается, и в него тут же вставляется следующий запрос из очереди. GPU загружен всегда.

### Scheduling и admission control

При перегрузке системе приходится выбирать: деградировать latency для всех или ограничить admission. Две основные стратегии:

| Стратегия | Механизм | Когда использовать |
|-----------|----------|-------------------|
| Priority queue | SLO-sensitive запросы обслуживаются первыми, low-priority — в tail | Продукт с разными SLA-тирами |
| Back-pressure | HTTP 429 + client-side retry с exponential backoff | Защита кластера от каскадного overload |

Scheduling policies: FCFS (простой и fair), priority-based (разные SLO-тиры получают разный latency), preemption (прервать длинный decode для urgent prefill, если TTFT SLO под угрозой).

## 21.4. Disaggregated prefill/decode

Prefill compute-bound, decode memory-bound. Объединять их на одном GPU — компромисс: GPU либо недоиспользует compute (во время decode), либо недоиспользует memory bandwidth (во время prefill).

**Disaggregated architecture** разделяет фазы на отдельные GPU-группы. Prefill-nodes обрабатывают входные промпты и передают готовый KV-state на decode-nodes, которые занимаются только генерацией.

Ключевая инженерная польза такого разделения — не «магический буст throughput», а **раздельная настройка TTFT и ITL**. В актуальной документации vLLM этот паттерн прямо описан как способ отдельно тюнить time-to-first-token и inter-token latency, а также контролировать **tail ITL**, то есть неприятные паузы в стриминге на p95/p99. Это важное уточнение: disaggregated prefill — прежде всего инструмент управления SLO, а не универсальный рецепт ускорения.

Преимущества: prefill-nodes оптимизируются под compute (высокий tensor parallelism), decode-nodes — под memory bandwidth. Каждая фаза масштабируется независимо: всплеск коротких запросов → масштабировать prefill, рост длины ответов → масштабировать decode. Но за это приходится платить: передача KV-state между нодами добавляет latency, требует высокоскоростного interconnect и усложняет операционную схему.

Практическая эвристика: для **крупного кластера** с жёсткими streaming-SLO подход оправдан. Для **single-GPU или небольшого self-hosted deployment** сначала почти всегда стоит выжать другие рычаги — batching, scheduling, chunked prefill, prefix caching, quantization. И ещё одна важная оговорка: в некоторых serving-стеках эта возможность до сих пор помечена как **experimental**, то есть не должна считаться «стабильным дефолтом» без собственного нагрузочного теста.

## 21.5. Speculative decoding

Decode фаза memory-bound — GPU ждёт данные из памяти на каждом шаге. Speculative decoding использует это «окно ожидания»: маленькая draft-модель быстро генерирует K токенов-кандидатов, затем target-модель верифицирует их одним forward pass. Принятые токены — финальные, отвергнутые — отбрасываются и генерируются заново.

Метод **lossless**: распределение вероятностей выходных токенов идентично target-модели. Speedup: 2–3× на типичных задачах.

### Инженерные решения

**Выбор draft-модели**: smaller version того же семейства (Llama 70B → Llama 8B как draft), модель после structured pruning, или специально обученная маленькая модель. Self-speculative decoding использует subset слоёв самой target-модели как draft — не требует отдельного deployment.

**Acceptance rate** зависит от задачи: простой текст → высокий AR (80%+), reasoning и code → ниже (50–60%). Чем выше AR, тем больше speedup.

vLLM и TensorRT-LLM поддерживают speculative decoding из коробки. Подробнее о месте speculative decoding в ландшафте — в [главе 24, §24.9](24_landscape_2026.md).

## 21.6. Quantization: точность vs ресурсы

Quantization уменьшает precision весов (и/или активаций) модели: FP16 → INT8, INT4, реже до предельных значений. Меньше бит → меньше памяти → больше concurrent запросов → ниже стоимость.

| Метод | Что квантизуется | Эффект | Деградация качества |
|-------|-----------------|--------|---------------------|
| GPTQ, AWQ | Только веса (FP16 → INT4) | Memory footprint ×4 ↓ | Минимальная (1–3% на бенчмарках) |
| SmoothQuant | Веса + активации (INT8) | Compute speedup + memory ↓ | Умеренная, зависит от задачи |
| BitNet b1.58 | Ternary weights ({-1, 0, 1}) | CPU inference без GPU | Research frontier, не production-ready |

**Практическое правило**: INT4 weight-only (AWQ/GPTQ) — sweet spot для self-hosted serving. Потеря качества минимальна, memory footprint уменьшается в 4 раза, inference быстрее за счёт сокращения memory transfers.

Важное следствие: quantized 70B-модель часто эффективнее, чем полноразмерная 8B — больше знаний при сопоставимом ресурсном бюджете. Подробнее о SLM и выборе размера модели — в [главе 24, §24.4](24_landscape_2026.md).

## 21.7. Multi-LoRA serving

LoRA (Low-Rank Adaptation) позволяет дообучить модель под конкретную задачу, добавляя к attention-слоям компактные low-rank матрицы (см. [главу 19](19_fine_tuning_and_post_training.md) для технических деталей). Ключевой вопрос serving: как обслуживать десятки LoRA-адаптеров без десятков копий модели?

**Multi-LoRA serving** загружает одну base-модель и переключает LoRA-адаптеры per-request. KV-кэш и основные веса — общие; адаптер добавляет только несколько мегабайт. Один GPU-кластер обслуживает десятки кастомизированных моделей вместо десятков отдельных deployment.

vLLM и SGLang поддерживают multi-LoRA serving. Переключение между адаптерами — микросекунды, overhead — минимальный. Это архитектурно выгодно для SaaS-платформ, где каждый tenant имеет собственную fine-tuned модель.

## 21.8. Benchmarking и метрики serving

Serving-метрики — это не абстрактные числа, а SLO-контракты с пользователями. Основные:

| Метрика | Что измеряет | Типичные SLO |
|---------|-------------|-------------|
| TTFT (p50, p95, p99) | Время до первого токена | p95 < 500ms (chat), < 2s (batch) |
| Decode throughput | Tokens/sec на один запрос | > 30 tok/s (chat streaming) |
| TPS | Total tokens/sec на кластер | Зависит от capacity |
| ITL / tail ITL | Плавность стриминга между токенами | Следить за p95/p99, если ответ идёт пользователю потоком |
| End-to-end latency | Полное время ответа | Зависит от output length |
| Queue wait time | Ожидание в очереди | p95 < 100ms |

### Методология тестирования

Тестировать с реалистичными промптами (не "hello"), реалистичным распределением длин input/output, реалистичным concurrency. Pitfalls: не сравнивать TTFT при разных prompt lengths; не сравнивать throughput batch-режима со streaming; учитывать warm-up GPU и заполнение KV-кэша. Для disaggregated prefill throughput сам по себе особенно обманчив: обязательно замеряйте **TTFT, median ITL и tail ITL отдельно**, иначе можно «улучшить» кластер по aggregate-токенам в секунду и одновременно ухудшить пользовательский UX.

> **Промпт для генерации нагрузочного теста:**
> «Напиши Python-скрипт для нагрузочного тестирования OpenAI-совместимого LLM endpoint. Скрипт должен: принимать URL endpoint, число concurrent запросов, и файл с тестовыми промптами; отправлять запросы с заданным concurrency через asyncio + aiohttp; замерять TTFT, decode throughput (tokens/sec) и end-to-end latency для каждого запроса; выводить p50, p95, p99 для каждой метрики. Формат вывода — таблица в stdout.»

## 21.9. GPU capacity planning

Упрощённая формула для оценки:

$$\frac{\text{model\_size\_GB}}{\text{quant\_factor}} + \text{KV\_cache\_per\_request} \times \text{max\_concurrent} \leq \text{total\_GPU\_memory}$$

**Пример**: Llama 70B в INT4 ≈ 35 GB весов. На A100 80 GB остаётся ~45 GB для KV-кэша. При 32K контексте один запрос потребляет ~5–10 GB KV-кэша (FP16) или ~2.5–5 GB при INT8 KV-cache quantization → максимум 4–9 (FP16) или 9–18 (INT8) concurrent запросов на одном GPU. Для 100 concurrent → нужен кластер (подробнее о фундаментальной механике KV-кэша — в [Главе 5, §5.3](05_long_context.md)).

### Parallelism

| Тип | Механизм | Когда использовать |
|-----|----------|-------------------|
| Tensor parallelism | Один запрос на нескольких GPU | Latency ↓ для больших моделей |
| Pipeline parallelism | Разные слои на разных GPU | Throughput ↑ при высоком concurrency |

### Self-hosted vs API vs hybrid

Выбор deployment mode — архитектурное решение (подробнее — в [главе 23](23_getting_started.md), «Как начать»):

- **API-first** (OpenAI, Anthropic, Google): для variable/unpredictable нагрузки, быстрого старта, минимального ops overhead.
- **Self-hosted**: для steady-state high-volume нагрузки, data residency requirements, custom моделей. При >N тысяч запросов/день может быть экономичнее, но требует ops capacity.
- **Hybrid**: API для burst capacity, self-hosted для baseline — позволяет оптимизировать и cost, и availability.

Breakeven point зависит от модели, quantization, цены hardware и ops-команды. Универсальной формулы нет — но capacity planning всегда начинается с расчёта memory budget.

> **Промпт для ИИ:** «Напиши калькулятор GPU memory budget для LLM-инференса. Принимает параметры: model_family (Llama, Qwen, DeepSeek, Mistral), model_size_B (количество параметров), quantization (FP16/INT8/INT4), max_context_len (токенов), max_batch_size, dtype_kv_cache (FP16/INT8), tensor_parallel_size. Вычисляет: (1) размер весов после квантизации, (2) размер KV-кэша на один запрос (layers × heads × head_dim × seq_len × 2 × dtype), (3) общий memory footprint = веса + KV × batch, (4) минимальное количество GPU (A100 80GB, H100 80GB). Вывод — таблица в терминале. Используй известные конфиги моделей (d_model, num_layers, num_heads) из открытых источников. Предупреди о приблизительности расчётов.»

---

## Практический вывод

### Чек-лист serving-архитектуры

| # | Пункт | Вопрос |
|---|-------|--------|
| 1 | Deployment target | API, self-hosted или hybrid? |
| 2 | Модель и размер | Какая модель? Нужна ли quantization? |
| 3 | Quantization | INT4 (sweet spot) или INT8? Проведён ли eval на golden set? |
| 4 | KV-кэш strategy | Prefix caching включён? Средняя и максимальная длина контекста? |
| 5 | Batching | Continuous batching настроен? Admission control есть? |
| 6 | Speculative decoding | Применим ли? Есть ли подходящая draft-модель? |
| 7 | Multi-LoRA | Нужны ли per-tenant адаптеры? |
| 8 | Мониторинг | TTFT, ITL/tail ITL, throughput, queue wait — замеряются на p95/p99? |
| 9 | Capacity planning | Memory budget рассчитан? Ceiling concurrency определён? |
| 10 | Fallback | Есть ли fallback на API при перегрузке self-hosted? |

### Задания

1. **Memory budget.** Рассчитайте GPU memory budget для модели, которую вы используете: размер весов после quantization + KV-кэш при целевом concurrency. Определите, сколько GPU нужно и какой параллелизм (tensor vs pipeline) оптимален. Ожидаемый результат: таблица с числами — model size, KV per request, max concurrent, total GPU count.

2. **Нагрузочный тест.** Настройте load test для вашего LLM endpoint: замерьте TTFT p50/p95 и decode throughput при 10, 50, 100 concurrent requests. Используйте промпт из §23.8 для генерации скрипта. Ожидаемый результат: отчёт с графиками latency vs concurrency.

3. **Quantization eval.** Сравните INT8 и INT4 quantization для вашей задачи: проведите eval на golden set (см. [главу 14](14_llm_system_quality_evaluation.md)) и замерьте разницу в качестве (accuracy/F1) и throughput (tokens/sec). Ожидаемый результат: таблица «precision → quality → speed → cost».

---

## Источники

- Kwon, W. et al. (2023). "Efficient Memory Management for Large Language Model Serving with PagedAttention." SOSP 2023.
- Leviathan, Y. et al. (2023). "Fast Inference from Transformers via Speculative Decoding." ICML 2023.
- Chen, C. et al. (2023). "Accelerating Large Language Model Decoding with Speculative Sampling." DeepMind.
- Frantar, E. et al. (2023). "GPTQ: Accurate Post-Training Quantization for Generative Pre-trained Transformers." ICLR 2023.
- Lin, J. et al. (2024). "AWQ: Activation-aware Weight Quantization for LLM Compression and Acceleration." MLSys 2024.
- Ma, S. et al. (2024). "The Era of 1-bit LLMs: All Large Language Models are in 1.58 Bits." Microsoft Research.
- Gim, I. et al. (2024). "Prompt Cache: Modular Attention Reuse for Low-Latency Inference." MLSys 2024.
- Xiao, G. et al. (2023). "SmoothQuant: Accurate and Efficient Post-Training Quantization for Large Language Models." ICML 2023.
- vLLM Project. "vLLM: Easy, Fast, and Cheap LLM Serving." https://docs.vllm.ai/
- vLLM Project. "Disaggregated Prefilling (experimental)." docs.vllm.ai
- NVIDIA. "TensorRT-LLM." https://github.com/NVIDIA/TensorRT-LLM

---

**Навигация:**
- Назад: [Глава 20. Паттерны проектирования LLM-приложений](20_llm_application_design_patterns.md)
- Далее: [Глава 22. Durable orchestration и жизненный цикл агента](22_durable_orchestration_and_agent_lifecycle.md)
