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
1 change: 0 additions & 1 deletion src/dstack/_internal/core/models/configurations.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@
DEFAULT_PROBE_READY_AFTER = 1
DEFAULT_PROBE_METHOD = "get"
DEFAULT_PROBE_UNTIL_READY = False
ROUTER_HEALTH_PROBE_URL = "/health"
MAX_PROBE_URL_LEN = 2048
DEFAULT_REPLICA_GROUP_NAME = "0"
OPENAI_MODEL_PROBE_TIMEOUT = 30
Expand Down
36 changes: 11 additions & 25 deletions src/dstack/_internal/server/services/jobs/configurators/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
DEFAULT_REPLICA_GROUP_NAME,
LEGACY_REPO_DIR,
OPENAI_MODEL_PROBE_TIMEOUT,
ROUTER_HEALTH_PROBE_URL,
HTTPHeaderSpec,
NodeGroup,
PortMapping,
Expand Down Expand Up @@ -513,17 +512,17 @@ def _probes(self) -> list[ProbeSpec]:
model = conf.model
if not isinstance(model, OpenAIChatModel):
return []
if all(group.router is None for group in conf.replica_groups):
# No router: every replica serves the model itself, so a chat completions
# request is a genuine end-to-end readiness check.
return [_openai_model_probe_spec(model.name, model.prefix)]
group = self._replica_group()
if group is not None and group.router is not None:
# Probe the router's own liveness endpoint, which does not depend on any
# worker. Workers get no default probe: they may not serve HTTP at all
# (gRPC workers), and they don't receive traffic directly.
return [_router_health_probe_spec()]
return []
if any(group.router is not None for group in conf.replica_groups):
group = self._replica_group()
if group is None or group.router is None:
# Workers get no default probe: they may not serve HTTP at all (gRPC
# workers), and they don't receive traffic directly.
return []
# The router answers chat completions only once it has workers, which it gets
# regardless of its own readiness. For SGLang routers, dstack registers workers
# via `ServiceRouterWorkerSyncWorker`, which doesn't check router readiness.
# Dynamo workers register themselves with the router via etcd/NATS.
return [_openai_model_probe_spec(model.name, model.prefix)]


def interpolate_job_volumes(
Expand Down Expand Up @@ -598,19 +597,6 @@ def _openai_model_probe_spec(model_name: str, prefix: str) -> ProbeSpec:
)


def _router_health_probe_spec() -> ProbeSpec:
# Both supported routers (SGLang/SMG and Dynamo) serve `/health` independently of
# whether any worker is registered.
return ProbeSpec(
type="http",
method=DEFAULT_PROBE_METHOD,
url=ROUTER_HEALTH_PROBE_URL,
timeout=DEFAULT_PROBE_TIMEOUT,
interval=DEFAULT_PROBE_INTERVAL,
ready_after=DEFAULT_PROBE_READY_AFTER,
)


def _join_shell_commands(commands: List[str]) -> str:
for i, cmd in enumerate(commands):
cmd = cmd.strip()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
from dstack._internal import settings
from dstack._internal.core.models.configurations import (
OPENAI_MODEL_PROBE_TIMEOUT,
ROUTER_HEALTH_PROBE_URL,
ProbeConfig,
PythonVersion,
ReplicaGroup,
Expand Down Expand Up @@ -116,9 +115,9 @@ def _router_worker_configuration() -> ServiceConfiguration:
],
)

async def test_router_group_gets_health_probe(self):
"""The router must not be probed with chat completions: it only answers those once
dstack has registered workers, and registration requires the router to be ready."""
async def test_router_group_gets_model_probe(self):
"""The router is probed with chat completions like a replica serving the model
itself: worker registration doesn't wait for the router to be ready."""
run_spec = get_run_spec(
run_name="run", repo_id="id", configuration=self._router_worker_configuration()
)
Expand All @@ -128,9 +127,9 @@ async def test_router_group_gets_health_probe(self):

probes = job_specs[0].probes
assert len(probes) == 1
assert probes[0].url == ROUTER_HEALTH_PROBE_URL
assert probes[0].method == "get"
assert probes[0].body is None
assert probes[0].method == "post"
assert probes[0].url == "/v1/chat/completions"
assert "meta-llama/Meta-Llama-3.1-8B-Instruct" in (probes[0].body or "")

async def test_worker_group_gets_no_derived_probe(self):
"""Workers behind a router may speak gRPC, so no probe can be derived from `model`."""
Expand Down
Loading