Разделение prefill и decode (PD)
Разделение prefill/decode (PD) разбивает развёртывание на независимо работающие роли: prefill вычисляет KV-кэш промпта, decode генерирует по нему токены, а стоящий перед ними маршрутизатор проводит каждый запрос через обе роли. KV-кэш передаётся между ними по сети вместо повторного вычисления.
У двух фаз разные потребности. Prefill ограничен вычислительной мощностью и выигрывает от широкого тензорного параллелизма; decode ограничен пропускной способностью памяти и выигрывает от множества одновременных последовательностей. Единое развёртывание вынуждено использовать один набор параметров для обеих фаз. Разделение позволяет подобрать ресурсы и настройки каждой стороны и их соотношение под нагрузку: длинным промптам нужно больше prefill, длинной генерации — больше decode.
У разделения есть издержки: во время обработки запрос пересекает сеть, вместо одной роли нужно поддерживать три, а доступность группы ограничена наименее доступной ролью. Разделение оправдано, когда требования двух фаз действительно конфликтуют.
Когда это помогает, а когда нет
Три вопроса решают это:
- Нужны ли для предварительного заполнения и декодирования разные параметры? Если один набор параметров двигателя приемлемо обслуживает обе фазы, дезагрегация добавляет оборудование без увеличения пропускной способности.
- Далеко ли соотношение подсказки к генерации от 1:1? Выгода достигается за счет независимого определения размера двух сторон, что дает результат только тогда, когда они загружены по-разному.
- Достаточно ли быстрая передача KV? Кэш KV подсказки пересекает сеть при каждом запросе. При использовании медленной или общей ссылки время передачи заменяет сохраненное вами время предварительного заполнения.
Одиночный графический процессор или рабочая нагрузка, запросы и генерации которой короткие, обычно будут работать быстрее без него.
Развертывание дезагрегированной модели
- Разверните модель как обычно, затем разверните раздел
Disaggregationи включите его. - Выберите PD режим. Режим — это один полный набор — двигатель, разъем KV и маршрутизатор — поэтому разъем, его переменные квитирования и порты определяются этим одним выбором, и ни один из них не отображается в форме. Доступные режимы зависят от серверной части; режим, созданный для другого движка, отображается отключенным с указанием причины.
- Установите реплики каждой роли. Вот такое соотношение: 2 префилла и 1 декодирование — это группа 2P1D.
- Переопределить настройки для каждой роли, если две стороны различаются. Каждое наследуемое поле принимает значение уровня развертывания, поэтому необходимо вводить только различия.
- Сохранить.
Маршрутизатор управляется за вас: его образ, команда, адреса одноранговых узлов и путь работоспособности берутся из рецепта режима, поэтому форма не запрашивает их. Что он действительно запрашивает, так это то, чего рецепт не может знать — собственные аргументы и среду маршрутизатора — и аргументы соединения, полученные платформой, помещаются в тот же список, поэтому вы можете видеть, что было визуализировано, а не доверять этому. Строка, которую вы оставляете нетронутой, снова удаляется при сохранении, и сервер продолжает ее рендеринг.
Маршрутизатор структурно представляет собой одну реплику, поэтому нет необходимости устанавливать счетчик: вторая разделит кэш префиксов и предоставит группе два адреса.
Если нужен собственный маршрутизатор, выберите режим custom и задайте образ и команду запуска у развёртывания, а не роли маршрутизатора: роль наследует оба значения. Один образ должен содержать и маршрутизатор, и движок, выбирая запускаемый компонент по роли. Это требует той же настройки, что и указание образа в двух местах, но избавляет от необходимости синхронизировать второе место.
Как начинается группа
Порядок не случаен и его стоит знать, потому что именно его вы увидите, когда подойдет группа:
- Экземпляры prefill и decode создаются вместе, в одной транзакции. При поддержке группового допуска они также допускаются вместе: частично запущенная группа не способна обслуживать запросы.
- Как только у каждого из них появится работающий член, создается маршрутизатор. Он создается последним, поскольку его командная строка содержит адреса его узлов, и эти адреса не существуют до тех пор, пока узлы не запустятся.
- Конечная точка группы регистрируется после включения маршрутизатора. Зарегистрирован только маршрутизатор — члены prefill и decode обслуживают API в форме OpenAI на своих собственных портах, но ни один из них не может ответить на весь запрос в одиночку.
Поэтому группа сообщает Partially ready некоторое время, прежде чем обслужить, и это нормальный путь, а не ошибка.
Чтение состояния группы
| Что вы видите | Что это значит |
|---|---|
Pending | Пока ни один участник не участвует. |
Partially ready | Участники активны, но группа по-прежнему не может работать — роль без готового участника или конечная точка маршрутизатора еще не зарегистрирована. |
Running | Каждая роль имеет хотя бы одного готового участника, и конечная точка зарегистрирована. |
Ratio 2:1 (currently 1:1) | Группа подает, но не в том соотношении, которое вы просили. Отображается рядом с состоянием, а не вместо него. |
Значок часов / Config changed | Действующие участники появились раньше показанной конфигурации, и до перезапуска роль невозможно масштабировать. См. Применение изменения конфигурации. |
Недостаток требуемого коэффициента является деградацией, а не состоянием: группа 3P1D, использующая 2P1D, все еще обслуживает, но не с той пропускной способностью, на которую вы рассчитывали.
Применение изменения конфигурации
Редактирование разделённого развёртывания не перезапускает его. Изменение сохраняется, группа помечается как работающая со старой конфигурацией, и до перезапуска ничего не меняется.
Это сделано намеренно. При поочерёдном перезапуске некоторое время вместе работали бы prefill с новой конфигурацией и decode со старой, а движки допускают такую пару. Например, при разной длине контекста рукопожатие и передача KV проходят успешно, короткие промпты обрабатываются правильно; ошибка возникает только для промпта длиннее окна decode, когда работа prefill уже выполнена. Неверный ответ хуже перезапуска, поэтому сначала останавливается вся группа и только затем запускается заново.
Для применения изменений нажмите Restart у развёртывания. Вся группа останавливается и создаётся заново с текущей конфигурацией. Перезапуск отдельных ролей не предлагается, поскольку именно он создавал бы описанную смешанную пару.
Масштабирование изменяет количество экземпляров каждой роли без перезапуска всей группы, при условии, что не ожидается никаких изменений конфигурации. Поведение маршрутизатора зависит от router.membership_update в pd-modes.yaml:
| Политика | Режимы поставки | Поведение при масштабировании или восстановлении участников |
|---|---|---|
dynamic | vLLM NIXL, SGLang Mooncake/NIXL, vLLM Ascend Mooncake | GPUStack обновляет маршрутизатор через API членства. |
restart | vLLM Mooncake | GPUStack заменяет маршрутизатор после того, как добавленные элементы GPU готовы, обновляя одноранговые узлы запуска и метаданные начальной загрузки. Удаленные или перезапущенные участники запускают обновление, как только у обеих сторон появляется пригодный для использования одноранговый узел. Уцелевшие экземпляры графического процессора продолжают работать, но сервис на короткое время недоступен, а запросы в реальном времени могут завершиться неудачей. |
manual | Пользовательский | Оператор обновляет одноранговые узлы маршрутизатора и метаданные начальной загрузки и при необходимости перезапускает маршрутизатор. |
Если пользователь задал собственную команду запуска маршрутизатора, состав группы обновляется вручную, даже когда выбранный режим поддерживает автоматическое обновление. Форма развёртывания и редактор реплик показывают политику из каталога. При удалении экземпляра также появляется предупреждение, если восстановление требует замены маршрутизатора или действий оператора.
Снимки статического маршрутизатора включают идентификатор экземпляра, генерацию перезапуска, HTTP-адрес и именованные порты. Таким образом, замена или перезапуск по тому же адресу по-прежнему обновляет метаданные начальной загрузки Mooncake. Существующие маршрутизаторы без моментального снимка заменяются один раз, когда их одноранговые узлы будут готовы.
Пока сохранённое изменение ожидает перезапуска, увеличить число реплик роли нельзя. Новый участник получил бы изменённую конфигурацию и работал бы рядом со старыми, создав описанную смешанную пару. Увеличение числа реплик применяется при перезапуске вместе с изменением конфигурации.
Масштабирование вниз по-прежнему применяется немедленно, независимо от того, ожидается редактирование или нет: потеря члена не может привести к образованию несовпадающей пары.
Использование общего кэша KV с ним.
Дезагрегированное развертывание и общий KV-кеш являются скорее взаимодополняющими, чем альтернативными вариантами, и GPUStack объединяет их за вас: подключите службу кэширования, как обычно, и два соединителя объединятся в одну конфигурацию, которую принимает движок.
Для каждой роли vLLM с кэшем создаётся MultiConnector[PD, Store]. Обёртка использует kv_producer для prefill и kv_consumer для decode. Каждый вложенный коннектор сохраняет роль, требуемую его транспортом или хранилищем.
| Режим | Без кэша | Внутрипроцессный кэш | Служба кэширования |
|---|---|---|---|
vllm-nixl (по умолчанию NVIDIA) | NixlConnector | NixlConnector + LMCacheConnectorV1 | Только предварительное заполнение: NixlConnector + LMCache; Только LMCache |
vllm-mooncake (NVIDIA) | MooncakeConnector | MooncakeConnector + LMCacheConnectorV1 | MooncakeConnector + MooncakeStoreConnector; Только Mooncake |
vllm-ascend-mooncake | Разъем Mooncake PD, выбранная моделью | Разъем PD + SimpleCPUOffloadConnector | Разъем PD + AscendStoreConnector; Только Mooncake |
Дочерний элемент LMCache в процессе использует kv_both в обеих ролях. Общий LMCache поддерживается при предварительном заполнении только для NIXL. Его соединитель MP может загружать кэшированный префикс, даже если MultiConnector назначает загрузку запроса NIXL; второе асинхронное завершение может привести к сбою декодирования (vLLM #53049). NVIDIA Mooncake Store использует kv_both при предварительном заполнении и kv_consumer при декодировании. Ascend Store использует kv_producer при предварительном заполнении и kv_consumer при декодировании. Эти конфигурации соответствуют vLLM 0.29 Mooncake Store и vLLM-Ascend 0.23 KV пулу.
Ascend выбирает MooncakeConnectorV1 для стандартных макетов KV, включая Qwen3, GLM-5 и DeepSeek V3.1, и MooncakeHybridConnector для гибридных макетов, таких как DeepSeek V4, V4 Flash и V4 Pro. Выбор считывает предварительно подготовленную конфигурацию, включая сжатое внимание и повторяющиеся уровни, а не имя развертывания. Обе роли используют один и тот же сохраненный выбор. Если метаданные макета недоступны, гибридный соединитель обрабатывает резервный вариант стандартного макета. GPUStack сохраняет этот выбор для входных данных модели развертывания, поэтому метаданные, которые становятся доступными после запуска члена, не могут заставить другого участника использовать другой соединитель.
Совместная работа кэша с NIXL использует контракт реальных блоков, доступный с vLLM 0.26. Конфигурации режимов PD объявляют поддерживаемых провайдеров кэша и настройки коннекторов; они не задают и не проверяют версии служб кэширования или требования к клиентским пакетам. За совместимость службы и клиента отвечают провайдеры кэша. Зависимость runner остаётся на 0.5.4. Для обоих режимов NVIDIA базовой версией комплектного runner служит vLLM 0.27.1 или новее: эти CUDA-образы содержат vllm-router 0.1.15, запускаемый из образа движка. По той же причине режим Ascend использует базовую версию CANN 0.23. Эти минимумы относятся к образам GPUStack, а не к первой upstream-версии каждого коннектора.
Для NVIDIA Mooncake с общим Store дополнительно требуется базовая версия 0.29. Runner-образы GPUStack 0.29 включают исправление отклонённой загрузки: вложенный коннектор Store должен пропускать загрузку, назначенную коннектору PD. MultiConnector передаёт невыбранному Store ноль внешних токенов, а исправление проверяет can_load перед запуском ожидающей загрузки. Это также касается попаданий в кэш на стороне decode. Для штатного vLLM 0.29 необходим перенос этого исправления; upstream 0.30 уже включает его. См. исправление runner.
Подключайте службу отдельно к каждой роли, которой она нужна. Раскрывающийся список фильтрует службы по режиму, провайдеру и роли; API проверяет эти правила совместимости. Диапазоны версий движка PD носят рекомендательный характер: при выборе встроенной версии вне диапазона появляется предупреждение для режима или включённого кэша, но развёртывание и подключение кэша разрешены. Для пользовательских версий и явно указанных образов предупреждений нет. Ошибки запуска отражаются в состоянии и журналах экземпляра.
Сейчас автоматический выбор runner учитывает бэкенд, среду выполнения ускорителя и платформу; версии не фильтруются по режиму PD или диапазонам совместной работы с кэшем. SGLang подключает LMCache или MeshFusion флагами и конфигурационным файлом, а не через MultiConnector vLLM.
Для NVIDIA Mooncake образ инференса должен содержать среду выполнения механизма передачи и поддержку RDMA. Runner-образы GPUStack также экспортируют метрики передачи и истечения срока кэша Mooncake; счётчики передачи считываются на prefill. GPUStack выделяет уникальный bootstrap-порт prefill и передаёт его маршрутизатору. В vllm-router 0.1.15 изменение состава группы перезапускает маршрутизатор для обновления bootstrap-метаданных, что может ненадолго прервать трафик.
Требования к сети
О Kubernetes: что поды запрашивают у кластера
Членам группы необходимы хост-сеть, хост-порт, хост-IPC и монтирование устройств — все четыре одновременно, что превышает уровень допуска Pod Security baseline. GPUStack соответственно помечает принадлежащие ему пространства имен; см. раздел Пространства имен и безопасность доступа к модулям, чтобы узнать, что это означает для кластера, который вы создали сами, включая то, что оно не охватывает.
Интерфейс передачи KV-кэша
KV-кэш пересекает сеть при каждом запросе, и интерфейс, через который он проходит, имеет значение. Предоставленный самому себе, транспортный уровень с радостью подберет контейнерный мост (docker0, br-*, flannel.1) и объявит адрес, к которому партнер не может добраться — что происходит из-за ошибки соединителя во время установления связи, а не из-за ошибки конфигурации.
GPUStack выводит интерфейс для каждого воркера:
- Один маршрутизируемый интерфейс — используется. Нечего настраивать.
- Несколько маршрутизируемых интерфейсов — GPUStack отказывается угадывать. Установите
--kv-transfer-ifname(или ключ конфигурации воркераkv_transfer_ifnameилиGPUSTACK_KV_TRANSFER_IFNAME) на этом воркере для интерфейса, передающего KV-трафик. В журнале воркеров перечислены найденные им кандидаты.
Установите его явно на любой машине с более чем одной структурой — сетевой адаптер управления плюс сетевой адаптер RDMA — это обычный случай, и сетевой адаптер управления почти никогда не является правильным ответом.
Порты
Каждой роли нужен свой собственный HTTP-порт, а также порты, необходимые для ее соединителя, и GPUStack выделяет их из диапазона сервисных портов воркера как смежные полосы, чтобы два участника на одном хосте не могли конфликтовать.
Диапазон по умолчанию содержит 64 порта, что достаточно для большинства развертываний, но не для всех: элемент Tensor-Parallel-8, использующий соединитель с портом каждого ранга, использует девять из них. Если вы используете несколько таких участников на одном хосте, расширьте --service-port-range.
Три типа портов, с которыми GPUStack не может справиться
Некоторые соединители связывают порты, которые выбираются движком во время выполнения и не объявлены нигде, где GPUStack может прочитать:
- Ранговый порт SGLang, взятый из эфемерного диапазона хоста;
- Порты рукопожатия механизма передачи для каждого ранга Mooncake;
- Порты сегмента Mooncake.
Для этих портов значение в конфигурационном файле не совпадает с фактически привязанным портом, поэтому GPUStack не может ни зарезервировать их, ни объявить как порты хоста. Они наблюдались в диапазонах 15000–17000 и 20000–20800, которые не пересекаются с диапазоном службы по умолчанию (40000–40063). Именно разнесение диапазонов предотвращает конфликт, а не резервирование.
Не переносите --service-port-range в эти диапазоны. В измеренном случае диапазон коннектора начинался с 20001: второй ранг участника пытался привязать 20003, но порт уже занимал механизм передачи, выбравший его несколькими мгновениями ранее. То же развёртывание запустилось с первой попытки, когда диапазон вернули к значению по умолчанию.
Если участник несколько раз перезагружается и никогда не достигает Running, проверьте его журнал двигателя на наличие Address already in use.
Дедупликация портов также охватывает только один воркер. Второй рабочий GPUStack на том же хосте или контейнер, который вы запустили вручную, находится за его пределами.
Ограничения
Конфигурация, которая приводит к тому, что группа дает неверные ответы, отклоняется во время сохранения, а не во время выполнения:
- Предварительное заполнение и декодирование должны объявлять одинаковую длину контекста. Ни один движок не проверяет это, и несоответствие невидимо до тех пор, пока не появится длинное приглашение.
- Тензорный параллелизм декодирования должен быть не ниже предварительного заполнения. Движок утверждает это, но сообщает об этом как о внутренней ошибке внутри декодирования, а не как о проблеме конфигурации.
- Тип данных, тип данных KV-кэша, размер блока и структура KV-кэша должны согласовываться между двумя сторонами.
- Ядро роли должно быть таким, который может быть настроен выбранным режимом. Для смешивания механизмов между ролями требуется режим
custom, в котором параметры подключения предоставляются вами. - Дезагрегированное развертывание с использованием режима
customне может также использовать расширенный кэш KV. Каждый другой режим объединяет два режима (см. Использование общего кэша KV с ним);customвообще не вводит конфигурацию коннектора, поэтому не из чего составлять кеш — напишите объединенную конфигурацию самостоятельно. - Запланированное масштабирование и дезагрегирование нельзя совмещать.
Для vllm-nixl проверки совместимости включают переменные среды, специфичные для роли. VLLM_KV_CACHE_LAYOUT принимает эквивалентные псевдонимы (NHD/LBNHC и HND/LBHNC). В моделях MLA VLLM_MLA_DISABLE должен создавать совместимые скрытые представления KV. Для рекуррентной передачи состояний NIXL требуется VLLM_SSM_CONV_STATE_LAYOUT=DS в поддерживаемых средах выполнения. Для локального внимания VLLM_ALLOW_CHUNKED_LOCAL_ATTN_WITH_HYBRID_KV_CACHE проверяется вместе с флагами HMA. Также сравниваются варианты серверной части явного внимания.
Роль с ненастроенной средой наследует среду развертывания; его заменяет явный словарь, включая пустой словарь. Зависимые от модели проверки используют исходную конфигурацию и переопределения ролей. О неизвестных конфигурациях, версиях среды выполнения и схемах передачи сообщается через pairing_unverified. Сетевые адреса, порты, имена устройств и настройки производительности могут различаться в зависимости от роли. Экспериментальная перестановка компоновки по-прежнему зависит от направления двигателя и ограничений HMA.
Ограничения
- Маршрутизатор работает как одна реплика.
- Маршрутизатор
customразвёртывается ровно с заданной конфигурацией. Встроенные режимы задают upstream-маршрутизатору стандартный порог размыкателя цепи — десять неудачных запросов; у собственного маршрутизатора остаются его исходные настройки. - В режимах vLLM элемент предварительного заполнения, который возвращает ошибки, не выводится из ротации. Восходящий маршрутизатор обрабатывает только ошибку транспорта как сбой предварительного заполнения, поэтому протокол HTTP 500 из предварительного заполнения регистрируется, а запрос переходит к декодированию и возвращает 200 — измерено с заглушкой предварительного заполнения, не дающей каждый из двенадцати запросов, ни один из которых не открывал канал. Режимы SGLang питают выключатель. В любом случае, показатель Эффективность PD на панели группы — это то, что определяет участника, который прекратил вносить свой вклад; собственные счетчики запросов маршрутизатора этого не покажут.
- Сбой участника во время простоя обнаруживается при следующем запросе, а не заранее. Обнаружение происходит при обработке запросов, поэтому без трафика проверять нечего; фоновый обход маршрутизатора выполняется раз в минуту. Первые запросы после простоя повторяются, а затем направляются в обход неисправного участника.
- Все члены группы должны использовать один и тот же тип графического процессора; групповое смешивание типов карт не может быть допущено атомарно.
- Роли ограничены: предварительное заполнение, декодирование и маршрутизация.
- Маршрутизация с учётом префиксов выбирает prefill, а не decode. Общий префикс полезен на фазе prefill, поэтому встроенные режимы выбирают её участника по приблизительному совпадению префикса. Decode удерживает запрос на всё время генерации, поэтому запросы распределяются между его участниками равномерно по кругу. Все встроенные режимы, включая
vllm-ascend-mooncake, явно задают--prefill-policy cache_awareи--decode-policy round_robin. Если нужна другая политика, переопределите их в параметрах роли маршрутизатора.