SGLang-Omni Rust Router#
sgl-omni-router is the multi-threaded Rust data plane for SGLang-Omni. It
routes OpenAI-compatible chat, speech, transcription, translation, and
realtime requests across compatible worker replicas with bounded admission,
health-aware selection, and backpressured streaming.
Overview#
OpenAI-compatible HTTP and WebSocket routes for Omni, TTS, and ASR workers.
Static worker manifests with explicit model, modality, and media contracts.
round_robinandleast_requestsrouting over compatible healthy replicas.Direct request streaming for homogeneous worker cohorts and bounded classification when routing depends on the request body.
Pooled upstream HTTP/1.1 connections with request-ID propagation and direct response backpressure.
Status-based health checks, exact admission and active-request accounting, Prometheus metrics, diagnostics, and graceful shutdown.
flowchart LR
Client[Client] --> Listener[Bounded HTTP/1 listener]
Listener --> Routes[HTTP and WebSocket routes]
Routes --> Direct[Direct request path]
Routes --> Classified[Bounded classification path]
Direct --> Selection[Admission and worker selection]
Classified --> Selection
Selection --> Relay[Backpressured relay]
Relay --> Workers[Compatible healthy worker replicas]
Health[Health checks] --> Selection
Operations[Metrics and diagnostics] --> Selection
Installation#
Prerequisites#
Rust and Cargo
# Install rustup (Rust installer and version manager) curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh # Reload shell environment source "$HOME/.cargo/env" # Verify installation rustc --version cargo --version
SGLang-Omni, installed using the installation guide, for running model workers.
The Rust router is built from this repository as a separate binary. It routes requests to configured SGLang-Omni workers but does not install, launch, or supervise them.
Rust Binary#
git clone https://github.com/sgl-project/sglang-omni.git
cd sglang-omni/sglang_omni_router/rust
# Build release binary
cargo build --release --locked
rust-toolchain.toml selects the supported Rust toolchain. The optimized
binary is written to target/release/sgl-omni-router.
Checking Version#
After installation, verify the installation and check the version:
./target/release/sgl-omni-router --version
Quick Start#
Choose the example that matches the worker service:
Configuration |
Worker service |
|---|---|
|
Multimodal chat with text or audio output |
|
Speech synthesis, including PCM streaming |
|
Transcription and speech-to-English translation |
The examples define two workers at 127.0.0.1:8000 and
127.0.0.1:8001. Set the worker URLs, model IDs, and service
profiles to match the processes you are running.
Validate the configuration before starting the router:
./target/release/sgl-omni-router \
--config examples/omni.toml \
--check-config
Start the router:
./target/release/sgl-omni-router --config examples/omni.toml
Wait for readiness and send a request:
curl --fail http://127.0.0.1:30000/ready
curl --http1.1 http://127.0.0.1:30000/v1/chat/completions \
--header 'content-type: application/json' \
--data-binary \
'{"model":"omni-model","messages":[{"role":"user","content":"hello"}]}'
Configuration#
Configuration is strict UTF-8 TOML. Unknown fields, duplicate fields, missing
required sections, unsupported schema versions, invalid tracing filters, and
invalid limits fail validation. --check-config validates the file without
creating a Tokio runtime, binding the listener, initializing tracing, probing
workers, or changing process limits.
The top-level sections are:
Section |
Purpose |
|---|---|
|
Listener address, accepted-connection limit, and request-head timeout |
|
Graceful drain deadline |
|
Structured log format and tracing filter |
|
Routing policy and optional voice owner |
|
Global and per-service in-flight limits |
|
Probe interval, timeout, and transition thresholds |
|
Shared upstream connection pool and aggregate buffering budget |
|
Chat trust domain, request limits, and deadline |
|
Enabled media routes, trust domain, request limits, and deadline |
|
Speech and realtime routes with setup and close bounds |
|
Worker identity, endpoint, health path, session capacity, and service profiles |
Each worker has a stable ID, base URL, trust domain, optional default model, health path, session capacity, and one or more correlated service profiles. A profile row describes a combination the worker supports; the router never combines independent fields from different rows.
DNS worker authorities are resolved when an upstream connection is opened, so health probes and new data connections follow DNS changes. Worker membership remains static for the process lifetime.
Configuration limits are deployment budgets. Set admission, connection-pool limits, and timeouts from the expected workload and worker topology.
Supported APIs#
Method |
Path |
Purpose |
|---|---|---|
|
|
Process liveness |
|
|
Readiness of every enabled service |
|
|
Chat and multimodal generation |
|
|
Encoded speech or streaming PCM |
|
|
Ordered, unsplit speech batch |
|
|
Multipart transcription |
|
|
Multipart translation |
|
|
Speech WebSocket |
|
|
OpenAI-compatible realtime WebSocket |
|
|
List or upload worker-local voices |
|
|
Delete a worker-local voice |
|
|
Static model inventory |
|
|
Prometheus lifecycle and capacity metrics |
|
|
Bounded router state |
Generation requests use HTTP/1.1, JSON content type, and no query string.
Fixed-length and chunked request bodies are accepted. A single
Expect: 100-continue is handled by the client connection and is not forwarded
upstream. Ambiguous framing, trailers, other expectations, unsupported content
encoding, and oversized uploads are rejected before dispatch.
A canonical x-request-id identifies each request. A valid caller value is
preserved; otherwise the router generates one. The same value is sent to the
worker and returned to the client.
Routing and Relay#
Request paths#
The direct path is available when every eligible replica in a trust-scoped
cohort has the same concrete default model and compatible profile contract.
Heterogeneous pools use the classified path. The optional
x-sglang-omni-route-model and x-sglang-omni-route-stream headers are checked
against the classified body and are not forwarded upstream. The router relays
a direct request body as a backpressured stream.
Requests that require body-owned routing facts reserve aggregate byte capacity, read the body once, and classify the model, content forms, media placement, input and output modalities, response format, and stream mode. Classification runs on Tokio’s blocking pool with execution limited to the available CPU parallelism, while the aggregate buffered-byte budget bounds concurrent classifier memory. The original bytes are forwarded without reconstructing JSON or multipart content.
The direct path is bounded by each route’s streamed_request_max_bytes. The
classified path is bounded by the route’s buffered_request_max_bytes per
request and http.buffered_request_total_bytes across chat and media requests.
Their defaults are 512 MiB, 8 MiB, and 256 MiB respectively. Chunked classified
requests acquire the shared budget as bytes arrive. Requests without an
explicit model return ambiguous_model when compatible workers do not share
one default. Classified JSON follows the standard JSON number grammar;
non-standard NaN and Infinity tokens are rejected.
Classification completes before worker selection, so classification does not occupy an upstream connection.
Worker selection#
round_robin rotates across compatible healthy replicas. least_requests
compares active requests and rotates equal ties. Selection increments the
chosen worker’s load before dispatch and performs no network or body work while
holding the policy lock.
Routing policy is workload-specific. Use full-corpus measurements at the target concurrency to choose between the supported policies.
Admission and backpressure#
Global admission counts request envelopes. Per-service admission counts requests or sessions, except speech-batch admission, which counts batch items. Admission is fail-fast, and its permits and worker-load guard remain held until response EOF, upstream error, or downstream cancellation. The worker remains responsible for its execution and queue capacity.
The router sends one upstream request through a shared HTTP/1.1 connection pool. Redirects, ambient proxies, retries, and automatic decompression are disabled. Request and response bodies use direct backpressure without a body pump, application queue, or extra relay task.
The request deadline covers upload, connection establishment, and upstream response headers. A final worker response received before the upload completes is rejected as an upstream protocol error and is not committed downstream. After response headers are committed, the response has no total wall-clock limit.
Responses still end normally on upstream EOF or error, downstream disconnect, or process drain.
Media and Realtime Sessions#
Media routes are enabled independently. Speech batches remain ordered and are never split. Classified transcription and translation requests buffer the full multipart upload, so heterogeneous ASR deployments must size the buffered limits for their largest accepted recordings. Transcription and translation share a capacity class but require separate profile tasks.
The router preserves the trusted worker’s response content type together with JSON, text, SSE, encoded audio, raw PCM, sample-rate and channel metadata, usage, completion-token, and finish-reason contracts. It does not decode, transcode, or regenerate audio.
Speech and realtime WebSockets terminate both handshakes and pin one worker for
the complete session. Each frame awaits its destination send, preserving frame
type and order without relay tasks or application queues. Both links use a 16
MiB message bound. Speech configuration, upstream transport setup, the first
worker event, and close convergence use separate deadlines. Application-level
idle behavior remains worker-owned. A realtime model query requires a worker
with the matching default; an omitted model can use any compatible worker in
the trust domain. worker_setup_timeout_ms is an operator safety bound on the
initial worker application event; it does not limit session lifetime or mark a
worker unhealthy when it expires.
Speech configuration is replayed byte-for-byte; the router extracts routing
facts while the worker owns protocol-value validation.
Uploaded voices have one explicit owner configured by
router.voice_owner_worker_id. Voice CRUD and uploaded-name requests are
pinned to that worker. Preset names and explicit references continue to use
normal worker selection. The router does not store, replicate, or reconcile
worker-local voice data. voice_name_policy = "preset" declares names provided
by the serving model. voice_name_policy = "uploaded" declares names resolved
from worker-local voice state; hybrid pipelines should use uploaded so named
requests are routed conservatively. A named-voice request is rejected when
otherwise compatible profiles disagree on this policy.
Qwen3-TTS CustomVoice profiles use preset; Qwen3-TTS Base, Higgs, and hybrid
dots-style profiles use uploaded.
Health and Readiness#
Workers start with unknown health. Each worker has one serial probe loop that applies the configured consecutive success and failure thresholds. Transport and upstream protocol failures can request an immediate coalesced probe. Application responses do not directly change worker health.
GET /ready returns 200 while the process is serving and every enabled
generation, media, and WebSocket service has a compatible healthy worker.
Readiness also requires the configured uploaded-voice owner to be healthy and
compatible. Current worker load does not change readiness.
Operations#
/v1/models returns a sorted, deduplicated inventory built from worker defaults
and correlated profile model IDs. /metrics exposes Prometheus lifecycle,
readiness, health, admission, worker-load, listener, buffered-byte,
classification-slot, and WebSocket-session gauges. It also exposes cumulative
request and response-header counts, router-generated faults, saturation
rejections, worker probe outcomes, classification outcomes, WebSocket
termination reasons, and committed HTTP response-body outcomes.
The response-header histogram measures from router boundary entry until an HTTP
response is available. It does not measure response-body completion, streaming
TTFT, or WebSocket-session duration. Requests cancelled before that boundary
are counted separately. Classification histograms separate slot wait,
blocking-executor wait, and execution for each request kind. Blocking work that
outlives a caller timeout or cancellation records its phase durations when that
work starts or finishes, without changing the caller’s terminal outcome.
WebSocket termination counters distinguish setup from active relay and record
one bounded terminal cause per upgraded session. They do not measure completion
of the bounded close handshake. HTTP response-body counters distinguish
complete bodies, upstream body errors, and bodies dropped before completion.
The post-commit relay-failure counter is the upstream-error subset.
/diagnostics returns bounded deterministic JSON for the same router-local
state and marks the configured voice owner. Each diagnostic worker includes
cumulative dispatch counts for the fixed service classes and voice-control
operations. A speech batch contributes one dispatch while active worker load
remains item-weighted. Workers also include their latest probe result, HTTP
status when present, observation time, transition streaks, and cumulative
outcomes.
Operations responses snapshot router-local state and never contact workers.
Admission values come from the semaphores that enforce router limits. Worker
load comes from the same counters used by least_requests. Metric labels use
fixed vocabularies instead of worker IDs, model IDs, request IDs, paths, or
client input. Listener usage includes the slot reserved by the pending accept.
registered WebSocket sessions represent callbacks retained for shutdown.
Structured logging covers lifecycle events, health transitions, and exceptional
conditions. logging.filter accepts a tracing filter expression, and
logging.format accepts json or compact.
Networking and Shutdown#
server.max_connections bounds accepted client sockets. The listener acquires
capacity before accept, and the accepted transport retains the permit through
HTTP upgrade until the socket closes. Accepted sockets enable TCP_NODELAY.
server.header_read_timeout_ms limits each initial or keep-alive HTTP/1 request
head. It does not limit request bodies, active handlers, responses, streams, or
upgraded transports. Connection-level accept errors retry immediately; other
accept errors are logged and retried after one second.
On Unix, startup attempts to raise the RLIMIT_NOFILE soft limit to 65,535.
If the process hard limit prevents that, the router logs a warning and continues.
Set the process file limit for the deployment’s configured connection and
admission concurrency.
The first SIGINT or SIGTERM closes admission, stops health work, drops the
listener, and drains owned tasks. A distinct second signal or the drain
deadline aborts and joins remaining work and exits with failure.
Security and Deployment#
The router uses one static manifest and one multi-threaded process. It supports numeric loopback and non-loopback listener addresses, Linux hosts and containers, and MPS data-parallel worker deployments without a Python control-plane/data-plane split.
The router does not implement client authentication or terminate TLS. Deploy it on a trusted network or behind an authenticated TLS proxy.
Dynamic worker discovery and CRUD, request retries, circuit breakers, cache-aware routing, prefill/decode routing, and worker supervision are outside this router’s data-plane contract.
Development#
Toolchains#
Install the pinned implementation toolchain and minimum-supported Rust version:
rustup toolchain install 1.97.1 \
--profile minimal \
--component clippy,rustfmt
rustup toolchain install 1.90.0 --profile minimal
rust-toolchain.toml selects Rust 1.97.1 for normal commands. Rust 1.90.0 is
used only for compatibility checks.
Quality gates#
Run the same checks used by CI:
cargo fmt --all -- --check
cargo +1.90.0 check --workspace --all-targets --all-features --locked
cargo clippy --workspace --all-targets --all-features --locked -- -D warnings
cargo test --workspace --all-targets --all-features --locked -- --test-threads=1
RUSTDOCFLAGS="-D warnings" \
cargo doc --workspace --all-features --no-deps --locked
cargo build --release --workspace --all-features --locked
Validate all tracked deployment examples with the release binary:
binary="${CARGO_TARGET_DIR:-./target}/release/sgl-omni-router"
git ls-files -- 'examples/*.toml' | LC_ALL=C sort | while IFS= read -r config; do
"$binary" --config "$config" --check-config
done
Unit tests live next to the implementation they exercise. Process, HTTP,
media, WebSocket, and voice integration tests live under tests/ and use real
loopback sockets where transport behavior is part of the contract.
Source layout#
Path |
Responsibility |
|---|---|
|
Strict configuration and cross-field validation |
|
Runtime assembly, routes, listener, and shutdown |
|
Admission, health, profiles, policy selection, and worker load |
|
Shared HTTP client, buffering, body adapters, and relay |
|
Chat validation and classification |
|
Speech, batch, transcription, translation, and voices |
|
Speech and realtime session setup and relay |
|
Models, metrics, and diagnostics |
|
Process and protocol integration tests |
Python Router#
The Python router remains available as sgl-omni-router-py. Its guide is
docs/basic_usage/python_router.md.