Example of Disaggregated Prefill in vLLM v1
This example demonstrates how to run LMCache with disaggregated prefill using NIXL on a single node.
Prerequisites
- Install LMCache. You can simply run
pip install lmcache. - Install NIXL.
- At least 4 GPUs
- Valid Hugging Face token (HF_TOKEN) for Llama 3.1 8B Instruct.
Usage
Run
bash disagg_example_xpyd.sh
to start disaggregated prefill and benchmark the performance.
The script will:
- Launch 2 decoder instances listening on port 7200 and 7201, respectively
- Launch 2 prefill instances listening on ports 7100 and 7101, respectively
- Launch a proxy server that uses round-robin to distribute requests between the prefill instances and decode instances, listening on port 9100
Press Ctrl+C to stop the servers.
Advanced Configuration
Multi-Host Support
The proxy server supports CSV format for specifying multiple hosts and ports for both prefillers and decoders. This enables flexible deployment across multiple machines:
# Multi-machine deployment
python disagg_proxy_server.py \
--prefiller-host "${host1-IP},${host2-IP}" \
--prefiller-port "8000" \
--decoder-host "${host3-IP},${host4-IP}" \
--decoder-port "8000,8001"
# Above example `--prefiller-port "8000"` means the host1 and host2 vLLM instances both use port 8000 for model serving.
# Above example `--decoder-port "8000,8001"` means host3 uses port 8000 and host4 uses 8001 for model serving. (Using different ports on different hosts is not required, but demonstrates argument flexibility)
To support Tensor-Parallel
In Decoder's lmcache configuration file, different ports are required for different TP ranks. The below example shows a TP=8 case with 8 ports (to support vLLM instance with --tensor-parallel-size 8)
nixl_peer_init_port: [7300,7301,7302,7303,7304,7305,7306,7307]
nixl_peer_alloc_port: [7400,7401,7402,7403,7404,7405,7406,7407]
Accordingly, the disagg_proxy_server.py should use the same ports aligning with decoder configuration:
python disagg_proxy_server.py \
.... \ # other arguments
--decoder-init-port "7300,7301,7302,7303,7304,7305,7306,7307" \
--decoder-alloc-port "7400,7401,7402,7403,7404,7405,7406,7407"
To support bound prefiller and decoder for the same session
For xPyD multi-round QA scenario, disagg_proxy_server can provide the clients with bound prefiller/decoder during the same session, in addition to round-robin.
http requests can provide a header (e.g., 'session-id: <unique-id>') for the disagg_proxy_server to select bound prefiller/decoder during the same session:
- Header Value: A unique identifier (e.g., <unique-id>) for the session. All requests within the same multi-round conversation must use the same identifier.
- Header Name: The name of the header field (e.g., 'session-id') for disagg_proxy_server to find the <unique-id>.
This Header Name must match the value of the '$CLIENT_BOUND_KEY' environment variable set on the proxy server.
export CLIENT_BOUND="true"
# The proxy uses the value of $CLIENT_BOUND_KEY as the header name to find the session ID.
# For example, if CLIENT_BOUND_KEY="session-id", the proxy looks for the "session-id" header.
# Requests with the same session ID are routed to the same services.
# If $CLIENT_BOUND_KEY is not set, it defaults to "session-id".
export CLIENT_BOUND_KEY="session-id" # optional, configure a constant string name.
python disagg_proxy_server.py \
.... \ # other arguments
# client side, set the kv pair, such as (session-id: uid) in header
# openai python sdk, put the kv pair in extra_headers param
import uuid
uid = str(uuid.uuid4()) # the uid can be changed to any other unique identifier string for a session
extra_headers = {"session-id" : uid}
response = await self.client.chat.completions.create(
messages=messages,
model=self.model,
temperature=0,
stream=True,
max_tokens=max_tokens,
stream_options={"include_usage": True},
extra_headers=extra_headers,
extra_body=extra_body
)
Example benchmark command
If you have vLLM's serving benchmark tool, you can run the following command to benchmark the serving performance of the disaggregated prefill setup:
vllm bench serve --port 9100 --seed $(date +%s) \
--model meta-llama/Llama-3.1-8B-Instruct \
--dataset-name random --random-input-len 7500 --random-output-len 200 \
--num-prompts 30 --burstiness 100 --request-rate 1 --ignore-eos
Expected output from the benchmark script:
============ Serving Benchmark Result ============
Successful requests: 30
Benchmark duration (s): 31.34
Total input tokens: 224970
Total generated tokens: 6000
Request throughput (req/s): 0.96
Output token throughput (tok/s): 191.44
Total Token throughput (tok/s): 7369.36
---------------Time to First Token----------------
Mean TTFT (ms): 313.41
Median TTFT (ms): 272.83
P99 TTFT (ms): 837.32
-----Time per Output Token (excl. 1st token)------
Mean TPOT (ms): 8.84
Median TPOT (ms): 8.72
P99 TPOT (ms): 11.35
---------------Inter-token Latency----------------
Mean ITL (ms): 8.84
Median ITL (ms): 8.61
P99 ITL (ms): 11.43
==================================================
Components
Server Scripts
disagg_vllm_launcher.sh- Launches individual vLLM servers for prefill/decode, and also launches the proxy server.disagg_proxy_server.py- FastAPI proxy server that coordinates between prefiller and decoderdisagg_example_xpyd.sh- Main script to run the example
Configuration
configs/lmcache-prefiller-config.yaml- Configuration for prefiller serverconfigs/lmcache-decoder-1-config.yaml- Configuration for decoder server 1configs/lmcache-decoder-2-config.yaml- Configuration for decoder server 2
Log Files
The main script generates several log files:
prefiller1.logandprefiller2.log- Logs from the prefill serversdecoder1.loganddecoder2.log- Logs from the decode serverproxy.log- Logs from the proxy server