diff --git a/.github/filters.yaml b/.github/filters.yaml index 8a6a7395b494..563a33266522 100644 --- a/.github/filters.yaml +++ b/.github/filters.yaml @@ -286,7 +286,11 @@ snapshot_vllm: - 'deploy/operator/internal/dynamo/backend_vllm.go' snapshot_sglang: + - 'components/src/dynamo/sglang/args.py' - 'components/src/dynamo/sglang/snapshot.py' + # The SGLang runtime tag controls the engine version exercised by this suite, + # so engine bumps must trigger it. Deliberately omit broad pyproject.toml. + - 'container/context.yaml' - 'deploy/operator/internal/dynamo/backend_sglang.go' snapshot_trtllm: diff --git a/.github/scripts/test-filters.js b/.github/scripts/test-filters.js index b1ed8f3c05d8..7cf4f66b0dce 100755 --- a/.github/scripts/test-filters.js +++ b/.github/scripts/test-filters.js @@ -280,6 +280,16 @@ const testCases = [ }, desc: 'sglang snapshot.py gates only sglang checkpoint tests' }, + { + file: 'container/context.yaml', + expect: { + snapshot: false, + snapshot_vllm: false, + snapshot_sglang: true, + snapshot_trtllm: false, + }, + desc: 'runtime tag changes gate the SGLang checkpoint tests' + }, { file: 'components/src/dynamo/trtllm/snapshot.py', expect: { diff --git a/components/src/dynamo/sglang/args.py b/components/src/dynamo/sglang/args.py index a3fe2882786e..769c3188e746 100644 --- a/components/src/dynamo/sglang/args.py +++ b/components/src/dynamo/sglang/args.py @@ -549,8 +549,13 @@ async def parse_args(args: list[str]) -> Config: if should_fetch_model(parsed_args, model_path): await fetch_model(model_path) - if is_snapshot_enabled(): + snapshot_enabled = is_snapshot_enabled() + if snapshot_enabled: configure_snapshot_capture_env() + # SGLang reads these raw fields before late resolution, so snapshot mode + # must set them before ServerArgs creation. + parsed_args.enable_memory_saver = True + parsed_args.enable_forward_pass_metrics = False # TODO: sglang downloads the model in `from_cli_args`, which means we had to # fetch_model (download the model) here, in `parse_args`. `parse_args` should not @@ -571,7 +576,11 @@ async def parse_args(args: list[str]) -> Config: parsed_args.enable_memory_saver = True fpm_source = _forward_pass_metrics_source(dynamo_config) - if fpm_source and not getattr(parsed_args, "enable_forward_pass_metrics", False): + if ( + not snapshot_enabled + and fpm_source + and not getattr(parsed_args, "enable_forward_pass_metrics", False) + ): parsed_args.enable_forward_pass_metrics = True logging.info("Enabled forward_pass_metrics from %s", fpm_source) diff --git a/components/src/dynamo/sglang/tests/test_sglang_unit.py b/components/src/dynamo/sglang/tests/test_sglang_unit.py index 1d59854237cd..396a8acf8aa0 100644 --- a/components/src/dynamo/sglang/tests/test_sglang_unit.py +++ b/components/src/dynamo/sglang/tests/test_sglang_unit.py @@ -517,6 +517,77 @@ def resolve(parsed_args): await parse_args(sys.argv[1:]) +@pytest.mark.asyncio +@pytest.mark.parametrize( + ("snapshot_enabled", "expected"), + [ + (False, False), + (True, True), + ], +) +async def test_parse_args_sets_raw_memory_saver_before_resolution( + monkeypatch, mock_sglang_cli, tmp_path, snapshot_enabled, expected +): + monkeypatch.setattr( + "dynamo.sglang.args.configure_snapshot_capture_env", lambda: None + ) + monkeypatch.delenv("DYN_GMS_USE_V1", raising=False) + if snapshot_enabled: + monkeypatch.setenv(SNAPSHOT_CONTROL_DIR_ENV, str(tmp_path)) + monkeypatch.setenv("NCCL_CUMEM_ENABLE", "0") + else: + monkeypatch.delenv(SNAPSHOT_CONTROL_DIR_ENV, raising=False) + server_args = SimpleNamespace( + disaggregation_mode="null", + dllm_algorithm=None, + kv_events_config=None, + get_model_config=lambda: SimpleNamespace(is_multimodal=False), + ) + + def resolve(parsed_args): + # SGLang 0.5.19 copies this raw field unchanged; late resolution does + # not update it before the parent process launches the scheduler. + server_args.enable_memory_saver = parsed_args.enable_memory_saver + return server_args + + monkeypatch.setattr("dynamo.sglang.args.ServerArgs.from_cli_args", resolve) + mock_sglang_cli(model=str(tmp_path)) + + config = await parse_args(sys.argv[1:]) + + assert config.server_args.enable_memory_saver is expected + + +@pytest.mark.asyncio +async def test_parse_args_disables_raw_fpm_for_snapshot_with_metric_port( + monkeypatch, mock_sglang_cli, tmp_path +): + monkeypatch.setattr( + "dynamo.sglang.args.configure_snapshot_capture_env", lambda: None + ) + monkeypatch.delenv("DYN_GMS_USE_V1", raising=False) + monkeypatch.setenv(SNAPSHOT_CONTROL_DIR_ENV, str(tmp_path)) + monkeypatch.setenv("DYN_FORWARDPASS_METRIC_PORT", "23456") + server_args = SimpleNamespace( + disaggregation_mode="null", + dllm_algorithm=None, + enable_forward_pass_metrics=False, + kv_events_config=None, + get_model_config=lambda: SimpleNamespace(is_multimodal=False), + ) + + def resolve(parsed_args): + assert parsed_args.enable_forward_pass_metrics is False + return server_args + + monkeypatch.setattr("dynamo.sglang.args.ServerArgs.from_cli_args", resolve) + mock_sglang_cli(model=str(tmp_path)) + + config = await parse_args(sys.argv[1:]) + + assert config.server_args.enable_forward_pass_metrics is False + + def test_compat_filters_async_generate_kwargs_for_older_engines(): class OldEngine: async def async_generate(self, input_ids=None, sampling_params=None):