vLLM Router¶
frontend.type: vllm-router runs the official
vLLM Router in front of direct
vllm serve workers. srtctl supplies the statically allocated worker URLs;
vLLM remains responsible for the DP/TP/PP engine topology inside each worker.
Responsibilities¶
- The existing vLLM backend allocates endpoints and derives
per_nodeDP, cross-node TP/PP rendezvous, device IDs, and headless followers. - The frontend adapter starts Router, supplies aggregate or P/D worker URLs, adds NIXL bootstrap ports for P/D, and derives Router's node-local DP expansion factor.
- Readiness first validates Router's
/workerscounts, then requires HTTP 200 from every exact base/healthURL advertised to Router. This prevents benchmark and eval traffic from racing a partially started DP pool. - Router stdout/stderr is captured in the normal job log directory alongside
backend and benchmark logs.
frontend.container_imagecan select a separate Router image; otherwise the model container is reused.
DP recipes must use upstream's default engine.dp_launch_mode: per_node.
The deprecated per_gpu mode launches Dynamo registrations rather than
independently routable API servers and is rejected for vLLM Router DP.
Aggregate cases¶
One direct engine behind Router¶
This is the smallest smoke topology. Router is functionally optional, but it validates the adapter and provides the same public interface as larger cases.
frontend:
type: vllm-router
enable_multiple_frontends: false
args:
policy: consistent_hash
resources:
gpus_per_node: 8
engine: vllm
roles:
agg:
nodes: 1
workers: 1
args:
tensor-parallel-size: 8
Multiple aggregate replicas¶
Each logical aggregate worker contributes one or more routable base URLs.
Router receives them through --worker-urls and distributes sessions between
the expanded ranks.
resources:
gpus_per_node: 8
roles:
agg:
nodes: 4
workers: 4
Node-local data parallelism¶
For DEP8 on two four-GPU nodes, upstream srt-slurm creates one hybrid-LB
vllm serve process on each node. Router receives both base URLs as two
node-local pools. srtctl does not apply --intra-node-data-parallel-size to
this multi-node endpoint because the second pool owns global ranks 4 through
7; vLLM's node-local hybrid load balancer selects among those ranks.
resources:
gpus_per_node: 4
engine: vllm
roles:
agg:
nodes: 2
workers: 1
args:
data-parallel-size: 8
enable-expert-parallel: true
Model parallelism spanning nodes¶
Upstream's native topology is preserved. If one TP/PP replica spans nodes, only its global API leader has a positive HTTP port and is advertised to Router; the remaining processes stay headless.
resources:
gpus_per_node: 4
engine: vllm
roles:
agg:
nodes: 2
workers: 1
args:
tensor-parallel-size: 8
Disaggregated P/D¶
Both pools are direct vLLM servers. Router is launched with
--vllm-pd-disaggregation, repeated --prefill URL NIXL_PORT entries, and
repeated --decode URL entries. The same aggregate topology rules apply
independently to every prefill and decode endpoint.
frontend:
type: vllm-router
enable_multiple_frontends: false
args:
policy: consistent_hash
resources:
gpus_per_node: 4
engine:
type: vllm
connector: nixl
roles:
prefill:
nodes: 1
workers: 1
args:
data-parallel-size: 4
decode:
nodes: 2
workers: 2
args:
data-parallel-size: 4
Router has one global --intra-node-data-parallel-size, so srtctl uses it only
when every logical endpoint has one advertised base URL. Multi-node hybrid-LB
endpoints remain unexpanded node-local pools because each later pool starts at
a nonzero global DP rank. Do not set the option manually unless it exactly
matches the allocated topology.
MoRI-IO discovery¶
engine.connector: moriio runs the P/D pair over AMD's MoRI-IO connector (ROCm). MoRI-IO workers do not take peer addresses on the command line; they register with the Router over a ZMQ discovery endpoint and learn each other's transfer addresses from it. srtctl therefore launches the Router in discovery mode and builds each worker's connector config from the realized topology instead of listing URLs:
frontend:
type: vllm-router
enable_multiple_frontends: false
engine:
type: vllm
connector: moriio
roles:
prefill:
nodes: 1
workers: 1
gpus: 1
decode:
nodes: colocate
workers: 1
gpus: 1
| Piece | srtctl sets | Why |
|---|---|---|
| Router command | --vllm-pd-disaggregation --kv-connector moriio --vllm-discovery-address 0.0.0.0:36367, no --prefill/--decode |
Discovery mode in vllm-project/router (RouterArgs.vllm_discovery_address); the address is the one listener workers register with |
kv_connector / kv_role |
MoRIIOConnector; kv_producer on prefill, kv_consumer on decode |
The connector's role follows the worker's mode |
proxy_ip, proxy_ping_port |
the head node's IP on the cluster interface, 36367 |
Where the worker registers; hence one Router, on the head node |
http_port, host_ip |
the worker's allocated HTTP port and its own routable IP | What the Router routes to and what peers connect to; upstream falls back to its own interface guess for host_ip, which is wrong on multi-homed nodes |
handshake_port, notify_port |
allocated per process by NodePortAllocator (moriio_handshake, moriio_notify kinds); both reserve one port per local rank |
vLLM adds rank offsets to both bases, so colocated workers need disjoint blocks. Defaults start at 26000/27000, below Linux's default ephemeral range, to avoid MoRI's other dynamically allocated listeners |
read_mode |
true |
The Router's discovery flow drives reads |
VLLM_PORT |
unset for these workers | MoRI-IO opens its other listeners inside the TP child processes, which inherit VLLM_PORT; a fixed scan base there hands several ranks the same unbound port, so vLLM takes ephemeral ports from the kernel |
Readiness is the Router's /health: it answers 503 Waiting for discovered workers until a prefill and a decode have registered, then 200 (vllm-project/router 43140bc8e2). This is a workaround for a gap in the Router, not a MoRI-IO property: in discovery mode the Router keeps registered workers in a separate registry but still serves /workers and /get_server_info from the static URL list, which is empty, so the count-based /workers gate srtctl uses for static routing has nothing to count. /health is the only endpoint that reads the discovery registry, and it only proves one worker of each role, so with several workers per role the benchmark can start before the Router knows all of them; the per-worker /health gate that follows proves the workers are up, not that they registered. The upstream fix is for /workers to merge the discovery registry (src/routers/router_manager.rs); when it does, VLLMRouterFrontend.probe_ready loses its discovery branch and the count-based probe covers both modes.
Rules the recipe must satisfy: both prefill and decode run the connector (an engine-wide connector: moriio, or the same value in both roles' args), the topology is prefill/decode, frontend.enable_multiple_frontends is false, and frontend.orchestrator_placement is head. srtctl dry-run rejects anything else, and any other frontend.type with this connector. Upstream key names are those moriio_common.py reads (vllm-project/vllm 9679173788). examples/vllm/vllm-router-moriio-disagg.yaml is the reference recipe.
Multiple Router processes¶
With enable_multiple_frontends: true, srtctl starts nginx on the public port
and multiple identical Router processes on the internal frontend port. Each
Router receives the same backend topology. Use nginx session affinity when a
client session must remain on one Router process.