From febbb0195c5e9109a6e345e8a5d199075bc9e659 Mon Sep 17 00:00:00 2001 From: Jvst Me Date: Wed, 22 Apr 2026 16:13:42 +0200 Subject: [PATCH] Add CLI deprecation warnings about gateway routers In `dstack ps`, `dstack gateway list`, and `dstack apply` (for runs and gateways). --- docs/docs/concepts/services.md | 2 ++ src/dstack/_internal/cli/commands/gateway.py | 15 +++++++++++++ src/dstack/_internal/cli/commands/ps.py | 21 +++++++++++++++++++ .../cli/services/configurators/gateway.py | 10 +++++++++ .../cli/services/configurators/run.py | 8 +++++++ 5 files changed, 56 insertions(+) diff --git a/docs/docs/concepts/services.md b/docs/docs/concepts/services.md index fd0d2a2dc2..338dcb3426 100644 --- a/docs/docs/concepts/services.md +++ b/docs/docs/concepts/services.md @@ -373,6 +373,8 @@ Setting the minimum number of replicas to `0` allows the service to scale down t ### PD disaggregation + + Since 0.20.17, `dstack` supports serving a model using PD disaggregation. To use it, configure three replica groups: one for a router (for example, [SGLang Model Gateway](https://docs.sglang.io/advanced_features/sgl_model_gateway.html)), one for prefill workers, and one for decode workers. > Currently, Prefill-Decode disaggregation is supported only for SGLang. diff --git a/src/dstack/_internal/cli/commands/gateway.py b/src/dstack/_internal/cli/commands/gateway.py index be7e6138a1..9455f3d5fc 100644 --- a/src/dstack/_internal/cli/commands/gateway.py +++ b/src/dstack/_internal/cli/commands/gateway.py @@ -17,6 +17,7 @@ print_gateways_table, ) from dstack._internal.core.errors import CLIError, ResourceNotExistsError +from dstack._internal.core.models.gateways import GatewayStatus from dstack._internal.utils.json_utils import pydantic_orjson_dumps_with_indent from dstack._internal.utils.logging import get_logger @@ -108,6 +109,20 @@ def _list(self, args: argparse.Namespace): raise CLIError("JSON output is not supported together with --watch") gateways = self.api.client.gateways.list(self.api.project) + deprecated_router_gateways = [ + g.name + for g in gateways + if g.status != GatewayStatus.FAILED and g.configuration.router is not None + ] + if deprecated_router_gateways and args.format != "json": + logger.warning( + "Specifying `router` in gateway configurations is deprecated" + " and will be disallowed in a future release." + " Please migrate to replica-based routers:" + " https://dstack.ai/docs/concepts/services/#pd-disaggregation" + " (affected gateways: %s)", + ", ".join(deprecated_router_gateways), + ) if not args.watch: if args.format == "json": print_gateways_json(gateways, project=self.api.project) diff --git a/src/dstack/_internal/cli/commands/ps.py b/src/dstack/_internal/cli/commands/ps.py index c94b5410f3..e254793a61 100644 --- a/src/dstack/_internal/cli/commands/ps.py +++ b/src/dstack/_internal/cli/commands/ps.py @@ -11,6 +11,10 @@ console, ) from dstack._internal.core.errors import CLIError +from dstack._internal.core.models.configurations import ServiceConfiguration +from dstack._internal.utils.logging import get_logger + +logger = get_logger(__name__) class PsCommand(APIBaseCommand): @@ -64,6 +68,23 @@ def _command(self, args: argparse.Namespace): raise CLIError("JSON output is not supported together with --watch") runs = self.api.runs.list(all=args.all, limit=args.last) + deprecated_router_runs = [ + run._run.run_spec.run_name + for run in runs + if not run.status.is_finished() + and isinstance(run._run.run_spec.configuration, ServiceConfiguration) + and run._run.run_spec.configuration.router is not None + and run._run.run_spec.run_name is not None + ] + if deprecated_router_runs and args.format != "json": + logger.warning( + "Specifying `router` in service configurations is deprecated" + " and will be disallowed in a future release." + " Please migrate to replica-based routers:" + " https://dstack.ai/docs/concepts/services/#pd-disaggregation" + " (affected runs: %s)", + ", ".join(deprecated_router_runs), + ) if not args.watch: if args.format == "json": run_utils.print_runs_json(self.api.project, runs) diff --git a/src/dstack/_internal/cli/services/configurators/gateway.py b/src/dstack/_internal/cli/services/configurators/gateway.py index 43d4460a17..1ddee68606 100644 --- a/src/dstack/_internal/cli/services/configurators/gateway.py +++ b/src/dstack/_internal/cli/services/configurators/gateway.py @@ -22,8 +22,11 @@ ) from dstack._internal.core.services.diff import diff_models from dstack._internal.utils.common import local_time +from dstack._internal.utils.logging import get_logger from dstack.api._public import Client +logger = get_logger(__name__) + class GatewayConfigurator(BaseApplyConfigurator[GatewayConfiguration]): TYPE = ApplyConfigurationType.GATEWAY @@ -40,6 +43,13 @@ def apply_configuration( configuration=conf, configuration_path=configuration_path, ) + if spec.configuration.router is not None: + logger.warning( + "Specifying `router` in gateway configurations is deprecated" + " and will be disallowed in a future release." + " Please migrate to replica-based routers:" + " https://dstack.ai/docs/concepts/services/#pd-disaggregation" + ) with console.status("Getting apply plan..."): plan = _get_plan(api=self.api, spec=spec) _print_plan_header(plan) diff --git a/src/dstack/_internal/cli/services/configurators/run.py b/src/dstack/_internal/cli/services/configurators/run.py index 98de46ed46..2ca8ee89ae 100644 --- a/src/dstack/_internal/cli/services/configurators/run.py +++ b/src/dstack/_internal/cli/services/configurators/run.py @@ -101,6 +101,14 @@ def apply_configuration( if conf.working_dir is not None and not is_absolute_posix_path(conf.working_dir): raise ConfigurationError("working_dir must be absolute") + if isinstance(conf, ServiceConfiguration) and conf.router is not None: + logger.warning( + "Specifying `router` in service configurations is deprecated" + " and will be disallowed in a future release." + " Please migrate to replica-based routers:" + " https://dstack.ai/docs/concepts/services/#pd-disaggregation" + ) + repo = self.get_repo(conf, configuration_path, configurator_args) if repo is None: repo = init_default_virtual_repo(api=self.api)