Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions docs/config-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,30 @@ Health checks, benchmark clients, and `SRT_FRONTEND_HOST` target the **aggregate
endpoint leader** (the node running the public `vllm serve`), not necessarily the
Slurm head node.

To use vLLM's Rust OpenAI frontend in managed-engine mode, set
`backend.vllm_serve_binary` to `vllm-rs`. An absolute path is also accepted when
the executable is installed in the container but is not on `PATH`:

```yaml
frontend:
type: vllm
enable_multiple_frontends: false

backend:
type: vllm
vllm_serve_binary: /usr/local/lib/python3.12/dist-packages/vllm/vllm-rs
vllm_config:
aggregated:
tensor-parallel-size: 4
tokenizer-mode: hf
reasoning-parser: auto
tool-call-parser: auto
```

The default remains `vllm`, so existing recipes continue to use the Python
frontend. This setting only changes direct `frontend.type: vllm` jobs; Dynamo,
sidecar, and `vllm-router` launch paths are unchanged.

Compare with `frontend.type: dynamo` + `backend.type: vllm`, which keeps Dynamo as
the request router and uses `python3 -m dynamo.vllm` workers with NATS/etcd.

Expand Down
7 changes: 6 additions & 1 deletion src/srtctl/backends/vllm.py
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,10 @@ class VLLMProtocol:
# Per-GPU remains available as a deprecated compatibility layout.
dp_launch_mode: DPLaunchMode = "per_node"

# Executable used by direct aggregate frontend.type=vllm jobs. This can be
# set to vllm-rs (or its absolute path) to use the Rust OpenAI frontend.
vllm_serve_binary: str = "vllm"

Schema: ClassVar[builtins.type[Schema]] = Schema

def find_dp_modes(self) -> list[tuple[str, dict[str, Any]]]:
Expand Down Expand Up @@ -1047,7 +1051,8 @@ def build_worker_command(
config.setdefault("kv-transfer-config", _connector_to_kv_transfer_config(connector))

node_rank = endpoint_nodes.index(process.node)
cmd.extend(["vllm", "serve", model_arg])
serve_binary = self.vllm_serve_binary if frontend_type == "vllm" else "vllm"
cmd.extend([serve_binary, "serve", model_arg])
# Collected as the command is built so the override report below can
# name the value srtslurm actually passed for each flag it took over.
srtslurm_owned: dict[str, str] = {}
Expand Down
55 changes: 55 additions & 0 deletions tests/test_configs.py
Original file line number Diff line number Diff line change
Expand Up @@ -2865,6 +2865,61 @@ def test_direct_vllm_command_preserves_current_main_device_binding(self):
assert "--request-plane" not in cmd
assert "dynamo.vllm" not in cmd

def test_direct_vllm_command_supports_vllm_rs_binary(self):
"""Direct vLLM can launch a managed-engine Rust frontend."""
from pathlib import Path
from unittest.mock import MagicMock, patch

from srtctl.backends import VLLMProtocol, VLLMServerConfig
from srtctl.core.topology import Process

vllm_rs = "/usr/local/lib/python3.12/dist-packages/vllm/vllm-rs"
backend = VLLMProtocol(
vllm_serve_binary=vllm_rs,
vllm_config=VLLMServerConfig(
aggregated={
"tokenizer-mode": "hf",
"reasoning-parser": "auto",
"tool-call-parser": "auto",
}
),
)
process = Process(
node="node0",
gpu_indices=frozenset(range(4)),
sys_port=8081,
http_port=0,
endpoint_mode="agg",
endpoint_index=0,
node_rank=0,
)
runtime = MagicMock()
runtime.model_path = Path("/model")
runtime.is_hf_model = False
runtime.frontend_port = 9000
runtime.network_interface = "eth0"

with patch("srtctl.core.slurm.get_hostname_ip", return_value="10.0.0.1"):
cmd = backend.build_worker_command(
process=process,
endpoint_processes=[process],
runtime=runtime,
frontend_type="vllm",
)

assert cmd[:3] == [vllm_rs, "serve", "/model"]
assert cmd[cmd.index("--reasoning-parser") + 1] == "auto"
assert cmd[cmd.index("--tool-call-parser") + 1] == "auto"

def test_vllm_serve_binary_schema_round_trip(self):
"""The direct serve executable can be configured from recipe YAML."""
from srtctl.backends import VLLMProtocol

backend = VLLMProtocol.Schema().load({"vllm_serve_binary": "vllm-rs"})

assert backend.vllm_serve_binary == "vllm-rs"
assert VLLMProtocol.Schema().dump(backend)["vllm_serve_binary"] == "vllm-rs"

def test_direct_vllm_command_keeps_iteration_profiler_config(self):
"""Direct vllm serve retains main's profiling-derived server option."""
from pathlib import Path
Expand Down
Loading