Skip to main content
BlogAI/ML

vLLM Disaggregated Prefill Decode: Panduan NixlConnector

Cara memisahkan vLLM menjadi instance prefill dan decode dengan NixlConnector, apa kata angka goodput L40S dari vLLM sendiri, dan pengecekan transfer agar Anda tidak mem-benchmark setup yang rusak.

Matthews Wong

Software Engineer

Oktober 202612 menit baca
vLLM Disaggregated Prefill Decode: Panduan NixlConnector

Ringkasan Utama

Disaggregated prefill dan decode di vLLM v0.31.0 menjalankan prefill dan decode sebagai instance vllm serve terpisah yang dihubungkan NixlConnector, sehingga prompt panjang tidak lagi menghambat streaming token. Tes L40S vLLM menurunkan p99 inter-token latency dari 169 ms ke 29 ms, tetapi tanpa akses GPU peer-to-peer, median time to first token naik dari 0,7 detik ke 2,2 detik.

Kesalahan paling mahal pada split prefill dan decode adalah mem-benchmark setup yang diam-diam rusak. Kalau dua GPU tidak bisa saling menjangkau secara langsung, split tetap jalan, tetap mengembalikan teks yang benar, dan tetap menampilkan grafik inter-token latency yang bersih, padahal setiap request membayar lebih dari satu detik untuk memindahkan KV cache. Anda baru sadar ketika ada yang bertanya kenapa time to first token jadi tiga kali lipat.

Tulisan ini adalah pembacaan teliti panduan disaggregated serving dari vLLM sendiri yang terbit pada 29 September 2026, bersama dokumentasi NixlConnector, halaman fitur disaggregated prefilling, dan release notes v0.31.0. Saya tidak menjalankan setup ini sendiri, jadi semua pengukuran di bawah adalah milik vLLM dan ditandai demikian, dan perintahnya adalah yang dicetak halaman-halaman tersebut. Kalau sebuah angka adalah hitungan saya sendiri dan bukan hasil pengukuran, saya sebutkan.

Kenapa satu proses vllm serve berebut dengan dirinya sendiri

Satu proses vllm serve mengerjakan tiga pekerjaan berbeda. Prefill membaca seluruh prompt dalam satu ledakan yang terikat compute dan menentukan time to first token. Decode menghasilkan satu token per langkah, terikat memory bandwidth, dan menentukan inter-token latency. Frontend CPU mengurus templating, tokenisation, dan parsing output. Di satu GPU, dua yang pertama bergantian memakai hardware yang sama, sehingga prefill panjang jatuh di tengah aliran token request lain.

Ukuran keberhasilan di panduan itu adalah goodput: jumlah request per detik yang memenuhi target time to first token sekaligus target inter-token latency, bukan token per detik puncak. Halaman fitur jujur soal sisi lainnya: 'Disaggregated prefill DOES NOT improve throughput.' Yang didapat adalah kemampuan menyetel kedua latency secara terpisah, dengan pengaturan paralel berbeda per instance, dan mencegah prefill menyela decode. Halaman yang sama menandai fitur ini sebagai experimental dan bisa berubah.

Idenya lebih tua dari implementasi vLLM. Paper DistServe menempatkan prefill dan decoding di GPU berbeda dan melaporkan bisa melayani 7,4x lebih banyak request, atau memenuhi target latency 12,6x lebih ketat, dibanding sistem pembandingnya. Anggap itu batas atas riset, bukan prakiraan untuk model Anda. Sisa tulisan ini membahas seberapa banyak yang bertahan ketika bertemu interconnect sungguhan.

Hitung biaya transfer KV sebelum membangun apa pun

Setiap request dalam split harus memindahkan KV cache prompt-nya dari GPU prefill ke GPU decode sebelum token pertama dihasilkan. Ukurannya ditentukan model, jadi tagihannya bisa diperkirakan dari model card. Panduan itu menyebut Llama-3.1-70B dalam BF16 memakai 320 KiB KV cache per token. Prompt 10.000 token berarti sekitar 3 GB, dan pada 400 Gb/s waktu di kabel saja sekitar 64 ms. Itu hitungan saya: 3,2 GB dikali 8 bit, dibagi 400 Gb/s, sebelum overhead protokol.

KasusVolume KVWaktu sampai ke decodeAsal angka
Llama-3.1-70B BF16, prompt 10.000 tokenSekitar 3 GB (320 KiB per token)Sekitar 64 ms pada 400 Gb/s, sebelum overheadKV per token dari panduan; waktunya hitungan saya
Qwen2.5-7B, prompt 8k token, dua L40S tanpa peer-to-peerSekitar 470 MBSekitar 1,3 detikDiukur panduan di mesin ujinya

Baris kedua adalah pelajarannya. Data yang kurang dari seperenam ukuran baris pertama memakan waktu sekitar dua puluh kali lebih lama, karena jalur antar-GPU bukan peer to peer. Saya membandingkan waktu kabel teoretis dengan waktu terukur di hardware berbeda, jadi baca ini sebagai orde besaran, bukan rasio. Intinya, bandwidth bukan satu-satunya variabel: rutenya juga. Panduan itu meminta dua pengecekan sebelum benchmark apa pun.

# 1. Topology. On one box this must say OK between the prefill GPU and
#    the decode GPU. Anything else means KV moves by a slower path.
nvidia-smi topo -p2p r

# 2. Send a few long prompts ONE AT A TIME through the proxy, then read the
#    decode instance's log for its "KV Transfer metrics" line. If
#    "Avg xfer time" is in the hundreds of milliseconds, fix the transport
#    before you measure anything else.
grep "KV Transfer metrics" decode.log | tail -n 5

Yang pertama memastikan topologi, yang kedua membaca biaya transfer yang sebenarnya. Keduanya tidak butuh harness benchmark dan hanya memakan semenit. Kalau salah satunya janggal, apa pun yang Anda ukur sesudahnya tidak berarti.

Jalankan tiga proses

Deployment minimal di panduan itu adalah dua instance vllm serve dan sebuah proxy, dengan asumsi vLLM v0.31.0 atau lebih baru. Prefiller memakai kv_role kv_producer, decoder memakai kv_consumer, dan keduanya memuat NixlConnector. NIXL dipasang dengan uv pip install nixl di NVIDIA; platform lain membangunnya dari source terhadap UCX. Saya mengganti contoh wiring Qwen3-0.6B dari panduan dengan Qwen2.5-7B-Instruct, karena panduan menyebut model 0,6B melakukan prefill begitu cepat sampai hampir tidak mengganggu decode, jadi tidak ada yang perlu diperbaiki oleh split.

# Requires vLLM v0.31.0 or later, and NIXL: uv pip install nixl

# Prefiller on GPU 0. kv_producer = this instance computes KV and hands it off.
CUDA_VISIBLE_DEVICES=0 UCX_NET_DEVICES=all VLLM_NIXL_SIDE_CHANNEL_PORT=5600 \
vllm serve Qwen/Qwen2.5-7B-Instruct --port 8100 \
  --kv-transfer-config '{"kv_connector":"NixlConnector","kv_role":"kv_producer","kv_load_failure_policy":"fail"}' \
  2>&1 | tee prefill.log

# Decoder on GPU 1. Its side-channel port MUST differ from the prefiller's
# whenever both run on one host: each worker needs a unique port per host.
CUDA_VISIBLE_DEVICES=1 UCX_NET_DEVICES=all VLLM_NIXL_SIDE_CHANNEL_PORT=5601 \
vllm serve Qwen/Qwen2.5-7B-Instruct --port 8200 \
  --kv-transfer-config '{"kv_connector":"NixlConnector","kv_role":"kv_consumer","kv_load_failure_policy":"fail"}' \
  2>&1 | tee decode.log

# Development proxy from the vLLM repository. Clients talk to :8192.
python tests/v1/kv_connector/nixl_integration/toy_proxy_server.py --port 8192 \
  --prefiller-hosts localhost --prefiller-ports 8100 \
  --decoder-hosts localhost --decoder-ports 8200

Contoh di dokumentasi NixlConnector juga memakai --enforce-eager. Saya tidak menyertakannya agar benchmark menjalankan engine seperti di production. Proxy-nya adalah toy_proxy_server.py dari repository vLLM, dan panduan menyebutnya hanya untuk development, dengan llm-d dan Dynamo sebagai pilihan production. Setelan yang penting adalah berikut.

SetelanLetakDefaultFungsi
VLLM_NIXL_SIDE_CHANNEL_PORTEnvironment, setiap worker5600Port handshake. Harus unik per worker di satu host; dengan TP atau DP nilainya base port ditambah dp_rank
VLLM_NIXL_SIDE_CHANNEL_HOSTEnvironmentlocalhostIsi dengan alamat yang bisa dijangkau saat prefill dan decode berjalan di mesin berbeda
kv_rolekv-transfer-configIsi secara eksplisitkv_producer untuk prefill, kv_consumer untuk decode. kv_both sudah deprecated untuk NixlConnector
kv_lease_durationkv_connector_extra_config30 detikBerapa lama prefill menahan block yang sudah selesai sambil menunggu decode membacanya; heartbeat memperpanjangnya
kv_load_failure_policykv-transfer-configfailfail menggagalkan request saat KV load gagal; recompute membangun ulang block yang gagal di decode
backendskv_connector_extra_configUCXMemilih transport lain, misalnya LIBFABRIC; ketersediaannya bergantung pada cara NIXL dibangun

Lintas mesin, beri setiap instance VLLM_NIXL_SIDE_CHANNEL_HOST sendiri dan berikan ke proxy alamat dan port setiap instance. Untuk multi-host data parallel, dokumentasi menyebut hanya host dan port instance head yang dibutuhkan.

Apa yang dilakukan proxy pada setiap request

Proxy adalah tempat split mulai terlihat, jadi layak mengenal empat langkahnya. Client hanya bicara ke port-nya, 8192 pada contoh, dan tidak pernah langsung ke prefill atau decode.

  1. Proxy mengirim request ke prefill dengan max_tokens bernilai 1 dan kv_transfer_params berisi do_remote_decode true.
  2. Prefill menghitung KV cache, menahan block-nya, dan mengembalikan kv_transfer_params yang menunjuk ke block tersebut.
  3. Proxy meneruskan request asli ke decode dengan params itu terlampir.
  4. Decode menarik block lewat NIXL, lalu generate.
# Step 1: what the proxy sends to the PREFILL instance (completions API).
# max_tokens is 1 because prefill only has to produce the KV cache.
{
  "model": "Qwen/Qwen2.5-7B-Instruct",
  "prompt": "<the full long prompt>",
  "max_tokens": 1,
  "kv_transfer_params": {"do_remote_decode": true}
}

# Step 2: prefill answers with kv_transfer_params describing the blocks it is
# holding. Step 3: the proxy sends the ORIGINAL request to the DECODE instance
# with those params attached. Step 4 happens inside decode: it pulls the
# blocks over NIXL, then generates.

# Chat completions variant: call prefill with "return_token_ids": true, then
# give decode its kv_transfer_params plus "prompt_token_ids" from prefill.

Untuk chat completions, varian di panduan meminta token ID dari prefill dan memberikannya ke decode. Halaman fitur menandai pemakaian ulang token ID itu experimental: ID diabaikan jika messages berisi konten non-teks atau echo diaktifkan, dan ID yang tidak valid menghasilkan HTTP 400. Konsekuensi praktisnya, bug di proxy terlihat seperti bug vLLM. Kirim satu request pendek lewat proxy dan pastikan completion normal sebelum menambah beban.

Benchmark goodput, bukan token per detik

Panduan itu mengukur dengan vllm bench serve dan definisi goodput time to first token dalam 2000 ms dan time per output token dalam 30 ms. Workload-nya dataset acak dengan input 8.192 token dan output 256 token serta ignore-eos, sehingga setiap request berjalan sampai panjang penuh. Kalau Anda memakai ulang seed di berbagai request rate, panduan meminta prefix caching dimatikan, kalau tidak run berikutnya bisa dilayani dari cache run sebelumnya.

# Goodput = requests per second that meet BOTH targets: TTFT 2000 ms, TPOT 30 ms.
# Run it against the collocated server and against the proxy on :8192 with the
# same hardware, then compare. Repeat per --request-rate.
vllm bench serve --model Qwen/Qwen2.5-7B-Instruct --port 8192 \
  --dataset-name random --random-input-len 8192 --random-output-len 256 --ignore-eos \
  --request-rate 0.4 --num-prompts 100 \
  --percentile-metrics ttft,tpot,itl --metric-percentiles 50,99 \
  --goodput ttft:2000 tpot:30
Pengukuran (Qwen2.5-7B, dua L40S, prompt sekitar 8k token)CollocatedSplit prefill/decode
p99 inter-token latency pada 0,4 req/s169 ms29 ms
p99 inter-token latency pada 2 req/s263 ms25 sampai 52 ms di semua beban
Median inter-token latency sampai 1 req/s21 sampai 24 ms21 sampai 24 ms
Median time to first token pada 0,2 req/s0,7 detik2,2 detik

Baca tabel itu untuk apa yang tidak dikatakannya. Median inter-token latency sama di kedua setup, jadi split tidak melakukan apa pun untuk token yang tipikal. Yang dihilangkan adalah tail: p99 pada 0,4 req/s turun dari 169 ms ke 29 ms, dan panduan menyebut split tidak pernah melewati 52 ms. Harganya ada di baris terakhir, ketika mesin L40S tidak punya akses peer-to-peer dan median time to first token naik dari 0,7 detik ke 2,2 detik. Split hanya menang jika target layanan Anda lebih peduli pada tail daripada waktu token pertama.

Dua hasil eksternal dikutip panduan itu, dan saya belum memeriksa benchmark aslinya. Pada satu node 8xMI300X yang menjalankan Qwen3-235B-A22B-FP8, connector MoRI-IO dari AMD memenuhi kedua target untuk 73 dari 100 request dibanding 30 dari 100 pada collocated, sekitar 2,4x goodput. llm-d melaporkan sekitar 59% lebih rendah untuk mean end-to-end latency dan 67% lebih rendah untuk P95 pada gpt-oss-120b di 16 H200. Keduanya memakai model lebih besar di akselerator kelas data center, bukan pasangan L40S di atas, jadi jangan membacanya sebagai prediksi untuk mesin kecil.

Bagaimana split yang rusak tampak seperti yang sehat

Sebuah split bisa salah dengan cara yang tetap mengembalikan teks. Berikut empat kegagalan yang dijelaskan sumber, beserta pengecekan yang membongkarnya. Baris pertama yang paling sering menjebak benchmark: grafik inter-token tampak sempurna sementara token pertama merangkak.

GejalaPenyebab yang mungkinPengecekan
Inter-token latency bagus, median time to first token beberapa kali lebih burukTidak ada jalur peer-to-peer antar GPU, sehingga KV merangkak lewat rute yang lambatnvidia-smi topo -p2p r, lalu Avg xfer time di baris KV Transfer metrics milik decode
Request error saat beban naikkv_load_failure_policy bernilai fail dan sebuah transfer gagalLog decode di sekitar request yang gagal
Block hilang sebelum dibaca decodekv_lease_duration 30 detik habis saat request mengantre di decodeMetrik vllm:nixl_num_kv_expired_reqs; naikkan lease jika nilainya tinggi
Output multi-turn dari reasoning model lancar tetapi salahbidirectional_kv_xfer dengan chat template yang membuang thinking trace giliran sebelumnya, sehingga block prefill dan decode tidak lagi cocokIssue vLLM 43094; panduan menyebut belum ada yang menangkap ketidakcocokan ini di vLLM saat ini

Ada pilihan sengaja pada failure policy. Default-nya, fail, langsung menggagalkan request dan menghindari prefill ulang di instance decode. Alternatifnya, recompute, membangun ulang block yang gagal secara lokal di decode, itulah cara request bertahan dari transfer yang gagal. Dokumentasi memperingatkan ini bisa menyebabkan jitter performa decode dan tail latency lebih tinggi, dan menyebutnya tidak efisien untuk production.

Jangan aktifkan recompute selagi Anda memvalidasi setup baru. Ia mengubah transfer yang gagal menjadi keberhasilan yang lambat, persis kegagalan yang hendak dibongkar pengecekan di atas. Pertahankan fail sampai jalur transfer lolos, supaya link yang rusak muncul sebagai error, bukan sebagai angka latency yang menyenangkan.

Kasus multi-turn pantas mendapat kalimat tersendiri. Dengan bidirectional_kv_xfer di kedua instance, prefill menarik kembali block giliran sebelumnya dari decode sehingga hanya token baru yang dihitung. Itu penghematan nyata, dan itu pula sebabnya template yang menulis ulang riwayat menghasilkan output yang salah, bukan sekadar lambat. Release notes v0.31.0 juga mencantumkan dead peer state dilepas tanpa menunggu TTL, salah satu alasan memakai versi itu dan bukan yang lebih lama.

Render tier tanpa GPU, dan apa yang masih terbuka

Split kedua memindahkan pekerjaan CPU, bukan GPU. vllm launch render menjalankan templating dan parsing tanpa GPU, dan vllm serve --tokens-only menjalankan engine hanya pada token ID. Render, prefill, dan decode lalu bisa berjalan sebagai tier terpisah, dengan kode Anda sendiri yang mengatur serah terimanya.

# CPU-only frontend: templating, tokenisation, parsing. No GPU needed.
# The parser flags must match the engine's configuration, or the split into
# content / reasoning / tool_calls will differ from an in-process server.
vllm launch render Qwen/Qwen3-0.6B --port 8100 \
  --reasoning-parser qwen3 --enable-auto-tool-choice --tool-call-parser hermes

# Engine on token IDs only. Rejects stop strings; cannot return text itself.
vllm serve Qwen/Qwen3-0.6B --tokens-only --port 8200

# The request travels three hops:
#   POST :8100/v1/chat/completions/render     -> token IDs
#   POST :8200/inference/v1/generate          -> generated token IDs
#   POST :8100/v1/chat/completions/derender   -> text, reasoning, tool calls
# Wrong: calling derender with only the generated tokens. It needs
#   chat_request, the original request, passed back in. Without it: HTTP 400.

Angka di panduan kecil, dan itulah daya tariknya. Me-render prompt 9.000 token memakan sekitar 15 ms CPU, dan satu server render menangani 73 request per detik pada sedikit lebih dari satu core. Jumlah worker diatur dengan --renderer-num-workers dengan default 1. Panduan juga memperingatkan bahwa konfigurasi render dan engine harus cocok, kalau tidak pemisahan content, reasoning, dan tool_calls akan berbeda dari server in-process.

Issue yang masih terbuka adalah bagian yang perlu dibaca sebelum mengadopsinya. Streaming derender dengan parser memutar ulang seluruh riwayat token di setiap chunk, yang diukur panduan sekitar 9x CPU in-process, dengan lapisan caching dilacak di issue 57571. Prompt panjang dikirim ulang di setiap chunk streaming. Batch derender bisa melewatkan finish_reason tool_calls (issue 47931), dan logprobs kembali sebagai string placeholder (issue 57574). Saya akan memperlakukan 73 request per detik sebagai batas atas yang perlu diukur terhadap traffic Anda sendiri.

Mulailah dengan split prefill dan decode saja. Render tier memindahkan pekerjaan CPU dari host GPU, tetapi issue terbuka di panduan itu menunjukkan streaming memakan sekitar 9x CPU in-process, jadi baru layak dipakai ketika CPU frontend terbukti jadi bottleneck.

Yang dikerjakan hari Senin

Inilah urutan yang akan saya ikuti, berhenti di langkah pertama yang gagal. Pengecekan murah dan benchmark mahal, jadi pengecekan didahulukan.

  1. Pastikan vLLM v0.31.0 atau lebih baru di kedua host dan pasang NIXL.
  2. Jalankan nvidia-smi topo -p2p r dan syaratkan OK antara GPU prefill dan decode. Di mesin yang tidak bisa menunjukkan OK, harapkan sesuatu seperti transfer 1,3 detik yang diukur panduan.
  3. Jalankan prefill dan decode dengan side-channel port berbeda dan kv_load_failure_policy fail, lalu kirim satu request pendek lewat proxy.
  4. Kirim beberapa prompt panjang satu per satu dan baca Avg xfer time di baris KV Transfer metrics milik decode. Ratusan milidetik berarti perbaiki transport lebih dulu.
  5. Baru setelah itu jalankan benchmark goodput, dengan model kelas 7B, terhadap baseline collocated di hardware yang sama, dengan prefix caching dimatikan jika Anda memakai ulang seed.
#!/usr/bin/env bash
# preflight.sh: stop at the first check that fails.
set -euo pipefail

# 1. Version. The guide assumes v0.31.0 or later.
python -c "import vllm; print(vllm.__version__)"

# 2. Peer-to-peer between the GPUs. Read the matrix: you want OK between
#    the prefill and decode devices.
nvidia-smi topo -p2p r

# 3. One short request end to end through the proxy before any load.
curl -s localhost:8192/v1/completions \
  -H "Content-Type: application/json" \
  -d '{"model":"Qwen/Qwen2.5-7B-Instruct","prompt":"Say hello.","max_tokens":16}'

# 4. A few long prompts at a low rate, then read the transfer cost.
vllm bench serve --model Qwen/Qwen2.5-7B-Instruct --port 8192 \
  --dataset-name random --random-input-len 8192 --random-output-len 64 \
  --ignore-eos --request-rate 0.1 --num-prompts 5
grep "KV Transfer metrics" decode.log | tail -n 5

# 5. Is the decode side losing blocks to lease expiry? (kv_lease_duration)
curl -s localhost:8200/metrics | grep nixl_num_kv_expired_reqs

Tulis aturan go atau no-go sebelum menjalankan benchmark. Kalau p99 inter-token latency Anda sudah memenuhi target saat collocated, split hanya menambah satu peran deployment dan jalur transfer tanpa manfaat, karena dokumentasi tegas bahwa ia tidak meningkatkan throughput. Kalau tail yang menyakitkan, dan pengecekan transfer lolos, angka di atas menunjukkan layak dicoba.

Disaggregation adalah alat pembentuk latency dengan tagihan transfer, bukan percepatan gratis. Aturan yang dibawa: ukur jalur transfer KV lebih dulu, benchmark goodput terhadap baseline collocated di hardware identik, dan biarkan kv_load_failure_policy tetap fail sampai angkanya nyata.

FAQ

Pertanyaan Umum

Jawaban singkat untuk pertanyaan yang paling sering diajukan pembaca tentang topik ini.

Apakah disaggregated prefill meningkatkan throughput vLLM?

Tidak. Halaman fitur vLLM menyatakan dengan jelas bahwa disaggregated prefill tidak meningkatkan throughput. Yang didapat adalah penyetelan time to first token dan inter-token latency secara terpisah, serta perlindungan decode dari gangguan prefill, yang tampak sebagai tail inter-token latency lebih rendah dan goodput lebih baik.

Versi vLLM mana yang dibutuhkan untuk memisahkan prefill dan decode dengan NixlConnector?

Panduan disaggregated serving vLLM bulan September 2026 mengasumsikan v0.31.0 atau lebih baru, dan v0.31.0 dirilis pada 5 Oktober. Pasang juga NIXL; di NVIDIA caranya uv pip install nixl. Beri prefiller kv_role kv_producer dan decoder kv_consumer.

Bagaimana cara memastikan transfer KV cache bukan bottleneck?

Di satu mesin, jalankan nvidia-smi topo -p2p r dan harapkan OK antara GPU prefill dan decode. Lalu kirim beberapa prompt panjang satu per satu dan baca baris KV Transfer metrics di log decode. Jika Avg xfer time mencapai ratusan milidetik, perbaiki transport sebelum benchmark.

Haruskah kv_load_failure_policy diatur ke recompute?

Jangan saat memvalidasi setup baru. Default-nya, fail, menggagalkan request ketika KV load gagal sehingga link yang rusak terlihat. Dokumentasi menyebut recompute membangun ulang block yang gagal di instance decode, yang bisa menambah jitter dan tail latency, dan menyebutnya tidak efisien untuk production.

Apa itu render tier tanpa GPU di vLLM?

Itu server khusus CPU yang dijalankan dengan vllm launch render untuk chat templating, tokenisation, dan parsing output, sementara vllm serve --tokens-only menjalankan engine pada token ID. Panduan mengukur sekitar 15 ms CPU untuk me-render prompt 9.000 token. Panduan juga mencantumkan issue terbuka, misalnya streaming derender yang memakan sekitar 9x CPU in-process.