Как оптимизировать LLM-инференс в 2026 году

от автора

Если вы в 2026 году запускаете LLM в продакшене, то почти наверняка больше всего денег тратите на инференс. Одна неоптимизированная модель размером 70B может сжигать десятки долларов в час на нескольких A100, тогда как грамотно оптимизированный стек дает сопоставимый результат за сравнительно меньшую сумму. При активном продакшене это выливается в тысячи долларов в месяц разницы только за счет настройки инференса.

Недавно я наткнулся на подробный гайд по оптимизации инференса на JobsByCulture. Материал показался мне очень ценным именно с точки зрения повседневной работы MLOps‑инженера.

Поэтому я решил не просто скинуть ссылку коллегам, а адаптировать статью под наши реалии, добавить пояснения и пометки, как это обычно выглядит в моей практике. Не ручаюсь, что описанный путь универсален для всех стеков и команд, но набор приемов внутри точно стоит того, чтобы о них знать и попробовать на своих нагрузках. 

Сразу дисклеймер: важно помнить, что некоторые приемы ускорения (например, квантизация), пусть и незначительно, но съедают качество. А другие, как например continous batching, позволяют повысить throughput модели, но увеличивают latency и TPOT, впрочем, обо всем по порядку…

Налог на инференс: почему это важно именно сейчас

Затраты на инференс популярных моделей — это постоянная статья расходов, и большая часть денег уходит в вычисления. Будь то развертывание опенсорс LLM локально или использование API.

Но узкое место здесь контринтуитивно: проблема не в самих вычислительных мощностях, а в пропускной способности памяти. Значительную часть времени GPU простаивает, так как ждет, пока веса и данные KV‑кеша будут поступать из HBM

Пример: H100 дает 3,35 ТБ/с пропускной способности памяти, но при этом 989 TFLOPS вычислений в FP16. Во время авторегрессионного декодинга вы используете лишь порядка 10–20% этой вычислительной мощности.

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

KV‑кеш: скрытый пожиратель памяти

KV‑кеш — это самый важный концепт в оптимизации инференса.

Во время авторегрессионной генерации каждый новый токен «смотрит» на все предыдущие токены. Без кеширования вам пришлось бы на каждом шаге генерации заново считать и key/value-представления для прошлых токенов, и attention по всей последовательности. В итоге получается квадратичная стоимость, которая делает генерацию с длинным контекстом слишком медленной и дорогой для практического применения. 

А KV‑кеш хранит проекции key и value из каждого слоя для каждого токена, сгенерированного до текущего момента, поэтому избавляет от необходимости пересчета. 

Приведу пример. Для модели Llama 3 70B с 80 слоями и 64 головами внимания генерация последовательности длиной 4 096 токенов требует:

// расчет размера KV‑кешаlayers = 80heads = 64 (8 KV‑голов с GQA)head_dim = 128seq_len = 4096dtype = float16 (2 байта)// размер KV на один токен:per_token = 2  layers  kv_heads  head_dim  dtypeper_token = 2  80  8  128  2 = 327,680 bytes ~ 320 KB// KV‑кеш для всей последовательности:total = 320 KB  4096 = ~1.3 GB на одну последовательность// при batch size 32:batch_kv = 1.3 GB  32 = ~41 GB// часто больше веса самой модели!

То есть выходит 41 ГБ только под KV‑кеш при довольно скромном размере батча, и это часто больше, чем весят сами квантованные веса модели.

Именно поэтому оптимизация KV‑кеша настолько критична.

PagedAttention

Традиционное выделение памяти под KV‑кеш резервирует непрерывный блок памяти под максимальную длину последовательности, даже если большинство запросов короткие.

Алгоритм PagedAttention, предложенный в vLLM, заимствует идею постраничной виртуальной памяти из операционных систем: KV‑кеш выделяется фиксированными блоками (страницами) и сопоставляется через таблицу страниц.

Итог: потери памяти падают с 60–80% почти до нуля. На практике это может заметно увеличить число одновременных запросов.

PagedAttention сейчас используется по умолчанию не только в vLLM, но и в большинстве фреймворков.

Prefix caching

Если множество запросов разделяют общий префикс (system prompt, few‑shot‑примеры, общий контекст), то такой подход поможет вычислить KV‑кеш для этого префикса один раз и переиспользовать его между запросами.

Для системного промпта длиной 2 000 токенов, который показывается 1 000 пользователям, вы экономите почти 2 млн токенов избыточных вычислений.

Все крупные фреймворки (vLLM, SGLang) сейчас поддерживают эту технику. Максимальный эффект она показывает в сценариях, где начало запроса повторяется токен в токен: например, при использовании промпт-шаблона или общих инструкций, которые не меняются между запросами, и/или в RAG‑пайплайнах, когда одни и те же извлеченные документы фигурируют в нескольких запросах.

Но важно помнить, что prefix caching работает только для совпадающего префикса: если от запроса к запросу меняется контекст, порядок чанков или текст инструкций, объем реально переиспользуемого кеша может оказаться довольно ограниченным.

Grouped-Query Attention (GQA)

GQA уменьшает размер KV‑кеша за счет того, что несколько query‑голов разделяют одни и те же key‑ и value‑головы.

Llama 3 использует 8 KV‑голов, которые разделяются 64 query‑головами. По итогу это дает 8‑кратное сокращение размера KV‑кеша по сравнению со стандартным multi‑head attention.

Это решение происходит на уровне архитектуры модели, а не обслуживания, но понимать это важно и критично для планирования емкости.

Квантование: больше за меньшее

Квантование снижает точность представления модели с FP16/BF16 (16 бит на параметр) до INT8 (8 бит), INT4 (4 бита) и даже ниже.

Это самая влиятельная техника оптимизации, которую можно применить к любой модели: более легкие (меньшие по размеру) веса означают меньше памяти, меньше трафика по шине и более быстрый инференс.

Формат

Биты

Сжатие памяти

Потеря качества

Где подойдет лучше всего

FP8 (E4M3)

8

в ~2 раза

Незначительная

Использование в продакшене на H100/H200

INT8 (W8A8)

8

в ~2 раза

<1% на бенчмарках

Использование в продакшене на более старых ускорителях

INT4 (GPTQ)

4

в ~4 раза

1–3% на бенчмарках

GPU с ограниченной памятью

INT4 (AWQ)

4

в ~4 раза

1–2% на бенчмарках

Лучше качество, чем GPTQ

GGUF (llama.cpp)

2–8

в ~2–8 раза

Зависит от уровня квантования

Локальный / CPU‑инференс

Практическое правило

Начинайте с FP8 на H100/H200 или INT8 на A100 — вы почти не заметите деградацию качества, но при этом сократите потребление памяти вдвое.

Переходите к INT4 только тогда, когда вам нужно впихнуть модель в более маленький GPU.

Ниже INT4 будут заметные потери качества на задачах со сложным рассуждением.

GPTQ vs AWQ: это всё методы постобученного квантования до INT4. AWQ в целом дает лучшее качество, потому что защищает тот самый 1% значимых весов, которые непропорционально сильно влияют на выход модели.

GPTQ же настраивается быстрее, но немного менее точен.

Для новых развертываний в 2026 году выбор по умолчанию — AWQ, при условии квантизации в INT4..

Спекулятивный декодинг: несколько токенов за один шаг

Стандартный авторегрессионный декодинг генерирует один токен за один прямой проход через всю модель.

Спекулятивный декодинг устраняет это узкое место за счет хитрой асимметрии: проверить N токенов стоит не сильно дороже, чем сгенерировать один.

Процесс работает так:

  • Draft‑фаза.
    Небольшая быстрая модель (1–7B) авторегрессионно генерирует K кандидатов‑токенов (обычно K в диапазоне 3–8).

  • Фаза проверки.
    Основная модель одним прямым проходом обрабатывает все K кандидатов и вычисляет вероятность каждого токена‑кандидата с учетом предыдущих.

  • Принятие или отклонение.
    Начиная с первого кандидата, принимаются токены, чьи вероятности по полной модели удовлетворяют порогу. Первый же несоответствующий токен отклоняется, и на его месте выполняется повторная выборка (resample) из распределения основной модели.

Если draft‑модель достаточно хороша (а для предсказуемых последовательностей вроде кода так обычно и есть), принимаются 70–90% кандидатов.

То есть 3–6 токенов за один прямой проход основной модели при математически идентичном распределении выходов по сравнению со стандартным декодингом.

Когда не стоит использовать спекулятивный декодинг

Спекулятивный декодинг улучшает latency (время выполнения одного запроса), но может снизить throughput (общее число токенов в секунду по всем пользователям).

Если вы обслуживаете много одновременных пользователей и загрузка GPU и так уже высокая, вычисления draft‑модели начинают конкурировать за ресурсы с основной моделью.

Используйте этот подход для сценариев с чувствительной к задержке, но низкой конкурентностью: интерактивная работа с кодом, real‑time‑агенты, потоковые ответы.

Flash Attention: внимание с эффективным использованием памяти

Flash Attention переписывает вычисление внимания так, чтобы не материализовывать полную N×N‑матрицу attention в HBM‑памяти GPU.

Вместо того чтобы вычислять внимание тремя отдельными операциями (QKᵀ, softmax, умножение на V), оно объединяет их в одно ядро, которое обрабатывает блоки матрицы внимания в быстрой SRAM. В результате память под вычисление внимания падает с O(N²) до O(N), что критично для моделей с длинным контекстом.

Алгоритм Flash Attention 2 и его наследники сейчас используются по умолчанию во всех основных фреймворках.

Реализовывать самостоятельно его не нужно, конечно, но нужно знать, что он есть: если вы ловите OOM‑ошибки на длинных последовательностях, причиной очень часто оказывается именно отсутствие Flash Attention.

Continuous batching: максимальная загрузка GPU

Static batching группирует запросы и обрабатывает их как единое целое.

Проблема в том, что если один запрос в батче генерирует 500 токенов, а другой — 10, GPU простаивает, так как ожидает завершения длинного запроса и не переходит к новым.

Continuous (или динамический) batching решает эту проблему, вставляя новые запросы в батч сразу, как только какой‑то запрос завершился. Загрузка GPU поднимается с 30–40% до 80–90%, а средняя латентность падает, потому что короткие запросы больше не находятся в заложниках у длинных. vLLM, TGI и TensorRT‑LLM поддерживают continuous batching из коробки.

Если вы обслуживаете модель без него, то теряете 2–3‑кратный запас по производительности. Think about it.

Стратегии для нескольких GPU: тензорный и конвейерный параллелизм

Когда модель не помещается на один GPU, у вас есть два основных варианта:

  • Тензорный параллелизм (TP).
    Подход разбивает отдельные слои модели между GPU. Каждый GPU хранит срез каждого слоя, и на каждом слое они обмениваются данными через all-reduce. Для этого нужен высокопроизводительный интерконнект (NVLink). Лучше всего подходит для 2–8 GPU в одном узле. Латентность при добавлении GPU остается примерно на том же уровне.

  • Конвейерный параллелизм (PP).
    Назначает разные слои на разные GPU. GPU 1 считает слои 0–19, GPU 2 — 20–39 и так далее. Нужно меньше пропускной способности соединения между устройствами, но появляются «пузырьки конвейера» — время простоя между стадиями. Лучше всего подходит для конфигураций, где задействовано несколько узлов.

Ландшафт фреймворков для инференса

Фреймворки не универсальны, поэтому разберем, что и когда использовать.

Фреймворк

Сильные стороны

Слабые стороны

Когда использовать

vLLM

PagedAttention, широкая поддержка моделей, активное сообщество, доступный API

Чуть меньший throughput, чем у TensorRT-LLM на NVIDIA

Выбор по умолчанию для продакшен‑обслуживания

TensorRT-LLM

Максимальный throughput на NVIDIA‑GPU, глубокая оптимизация под H100/H200

Сложная настройка, только NVIDIA, более узкая поддержка моделей

Когда нужен максимум производительности на NVIDIA

TGI

Экосистема Hugging Face, хорошая документация, простой деплой

Отставание по производительности от vLLM/TRT-LLM на больших моделях

HuggingFace‑центричные пайплайны

llama.cpp/Ollama

Инференс на CPU, квантование в GGUF, запускается почти где угодно

Не рассчитаны на высокопроизводительное обслуживание

Локальная разработка, edge‑развертывания

SGLang

RadixAttention, оптимизация структурированного вывода, быстрое кэширование префикса

Относительно молодой проект, меньшее сообщество

Структурированная генерация, агентные нагрузки

Практический воркфлоу оптимизации

Если вы наследуете медленный и дорогой деплой LLM, то предлагаю рабочую последовательность шагов:

  1. Сначала профилируйте.
    Используйте nvidia-smi, PyTorch Profiler или NSight Systems, чтобы найти узкое место. Может, это память (OOM уже на batch size = 4)? Или пропускная способность (низкая загрузка GPU во время декодинга)? Или вычисления (медленный prefill)?

  2. Включите continuous batching.
    Перейдите со статического батчинга на vLLM или TGI. Одно это часто удваивает производительность при практически нулевом риске для качества.

  3. Квантование.
    Начните с FP8 или INT8. Измерьте качество на своей eval‑выборке. Если оно остается на том же уровне, поздравляю, вы сократили потребление памяти вдвое и можете удвоить размер батча.

  4. Включите prefix caching.
    Если у вас есть общий системный промпт (а он есть у большинства продакшен‑приложений), то получите бесплатный прирост по производительности.

  5. Настройте batch size и concurrency.
    Увеличивайте максимальный размер батча, пока не упретесь в компромисс throughput/latency, который задает ваш SLA. Больше параллельных запросов = лучшая загрузка GPU, но выше латентность на один запрос.

  6. Подумайте о speculative decoding.
    Первоисточник этой статьи гласит, что если SLA по латентности очень жесткий и вы обслуживаете индивидуальных пользователей (а не большие батчи), то спекулятивный декодинг может сократить time‑to‑first‑token в 2–3 раза. Тут я не уверен, скорее всего сократится другой параметр — time-per-output-token, но со стороны пользователя это все равно будет выглядеть как сокращение общего времени генерации ответа. 

  7. Масштабируйтесь.
    Если одного GPU (или узла) не хватает, добавляйте TP внутри узлов и PP между узлами.

Кстати, пункты 2–7 можно настраивать при развертывании модели в Evolution ML Inference через переключатели или численные поля. Вот как это выглядит: 

Правило 80/20 для оптимизации инференса

Шаги 1–4 дают 80% профита при минимальном риске.

Шаги 5–7 требуют аккуратного бенчмаркинга и могут приводить к регрессиям.

Не перескакивайте к продвинутым техникам, пока не отладили базовые.

От себя добавлю, что любая оптимизация или донастройка параметров сервинга LLM — это почти всегда trade-off между качеством, скоростью ответа, общей производительностью и количеством одновременных запросов.

Квантизация будет работать быстрее и занимать меньше памяти, но за это придется заплатить ухудшением качества работы модели. А вот насколько сильным будет ухудшение — нужно тестировать на конкретном сценарии. В комментах можете поделиться, что сработало у вас, а заодно высказаться, как по-вашему стоит переводить: квантизация, квантирование или квантование? Лично мне больше по душе первое, но использовал я оба варианта. 

ссылка на оригинал статьи https://habr.com/ru/articles/1050512/