Хук: 18 tok/s → 100 tok/s за 3 недели
Первый запуск vLLM на 2×3090 дал нам 18 tok/s. Через 3 недели — 75. Разница — 4 параметра в конфиге и 2 дня отладки. Вот эти 4 параметра, и вот что пошло не так по пути.
Первый запуск (и почему он был плохой)
vllm serve Qwen3.8-27B-AWQ-MTP --tensor-parallel-size 2
18 tok/s. TTFT 4.2s. «Работает, но это не то».
Ошибки первого запуска:
- Не настроили MTP — модель с MTP-головами гонялась в обычном режиме, 1 токен за итерацию.
- Не настроили KV-cache — контекст 32K по умолчанию, 256K не заявлен.
- OMP_NUM_THREADS по умолчанию — CPU-многопоточность создавала overhead, а не ускорение.
Итерация 1: MTP-спекулятивное декодирование
--speculative-config '{"method": "mtp", "num_speculative_tokens": 3}'
18 → 45 tok/s (×2.5). Первый ощутимый скачок.
Проблема: MTP в vLLM 0.27.1 — нестабильный. Wild write bug (PR #50021): после нескольких часов нагрузки процессорный буфер уходил в мусор, и vLLM крашился. Наш vLLM крашнулся на 3-й день. Пришлось патчить.
Решение: upgrade на 0.28.0 — баг закрыт. (NVFP4 и TRT-LLM из 0.28.0 нам не нужны: это Hopper/Blackwell-фичи, а у нас Ampere.)
Итерация 2: KV-cache и max-model-len
--max-model-len 262144
256K контекст. Проблема: на 24GB×2 KV-cache под 256K съедает ~18GB. Осталось 6GB для весов. Не влезает.
Решение:
--max-num-seqs 2
Лимит параллельных запросов. Мы жертвуем параллельностью ради контекста. Для агента это ок — он генерирует последовательно. (Почему это честный компромисс, а не «обход» — в части 5.)
Итерация 3: OMP_NUM_THREADS и async scheduling
OMP_NUM_THREADS=1
--disable-async-scheduling
Почему OMP_NUM_THREADS=1 (не 8, не 16): на 3090 (Ampere) GPU упирается в память, и CPU-многопоточность в CUDA-рантайме создаёт overhead, а не ускорение. Мы замерили: 1 тред — быстрее, чем 8.
Почему --disable-async-scheduling: async scheduling конфликтует с MTP-декодированием — планировщик «опережает» спекулятивные токены, и acceptance rate падает.
Результат: TTFT 4.2s → 1.8s.
Итерация 4: mamba-cache-mode
--mamba-cache-mode align
Почему: по умолчанию vLLM использует auto, который на 3090 даёт suboptimal memory layout для гибридного KV-хранилища. align выравнивает блоки — +5 tok/s.
Финальный конфиг (полный)
vllm serve Qwen3.8-27B-AWQ-MTP \
--tensor-parallel-size 2 \
--max-model-len 262144 \
--max-num-seqs 2 \
--speculative-config '{"method": "mtp", "num_speculative_tokens": 3}' \
--mamba-cache-mode align \
--disable-async-scheduling \
--gpu-memory-utilization 0.92
# OMP_NUM_THREADS=1
Бенчмарк: до/после
| Параметр | Первый запуск | Финальный |
|---|---|---|
| Tok/s (decode) | 18 | 75 |
| TTFT (8K) | 4.2s | 1.8s |
| Контекст | 32K | 256K |
| Параллельность | 8 seqs | 2 seqs |
| VRAM usage | 42GB | 46GB |
×4.2 по decode-скорости. И обратите внимание на параллельность: мы сознательно ушли с 8 последовательностей на 2. Это не деградация — это расчёт (см. часть 5).
Что мы НЕ делали (и почему)
- NVFP4 — не поддерживается на Ampere.
- TRT-LLM — не нужен, vLLM достаточно.
- TensorRT — overhead больше, чем gain.
- 8 параллельных запросов — для агента 2 достаточно. Гонять 8 — значит жертвовать контекстом и TTFT ради throughput, который нам не нужен.
Смежные материалы: Tensor Parallelism, AWQ, PagedAttention.
Уроки тюнинга
- Начинайте с baseline, а не с «лучшего конфига из интернета». Наш первый конфиг — дефолтный, и именно он показал, где боль.
- Каждая итерация — одна переменная. MTP → KV-cache → CPU → cache-mode. Иначе не понять, что дало эффект.
- Версия рантайма — часть тюнинга. 2 недели на нестабильном 0.27.1 — это потерянное время. Проверяйте, что вы патчите баги, а не живёте с ними.
Именно так мы работаем в проектах по тюнингу vLLM: baseline → итерации по одной переменной → нагрузочное тестирование → отчёт с метриками до/после. Если ваш инференс «работает, но медленно» — обсудим проект.