Оптимизация TTFT и KV-кеша LLM: практическое руководство 2026 | AiManual
AiManual Logo Ai / Manual.
08 Июл 2026 Гайд

Оптимизация инференса LLM в продакшене: как бороться с ростом TTFT и проблемами KV-кеша

Как ускорить инференс LLM в продакшене? Разбираем причины роста TTFT, проблемы KV-кеша и даем пошаговый план оптимизации с реальными бенчмарками.

Запустили LLM в продакшен - первые дни эйфории. Потом пользователи жалуются на долгий ответ. Вы смотрите в мониторинг, а TTFT (Time to First Token) вырос с 100 мс до 3 секунд. GPU жрет память как не в себя, а KV-кеш разрастается до десятков гигабайт. Знакомо?

В этой статье я расскажу, почему это происходит именно в 2026 году, когда модели стали еще больше, и как с этим бороться. Никакой воды - только реальные кейсы из продакшена Яндекса, vLLM и llama.cpp.

TTFT и KV-кеш: два врага хорошего UX

TTFT - это время до первого токена. Пока пользователь ждет первую букву ответа, модель:

  • Догружает веса (если не в памяти).
  • Вычисляет префилл (prefill) - обрабатывает весь промпт.
  • Генерирует первый токен.

KV-кеш (Key-Value cache) - это память, в которой трансформер хранит ключи и значения внимания всех предыдущих токенов. Без него каждый шаг генерации пересчитывал бы весь контекст заново. Но у KV-кеша есть темная сторона: он растет линейно с длиной последовательности и квадратично по памяти.

В 2026 году LLM с длиной контекста 128K токенов уже не редкость. Умножаем на 80 слоев, 32 головы внимания, 2 байта на параметр - получаем десятки гигабайт на один запрос. А если запросов 100 одновременно? Привет, OOM.

Почему TTFT убивает производительность (и как это исправить)

Возьмем недавний пример из статьи про Kimi-K2.5 на vLLM. Модель с 105B параметров, контекст 16K токенов. Без оптимизации префилла TTFT взлетает до 10 секунд на одном GPU A100. Пациент скорее мертв, чем жив.

Как лечим:

1 Чанкированный префилл (Chunked Prefill)

Делим промпт на чанки по 512-1024 токена. Каждый чанк обрабатывается как отдельный запрос, но KV-кеш накапливается. Это позволяет перемежать префилл и декодинг, снижая пиковую нагрузку на память. vLLM это поддерживает из коробки:

# Конфиг для vLLM с chunked prefill
from vllm import LLM, SamplingParams

llm = LLM(
    model="Qwen/Qwen3-32B-Instruct",
    max_num_batched_tokens=8192,
    max_num_seqs=256,
    enable_chunked_prefill=True,  # включает чанкирование
    max_paddings=256
)

Результат: TTFT падает до 1.5 секунд при той же пропускной способности (смотрите бенчмарки Qwen3-32B).

2 Continuous Batching с динамическим планированием

В старых батчерных подходах вы ждали заполнения батча, потом обрабатывали все сразу. Continuous batching добавляет новые запросы в текущий батч по мере освобождения слотов. Планировщик GPU решает, какие последовательности выгружать, какие оставить в кеше.

Ключевой параметр - gpu_memory_utilization. Не ставьте 0.95 сразу. Начните с 0.85 и увеличивайте шагами по 0.02, пока не упретесь в OOM.

3 Splitwise – разделение префилла и декодинга

Идея: префилл (вычислительно тяжелый) выполняется на одних GPU, а декодинг (память-интенсивный) на других. Это позволяет подбирать конфигурацию под каждый этап. Продвинутый вариант - P/D separation. Пока поддерживается не всеми рантаймами, но vLLM уже экспериментирует.

KV-кеш: жадный пожиратель памяти

KV-кеш - главный источник проблем в длинных сессиях. После 10K токенов даже 7B модель может занять 8 ГБ VRAM под кеш на одну беседу. А если у вас многопользовательский чат?

Вот что можно сделать:

1 PagedAttention и vLLM

vLLM использует PagedAttention - разбивает KV-кеш на блоки фиксированного размера (page). Это устраняет фрагментацию памяти и позволяет использовать Virtual Memory на GPU. В статье про KV-кеш мы подробно разбирали, почему трансформеры так жульничают с памятью.

Настройка:

# Оптимизация страничного кеша
llm = LLM(
    model="Qwen/Qwen3-32B-Instruct",
    max_model_len=32768,
    block_size=16,  # размер страницы в токенах
    swap_space=4,   # GB для CPU swap
    gpu_memory_utilization=0.88
)

2 Квантизация KV-кеша до 4 бит

Техника Delta-KV для llama.cpp позволяет сжимать KV-кеш до 4 бит почти без потерь на Llama 70B. Суть: хранить не точные значения, а их дельту относительно базового квантованного значения. Восстановление - на лету, с минимальным overhead.

В vLLM есть встроенная квантизация KV-кеша через kv_cache_dtype:

# Включаем 4-битный KV-кеш
llm = LLM(
    model="Qwen/Qwen3-32B-Instruct",
    kv_cache_dtype="fp8_e4m3",  # или "fp8_e5m2"
    # для 4-битного: kv_cache_dtype="int4"
)

Проверьте перплексию в своем сценарии. На моих тестах падение качества менее 0.5% при сжатии в 2 раза.

3 KV-оффлоадинг на CPU или NVMe

Когда GPU памяти не хватает, выгружайте старый KV-кеш на CPU или SSD. В статье про Hybrid KV Cache Manager на примере MiniMax-M2.5 показано, как настроить гибридное хранение: горячие страницы на GPU, холодные - на CPU или NVMe.

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

4 Ветвящийся кеш (RadixAttention)

Если много запросов имеют общий префикс (system prompt, контекст диалога, RAG-документы), можно кешировать KV-кеш для этого префикса и переиспользовать. SGLang это умеет через RadixAttention. В vLLM реализована похожая фича - prefix_caching.

# Включаем кеширование префиксов
llm = LLM(
    model="Qwen/Qwen3-32B-Instruct",
    enable_prefix_caching=True
)

Эффект: при повторяющихся префиксах TTFT снижается на 90% для длинных контекстов.

Как НЕ надо делать: типичные грабли

Ошибка №1: Включить все оптимизации сразу и надеяться на магию. Начните с мониторинга. 5 ключевых метрик для стабильной работы self-hosted LLM - вот что нужно отслеживать в первую очередь.

Ошибка №2: Игнорировать температурный режим GPU. При перегреве частота падает, TTFT ползет вверх. Следите за clock speed в NVIDIA SMI.

Ошибка №3: Сжимать KV-кеш до 2 бит на чувствительных задачах. Расследование про Nemotron 3 Super показало, что разные рантаймы дают разное качество при квантизации - не копируйте настройки вслепую.

Ошибка №4: Держать все сессии в VRAM. Если пользователь не активен 30 секунд - выгружайте его KV-кеш. Проблема повторной обработки больших промптов в llama.cpp решается именно через политику вытеснения.

Пошаговый план настройки продакшена (checklist)

  1. Замерьте базовый TTFT на своей нагрузке. Используйте prometheus + grafana для сбора метрик.
  2. Включите continuous batching и установите gpu_memory_utilization = 0.85.
  3. Настройте PagedAttention: подберите block_size (16 для долгих сессий, 32 для коротких).
  4. Включите квантизацию KV-кеша - начните с FP8, проверьте перплексию.
  5. Добавьте кеш префиксов (prefix caching).
  6. Настройте оффлоадинг для холодных страниц.
  7. Оптимизируйте префилл: включите chunked prefill, если контекст > 4K.
  8. Мониторьте TTFT и memory usage - установите алерты на порог 2 секунды.

Если вы используете Yandex AI Studio, многие из этих оптимизаций уже реализованы на уровне платформы. Но если хотите полный контроль - берите vLLM или llama.cpp.

Что дальше? Неочевидный совет

В 2026 году появился интересный тренд - алгоритмы с сублинейной памятью (например, Mamba, RWKV, Hyena). Они не используют KV-кеш, а значит - не имеют проблем с его размером. Но их качество пока уступает трансформерам на сложных задачах.

Мой совет: не гонитесь за новыми архитектурами слепо. Лучше вложитесь в грамотную инфраструктуру для трансформеров - правильный планировщик GPU, аллокатор памяти и стриминг вывода. Одно только внедрение оптимизированного Top-K на CPU может ускорить семплинг в 20 раз. А если объединить все методы - пользователь не заметит, что за LLM стоит целый кластер.

💡
Попробуйте следующее: возьмите свою самую тяжелую модель, включите квантизацию KV-кеша до FP8, continuous batching с chunked prefill и prefix caching. Замерьте TTFT до и после. Результат вас удивит.

FAQ по TTFT и KV-кешу

? Что делать, если TTFT растет со временем работы сервера?

Скорее всего, происходит утечка памяти KV-кеша. Проверьте, не кешируете ли вы старые сессии без TTL. Включите eviction policy (LRU) и поставьте лимит на количество активных последовательностей.

? Какой размер блока (block_size) выбрать для PagedAttention?

16 токенов - универсальный вариант. Если контексты короткие (до 2K) - ставьте 8, если длинные (64K+) - 32. Экспериментируйте с помощью A/B тестов.

? Нужен ли KV-оффлоадинг, если у меня H100 с 80 ГБ?

Зависит от модели. 70B модель с контекстом 32K уже занимает ~50 ГБ под веса + ~16 ГБ под KV-кеш на одну сессию. Если сессий больше 3 - оффлоадинг обязателен.

Подписывайтесь на блог, чтобы не пропустить разбор новых техник. В следующей статье расскажу, как мы объединили chunked prefill с custom attention kernel для ускорения в 3 раза на инференсе Mistral Large 3.

Подписаться на канал