Disaggregated prefill and decode для LLM-инференса в Amazon SageMaker HyperPod
Когда prefill и decode работают на одном GPU, длинные запросы останавливают генерацию токенов для всех параллельных обращений. Disaggregated Prefill and Decode (DPD) устраняет это влияние, разделяя каждую фазу между отдельными пулами GPU, соединёнными через Elastic Fabric Adapter (EFA) с Remote Direct Memory Access (RDMA). Инференс large language model (LLM) состоит из двух принципиально разных фаз. Prefill вычислительно ограничен: он обрабатывает весь входной prompt параллельно и формирует начальный key-value (KV) cache. Decode ограничен пропускной способностью памяти: он генерирует по одному токену за раз и требует значительной bandwidth к весам модели и растущему KV cache. Разделяя эти фазы на специализированные движки, можно задавать разные стратегии параллелизма для каждой из них. Такое разделение позволяет независимо настраивать time to first token (TTFT) и inter-token latency (ITL), надёжнее контролировать tail latency по сравнению с chunked prefill tuning и не допускать, чтобы длинные prefills блокировали текущие decode-запросы. vLLM повышает эффективность на одном узле за счёт continuous batching и PagedAttention. Однако организациям, которые разворачивают систему в масштабе, всё ещё приходится решать задачи оркестрации многонодовых деплоев и оптимизации routing.
В этой статье показано, как реализовать DPD с vLLM на Amazon SageMaker HyperPod с помощью HyperPod Inference Operator.
Когда использовать disaggregated inference
Разделение prefill и decode даёт наибольший эффект в потоковых нагрузках с длинным контекстом и высокой конкуренцией: чат-ассистентах, agentic pipelines, endpoints для анализа документов и Retrieval Augmented Generation (RAG) с крупными retrieved contexts. В таких сценариях один длинный prompt на совместно используемом GPU останавливает in-flight decode для всех остальных запросов, вызывая всплески per-token latency, которых DPD избегает по своей конструкции.
Рассмотрите DPD, если в вашей нагрузке есть:
- входные prompts, которые регулярно превышают 4,096 токенов;
- несколько параллельных пользователей или запросов;
- потоковые ответы, где важна стабильная доставка токенов;
- смешанный трафик с длинными и короткими prompts.
Совместное размещение prefill и decode проще, когда конкуренция за GPU не является проблемой: для batch или offline workload, оптимизирующих TTFT, для низкоконкурентных развёртываний или для трафика только с короткими prompts. Ниже порога routing фиксированная стоимость переноса KV cache через EFA RDMA перевешивает выгоду от изоляции decode. DPD router отправляет такие запросы напрямую в decoder. В результате один endpoint автоматически обслуживает смешанный длинный и короткий трафик без ручной логики routing.
DPD требует как минимум один prefill node и один decode node с RDMA-совместимой EFA-сетью. Поддерживаемые типы инстансов см. в разделе Deploy a DPD model endpoint to your HyperPod cluster.
Архитектура
Реализация DPD в HyperPod построена на router из vLLM Production Stack, а LMCache предоставляет слой переноса KV cache поверх NIXL и EFA. Развёртывание состоит из трёх компонентов плюс транспортного стека.

Интеллектуальный router
Router — это control plane. Он токенизирует каждый prompt и применяет настраиваемый порог по количеству токенов, чтобы определить, пойдёт ли запрос по disaggregated path или будет обработан end-to-end на decoder. Длинные prompts проходят через prefiller, а затем через decoder. Короткие prompts обходят prefiller, избегая cross-GPU KV transfer, который не окупается. Для disaggregated запросов router направляет prefiller на вычисление и отправку KV cache в decoder, а затем пересылает запрос в этот decoder для генерации. Он также поддерживает стратегии routing для каждого prefiller (prefixaware, kvaware, session, roundrobin) через intelligentRoutingSpec.routingStrategy, чтобы максимизировать cache locality между репликами.
Pod prefiller
Prefiller — это worker vLLM с LMCache в роли KV connector через LMCacheConnectorV1. Он вычисляет KV cache для длинных prompts и отправляет его в выбранный decoder через PD sender backend LMCache layer-by-layer, перекрывая вычисления и передачу, чтобы GPU не простаивали. LMCache также даёт каждому prefiller L1 CPU cache. Когда префикс повторяется (system prompts, multi-turn history, retrieval contexts), данные берутся из CPU memory без повторного вычисления на GPU. Это даёт заметный выигрыш по TTFT. Активация DPD на InferenceEndpointConfig автоматически разворачивает и connector, и cache.
Pod decoder
Decoder — это worker vLLM с LMCache в роли receiver. Он резервирует GPU memory (PD buffer, размер которого задаётся PD_BUFFER_SIZE) для входящих KV transfers. Он запускает полные CUDA graphs для decode kernel и начинает генерацию сразу после завершения передачи. Поскольку он никогда не выполняет prefill, decode latency остаётся стабильной под нагрузкой, а добавление длинного запроса не нарушает поток уже идущих токенов.
Передача KV cache
Передача KV cache использует четырёхуровневый стек (LMCache PD → NIXL → libfabric → EFA), который HyperPod собирает end-to-end. PD backend LMCache управляет put на стороне prefiller и retrieval на стороне decoder. NIXL предоставляет унифицированную абстракцию памяти для GPU, CPU и удалённых peers и выбирает нужную RDMA-операцию. Provider libfabric открывает EFA как kernel-bypass, GPU-Direct RDMA, убирая host CPU из data path. Это делает стоимость передачи незначительной по сравнению с вычислением prefill: на ml.p5.48xlarge с 3,200 Gbps EFA передача 8,000 токенов для Llama 3.3 70B занимает несколько миллисекунд. HyperPod поставляет этот стек прединтегрированным, поэтому вы выбираете worker image с поддержкой DPD, а operator на каждом pod настраивает connector, NIXL и EFA.
Обзор развёртывания
Disaggregated Prefill and Decode (DPD) — это функция, реализованная в HyperPod Inference Operator. В этом разделе описаны prerequisites, установка inference operator и развёртывание inference endpoint, использующего DPD для эффективного обслуживания модели Llama 70B.
Prerequisites и установка HyperPod Inference Operator
Убедитесь, что у вас есть AWS Command Line Interface (AWS CLI), доступ kubectl к вашему кластеру HyperPod, токен HuggingFace и достаточная service quota. Настройте локальную конфигурацию kubectl для подключения к кластеру HyperPod. Подробнее см. Disaggregated Prefill and Decode for HyperPod inference.
DPD требует HyperPod Inference Operator версии 3.2 или новее. На новых HyperPod EKS-кластерах operator устанавливается по умолчанию. Инструкции по установке, настройке и обновлению см. в материале Unlock efficient model deployment: simplified Inference Operator setup on Amazon SageMaker HyperPod.
Проверьте версию operator, выполнив:
kubectl get deployment hyperpod-inference-operator-controller-manager \
-n hyperpod-inference-system \
-o jsonpath='{.spec.template.spec.containers[?(@.name=="manager")].image}{"\n"}'
Вывод — это полный reference контейнерного image. Тег в конце кодирует версию, например:
XXXXXXXXXXX.dkr.ecr.us-east-2.amazonaws.com/hyperpod-inference-operator:v3.2
Если версия operator неактуальна, обновите её перед продолжением, следуя инструкциям по обновлению в HyperPod Inference Operator Release Notes.
Развёртывание DPD model endpoint в кластере HyperPod
В примере мы разворачиваем модель Meta Llama 3.3 70B на двух инстансах ml.p5.48xlarge. Перед продолжением убедитесь, что инстансы доступны в instance group внутри кластера HyperPod. Для DPD inference deployments выбирайте типы инстансов, поддерживающие и NVLink, и EFA. EFA должна поддерживать RDMA в режиме read и write. Это включает семейства P5 и P6 в AWS. Обратите внимание, что инстансы должны находиться в одной Availability Zone (AZ) для высокоскоростной EFA-связи. Хотя семейства G6, G6e и G7e тоже поддерживают EFA с RDMA read/write, производительность на многопроцессорных GPU-инстансах ограничивается связью GPU-to-GPU через PCIe.
Worker image для inference deployment должен включать vLLM, LMCache, NVIDIA NIXL и EFA libfabric provider. На момент публикации поддерживаются два варианта image:
- Open source LMCache: lmcache/vllm-openai v0.4.3.
- SageMaker Deep Learning Container (DLC): vllm:server-hyperpod-cuda-v1.1.
Расположение checkpoint модели
HyperPod Inference Operator поддерживает широкий набор источников загрузки checkpoint, включая бакеты Amazon Simple Storage Service (Amazon S3), файловые системы Amazon FSx, прямое получение из HuggingFace и instance NVMe storage. В этой статье checkpoint модели загружается из бакета Amazon S3.
Убедитесь, что вы загрузили нужный checkpoint в S3 bucket в том же Region, что и кластер HyperPod. Если вы этого ещё не сделали, задайте имя bucket и токен HuggingFace, затем скачайте Meta Llama 3.3 70B Instruct из HuggingFace и синхронизируйте его с Amazon S3. Для высокоскоростной передачи в Amazon S3 рекомендуется выполнять это с экземпляра Amazon Elastic Compute Cloud (Amazon EC2).
export MODEL_BUCKET=<YOUR_BUCKET>
export MODEL_PREFIX=Llama-3.3-70B-Instruct
export AWS_REGION=<CLUSTER_REGION>
export HF_TOKEN=<YOUR_HUGGINGFACE_TOKEN>
pip install -U "huggingface_hub[cli]" "huggingface_hub[hf-transfer]"
HF_HUB_ENABLE_HF_TRANSFER=1 hf download meta-llama/Llama-3.3-70B-Instruct \
--local-dir ./$MODEL_PREFIX \
--token "$HF_TOKEN"
aws s3 sync ./$MODEL_PREFIX \
s3://$MODEL_BUCKET/$MODEL_PREFIX/ \
--region "$AWS_REGION"
Подготовьте manifest развёртывания модели и при необходимости измените переменные окружения:
export DEPLOYMENT_NAME="dpd-test-deployment"
export ENDPOINT_NAME="dpd-test"
export MODEL_NAME="meta-llama-3-3-70b"
export NAMESPACE="default"
export INSTANCE_TYPE="ml.p5.48xlarge"
export GPUS_PER_NODE="8"
export MODEL_IMAGE="lmcache/vllm-openai:v0.4.3"
Полный YAML для развёртывания см. в Deploy a DPD endpoint.
Поля manifest, важные для DPD
Большинство полей InferenceEndpointConfig общие для DPD и не-DPD endpoints и описаны в документации Inference Operator. Ниже перечислены поля, обязательные или имеющие иную семантику для DPD.
spec.pdSpec: объявляет топологию prefill/decode и задаёт аргументы. Наличие этого поля и делает endpoint disaggregated: operator создаёт отдельные объекты Deployment для prefill и decode и соединяет их через router и LMCache PD backend.
replicas: масштабирует prefill и decode независимо.resources: применяется к pod spec роли. Верхнеуровневоеworker.resourcesигнорируется для DPD pod. Значения для роли имеют приоритет.routingThreshold: порог длины в токенах, выше которого запросы идут по disaggregated path. Ниже порога запросы минуют prefiller и сразу идут к decoder.args: флаги vLLM, специфичные для роли; они объединяются сworker.argsпри старте. Флаги, уже присутствующие вworker.args, заменяются значением для роли, а отсутствующие добавляются.
spec.worker.environmentVariables: эти переменные окружения применяются одинаково к контейнерам prefiller и decoder. Отдельного поля environment-variable для роли пока нет. Для поведения на уровне роли используйте вместо этого pdSpec.{prefillSpec,decodingSpec}.args.
Дополнительные сведения о переменных окружения см. в Deploy a DPD endpoint.
Применение manifest и проверка развёртывания
kubectl apply -f inference_endpoint_dpd_config.yaml
Operator создаёт два объекта Deployment в вашем namespace и один router Deployment в hyperpod-inference-system. Загрузка image и модели занимает несколько минут. Сначала pods переходят в ContainerCreating, затем становятся Running, когда контейнеры поднимаются. Посмотрите pods в обоих namespaces:
kubectl get pods -A \
| grep -E "prefill-${DEPLOYMENT_NAME}|decode-${DEPLOYMENT_NAME}|${DEPLOYMENT_NAME}-${NAMESPACE}-router"
NAMESPACE NAME READY STATUS RESTARTS AGE
default prefill-dpd-test-deployment-XXXX 3/3 Running 0 7m
default decode-dpd-test-deployment-XXXX 3/3 Running 0 7m
hyperpod-inference-system dpd-test-deployment-default-router-XXXX 2/2 Running 0 7m
Каждый model pod содержит 3 контейнера: worker vLLM, Nginx reverse proxy и OpenTelemetry collector. Router pod содержит 2 контейнера (router, otel). Состояние готовности сообщает условие IEC:
kubectl get inferenceendpointconfig ${DEPLOYMENT_NAME} -n ${NAMESPACE} \
-o jsonpath='{.status.conditions[0].message}{"\n"}'
DPD prefill and decode deployments are ready
Вызов endpoint и проверка KV transfer
Когда endpoint готов, отправьте короткий и длинный prompt, чтобы задействовать оба маршрута routing. Проверьте логи prefiller и decoder, чтобы подтвердить, что KV cache передаётся по EFA. Приведённые ниже команды предполагают, что IEC развёрнут с ${NAMESPACE}, ${ENDPOINT_NAME} и ${DEPLOYMENT_NAME}, как указано в предыдущем manifest.
Получите нужные имена pods и URL router:
PREFILL_POD=$(kubectl get pod -n ${NAMESPACE} \
-l 'inference.sagemaker.aws.amazon.com/dpd-role=prefill' \
-o jsonpath='{.items[0].metadata.name}')
DECODE_POD=$(kubectl get pod -n ${NAMESPACE} \
-l 'inference.sagemaker.aws.amazon.com/dpd-role=decode' \
-o jsonpath='{.items[0].metadata.name}')
ROUTER_POD=$(kubectl get pods -n hyperpod-inference-system -o name \
| grep -- "${DEPLOYMENT_NAME}-${NAMESPACE}-router" | head -1)
ROUTER_URL=http://${DEPLOYMENT_NAME}-${NAMESPACE}-routing-service.hyperpod-inference-system.svc.cluster.local:443/v1/chat/completions
Короткий prompt (ниже порога, прямой путь к decoder)
Запустите короткий prompt через pod внутри кластера, который вызывает endpoint:
kubectl run curl-short --rm -it --image=curlimages/curl --restart=Never -- \
curl -s -k -X POST "$ROUTER_URL" \
-H "Content-Type: application/json" \
-d '{
"model": "/opt/ml/model",
"messages": [{"role": "user", "content": "What is disaggregated prefill-decode in one sentence?"}],
"max_tokens": 80,
"temperature": 0.0
}'
{
"id": "chatcmpl-7-...",
"object": "chat.completion",
"model": "/opt/ml/model",
"choices": [{
"index": 0,
"message": {"role": "assistant", "content": "Disaggregated prefill-decode is an inference architecture that ..."},
"finish_reason": "stop"
}]
}
Длинный prompt (выше порога, DPD path)
Prompt длиной выше routing threshold 4,096 токенов проходит через prefiller, а затем через decoder для генерации токенов. В следующем примере создаётся prompt примерно на 6,000 токенов путём повторения предложения:
kubectl run curl-long --rm -it --image=curlimages/curl --restart=Never -- sh -c '
ROUTER="'"$ROUTER_URL"'"
LONG=""
i=0; while [ $i -lt 600 ]; do LONG="${LONG}The quick brown fox jumps over the lazy dog. "; i=$((i+1)); done
curl -s -k -X POST "$ROUTER" \
-H "Content-Type: application/json" \
-d "{\"model\":\"/opt/ml/model\",\"messages\":[{\"role\":\"user\",\"content\":\"${LONG}\"}],\"max_tokens\":30,\"temperature\":0.0}"
'
{
"id": "chatcmpl-7-...",
"object": "chat.completion",
"model": "/opt/ml/model",
"choices": [{
"index": 0,
"message": {"role": "assistant", "content": "The text is a repetitive sequence of ..."},
"finish_reason": "length"
}]
}
Чтобы подтвердить, что invocation использовал disaggregated path, можно проверить логи pod router:
kubectl logs $ROUTER_POD -n hyperpod-inference-system -c router-container --tail=20 \
| grep -E "Conditional routing|prefill selection|prefill time|to decoder"
Для длинного prompt можно увидеть такой вывод:
[INFO] Conditional routing: estimated_tokens=6750, threshold=4096, disaggregate=True
[INFO] DPD prefill selection: delegating to PrefixAwareRouter with 1 endpoints
[INFO] DPD prefill selection: PrefixAwareRouter selected http://10.1.54.203:8081
[INFO] <req-id> prefill time (TTFT): 7.1913
[INFO] Routing request <req-id> to http://10.1.54.203:8081 at 1780486382.5954, process time = 7.1929
[INFO] Routing request <req-id> to decoder http://10.1.172.158:8081 at 1780486382.6098, process time = 0.0144
disaggregate=True подтверждает, что запрос прошёл через prefiller. Две строки Routing request показывают сначала hop prefill, а затем hop decode.
Рекомендации по масштабированию
Сейчас DPD поддерживает один decoder replica и несколько prefiller replicas. Это означает, что вы масштабируете prefill capacity независимо, а decoder остаётся фиксированным в одном экземпляре.
Начните с соотношения prefill-to-decode 1:1 для сбалансированных нагрузок (chat, code generation), где длины входа и выхода сопоставимы. Переходите к 2:1 или 3:1, когда нагрузка смещена в сторону prefill: summarization, classification или RAG с длинными retrieved contexts. Такое соотношение уместно, если вы видите, что TTFT растёт под нагрузкой, а per-token output latency (TPOT) остаётся стабильной.
При нескольких prefiller задайте intelligentRoutingSpec.routingStrategy для вашей нагрузки. Используйте kvaware для сценариев с повторяющимися префиксами: это максимизирует L1 cache hits между prefiller partitions. Используйте session для multi-turn conversations, где выгодно удерживать контекст пользователя на одном prefiller.
Если вместо этого растёт TPOT и output throughput выходит на плато, несмотря на доступность prefiller, значит, перегружен единственный decoder. В этом случае увеличьте PD_BUFFER_SIZE, уменьшите max-model-len или снизьте concurrency к endpoint, пока не появится поддержка multi-decoder.
Проверка производительности
В бенчмарках использовался genai-bench со synthetic prompts фиксированной длины: 4,096 input tokens и 256 output tokens, при уровнях concurrency 8, 16 и 32. Каждый уровень concurrency прогонялся до стабилизации результатов. Конфигурация DPD: 1 prefiller и 1 decoder на 2 узлах (16 GPUs), KV-aware routing, enforce-eager на prefiller и CUDA graphs на decoder. Базовый вариант: один узел (8 GPUs), та же модель и те же настройки GPU. Аппаратная платформа: ml.p5.48xlarge (8x H100 80GB, EFA enabled). Модель: Llama-3.3-70B-Instruct с tensor-parallel-size=8 и max-model-len=16,384. Следующие диаграммы показывают процент улучшения, которое даёт DPD по сравнению с colocated baseline на двух семействах инстансов. Более высокие столбцы означают больший выигрыш DPD.

Следующая диаграмма показывает те же бенчмарки на инстансах ml.p5en.48xlarge с GPU H200, где выигрыш DPD столь же выражен.

На обеих конфигурациях оборудования DPD даёт устойчивый выигрыш по per-token output latency (TPOT), end-to-end latency и throughput по мере роста concurrency:
- Per-token latency остаётся стабильной под нагрузкой. DPD изолирует decode от вмешательства prefill, сохраняя TPOT постоянной независимо от количества параллельных длинных запросов. Для workloads D(4096,256) при concurrency от 8 до 32 улучшение составляет от 22% на низкой concurrency до 66% на высокой concurrency на H100 и от 28% до 48% на H200.
- Throughput масштабируется с concurrency. Выделенный decoder работает с полной эффективностью CUDA graph без прерываний со стороны prefill. Output throughput увеличивается до 35% на H100 и до 64% на H200 при более высокой concurrency.
- End-to-end latency улучшается на P50. Совокупная экономия TPOT на выходных токенах перекрывает стоимость передачи KV cache. E2E P50 улучшается на 14-32% на H100 и на 29-41% на H200.
DPD действительно немного увеличивает time to first token из-за передачи KV cache по EFA RDMA. Для потоковых нагрузок, где важнее стабильная доставка каждого токена, чем самый первый ответ, такой компромисс обычно выгоден. Порог conditional routing по умолчанию 4,096 токенов нужен, чтобы короткие запросы вообще не проходили через disaggregation и не платили лишнюю стоимость передачи там, где это не нужно.
Наблюдаемость
Следить за метриками DPD можно через возможности наблюдаемости SageMaker HyperPod. Подробнее см. Accelerate foundation model development with one-click observability in Amazon SageMaker HyperPod.
Метрики DPD доступны в Inference dashboard.

Дополнительными полезными метриками могут быть CPU/GPU usage, доступные в dashboard Tasks, а также метрики из dashboard Cluster Overview.
Очистка ресурсов
Чтобы не нести дальнейшие расходы, удалите ресурсы, созданные в ходе этого walkthrough, когда закончите эксперименты.
- Удалите
InferenceEndpointConfig, чтобы убрать pod prefiller, pod decoder, router и все связанные services:kubectl delete inferenceendpointconfig ${DEPLOYMENT_NAME} -n ${NAMESPACE} - (Опционально) удалите model из S3, если загружали её специально для этого walkthrough:
aws s3 rm s3://${MODEL_BUCKET}/${MODEL_PREFIX}/ --recursive --region ${AWS_REGION} - (Опционально) уменьшите масштаб или удалите instance group HyperPod, если GPU-инстансы были выделены только под это развёртывание. Инструкции см. в документации Managing HyperPod clusters.
Заключение
Disaggregated Prefill and Decode (DPD) в Amazon SageMaker HyperPod запускает prefill и decode на отдельных пулах GPU. KV cache между ними передаётся по EFA с использованием GPU-Direct RDMA. Prefill ограничен вычислениями. Decode ограничен пропускной способностью памяти. При совместном размещении эти две фазы конкурируют за одни и те же GPU-ресурсы. Один длинный prompt может остановить in-flight decoding и увеличить tail per-token latency. Разделение устраняет это влияние, делает latency более предсказуемой при смешанном трафике и позволяет независимо масштабировать каждую фазу.
HyperPod Inference Operator берёт на себя базовую оркестрацию: разворачивает router, связывает pod prefiller и decoder и интегрируется с observability HyperPod. DPD включается добавлением нескольких полей в тот же ресурс InferenceEndpointConfig, который вы уже используете для неразделённых endpoints.
Начать можно уже сегодня, развернув DPD endpoint в вашем HyperPod EKS cluster по шагам из этой статьи. Чтобы узнать больше, см. документацию Amazon SageMaker HyperPod, руководство по развёртыванию моделей HyperPod Inference Operator или сразу попробуйте пример manifest из этой статьи.
Материал — перевод статьи с английского.
Оригинал: Disaggregated prefill and decode for LLM inference on SageMaker HyperPod