Что проверяем в конфигурации: краткий ответ
Состав флагов для задачи «27B в 4-битной квантизации плюс контекст 1M токенов» собран корректно: все ключевые механизмы vLLM в нём есть. Проверять стоит три вещи. Хватит ли памяти под KV-кэш на четырёх GPU. Совпадут ли rope-параметры с базовым окном модели. И существуют ли в вашей версии vLLM те имена парсеров, которые указаны в команде.
Первая прикидка по памяти: KV-кэш на 1M токенов в fp8 при tensor-parallel-size 4 требует около 30 ГБ на каждую карту только под кэш, ещё до весов и активаций. Это отсекает GPU с 24 и 32 ГБ в конфигурации из четырёх штук. Вторая прикидка: rope с factor 4.0 даёт миллион позиций только в том случае, если базовое окно модели равно 256K. Если в config.json стоит 32K или 128K, factor 4.0 растянет окно до 128K или 512K, а --max-model-len 1000000 либо упадёт с ошибкой, либо даст деградацию на длинных промптах.
Шпаргалка по флагам из конфигурации:
| Флаг | Что делает | Что проверить |
|---|---|---|
| tensor-parallel-size 4 | Делит веса модели и KV-кэш между четырьмя GPU | Число карт в системе, делимость голов внимания на 4 |
| kv-cache-dtype fp8 | Хранит ключи и значения в 8-битном float, вдвое экономя память | Поддержку fp8 у GPU, влияние на качество на ваших задачах |
| спекулятивное декодирование mtp | Предсказывает несколько токенов за шаг и проверяет их основной моделью | Имя метода в вашей версии и значение num_speculative_tokens |
| rope yarn, factor 4.0 | Растягивает позиционное окно модели | Поле max_position_embeddings и rope_scaling в config.json |
| mrope | Многомерный rope, обычно у мультимодальных веток Qwen | Есть ли смысл для текстовой модели |
| reasoning-parser qwen3 | Отделяет блок рассуждений от финального ответа | Совпадение с форматом, на котором обучена модель |
| tool-call-parser qwen3 | Превращает текст в структурированный вызов инструмента | Точное имя парсера в вашей сборке |
| enable-auto-tool-choice | Позволяет модели самой решать, какой инструмент вызвать | Согласованность chat template с форматом вызовов |
| prefetch safetensors | Заранее читает файлы весов, ускоряя старт | Наличие параметра в вашей версии |
Гарантировать стабильность на любом сценарии по одной команде нельзя: результат зависит от версии vLLM, драйвера CUDA, сборки ядер и того, как именно обучена модель. Дальше то, что посчитать и проверить в логах.
Требования к железу для запуска Qwen3.8-27B-NVFP4 с контекстом 1M
NVFP4 это 4-битный формат с плавающей запятой: элементы E2M1, масштаб FP8 E4M3 на блок из 16 значений и общий FP32-масштаб на тензор. Двухуровневое масштабирование даёт точность выше, чем у простого 4-битного целочисленного квантования, но на объём памяти влияет слабо. 27B параметров в NVFP4 занимают около 13,5-14 ГБ плюс накладные расходы на блочные масштабы. Основной расход в конфигурации с 1M контекста создаёт не модель, а KV-кэш.
Расчёт VRAM для KV-кэша в fp8
Объём кэша считается по формуле:
KV = 2 * L * H_kv * d_head * seq_len * bytes_per_element
Двойка отвечает за отдельные тензоры ключей и значений, L это число слоёв, H_kv это число KV-голов, d_head это размерность головы, bytes_per_element равен 1 для fp8 и 2 для fp16. В семействе Qwen3 у моделей класса 27-32B встречается связка 64 слоя, 8 KV-голов и head_dim 128. Точные значения берите из config.json.
При такой архитектуре на один токен приходится 2 * 64 * 8 * 128 = 131 072 байта, то есть 128 КиБ. Дальше арифметика простая:
| Длина контекста | KV-кэш в fp8, всего | На одну GPU при tensor-parallel-size 4 |
|---|---|---|
| 128K токенов | 16 ГиБ | 4 ГиБ |
| 256K токенов | 32 ГиБ | 8 ГиБ |
| 512K токенов | 64 ГиБ | 16 ГиБ |
| 1M токенов | 122 ГиБ | 30,5 ГиБ |
В fp16 те же 1M токенов дали бы 244 ГиБ суммарно и 61 ГиБ на карту. Четыре карты по 48 ГБ такое уже не вмещают, так что fp8 KV-кэш здесь не украшение, а условие запуска.
Одна оговорка, которая может сильно изменить цифры. Если модель гибридная и часть слоёв работает на скользящем окне или на линейном внимании, KV-кэш считается только для слоёв с полным вниманием. Посмотрите в config.json поле layer_types или аналогичное: разница между полным и гибридным вниманием измеряется в разы.
Почему tensor-parallel-size 4 - разумный минимум
При tensor-parallel-size 4 каждый GPU получает четверть весов (примерно 3,5 ГиБ) и четверть KV-кэша (30,5 ГиБ на миллионе токенов). Плюс активации, буферы и графы CUDA, итого около 36-38 ГиБ на карту. Это укладывается в 48 ГБ (L40S, RTX 6000 Ada, A6000) с запасом и совсем свободно чувствует себя на 80-96 ГБ.
На картах с 24 ГБ четвёрки не хватит: 30,5 ГиБ на карту в 24 ГБ не помещаются. Придётся переходить на tensor-parallel-size 8, где на карту придётся около 15 ГиБ кэша и примерно 1,8 ГиБ весов, то есть 18-20 ГиБ суммарно. Это влезает, но обмен между восемью GPU по PCIe на длинном префилле становится узким местом.
Отдельно про архитектуру. Нативные ядра NVFP4 в vLLM рассчитаны в первую очередь на Blackwell, поэтому на Ada и Hopper сборка может уходить на резервные пути с потерей скорости. С fp8 KV-кэшем проще: он требует вычислительной способности 8.9 и выше, то есть Ada и Hopper подходят, а A100 (8.0) уже нет.
Альтернатива tensor parallelism это pipeline-parallel-size: слои делятся между GPU, и KV-кэш тоже разъезжается по стадиям, а обмен между картами во время декодирования резко падает. Расплата - задержка на коротких запросах и более слабая поддержка спекулятивного декодирования в части релизов vLLM. Проверяйте совместимость в своей версии.
Разбор ключевых флагов vLLM: что они делают и как влияют на работу
Каждый флаг из конфигурации решает одну конкретную проблему: либо не влезает память, либо не хватает длины контекста, либо замедляется генерация, либо ломается формат вывода. Разбираем по порядку.
Tensor-parallel-size и распределение модели
Параметр задаёт, на сколько GPU разрезается каждый тензор. При значении 4 модель делится на четыре части, и каждая карта считает свою долю на каждом слое. Число GPU в системе должно быть кратно этому значению, а число голов внимания и KV-голов должно делиться на него нацело: 8 KV-голов на 4 карты делятся по две, всё корректно. При tensor-parallel-size, большем числа GPU, запуск падает на старте.
Цена масштабирования это коммуникации: на каждом слое карты обмениваются частичными результатами, и при tensor parallelism по PCIe накладные расходы растут линейно с числом карт. NVLink заметно сглаживает картину, но на потребительских картах его нет, поэтому 4 GPU по PCIe 5.0 это рабочий вариант, а 8 GPU по тому же интерфейсу уже заметно медленнее на префилле.
fp8 KV-кэш: экономия памяти для длинного контекста
Ключи и значения внимания хранятся в 8-битном формате (обычно E4M3) вместо 16-битного, и объём кэша падает вдвое. На 1M токенов это разница между 122 ГиБ и 244 ГиБ, то есть между рабочей и нерабочей конфигурацией на четырёх картах.
Влияние на качество есть, но оно обычно меньше, чем у квантизации самих весов: KV-кэш не участвует в обучении, а ошибка накапливается только в значениях внимания. Если хочется подстраховаться, включите расчёт масштабов кэша (в vLLM это отдельный флаг вида --calculate-kv-scales): он добавляет один прогон профилирования при старте, зато точность fp8 становится предсказуемее. На очень длинных контекстах именно масштабы, подобранные под конкретную модель, дают основной эффект.
Спекулятивное декодирование mtp: ускорение без потери качества
MTP (Multi-Token Prediction) это встроенный в модель блок, который предсказывает следующие токены и передаёт их основной голове на проверку. Отдельная draft-модель не нужна, что удобно: не приходится держать в памяти вторую сеть и подбирать её под основную.
Выигрыш зависит от того, как часто предсказания принимаются. На структурных задачах (код, JSON, вызовы инструментов) доля принятых токенов высокая, на свободном тексте и рассуждениях она падает, и ускорение может сойти к нулю. Ориентир по порядку величин: в разборе запуска этой же модели на одной RTX 4090 через llama.cpp включение MTP поднимало скорость генерации примерно с 40 до 65-80 токенов в секунду, но урезало доступный контекст (подробности в разборе конфигурации llama.cpp для 24 ГБ VRAM). В vLLM механика другая, но логика та же: спекулятивные токены занимают место в кэше.
Имя метода отличается между релизами: в одних сборках это mtp, в других встречаются варианты вида deepseek_mtp или qwen3_next_mtp. В части версий спекулятивное декодирование с MTP и tensor parallelism больше единицы работало с ограничениями. Проверяйте release notes и подставляйте ровно то имя, которое принимает ваша сборка, иначе флаг либо вызовет ошибку, либо будет проигнорирован.
Rope-параметры yarn, factor 4.0 и mrope: расширение контекста
YaRN это способ масштабирования rotary position embeddings для длинных последовательностей. Он не меняет веса, а растягивает шкалу позиций и корректирует температуру внимания через множители mscale, чтобы модель не «поплыла» на непривычных длинах. Формула простая: эффективная длина равна базовому окну, умноженному на factor. Значит, factor 4.0 даёт миллион позиций только при базовом окне 262 144. Если в config.json модели стоит 32 768, те же 4.0 дадут 131 072 позиции, и запрос на 1M упрётся в ограничение конфигурации.
Практический порядок действий такой: сначала посмотреть, что уже прописано в config.json. Если там есть rope_scaling с типом yarn и нужным factor под 1M, дублировать параметры в командной строке не нужно, иначе легко получить рассинхрон между тем, на чём модель обучали, и тем, что вы подали на вход.
Отдельный вопрос - mrope. Многомерный rope применяется в мультимодальных ветках Qwen, где позиции кодируются отдельно для текста и изображения, и в конфиге ему соответствует поле вида mrope_section. Для чисто текстовой модели этот параметр обычно не нужен: он либо игнорируется, либо приводит к ошибке разбора конфигурации. Стоит проверить, откуда он взялся в вашей команде.
Reasoning-parser и tool-call-parser qwen3: обработка вывода
Парсеры не влияют на то, что генерирует модель. Они разбирают уже готовый текст: reasoning-parser отделяет блок рассуждений от финального ответа, tool-call-parser превращает текстовый вызов инструмента в структурированный объект с именем функции и аргументами. Без них агентная обвязка получает сырую строку и должна парсить её сама.
Флаг auto-tool-choice снимает с клиента выбор инструмента: модель сама решает, нужен ли вызов. Для этого chat template должен уметь формат с описанием инструментов, иначе модель просто не увидит список функций и никогда их не вызовет.
Про имена. В разных версиях vLLM парсер инструментов для Qwen называется по-разному, встречаются варианты вида qwen3_xml и qwen3_coder. Если указать несуществующее имя, сервер не поднимется: vLLM валидирует список парсеров при старте. Проверьте доступные значения через vllm serve --help в своей сборке. И держите в голове, что связка reasoning-парсера и tool-call-парсера в отдельных релизах конфликтует: рассуждения и вызовы инструментов начинают перепутываться в одном ответе.
Prefetch safetensors: ускорение загрузки модели
Стратегия предварительного чтения заставляет загрузчик начинать читать файлы safetensors заранее, не дожидаясь, пока освободится память под предыдущий шард. Время старта сокращается, особенно когда весов много и они лежат на сетевом хранилище или на медленном NVMe.
На качество и скорость генерации параметр не влияет вообще, это чистая экономия времени на запуске. Важный момент: имя параметра менялось между версиями, в одних сборках это флаг стратегии загрузки safetensors, в других переменная окружения. Если ваша версия его не знает, запуск упадёт с ошибкой неизвестного аргумента, так что сверяйтесь со справкой, а не с чужой командой годичной давности.
Типичные ошибки и точки оптимизации
Три класса проблем встречаются чаще остальных: не хватает памяти на старте, падает качество на длинном контексте и не даёт выигрыша спекулятивное декодирование.
OOM и нехватка VRAM: как диагностировать и что делать
Сначала посмотрите, что vLLM пишет до падения. Сервер печатает число токенов, которые помещаются в KV-кэш при текущих настройках. Если это число меньше max-model-len, запуск не поднимется, и сообщение прямо скажет, сколько токенов доступно и сколько запрошено. Дальше варианты по возрастанию затрат: снизить max-model-len до доступного значения, поднять gpu-memory-utilization, уменьшить max-num-batched-tokens, включить chunked prefill, увеличить tensor-parallel-size.
Отдельная ловушка это фрагментация и графы CUDA: при tensor-parallel-size 4 и длинном контексте захват графов съедает заметный кусок. Если старт падает в самом конце, помогает режим без графов и меньшее значение max-num-seqs. Ровно та же возня с AOT-компиляцией против OOM описана в разборе запуска Qwen3 27B INT4 на одной RTX 3090: там контекст 147 456 токенов удалось получить именно за счёт порядка выбора параметров и ограничения параллельных последовательностей.
Ещё одна точка роста КПД на длинных повторяющихся запросах это кэш префиксов и внешнее хранилище KV. Если один и тот же документ уходит в модель много раз, имеет смысл посмотреть в сторону вынесения кэша за пределы GPU: схема с LMCache и fp8 KV-кэшем разобрана в материале о сборке локального стека для Qwen3.8 27B на двух RTX 3090.
Проблемы с rope scaling: как не потерять качество
Симптомы неверных rope-параметров выглядят не как ошибка, а как деградация: модель путает порядок абзацев, теряет факт из середины документа, отвечает на вопрос по первой странице, когда нужная информация лежала на последней. Проверять это нужно целенаправленно, а не «на глазок».
Простой тест: положите в начало промпта уникальную строку (например, выдуманный набор символов), набейте середину нейтральным текстом и задайте вопрос об этой строке в конце. Прогоните на 100K, 500K и 1M токенов. Если на 100K модель отвечает, а на 1M начинает выдумывать, дело либо в масштабировании позиций, либо в том, что окно внимания модели физически не рассчитано на такую длину. Понижение factor, использование рекомендованных авторами модели значений mscale и отказ от 1M в пользу 512K часто дают более полезный результат, чем попытка выжать миллион ради цифры в конфиге.
Спекулятивное декодирование: когда оно не помогает
MTP не даёт ускорения, когда доля принятых предсказаний низкая. Это типично для творческих задач, длинных рассуждений и диалогов без чёткой структуры. Ситуацию усугубляет длинный контекст: спекулятивные токены увеличивают нагрузку на кэш, а суммарный выигрыш времени на декодировании может не перекрыть потери памяти.
Рабочий порядок: сначала поднимите сервер без спекулятивного декодирования и замерьте decode на своём типовом промпте. Потом включите MTP с одним спекулятивным токеном, затем с двумя или тремя. Останавливайтесь на значении, после которого прирост скорости перестаёт расти. Замеряйте prefill и decode отдельно: MTP влияет в основном на второе.
Пошаговый чек-лист для запуска
- Проверьте версию vLLM и её поддержку NVFP4, MTP и нужных парсеров: vllm --version и vllm serve --help.
- Откройте config.json модели и выпишите num_hidden_layers, num_key_value_heads, head_dim, max_position_embeddings, rope_scaling и поле типов слоёв, если оно есть.
- Посчитайте KV-кэш по формуле и сравните результат с суммарной VRAM карт.
- Убедитесь, что число GPU совпадает с tensor-parallel-size и головы внимания делятся нацело.
- Скачайте веса safetensors и проверьте целостность файлов перед первым запуском.
- Соберите команду и запустите её вручную, без обёрток и контейнеров, чтобы видеть полный лог.
- Найдите в логе строку о размере KV-кэша: число доступных токенов должно быть не меньше max-model-len.
- Проверьте на коротком промпте: обычный ответ, вызов инструмента, блок рассуждений.
- Прогоните тест с фактом в начале промпта на 100K, 500K и 1M токенов.
- Замерьте prefill и decode отдельно на 10K и на 200K токенов, затем включайте MTP и кэш префиксов по одному изменению.
Шаблон запуска, в котором имена части параметров нужно сверить со справкой вашей версии:
vllm serve /models/Qwen3.8-27B-NVFP4 \
--tensor-parallel-size 4 \
--max-model-len 1000000 \
--kv-cache-dtype fp8 \
--speculative-config '{"method": "mtp", "num_speculative_tokens": 1}' \
--rope-scaling '{"rope_type": "yarn", "factor": 4.0, "original_max_position_embeddings": 262144}' \
--reasoning-parser qwen3 \
--tool-call-parser qwen3 \
--enable-auto-tool-choice \
--enable-chunked-prefill \
--enable-prefix-caching \
--gpu-memory-utilization 0.90 \
--max-num-seqs 4
Число спекулятивных токенов начните с единицы и повышайте по результатам замеров. Кэш префиксов полезен, если в модель регулярно уходит один и тот же длинный документ.
Альтернативы и сравнение
llama.cpp работает с GGUF-квантами и NVFP4 не поддерживает, зато неприхотлив к железу и хорошо ведёт себя на потребительских картах. Логика выбора там строится вокруг размера контекста и квантования KV-кэша, как в материале про Qwen3.8-27B в true Q4_K_M на RTX 5080 с 16 ГБ VRAM.
TensorRT-LLM даёт максимальную скорость на NVIDIA и поддерживает FP4 на Blackwell, но движок собирается под конкретную конфигурацию, и любое изменение модели или железа означает пересборку. SGLang близок к vLLM по возможностям, силён в структурированном выводе и агентных сценариях. ExLlamaV2 ориентирован на потребительские GPU и собственные форматы квантования, длинные контексты там упираются в VRAM быстрее.
NVFP4 остаётся форматом NVIDIA, поэтому выбор движка фактически ограничен теми, кто реализовал под него ядра. Для 1M контекста на четырёх картах vLLM выигрывает гибкостью: те же веса можно запустить с fp8 KV-кэшем, с MTP и без него, с парсерами и без, не меняя формат модели.
Итоги: что важно запомнить
Конфигурация собрана грамотно, но два её параметра критичны и оба проверяются до запуска: объём KV-кэша в fp8 и соответствие rope-параметров базовому окну модели. При архитектуре с 64 слоями и 8 KV-головами миллион токенов требует около 122 ГиБ кэша, то есть 30,5 ГиБ на карту при tensor-parallel-size 4, и это отсекает четырёхкарточные сборки с 24 и 32 ГБ.
Дальше по значимости идут имена: метода спекулятивного декодирования, парсера инструментов и параметра предзагрузки safetensors. Все три менялись между версиями vLLM, и все три проверяются через vllm serve --help за минуту.
Практический план на вечер: посчитать KV-кэш под свои карты, поднять сервер без MTP, прогнать тест с фактом в начале промпта на 100K и 1M токенов, и только потом включать спекулятивное декодирование, сверяя decode до и после. Если после всех проверок качество на 1M всё равно проседает, снижение до 512K почти всегда даёт более полезный результат, чем миллион токенов формально в конфиге.