From 691794537d6a1761d8246a5e868e5107563f6e05 Mon Sep 17 00:00:00 2001 From: Dmitry Meyer Date: Wed, 14 Oct 2026 10:43:40 +0000 Subject: [PATCH] Rename router type `sglang` to `smg` The replica group router `type: sglang` runs the Shepherd Model Gateway (SMG), so rename the value to `smg` and the enum member to `RouterType.SMG`. `ReplicaGroupRouterConfig.type` is now typed as `RouterType` instead of a `Literal` duplicating its values. `sglang` is accepted as an alias indefinitely, since run specs stored in the DB and older clients' requests use it. Until 0.23.0, `smg` is serialized to JSON as `sglang`, since servers and clients < 0.22.4 reject `smg`. This covers client requests, server responses, and run specs stored in the DB, which older server replicas read during a rolling deployment. Dropping the serializer in 0.23.0 requires patching responses for clients < 0.22.4. Also rename SGLang router mentions in names, comments, and docs, and add `groups[n].router` to the service reference. Blog posts are left as is. Co-Authored-By: Claude Opus 5.5 (1M context) --- mkdocs/docs/concepts/services.md | 6 +-- mkdocs/docs/concepts/tasks.md | 2 +- mkdocs/docs/examples/inference/sglang.md | 6 +-- mkdocs/docs/examples/inference/vllm.md | 2 +- mkdocs/docs/reference/dstack.yml/service.md | 8 +++- .../_internal/core/models/configurations.py | 2 +- src/dstack/_internal/core/models/routers.py | 43 +++++++++++++++---- .../proxy/gateway/services/registry.py | 14 +++--- src/dstack/_internal/proxy/lib/const.py | 2 +- .../background/pipeline_tasks/jobs_running.py | 2 +- .../service_router_worker_sync.py | 4 +- .../services/jobs/configurators/base.py | 2 +- .../server/services/runs/replicas.py | 2 +- .../services/runs/router_worker_sync.py | 8 ++-- .../runs/service_router_worker_sync.py | 6 +-- .../core/models/test_configurations.py | 8 ++-- .../_internal/core/models/test_routers.py | 30 +++++++++++++ src/tests/_internal/core/models/test_runs.py | 8 ++-- .../pipeline_tasks/test_running_jobs.py | 18 ++++---- .../pipeline_tasks/test_runs/test_active.py | 2 +- .../test_service_router_worker_sync.py | 4 +- .../jobs/configurators/test_service.py | 4 +- .../services/runs/test_router_worker_sync.py | 2 +- .../server/services/runs/test_spec.py | 20 ++++----- 24 files changed, 132 insertions(+), 73 deletions(-) create mode 100644 src/tests/_internal/core/models/test_routers.py diff --git a/mkdocs/docs/concepts/services.md b/mkdocs/docs/concepts/services.md index 461ef530d6..a7d24fe9a6 100644 --- a/mkdocs/docs/concepts/services.md +++ b/mkdocs/docs/concepts/services.md @@ -197,7 +197,7 @@ Below is an example for running `Qwen/Qwen3.8-Flash-Next` on `H200`: resources: cpu: 4 router: - type: sglang + type: smg - replicas: 2 commands: @@ -363,7 +363,7 @@ Below is an example for running `zai-org/GLM-4.5-Air-FP8` on `H200`: resources: cpu: 4 router: - type: sglang + type: smg - replicas: 4 commands: @@ -537,7 +537,7 @@ groups: resources: cpu: 4.. router: - type: sglang + type: smg - replicas: 2 commands: diff --git a/mkdocs/docs/concepts/tasks.md b/mkdocs/docs/concepts/tasks.md index 6c4d36b8aa..34e68698b9 100644 --- a/mkdocs/docs/concepts/tasks.md +++ b/mkdocs/docs/concepts/tasks.md @@ -209,7 +209,7 @@ and the [NCCL/RCCL tests](../examples/clusters/nccl-rccl-tests.md) example for r master node. > In a service, worker registration happens automatically when the router group sets - > [`router`](services.md#router) to `type: sglang`. + > [`router`](services.md#router) to `type: smg`.
diff --git a/mkdocs/docs/examples/inference/sglang.md b/mkdocs/docs/examples/inference/sglang.md index 94789a2afa..8ca795a250 100644 --- a/mkdocs/docs/examples/inference/sglang.md +++ b/mkdocs/docs/examples/inference/sglang.md @@ -166,7 +166,7 @@ To run SGLang with [PD disaggregation](https://docs.sglang.io/advanced_features/ resources: cpu: 4 router: - type: sglang + type: smg - replicas: 1..4 scaling: @@ -240,7 +240,7 @@ To run SGLang with [PD disaggregation](https://docs.sglang.io/advanced_features/ --port 8000 \ --prefill-policy cache_aware router: - type: sglang + type: smg resources: cpu: 4 @@ -321,7 +321,7 @@ To run SGLang with [PD disaggregation](https://docs.sglang.io/advanced_features/ resources: cpu: 4.. router: - type: sglang + type: smg - replicas: 1..2 scaling: diff --git a/mkdocs/docs/examples/inference/vllm.md b/mkdocs/docs/examples/inference/vllm.md index 0fac3d2370..08f45f82ab 100644 --- a/mkdocs/docs/examples/inference/vllm.md +++ b/mkdocs/docs/examples/inference/vllm.md @@ -154,7 +154,7 @@ groups: --port 8000 \ --prefill-policy cache_aware router: - type: sglang + type: smg resources: cpu: 4 diff --git a/mkdocs/docs/reference/dstack.yml/service.md b/mkdocs/docs/reference/dstack.yml/service.md index ad4e5585af..315278a1d7 100644 --- a/mkdocs/docs/reference/dstack.yml/service.md +++ b/mkdocs/docs/reference/dstack.yml/service.md @@ -89,8 +89,12 @@ The `service` configuration type allows running [services](../../concepts/servic #SCHEMA# dstack._internal.core.models.configurations.ReplicaGroup overrides: show_root_heading: false - type: - required: true + +##### `groups[n].router` { #router data-toc-label="router" } + +#SCHEMA# dstack._internal.core.models.routers.ReplicaGroupRouterConfig + overrides: + show_root_heading: false ### `retry` diff --git a/src/dstack/_internal/core/models/configurations.py b/src/dstack/_internal/core/models/configurations.py index 81d5d6226a..ba7c62af0c 100644 --- a/src/dstack/_internal/core/models/configurations.py +++ b/src/dstack/_internal/core/models/configurations.py @@ -1066,7 +1066,7 @@ class ReplicaGroup(CoreModel): router: Annotated[ Optional[ReplicaGroupRouterConfig], Field( - description="When set, replicas in this group run the in-service HTTP router (e.g. SGLang).", + description="When set, replicas in this group run the in-service HTTP router (e.g. SMG)", ), ] = None diff --git a/src/dstack/_internal/core/models/routers.py b/src/dstack/_internal/core/models/routers.py index 7d4acd169a..647c62b2cf 100644 --- a/src/dstack/_internal/core/models/routers.py +++ b/src/dstack/_internal/core/models/routers.py @@ -1,26 +1,51 @@ from enum import Enum -from typing import Literal +from typing import Any -from pydantic import Field +from pydantic import Field, field_serializer, field_validator from typing_extensions import Annotated from dstack._internal.core.models.common import CoreModel class RouterType(str, Enum): - SGLANG = "sglang" + SMG = "smg" DYNAMO = "dynamo" +# The value `RouterType.SMG` had before the rename +_LEGACY_SMG_VALUE = "sglang" + + class ReplicaGroupRouterConfig(CoreModel): type: Annotated[ - Literal["sglang", "dynamo"], + RouterType, Field( description=( - "The router implementation for this replica group. " - "`sglang` runs the SGLang router and dstack syncs worker URLs to it. " - "`dynamo` runs the NVIDIA Dynamo frontend, which discovers workers " - "itself via etcd/NATS." + "The router implementation for this replica group." + " `smg` runs the Shepherd Model Gateway (SMG) and dstack syncs worker URLs to it." + " `dynamo` runs the NVIDIA Dynamo frontend, which discovers workers" + " itself via etcd/NATS" ), ), - ] = "sglang" + ] = RouterType.SMG + + @field_validator("type", mode="before") + @classmethod + def _accept_legacy_smg_value(cls, v: Any) -> Any: + # Stored run specs and older clients' requests use the legacy value + if v == _LEGACY_SMG_VALUE: + return RouterType.SMG + return v + + @field_serializer("type", when_used="json") + def _serialize_type(self, v: RouterType) -> str: + # Servers and clients < 0.22.4 reject `smg`, so keep sending and storing the legacy value + # for one minor release. This covers client requests, server responses, and the run specs + # stored in the DB, which older server replicas read during a rolling deployment. + # TODO(0.23.0): Drop this serializer. When doing so, patch run specs in server responses + # back to the legacy value for clients < 0.22.4 (see `server/compatibility/runs.py`), + # since 0.23.* servers still support 0.22.* clients. Keep `_accept_legacy_smg_value`: + # run specs stored with the legacy value stay in the DB. + if v == RouterType.SMG: + return _LEGACY_SMG_VALUE + return v.value diff --git a/src/dstack/_internal/proxy/gateway/services/registry.py b/src/dstack/_internal/proxy/gateway/services/registry.py index e0f67868bb..1bc47d7195 100644 --- a/src/dstack/_internal/proxy/gateway/services/registry.py +++ b/src/dstack/_internal/proxy/gateway/services/registry.py @@ -349,8 +349,8 @@ async def get_nginx_service_config( ) -> ServiceConfig: limit_req_zones: list[LimitReqZoneConfig] = [] locations: list[LocationConfig] = [] - sglang_limits: dict[str, LimitReqConfig] = {} - sglang_prefix_lengths: dict[str, int] = {} # Track prefix lengths for most-specific selection + router_limits: dict[str, LimitReqConfig] = {} + router_prefix_lengths: dict[str, int] = {} # Track prefix lengths for most-specific selection for i, rate_limit in enumerate(service.rate_limits): zone_name = f"{i}.{service.domain_safe}" @@ -368,13 +368,13 @@ async def get_nginx_service_config( if rate_limit.prefix == path or path.startswith(rate_limit.prefix): # Use the longest prefix if multiple prefixes match the same path current_prefix_len = len(rate_limit.prefix) - if path not in sglang_limits or current_prefix_len > sglang_prefix_lengths.get( + if path not in router_limits or current_prefix_len > router_prefix_lengths.get( path, 0 ): - sglang_limits[path] = LimitReqConfig( + router_limits[path] = LimitReqConfig( zone=zone_name, burst=rate_limit.burst ) - sglang_prefix_lengths[path] = current_prefix_len + router_prefix_lengths[path] = current_prefix_len else: locations.append( LocationConfig( @@ -388,10 +388,10 @@ async def get_nginx_service_config( for path in ROUTER_WHITELISTED_PATHS: # Use prefix match for paths that end with a slash and exact match for paths that don't if path.endswith("/"): - locations.append(LocationConfig(prefix=path, limit_req=sglang_limits.get(path))) + locations.append(LocationConfig(prefix=path, limit_req=router_limits.get(path))) else: locations.append( - LocationConfig(prefix=f"= {path}", limit_req=sglang_limits.get(path)) + LocationConfig(prefix=f"= {path}", limit_req=router_limits.get(path)) ) # Don't auto-add / location for router-based services (catch-all 403 handles it) diff --git a/src/dstack/_internal/proxy/lib/const.py b/src/dstack/_internal/proxy/lib/const.py index 0824a5edb7..2c12512bbd 100644 --- a/src/dstack/_internal/proxy/lib/const.py +++ b/src/dstack/_internal/proxy/lib/const.py @@ -5,7 +5,7 @@ DEFAULT_SERVICE_READ_TIMEOUT = 300 # Inference endpoints exposed by the in-replica HTTP router. Applies to both -# SGLang's router and Dynamo's `dynamo.frontend` — they share the +# SMG and Dynamo's `dynamo.frontend` — they share the # OpenAI-compatible endpoint surface. ROUTER_WHITELISTED_PATHS: tuple[str, ...] = ( "/generate", diff --git a/src/dstack/_internal/server/background/pipeline_tasks/jobs_running.py b/src/dstack/_internal/server/background/pipeline_tasks/jobs_running.py index bc551a5390..35d74edc21 100644 --- a/src/dstack/_internal/server/background/pipeline_tasks/jobs_running.py +++ b/src/dstack/_internal/server/background/pipeline_tasks/jobs_running.py @@ -414,7 +414,7 @@ class _StartupContext: """Dynamo-specific env (e.g. DSTACK_ROUTER_INTERNAL_IP) computed from the router replica's state. Passed through to RunnerClient.submit_job, which merges it into a deep-copied job_spec.env so the shared job_spec is not - mutated. None for SGLang services, non-router runs, and the router + mutated. None for SMG services, non-router runs, and the router replica itself.""" diff --git a/src/dstack/_internal/server/background/pipeline_tasks/service_router_worker_sync.py b/src/dstack/_internal/server/background/pipeline_tasks/service_router_worker_sync.py index 3d66280bf9..0afe59af01 100644 --- a/src/dstack/_internal/server/background/pipeline_tasks/service_router_worker_sync.py +++ b/src/dstack/_internal/server/background/pipeline_tasks/service_router_worker_sync.py @@ -34,7 +34,7 @@ from dstack._internal.server.services.logging import fmt from dstack._internal.server.services.pipelines import PipelineHinterProtocol from dstack._internal.server.services.runs.router_worker_sync import ( - run_model_has_sglang_router_replica_group, + run_model_has_smg_router_replica_group, sync_router_workers_for_run_model, ) from dstack._internal.server.utils import tracing @@ -220,7 +220,7 @@ async def process(self, item: ServiceRouterWorkerSyncPipelineItem) -> None: run_model.deleted or run_model.status.is_finished() or run_model.status != RunStatus.RUNNING - or not run_model_has_sglang_router_replica_group(run_model) + or not run_model_has_smg_router_replica_group(run_model) ): early_cleanup_update_map: _SyncRowUpdateMap = {"deleted": True} set_processed_update_map_fields(early_cleanup_update_map) diff --git a/src/dstack/_internal/server/services/jobs/configurators/base.py b/src/dstack/_internal/server/services/jobs/configurators/base.py index 1a1fadf375..9b50b67b3e 100644 --- a/src/dstack/_internal/server/services/jobs/configurators/base.py +++ b/src/dstack/_internal/server/services/jobs/configurators/base.py @@ -519,7 +519,7 @@ def _probes(self) -> list[ProbeSpec]: # 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 + # regardless of its own readiness. For SMG 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)] diff --git a/src/dstack/_internal/server/services/runs/replicas.py b/src/dstack/_internal/server/services/runs/replicas.py index e579335e5c..d917599472 100644 --- a/src/dstack/_internal/server/services/runs/replicas.py +++ b/src/dstack/_internal/server/services/runs/replicas.py @@ -233,7 +233,7 @@ def get_router_env_for_job( router_group = get_router_replica_group(run_spec) if router_group is None or router_group.name is None: return None - # DSTACK_ROUTER_INTERNAL_IP is Dynamo-specific. SGLang workers + # DSTACK_ROUTER_INTERNAL_IP is Dynamo-specific. SMG workers # are registered via the worker-sync pipeline (ServiceRouterWorkerSyncModel) if router_group.router is None or router_group.router.type != RouterType.DYNAMO: return None diff --git a/src/dstack/_internal/server/services/runs/router_worker_sync.py b/src/dstack/_internal/server/services/runs/router_worker_sync.py index bd40ae9e75..65da86eedd 100644 --- a/src/dstack/_internal/server/services/runs/router_worker_sync.py +++ b/src/dstack/_internal/server/services/runs/router_worker_sync.py @@ -1,4 +1,4 @@ -"""Reconcile SGLang router /workers with dstack's ready worker replicas (async, SSH-tunneled).""" +"""Reconcile SMG /workers with dstack's ready worker replicas (async, SSH-tunneled).""" import asyncio import logging @@ -42,14 +42,14 @@ from dstack._internal.utils.logging import get_logger from .replicas import job_belongs_to_group -from .service_router_worker_sync import run_spec_has_sglang_router_replica_group +from .service_router_worker_sync import run_spec_has_smg_router_replica_group logger = get_logger(__name__) -def run_model_has_sglang_router_replica_group(run_model: RunModel) -> bool: +def run_model_has_smg_router_replica_group(run_model: RunModel) -> bool: run_spec = get_run_spec(run_model) - return run_spec_has_sglang_router_replica_group(run_spec) + return run_spec_has_smg_router_replica_group(run_spec) async def sync_router_workers_for_run_model(run_model: RunModel) -> None: diff --git a/src/dstack/_internal/server/services/runs/service_router_worker_sync.py b/src/dstack/_internal/server/services/runs/service_router_worker_sync.py index b251e76f92..3a38beebb7 100644 --- a/src/dstack/_internal/server/services/runs/service_router_worker_sync.py +++ b/src/dstack/_internal/server/services/runs/service_router_worker_sync.py @@ -32,14 +32,14 @@ def _reactivate_sync_row_update_map(*, now: datetime) -> _SyncRowUpdateMap: } -def run_spec_has_sglang_router_replica_group(run_spec: RunSpec) -> bool: +def run_spec_has_smg_router_replica_group(run_spec: RunSpec) -> bool: if run_spec.configuration.type != "service": return False cfg = run_spec.configuration if not isinstance(cfg, ServiceConfiguration): return False return any( - g.router is not None and g.router.type == RouterType.SGLANG for g in cfg.replica_groups + g.router is not None and g.router.type == RouterType.SMG for g in cfg.replica_groups ) @@ -48,7 +48,7 @@ async def ensure_service_router_worker_sync_row( run_model: RunModel, run_spec: RunSpec, ) -> None: - if not run_spec_has_sglang_router_replica_group(run_spec): + if not run_spec_has_smg_router_replica_group(run_spec): return res = await session.execute( select(ServiceRouterWorkerSyncModel).where( diff --git a/src/tests/_internal/core/models/test_configurations.py b/src/tests/_internal/core/models/test_configurations.py index d1949a6edc..19188bdc85 100644 --- a/src/tests/_internal/core/models/test_configurations.py +++ b/src/tests/_internal/core/models/test_configurations.py @@ -18,7 +18,7 @@ parse_run_configuration, ) from dstack._internal.core.models.resources import Range, ResourcesSpec -from dstack._internal.core.models.routers import ReplicaGroupRouterConfig +from dstack._internal.core.models.routers import ReplicaGroupRouterConfig, RouterType class TestParseConfiguration: @@ -154,8 +154,8 @@ def test_replica_group_router(self): { "name": "router", "count": 1, - "commands": ["sglang serve"], - "router": {"type": "sglang"}, + "commands": ["smg launch"], + "router": {"type": "smg"}, }, {"name": "worker", "count": 2, "commands": ["worker"]}, ], @@ -165,7 +165,7 @@ def test_replica_group_router(self): assert parsed.groups is not None router_g = next(g for g in parsed.groups if g.name == "router") assert isinstance(router_g.router, ReplicaGroupRouterConfig) - assert router_g.router.type == "sglang" + assert router_g.router.type == RouterType.SMG def test_spot_policy_set_at_both_service_and_group_rejected(self): with pytest.raises( diff --git a/src/tests/_internal/core/models/test_routers.py b/src/tests/_internal/core/models/test_routers.py new file mode 100644 index 0000000000..b0bf2a7ea5 --- /dev/null +++ b/src/tests/_internal/core/models/test_routers.py @@ -0,0 +1,30 @@ +import pytest + +from dstack._internal.core.models.routers import ReplicaGroupRouterConfig, RouterType + + +class TestReplicaGroupRouterConfig: + def test_accepts_legacy_smg_value(self): + router = ReplicaGroupRouterConfig.model_validate({"type": "sglang"}) + assert router.type == RouterType.SMG + + def test_accepts_legacy_smg_value_from_json(self): + router = ReplicaGroupRouterConfig.model_validate_json('{"type": "sglang"}') + assert router.type == RouterType.SMG + + @pytest.mark.parametrize( + ("router_type", "expected"), + [ + pytest.param(RouterType.SMG, "sglang", id="smg"), + pytest.param(RouterType.DYNAMO, "dynamo", id="dynamo"), + ], + ) + def test_serializes_smg_to_legacy_value_in_json(self, router_type: RouterType, expected: str): + router = ReplicaGroupRouterConfig(type=router_type) + assert router.model_dump(mode="json") == {"type": expected} + assert router.model_dump_json() == f'{{"type":"{expected}"}}' + assert ReplicaGroupRouterConfig.model_validate_json(router.model_dump_json()) == router + + def test_keeps_enum_in_python_dump(self): + router = ReplicaGroupRouterConfig(type=RouterType.SMG) + assert router.model_dump() == {"type": RouterType.SMG} diff --git a/src/tests/_internal/core/models/test_runs.py b/src/tests/_internal/core/models/test_runs.py index 3ec6f8024c..e911a07125 100644 --- a/src/tests/_internal/core/models/test_runs.py +++ b/src/tests/_internal/core/models/test_runs.py @@ -116,7 +116,7 @@ def test_run_get_error_returns_none_for_specific_reasons(): def _service_run_spec_dict(router_type=None, retry=None, top_level_extras=None): """Build a minimal RunSpec dict for a service. - `router_type`: None | "sglang" | "dynamo" — controls whether/how the + `router_type`: None | "smg" | "dynamo" — controls whether/how the second replica group has a router field. `retry`: optional dict passed as `profile.retry`. `top_level_extras`: optional dict merged into the service configuration. @@ -177,12 +177,12 @@ def test_dynamo_router_without_retry_is_accepted(self): # Should not raise: RunSpec.model_validate(spec) - def test_sglang_router_with_retry_is_accepted(self): + def test_smg_router_with_retry_is_accepted(self): spec = _service_run_spec_dict( - router_type="sglang", + router_type="smg", retry={"on_events": ["error"]}, ) - # SGLang services are unaffected by the validator. + # SMG services are unaffected by the validator. RunSpec.model_validate(spec) def test_service_without_router_with_retry_is_accepted(self): diff --git a/src/tests/_internal/server/background/pipeline_tasks/test_running_jobs.py b/src/tests/_internal/server/background/pipeline_tasks/test_running_jobs.py index 6b82e0c3f0..9493cc1104 100644 --- a/src/tests/_internal/server/background/pipeline_tasks/test_running_jobs.py +++ b/src/tests/_internal/server/background/pipeline_tasks/test_running_jobs.py @@ -2968,7 +2968,7 @@ async def test_registers_router_replica_but_not_worker_replica_in_gateway( run_spec=get_run_spec( run_name="test", repo_id=repo.name, - configuration=_router_service_configuration("sglang", gateway="test-gateway"), + configuration=_router_service_configuration("smg", gateway="test-gateway"), ), gateway=gateway, ) @@ -3046,7 +3046,7 @@ async def test_resets_stale_registered_flag_for_non_router_replica( run_name="test", repo_id=repo.name, configuration=_router_service_configuration( - "sglang", + "smg", probes=[ProbeConfig(type="http", url="/health", ready_after=1)], ), ), @@ -3645,7 +3645,7 @@ async def test_non_dynamo_loads_only_own_replica(self, test_db, session: AsyncSe repo = await create_repo(session=session, project_id=project.id) run_spec = get_run_spec( repo_id=repo.name, - configuration=_router_service_configuration("sglang"), + configuration=_router_service_configuration("smg"), ) run = await create_run( session=session, project=project, repo=repo, user=user, run_spec=run_spec @@ -3674,7 +3674,7 @@ async def test_non_dynamo_loads_only_own_replica(self, test_db, session: AsyncSe ) assert {j.replica_num for j in run_model.jobs} == {0} - async def test_sglang_with_replica_ip_refs_loads_all_replicas( + async def test_smg_with_replica_ip_refs_loads_all_replicas( self, test_db, session: AsyncSession ): project = await create_project(session=session) @@ -3683,7 +3683,7 @@ async def test_sglang_with_replica_ip_refs_loads_all_replicas( run_spec = get_run_spec( repo_id=repo.name, configuration=_router_service_configuration( - "sglang", + "smg", router_commands=["echo ${{ groups[0].replicas[0].IP_ADDRESS }}"], ), ) @@ -3714,7 +3714,7 @@ async def test_sglang_with_replica_ip_refs_loads_all_replicas( ) assert {j.replica_num for j in run_model.jobs} == {0, 1} - async def test_sglang_without_replica_ip_refs_loads_only_own_replica( + async def test_smg_without_replica_ip_refs_loads_only_own_replica( self, test_db, session: AsyncSession ): project = await create_project(session=session) @@ -3722,7 +3722,7 @@ async def test_sglang_without_replica_ip_refs_loads_only_own_replica( repo = await create_repo(session=session, project_id=project.id) run_spec = get_run_spec( repo_id=repo.name, - configuration=_router_service_configuration("sglang"), + configuration=_router_service_configuration("smg"), ) run = await create_run( session=session, project=project, repo=repo, user=user, run_spec=run_spec @@ -3751,7 +3751,7 @@ async def test_sglang_without_replica_ip_refs_loads_only_own_replica( ) assert {j.replica_num for j in run_model.jobs} == {1} - async def test_sglang_with_replica_ip_refs_drops_terminated_replicas( + async def test_smg_with_replica_ip_refs_drops_terminated_replicas( self, test_db, session: AsyncSession ): project = await create_project(session=session) @@ -3760,7 +3760,7 @@ async def test_sglang_with_replica_ip_refs_drops_terminated_replicas( run_spec = get_run_spec( repo_id=repo.name, configuration=_router_service_configuration( - "sglang", + "smg", router_commands=["echo ${{ groups[0].replicas[0].IP_ADDRESS }}"], ), ) diff --git a/src/tests/_internal/server/background/pipeline_tasks/test_runs/test_active.py b/src/tests/_internal/server/background/pipeline_tasks/test_runs/test_active.py index 389f70f458..b2c2971725 100644 --- a/src/tests/_internal/server/background/pipeline_tasks/test_runs/test_active.py +++ b/src/tests/_internal/server/background/pipeline_tasks/test_runs/test_active.py @@ -1871,7 +1871,7 @@ def _router_worker_service_configuration() -> ServiceConfiguration: {"name": "worker", "commands": ["echo worker"], "count": 1}, { "name": "router", - "router": {"type": "sglang"}, + "router": {"type": "smg"}, "commands": ["echo router"], "count": 1, }, diff --git a/src/tests/_internal/server/background/pipeline_tasks/test_service_router_worker_sync.py b/src/tests/_internal/server/background/pipeline_tasks/test_service_router_worker_sync.py index a62d50b01d..04cd5cf0b0 100644 --- a/src/tests/_internal/server/background/pipeline_tasks/test_service_router_worker_sync.py +++ b/src/tests/_internal/server/background/pipeline_tasks/test_service_router_worker_sync.py @@ -43,8 +43,8 @@ def _router_service_run_spec(repo_id: str, run_name: str = "test-run"): { "name": "router", "count": 1, - "commands": ["sglang serve"], - "router": {"type": "sglang"}, + "commands": ["smg launch"], + "router": {"type": "smg"}, }, {"name": "worker", "count": 2, "commands": ["worker"]}, ], diff --git a/src/tests/_internal/server/services/jobs/configurators/test_service.py b/src/tests/_internal/server/services/jobs/configurators/test_service.py index 3f0de12890..a13b24701a 100644 --- a/src/tests/_internal/server/services/jobs/configurators/test_service.py +++ b/src/tests/_internal/server/services/jobs/configurators/test_service.py @@ -13,7 +13,7 @@ ) from dstack._internal.core.models.profiles import SpotPolicy from dstack._internal.core.models.resources import Range -from dstack._internal.core.models.routers import ReplicaGroupRouterConfig +from dstack._internal.core.models.routers import ReplicaGroupRouterConfig, RouterType from dstack._internal.core.models.services import OpenAIChatModel from dstack._internal.server.services.docker import ImageConfig from dstack._internal.server.services.jobs.configurators.base import get_default_image @@ -106,7 +106,7 @@ def _router_worker_configuration() -> ServiceConfiguration: ReplicaGroup( name="router", replicas=Range[int](min=1, max=1), - router=ReplicaGroupRouterConfig(type="sglang"), + router=ReplicaGroupRouterConfig(type=RouterType.SMG), ), ReplicaGroup( name="worker", diff --git a/src/tests/_internal/server/services/runs/test_router_worker_sync.py b/src/tests/_internal/server/services/runs/test_router_worker_sync.py index 0bdd03ffa1..7c411fd829 100644 --- a/src/tests/_internal/server/services/runs/test_router_worker_sync.py +++ b/src/tests/_internal/server/services/runs/test_router_worker_sync.py @@ -1122,7 +1122,7 @@ def _service_configuration() -> ServiceConfiguration: "name": "router", "replicas": 1, "commands": ["smg launch"], - "router": {"type": "sglang"}, + "router": {"type": "smg"}, }, {"name": "worker", "replicas": 1, "commands": ["worker"]}, ], diff --git a/src/tests/_internal/server/services/runs/test_spec.py b/src/tests/_internal/server/services/runs/test_spec.py index 419d952201..20ac7bd605 100644 --- a/src/tests/_internal/server/services/runs/test_spec.py +++ b/src/tests/_internal/server/services/runs/test_spec.py @@ -482,15 +482,15 @@ def test_false_for_task_node_refs(self): class TestCheckCanUpdateConfigurationRouterType: - def test_sglang_to_dynamo_router_type_change_is_rejected(self): - current = _run_spec(_service_configuration(router_type="sglang")) + def test_smg_to_dynamo_router_type_change_is_rejected(self): + current = _run_spec(_service_configuration(router_type="smg")) new = _run_spec(_service_configuration(router_type="dynamo")) with pytest.raises(ServerClientError, match="router.type"): check_can_update_run_spec(current, new) - def test_dynamo_to_sglang_router_type_change_is_rejected(self): + def test_dynamo_to_smg_router_type_change_is_rejected(self): current = _run_spec(_service_configuration(router_type="dynamo")) - new = _run_spec(_service_configuration(router_type="sglang")) + new = _run_spec(_service_configuration(router_type="smg")) with pytest.raises(ServerClientError, match="router.type"): check_can_update_run_spec(current, new) @@ -592,10 +592,10 @@ def test_dynamo_worker_commands_change_is_allowed(self): class TestCheckCanUpdateConfigurationNonDynamoUnchanged: - def test_sglang_top_level_image_change_is_allowed(self): - current = _run_spec(_service_configuration(router_type="sglang", image="img:1")) - new = _run_spec(_service_configuration(router_type="sglang", image="img:2")) - # Top-level changes on SGLang services flow through to the existing + def test_smg_top_level_image_change_is_allowed(self): + current = _run_spec(_service_configuration(router_type="smg", image="img:1")) + new = _run_spec(_service_configuration(router_type="smg", image="img:2")) + # Top-level changes on SMG services flow through to the existing # rolling-deployment path; no Dynamo gate fires. check_can_update_run_spec(current, new) @@ -609,8 +609,8 @@ class TestCheckCanUpdateConfigurationFieldAllowlist: """`_check_can_update_configuration` is also called directly with configs only.""" def test_non_dynamo_image_change_passes_configuration_gate(self): - current = _service_configuration(router_type="sglang", image="img:1") - new = _service_configuration(router_type="sglang", image="img:2") + current = _service_configuration(router_type="smg", image="img:1") + new = _service_configuration(router_type="smg", image="img:2") _check_can_update_configuration(current, new, ignore_files=True)