Настоящий материал представляет собой обзор семнадцатой главы книги Chris Fregly, «AI Systems Performance Engineering: Optimizing Model Training and Inference Workloads with GPUs, CUDA, and PyTorch», O'Reilly Media, 2026 (Copyright © 2026 Flux Capacitor, LLC).
Прямые цитаты из книги выделены отдельными блоками с пометкой «Цитата из книги». Остальной текст — систематизированное изложение содержания главы, сопоставление с актуальными данными, проверка прогнозов, а также технические детали, выходящие за пределы исходного текста и основанные на официальной документации и открытых источниках. Цитаты приводятся в ограниченном объёме в целях обзора и критического анализа.
Процесс вывода большой языковой модели естественным образом распадается на две принципиально разные фазы: prefill и decode. Разделив их физически, можно одновременно оптимизировать время до первого токена (TTFT) и время на один выходной токен (TPOT) — то, что в монолитной архитектуре недостижимо. Эта глава — систематический разбор того, как проектировать, эксплуатировать и масштабировать разделённый prefill/decode инференс на масштабе ультрабольших кластеров.
«В конце 2024 года китайский стартап DeepSeek.AI поразил AI-сообщество, обучив фронтирную большую языковую модель без доступа к самым современным GPU NVIDIA. Из-за экспортных ограничений инженеры DeepSeek не могли получить топовые ускорители Blackwell или Hopper, поэтому использовали локально доступные альтернативы, включая NVIDIA H800. Они применили собственные ядра и продвинутые методы оптимизации, чтобы выжать максимум из менее производительного железа».
Процесс вывода LLM распадается на два этапа. Prefill принимает входной запрос целиком и формирует KV-кэш для всей последовательности. Decode порождает выходные токены по одному (или небольшими группами при спекулятивном декодировании), опираясь на уже сформированный KV-кэш.
Эти фазы обладают диаметрально противоположными профилями производительности. Prefill — вычислительно тяжёлая операция с интенсивной работой тензорных ядер; она ограничена FLOPS. Decode — ограничен пропускной способностью памяти: для каждого нового токена приходится перечитывать огромный KV-кэш и дописывать в него новые значения. Проще говоря, prefill — высокопараллельная задача с упором на пропускную способность, тогда как decode — последовательный процесс, критичный к задержкам.
Первые системы обслуживания LLM обрабатывали обе фазы в едином конвейере на одном оборудовании. Такой подход отдавал приоритет prefill, поскольку позволял максимизировать пропускную способность за счёт батчинга. Однако с ростом популярности интерактивных приложений на первый план вышли TTFT и TPOT. Обеспечить одновременную оптимизацию обоих в едином движке на одном GPU крайне затруднительно.
Понимание того, что prefill и decode имеют разные ресурсные профили, возникло не одномоментно. Уже в 2023 году практика непрерывного батчирования, популяризированная системой Orca и затем vLLM, показала: совмещение фаз на одном GPU создаёт «пузыри» — decode-последовательности простаивают, пока GPU обрабатывает тяжёлый prefill. Первые экспериментальные системы — Splitwise (Microsoft Research, 2024) и DistServe (2024) — независимо показали, что физическое разделение фаз на разные пулы GPU даёт двукратное и более улучшение goodput при тех же SLO. К 2025 году практически все крупные фреймворки — vLLM, SGLang, TensorRT-LLM, NVIDIA Dynamo — встроили поддержку disaggregated prefill/decode как первоклассную возможность. К 2026 году подход стал стандартом де-факто для инференса в масштабе сотен тысяч GPU.
Разделение фаз означает закрепление каждой фазы за специализированными ресурсами, оптимизированными под её нужды. DistServe показала потенциал обслуживания в 7,4 раза большего числа запросов в рамках строгих SLO по задержке по сравнению с монолитным решением. С тех пор промышленные фреймворки начали массово внедрять отдельные серверы для prefill и decode.
Открытая библиотека vLLM внедрила поддержку раздельного режима совместно с LMCache. NVIDIA Dynamo реализует disaggregated prefill и decode с динамической маршрутизацией и автомасштабированием. По сообщениям, масштабные сервисные системы OpenAI, Meta и xAI используют этот подход для соблюдения жёстких SLO по задержке. В ультрамасштабных развёртываниях речь может идти о сотнях тысяч или даже миллионах GPU, обслуживающих миллиарды запросов. В таких условиях выгода от разделения становится колоссальной.
Современные интерактивные сервисы на базе LLM зачастую ставят целью обеспечить задержку TTFT < 200–300 мс на уровне p99. Без разделения prefill-вычислений гарантировать это практически невозможно.
Рассмотрим сценарий: один пользователь отправляет запрос с чрезвычайно длинным промптом в несколько тысяч токенов, другой — с очень коротким. Без разделения, если эти запросы приходят одновременно, вычисление prefill для длинного промпта надолго займёт GPU. Второму запросу придётся ждать неоправданно долго, прежде чем начнётся его декодирование. Это явление называется интерференцией: работа по prefill одного запроса задерживает декодирование другого.
Prefill (compute-bound) и decode (memory-bound) борются за один GPU → decode задерживается
При простой стратегии FIFO длинные промпты усиливают хвостовую задержку для всех. Тяжёлые prefill-задачи в начале очереди блокируют более лёгкие запросы позади — это блокировка начала очереди (head-of-line blocking), ведущая к низкой утилизации, выбросам задержки и недовольству пользователей.
В разделённой архитектуре можно направить prefill большого промпта в выделенный пул вычислительно-оптимизированных prefill-воркеров, а prefill лёгкого промпта — напрямую декодерам. Такая гибкость не даёт коротким запросам страдать от head-of-line blocking.
В монолитной системе, обслуживающей модель Llama-3.1-70B на одном H100 SXM5, типичное время prefill для промпта из 2048 токенов — около 150–250 мс. За это время все decode-потоки на этом GPU замораживаются. Если одновременно в батче 32 активные decode-последовательности с целевым TPOT 20 мс, каждая получает «паузу» до 250 мс — задержку в 12,5 раз больше целевой. В p99 это всплеск с 20 мс до 250+ мс, что нарушает SLO для подавляющего большинства активных запросов.
В разделённой системе тот же prefill выполняется на выделенном GPU, не затрагивая decode-пул. TPOT активных последовательностей остаётся стабильным — единственная добавка — время передачи KV-кэша от prefill-воркера к decode-воркеру.
Goodput в пределах SLO: P90 TTFT ≤ 0,4 с, P90 TPOT ≤ 0,04 с
Здесь SLO установлен на уровне 0,4 с для P90 TTFT и 0,04 с для P90 TPOT. Совмещённая система способна поддерживать лишь около 3 RPS полезной пропускной способности в границах TTFT и только 1,6 RPS в границах TPOT. Goodput совмещённой конфигурации — 1,6 RPS. После разделения (2 prefill + 1 decode) prefill-воркеры дают ~5,6 RPS, decode — ~10 RPS. Goodput конфигурации 2P1D — 3,3 RPS на GPU — двукратный рост.
Устранение интерференции не только улучшает средние задержки, но и сжимает хвосты. Эмпирически разделённые системы демонстрируют более плотное распределение задержек и избегают длинных хвостов, характерных для монолитных систем.
В разделённом развёртывании поддерживаются два (или более) пула воркеров: один набор GPU выделен под обработку промптов (prefill), другой — под генерацию токенов (decode). Эти воркеры могут располагаться на отдельных узлах или стойках — или даже в разных дата-центрах, если пропускная способность межсоединений достаточна.
Пулы общаются по сети для передачи KV-кэша, сформированного prefill-воркером, тому GPU, который будет выполнять декодирование. Этой коммуникацией управляет планировщик, или маршрутизатор.
Обычно эти две группы воркеров взаимодействуют через высокоскоростные межсоединения (NVLink/NVSwitch и InfiniBand) и zero-copy передачи GPU-to-GPU с использованием RDMA. На практике такие передачи задействуют GPUDirect RDMA или UCX. Для NVL-фабрик на базе суперчипов (Grace-Blackwell, Vera-Rubin) используют NVIDIA Multi-Node NVLink (MNNVL), держат NVLink-first коллективы включёнными для TP-декодирования и включают SHARP для коллективов AllGather и ReduceScatter.
Когда система получает новый запрос, обычно он принимается на decode-воркере. Это decode-центричный дизайн. Он предпочтителен, поскольку prefill-воркеры уже загружены вычислениями KV. Перекладывая на decode-воркеры клиентский ввод-вывод, маршрутизацию и управление состоянием сессии, система избегает перегрузки prefill-воркеров.
Другой подход — выделенный централизованный API-роутер, направляющий запрос либо prefill-, либо decode-воркеру. Третий — реализованный в llm-d — использует шлюз kgateway на базе Envoy AI Gateway, который перехватывает запросы и направляет их в пулы prefill и decode через Kubernetes-оператор.
Ниже приведён фрагмент упрощённой конфигурации кластера NVIDIA Dynamo, определяющей две роли, использующие NVIDIA Inference Xfer Library (NIXL) для передачи KV-кэша через GPUDirect RDMA:
roles:
- name: prefill_worker
model_path: models/llm-70b
instance_count: 4
gpu_type: B200
- name: decode_worker
model_path: models/llm-70b
instance_count: 8
gpu_type: B300
NVIDIA Rubin CPX (CP = context processing) специально спроектирован под вычислительно-тяжёлые задачи — такие как prefill. Он знаменует отход NVIDIA от универсальных ускорителей к специализированным чипам, оптимизированным под конкретную фазу AI-нагрузки.
Внутри, когда система работает, каждый decode-воркер регистрирует область GPU-памяти, чтобы prefill-воркеры могли писать в неё напрямую через RDMA. Метаданные регистрации обмениваются при старте или первом контакте — так, для каждой удалённой prefill-задачи достаточно передать лишь небольшой идентификатор.
Dynamo использует etcd для обнаружения воркеров и аренды. Воркеры регистрируют дескрипторы памяти в роутере, чтобы пиры могли получить их при необходимости. Так prefill-запрос может содержать лишь ID целевого KV-буфера.
Критический вопрос: сколько фактически времени занимает передача KV-кэша между prefill- и decode-воркерами? Без этих чисел невозможно осознанно выбрать порог PREFILL_LENGTH_THRESHOLD, при котором передача становится выгодной.
Размер KV-кэша для одного запроса вычисляется по формуле:
KV_размер = 2 × n_layers × n_kv_heads × head_dim
× bytes_per_element × n_tokens
| Модель | Точность | 1024 токена | 4096 токенов | 8192 токена |
|---|---|---|---|---|
| Llama-3.1-8B (GQA, 8 KV heads) | FP16 | 128 МБ | 512 МБ | 1024 МБ |
| Llama-3.1-70B (GQA, 8 KV heads) | FP16 | 320 МБ | 1280 МБ | 2560 МБ |
| Llama-3.1-70B (GQA, 8 KV heads) | FP8 | 160 МБ | 640 МБ | 1280 МБ |
| DeepSeek-R1 671B (MLA, FP8) | FP8 | 70 МБ | 280 МБ | 560 МБ |
Для DeepSeek-R1 с MLA размер KV-кэша определяется латентным представлением: 61 слой × (512 латент + 64 RoPE) × 1 байт ≈ 35 КБ/токен. Даже в FP16 это ~68,6 КБ/токен — примерно в 40 раз меньше, чем у Llama-70B с GQA.
Время передачи зависит от доступной пропускной способности сети:
| Транспорт | Пропускная способность | Задержка | GPUDirect |
|---|---|---|---|
| InfiniBand RDMA | 20–50 ГБ/с | ~1 мкс | Да |
| RoCE RDMA | 10–25 ГБ/с | ~2 мкс | Да |
| AWS EFA + libfabric | ~9,6 ГБ/с | ~5 мкс | Да |
| TCP (fallback) | ~1 ГБ/с | ~50 мкс | Нет |
Для Llama-3.1-70B в FP8 и промпта из 1024 токенов (KV ~160 МБ) передача через InfiniBand RDMA занимает 3–6 мс. Через TCP — около 170 мс. Эмпирические данные из продакшена подтверждают: на AWS p5.48xlarge для Llama-3.1-8B при длине входа 8000 токенов накладные расходы на передачу KV — всего ~37 мс, при том что disaggregated режим дал 41% ускорение на токен и 22% рост пропускной способности.
PagedAttention блоки коалесцируются в ~128-токеновые страницы → RDMA / NVLink
Передача оправдана, когда время удалённого prefill плюс время передачи KV-кэша меньше времени локального prefill на decode-воркере. Для моделей 70B+ с длинными промптами (>256 токенов после учёта prefix-кэша) и InfiniBand/RDMA-сетью передача почти всегда выгодна. Для моделей 4B и меньше либо при отсутствии RDMA — почти никогда.
Бенчмарки на AMD MI300X (400 Гбит/с NIC) показали следующие результаты для передачи 1 ГБ данных:
| Движок | Время (1 ГБ) | Пропускная способность | Расход GPU SM |
|---|---|---|---|
| UCCL P2P | ~205 мс | ~4,9 ГБ/с | Нет |
| NIXL | ~216 мс | ~4,6 ГБ/с | Нет |
| NCCL/RCCL | ~300 мс | ~3,3 ГБ/с | Да |
| Mooncake TE | ~298 мс | ~3,5 ГБ/с | Нет |
NIXL и UCCL P2P достигают наилучших результатов для типичных размеров KV-кэша (256 КБ – 1 МБ), поскольку не расходуют GPU SM на передачу — данные идут напрямую через GPUDirect RDMA.
Prefill-воркеры — вычислительные узлы, предназначенные для выполнения начальной фазы. Поскольку prefill-нагрузка вычислительно интенсивна, prefill-узлы должны использовать GPU с высокой вычислительной мощностью и быть оптимизированы под крупные матричные умножения.
read prefix-cache → compute prefill → write KV blocks обратно декодеру
Prefill заполняет память GPU параметрами модели и рабочими активациями прямого прохода с входными токенами промпта. Как только KV-кэш создан, он немедленно отправляется decode-воркерам. KV-кэш не задерживается надолго в памяти prefill-узла. Некоторые фреймворки предварительно резервируют большой блок GPU-памяти под prefill как рабочее пространство — это снижает фрагментацию и время на выделение буферов.
При настройке разделённого prefill-кластера возникает фундаментальный компромисс между минимизацией TTFT для каждого отдельного промпта и максимизацией общего RPS.
Подход «сначала задержка». Чтобы сократить TTFT, prefill-узлы обрабатывают промпты сразу по мере поступления — с минимальным батчированием или без него. Недостаток — более низкая утилизация GPU. Можно либо избыточно зарезервировать ёмкость, либо использовать размер батча, равный 1.
Подход «сначала пропускная способность». Объединение промптов в крупные батчи (8–32) повышает арифметическую интенсивность и загружает вычислительные блоки. Недостаток — каждый запрос несёт задержку батчирования, равную времени сбора батча.
Многие inference-движки отдают предпочтение задержке для prefill-воркеров: быстрый первый токен значительно улучшает UX. Обычно для prefill-кластера резервируют больше ёмкости, чем нужно для средней нагрузки — так он поглощает всплески промптов без скачков задержки.
Разделённые системы применяют политики планирования с учётом задержек: гарантируют выполнение одиночного запроса, пока нагрузка не станет достаточно высокой, применяют адаптивные окна батчирования (при низкой нагрузке — батч 1, при высокой — микробатчи 2–10 мс), используют очереди приоритетов для коротких промптов. NVIDIA Dynamo поддерживает классы задержки в планировании.
Decode-воркеры предназначены для авторегрессионной фазы декодирования. Как только KV-кэш промпта готов, он передаётся decode-воркеру, который использует его для генерации оставшихся выходных токенов как можно быстрее, чтобы поддерживать низкую задержку TPOT.
Если запрос изначально направляется decode-воркеру, он решает, выполнять prefill локально или удалённо. Prefill-воркер непрерывно забирает задачи из очереди prefill, читает KV-блоки из prefix-кэша и выполняет prefill. Затем записывает KV-блоки обратно decode-воркеру.
Decode-серверы активно полагаются на непрерывное батчирование. В отличие от prefill-фазы, выполняющей крупные матрично-матричные вычисления, фаза декодирования выполняет множество малых вычислений. Чтобы избежать низкой утилизации GPU, decode-воркеры объединяют несколько входных последовательностей, формируя более крупные матрично-матричные вычисления на каждой итерации.
Например, если 32 различных запроса готовы сгенерировать следующий токен, планировщик непрерывного батчирования выполнит один прямой проход, генерирующий 32 токена параллельно. Так матричные умножения получают эффективный размер батча 32.
Непрерывное батчирование гарантирует, что decode-GPU-воркеры не простаивают. Они всегда работают над доступными запросами. При высокой нагрузке оно эффективно, при низкой — отзывчиво. Это хороший баланс между высокой пропускной способностью и низкой задержкой.
Обработка последовательностей переменной длины требует тщательного планирования. Смешивание коротких и длинных промптов в одном батче ведёт к накладным расходам на padding — иногда до 50% от числа токенов. Решение — группировать запросы в корзины по длине последовательности. Более продвинутый подход — Flash Attention с интерфейсом переменной длины (varlen), упаковывающий все последовательности в один непрерывный тензор с метаданными кумулятивных длин. Это полностью устраняет padding (экономия до 63%) и сокращает число запусков ядер с N до 1 на каждый слой внимания.
NVIDIA Programmatic Dependent Launch (PDL) и device-initiated CUDA Graph Launch используются для снижения накладных расходов на запуск на каждом токене, перекрытия работы и устранения пузырей между итерациями декодирования.
Поскольку декодирование включает внимание ко всей последовательности, память под KV-кэш — критичный ресурс для decode-воркеров. Для больших моделей и длинных последовательностей память KV растёт линейно с числом токенов.
Практическая оценка: байт_на_токен = 2 × n_layers × n_kv_heads × head_dim × байт_на_элемент.
Для модели класса Llama 13B с 40 слоями, 40 головами внимания и FP16 при контексте 4096 токенов со стандартным MHA размер KV составляет ~0,819 МБ/токен или ~3,36 ГБ всего. При KV в FP8 — ~1,68 ГБ. При GQA с 8 группами запросов — ~0,671 ГБ в FP16 и ~0,336 ГБ в FP8. Для MQA с 1 KV-головой — ~0,084 ГБ в FP16.
Paged GPU memory allocator. Механизм PagedAttention в vLLM разбивает KV-кэш на страницы фиксированного размера и может выгружать неактивные страницы в память CPU. Менеджеры paged KV-памяти реализованы в SGLang, TensorRT-LLM, NVIDIA Dynamo.
Выгрузка KV-кэша. Когда GPU-память заполняется, decode-воркеры могут выгружать более старые KV-блоки в CPU DRAM или NVMe. NVIDIA Dynamo реализует многоуровневое управление через KV Block Manager (KVBM). В сценарии с 20 многоходовыми диалогами и 15 пользователями KVBM с выгрузкой в CPU-память дал улучшение TTFT в 2,2–12 раз.
GPU-память decode-кластера полностью посвящена хранению весов модели и KV-кэша. Она не пытается обрабатывать крупные вычисления prefill промптов. Разделение не устраняет проблемы памяти KV, но позволяет выбирать типы decode-узлов, оптимизированные под ёмкость памяти и пропускную способность.
Не каждый запрос нужно передавать prefill-воркеру. Разделённая система использует политику маршрутизации, чтобы условно использовать удалённый путь prefill только тогда, когда это вероятно поможет.
| Стратегия маршрутизации | Описание |
|---|---|
| Round robin | Направляет по одному узлу по очереди |
| Least requests | Направляет воркеру с наименьшим числом активных запросов |
| Prefix aware | Использует префикс запроса для выбора воркера |
| KV aware | Направляет воркеру, чей KV-кэш лучше всего соответствует запросу |
| Фактор | Описание | Влияние |
|---|---|---|
| Длина промпта | Число токенов после prefix-кэширования | Длинные → передавать; короткие → локально |
| Попадание в prefix-кэш | Степень, в которой промпт уже в KV-кэше | Большое → локально; нет → передача выгодна |
| Длина очереди prefill | Число ожидающих задач | Длинная → избегать передачи |
| Нагрузка decode | Текущая нагрузка на локальный воркер | Занятый → передача помогает |
| Срочность по SLO | Приоритет или жёсткость требования | Срочное → немедленно передать |
Роутер получает KV-cache events и направляет запрос туда, где префикс уже в памяти
На практике политика маршрутизации реализуется как условная проверка. Ниже — упрощённая версия логики маршрутизации:
def should_offload_prefill(prompt_length: int,
prefix_cached_length: int,
prefill_queue_size: int,
decode_active_reqs: int,
ttft_slo_ms: int = 500) -> bool:
eff_len = max(0, prompt_length - prefix_cached_length)
PREFILL_LENGTH_THRESHOLD = 256
PREFILL_QUEUE_MAX = 10
DECODE_LOAD_THRESHOLD = 8
long_prefill = (eff_len >= PREFILL_LENGTH_THRESHOLD)
prefill_available = (prefill_queue_size < PREFILL_QUEUE_MAX)
if long_prefill and prefill_available:
return True
if decode_active_reqs >= DECODE_LOAD_THRESHOLD and eff_len >= 64:
return True
return False
В производственном развёртывании политику маршрутизации следует настраивать через файл. NVIDIA Dynamo позволяет задавать сложные правила маршрутизации и автомасштабирования в JSON или YAML:
model: ...
split_policy:
prompt_length_threshold: 256
prefix_cache_weight: 10.0
queue_length_weight: 1.5
decode_load_weight: 0.5
enable_hotspot_prevention: true
cache:
reuse_prefix: true
min_cache_hit_ratio: 0.75
autoscale:
prefill:
min_replicas: 4
max_replicas: 12
scale_up: { queue_length: 8, gpu_utilization: 80 }
scale_down: { queue_length: 2, gpu_utilization: 40 }
decode:
min_replicas: 8
max_replicas: 24
scale_up: { queue_length: 16, kv_cache_usage: 75 }
scale_down: { queue_length: 4, kv_cache_usage: 30 }
qos:
enable_early_rejection: true
low_priority_threshold_ms: 500
reject_on_slo_violation: true
Dynamo Planner использует метрики вроде TTFT, TPOT и оценённой стоимости передачи KV-кэша для решения об изменении маршрутизации — или даже о перераспределении GPU для конкретной фазы. При наплыве задач суммаризации Planner перераспределит больше GPU в фазу prefill. При наплыве reasoning-задач — в фазу decode.
По метрикам (TTFT/TPOT, GPU util, queue depth) планировщик перераспределяет GPU между prefill и decode
NVIDIA Dynamo сообщает 3-кратное улучшение TTFT и 2-кратное снижение средней задержки запроса при маршрутизации 100 000 реальных запросов к DeepSeek-R1 Distilled Llama 70B (FP8, 2 узла H100) с учётом KV-кэша по сравнению со случайной маршрутизацией. Проект llm-d на B200 сообщает о 3-кратном росте выходной пропускной способности и 2-кратном ускорении TTFT с prefix-cache-aware маршрутизацией.
Спекулятивное декодирование может быть наложено поверх процесса принятия решений роутером. Система может обнаружить неопределённую ветвь спекулятивного декодирования и отправить несколько спекулятивных запросов разным decode-воркерам параллельно. Это обменивает дополнительные вычисления на более низкую задержку для генераций с непредсказуемыми ветвями.
Адаптивное спекулятивное декодирование. Система StreamServe (2026) объединила disaggregated prefill/decode с адаптивным спекулятивным декодированием, глубина которого настраивается онлайн на основе runtime-метрик. На бенчмарках StreamShow показал снижение задержки на 11–18% относительно tensor-parallel vLLM и прирост пропускной способности до 6,8% при стабильном TPOT. Взаимодействие между disaggregation, metric-aware routing и adaptive speculation даёт сверхаддитивный эффект.
В практическом плане спекулятивное декодирование через EAGLE-3 может давать до 6× ускорения на плотных моделях. Однако при constrained decoding (JSON mode, tool calls с грамматиками) acceptance rate часто резко падает, поскольку constraint mask инвалидирует спекулятивные токены. Это важно учитывать при планировании нагрузки в разделённом кластере.
Политики QoS приоритизируют или ограничивают определённые запросы для соблюдения целей по задержке. Раннее отклонение, также называемое контролем допуска, может отказать в низкоприоритетных запросах, когда система насыщена. Так сохраняются ресурсы и SLO для других запросов.
Современные системы делают это на основе порога времени в очереди. Если запрос пробыл в очереди > X мс, возможно, лучше отклонить его, чем допустить серьёзное нарушение SLO. Такой QoS-барьер становится всё более распространённым для критически важных систем инференса.
В ультрамасштабных системах механизмы QoS необходимы для поддержания гарантий хвостовой задержки под тяжёлой нагрузкой. Вместо того чтобы позволить нарушение SLO для всех запросов, хорошо спроектированная система предпочтёт изящно сбросить нагрузку: отклонив или отложив низкоприоритетные запросы, когда ясно, что их обслуживание нарушит гарантии для других.
class QoSController:
def __init__(self, ttft_slo_ms: int = 500):
self.ttft_slo_ms = ttft_slo_ms
def admit_request(self, priority: str) -> bool:
est_ttft = (get_current_prefill_queue_length()
* get_avg_prefill_time_per_req()
+ get_current_decode_queue_length()
* get_avg_decode_time_per_req())
if est_ttft > self.ttft_slo_ms and priority == "low":
return False
return True
Адаптивные лимиты генерации. Если фаза decode угрожает затянуться слишком долго и вызвать нарушение SLO по TPOT, система может досрочно прервать генерацию. Если пользователь запросил 1000 токенов, но система под давлением, она может разрешить только 200 токенов, оставляя ресурсы для других запросов.
Изящная деградация. Одна из интересных форм — временное отключение удалённого prefill для низкоприоритетных запросов, когда система под нагрузкой. Низкоприоритетные запросы выполняют локальный prefill, освобождая prefill-воркеры для высокоприоритетных.
По мере масштабирования систем до тысяч и миллионов GPU-узлов становится труднее контролировать хвостовую задержку. Изоляция prefill-задач на одном узле, а decode-задач на другом означает, что медленный узел с одной стороны не окажет резкого влияния на задачи с другой стороны. Кэширование больших частей контекста означает, что одна медленная операция не замедлит всю генерацию так сильно.
Одно из самых мощных следствий disaggregation — возможность использовать разные типы GPU для prefill и decode, подбирая оборудование под ресурсный профиль каждой фазы.
Prefill — compute-bound: нужен максимум FP8/FP4 TFLOPS, ёмкость HBM не критична. Decode — memory-bound: нужна максимальная пропускная способность HBM, вычислительная мощность почти не используется. Это создаёт неэффективность: использовать H100 (стоимость ~$2,57/час за GPU) для decode — значит платить примерно в 3,6 раза больше за пропускную способность памяти, которая вам не нужна. L40S (стоимость ~$0,72/час) имеет достаточную пропускную способность HBM для decode моделей 70B и стоит в 3,5 раза дешевле.
| Конфигурация | GPU | Стоимость/час | Пропускная способность | Стоимость 1M токенов |
|---|---|---|---|---|
| Однородная | 4× H100 SXM5 | $10,28 | 3200 tok/s | $0,89 |
| Гетерогенная 1 | 1× H100 + 2× L40S | $4,01 | 2400 tok/s | $0,46 |
| Гетерогенная 2 | 2× H100 + 4× A100 | $9,30 | 4800 tok/s | $0,54 |
Гетерогенная конфигурация 1 (H100 для prefill + L40S для decode) экономит 61% на почасовой стоимости и снижает цену за 1M токенов на 48% — при снижении пропускной способности на 25%. Для моделей до 70B это наиболее рентабельная пара. Для моделей 70B–405B лучше подходит связка B200 + A100 80G.
«Вычислительный prefill + бюджетный decode» — prefill на H100/B200, decode на L40S/A100. Роутер Dynamo направляет prefill на вычислительный пул, KV-кэш передаётся через NIXL/RDMA на decode-пул.
«Перекрёстное перемешивание» (work stealing) — реализован в HeteroServe. Когда consumer-GPU простаивают, они временно забирают decode-задачи у других воркеров, восстанавливая простаивающую ёмкость.
«Спотовые GPU с on-demand fallback» — ShuntServe. Decode на дешёвых spot-инстансах, при отзыве — переключение на on-demand. DP-оптимизатор на основе roofline-модели находит оптимальное размещение слоёв. Достигается 1,42× более высокая пропускная способность на Llama-3.1-70B.
«GPU Combo» — Cauchy. Концептуальная «GPU-комбинация» выбирается для каждого типа нагрузки на основе cost-efficiency.
Работа DUET (2026) предлагает аппаратное разделение на уровне чипов: «Prefill Chips» с увеличенными систолическими массивами и GDDR7 вместо HBM, и «Decode Chips» с сохранением высокой пропускной способности, но сокращённой вычислительной мощности. Отчёты показывают: 8% выше prefill-производительность при 52% меньшей стоимости для Prefill Chips, и 97% decode-производительности при 28% меньшем TDP для Decode Chips — относительно смоделированных H100.
Базовый эксперимент показал: goodput совмещённой конфигурации — 1,6 RPS на GPU; разделённой 2P1D — 3,3 RPS на GPU (2,1× улучшение) при строгих SLO.
PD-дезагрегация с экспертным параллелизмом: 52,3k входных токенов/с на узел prefill (24 GPU), 22,3k выходных токенов/с на узел decode (72 GPU). На GB200 NVL72 — до 3,8× для prefill и 4,8× для decode по сравнению с H100-конфигурацией.
Измеренные накладные расходы на передачу KV-кэша для Llama-3.1-8B (ISL=8000, OSL=50): всего 37 мс (TTFT: aggregated 173 мс vs disaggregated 210 мс). При этом disaggregated режим даёт 41% ускорение на токен и 22% рост пропускной способности.
Дезагрегированная квантизация. Использование разных форматов квантизации для prefill (аппаратно-нативный NVFP4) и decode (weight-only 2–3 бита) с совместной оптимизацией весов. QADD показал прирост точности на decode-задачах при сохранении скорости prefill — до 1,49× ускорения над BF16 на Qwen3-8B и Gemma3-12B.
Адаптивное спекулятивное декодирование. Объединение disaggregation с онлайн-настройкой глубины спекуляции на основе runtime-метрик. StreamServe показал сверхаддитивный эффект от этого сочетания.
Специализированные чипы для фаз. Проекты вроде DUET предлагают чипы, спроектированные исключительно под prefill или decode, с разной архитектурой памяти и вычислительных блоков.
Дезагрегация для диффузионных LLM. Prefilling-dLLM расширяет disaggregation на диффузионные языковые модели, кэшируя chunked prefix KV один раз и переиспользуя его между шагами денойзинга. На LongBench и Infinitebench — ускорение 9,1–28,0× при контекстах 8K–32K.
Мультимодальная дезагрегация. Расширение на трёхфазную архитектуру: encode (визионный энкодер) → prefill (языковой prefill) → decode. NVIDIA Dynamo уже поддерживает disaggregated encode/prefill/decode с кэшем эмбеддингов — 30% быстрее TTFT на image-нагрузках.
Разделение prefill и decode стало обычной практикой в современном высокомасштабном LLM-инференсе. Практически все крупные AI-провайдеры — Meta, Amazon, NVIDIA, Google и OpenAI — в той или иной форме внедрили разделённую архитектуру в своих крупномасштабных развёртываниях LLM.
Хотя большинство этих компаний не публикуют свою полную производственную архитектуру, существует множество открытых реализаций — vLLM, SGLang, NVIDIA Dynamo, llm-d, LMCache, Mooncake — которые демонстрируют этот подход и его преимущества. Разделённый prefill-decode даёт более высокий goodput — полезную пропускную способность в рамках ограничений по задержке — и лучшую экономическую эффективность, чем монолитные дизайны.
Спустя 18 месяцев после выхода DistServe концепция эволюционировала от небольшого прототипа до фундаментальной техники, питающей практически каждый современный inference-движок.
Разделение фаз prefill и decode — не конечная точка, а платформа для дальнейших оптимизаций: дезагрегированной квантизации, адаптивного спекулятивного декодирования, специализированных чипов, мультимодальной дезагрегации. Каждый уровень даёт свой прирост, и правильный порядок шагов — профилирование, KV-кэш, маршрутизация, QoS, гетерогенность — важнее отдельной «серебряной пули».
This material is a review of Chapter 17 of Chris Fregly, «AI Systems Performance Engineering: Optimizing Model Training and Inference Workloads with GPUs, CUDA, and PyTorch», O'Reilly Media, 2026 (Copyright © 2026 Flux Capacitor, LLC).
Direct quotes from the book are marked with separate blocks labeled «Quote from the book». The rest of the text is a systematized exposition of the chapter's content, a comparison with current data, forecast verification, and technical details beyond the original text based on official documentation and open sources. Quotes are provided in limited volume for review and critical analysis purposes.
LLM inference naturally splits into two fundamentally different phases: prefill and decode. Separating them physically allows you to optimize both Time To First Token (TTFT) and Time Per Output Token (TPOT) simultaneously — something monolithic architectures cannot achieve. This chapter is a systematic examination of how to design, operate, and scale disaggregated prefill/decode inference at the scale of ultra-large clusters.
«In late 2024, the Chinese startup DeepSeek.AI stunned the AI community by training a frontier large language model without access to NVIDIA's most advanced GPUs. Due to export restrictions, DeepSeek's engineers could not obtain top-tier Blackwell or Hopper accelerators, so they used locally available alternatives, including the NVIDIA H800. They applied custom kernels and advanced optimization techniques to squeeze maximum performance out of less capable hardware».
LLM inference splits into two stages. Prefill accepts the input request in full and forms the KV cache for the entire sequence. Decode generates output tokens one at a time (or in small groups with speculative decoding), relying on the already formed KV cache.
These phases have diametrically opposite performance profiles. Prefill is a compute-heavy operation with intensive Tensor Core work; it is FLOPS-bound. Decode is memory-bandwidth-bound: for every new token, the huge KV cache must be re-read and new values appended. In short, prefill is a highly parallel throughput-oriented task, while decode is a sequential latency-critical process.
The first LLM serving systems processed both phases in a single pipeline on the same hardware. That approach prioritized prefill, as it maximized throughput through request batching. However, with the rise of interactive applications, TTFT and TPOT moved to the foreground. Simultaneously optimizing both in a single engine on one GPU is extremely challenging.
The realization that prefill and decode have different resource profiles emerged gradually. As early as 2023, continuous batching — popularized by Orca and then vLLM — showed that co-locating phases on one GPU creates "bubbles": decode sequences idle while the GPU processes a heavy prefill. The first experimental systems — Splitwise (Microsoft Research, 2024) and DistServe (2024) — independently showed that physically separating phases into different GPU pools yields 2× or more goodput improvement at the same SLO. By 2025, virtually every major framework — vLLM, SGLang, TensorRT-LLM, NVIDIA Dynamo — had built disaggregated prefill/decode support as a first-class feature. By 2026, the approach became the de-facto standard for inference at the scale of hundreds of thousands of GPUs.
Disaggregation means assigning each phase to specialized resources optimized for its needs. DistServe demonstrated the potential to serve 7.4× more requests within strict latency SLOs compared to the monolithic solution of its time. Since then, production frameworks have mass-adopted separate servers for prefill and decode.
The open-source vLLM library added split-mode support together with LMCache. NVIDIA Dynamo implements disaggregated prefill and decode with dynamic routing and autoscaling. Large-scale serving systems at OpenAI, Meta, and xAI are reported to use this approach to meet strict latency SLOs. At ultra-scale deployments, this can mean hundreds of thousands or even millions of GPUs serving billions of requests. Under those conditions, the benefit of disaggregation becomes enormous.
Modern interactive LLM services often target p99 TTFT < 200–300 ms. Without prefill disaggregation, guaranteeing this is practically impossible.
Consider a scenario: one user sends a request with an extremely long prompt of several thousand tokens, another with a very short one. Without disaggregation, if these arrive at the same time, computing the prefill for the long prompt occupies the GPU for a long time. The second request must wait unjustifiably long before its decode begins. This phenomenon is called interference: prefill work for one request delays decoding for another.
Prefill (compute-bound) and decode (memory-bound) fight over one GPU → decode is delayed
With a simple FIFO strategy, long prompts exacerbate tail latency for everyone. Heavy prefill tasks at the head of the queue block lighter requests behind them — head-of-line blocking, leading to low utilization, latency spikes, and user frustration.
In a disaggregated architecture, you can route the prefill of a large prompt to a dedicated pool of compute-optimized prefill workers, and the prefill of a light prompt directly to decoders. This flexibility prevents short requests from suffering head-of-line blocking.
In a monolithic system serving Llama-3.1-70B on one H100 SXM5, typical prefill time for a 2048-token prompt is 150–250 ms. During this time, all decode streams on that GPU freeze. With 32 active decode sequences targeting 20 ms TPOT, each gets a "pause" of up to 250 ms — a delay 12.5× the target. At p99, this is a spike from 20 ms to 250+ ms, violating SLOs for the vast majority of active requests.
In a disaggregated system, the same prefill runs on a dedicated GPU without touching the decode pool. TPOT of active sequences remains stable — the only addition is KV cache transfer time from the prefill worker to the decode worker.
Goodput within SLO: P90 TTFT ≤ 0.4 s, P90 TPOT ≤ 0.04 s
Here SLO is 0.4 s for P90 TTFT and 0.04 s for P90 TPOT. The colocated system sustains only ~3 RPS of useful throughput within the TTFT bound and only 1.6 RPS within the TPOT bound. Goodput of the colocated configuration is 1.6 RPS. After disaggregation (2 prefill + 1 decode), prefill workers give ~5.6 RPS, decode ~10 RPS. Goodput of the 2P1D configuration is 3.3 RPS per GPU — a 2× improvement.
Eliminating interference not only improves average latencies but also tightens tails. Empirically, disaggregated systems show tighter latency distributions and avoid the long tails characteristic of monolithic systems.
In a disaggregated deployment, two (or more) worker pools are maintained: one set of GPUs is dedicated to prompt processing (prefill), another to token generation (decode). These workers may sit on separate nodes or racks — or even in different data centers if interconnect bandwidth is sufficient.
The pools communicate over the network to transfer the KV cache formed by the prefill worker to the GPU that will perform decoding. This communication is managed by a scheduler or router.
Typically, the two worker groups interact over high-speed interconnects (NVLink/NVSwitch and InfiniBand) and zero-copy GPU-to-GPU transfers using RDMA. In practice, these transfers leverage GPUDirect RDMA or UCX. For NVL-fabrics based on superchips (Grace-Blackwell, Vera-Rubin), use NVIDIA Multi-Node NVLink (MNNVL), keep NVLink-first collectives enabled for TP-decode, and enable SHARP for AllGather and ReduceScatter collectives.
When the system receives a new request, it is usually admitted on a decode worker. This is the decode-centric design. It is preferable because prefill workers are already busy computing KV. By pushing client I/O, routing, and session state management onto decode workers, the system avoids overloading prefill workers.
Another approach is a dedicated centralized API router that directs the request either to a prefill or a decode worker. A third — implemented in llm-d — uses the kgateway Envoy AI Gateway, which intercepts requests and routes them to prefill and decode pools via a Kubernetes operator.
Below is an excerpt of a simplified NVIDIA Dynamo cluster configuration defining two roles that use NVIDIA Inference Xfer Library (NIXL) for KV cache transfer over GPUDirect RDMA:
roles:
- name: prefill_worker
model_path: models/llm-70b
instance_count: 4
gpu_type: B200
- name: decode_worker
model_path: models/llm-70b
instance_count: 8
gpu_type: B300
NVIDIA Rubin CPX (CP = context processing) is specifically designed for compute-heavy tasks like prefill. It marks NVIDIA's shift from universal compute accelerators to specialized chips optimized for a specific AI phase.
Inside a running system, each decode worker registers a region of GPU memory so prefill workers can write to it directly via RDMA. Registration metadata is exchanged at startup or first contact — so for each remote prefill task, only a small ID needs to be sent.
Dynamo uses etcd for worker discovery and leases. Workers register memory descriptors in the router so peers can fetch them as needed. A prefill request can thus carry only the target KV buffer ID.
A critical question: how much time does KV cache transfer actually take between prefill and decode workers? Without these numbers, you cannot consciously choose PREFILL_LENGTH_THRESHOLD at which transfer becomes worthwhile.
KV cache size per request is computed as:
KV_size = 2 × n_layers × n_kv_heads × head_dim
× bytes_per_element × n_tokens
| Model | Precision | 1024 tokens | 4096 tokens | 8192 tokens |
|---|---|---|---|---|
| Llama-3.1-8B (GQA, 8 KV heads) | FP16 | 128 MB | 512 MB | 1024 MB |
| Llama-3.1-70B (GQA, 8 KV heads) | FP16 | 320 MB | 1280 MB | 2560 MB |
| Llama-3.1-70B (GQA, 8 KV heads) | FP8 | 160 MB | 640 MB | 1280 MB |
| DeepSeek-R1 671B (MLA, FP8) | FP8 | 70 MB | 280 MB | 560 MB |
For DeepSeek-R1 with MLA, KV cache size is determined by the latent representation: 61 layers × (512 latent + 64 RoPE) × 1 byte ≈ 35 KB/token. Even in FP16 it is ~68.6 KB/token — roughly 40× smaller than Llama-70B with GQA.
Transfer time depends on available network bandwidth:
| Transport | Bandwidth | Latency | GPUDirect |
|---|---|---|---|
| InfiniBand RDMA | 20–50 GB/s | ~1 µs | Yes |
| RoCE RDMA | 10–25 GB/s | ~2 µs | Yes |
| AWS EFA + libfabric | ~9.6 GB/s | ~5 µs | Yes |
| TCP (fallback) | ~1 GB/s | ~50 µs | No |
For Llama-3.1-70B in FP8 with a 1024-token prompt (KV ~160 MB), transfer over InfiniBand RDMA takes 3–6 ms. Over TCP — about 170 ms. Production data confirms this: on AWS p5.48xlarge for Llama-3.1-8B with an 8000-token input length, KV transfer overhead was only ~37 ms, while the disaggregated regime gave 41% per-token speedup and 22% throughput increase.
PagedAttention blocks coalesce into ~128-token pages → RDMA / NVLink
Transfer is justified when remote prefill time plus KV transfer time is less than local prefill on the decode worker. For models 70B+ with long prompts (>256 tokens after prefix caching) and InfiniBand/RDMA network, transfer is almost always beneficial. For models 4B and under or without RDMA — almost never.
Benchmarks on AMD MI300X (400 Gbit/s NIC) produced the following results for transferring 1 GB of data:
| Engine | Time (1 GB) | Bandwidth | GPU SM usage |
|---|---|---|---|
| UCCL P2P | ~205 ms | ~4.9 GB/s | No |
| NIXL | ~216 ms | ~4.6 GB/s | No |
| NCCL/RCCL | ~300 ms | ~3.3 GB/s | Yes |
| Mooncake TE | ~298 ms | ~3.5 GB/s | No |
NIXL and UCCL P2P achieve the best results for typical KV cache sizes (256 KB – 1 MB) because they do not consume GPU SMs for transfer — data flows directly through GPUDirect RDMA.
Prefill workers are compute nodes designed for the initial phase. Since prefill load is compute-intensive, prefill nodes should use GPUs with high compute power and be optimized for large matrix multiplications.
read prefix-cache → compute prefill → write KV blocks back to decoder
Prefill fills GPU memory with model parameters and forward-pass activations. Once the KV cache is created, it is immediately sent to decode workers. The KV cache does not linger in prefill node memory. Some frameworks pre-reserve a large GPU memory block as a workspace — reducing fragmentation and buffer allocation time.
Configuring a disaggregated prefill cluster involves a fundamental trade-off between minimizing TTFT for each prompt and maximizing overall RPS.
Latency-first approach. To cut TTFT, prefill nodes process prompts immediately as they arrive — with minimal or no batching. The downside is lower GPU utilization. You can either overprovision capacity or use batch size 1.
Throughput-first approach. Combining prompts into larger batches (8–32) increases arithmetic intensity and loads compute units. The downside is that each request incurs a batching delay equal to the time to collect the batch.
Many inference engines favor latency for prefill workers: a fast first token significantly improves UX. Prefill clusters are usually overprovisioned relative to average load — so they absorb prompt bursts without latency spikes.
Disaggregated systems apply latency-aware scheduling policies: they guarantee single-request execution until load is high enough, use adaptive batching windows (batch 1 at low load, microbatch 2–10 ms at high load), and use priority queues for short prompts. NVIDIA Dynamo supports latency classes in scheduling.
Decode workers are designed for the autoregressive decoding phase. Once the prompt's KV cache is ready, it is passed to a decode worker, which uses it to generate remaining output tokens as quickly as possible to keep TPOT low.
If a request is initially routed to a decode worker, it decides whether to run prefill locally or remotely. The prefill worker continuously pulls tasks from the prefill queue, reads KV blocks from the prefix cache, and performs prefill. It then writes KV blocks back to the decode worker.
Decode servers rely heavily on continuous batching. Unlike prefill, which performs large matrix-matrix computations, the decode phase performs many small computations. To avoid low GPU utilization, decode workers combine several input sequences into larger matrix-matrix computations on each iteration.
For example, if 32 different requests are ready to generate the next token, the continuous batching scheduler performs a single forward pass generating 32 tokens in parallel. This gives matmuls an effective batch size of 32.
Continuous batching keeps decode GPU workers busy. At high load it is efficient; at low load it is responsive. That is a good balance between high throughput and low latency.
Handling variable-length sequences requires careful planning. Mixing short and long prompts in one batch leads to padding overhead — sometimes up to 50% of tokens. The solution is to bucket requests by sequence length. A more advanced approach is Flash Attention with a variable-length (varlen) interface, packing all sequences into one contiguous tensor with cumulative-length metadata. This eliminates padding entirely (up to 63% savings) and reduces the number of kernel launches from N to 1 per attention layer.
NVIDIA Programmatic Dependent Launch (PDL) and device-initiated CUDA Graph Launch reduce per-token launch overhead, overlap work, and eliminate bubbles between decode iterations.
Because decoding involves attention over the entire sequence, KV cache memory is a critical resource for decode workers. For large models and long sequences, KV memory grows linearly with the number of tokens.
Practical estimate: bytes_per_token = 2 × n_layers × n_kv_heads × head_dim × bytes_per_element.
For a Llama 13B-class model with 40 layers, 40 attention heads, FP16, at 4096-token context with standard MHA, KV size is ~0.819 MB/token or ~3.36 GB total. With FP8 KV — ~1.68 GB. With GQA of 8 query groups — ~0.671 GB in FP16 and ~0.336 GB in FP8. For MQA with 1 KV head — ~0.084 GB in FP16.
Paged GPU memory allocator. vLLM's PagedAttention splits the KV cache into fixed-size pages and can offload inactive pages to CPU memory. Paged KV memory managers are implemented in SGLang, TensorRT-LLM, and NVIDIA Dynamo.
KV cache offloading. When GPU memory fills up, decode workers can offload older KV blocks to CPU DRAM or NVMe. NVIDIA Dynamo implements multi-tier management via KV Block Manager (KVBM). In a scenario with 20 multi-turn conversations and 15 users, KVBM with CPU offload gave 2.2–12× TTFT improvement.
The decode cluster's GPU memory is entirely dedicated to holding model weights and KV cache. It is not trying to handle large prefill prompt computations. Disaggregation does not eliminate KV memory issues, but it allows choosing decode node types optimized for memory capacity and bandwidth.
Not every request must be passed to a prefill worker. A disaggregated system uses a routing policy to conditionally use the remote prefill path only when it is likely to help.
| Routing strategy | Description |
|---|---|
| Round robin | Directs to one node in turn |
| Least requests | Directs to the worker with the fewest active requests |
| Prefix aware | Uses request prefix to select a worker |
| KV aware | Directs to the worker whose KV cache best matches the request |
| Factor | Description | Effect |
|---|---|---|
| Prompt length | Token count after prefix caching | Long → offload; short → local |
| Prefix cache hit | Degree to which prompt is already in KV cache | Large → local; none → offload helps |
| Prefill queue length | Number of pending tasks | Long → avoid offload |
| Decode load | Current load on the local worker | Busy → offload helps |
| SLO urgency | Priority or strictness of the requirement | Urgent → dispatch immediately |
The router receives KV cache events and routes the request where the prefix is already in memory
In practice, the routing policy is implemented as a conditional check. Below is a simplified version of the routing logic:
def should_offload_prefill(prompt_length: int,
prefix_cached_length: int,
prefill_queue_size: int,
decode_active_reqs: int,
ttft_slo_ms: int = 500) -> bool:
eff_len = max(0, prompt_length - prefix_cached_length)
PREFILL_LENGTH_THRESHOLD = 256
PREFILL_QUEUE_MAX = 10
DECODE_LOAD_THRESHOLD = 8
long_prefill = (eff_len >= PREFILL_LENGTH_THRESHOLD)
prefill_available = (prefill_queue_size < PREFILL_QUEUE_MAX)
if long_prefill and prefill_available:
return True
if decode_active_reqs >= DECODE_LOAD_THRESHOLD and eff_len >= 64:
return True
return False
In a production deployment, the routing policy should be configured through a file. NVIDIA Dynamo allows complex routing and autoscaling rules in JSON or YAML:
model: ...
split_policy:
prompt_length_threshold: 256
prefix_cache_weight: 10.0
queue_length_weight: 1.5
decode_load_weight: 0.5
enable_hotspot_prevention: true
cache:
reuse_prefix: true
min_cache_hit_ratio: 0.75
autoscale:
prefill:
min_replicas: 4
max_replicas: 12
scale_up: { queue_length: 8, gpu_utilization: 80 }
scale_down: { queue_length: 2, gpu_utilization: 40 }
decode:
min_replicas: 8
max_replicas: 24
scale_up: { queue_length: 16, kv_cache_usage: 75 }
scale_down: { queue_length: 4, kv_cache_usage: 30 }
qos:
enable_early_rejection: true
low_priority_threshold_ms: 500
reject_on_slo_violation: true
Dynamo Planner uses metrics such as TTFT, TPOT, and estimated KV transfer cost to decide whether to change routing — or even reassign GPUs for a specific phase. When summarization tasks surge, Planner reassigns more GPUs to prefill. When reasoning tasks surge, it moves them to decode.
Based on metrics (TTFT/TPOT, GPU util, queue depth), the planner reallocates GPUs between prefill and decode
NVIDIA Dynamo reports 3× TTFT improvement and 2× reduction in average request latency when routing 100,000 real requests to DeepSeek-R1 Distilled Llama 70B (FP8, 2× H100 nodes) with KV cache awareness compared to random routing. The llm-d project on B200 reports 3× output throughput and 2× TTFT speedup with prefix-cache-aware routing.
Speculative decoding can be layered on top of the router's decision process. The system might detect an uncertain branch and send several speculative requests to different decode workers in parallel, trading extra compute for lower latency on unpredictable generation branches.
Adaptive speculative decoding. StreamServe (2026) combined disaggregated prefill/decode with adaptive speculative decoding whose depth is tuned online based on runtime metrics. On StreamShow benchmarks it showed 11–18% latency reduction vs tensor-parallel vLLM and up to 6.8% throughput improvement while keeping TPOT stable. The interaction between disaggregation, metric-aware routing, and adaptive speculation is super-additive.
In practice, speculative decoding via EAGLE-3 can yield up to 6× speedup on dense models. However, with constrained decoding (JSON mode, tool calls with grammars), acceptance rate often drops sharply because the constraint mask invalidates speculative tokens. This must be factored in when planning load in a disaggregated cluster.
QoS policies prioritize or restrict certain requests to meet latency goals. Early rejection — also called admission control — can refuse low-priority requests when the system is saturated. This preserves resources and SLOs for other requests.
Modern systems do this based on a queue time threshold. If a request has waited > X ms, it may be better to reject it than to allow a serious SLO violation. Such a QoS barrier is becoming increasingly common for critical inference systems.
At ultra-scale, QoS mechanisms are necessary to maintain tail latency guarantees under heavy load. Instead of allowing SLO violation for all requests, a well-designed system prefers graceful load shedding: rejecting or deferring low-priority requests when it is clear that serving them would violate guarantees for others.
class QoSController:
def __init__(self, ttft_slo_ms: int = 500):
self.ttft_slo_ms = ttft_slo_ms
def admit_request(self, priority: str) -> bool:
est_ttft = (get_current_prefill_queue_length()
* get_avg_prefill_time_per_req()
+ get_current_decode_queue_length()
* get_avg_decode_time_per_req())
if est_ttft > self.ttft_slo_ms and priority == "low":
return False
return True
Adaptive generation limits. If the decode phase threatens to take too long and violate the TPOT SLO, the system can terminate generation early. If the user requested 1000 tokens but the system is under pressure, it may allow only 200 tokens, preserving resources for other requests.
Graceful degradation. One interesting form is temporarily disabling remote prefill for low-priority requests when the system is under load. Low-priority requests do local prefill, freeing prefill workers for high-priority ones.
As systems scale to thousands and millions of GPU nodes, controlling tail latency becomes harder. Isolating prefill tasks on one node and decode tasks on another means a slow node on one side does not sharply affect tasks on the other side. Caching large parts of the context means one slow operation does not slow the whole generation as much.
One of the most powerful consequences of disaggregation is the ability to use different GPU types for prefill and decode, matching hardware to each phase's resource profile.
Prefill is compute-bound: it needs maximum FP8/FP4 TFLOPS; HBM capacity is not critical. Decode is memory-bound: it needs maximum HBM bandwidth; compute power is barely used. This creates inefficiency: using an H100 (~$2.57/hour per GPU) for decode means paying roughly 3.6× more for memory bandwidth you do not need. The L40S (~$0.72/hour) has sufficient HBM bandwidth for decode of 70B models and is 3.5× cheaper.
| Configuration | GPU | Cost/hour | Throughput | Cost per 1M tokens |
|---|---|---|---|---|
| Homogeneous | 4× H100 SXM5 | $10.28 | 3200 tok/s | $0.89 |
| Heterogeneous 1 | 1× H100 + 2× L40S | $4.01 | 2400 tok/s | $0.46 |
| Heterogeneous 2 | 2× H100 + 4× A100 | $9.30 | 4800 tok/s | $0.54 |
Heterogeneous configuration 1 (H100 for prefill + L40S for decode) saves 61% on hourly cost and reduces cost per 1M tokens by 48% — while reducing throughput by 25%. For models up to 70B this is the most cost-effective pairing. For 70B–405B models, B200 + A100 80G works better.
"Compute prefill + budget decode" — prefill on H100/B200, decode on L40S/A100. Dynamo router sends prefill to the compute pool, KV cache transfers via NIXL/RDMA to the decode pool.
"Work stealing" — implemented in HeteroServe. When consumer GPUs idle, they temporarily take decode tasks from other workers, restoring idle capacity.
"Spot GPUs with on-demand fallback" — ShuntServe. Decode on cheap spot instances; on revocation, fall back to on-demand. A DP-optimizer based on a roofline model finds optimal layer placement. Achieves 1.42× higher throughput on Llama-3.1-70B.
"GPU Combo" — Cauchy. A conceptual "GPU combination" is chosen for each workload class based on cost-efficiency.
The DUET (2026) work proposes chip-level hardware partitioning: "Prefill Chips" with enlarged systolic arrays and GDDR7 instead of HBM, and "Decode Chips" retaining high bandwidth but reduced compute power. Reports show 8% higher prefill performance at 52% lower cost for Prefill Chips, and 97% decode performance at 28% lower TDP for Decode Chips — relative to modeled H100s.
The baseline experiment showed: goodput of the colocated configuration — 1.6 RPS per GPU; disaggregated 2P1D — 3.3 RPS per GPU (2.1× improvement) under strict SLOs.
PD disaggregation with expert parallelism: 52.3k input tokens/s per prefill node (24 GPUs), 22.3k output tokens/s per decode node (72 GPUs). On GB200 NVL72 — up to 3.8× for prefill and 4.8× for decode vs an H100 configuration.
Measured KV transfer overhead for Llama-3.1-8B (ISL=8000, OSL=50): only 37 ms (TTFT: aggregated 173 ms vs disaggregated 210 ms). The disaggregated regime gives 41% per-token speedup and 22% throughput increase.
Disaggregated quantization. Using different quantization formats for prefill (hardware-native NVFP4) and decode (weight-only 2–3 bit) with joint weight optimization. QADD showed accuracy gains on decode tasks while preserving prefill speed — up to 1.49× speedup over BF16 on Qwen3-8B and Gemma3-12B.
Adaptive speculative decoding. Combining disaggregation with online tuning of speculation depth based on runtime metrics. StreamServe showed super-additive benefit from this combination.
Phase-specialized chips. Projects such as DUET propose chips designed exclusively for prefill or decode, with different memory architecture and compute blocks.
Disaggregation for diffusion LLMs. Prefilling-dLLM extends disaggregation to diffusion language models, caching chunked prefix KV once and reusing it across denoising steps. On LongBench and Infinitebench — 9.1–28.0× speedup at 8K–32K contexts.
Multimodal disaggregation. Extending to a three-phase architecture: encode (vision encoder) → prefill (language prefill) → decode. NVIDIA Dynamo already supports disaggregated encode/prefill/decode with embedding cache — 30% faster TTFT on image workloads.
Prefill/decode disaggregation has become standard practice in modern high-scale LLM inference. Virtually all major AI providers — Meta, Amazon, NVIDIA, Google, and OpenAI — have deployed a disaggregated architecture in some form in their large-scale LLM deployments.
Although most of these companies do not publish their full production architecture, many open implementations — vLLM, SGLang, NVIDIA Dynamo, llm-d, LMCache, Mooncake — demonstrate the approach and its benefits. Disaggregated prefill-decode yields higher goodput — useful throughput within latency constraints — and better economic efficiency than monolithic designs.
Eighteen months after DistServe, the concept evolved from a small prototype to a foundational technique powering virtually every modern inference engine.
Prefill/decode disaggregation is not an endpoint but a platform for further optimizations: disaggregated quantization, adaptive speculative decoding, phase-specialized chips, multimodal disaggregation. Each layer gives its own gain, and the right order of steps — profiling, KV cache, routing, QoS, heterogeneity — matters more than any single "silver bullet".