diff --git a/python/sglang/multimodal_gen/runtime/server_args.py b/python/sglang/multimodal_gen/runtime/server_args.py index 7d934910e..159fcea46 100644 --- a/python/sglang/multimodal_gen/runtime/server_args.py +++ b/python/sglang/multimodal_gen/runtime/server_args.py @@ -130,8 +130,8 @@ class ServerArgs: dp_size: int = 1 # number of gpu in a dp group dp_degree: int = 1 - # cfg parallel - enable_cfg_parallel: bool = False + # cfg parallel (None = auto-decide based on num_gpus) + enable_cfg_parallel: Optional[bool] = None hsdp_replicate_dim: int = 1 hsdp_shard_dim: Optional[int] = None @@ -428,6 +428,7 @@ class ServerArgs: sp_unspecified = self.sp_degree is None ulysses_unspecified = self.ulysses_degree is None ring_unspecified = self.ring_degree is None + cfg_unspecified = self.enable_cfg_parallel is None if self.hsdp_shard_dim is None: self.hsdp_shard_dim = self.num_gpus @@ -435,6 +436,30 @@ class ServerArgs: if self.tp_size is None: self.tp_size = 1 + # Auto-enable CFG parallel when user hasn't set any parallelism flags + # and there are enough GPUs. Only auto-enable for models whose default + # SamplingParams use classifier-free guidance (negative_prompt is not None), + # because non-CFG models (e.g. FLUX) crash when CFG parallel splits ranks. + if cfg_unspecified: + cfg_group_size = self.dp_size * self.tp_size * 2 + if ( + self.num_gpus >= 2 + and self.num_gpus % cfg_group_size == 0 + and sp_unspecified + and ulysses_unspecified + and ring_unspecified + and self._model_default_uses_cfg() + ): + self.enable_cfg_parallel = True + logger.info( + "Automatically enabled CFG parallel for %d GPUs. " + "Use --sp-degree / --ulysses-degree to use sequence " + "parallelism instead.", + self.num_gpus, + ) + else: + self.enable_cfg_parallel = False + # adjust sp_degree: allocate all remaining GPUs after TP and DP if self.sp_degree is None: num_gpus_per_group = self.dp_size * self.tp_size @@ -466,6 +491,28 @@ class ServerArgs: self.ring_degree = 1 logger.debug(f"Ring degree not set, using default value {self.ring_degree}") + def _model_default_uses_cfg(self) -> bool: + """ + Check whether the model uses classifier-free guidance by default. + + CFG is active when *both* ``negative_prompt is not None`` and ``guidance_scale > 1``. + """ + from sglang.multimodal_gen.registry import get_model_info + + model_info = get_model_info(self.model_path, self.backend, self.model_id) + if model_info is None: + return False + default_params = model_info.sampling_param_cls() + + # for ltx2.3, cfg-parallel performs worse than ulysses-sp + is_ltx = "ltx" in type(default_params).__name__.lower() + if is_ltx: + return False + return ( + getattr(default_params, "negative_prompt", None) is not None + and getattr(default_params, "guidance_scale", 0) > 1.0 + ) + @staticmethod def _is_ltx23_model_path(model_path: str | None) -> bool: if not model_path: @@ -679,8 +726,8 @@ class ServerArgs: parser.add_argument( "--enable-cfg-parallel", action="store_true", - default=ServerArgs.enable_cfg_parallel, - help="Enable cfg parallel.", + default=None, + help="Enable cfg parallel. Auto-enabled when num_gpus >= 2 and no SP flags are set.", ) parser.add_argument( "--data-parallel-size", diff --git a/python/sglang/multimodal_gen/test/server/perf_baselines.json b/python/sglang/multimodal_gen/test/server/perf_baselines.json index 3011ddbd7..8d29991ba 100644 --- a/python/sglang/multimodal_gen/test/server/perf_baselines.json +++ b/python/sglang/multimodal_gen/test/server/perf_baselines.json @@ -1655,7 +1655,8 @@ }, "expected_e2e_ms": 149864.99, "expected_avg_denoise_ms": 3608.89, - "expected_median_denoise_ms": 2400.38 + "expected_median_denoise_ms": 2400.38, + "estimated_full_test_time_s": 253.9 }, "wan2_1_t2v_14b_2gpu": { "stages_ms": { diff --git a/python/sglang/multimodal_gen/test/server/test_server_common.py b/python/sglang/multimodal_gen/test/server/test_server_common.py index df4f3f3fc..956231c5c 100644 --- a/python/sglang/multimodal_gen/test/server/test_server_common.py +++ b/python/sglang/multimodal_gen/test/server/test_server_common.py @@ -154,7 +154,9 @@ def diffusion_server(case: DiffusionTestCase) -> ServerContext: # pipeline class. This avoids hard failures when a model needs a # newer diffusers release than what is currently installed in CI. msg = str(exc) - if "not found in diffusers" in msg or "has no attribute" in msg: + if "not found in diffusers" in msg or ( + "has no attribute" in msg and "diffusers" in msg.lower() + ): pytest.skip( f"Skipping {case.id}: required diffusers pipeline class " f"is not available in the installed version. " diff --git a/python/sglang/multimodal_gen/test/server/testcase_configs.py b/python/sglang/multimodal_gen/test/server/testcase_configs.py index 0ca40edca..b132547e9 100644 --- a/python/sglang/multimodal_gen/test/server/testcase_configs.py +++ b/python/sglang/multimodal_gen/test/server/testcase_configs.py @@ -808,6 +808,7 @@ TWO_GPU_CASES_A = [ modality="video", custom_validator="video", num_gpus=2, + extras=["--ulysses-degree=2"], ), T2V_sampling_params, ), @@ -821,6 +822,7 @@ TWO_GPU_CASES_A = [ modality="video", custom_validator="video", num_gpus=2, + extras=["--ulysses-degree=2"], ), DiffusionSamplingParams( prompt=T2V_PROMPT, @@ -912,7 +914,7 @@ TWO_GPU_CASES_A = [ model_path="Lightricks/LTX-2", modality="video", num_gpus=2, - extras=["--pipeline-class-name LTX2TwoStagePipeline"], + extras=["--pipeline-class-name LTX2TwoStagePipeline", "--ulysses-degree=2"], ), T2V_sampling_params, ), @@ -936,6 +938,7 @@ TWO_GPU_CASES_B = [ modality="video", custom_validator="video", num_gpus=2, + extras=["--ulysses-degree=2"], ), TI2V_sampling_params, ), @@ -958,6 +961,7 @@ TWO_GPU_CASES_B = [ custom_validator="video", num_gpus=2, lora_path="starsfriday/Wan2.1-Divine-Power-LoRA", + extras=["--ulysses-degree=2"], ), TI2V_sampling_params, run_lora_basic_api_check=True, @@ -969,6 +973,7 @@ TWO_GPU_CASES_B = [ modality="video", custom_validator="video", num_gpus=2, + extras=["--ulysses-degree=2"], ), TI2V_sampling_params, ),