Перейти к содержанию
Публикация AiManual

Запуск Qwen3.8-27B-NVFP4 с контекстом 1M через vLLM: разбор флагов, памяти и узких мест

Разбираем конфигурацию запуска Qwen3.8-27B-NVFP4 в NVFP4 через vLLM с контекстом до 1M токенов: сколько VRAM съедает fp8 KV-кэш, что делают tensor-parallel-size

Коротко

Что будет в материале

  1. 01

    Что проверяем в конфигурации: краткий ответ

  2. 02

    Требования к железу для запуска Qwen3.8-27B-NVFP4 с контекстом 1M

  3. 03

    Разбор ключевых флагов vLLM: что они делают и как влияют на работу

  4. 04

    Типичные ошибки и точки оптимизации

Что проверяем в конфигурации: краткий ответ

Состав флагов для задачи «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 влияет в основном на второе.

Пошаговый чек-лист для запуска

  1. Проверьте версию vLLM и её поддержку NVFP4, MTP и нужных парсеров: vllm --version и vllm serve --help.
  2. Откройте config.json модели и выпишите num_hidden_layers, num_key_value_heads, head_dim, max_position_embeddings, rope_scaling и поле типов слоёв, если оно есть.
  3. Посчитайте KV-кэш по формуле и сравните результат с суммарной VRAM карт.
  4. Убедитесь, что число GPU совпадает с tensor-parallel-size и головы внимания делятся нацело.
  5. Скачайте веса safetensors и проверьте целостность файлов перед первым запуском.
  6. Соберите команду и запустите её вручную, без обёрток и контейнеров, чтобы видеть полный лог.
  7. Найдите в логе строку о размере KV-кэша: число доступных токенов должно быть не меньше max-model-len.
  8. Проверьте на коротком промпте: обычный ответ, вызов инструмента, блок рассуждений.
  9. Прогоните тест с фактом в начале промпта на 100K, 500K и 1M токенов.
  10. Замерьте 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 почти всегда даёт более полезный результат, чем миллион токенов формально в конфиге.

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