Демо прошло отлично, ассистент поддержки работает — а через месяц финансы присылают счёт, как за трёх разработчиков, и продукт жалуется на 15–20 секунд до первого токена. nvidia-smi показывает утилизацию 100%, и кажется, что выход один — докупать GPU. Но чаще проблема в другом: карты снова и снова пересчитывают одни и те же длинные промпты.

Ниже — маршрут из шести шагов, который описал Сергей Прощаев (Tech Lead, преподаватель ОТУС) на примере реального стека: Llama 3.3 70B Instruct на двух репликах по 2×H100 SXM 80 GB, vLLM V1, Kubernetes, Envoy Gateway, Prometheus, Grafana, DCGM Exporter. Чекпойнт и парк карт не меняются — меняются конфигурация сервинга и точность весов.

Почему nvidia-smi врёт о реальной загрузке

GPU-Util в nvidia-smi показывает долю времени, когда на карте выполнялось хоть какое-то вычислительное ядро — не то, насколько она занята полезной работой. H100 выдаёт 3,35 ТБ/с пропускной способности памяти при 989 TFLOPS в FP16, но при генерации токенов используется лишь около 10–20% этой вычислительной мощности. Узкое место почти всегда в памяти, а не в вычислениях.

Для реальной диагностики нужны DCGM-метрики профилирования:

  • DCGM_FI_PROF_DRAM_ACTIVE — загрузка памяти
  • DCGM_FI_PROF_SM_ACTIVE — активность вычислительных блоков

Главная экономическая метрика — GPU-часы на миллион выходных токенов при соблюдении SLA. Именно она показывает, подешевел ли токен после оптимизации, а не просто улучшился ли TTFT.

Шаг 1: снять базовую линию до любых изменений

Без замера до и после любая оптимизация — спор мнений. Сначала считаем стоимость одного миллиона токенов:

python
# GPU-only стоимость 1M выходных токенов за неделю
GPU_HOUR_PRICE = 2.0  # внутренняя ставка часа одной H100
GPUS = 4              # 2 реплики × 2 GPU
HOURS = 24 * 7        # тот же интервал, что и в PromQL ниже
TOKENS_WEEK = 907_200_000  # sum(increase(vllm:generation_tokens_total[7d]))

gpu_hours_per_1m = GPUS * HOURS / (TOKENS_WEEK / 1_000_000)
print(f"{gpu_hours_per_1m:.3f} GPU-ч на 1M токенов, "
      f"{gpu_hours_per_1m * GPU_HOUR_PRICE:.2f} за 1M")
# 0.741 GPU-ч, 1.48 за 1M

Параллельно снимаем метрики из Prometheus. Имена счётчиков в документации vLLM записаны без суффикса, в экспозиции Prometheus у них есть _total — сверяйте с /metrics своей версии:

plain
# Все выходные токены за неделю — основа для стоимости
sum(increase(vllm:generation_tokens_total[7d]))

# TTFT p95 за неделю
histogram_quantile(0.95,
  sum by (le) (increase(vllm:time_to_first_token_seconds_bucket[7d])))

# Hit rate префиксного кэша
sum(increase(vllm:prefix_cache_hits_total[7d])) /
sum(increase(vllm:prefix_cache_queries_total[7d]))

# Соотношение входных токенов к выходным — быстрый индикатор повторяемости промптов
sum(increase(vllm:prompt_tokens_total[7d])) /
sum(increase(vllm:generation_tokens_total[7d]))

Шаг 2: включить и правильно настроить префиксный кэш

Префиксный кэш (prefix caching) в vLLM позволяет не пересчитывать KV-состояние для повторяющихся начал промптов. При системном промпте в 3 000 токенов и сотнях одновременных пользователей это прямая экономия prefill-времени на каждом запросе.

В конфигурации vLLM V1 кэш включается параметром запуска:

bash
vllm serve meta-llama/Llama-3.3-70B-Instruct \
  --enable-prefix-caching \
  --max-model-len 8192 \
  --gpu-memory-utilization 0.90

После включения следите за vllm:prefix_cache_hits_total / vllm:prefix_cache_queries_total. Hit rate выше 60–70% при RAG-нагрузке с фиксированным системным промптом — нормальный результат.

Шаг 3: квантизация весов без смены чекпойнта

Квантизация снижает потребление памяти и увеличивает пропускную способность за счёт уменьшения точности весов. BF16-чекпойнт Llama 3.3 70B занимает около 140 ГБ; при переходе на FP8 — вдвое меньше, что на двух H100 по 80 ГБ принципиально меняет картину с тензорным параллелизмом.

vLLM поддерживает квантизацию на лету через флаг --quantization:

bash
vllm serve meta-llama/Llama-3.3-70B-Instruct \
  --quantization fp8 \
  --enable-prefix-caching \
  --tensor-parallel-size 2 \
  --gpu-memory-utilization 0.90
РежимVRAM на 70BПотеря качества (MMLU)
BF16~140 ГБbaseline
FP8~70 ГБ< 1%
INT4 (AWQ/GPTQ)~35 ГБ1–3%

Где это ломается

Промпты уникальны. Если каждый запрос содержит уникальный контекст и системный промпт меняется — hit rate кэша будет близок к нулю, шаги с префиксным кэшем не дадут эффекта. Проверяйте соотношение prompt/generation токенов до начала оптимизации.

Метрики не совпадают с версией vLLM. Имена счётчиков менялись между минорными версиями. Всегда сверяйте названия с /metrics вашего инстанса, а не с документацией другой версии.

GPU-Util растёт после квантизации. Это нормально: карта делает больше работы за то же время. Смотрите на GPU-часы на миллион токенов — если метрика снизилась, оптимизация работает.

Улучшился только TTFT, но не пропускная способность. Снижение TTFT само по себе не удешевляет токен. Деньги экономятся тогда, когда освобождённое время GPU превращается в большее число обслуженных запросов на тех же картах.

Что попробовать дальше

После базовых шагов стоит посмотреть на speculative decoding (ускорение decode-фазы через черновую модель), chunked prefill (разбивка длинных промптов на чанки для снижения TTFT при высокой нагрузке) и disaggregated serving — разделение prefill и decode на разные узлы, которое vLLM V1 начал поддерживать экспериментально.

Замкнутый контур «замер — изменение — повторный замер» обязателен на каждом шаге. Только цифры GPU-часов на миллион токенов дают обоснованный ответ на вопрос, нужны ли новые карты.


FAQ

Как быстро понять, поможет ли префиксный кэш в моём случае?

Посмотрите на соотношение vllm:prompt_tokens_total / vllm:generation_tokens_total за неделю. Если оно больше 5–10 и системный промпт фиксирован — префиксный кэш даст заметный эффект. Если промпты уникальны, hit rate будет близок к нулю.

Можно ли включить FP8-квантизацию без переобучения модели?

Да, vLLM поддерживает динамическую квантизацию через флаг --quantization fp8 без изменения чекпойнта. На H100 FP8 поддерживается аппаратно, деградация качества по MMLU обычно меньше 1%.

Что такое TTFT и почему p95, а не среднее?

TTFT (Time To First Token) — время от отправки запроса до получения первого токена ответа. p95 показывает, что испытывают 5% самых «невезучих» пользователей в пиковую нагрузку. Среднее скрывает хвосты распределения, которые и создают жалобы.

Почему nvidia-smi показывает 100%, а карта всё равно не справляется?

GPU-Util в nvidia-smi фиксирует любую активность ядра, а не полезную загрузку. При генерации токенов узкое место — пропускная способность памяти, а не вычисления. Используйте DCGM_FI_PROF_DRAM_ACTIVE для реальной диагностики.

Нужно ли менять код приложения для включения префиксного кэша?

Нет, если системный промпт уже передаётся первым сообщением в каждом запросе. Кэш включается на стороне vLLM одним флагом. Важно только не менять порядок сообщений и не добавлять динамические токены перед системным промптом.

Источники