Repository navigation
[Bug]: gRPC router workers can never become ready when model is set #4240
Description
Steps to reproduce
- Create a service with a router replica group and a gRPC worker replica group, and set
modelon the service:
type: service
name: qwen38-27b-agentic-smg
groups:
- replicas: 1
python: "3.12"
commands:
- pip install smg
- smg launch --host 0.0.0.0 --port 8000 --enable-igw --policy cache_aware --model-path Qwen/Qwen3.8-27B
router:
type: sglang
resources:
gpu: 0
- replicas: 1
image: vllm/vllm-openai:qwen38
commands:
- pip install smg-grpc-servicer
- python3 -m vllm.entrypoints.grpc_server --host 0.0.0.0 --port 8000 --model Qwen/Qwen3.8-27B-FP8
resources:
gpu: H100:80GB
port: 8000
model:
type: chat
name: Qwen/Qwen3.8-27B
format: openai
prefix: /v1- Apply it and wait for both replicas to reach
running. - Send a request to the service endpoint.
Actual behaviour
The request fails with SMG's own error:
{"error":{"type":"Not Found","code":"model_not_found",
"message":"No worker available for model 'Qwen/Qwen3.8-27B'","param":null}}The worker never registers with the router. dstack ps shows both replicas running, the worker's engine is fully up (VllmEngineServicer initialized, Standard gRPC health service initialized), and the router is reachable — but the router log contains zero POST /workers, and Tree memory: string_trees=0 models.
Chain
Setting model injects a default HTTP probe. _probes() has no router/worker distinction, so it is applied to every replica group:
dstack/src/dstack/_internal/server/services/jobs/configurators/base.py
Lines 492 to 501 in e828c9e
| def _probes(self) -> list[ProbeSpec]: | |
| if isinstance(self.run_spec.configuration, ServiceConfiguration): | |
| probes = self.run_spec.configuration.probes | |
| if probes is not None: | |
| return list(map(_probe_config_to_spec, probes)) | |
| # Generate default probe if model is set | |
| model = self.run_spec.configuration.model | |
| if isinstance(model, OpenAIChatModel): | |
| return [_openai_model_probe_spec(model.name, model.prefix)] | |
| return [] |
That probe cannot succeed against a gRPC worker, because probes are HTTP-only:
dstack/src/dstack/_internal/core/models/configurations.py
Lines 382 to 386 in e828c9e
| class ProbeConfig(CoreModel): | |
| type: Annotated[ | |
| Literal["http"], | |
| Field(description="The probe type. Must be `http`"), | |
| ] # expect other probe types in the future, namely `exec` |
Server log, replica 1 being the gRPC worker and replica 0 the router:
probe(d861b2)qwen38-27b-agentic-smg-0-1-0: probe failed: RemoteProtocolError('illegal request line')
probe(b4838d)qwen38-27b-agentic-smg-0-0-0: probe status code: 404
illegal request line is an HTTP client hitting a gRPC port. With the probe failing, is_job_ready() stays false and JobModel.ready stays 0 for both replicas.
The router-worker sync then loads only ready jobs:
dstack/src/dstack/_internal/server/background/pipeline_tasks/service_router_worker_sync.py
Lines 232 to 238 in e828c9e
| load_only(RunModel.id, RunModel.run_spec), | |
| selectinload( | |
| RunModel.jobs.and_( | |
| JobModel.status == JobStatus.RUNNING, | |
| JobModel.ready == True, | |
| ) | |
| ) |
so _get_router_job() finds nothing, sync_router_workers_for_run_model() returns immediately, and no worker is ever pushed to the router. This is visible as a sub-25ms sync:
DEBUG Processing service_router_worker_sync item 9afc133b-...
DEBUG Processed service_router_worker_sync item 9afc133b-... in 0.010
Nothing is logged when this happens — the early return is silent, so from the outside the service looks healthy.
Why the documented example is unaffected
vLLM PD disaggregation uses gRPC workers and works, because it sets neither model nor probes. _probes() then returns [], and is_job_ready() is all() over an empty sequence, i.e. True.
SGLang PD disaggregation sets model but also an explicit /health probe, which works because SGLang workers speak HTTP.
The broken combination is model + gRPC workers, which neither example covers.
Note this is easy to hit unintentionally: presets generate model by default, so a service derived from a preset gets the HTTP probe without the user writing one.
Expected behaviour
A gRPC worker replica group should be able to become ready. Options, roughly in order of preference:
- A gRPC probe type.
ProbeConfig.typeisLiteral["http"]with the comment# expect other probe types in the future; vLLM's gRPC server already registers the standardgrpc.health.v1.Healthservice, so a gRPC health probe would work out of the box. - Per-group
probes, so a router group and a worker group can be probed differently. Todayprobesis service-wide, but with a router the two groups routinely speak different protocols. - At minimum, do not apply the
modeldefault probe to non-router replica groups, and log when the router-worker sync returns early because no ready router job was found.
dstack version
0.21.3 (master, e828c9e14)
Additional information
Workaround, confirmed working: set probes: [] explicitly. The field is documented as [] = explicit empty, which bypasses the model default. Both replicas then flip to ready=1 and the sync starts reaching the router. The cost is losing dstack-side health detection; for a router deployment SMG does its own worker health checking and circuit breaking, so this is tolerable.
The other workaround is to omit model, matching the vLLM PD example.
Activity
mikemikimike commentedon Sep 1, 2026