Skip to content

Repository files navigation

DLRouter

Python 3.9+

DLRouter is an OpenAI-compatible inference gateway for large language model backends. It routes requests across LMDeploy, vLLM, and SGLang instances with pluggable routing strategies, runtime node management, health checks, and Prefill/Decode disaggregation support.

Use DLRouter when you want one API endpoint in front of multiple LLM serving nodes, while keeping backend-specific DistServe / PD orchestration out of your application code.

Highlights

  • OpenAI-compatible API: /v1/models, /v1/chat/completions, and /v1/completions.
  • Multiple routing policies: round-robin, weighted random, consistent hash, latency-aware routing, and prefix-cache-aware routing.
  • Multi-backend support: LMDeploy, vLLM, and SGLang through a pluggable backend adapter interface.
  • DistServe / PD disaggregation: backend-owned Prefill/Decode flows for LMDeploy, vLLM, and SGLang.
  • Dynamic node management: register, remove, inspect, and terminate backend nodes through REST APIs.
  • Health checking and lazy model discovery: unhealthy nodes are removed after consecutive failures, and model lists can be discovered after a backend becomes ready.
  • Optional authentication and TLS: Bearer-token API keys and SSL/TLS support are available through CLI and environment configuration.

Supported Backends

BackendHybrid forwardingDistServe / PDDiscovery modesNotes
LMDeployYesYesExternal node registrationUses LMDeploy PD connection pool and RDMA migration when available.
vLLMYesYesStatic, heartbeatSupports two-stage KV transfer and static NIXL DP-aware rank routing.
SGLangYesYesStaticUses bootstrap dual dispatch with aligned prefill bootstrap ports.
DLEngineYesYesdlslime-ctrl (nanoctrl)Hybrid dlengine serve nodes; auto-discovery when --ctrl_address is set.

DLRouter is configured with one backend type per router process through --backend. Run multiple router processes if you need separate backend types at the same time.

Installation

pip install -e .

For development:

pip install -e ".[dev]"

Python 3.9 or newer is required.

Quick Start

This example starts DLRouter in vLLM hybrid mode, registers one vLLM server, and sends an OpenAI-compatible chat request through DLRouter.

Start a vLLM server:

vllm serve /path/to/model \
--host 0.0.0.0 \
--port 8100 \
--served-model-name Qwen3-4B
# For single-node setups without Ray, add:# --distributed-executor-backend mp

Start DLRouter:

python -m dlrouter \
--serving_strategy hybrid \
--backend vllm

Register the backend node:

curl -X POST http://localhost:8000/nodes/add \
-H "Content-Type: application/json" \
-d '{"url": "http://127.0.0.1:8100"}'

Send a request:

curl -X POST http://localhost:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{ "model": "Qwen3-4B", "messages": [{"role": "user", "content": "Hello!"}], "stream": false }'

DLEngine with dlslime-ctrl discovery

Start the control plane and a DLEngine OpenAI server (see DLEngine dlengine serve), then run DLRouter with auto-discovery:

dlslime-ctrl server --redis-url redis://127.0.0.1:6379
dlengine serve /path/to/model \
--host 0.0.0.0 --port 8100 \
--served-model-name Qwen3-4B \
--ctrl-address 127.0.0.1:4479
pip install -e ".[dlengine]"# pulls dlslime for NanoCtrlClient
python -m dlrouter \
--backend dlengine \
--serving_strategy hybrid \
--ctrl_address 127.0.0.1:4479

DLRouter polls dlslime-ctrl for entities with kind dlengine and registers their HTTP endpoints. Use the same model name as --served-model-name in requests. Manual registration still works via POST /nodes/add when --ctrl_address is omitted.

Send a request (the served model name, model path, and its basename are all accepted as the model value):

curl -X POST http://localhost:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{ "model": "Qwen3-4B", "messages": [{"role": "user", "content": "Hello!"}], "stream": false }'

DLRouter also installs a dlrouter console script, so dlrouter ... is equivalent to python -m dlrouter ... after installation.

Common Usage

Routing Strategies

python -m dlrouter \
--backend vllm \
--serving_strategy hybrid \
--routing_strategy min_expected_latency

Available strategies:

StrategyDescription
round_robinSequentially cycle through nodes serving the requested model.
randomWeighted random selection. Nodes reporting higher speed receive more traffic.
consistent_hashRoute requests with the same key to the same node for affinity or cache locality.
min_expected_latencySelect the node with the lowest estimated latency: unfinished_requests / speed.
min_observed_latencySelect the node with the lowest recent average latency.
prefix_cacheRoute by KV cache prefix locality to improve cache hit rate.

vLLM DistServe: Static P/D Lists

Use static mode when prefill and decode HTTP endpoints are known at router startup. DLRouter infers static discovery when both --prefill_urls and --decode_urls are provided.

python -m dlrouter \
--serving_strategy distserve \
--backend vllm \
--prefill_urls "http://prefill-1:30000,http://prefill-2:30000" \
--decode_urls "http://decode-1:30000,http://decode-2:30000" \
--models "Qwen3-32B" \
--disable_cache_status

For NIXL intra-node data parallel routing, set --intra_node_data_parallel_size to the local DP size. DLRouter expands each physical URL into url@rank logical nodes for routing state, strips the suffix before forwarding, and sends X-data-parallel-rank to vLLM.

python -m dlrouter \
--serving_strategy distserve \
--backend vllm \
--prefill_urls "http://prefill-node:8001" \
--decode_urls "http://decode-node:8002" \
--models "Qwen3-32B" \
--intra_node_data_parallel_size 8 \
--disable_cache_status

If you change the DP size between router restarts, clear the persisted node cache or use --disable_cache_status to avoid stale url@rank entries.

vLLM DistServe: Heartbeat Discovery

When neither --prefill_urls nor --decode_urls is provided, vLLM DistServe uses heartbeat discovery. P/D instances register themselves with DLRouter by publishing HTTP and ZMQ addresses.

python -m dlrouter \
--serving_strategy distserve \
--backend vllm \
--zmq_host 0.0.0.0 \
--zmq_port 30001 \
--models "Qwen3-32B" \
--disable_cache_status

In heartbeat mode, a node enters the routable set only after DLRouter resolves its model information. If a restarted node sends heartbeats before its HTTP API is ready, registration is skipped temporarily and retried by later heartbeats.

SGLang DistServe

SGLang currently uses static discovery in DLRouter. Provide both prefill and decode URL lists. --prefill_bootstrap_ports is aligned with --prefill_urls; if omitted, each prefill defaults to 8998.

python -m dlrouter \
--serving_strategy distserve \
--backend sglang \
--prefill_urls "http://prefill-1:13700,http://prefill-2:13700" \
--decode_urls "http://decode-1:13701,http://decode-2:13701" \
--prefill_bootstrap_ports "8998,8998" \
--models "Qwen3-32B" \
--disable_cache_status

DLRouter injects bootstrap_host, bootstrap_port, and bootstrap_room into the request body, sends the decorated request to prefill and decode concurrently, and returns the decode response.

LMDeploy DistServe

LMDeploy DistServe relies on externally registered prefill and decode nodes. DLRouter selects P/D nodes through NodeManager; no separate discovery component is created by the app factory.

python -m dlrouter \
--serving_strategy distserve \
--backend lmdeploy \
--migration_protocol RDMA \
--link_type RoCE

LMDeploy-specific PD features require the LMDeploy disaggregation dependencies to be installed in the runtime environment.

CLI Reference

Common Options

OptionDefaultDescription
--server_name0.0.0.0Bind address.
--server_port8000Listen port.
--backendlmdeployBackend type: lmdeploy, vllm, sglang, or dlengine.
--routing_strategymin_expected_latencyRequest routing strategy.
--serving_strategyhybridServing mode: hybrid or distserve.
--api_keysNoneComma-separated Bearer tokens for API authentication.
--sslFalseEnable SSL. Requires SSL_KEYFILE and SSL_CERTFILE.
--log_levelINFODLRouter log level.
--disable_cache_statusFalseDisable persisted node status.
--config_pathNoneCustom node status persistence file.
--workers1Number of worker processes. Values greater than 1 use Gunicorn.

Backend Options

Backend-specific options are added dynamically and are visible with --help after selecting a backend.

BackendOptionDefaultDescription
LMDeploy--migration_protocolRDMAPD migration protocol.
LMDeploy--link_typeRoCERDMA link type: RoCE or IB.
LMDeploy--with_gdrTrueEnable GPU Direct RDMA.
LMDeploy--dummy_prefillFalseUse dummy prefill for testing.
vLLM--zmq_host0.0.0.0ZMQ discovery bind host.
vLLM--zmq_port30001ZMQ discovery port.
vLLM--zmq_ping_timeout5ZMQ instance ping timeout in seconds.
vLLM--prefill_urlsNoneComma-separated prefill URLs for static mode.
vLLM--decode_urlsNoneComma-separated decode URLs for static mode.
vLLM--modelsNoneComma-separated model names.
vLLM--intra_node_data_parallel_size1Static NIXL DP-aware logical rank count per physical URL.
SGLang--prefill_urlsNoneComma-separated SGLang prefill HTTP URLs.
SGLang--decode_urlsNoneComma-separated SGLang decode HTTP URLs.
SGLang--prefill_bootstrap_ports8998 per prefillComma-separated bootstrap ports aligned with prefill URLs.
SGLang--modelsNoneComma-separated model names.

API Reference

Inference

MethodPathDescription
GET/healthRouter health check.
GET/v1/modelsList available models across registered nodes.
POST/v1/chat/completionsOpenAI-compatible chat completion endpoint.
POST/v1/completionsOpenAI-compatible text completion endpoint.

Node Management

MethodPathDescription
GET/nodes/statusShow registered nodes and routing state.
POST/nodes/addRegister a backend node.
POST/nodes/removeRemove a backend node.
POST/nodes/terminateTerminate and remove a backend node.
POST/nodes/terminate_allTerminate all registered nodes.

Node registration can provide only a URL, or a URL plus explicit status metadata:

curl -X POST http://localhost:8000/nodes/add \
-H "Content-Type: application/json" \
-d '{"url": "http://backend-host:8000"}'

Architecture

Client (OpenAI SDK / curl)
|
v
FastAPI routes
|
v
ProxyEngine
|
+--> Hybrid: NodeManager -> RoutingStrategy -> Backend HTTP forward
|
+--> DistServe: Backend-owned PD executor
|
+--> LMDeploy PD / vLLM two-stage KV transfer / SGLang bootstrap

Key modules:

ModuleResponsibility
dlrouter/api/FastAPI app, middleware, and OpenAI-compatible routes.
dlrouter/core/proxy_engine.pyDispatches hybrid requests and delegates DistServe requests to backends.
dlrouter/core/node_manager.pyMaintains node state, model lists, request counters, and routing strategy instances.
dlrouter/core/health_check.pyRuns background health checks and lazy model discovery.
dlrouter/routing/Pluggable routing strategy implementations.
dlrouter/backends/Backend adapters, shared HTTP transport, and PD execution helpers.

Backend adapters share the async HTTP transport layer in dlrouter/backends/http.py for normal forwarding, streaming forwarding, health checks, session lifecycle, and backend-specific stream framing. Backend-specific logic remains in each backend package.

Discovery Semantics

ModeBehavior
HYBRIDBackend instances are registered explicitly, usually through /nodes/add.
DISTSERVE + vLLM + staticProviding both prefill_urls and decode_urls selects static discovery.
DISTSERVE + vLLM + heartbeatProviding neither URL list selects heartbeat discovery.
DISTSERVE + SGLangStatic P/D lists are required; heartbeat discovery is not used.
DISTSERVE + LMDeployP/D nodes are selected from NodeManager; no router-startup discovery object is created.

Providing only one of prefill_urls or decode_urls is treated as a configuration error.

Environment Variables

VariableDescription
DLROUTER_HEARTBEAT_EXPIRATIONHeartbeat timeout in seconds. Default: 90.
DLROUTER_HEALTH_CHECK_TIMEOUTPer-node health-check HTTP timeout in seconds. Default: 30.
DLROUTER_HEALTH_CHECK_MAX_FAILURESConsecutive failures before removing a node. Default: 3.
DLROUTER_AIOHTTP_TIMEOUTHTTP request timeout to backends in seconds. Default: 1800.
UVICORN_LOG_LEVELUvicorn log level. Default: info.
SSL_KEYFILESSL key file path when --ssl is enabled.
SSL_CERTFILESSL certificate file path when --ssl is enabled.

Development

# Install dev dependencies
pip install -e ".[dev]"# Format code
make format
# Lint
make lint
# Auto-fix lint issues
make fix
# Type-check
make type-check
# Run tests
make test# Run all checks (local: auto-fix + test)
make all
# CI-equivalent checks (no auto-fix; same as GitHub Actions)
make ci

Before opening a pull request, run make ci. GitHub Actions runs the same lint, format, type-check, and test checks on pull requests to main.

The test suite lives under tests/ and covers backend contracts, routing strategies, service discovery, health checks, and PD executors.

Current Limitations

  • One DLRouter process is configured for one backend type at startup.
  • SGLang DistServe currently uses static discovery only.
  • LMDeploy PD features require LMDeploy disaggregation dependencies in the runtime environment.
  • fetch_models() is synchronous in the current backend contract because node registration and lazy health-check discovery call it synchronously.

Acknowledgements

DLRouter draws inspiration from these open-source projects:

  • LMDeploy, especially its proxy and PD disaggregation design.
  • vLLM, including router and cache-aware load-balancing ideas.
  • SGLang, especially router and mini load-balancer patterns for bootstrap-based PD proxying.

Thanks to the developers and contributors of these projects for their work in the LLM inference ecosystem.

License

Apache-2.0

About

No description, website, or topics provided.

Resources

Stars

21 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
GitHub - DeepLink-org/DLRouter · GitHub
Skip to content

Repository files navigation

DLRouter

Python 3.9+

DLRouter is an OpenAI-compatible inference gateway for large language model backends. It routes requests across LMDeploy, vLLM, and SGLang instances with pluggable routing strategies, runtime node management, health checks, and Prefill/Decode disaggregation support.

Use DLRouter when you want one API endpoint in front of multiple LLM serving nodes, while keeping backend-specific DistServe / PD orchestration out of your application code.

Highlights

  • OpenAI-compatible API: /v1/models, /v1/chat/completions, and /v1/completions.
  • Multiple routing policies: round-robin, weighted random, consistent hash, latency-aware routing, and prefix-cache-aware routing.
  • Multi-backend support: LMDeploy, vLLM, and SGLang through a pluggable backend adapter interface.
  • DistServe / PD disaggregation: backend-owned Prefill/Decode flows for LMDeploy, vLLM, and SGLang.
  • Dynamic node management: register, remove, inspect, and terminate backend nodes through REST APIs.
  • Health checking and lazy model discovery: unhealthy nodes are removed after consecutive failures, and model lists can be discovered after a backend becomes ready.
  • Optional authentication and TLS: Bearer-token API keys and SSL/TLS support are available through CLI and environment configuration.

Supported Backends

BackendHybrid forwardingDistServe / PDDiscovery modesNotes
LMDeployYesYesExternal node registrationUses LMDeploy PD connection pool and RDMA migration when available.
vLLMYesYesStatic, heartbeatSupports two-stage KV transfer and static NIXL DP-aware rank routing.
SGLangYesYesStaticUses bootstrap dual dispatch with aligned prefill bootstrap ports.
DLEngineYesYesdlslime-ctrl (nanoctrl)Hybrid dlengine serve nodes; auto-discovery when --ctrl_address is set.

DLRouter is configured with one backend type per router process through --backend. Run multiple router processes if you need separate backend types at the same time.

Installation

pip install -e .

For development:

pip install -e ".[dev]"

Python 3.9 or newer is required.

Quick Start

This example starts DLRouter in vLLM hybrid mode, registers one vLLM server, and sends an OpenAI-compatible chat request through DLRouter.

Start a vLLM server:

vllm serve /path/to/model \
--host 0.0.0.0 \
--port 8100 \
--served-model-name Qwen3-4B
# For single-node setups without Ray, add:# --distributed-executor-backend mp

Start DLRouter:

python -m dlrouter \
--serving_strategy hybrid \
--backend vllm

Register the backend node:

curl -X POST http://localhost:8000/nodes/add \
-H "Content-Type: application/json" \
-d '{"url": "http://127.0.0.1:8100"}'

Send a request:

curl -X POST http://localhost:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{ "model": "Qwen3-4B", "messages": [{"role": "user", "content": "Hello!"}], "stream": false }'

DLEngine with dlslime-ctrl discovery

Start the control plane and a DLEngine OpenAI server (see DLEngine dlengine serve), then run DLRouter with auto-discovery:

dlslime-ctrl server --redis-url redis://127.0.0.1:6379
dlengine serve /path/to/model \
--host 0.0.0.0 --port 8100 \
--served-model-name Qwen3-4B \
--ctrl-address 127.0.0.1:4479
pip install -e ".[dlengine]"# pulls dlslime for NanoCtrlClient
python -m dlrouter \
--backend dlengine \
--serving_strategy hybrid \
--ctrl_address 127.0.0.1:4479

DLRouter polls dlslime-ctrl for entities with kind dlengine and registers their HTTP endpoints. Use the same model name as --served-model-name in requests. Manual registration still works via POST /nodes/add when --ctrl_address is omitted.

Send a request (the served model name, model path, and its basename are all accepted as the model value):

curl -X POST http://localhost:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{ "model": "Qwen3-4B", "messages": [{"role": "user", "content": "Hello!"}], "stream": false }'

DLRouter also installs a dlrouter console script, so dlrouter ... is equivalent to python -m dlrouter ... after installation.

Common Usage

Routing Strategies

python -m dlrouter \
--backend vllm \
--serving_strategy hybrid \
--routing_strategy min_expected_latency

Available strategies:

StrategyDescription
round_robinSequentially cycle through nodes serving the requested model.
randomWeighted random selection. Nodes reporting higher speed receive more traffic.
consistent_hashRoute requests with the same key to the same node for affinity or cache locality.
min_expected_latencySelect the node with the lowest estimated latency: unfinished_requests / speed.
min_observed_latencySelect the node with the lowest recent average latency.
prefix_cacheRoute by KV cache prefix locality to improve cache hit rate.

vLLM DistServe: Static P/D Lists

Use static mode when prefill and decode HTTP endpoints are known at router startup. DLRouter infers static discovery when both --prefill_urls and --decode_urls are provided.

python -m dlrouter \
--serving_strategy distserve \
--backend vllm \
--prefill_urls "http://prefill-1:30000,http://prefill-2:30000" \
--decode_urls "http://decode-1:30000,http://decode-2:30000" \
--models "Qwen3-32B" \
--disable_cache_status

For NIXL intra-node data parallel routing, set --intra_node_data_parallel_size to the local DP size. DLRouter expands each physical URL into url@rank logical nodes for routing state, strips the suffix before forwarding, and sends X-data-parallel-rank to vLLM.

python -m dlrouter \
--serving_strategy distserve \
--backend vllm \
--prefill_urls "http://prefill-node:8001" \
--decode_urls "http://decode-node:8002" \
--models "Qwen3-32B" \
--intra_node_data_parallel_size 8 \
--disable_cache_status

If you change the DP size between router restarts, clear the persisted node cache or use --disable_cache_status to avoid stale url@rank entries.

vLLM DistServe: Heartbeat Discovery

When neither --prefill_urls nor --decode_urls is provided, vLLM DistServe uses heartbeat discovery. P/D instances register themselves with DLRouter by publishing HTTP and ZMQ addresses.

python -m dlrouter \
--serving_strategy distserve \
--backend vllm \
--zmq_host 0.0.0.0 \
--zmq_port 30001 \
--models "Qwen3-32B" \
--disable_cache_status

In heartbeat mode, a node enters the routable set only after DLRouter resolves its model information. If a restarted node sends heartbeats before its HTTP API is ready, registration is skipped temporarily and retried by later heartbeats.

SGLang DistServe

SGLang currently uses static discovery in DLRouter. Provide both prefill and decode URL lists. --prefill_bootstrap_ports is aligned with --prefill_urls; if omitted, each prefill defaults to 8998.

python -m dlrouter \
--serving_strategy distserve \
--backend sglang \
--prefill_urls "http://prefill-1:13700,http://prefill-2:13700" \
--decode_urls "http://decode-1:13701,http://decode-2:13701" \
--prefill_bootstrap_ports "8998,8998" \
--models "Qwen3-32B" \
--disable_cache_status

DLRouter injects bootstrap_host, bootstrap_port, and bootstrap_room into the request body, sends the decorated request to prefill and decode concurrently, and returns the decode response.

LMDeploy DistServe

LMDeploy DistServe relies on externally registered prefill and decode nodes. DLRouter selects P/D nodes through NodeManager; no separate discovery component is created by the app factory.

python -m dlrouter \
--serving_strategy distserve \
--backend lmdeploy \
--migration_protocol RDMA \
--link_type RoCE

LMDeploy-specific PD features require the LMDeploy disaggregation dependencies to be installed in the runtime environment.

CLI Reference

Common Options

OptionDefaultDescription
--server_name0.0.0.0Bind address.
--server_port8000Listen port.
--backendlmdeployBackend type: lmdeploy, vllm, sglang, or dlengine.
--routing_strategymin_expected_latencyRequest routing strategy.
--serving_strategyhybridServing mode: hybrid or distserve.
--api_keysNoneComma-separated Bearer tokens for API authentication.
--sslFalseEnable SSL. Requires SSL_KEYFILE and SSL_CERTFILE.
--log_levelINFODLRouter log level.
--disable_cache_statusFalseDisable persisted node status.
--config_pathNoneCustom node status persistence file.
--workers1Number of worker processes. Values greater than 1 use Gunicorn.

Backend Options

Backend-specific options are added dynamically and are visible with --help after selecting a backend.

BackendOptionDefaultDescription
LMDeploy--migration_protocolRDMAPD migration protocol.
LMDeploy--link_typeRoCERDMA link type: RoCE or IB.
LMDeploy--with_gdrTrueEnable GPU Direct RDMA.
LMDeploy--dummy_prefillFalseUse dummy prefill for testing.
vLLM--zmq_host0.0.0.0ZMQ discovery bind host.
vLLM--zmq_port30001ZMQ discovery port.
vLLM--zmq_ping_timeout5ZMQ instance ping timeout in seconds.
vLLM--prefill_urlsNoneComma-separated prefill URLs for static mode.
vLLM--decode_urlsNoneComma-separated decode URLs for static mode.
vLLM--modelsNoneComma-separated model names.
vLLM--intra_node_data_parallel_size1Static NIXL DP-aware logical rank count per physical URL.
SGLang--prefill_urlsNoneComma-separated SGLang prefill HTTP URLs.
SGLang--decode_urlsNoneComma-separated SGLang decode HTTP URLs.
SGLang--prefill_bootstrap_ports8998 per prefillComma-separated bootstrap ports aligned with prefill URLs.
SGLang--modelsNoneComma-separated model names.

API Reference

Inference

MethodPathDescription
GET/healthRouter health check.
GET/v1/modelsList available models across registered nodes.
POST/v1/chat/completionsOpenAI-compatible chat completion endpoint.
POST/v1/completionsOpenAI-compatible text completion endpoint.

Node Management

MethodPathDescription
GET/nodes/statusShow registered nodes and routing state.
POST/nodes/addRegister a backend node.
POST/nodes/removeRemove a backend node.
POST/nodes/terminateTerminate and remove a backend node.
POST/nodes/terminate_allTerminate all registered nodes.

Node registration can provide only a URL, or a URL plus explicit status metadata:

curl -X POST http://localhost:8000/nodes/add \
-H "Content-Type: application/json" \
-d '{"url": "http://backend-host:8000"}'

Architecture

Client (OpenAI SDK / curl)
|
v
FastAPI routes
|
v
ProxyEngine
|
+--> Hybrid: NodeManager -> RoutingStrategy -> Backend HTTP forward
|
+--> DistServe: Backend-owned PD executor
|
+--> LMDeploy PD / vLLM two-stage KV transfer / SGLang bootstrap

Key modules:

ModuleResponsibility
dlrouter/api/FastAPI app, middleware, and OpenAI-compatible routes.
dlrouter/core/proxy_engine.pyDispatches hybrid requests and delegates DistServe requests to backends.
dlrouter/core/node_manager.pyMaintains node state, model lists, request counters, and routing strategy instances.
dlrouter/core/health_check.pyRuns background health checks and lazy model discovery.
dlrouter/routing/Pluggable routing strategy implementations.
dlrouter/backends/Backend adapters, shared HTTP transport, and PD execution helpers.

Backend adapters share the async HTTP transport layer in dlrouter/backends/http.py for normal forwarding, streaming forwarding, health checks, session lifecycle, and backend-specific stream framing. Backend-specific logic remains in each backend package.

Discovery Semantics

ModeBehavior
HYBRIDBackend instances are registered explicitly, usually through /nodes/add.
DISTSERVE + vLLM + staticProviding both prefill_urls and decode_urls selects static discovery.
DISTSERVE + vLLM + heartbeatProviding neither URL list selects heartbeat discovery.
DISTSERVE + SGLangStatic P/D lists are required; heartbeat discovery is not used.
DISTSERVE + LMDeployP/D nodes are selected from NodeManager; no router-startup discovery object is created.

Providing only one of prefill_urls or decode_urls is treated as a configuration error.

Environment Variables

VariableDescription
DLROUTER_HEARTBEAT_EXPIRATIONHeartbeat timeout in seconds. Default: 90.
DLROUTER_HEALTH_CHECK_TIMEOUTPer-node health-check HTTP timeout in seconds. Default: 30.
DLROUTER_HEALTH_CHECK_MAX_FAILURESConsecutive failures before removing a node. Default: 3.
DLROUTER_AIOHTTP_TIMEOUTHTTP request timeout to backends in seconds. Default: 1800.
UVICORN_LOG_LEVELUvicorn log level. Default: info.
SSL_KEYFILESSL key file path when --ssl is enabled.
SSL_CERTFILESSL certificate file path when --ssl is enabled.

Development

# Install dev dependencies
pip install -e ".[dev]"# Format code
make format
# Lint
make lint
# Auto-fix lint issues
make fix
# Type-check
make type-check
# Run tests
make test# Run all checks (local: auto-fix + test)
make all
# CI-equivalent checks (no auto-fix; same as GitHub Actions)
make ci

Before opening a pull request, run make ci. GitHub Actions runs the same lint, format, type-check, and test checks on pull requests to main.

The test suite lives under tests/ and covers backend contracts, routing strategies, service discovery, health checks, and PD executors.

Current Limitations

  • One DLRouter process is configured for one backend type at startup.
  • SGLang DistServe currently uses static discovery only.
  • LMDeploy PD features require LMDeploy disaggregation dependencies in the runtime environment.
  • fetch_models() is synchronous in the current backend contract because node registration and lazy health-check discovery call it synchronously.

Acknowledgements

DLRouter draws inspiration from these open-source projects:

  • LMDeploy, especially its proxy and PD disaggregation design.
  • vLLM, including router and cache-aware load-balancing ideas.
  • SGLang, especially router and mini load-balancer patterns for bootstrap-based PD proxying.

Thanks to the developers and contributors of these projects for their work in the LLM inference ecosystem.

License

Apache-2.0

About

No description, website, or topics provided.

Resources

Stars

21 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - DeepLink-org/DLRouter · GitHub
Skip to content

Repository files navigation

DLRouter

Python 3.9+

DLRouter is an OpenAI-compatible inference gateway for large language model backends. It routes requests across LMDeploy, vLLM, and SGLang instances with pluggable routing strategies, runtime node management, health checks, and Prefill/Decode disaggregation support.

Use DLRouter when you want one API endpoint in front of multiple LLM serving nodes, while keeping backend-specific DistServe / PD orchestration out of your application code.

Highlights

  • OpenAI-compatible API: /v1/models, /v1/chat/completions, and /v1/completions.
  • Multiple routing policies: round-robin, weighted random, consistent hash, latency-aware routing, and prefix-cache-aware routing.
  • Multi-backend support: LMDeploy, vLLM, and SGLang through a pluggable backend adapter interface.
  • DistServe / PD disaggregation: backend-owned Prefill/Decode flows for LMDeploy, vLLM, and SGLang.
  • Dynamic node management: register, remove, inspect, and terminate backend nodes through REST APIs.
  • Health checking and lazy model discovery: unhealthy nodes are removed after consecutive failures, and model lists can be discovered after a backend becomes ready.
  • Optional authentication and TLS: Bearer-token API keys and SSL/TLS support are available through CLI and environment configuration.

Supported Backends

BackendHybrid forwardingDistServe / PDDiscovery modesNotes
LMDeployYesYesExternal node registrationUses LMDeploy PD connection pool and RDMA migration when available.
vLLMYesYesStatic, heartbeatSupports two-stage KV transfer and static NIXL DP-aware rank routing.
SGLangYesYesStaticUses bootstrap dual dispatch with aligned prefill bootstrap ports.
DLEngineYesYesdlslime-ctrl (nanoctrl)Hybrid dlengine serve nodes; auto-discovery when --ctrl_address is set.

DLRouter is configured with one backend type per router process through --backend. Run multiple router processes if you need separate backend types at the same time.

Installation

pip install -e .

For development:

pip install -e ".[dev]"

Python 3.9 or newer is required.

Quick Start

This example starts DLRouter in vLLM hybrid mode, registers one vLLM server, and sends an OpenAI-compatible chat request through DLRouter.

Start a vLLM server:

vllm serve /path/to/model \
--host 0.0.0.0 \
--port 8100 \
--served-model-name Qwen3-4B
# For single-node setups without Ray, add:# --distributed-executor-backend mp

Start DLRouter:

python -m dlrouter \
--serving_strategy hybrid \
--backend vllm

Register the backend node:

curl -X POST http://localhost:8000/nodes/add \
-H "Content-Type: application/json" \
-d '{"url": "http://127.0.0.1:8100"}'

Send a request:

curl -X POST http://localhost:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{ "model": "Qwen3-4B", "messages": [{"role": "user", "content": "Hello!"}], "stream": false }'

DLEngine with dlslime-ctrl discovery

Start the control plane and a DLEngine OpenAI server (see DLEngine dlengine serve), then run DLRouter with auto-discovery:

dlslime-ctrl server --redis-url redis://127.0.0.1:6379
dlengine serve /path/to/model \
--host 0.0.0.0 --port 8100 \
--served-model-name Qwen3-4B \
--ctrl-address 127.0.0.1:4479
pip install -e ".[dlengine]"# pulls dlslime for NanoCtrlClient
python -m dlrouter \
--backend dlengine \
--serving_strategy hybrid \
--ctrl_address 127.0.0.1:4479

DLRouter polls dlslime-ctrl for entities with kind dlengine and registers their HTTP endpoints. Use the same model name as --served-model-name in requests. Manual registration still works via POST /nodes/add when --ctrl_address is omitted.

Send a request (the served model name, model path, and its basename are all accepted as the model value):

curl -X POST http://localhost:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{ "model": "Qwen3-4B", "messages": [{"role": "user", "content": "Hello!"}], "stream": false }'

DLRouter also installs a dlrouter console script, so dlrouter ... is equivalent to python -m dlrouter ... after installation.

Common Usage

Routing Strategies

python -m dlrouter \
--backend vllm \
--serving_strategy hybrid \
--routing_strategy min_expected_latency

Available strategies:

StrategyDescription
round_robinSequentially cycle through nodes serving the requested model.
randomWeighted random selection. Nodes reporting higher speed receive more traffic.
consistent_hashRoute requests with the same key to the same node for affinity or cache locality.
min_expected_latencySelect the node with the lowest estimated latency: unfinished_requests / speed.
min_observed_latencySelect the node with the lowest recent average latency.
prefix_cacheRoute by KV cache prefix locality to improve cache hit rate.

vLLM DistServe: Static P/D Lists

Use static mode when prefill and decode HTTP endpoints are known at router startup. DLRouter infers static discovery when both --prefill_urls and --decode_urls are provided.

python -m dlrouter \
--serving_strategy distserve \
--backend vllm \
--prefill_urls "http://prefill-1:30000,http://prefill-2:30000" \
--decode_urls "http://decode-1:30000,http://decode-2:30000" \
--models "Qwen3-32B" \
--disable_cache_status

For NIXL intra-node data parallel routing, set --intra_node_data_parallel_size to the local DP size. DLRouter expands each physical URL into url@rank logical nodes for routing state, strips the suffix before forwarding, and sends X-data-parallel-rank to vLLM.

python -m dlrouter \
--serving_strategy distserve \
--backend vllm \
--prefill_urls "http://prefill-node:8001" \
--decode_urls "http://decode-node:8002" \
--models "Qwen3-32B" \
--intra_node_data_parallel_size 8 \
--disable_cache_status

If you change the DP size between router restarts, clear the persisted node cache or use --disable_cache_status to avoid stale url@rank entries.

vLLM DistServe: Heartbeat Discovery

When neither --prefill_urls nor --decode_urls is provided, vLLM DistServe uses heartbeat discovery. P/D instances register themselves with DLRouter by publishing HTTP and ZMQ addresses.

python -m dlrouter \
--serving_strategy distserve \
--backend vllm \
--zmq_host 0.0.0.0 \
--zmq_port 30001 \
--models "Qwen3-32B" \
--disable_cache_status

In heartbeat mode, a node enters the routable set only after DLRouter resolves its model information. If a restarted node sends heartbeats before its HTTP API is ready, registration is skipped temporarily and retried by later heartbeats.

SGLang DistServe

SGLang currently uses static discovery in DLRouter. Provide both prefill and decode URL lists. --prefill_bootstrap_ports is aligned with --prefill_urls; if omitted, each prefill defaults to 8998.

python -m dlrouter \
--serving_strategy distserve \
--backend sglang \
--prefill_urls "http://prefill-1:13700,http://prefill-2:13700" \
--decode_urls "http://decode-1:13701,http://decode-2:13701" \
--prefill_bootstrap_ports "8998,8998" \
--models "Qwen3-32B" \
--disable_cache_status

DLRouter injects bootstrap_host, bootstrap_port, and bootstrap_room into the request body, sends the decorated request to prefill and decode concurrently, and returns the decode response.

LMDeploy DistServe

LMDeploy DistServe relies on externally registered prefill and decode nodes. DLRouter selects P/D nodes through NodeManager; no separate discovery component is created by the app factory.

python -m dlrouter \
--serving_strategy distserve \
--backend lmdeploy \
--migration_protocol RDMA \
--link_type RoCE

LMDeploy-specific PD features require the LMDeploy disaggregation dependencies to be installed in the runtime environment.

CLI Reference

Common Options

OptionDefaultDescription
--server_name0.0.0.0Bind address.
--server_port8000Listen port.
--backendlmdeployBackend type: lmdeploy, vllm, sglang, or dlengine.
--routing_strategymin_expected_latencyRequest routing strategy.
--serving_strategyhybridServing mode: hybrid or distserve.
--api_keysNoneComma-separated Bearer tokens for API authentication.
--sslFalseEnable SSL. Requires SSL_KEYFILE and SSL_CERTFILE.
--log_levelINFODLRouter log level.
--disable_cache_statusFalseDisable persisted node status.
--config_pathNoneCustom node status persistence file.
--workers1Number of worker processes. Values greater than 1 use Gunicorn.

Backend Options

Backend-specific options are added dynamically and are visible with --help after selecting a backend.

BackendOptionDefaultDescription
LMDeploy--migration_protocolRDMAPD migration protocol.
LMDeploy--link_typeRoCERDMA link type: RoCE or IB.
LMDeploy--with_gdrTrueEnable GPU Direct RDMA.
LMDeploy--dummy_prefillFalseUse dummy prefill for testing.
vLLM--zmq_host0.0.0.0ZMQ discovery bind host.
vLLM--zmq_port30001ZMQ discovery port.
vLLM--zmq_ping_timeout5ZMQ instance ping timeout in seconds.
vLLM--prefill_urlsNoneComma-separated prefill URLs for static mode.
vLLM--decode_urlsNoneComma-separated decode URLs for static mode.
vLLM--modelsNoneComma-separated model names.
vLLM--intra_node_data_parallel_size1Static NIXL DP-aware logical rank count per physical URL.
SGLang--prefill_urlsNoneComma-separated SGLang prefill HTTP URLs.
SGLang--decode_urlsNoneComma-separated SGLang decode HTTP URLs.
SGLang--prefill_bootstrap_ports8998 per prefillComma-separated bootstrap ports aligned with prefill URLs.
SGLang--modelsNoneComma-separated model names.

API Reference

Inference

MethodPathDescription
GET/healthRouter health check.
GET/v1/modelsList available models across registered nodes.
POST/v1/chat/completionsOpenAI-compatible chat completion endpoint.
POST/v1/completionsOpenAI-compatible text completion endpoint.

Node Management

MethodPathDescription
GET/nodes/statusShow registered nodes and routing state.
POST/nodes/addRegister a backend node.
POST/nodes/removeRemove a backend node.
POST/nodes/terminateTerminate and remove a backend node.
POST/nodes/terminate_allTerminate all registered nodes.

Node registration can provide only a URL, or a URL plus explicit status metadata:

curl -X POST http://localhost:8000/nodes/add \
-H "Content-Type: application/json" \
-d '{"url": "http://backend-host:8000"}'

Architecture

Client (OpenAI SDK / curl)
|
v
FastAPI routes
|
v
ProxyEngine
|
+--> Hybrid: NodeManager -> RoutingStrategy -> Backend HTTP forward
|
+--> DistServe: Backend-owned PD executor
|
+--> LMDeploy PD / vLLM two-stage KV transfer / SGLang bootstrap

Key modules:

ModuleResponsibility
dlrouter/api/FastAPI app, middleware, and OpenAI-compatible routes.
dlrouter/core/proxy_engine.pyDispatches hybrid requests and delegates DistServe requests to backends.
dlrouter/core/node_manager.pyMaintains node state, model lists, request counters, and routing strategy instances.
dlrouter/core/health_check.pyRuns background health checks and lazy model discovery.
dlrouter/routing/Pluggable routing strategy implementations.
dlrouter/backends/Backend adapters, shared HTTP transport, and PD execution helpers.

Backend adapters share the async HTTP transport layer in dlrouter/backends/http.py for normal forwarding, streaming forwarding, health checks, session lifecycle, and backend-specific stream framing. Backend-specific logic remains in each backend package.

Discovery Semantics

ModeBehavior
HYBRIDBackend instances are registered explicitly, usually through /nodes/add.
DISTSERVE + vLLM + staticProviding both prefill_urls and decode_urls selects static discovery.
DISTSERVE + vLLM + heartbeatProviding neither URL list selects heartbeat discovery.
DISTSERVE + SGLangStatic P/D lists are required; heartbeat discovery is not used.
DISTSERVE + LMDeployP/D nodes are selected from NodeManager; no router-startup discovery object is created.

Providing only one of prefill_urls or decode_urls is treated as a configuration error.

Environment Variables

VariableDescription
DLROUTER_HEARTBEAT_EXPIRATIONHeartbeat timeout in seconds. Default: 90.
DLROUTER_HEALTH_CHECK_TIMEOUTPer-node health-check HTTP timeout in seconds. Default: 30.
DLROUTER_HEALTH_CHECK_MAX_FAILURESConsecutive failures before removing a node. Default: 3.
DLROUTER_AIOHTTP_TIMEOUTHTTP request timeout to backends in seconds. Default: 1800.
UVICORN_LOG_LEVELUvicorn log level. Default: info.
SSL_KEYFILESSL key file path when --ssl is enabled.
SSL_CERTFILESSL certificate file path when --ssl is enabled.

Development

# Install dev dependencies
pip install -e ".[dev]"# Format code
make format
# Lint
make lint
# Auto-fix lint issues
make fix
# Type-check
make type-check
# Run tests
make test# Run all checks (local: auto-fix + test)
make all
# CI-equivalent checks (no auto-fix; same as GitHub Actions)
make ci

Before opening a pull request, run make ci. GitHub Actions runs the same lint, format, type-check, and test checks on pull requests to main.

The test suite lives under tests/ and covers backend contracts, routing strategies, service discovery, health checks, and PD executors.

Current Limitations

  • One DLRouter process is configured for one backend type at startup.
  • SGLang DistServe currently uses static discovery only.
  • LMDeploy PD features require LMDeploy disaggregation dependencies in the runtime environment.
  • fetch_models() is synchronous in the current backend contract because node registration and lazy health-check discovery call it synchronously.

Acknowledgements

DLRouter draws inspiration from these open-source projects:

  • LMDeploy, especially its proxy and PD disaggregation design.
  • vLLM, including router and cache-aware load-balancing ideas.
  • SGLang, especially router and mini load-balancer patterns for bootstrap-based PD proxying.

Thanks to the developers and contributors of these projects for their work in the LLM inference ecosystem.

License

Apache-2.0

About

No description, website, or topics provided.

Resources

Stars

21 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - DeepLink-org/DLRouter · GitHub
Skip to content

Repository files navigation

DLRouter

Python 3.9+

DLRouter is an OpenAI-compatible inference gateway for large language model backends. It routes requests across LMDeploy, vLLM, and SGLang instances with pluggable routing strategies, runtime node management, health checks, and Prefill/Decode disaggregation support.

Use DLRouter when you want one API endpoint in front of multiple LLM serving nodes, while keeping backend-specific DistServe / PD orchestration out of your application code.

Highlights

  • OpenAI-compatible API: /v1/models, /v1/chat/completions, and /v1/completions.
  • Multiple routing policies: round-robin, weighted random, consistent hash, latency-aware routing, and prefix-cache-aware routing.
  • Multi-backend support: LMDeploy, vLLM, and SGLang through a pluggable backend adapter interface.
  • DistServe / PD disaggregation: backend-owned Prefill/Decode flows for LMDeploy, vLLM, and SGLang.
  • Dynamic node management: register, remove, inspect, and terminate backend nodes through REST APIs.
  • Health checking and lazy model discovery: unhealthy nodes are removed after consecutive failures, and model lists can be discovered after a backend becomes ready.
  • Optional authentication and TLS: Bearer-token API keys and SSL/TLS support are available through CLI and environment configuration.

Supported Backends

BackendHybrid forwardingDistServe / PDDiscovery modesNotes
LMDeployYesYesExternal node registrationUses LMDeploy PD connection pool and RDMA migration when available.
vLLMYesYesStatic, heartbeatSupports two-stage KV transfer and static NIXL DP-aware rank routing.
SGLangYesYesStaticUses bootstrap dual dispatch with aligned prefill bootstrap ports.
DLEngineYesYesdlslime-ctrl (nanoctrl)Hybrid dlengine serve nodes; auto-discovery when --ctrl_address is set.

DLRouter is configured with one backend type per router process through --backend. Run multiple router processes if you need separate backend types at the same time.

Installation

pip install -e .

For development:

pip install -e ".[dev]"

Python 3.9 or newer is required.

Quick Start

This example starts DLRouter in vLLM hybrid mode, registers one vLLM server, and sends an OpenAI-compatible chat request through DLRouter.

Start a vLLM server:

vllm serve /path/to/model \
--host 0.0.0.0 \
--port 8100 \
--served-model-name Qwen3-4B
# For single-node setups without Ray, add:# --distributed-executor-backend mp

Start DLRouter:

python -m dlrouter \
--serving_strategy hybrid \
--backend vllm

Register the backend node:

curl -X POST http://localhost:8000/nodes/add \
-H "Content-Type: application/json" \
-d '{"url": "http://127.0.0.1:8100"}'

Send a request:

curl -X POST http://localhost:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{ "model": "Qwen3-4B", "messages": [{"role": "user", "content": "Hello!"}], "stream": false }'

DLEngine with dlslime-ctrl discovery

Start the control plane and a DLEngine OpenAI server (see DLEngine dlengine serve), then run DLRouter with auto-discovery:

dlslime-ctrl server --redis-url redis://127.0.0.1:6379
dlengine serve /path/to/model \
--host 0.0.0.0 --port 8100 \
--served-model-name Qwen3-4B \
--ctrl-address 127.0.0.1:4479
pip install -e ".[dlengine]"# pulls dlslime for NanoCtrlClient
python -m dlrouter \
--backend dlengine \
--serving_strategy hybrid \
--ctrl_address 127.0.0.1:4479

DLRouter polls dlslime-ctrl for entities with kind dlengine and registers their HTTP endpoints. Use the same model name as --served-model-name in requests. Manual registration still works via POST /nodes/add when --ctrl_address is omitted.

Send a request (the served model name, model path, and its basename are all accepted as the model value):

curl -X POST http://localhost:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{ "model": "Qwen3-4B", "messages": [{"role": "user", "content": "Hello!"}], "stream": false }'

DLRouter also installs a dlrouter console script, so dlrouter ... is equivalent to python -m dlrouter ... after installation.

Common Usage

Routing Strategies

python -m dlrouter \
--backend vllm \
--serving_strategy hybrid \
--routing_strategy min_expected_latency

Available strategies:

StrategyDescription
round_robinSequentially cycle through nodes serving the requested model.
randomWeighted random selection. Nodes reporting higher speed receive more traffic.
consistent_hashRoute requests with the same key to the same node for affinity or cache locality.
min_expected_latencySelect the node with the lowest estimated latency: unfinished_requests / speed.
min_observed_latencySelect the node with the lowest recent average latency.
prefix_cacheRoute by KV cache prefix locality to improve cache hit rate.

vLLM DistServe: Static P/D Lists

Use static mode when prefill and decode HTTP endpoints are known at router startup. DLRouter infers static discovery when both --prefill_urls and --decode_urls are provided.

python -m dlrouter \
--serving_strategy distserve \
--backend vllm \
--prefill_urls "http://prefill-1:30000,http://prefill-2:30000" \
--decode_urls "http://decode-1:30000,http://decode-2:30000" \
--models "Qwen3-32B" \
--disable_cache_status

For NIXL intra-node data parallel routing, set --intra_node_data_parallel_size to the local DP size. DLRouter expands each physical URL into url@rank logical nodes for routing state, strips the suffix before forwarding, and sends X-data-parallel-rank to vLLM.

python -m dlrouter \
--serving_strategy distserve \
--backend vllm \
--prefill_urls "http://prefill-node:8001" \
--decode_urls "http://decode-node:8002" \
--models "Qwen3-32B" \
--intra_node_data_parallel_size 8 \
--disable_cache_status

If you change the DP size between router restarts, clear the persisted node cache or use --disable_cache_status to avoid stale url@rank entries.

vLLM DistServe: Heartbeat Discovery

When neither --prefill_urls nor --decode_urls is provided, vLLM DistServe uses heartbeat discovery. P/D instances register themselves with DLRouter by publishing HTTP and ZMQ addresses.

python -m dlrouter \
--serving_strategy distserve \
--backend vllm \
--zmq_host 0.0.0.0 \
--zmq_port 30001 \
--models "Qwen3-32B" \
--disable_cache_status

In heartbeat mode, a node enters the routable set only after DLRouter resolves its model information. If a restarted node sends heartbeats before its HTTP API is ready, registration is skipped temporarily and retried by later heartbeats.

SGLang DistServe

SGLang currently uses static discovery in DLRouter. Provide both prefill and decode URL lists. --prefill_bootstrap_ports is aligned with --prefill_urls; if omitted, each prefill defaults to 8998.

python -m dlrouter \
--serving_strategy distserve \
--backend sglang \
--prefill_urls "http://prefill-1:13700,http://prefill-2:13700" \
--decode_urls "http://decode-1:13701,http://decode-2:13701" \
--prefill_bootstrap_ports "8998,8998" \
--models "Qwen3-32B" \
--disable_cache_status

DLRouter injects bootstrap_host, bootstrap_port, and bootstrap_room into the request body, sends the decorated request to prefill and decode concurrently, and returns the decode response.

LMDeploy DistServe

LMDeploy DistServe relies on externally registered prefill and decode nodes. DLRouter selects P/D nodes through NodeManager; no separate discovery component is created by the app factory.

python -m dlrouter \
--serving_strategy distserve \
--backend lmdeploy \
--migration_protocol RDMA \
--link_type RoCE

LMDeploy-specific PD features require the LMDeploy disaggregation dependencies to be installed in the runtime environment.

CLI Reference

Common Options

OptionDefaultDescription
--server_name0.0.0.0Bind address.
--server_port8000Listen port.
--backendlmdeployBackend type: lmdeploy, vllm, sglang, or dlengine.
--routing_strategymin_expected_latencyRequest routing strategy.
--serving_strategyhybridServing mode: hybrid or distserve.
--api_keysNoneComma-separated Bearer tokens for API authentication.
--sslFalseEnable SSL. Requires SSL_KEYFILE and SSL_CERTFILE.
--log_levelINFODLRouter log level.
--disable_cache_statusFalseDisable persisted node status.
--config_pathNoneCustom node status persistence file.
--workers1Number of worker processes. Values greater than 1 use Gunicorn.

Backend Options

Backend-specific options are added dynamically and are visible with --help after selecting a backend.

BackendOptionDefaultDescription
LMDeploy--migration_protocolRDMAPD migration protocol.
LMDeploy--link_typeRoCERDMA link type: RoCE or IB.
LMDeploy--with_gdrTrueEnable GPU Direct RDMA.
LMDeploy--dummy_prefillFalseUse dummy prefill for testing.
vLLM--zmq_host0.0.0.0ZMQ discovery bind host.
vLLM--zmq_port30001ZMQ discovery port.
vLLM--zmq_ping_timeout5ZMQ instance ping timeout in seconds.
vLLM--prefill_urlsNoneComma-separated prefill URLs for static mode.
vLLM--decode_urlsNoneComma-separated decode URLs for static mode.
vLLM--modelsNoneComma-separated model names.
vLLM--intra_node_data_parallel_size1Static NIXL DP-aware logical rank count per physical URL.
SGLang--prefill_urlsNoneComma-separated SGLang prefill HTTP URLs.
SGLang--decode_urlsNoneComma-separated SGLang decode HTTP URLs.
SGLang--prefill_bootstrap_ports8998 per prefillComma-separated bootstrap ports aligned with prefill URLs.
SGLang--modelsNoneComma-separated model names.

API Reference

Inference

MethodPathDescription
GET/healthRouter health check.
GET/v1/modelsList available models across registered nodes.
POST/v1/chat/completionsOpenAI-compatible chat completion endpoint.
POST/v1/completionsOpenAI-compatible text completion endpoint.

Node Management

MethodPathDescription
GET/nodes/statusShow registered nodes and routing state.
POST/nodes/addRegister a backend node.
POST/nodes/removeRemove a backend node.
POST/nodes/terminateTerminate and remove a backend node.
POST/nodes/terminate_allTerminate all registered nodes.

Node registration can provide only a URL, or a URL plus explicit status metadata:

curl -X POST http://localhost:8000/nodes/add \
-H "Content-Type: application/json" \
-d '{"url": "http://backend-host:8000"}'

Architecture

Client (OpenAI SDK / curl)
|
v
FastAPI routes
|
v
ProxyEngine
|
+--> Hybrid: NodeManager -> RoutingStrategy -> Backend HTTP forward
|
+--> DistServe: Backend-owned PD executor
|
+--> LMDeploy PD / vLLM two-stage KV transfer / SGLang bootstrap

Key modules:

ModuleResponsibility
dlrouter/api/FastAPI app, middleware, and OpenAI-compatible routes.
dlrouter/core/proxy_engine.pyDispatches hybrid requests and delegates DistServe requests to backends.
dlrouter/core/node_manager.pyMaintains node state, model lists, request counters, and routing strategy instances.
dlrouter/core/health_check.pyRuns background health checks and lazy model discovery.
dlrouter/routing/Pluggable routing strategy implementations.
dlrouter/backends/Backend adapters, shared HTTP transport, and PD execution helpers.

Backend adapters share the async HTTP transport layer in dlrouter/backends/http.py for normal forwarding, streaming forwarding, health checks, session lifecycle, and backend-specific stream framing. Backend-specific logic remains in each backend package.

Discovery Semantics

ModeBehavior
HYBRIDBackend instances are registered explicitly, usually through /nodes/add.
DISTSERVE + vLLM + staticProviding both prefill_urls and decode_urls selects static discovery.
DISTSERVE + vLLM + heartbeatProviding neither URL list selects heartbeat discovery.
DISTSERVE + SGLangStatic P/D lists are required; heartbeat discovery is not used.
DISTSERVE + LMDeployP/D nodes are selected from NodeManager; no router-startup discovery object is created.

Providing only one of prefill_urls or decode_urls is treated as a configuration error.

Environment Variables

VariableDescription
DLROUTER_HEARTBEAT_EXPIRATIONHeartbeat timeout in seconds. Default: 90.
DLROUTER_HEALTH_CHECK_TIMEOUTPer-node health-check HTTP timeout in seconds. Default: 30.
DLROUTER_HEALTH_CHECK_MAX_FAILURESConsecutive failures before removing a node. Default: 3.
DLROUTER_AIOHTTP_TIMEOUTHTTP request timeout to backends in seconds. Default: 1800.
UVICORN_LOG_LEVELUvicorn log level. Default: info.
SSL_KEYFILESSL key file path when --ssl is enabled.
SSL_CERTFILESSL certificate file path when --ssl is enabled.

Development

# Install dev dependencies
pip install -e ".[dev]"# Format code
make format
# Lint
make lint
# Auto-fix lint issues
make fix
# Type-check
make type-check
# Run tests
make test# Run all checks (local: auto-fix + test)
make all
# CI-equivalent checks (no auto-fix; same as GitHub Actions)
make ci

Before opening a pull request, run make ci. GitHub Actions runs the same lint, format, type-check, and test checks on pull requests to main.

The test suite lives under tests/ and covers backend contracts, routing strategies, service discovery, health checks, and PD executors.

Current Limitations

  • One DLRouter process is configured for one backend type at startup.
  • SGLang DistServe currently uses static discovery only.
  • LMDeploy PD features require LMDeploy disaggregation dependencies in the runtime environment.
  • fetch_models() is synchronous in the current backend contract because node registration and lazy health-check discovery call it synchronously.

Acknowledgements

DLRouter draws inspiration from these open-source projects:

  • LMDeploy, especially its proxy and PD disaggregation design.
  • vLLM, including router and cache-aware load-balancing ideas.
  • SGLang, especially router and mini load-balancer patterns for bootstrap-based PD proxying.

Thanks to the developers and contributors of these projects for their work in the LLM inference ecosystem.

License

Apache-2.0

About

No description, website, or topics provided.

Resources

Stars

21 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' GitHub - DeepLink-org/DLRouter · GitHub
Skip to content

Repository files navigation

DLRouter

Python 3.9+

DLRouter is an OpenAI-compatible inference gateway for large language model backends. It routes requests across LMDeploy, vLLM, and SGLang instances with pluggable routing strategies, runtime node management, health checks, and Prefill/Decode disaggregation support.

Use DLRouter when you want one API endpoint in front of multiple LLM serving nodes, while keeping backend-specific DistServe / PD orchestration out of your application code.

Highlights

  • OpenAI-compatible API: /v1/models, /v1/chat/completions, and /v1/completions.
  • Multiple routing policies: round-robin, weighted random, consistent hash, latency-aware routing, and prefix-cache-aware routing.
  • Multi-backend support: LMDeploy, vLLM, and SGLang through a pluggable backend adapter interface.
  • DistServe / PD disaggregation: backend-owned Prefill/Decode flows for LMDeploy, vLLM, and SGLang.
  • Dynamic node management: register, remove, inspect, and terminate backend nodes through REST APIs.
  • Health checking and lazy model discovery: unhealthy nodes are removed after consecutive failures, and model lists can be discovered after a backend becomes ready.
  • Optional authentication and TLS: Bearer-token API keys and SSL/TLS support are available through CLI and environment configuration.

Supported Backends

BackendHybrid forwardingDistServe / PDDiscovery modesNotes
LMDeployYesYesExternal node registrationUses LMDeploy PD connection pool and RDMA migration when available.
vLLMYesYesStatic, heartbeatSupports two-stage KV transfer and static NIXL DP-aware rank routing.
SGLangYesYesStaticUses bootstrap dual dispatch with aligned prefill bootstrap ports.
DLEngineYesYesdlslime-ctrl (nanoctrl)Hybrid dlengine serve nodes; auto-discovery when --ctrl_address is set.

DLRouter is configured with one backend type per router process through --backend. Run multiple router processes if you need separate backend types at the same time.

Installation

pip install -e .

For development:

pip install -e ".[dev]"

Python 3.9 or newer is required.

Quick Start

This example starts DLRouter in vLLM hybrid mode, registers one vLLM server, and sends an OpenAI-compatible chat request through DLRouter.

Start a vLLM server:

vllm serve /path/to/model \
--host 0.0.0.0 \
--port 8100 \
--served-model-name Qwen3-4B
# For single-node setups without Ray, add:# --distributed-executor-backend mp

Start DLRouter:

python -m dlrouter \
--serving_strategy hybrid \
--backend vllm

Register the backend node:

curl -X POST http://localhost:8000/nodes/add \
-H "Content-Type: application/json" \
-d '{"url": "http://127.0.0.1:8100"}'

Send a request:

curl -X POST http://localhost:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{ "model": "Qwen3-4B", "messages": [{"role": "user", "content": "Hello!"}], "stream": false }'

DLEngine with dlslime-ctrl discovery

Start the control plane and a DLEngine OpenAI server (see DLEngine dlengine serve), then run DLRouter with auto-discovery:

dlslime-ctrl server --redis-url redis://127.0.0.1:6379
dlengine serve /path/to/model \
--host 0.0.0.0 --port 8100 \
--served-model-name Qwen3-4B \
--ctrl-address 127.0.0.1:4479
pip install -e ".[dlengine]"# pulls dlslime for NanoCtrlClient
python -m dlrouter \
--backend dlengine \
--serving_strategy hybrid \
--ctrl_address 127.0.0.1:4479

DLRouter polls dlslime-ctrl for entities with kind dlengine and registers their HTTP endpoints. Use the same model name as --served-model-name in requests. Manual registration still works via POST /nodes/add when --ctrl_address is omitted.

Send a request (the served model name, model path, and its basename are all accepted as the model value):

curl -X POST http://localhost:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{ "model": "Qwen3-4B", "messages": [{"role": "user", "content": "Hello!"}], "stream": false }'

DLRouter also installs a dlrouter console script, so dlrouter ... is equivalent to python -m dlrouter ... after installation.

Common Usage

Routing Strategies

python -m dlrouter \
--backend vllm \
--serving_strategy hybrid \
--routing_strategy min_expected_latency

Available strategies:

StrategyDescription
round_robinSequentially cycle through nodes serving the requested model.
randomWeighted random selection. Nodes reporting higher speed receive more traffic.
consistent_hashRoute requests with the same key to the same node for affinity or cache locality.
min_expected_latencySelect the node with the lowest estimated latency: unfinished_requests / speed.
min_observed_latencySelect the node with the lowest recent average latency.
prefix_cacheRoute by KV cache prefix locality to improve cache hit rate.

vLLM DistServe: Static P/D Lists

Use static mode when prefill and decode HTTP endpoints are known at router startup. DLRouter infers static discovery when both --prefill_urls and --decode_urls are provided.

python -m dlrouter \
--serving_strategy distserve \
--backend vllm \
--prefill_urls "http://prefill-1:30000,http://prefill-2:30000" \
--decode_urls "http://decode-1:30000,http://decode-2:30000" \
--models "Qwen3-32B" \
--disable_cache_status

For NIXL intra-node data parallel routing, set --intra_node_data_parallel_size to the local DP size. DLRouter expands each physical URL into url@rank logical nodes for routing state, strips the suffix before forwarding, and sends X-data-parallel-rank to vLLM.

python -m dlrouter \
--serving_strategy distserve \
--backend vllm \
--prefill_urls "http://prefill-node:8001" \
--decode_urls "http://decode-node:8002" \
--models "Qwen3-32B" \
--intra_node_data_parallel_size 8 \
--disable_cache_status

If you change the DP size between router restarts, clear the persisted node cache or use --disable_cache_status to avoid stale url@rank entries.

vLLM DistServe: Heartbeat Discovery

When neither --prefill_urls nor --decode_urls is provided, vLLM DistServe uses heartbeat discovery. P/D instances register themselves with DLRouter by publishing HTTP and ZMQ addresses.

python -m dlrouter \
--serving_strategy distserve \
--backend vllm \
--zmq_host 0.0.0.0 \
--zmq_port 30001 \
--models "Qwen3-32B" \
--disable_cache_status

In heartbeat mode, a node enters the routable set only after DLRouter resolves its model information. If a restarted node sends heartbeats before its HTTP API is ready, registration is skipped temporarily and retried by later heartbeats.

SGLang DistServe

SGLang currently uses static discovery in DLRouter. Provide both prefill and decode URL lists. --prefill_bootstrap_ports is aligned with --prefill_urls; if omitted, each prefill defaults to 8998.

python -m dlrouter \
--serving_strategy distserve \
--backend sglang \
--prefill_urls "http://prefill-1:13700,http://prefill-2:13700" \
--decode_urls "http://decode-1:13701,http://decode-2:13701" \
--prefill_bootstrap_ports "8998,8998" \
--models "Qwen3-32B" \
--disable_cache_status

DLRouter injects bootstrap_host, bootstrap_port, and bootstrap_room into the request body, sends the decorated request to prefill and decode concurrently, and returns the decode response.

LMDeploy DistServe

LMDeploy DistServe relies on externally registered prefill and decode nodes. DLRouter selects P/D nodes through NodeManager; no separate discovery component is created by the app factory.

python -m dlrouter \
--serving_strategy distserve \
--backend lmdeploy \
--migration_protocol RDMA \
--link_type RoCE

LMDeploy-specific PD features require the LMDeploy disaggregation dependencies to be installed in the runtime environment.

CLI Reference

Common Options

OptionDefaultDescription
--server_name0.0.0.0Bind address.
--server_port8000Listen port.
--backendlmdeployBackend type: lmdeploy, vllm, sglang, or dlengine.
--routing_strategymin_expected_latencyRequest routing strategy.
--serving_strategyhybridServing mode: hybrid or distserve.
--api_keysNoneComma-separated Bearer tokens for API authentication.
--sslFalseEnable SSL. Requires SSL_KEYFILE and SSL_CERTFILE.
--log_levelINFODLRouter log level.
--disable_cache_statusFalseDisable persisted node status.
--config_pathNoneCustom node status persistence file.
--workers1Number of worker processes. Values greater than 1 use Gunicorn.

Backend Options

Backend-specific options are added dynamically and are visible with --help after selecting a backend.

BackendOptionDefaultDescription
LMDeploy--migration_protocolRDMAPD migration protocol.
LMDeploy--link_typeRoCERDMA link type: RoCE or IB.
LMDeploy--with_gdrTrueEnable GPU Direct RDMA.
LMDeploy--dummy_prefillFalseUse dummy prefill for testing.
vLLM--zmq_host0.0.0.0ZMQ discovery bind host.
vLLM--zmq_port30001ZMQ discovery port.
vLLM--zmq_ping_timeout5ZMQ instance ping timeout in seconds.
vLLM--prefill_urlsNoneComma-separated prefill URLs for static mode.
vLLM--decode_urlsNoneComma-separated decode URLs for static mode.
vLLM--modelsNoneComma-separated model names.
vLLM--intra_node_data_parallel_size1Static NIXL DP-aware logical rank count per physical URL.
SGLang--prefill_urlsNoneComma-separated SGLang prefill HTTP URLs.
SGLang--decode_urlsNoneComma-separated SGLang decode HTTP URLs.
SGLang--prefill_bootstrap_ports8998 per prefillComma-separated bootstrap ports aligned with prefill URLs.
SGLang--modelsNoneComma-separated model names.

API Reference

Inference

MethodPathDescription
GET/healthRouter health check.
GET/v1/modelsList available models across registered nodes.
POST/v1/chat/completionsOpenAI-compatible chat completion endpoint.
POST/v1/completionsOpenAI-compatible text completion endpoint.

Node Management

MethodPathDescription
GET/nodes/statusShow registered nodes and routing state.
POST/nodes/addRegister a backend node.
POST/nodes/removeRemove a backend node.
POST/nodes/terminateTerminate and remove a backend node.
POST/nodes/terminate_allTerminate all registered nodes.

Node registration can provide only a URL, or a URL plus explicit status metadata:

curl -X POST http://localhost:8000/nodes/add \
-H "Content-Type: application/json" \
-d '{"url": "http://backend-host:8000"}'

Architecture

Client (OpenAI SDK / curl)
|
v
FastAPI routes
|
v
ProxyEngine
|
+--> Hybrid: NodeManager -> RoutingStrategy -> Backend HTTP forward
|
+--> DistServe: Backend-owned PD executor
|
+--> LMDeploy PD / vLLM two-stage KV transfer / SGLang bootstrap

Key modules:

ModuleResponsibility
dlrouter/api/FastAPI app, middleware, and OpenAI-compatible routes.
dlrouter/core/proxy_engine.pyDispatches hybrid requests and delegates DistServe requests to backends.
dlrouter/core/node_manager.pyMaintains node state, model lists, request counters, and routing strategy instances.
dlrouter/core/health_check.pyRuns background health checks and lazy model discovery.
dlrouter/routing/Pluggable routing strategy implementations.
dlrouter/backends/Backend adapters, shared HTTP transport, and PD execution helpers.

Backend adapters share the async HTTP transport layer in dlrouter/backends/http.py for normal forwarding, streaming forwarding, health checks, session lifecycle, and backend-specific stream framing. Backend-specific logic remains in each backend package.

Discovery Semantics

ModeBehavior
HYBRIDBackend instances are registered explicitly, usually through /nodes/add.
DISTSERVE + vLLM + staticProviding both prefill_urls and decode_urls selects static discovery.
DISTSERVE + vLLM + heartbeatProviding neither URL list selects heartbeat discovery.
DISTSERVE + SGLangStatic P/D lists are required; heartbeat discovery is not used.
DISTSERVE + LMDeployP/D nodes are selected from NodeManager; no router-startup discovery object is created.

Providing only one of prefill_urls or decode_urls is treated as a configuration error.

Environment Variables

VariableDescription
DLROUTER_HEARTBEAT_EXPIRATIONHeartbeat timeout in seconds. Default: 90.
DLROUTER_HEALTH_CHECK_TIMEOUTPer-node health-check HTTP timeout in seconds. Default: 30.
DLROUTER_HEALTH_CHECK_MAX_FAILURESConsecutive failures before removing a node. Default: 3.
DLROUTER_AIOHTTP_TIMEOUTHTTP request timeout to backends in seconds. Default: 1800.
UVICORN_LOG_LEVELUvicorn log level. Default: info.
SSL_KEYFILESSL key file path when --ssl is enabled.
SSL_CERTFILESSL certificate file path when --ssl is enabled.

Development

# Install dev dependencies
pip install -e ".[dev]"# Format code
make format
# Lint
make lint
# Auto-fix lint issues
make fix
# Type-check
make type-check
# Run tests
make test# Run all checks (local: auto-fix + test)
make all
# CI-equivalent checks (no auto-fix; same as GitHub Actions)
make ci

Before opening a pull request, run make ci. GitHub Actions runs the same lint, format, type-check, and test checks on pull requests to main.

The test suite lives under tests/ and covers backend contracts, routing strategies, service discovery, health checks, and PD executors.

Current Limitations

  • One DLRouter process is configured for one backend type at startup.
  • SGLang DistServe currently uses static discovery only.
  • LMDeploy PD features require LMDeploy disaggregation dependencies in the runtime environment.
  • fetch_models() is synchronous in the current backend contract because node registration and lazy health-check discovery call it synchronously.

Acknowledgements

DLRouter draws inspiration from these open-source projects:

  • LMDeploy, especially its proxy and PD disaggregation design.
  • vLLM, including router and cache-aware load-balancing ideas.
  • SGLang, especially router and mini load-balancer patterns for bootstrap-based PD proxying.

Thanks to the developers and contributors of these projects for their work in the LLM inference ecosystem.

License

Apache-2.0

About

No description, website, or topics provided.

Resources

Stars

21 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - DeepLink-org/DLRouter · GitHub
Skip to content

Repository files navigation

DLRouter

Python 3.9+

DLRouter is an OpenAI-compatible inference gateway for large language model backends. It routes requests across LMDeploy, vLLM, and SGLang instances with pluggable routing strategies, runtime node management, health checks, and Prefill/Decode disaggregation support.

Use DLRouter when you want one API endpoint in front of multiple LLM serving nodes, while keeping backend-specific DistServe / PD orchestration out of your application code.

Highlights

  • OpenAI-compatible API: /v1/models, /v1/chat/completions, and /v1/completions.
  • Multiple routing policies: round-robin, weighted random, consistent hash, latency-aware routing, and prefix-cache-aware routing.
  • Multi-backend support: LMDeploy, vLLM, and SGLang through a pluggable backend adapter interface.
  • DistServe / PD disaggregation: backend-owned Prefill/Decode flows for LMDeploy, vLLM, and SGLang.
  • Dynamic node management: register, remove, inspect, and terminate backend nodes through REST APIs.
  • Health checking and lazy model discovery: unhealthy nodes are removed after consecutive failures, and model lists can be discovered after a backend becomes ready.
  • Optional authentication and TLS: Bearer-token API keys and SSL/TLS support are available through CLI and environment configuration.

Supported Backends

BackendHybrid forwardingDistServe / PDDiscovery modesNotes
LMDeployYesYesExternal node registrationUses LMDeploy PD connection pool and RDMA migration when available.
vLLMYesYesStatic, heartbeatSupports two-stage KV transfer and static NIXL DP-aware rank routing.
SGLangYesYesStaticUses bootstrap dual dispatch with aligned prefill bootstrap ports.
DLEngineYesYesdlslime-ctrl (nanoctrl)Hybrid dlengine serve nodes; auto-discovery when --ctrl_address is set.

DLRouter is configured with one backend type per router process through --backend. Run multiple router processes if you need separate backend types at the same time.

Installation

pip install -e .

For development:

pip install -e ".[dev]"

Python 3.9 or newer is required.

Quick Start

This example starts DLRouter in vLLM hybrid mode, registers one vLLM server, and sends an OpenAI-compatible chat request through DLRouter.

Start a vLLM server:

vllm serve /path/to/model \
--host 0.0.0.0 \
--port 8100 \
--served-model-name Qwen3-4B
# For single-node setups without Ray, add:# --distributed-executor-backend mp

Start DLRouter:

python -m dlrouter \
--serving_strategy hybrid \
--backend vllm

Register the backend node:

curl -X POST http://localhost:8000/nodes/add \
-H "Content-Type: application/json" \
-d '{"url": "http://127.0.0.1:8100"}'

Send a request:

curl -X POST http://localhost:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{ "model": "Qwen3-4B", "messages": [{"role": "user", "content": "Hello!"}], "stream": false }'

DLEngine with dlslime-ctrl discovery

Start the control plane and a DLEngine OpenAI server (see DLEngine dlengine serve), then run DLRouter with auto-discovery:

dlslime-ctrl server --redis-url redis://127.0.0.1:6379
dlengine serve /path/to/model \
--host 0.0.0.0 --port 8100 \
--served-model-name Qwen3-4B \
--ctrl-address 127.0.0.1:4479
pip install -e ".[dlengine]"# pulls dlslime for NanoCtrlClient
python -m dlrouter \
--backend dlengine \
--serving_strategy hybrid \
--ctrl_address 127.0.0.1:4479

DLRouter polls dlslime-ctrl for entities with kind dlengine and registers their HTTP endpoints. Use the same model name as --served-model-name in requests. Manual registration still works via POST /nodes/add when --ctrl_address is omitted.

Send a request (the served model name, model path, and its basename are all accepted as the model value):

curl -X POST http://localhost:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{ "model": "Qwen3-4B", "messages": [{"role": "user", "content": "Hello!"}], "stream": false }'

DLRouter also installs a dlrouter console script, so dlrouter ... is equivalent to python -m dlrouter ... after installation.

Common Usage

Routing Strategies

python -m dlrouter \
--backend vllm \
--serving_strategy hybrid \
--routing_strategy min_expected_latency

Available strategies:

StrategyDescription
round_robinSequentially cycle through nodes serving the requested model.
randomWeighted random selection. Nodes reporting higher speed receive more traffic.
consistent_hashRoute requests with the same key to the same node for affinity or cache locality.
min_expected_latencySelect the node with the lowest estimated latency: unfinished_requests / speed.
min_observed_latencySelect the node with the lowest recent average latency.
prefix_cacheRoute by KV cache prefix locality to improve cache hit rate.

vLLM DistServe: Static P/D Lists

Use static mode when prefill and decode HTTP endpoints are known at router startup. DLRouter infers static discovery when both --prefill_urls and --decode_urls are provided.

python -m dlrouter \
--serving_strategy distserve \
--backend vllm \
--prefill_urls "http://prefill-1:30000,http://prefill-2:30000" \
--decode_urls "http://decode-1:30000,http://decode-2:30000" \
--models "Qwen3-32B" \
--disable_cache_status

For NIXL intra-node data parallel routing, set --intra_node_data_parallel_size to the local DP size. DLRouter expands each physical URL into url@rank logical nodes for routing state, strips the suffix before forwarding, and sends X-data-parallel-rank to vLLM.

python -m dlrouter \
--serving_strategy distserve \
--backend vllm \
--prefill_urls "http://prefill-node:8001" \
--decode_urls "http://decode-node:8002" \
--models "Qwen3-32B" \
--intra_node_data_parallel_size 8 \
--disable_cache_status

If you change the DP size between router restarts, clear the persisted node cache or use --disable_cache_status to avoid stale url@rank entries.

vLLM DistServe: Heartbeat Discovery

When neither --prefill_urls nor --decode_urls is provided, vLLM DistServe uses heartbeat discovery. P/D instances register themselves with DLRouter by publishing HTTP and ZMQ addresses.

python -m dlrouter \
--serving_strategy distserve \
--backend vllm \
--zmq_host 0.0.0.0 \
--zmq_port 30001 \
--models "Qwen3-32B" \
--disable_cache_status

In heartbeat mode, a node enters the routable set only after DLRouter resolves its model information. If a restarted node sends heartbeats before its HTTP API is ready, registration is skipped temporarily and retried by later heartbeats.

SGLang DistServe

SGLang currently uses static discovery in DLRouter. Provide both prefill and decode URL lists. --prefill_bootstrap_ports is aligned with --prefill_urls; if omitted, each prefill defaults to 8998.

python -m dlrouter \
--serving_strategy distserve \
--backend sglang \
--prefill_urls "http://prefill-1:13700,http://prefill-2:13700" \
--decode_urls "http://decode-1:13701,http://decode-2:13701" \
--prefill_bootstrap_ports "8998,8998" \
--models "Qwen3-32B" \
--disable_cache_status

DLRouter injects bootstrap_host, bootstrap_port, and bootstrap_room into the request body, sends the decorated request to prefill and decode concurrently, and returns the decode response.

LMDeploy DistServe

LMDeploy DistServe relies on externally registered prefill and decode nodes. DLRouter selects P/D nodes through NodeManager; no separate discovery component is created by the app factory.

python -m dlrouter \
--serving_strategy distserve \
--backend lmdeploy \
--migration_protocol RDMA \
--link_type RoCE

LMDeploy-specific PD features require the LMDeploy disaggregation dependencies to be installed in the runtime environment.

CLI Reference

Common Options

OptionDefaultDescription
--server_name0.0.0.0Bind address.
--server_port8000Listen port.
--backendlmdeployBackend type: lmdeploy, vllm, sglang, or dlengine.
--routing_strategymin_expected_latencyRequest routing strategy.
--serving_strategyhybridServing mode: hybrid or distserve.
--api_keysNoneComma-separated Bearer tokens for API authentication.
--sslFalseEnable SSL. Requires SSL_KEYFILE and SSL_CERTFILE.
--log_levelINFODLRouter log level.
--disable_cache_statusFalseDisable persisted node status.
--config_pathNoneCustom node status persistence file.
--workers1Number of worker processes. Values greater than 1 use Gunicorn.

Backend Options

Backend-specific options are added dynamically and are visible with --help after selecting a backend.

BackendOptionDefaultDescription
LMDeploy--migration_protocolRDMAPD migration protocol.
LMDeploy--link_typeRoCERDMA link type: RoCE or IB.
LMDeploy--with_gdrTrueEnable GPU Direct RDMA.
LMDeploy--dummy_prefillFalseUse dummy prefill for testing.
vLLM--zmq_host0.0.0.0ZMQ discovery bind host.
vLLM--zmq_port30001ZMQ discovery port.
vLLM--zmq_ping_timeout5ZMQ instance ping timeout in seconds.
vLLM--prefill_urlsNoneComma-separated prefill URLs for static mode.
vLLM--decode_urlsNoneComma-separated decode URLs for static mode.
vLLM--modelsNoneComma-separated model names.
vLLM--intra_node_data_parallel_size1Static NIXL DP-aware logical rank count per physical URL.
SGLang--prefill_urlsNoneComma-separated SGLang prefill HTTP URLs.
SGLang--decode_urlsNoneComma-separated SGLang decode HTTP URLs.
SGLang--prefill_bootstrap_ports8998 per prefillComma-separated bootstrap ports aligned with prefill URLs.
SGLang--modelsNoneComma-separated model names.

API Reference

Inference

MethodPathDescription
GET/healthRouter health check.
GET/v1/modelsList available models across registered nodes.
POST/v1/chat/completionsOpenAI-compatible chat completion endpoint.
POST/v1/completionsOpenAI-compatible text completion endpoint.

Node Management

MethodPathDescription
GET/nodes/statusShow registered nodes and routing state.
POST/nodes/addRegister a backend node.
POST/nodes/removeRemove a backend node.
POST/nodes/terminateTerminate and remove a backend node.
POST/nodes/terminate_allTerminate all registered nodes.

Node registration can provide only a URL, or a URL plus explicit status metadata:

curl -X POST http://localhost:8000/nodes/add \
-H "Content-Type: application/json" \
-d '{"url": "http://backend-host:8000"}'

Architecture

Client (OpenAI SDK / curl)
|
v
FastAPI routes
|
v
ProxyEngine
|
+--> Hybrid: NodeManager -> RoutingStrategy -> Backend HTTP forward
|
+--> DistServe: Backend-owned PD executor
|
+--> LMDeploy PD / vLLM two-stage KV transfer / SGLang bootstrap

Key modules:

ModuleResponsibility
dlrouter/api/FastAPI app, middleware, and OpenAI-compatible routes.
dlrouter/core/proxy_engine.pyDispatches hybrid requests and delegates DistServe requests to backends.
dlrouter/core/node_manager.pyMaintains node state, model lists, request counters, and routing strategy instances.
dlrouter/core/health_check.pyRuns background health checks and lazy model discovery.
dlrouter/routing/Pluggable routing strategy implementations.
dlrouter/backends/Backend adapters, shared HTTP transport, and PD execution helpers.

Backend adapters share the async HTTP transport layer in dlrouter/backends/http.py for normal forwarding, streaming forwarding, health checks, session lifecycle, and backend-specific stream framing. Backend-specific logic remains in each backend package.

Discovery Semantics

ModeBehavior
HYBRIDBackend instances are registered explicitly, usually through /nodes/add.
DISTSERVE + vLLM + staticProviding both prefill_urls and decode_urls selects static discovery.
DISTSERVE + vLLM + heartbeatProviding neither URL list selects heartbeat discovery.
DISTSERVE + SGLangStatic P/D lists are required; heartbeat discovery is not used.
DISTSERVE + LMDeployP/D nodes are selected from NodeManager; no router-startup discovery object is created.

Providing only one of prefill_urls or decode_urls is treated as a configuration error.

Environment Variables

VariableDescription
DLROUTER_HEARTBEAT_EXPIRATIONHeartbeat timeout in seconds. Default: 90.
DLROUTER_HEALTH_CHECK_TIMEOUTPer-node health-check HTTP timeout in seconds. Default: 30.
DLROUTER_HEALTH_CHECK_MAX_FAILURESConsecutive failures before removing a node. Default: 3.
DLROUTER_AIOHTTP_TIMEOUTHTTP request timeout to backends in seconds. Default: 1800.
UVICORN_LOG_LEVELUvicorn log level. Default: info.
SSL_KEYFILESSL key file path when --ssl is enabled.
SSL_CERTFILESSL certificate file path when --ssl is enabled.

Development

# Install dev dependencies
pip install -e ".[dev]"# Format code
make format
# Lint
make lint
# Auto-fix lint issues
make fix
# Type-check
make type-check
# Run tests
make test# Run all checks (local: auto-fix + test)
make all
# CI-equivalent checks (no auto-fix; same as GitHub Actions)
make ci

Before opening a pull request, run make ci. GitHub Actions runs the same lint, format, type-check, and test checks on pull requests to main.

The test suite lives under tests/ and covers backend contracts, routing strategies, service discovery, health checks, and PD executors.

Current Limitations

  • One DLRouter process is configured for one backend type at startup.
  • SGLang DistServe currently uses static discovery only.
  • LMDeploy PD features require LMDeploy disaggregation dependencies in the runtime environment.
  • fetch_models() is synchronous in the current backend contract because node registration and lazy health-check discovery call it synchronously.

Acknowledgements

DLRouter draws inspiration from these open-source projects:

  • LMDeploy, especially its proxy and PD disaggregation design.
  • vLLM, including router and cache-aware load-balancing ideas.
  • SGLang, especially router and mini load-balancer patterns for bootstrap-based PD proxying.

Thanks to the developers and contributors of these projects for their work in the LLM inference ecosystem.

License

Apache-2.0

About

No description, website, or topics provided.

Resources

Stars

21 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - DeepLink-org/DLRouter · GitHub
Skip to content

Repository files navigation

DLRouter

Python 3.9+

DLRouter is an OpenAI-compatible inference gateway for large language model backends. It routes requests across LMDeploy, vLLM, and SGLang instances with pluggable routing strategies, runtime node management, health checks, and Prefill/Decode disaggregation support.

Use DLRouter when you want one API endpoint in front of multiple LLM serving nodes, while keeping backend-specific DistServe / PD orchestration out of your application code.

Highlights

  • OpenAI-compatible API: /v1/models, /v1/chat/completions, and /v1/completions.
  • Multiple routing policies: round-robin, weighted random, consistent hash, latency-aware routing, and prefix-cache-aware routing.
  • Multi-backend support: LMDeploy, vLLM, and SGLang through a pluggable backend adapter interface.
  • DistServe / PD disaggregation: backend-owned Prefill/Decode flows for LMDeploy, vLLM, and SGLang.
  • Dynamic node management: register, remove, inspect, and terminate backend nodes through REST APIs.
  • Health checking and lazy model discovery: unhealthy nodes are removed after consecutive failures, and model lists can be discovered after a backend becomes ready.
  • Optional authentication and TLS: Bearer-token API keys and SSL/TLS support are available through CLI and environment configuration.

Supported Backends

BackendHybrid forwardingDistServe / PDDiscovery modesNotes
LMDeployYesYesExternal node registrationUses LMDeploy PD connection pool and RDMA migration when available.
vLLMYesYesStatic, heartbeatSupports two-stage KV transfer and static NIXL DP-aware rank routing.
SGLangYesYesStaticUses bootstrap dual dispatch with aligned prefill bootstrap ports.
DLEngineYesYesdlslime-ctrl (nanoctrl)Hybrid dlengine serve nodes; auto-discovery when --ctrl_address is set.

DLRouter is configured with one backend type per router process through --backend. Run multiple router processes if you need separate backend types at the same time.

Installation

pip install -e .

For development:

pip install -e ".[dev]"

Python 3.9 or newer is required.

Quick Start

This example starts DLRouter in vLLM hybrid mode, registers one vLLM server, and sends an OpenAI-compatible chat request through DLRouter.

Start a vLLM server:

vllm serve /path/to/model \
--host 0.0.0.0 \
--port 8100 \
--served-model-name Qwen3-4B
# For single-node setups without Ray, add:# --distributed-executor-backend mp

Start DLRouter:

python -m dlrouter \
--serving_strategy hybrid \
--backend vllm

Register the backend node:

curl -X POST http://localhost:8000/nodes/add \
-H "Content-Type: application/json" \
-d '{"url": "http://127.0.0.1:8100"}'

Send a request:

curl -X POST http://localhost:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{ "model": "Qwen3-4B", "messages": [{"role": "user", "content": "Hello!"}], "stream": false }'

DLEngine with dlslime-ctrl discovery

Start the control plane and a DLEngine OpenAI server (see DLEngine dlengine serve), then run DLRouter with auto-discovery:

dlslime-ctrl server --redis-url redis://127.0.0.1:6379
dlengine serve /path/to/model \
--host 0.0.0.0 --port 8100 \
--served-model-name Qwen3-4B \
--ctrl-address 127.0.0.1:4479
pip install -e ".[dlengine]"# pulls dlslime for NanoCtrlClient
python -m dlrouter \
--backend dlengine \
--serving_strategy hybrid \
--ctrl_address 127.0.0.1:4479

DLRouter polls dlslime-ctrl for entities with kind dlengine and registers their HTTP endpoints. Use the same model name as --served-model-name in requests. Manual registration still works via POST /nodes/add when --ctrl_address is omitted.

Send a request (the served model name, model path, and its basename are all accepted as the model value):

curl -X POST http://localhost:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{ "model": "Qwen3-4B", "messages": [{"role": "user", "content": "Hello!"}], "stream": false }'

DLRouter also installs a dlrouter console script, so dlrouter ... is equivalent to python -m dlrouter ... after installation.

Common Usage

Routing Strategies

python -m dlrouter \
--backend vllm \
--serving_strategy hybrid \
--routing_strategy min_expected_latency

Available strategies:

StrategyDescription
round_robinSequentially cycle through nodes serving the requested model.
randomWeighted random selection. Nodes reporting higher speed receive more traffic.
consistent_hashRoute requests with the same key to the same node for affinity or cache locality.
min_expected_latencySelect the node with the lowest estimated latency: unfinished_requests / speed.
min_observed_latencySelect the node with the lowest recent average latency.
prefix_cacheRoute by KV cache prefix locality to improve cache hit rate.

vLLM DistServe: Static P/D Lists

Use static mode when prefill and decode HTTP endpoints are known at router startup. DLRouter infers static discovery when both --prefill_urls and --decode_urls are provided.

python -m dlrouter \
--serving_strategy distserve \
--backend vllm \
--prefill_urls "http://prefill-1:30000,http://prefill-2:30000" \
--decode_urls "http://decode-1:30000,http://decode-2:30000" \
--models "Qwen3-32B" \
--disable_cache_status

For NIXL intra-node data parallel routing, set --intra_node_data_parallel_size to the local DP size. DLRouter expands each physical URL into url@rank logical nodes for routing state, strips the suffix before forwarding, and sends X-data-parallel-rank to vLLM.

python -m dlrouter \
--serving_strategy distserve \
--backend vllm \
--prefill_urls "http://prefill-node:8001" \
--decode_urls "http://decode-node:8002" \
--models "Qwen3-32B" \
--intra_node_data_parallel_size 8 \
--disable_cache_status

If you change the DP size between router restarts, clear the persisted node cache or use --disable_cache_status to avoid stale url@rank entries.

vLLM DistServe: Heartbeat Discovery

When neither --prefill_urls nor --decode_urls is provided, vLLM DistServe uses heartbeat discovery. P/D instances register themselves with DLRouter by publishing HTTP and ZMQ addresses.

python -m dlrouter \
--serving_strategy distserve \
--backend vllm \
--zmq_host 0.0.0.0 \
--zmq_port 30001 \
--models "Qwen3-32B" \
--disable_cache_status

In heartbeat mode, a node enters the routable set only after DLRouter resolves its model information. If a restarted node sends heartbeats before its HTTP API is ready, registration is skipped temporarily and retried by later heartbeats.

SGLang DistServe

SGLang currently uses static discovery in DLRouter. Provide both prefill and decode URL lists. --prefill_bootstrap_ports is aligned with --prefill_urls; if omitted, each prefill defaults to 8998.

python -m dlrouter \
--serving_strategy distserve \
--backend sglang \
--prefill_urls "http://prefill-1:13700,http://prefill-2:13700" \
--decode_urls "http://decode-1:13701,http://decode-2:13701" \
--prefill_bootstrap_ports "8998,8998" \
--models "Qwen3-32B" \
--disable_cache_status

DLRouter injects bootstrap_host, bootstrap_port, and bootstrap_room into the request body, sends the decorated request to prefill and decode concurrently, and returns the decode response.

LMDeploy DistServe

LMDeploy DistServe relies on externally registered prefill and decode nodes. DLRouter selects P/D nodes through NodeManager; no separate discovery component is created by the app factory.

python -m dlrouter \
--serving_strategy distserve \
--backend lmdeploy \
--migration_protocol RDMA \
--link_type RoCE

LMDeploy-specific PD features require the LMDeploy disaggregation dependencies to be installed in the runtime environment.

CLI Reference

Common Options

OptionDefaultDescription
--server_name0.0.0.0Bind address.
--server_port8000Listen port.
--backendlmdeployBackend type: lmdeploy, vllm, sglang, or dlengine.
--routing_strategymin_expected_latencyRequest routing strategy.
--serving_strategyhybridServing mode: hybrid or distserve.
--api_keysNoneComma-separated Bearer tokens for API authentication.
--sslFalseEnable SSL. Requires SSL_KEYFILE and SSL_CERTFILE.
--log_levelINFODLRouter log level.
--disable_cache_statusFalseDisable persisted node status.
--config_pathNoneCustom node status persistence file.
--workers1Number of worker processes. Values greater than 1 use Gunicorn.

Backend Options

Backend-specific options are added dynamically and are visible with --help after selecting a backend.

BackendOptionDefaultDescription
LMDeploy--migration_protocolRDMAPD migration protocol.
LMDeploy--link_typeRoCERDMA link type: RoCE or IB.
LMDeploy--with_gdrTrueEnable GPU Direct RDMA.
LMDeploy--dummy_prefillFalseUse dummy prefill for testing.
vLLM--zmq_host0.0.0.0ZMQ discovery bind host.
vLLM--zmq_port30001ZMQ discovery port.
vLLM--zmq_ping_timeout5ZMQ instance ping timeout in seconds.
vLLM--prefill_urlsNoneComma-separated prefill URLs for static mode.
vLLM--decode_urlsNoneComma-separated decode URLs for static mode.
vLLM--modelsNoneComma-separated model names.
vLLM--intra_node_data_parallel_size1Static NIXL DP-aware logical rank count per physical URL.
SGLang--prefill_urlsNoneComma-separated SGLang prefill HTTP URLs.
SGLang--decode_urlsNoneComma-separated SGLang decode HTTP URLs.
SGLang--prefill_bootstrap_ports8998 per prefillComma-separated bootstrap ports aligned with prefill URLs.
SGLang--modelsNoneComma-separated model names.

API Reference

Inference

MethodPathDescription
GET/healthRouter health check.
GET/v1/modelsList available models across registered nodes.
POST/v1/chat/completionsOpenAI-compatible chat completion endpoint.
POST/v1/completionsOpenAI-compatible text completion endpoint.

Node Management

MethodPathDescription
GET/nodes/statusShow registered nodes and routing state.
POST/nodes/addRegister a backend node.
POST/nodes/removeRemove a backend node.
POST/nodes/terminateTerminate and remove a backend node.
POST/nodes/terminate_allTerminate all registered nodes.

Node registration can provide only a URL, or a URL plus explicit status metadata:

curl -X POST http://localhost:8000/nodes/add \
-H "Content-Type: application/json" \
-d '{"url": "http://backend-host:8000"}'

Architecture

Client (OpenAI SDK / curl)
|
v
FastAPI routes
|
v
ProxyEngine
|
+--> Hybrid: NodeManager -> RoutingStrategy -> Backend HTTP forward
|
+--> DistServe: Backend-owned PD executor
|
+--> LMDeploy PD / vLLM two-stage KV transfer / SGLang bootstrap

Key modules:

ModuleResponsibility
dlrouter/api/FastAPI app, middleware, and OpenAI-compatible routes.
dlrouter/core/proxy_engine.pyDispatches hybrid requests and delegates DistServe requests to backends.
dlrouter/core/node_manager.pyMaintains node state, model lists, request counters, and routing strategy instances.
dlrouter/core/health_check.pyRuns background health checks and lazy model discovery.
dlrouter/routing/Pluggable routing strategy implementations.
dlrouter/backends/Backend adapters, shared HTTP transport, and PD execution helpers.

Backend adapters share the async HTTP transport layer in dlrouter/backends/http.py for normal forwarding, streaming forwarding, health checks, session lifecycle, and backend-specific stream framing. Backend-specific logic remains in each backend package.

Discovery Semantics

ModeBehavior
HYBRIDBackend instances are registered explicitly, usually through /nodes/add.
DISTSERVE + vLLM + staticProviding both prefill_urls and decode_urls selects static discovery.
DISTSERVE + vLLM + heartbeatProviding neither URL list selects heartbeat discovery.
DISTSERVE + SGLangStatic P/D lists are required; heartbeat discovery is not used.
DISTSERVE + LMDeployP/D nodes are selected from NodeManager; no router-startup discovery object is created.

Providing only one of prefill_urls or decode_urls is treated as a configuration error.

Environment Variables

VariableDescription
DLROUTER_HEARTBEAT_EXPIRATIONHeartbeat timeout in seconds. Default: 90.
DLROUTER_HEALTH_CHECK_TIMEOUTPer-node health-check HTTP timeout in seconds. Default: 30.
DLROUTER_HEALTH_CHECK_MAX_FAILURESConsecutive failures before removing a node. Default: 3.
DLROUTER_AIOHTTP_TIMEOUTHTTP request timeout to backends in seconds. Default: 1800.
UVICORN_LOG_LEVELUvicorn log level. Default: info.
SSL_KEYFILESSL key file path when --ssl is enabled.
SSL_CERTFILESSL certificate file path when --ssl is enabled.

Development

# Install dev dependencies
pip install -e ".[dev]"# Format code
make format
# Lint
make lint
# Auto-fix lint issues
make fix
# Type-check
make type-check
# Run tests
make test# Run all checks (local: auto-fix + test)
make all
# CI-equivalent checks (no auto-fix; same as GitHub Actions)
make ci

Before opening a pull request, run make ci. GitHub Actions runs the same lint, format, type-check, and test checks on pull requests to main.

The test suite lives under tests/ and covers backend contracts, routing strategies, service discovery, health checks, and PD executors.

Current Limitations

  • One DLRouter process is configured for one backend type at startup.
  • SGLang DistServe currently uses static discovery only.
  • LMDeploy PD features require LMDeploy disaggregation dependencies in the runtime environment.
  • fetch_models() is synchronous in the current backend contract because node registration and lazy health-check discovery call it synchronously.

Acknowledgements

DLRouter draws inspiration from these open-source projects:

  • LMDeploy, especially its proxy and PD disaggregation design.
  • vLLM, including router and cache-aware load-balancing ideas.
  • SGLang, especially router and mini load-balancer patterns for bootstrap-based PD proxying.

Thanks to the developers and contributors of these projects for their work in the LLM inference ecosystem.

License

Apache-2.0

About

No description, website, or topics provided.

Resources

Stars

21 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); GitHub - DeepLink-org/DLRouter · GitHub
Skip to content

Repository files navigation

DLRouter

Python 3.9+

DLRouter is an OpenAI-compatible inference gateway for large language model backends. It routes requests across LMDeploy, vLLM, and SGLang instances with pluggable routing strategies, runtime node management, health checks, and Prefill/Decode disaggregation support.

Use DLRouter when you want one API endpoint in front of multiple LLM serving nodes, while keeping backend-specific DistServe / PD orchestration out of your application code.

Highlights

  • OpenAI-compatible API: /v1/models, /v1/chat/completions, and /v1/completions.
  • Multiple routing policies: round-robin, weighted random, consistent hash, latency-aware routing, and prefix-cache-aware routing.
  • Multi-backend support: LMDeploy, vLLM, and SGLang through a pluggable backend adapter interface.
  • DistServe / PD disaggregation: backend-owned Prefill/Decode flows for LMDeploy, vLLM, and SGLang.
  • Dynamic node management: register, remove, inspect, and terminate backend nodes through REST APIs.
  • Health checking and lazy model discovery: unhealthy nodes are removed after consecutive failures, and model lists can be discovered after a backend becomes ready.
  • Optional authentication and TLS: Bearer-token API keys and SSL/TLS support are available through CLI and environment configuration.

Supported Backends

BackendHybrid forwardingDistServe / PDDiscovery modesNotes
LMDeployYesYesExternal node registrationUses LMDeploy PD connection pool and RDMA migration when available.
vLLMYesYesStatic, heartbeatSupports two-stage KV transfer and static NIXL DP-aware rank routing.
SGLangYesYesStaticUses bootstrap dual dispatch with aligned prefill bootstrap ports.
DLEngineYesYesdlslime-ctrl (nanoctrl)Hybrid dlengine serve nodes; auto-discovery when --ctrl_address is set.

DLRouter is configured with one backend type per router process through --backend. Run multiple router processes if you need separate backend types at the same time.

Installation

pip install -e .

For development:

pip install -e ".[dev]"

Python 3.9 or newer is required.

Quick Start

This example starts DLRouter in vLLM hybrid mode, registers one vLLM server, and sends an OpenAI-compatible chat request through DLRouter.

Start a vLLM server:

vllm serve /path/to/model \
--host 0.0.0.0 \
--port 8100 \
--served-model-name Qwen3-4B
# For single-node setups without Ray, add:# --distributed-executor-backend mp

Start DLRouter:

python -m dlrouter \
--serving_strategy hybrid \
--backend vllm

Register the backend node:

curl -X POST http://localhost:8000/nodes/add \
-H "Content-Type: application/json" \
-d '{"url": "http://127.0.0.1:8100"}'

Send a request:

curl -X POST http://localhost:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{ "model": "Qwen3-4B", "messages": [{"role": "user", "content": "Hello!"}], "stream": false }'

DLEngine with dlslime-ctrl discovery

Start the control plane and a DLEngine OpenAI server (see DLEngine dlengine serve), then run DLRouter with auto-discovery:

dlslime-ctrl server --redis-url redis://127.0.0.1:6379
dlengine serve /path/to/model \
--host 0.0.0.0 --port 8100 \
--served-model-name Qwen3-4B \
--ctrl-address 127.0.0.1:4479
pip install -e ".[dlengine]"# pulls dlslime for NanoCtrlClient
python -m dlrouter \
--backend dlengine \
--serving_strategy hybrid \
--ctrl_address 127.0.0.1:4479

DLRouter polls dlslime-ctrl for entities with kind dlengine and registers their HTTP endpoints. Use the same model name as --served-model-name in requests. Manual registration still works via POST /nodes/add when --ctrl_address is omitted.

Send a request (the served model name, model path, and its basename are all accepted as the model value):

curl -X POST http://localhost:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{ "model": "Qwen3-4B", "messages": [{"role": "user", "content": "Hello!"}], "stream": false }'

DLRouter also installs a dlrouter console script, so dlrouter ... is equivalent to python -m dlrouter ... after installation.

Common Usage

Routing Strategies

python -m dlrouter \
--backend vllm \
--serving_strategy hybrid \
--routing_strategy min_expected_latency

Available strategies:

StrategyDescription
round_robinSequentially cycle through nodes serving the requested model.
randomWeighted random selection. Nodes reporting higher speed receive more traffic.
consistent_hashRoute requests with the same key to the same node for affinity or cache locality.
min_expected_latencySelect the node with the lowest estimated latency: unfinished_requests / speed.
min_observed_latencySelect the node with the lowest recent average latency.
prefix_cacheRoute by KV cache prefix locality to improve cache hit rate.

vLLM DistServe: Static P/D Lists

Use static mode when prefill and decode HTTP endpoints are known at router startup. DLRouter infers static discovery when both --prefill_urls and --decode_urls are provided.

python -m dlrouter \
--serving_strategy distserve \
--backend vllm \
--prefill_urls "http://prefill-1:30000,http://prefill-2:30000" \
--decode_urls "http://decode-1:30000,http://decode-2:30000" \
--models "Qwen3-32B" \
--disable_cache_status

For NIXL intra-node data parallel routing, set --intra_node_data_parallel_size to the local DP size. DLRouter expands each physical URL into url@rank logical nodes for routing state, strips the suffix before forwarding, and sends X-data-parallel-rank to vLLM.

python -m dlrouter \
--serving_strategy distserve \
--backend vllm \
--prefill_urls "http://prefill-node:8001" \
--decode_urls "http://decode-node:8002" \
--models "Qwen3-32B" \
--intra_node_data_parallel_size 8 \
--disable_cache_status

If you change the DP size between router restarts, clear the persisted node cache or use --disable_cache_status to avoid stale url@rank entries.

vLLM DistServe: Heartbeat Discovery

When neither --prefill_urls nor --decode_urls is provided, vLLM DistServe uses heartbeat discovery. P/D instances register themselves with DLRouter by publishing HTTP and ZMQ addresses.

python -m dlrouter \
--serving_strategy distserve \
--backend vllm \
--zmq_host 0.0.0.0 \
--zmq_port 30001 \
--models "Qwen3-32B" \
--disable_cache_status

In heartbeat mode, a node enters the routable set only after DLRouter resolves its model information. If a restarted node sends heartbeats before its HTTP API is ready, registration is skipped temporarily and retried by later heartbeats.

SGLang DistServe

SGLang currently uses static discovery in DLRouter. Provide both prefill and decode URL lists. --prefill_bootstrap_ports is aligned with --prefill_urls; if omitted, each prefill defaults to 8998.

python -m dlrouter \
--serving_strategy distserve \
--backend sglang \
--prefill_urls "http://prefill-1:13700,http://prefill-2:13700" \
--decode_urls "http://decode-1:13701,http://decode-2:13701" \
--prefill_bootstrap_ports "8998,8998" \
--models "Qwen3-32B" \
--disable_cache_status

DLRouter injects bootstrap_host, bootstrap_port, and bootstrap_room into the request body, sends the decorated request to prefill and decode concurrently, and returns the decode response.

LMDeploy DistServe

LMDeploy DistServe relies on externally registered prefill and decode nodes. DLRouter selects P/D nodes through NodeManager; no separate discovery component is created by the app factory.

python -m dlrouter \
--serving_strategy distserve \
--backend lmdeploy \
--migration_protocol RDMA \
--link_type RoCE

LMDeploy-specific PD features require the LMDeploy disaggregation dependencies to be installed in the runtime environment.

CLI Reference

Common Options

OptionDefaultDescription
--server_name0.0.0.0Bind address.
--server_port8000Listen port.
--backendlmdeployBackend type: lmdeploy, vllm, sglang, or dlengine.
--routing_strategymin_expected_latencyRequest routing strategy.
--serving_strategyhybridServing mode: hybrid or distserve.
--api_keysNoneComma-separated Bearer tokens for API authentication.
--sslFalseEnable SSL. Requires SSL_KEYFILE and SSL_CERTFILE.
--log_levelINFODLRouter log level.
--disable_cache_statusFalseDisable persisted node status.
--config_pathNoneCustom node status persistence file.
--workers1Number of worker processes. Values greater than 1 use Gunicorn.

Backend Options

Backend-specific options are added dynamically and are visible with --help after selecting a backend.

BackendOptionDefaultDescription
LMDeploy--migration_protocolRDMAPD migration protocol.
LMDeploy--link_typeRoCERDMA link type: RoCE or IB.
LMDeploy--with_gdrTrueEnable GPU Direct RDMA.
LMDeploy--dummy_prefillFalseUse dummy prefill for testing.
vLLM--zmq_host0.0.0.0ZMQ discovery bind host.
vLLM--zmq_port30001ZMQ discovery port.
vLLM--zmq_ping_timeout5ZMQ instance ping timeout in seconds.
vLLM--prefill_urlsNoneComma-separated prefill URLs for static mode.
vLLM--decode_urlsNoneComma-separated decode URLs for static mode.
vLLM--modelsNoneComma-separated model names.
vLLM--intra_node_data_parallel_size1Static NIXL DP-aware logical rank count per physical URL.
SGLang--prefill_urlsNoneComma-separated SGLang prefill HTTP URLs.
SGLang--decode_urlsNoneComma-separated SGLang decode HTTP URLs.
SGLang--prefill_bootstrap_ports8998 per prefillComma-separated bootstrap ports aligned with prefill URLs.
SGLang--modelsNoneComma-separated model names.

API Reference

Inference

MethodPathDescription
GET/healthRouter health check.
GET/v1/modelsList available models across registered nodes.
POST/v1/chat/completionsOpenAI-compatible chat completion endpoint.
POST/v1/completionsOpenAI-compatible text completion endpoint.

Node Management

MethodPathDescription
GET/nodes/statusShow registered nodes and routing state.
POST/nodes/addRegister a backend node.
POST/nodes/removeRemove a backend node.
POST/nodes/terminateTerminate and remove a backend node.
POST/nodes/terminate_allTerminate all registered nodes.

Node registration can provide only a URL, or a URL plus explicit status metadata:

curl -X POST http://localhost:8000/nodes/add \
-H "Content-Type: application/json" \
-d '{"url": "http://backend-host:8000"}'

Architecture

Client (OpenAI SDK / curl)
|
v
FastAPI routes
|
v
ProxyEngine
|
+--> Hybrid: NodeManager -> RoutingStrategy -> Backend HTTP forward
|
+--> DistServe: Backend-owned PD executor
|
+--> LMDeploy PD / vLLM two-stage KV transfer / SGLang bootstrap

Key modules:

ModuleResponsibility
dlrouter/api/FastAPI app, middleware, and OpenAI-compatible routes.
dlrouter/core/proxy_engine.pyDispatches hybrid requests and delegates DistServe requests to backends.
dlrouter/core/node_manager.pyMaintains node state, model lists, request counters, and routing strategy instances.
dlrouter/core/health_check.pyRuns background health checks and lazy model discovery.
dlrouter/routing/Pluggable routing strategy implementations.
dlrouter/backends/Backend adapters, shared HTTP transport, and PD execution helpers.

Backend adapters share the async HTTP transport layer in dlrouter/backends/http.py for normal forwarding, streaming forwarding, health checks, session lifecycle, and backend-specific stream framing. Backend-specific logic remains in each backend package.

Discovery Semantics

ModeBehavior
HYBRIDBackend instances are registered explicitly, usually through /nodes/add.
DISTSERVE + vLLM + staticProviding both prefill_urls and decode_urls selects static discovery.
DISTSERVE + vLLM + heartbeatProviding neither URL list selects heartbeat discovery.
DISTSERVE + SGLangStatic P/D lists are required; heartbeat discovery is not used.
DISTSERVE + LMDeployP/D nodes are selected from NodeManager; no router-startup discovery object is created.

Providing only one of prefill_urls or decode_urls is treated as a configuration error.

Environment Variables

VariableDescription
DLROUTER_HEARTBEAT_EXPIRATIONHeartbeat timeout in seconds. Default: 90.
DLROUTER_HEALTH_CHECK_TIMEOUTPer-node health-check HTTP timeout in seconds. Default: 30.
DLROUTER_HEALTH_CHECK_MAX_FAILURESConsecutive failures before removing a node. Default: 3.
DLROUTER_AIOHTTP_TIMEOUTHTTP request timeout to backends in seconds. Default: 1800.
UVICORN_LOG_LEVELUvicorn log level. Default: info.
SSL_KEYFILESSL key file path when --ssl is enabled.
SSL_CERTFILESSL certificate file path when --ssl is enabled.

Development

# Install dev dependencies
pip install -e ".[dev]"# Format code
make format
# Lint
make lint
# Auto-fix lint issues
make fix
# Type-check
make type-check
# Run tests
make test# Run all checks (local: auto-fix + test)
make all
# CI-equivalent checks (no auto-fix; same as GitHub Actions)
make ci

Before opening a pull request, run make ci. GitHub Actions runs the same lint, format, type-check, and test checks on pull requests to main.

The test suite lives under tests/ and covers backend contracts, routing strategies, service discovery, health checks, and PD executors.

Current Limitations

  • One DLRouter process is configured for one backend type at startup.
  • SGLang DistServe currently uses static discovery only.
  • LMDeploy PD features require LMDeploy disaggregation dependencies in the runtime environment.
  • fetch_models() is synchronous in the current backend contract because node registration and lazy health-check discovery call it synchronously.

Acknowledgements

DLRouter draws inspiration from these open-source projects:

  • LMDeploy, especially its proxy and PD disaggregation design.
  • vLLM, including router and cache-aware load-balancing ideas.
  • SGLang, especially router and mini load-balancer patterns for bootstrap-based PD proxying.

Thanks to the developers and contributors of these projects for their work in the LLM inference ecosystem.

License

Apache-2.0

About

No description, website, or topics provided.

Resources

Stars

21 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages