From 8cb79308c68d8ada6ea07855811ff533c6900965 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harenome=20=E5=AE=89=E4=BB=81=20Ranaivoarivony-Razanajato?= <676776+harenome@users.noreply.github.com> Date: Sat, 1 Aug 2026 05:19:21 +0200 Subject: [PATCH] diffusion: Fix silent override of explicit `guidance_scale=0` (#4999) Signed-off-by: Harenome Ranaivoarivony-Razanajato --- tests/diffusion/test_diffusion_request.py | 37 +++++++++++++++++++ .../models/flux/pipeline_flux_kontext.py | 4 +- .../models/glm_image/pipeline_glm_image.py | 2 +- .../pipeline_soulx_singer_base.py | 2 +- vllm_omni/diffusion/request.py | 10 ++--- vllm_omni/inputs/data.py | 2 +- 6 files changed, 48 insertions(+), 9 deletions(-) diff --git a/tests/diffusion/test_diffusion_request.py b/tests/diffusion/test_diffusion_request.py index ffaab3c53..259492c1e 100644 --- a/tests/diffusion/test_diffusion_request.py +++ b/tests/diffusion/test_diffusion_request.py @@ -72,3 +72,40 @@ def test_tp_seed_same_across_ranks_and_varies_across_requests(): # Seeds must vary across requests (non-determinism preserved). assert len(set(seeds)) == n_requests, f"Expected {n_requests} unique seeds but got {len(set(seeds))}: {seeds}" + + +def _request_with_guidance(**sampling_kwargs) -> OmniDiffusionRequest: + return OmniDiffusionRequest( + prompt={"prompt": "a cup of coffee on a table"}, + sampling_params=OmniDiffusionSamplingParams(num_inference_steps=1, **sampling_kwargs), + request_id="request-test", + ) + + +def test_omitted_guidance_scale_is_unset_and_resolved_to_one(): + # No guidance_scale passed: the caller did not set it, so it must NOT count as + # "provided" (each pipeline applies its own default), and __post_init__ resolves the + # None "unset" sentinel to a concrete float so no consumer ever observes None. + req = _request_with_guidance() + + assert req.sampling_params.guidance_scale_provided is False + assert req.sampling_params.guidance_scale == 1.0 + + +def test_explicit_zero_guidance_scale_is_honored(): + # Regression (#4998): an explicit guidance_scale=0 (disable CFG) must be honored, + # not mistaken for "unset" and overridden by the pipeline's model default. This is + # the sentinel collision that the None default + ``is not None`` check fixes. + req = _request_with_guidance(guidance_scale=0.0) + + assert req.sampling_params.guidance_scale_provided is True + assert req.sampling_params.guidance_scale == 0.0 + + +@pytest.mark.parametrize("value", [0.999, 1.0, 5.0]) +def test_explicit_nonzero_guidance_scale_is_honored(value): + # Any explicitly-set value (truthy or not) is honored as provided and preserved. + req = _request_with_guidance(guidance_scale=value) + + assert req.sampling_params.guidance_scale_provided is True + assert req.sampling_params.guidance_scale == value diff --git a/vllm_omni/diffusion/models/flux/pipeline_flux_kontext.py b/vllm_omni/diffusion/models/flux/pipeline_flux_kontext.py index 5aff911c6..7edc4751a 100644 --- a/vllm_omni/diffusion/models/flux/pipeline_flux_kontext.py +++ b/vllm_omni/diffusion/models/flux/pipeline_flux_kontext.py @@ -640,7 +640,9 @@ class FluxKontextPipeline( width = req.sampling_params.width or width num_inference_steps = req.sampling_params.num_inference_steps or num_inference_steps sigmas = req.sampling_params.sigmas or sigmas - guidance_scale = req.sampling_params.guidance_scale or guidance_scale + guidance_scale = ( + req.sampling_params.guidance_scale if req.sampling_params.guidance_scale_provided else guidance_scale + ) num_images_per_prompt = ( req.sampling_params.num_outputs_per_prompt if req.sampling_params.num_outputs_per_prompt > 0 diff --git a/vllm_omni/diffusion/models/glm_image/pipeline_glm_image.py b/vllm_omni/diffusion/models/glm_image/pipeline_glm_image.py index 9f3d29400..6ceb5959b 100644 --- a/vllm_omni/diffusion/models/glm_image/pipeline_glm_image.py +++ b/vllm_omni/diffusion/models/glm_image/pipeline_glm_image.py @@ -733,7 +733,7 @@ class GlmImagePipeline(nn.Module, DiffusionPipelineProfilerMixin, SupportsCompon ) width = ar_width or req.sampling_params.width or img_width or self.default_sample_size * self.vae_scale_factor num_inference_steps = req.sampling_params.num_inference_steps or 50 - guidance_scale = req.sampling_params.guidance_scale or 1.5 + guidance_scale = req.sampling_params.guidance_scale if req.sampling_params.guidance_scale_provided else 1.5 # Ensure dimensions are multiples of vae_scale_factor * patch_size multiple_of = self.vae_scale_factor * self._patch_size diff --git a/vllm_omni/diffusion/models/soulx_singer/pipeline_soulx_singer_base.py b/vllm_omni/diffusion/models/soulx_singer/pipeline_soulx_singer_base.py index a48b5236d..0961e6916 100644 --- a/vllm_omni/diffusion/models/soulx_singer/pipeline_soulx_singer_base.py +++ b/vllm_omni/diffusion/models/soulx_singer/pipeline_soulx_singer_base.py @@ -353,7 +353,7 @@ class FlowMatchingAudioPipeline( sampling_params.extra_args = extra_args num_inference_steps = sampling_params.num_inference_steps or 32 - guidance_scale = sampling_params.guidance_scale or 3.0 + guidance_scale = sampling_params.guidance_scale if sampling_params.guidance_scale_provided else 3.0 generator = self._resolve_diffusion_generator(sampling_params) with self._stage_timer("consume_payload"): diff --git a/vllm_omni/diffusion/request.py b/vllm_omni/diffusion/request.py index 0edb30d23..9ea879212 100644 --- a/vllm_omni/diffusion/request.py +++ b/vllm_omni/diffusion/request.py @@ -42,11 +42,11 @@ class OmniDiffusionRequest: self.sampling_params.seed = random.randint(0, 2**31 - 1) # Detect whether user explicitly provided guidance_scale. - # The sentinel default is 0.0 (false-like); any truthy value means - # the caller set it intentionally. We must resolve this BEFORE - # auto-filling guidance_scale_2, otherwise the sentinel leaks into - # guidance_scale_2. - if self.sampling_params.guidance_scale: + # The unset sentinel is None (see OmniDiffusionSamplingParams); any value + # the caller set -- including an explicit 0.0 (disable CFG) -- must be + # honored. Resolve before auto-filling guidance_scale_2, else the sentinel + # leaks into guidance_scale_2. + if self.sampling_params.guidance_scale is not None: self.sampling_params.guidance_scale_provided = True else: self.sampling_params.guidance_scale = 1.0 diff --git a/vllm_omni/inputs/data.py b/vllm_omni/inputs/data.py index f0344c994..5e6b5275e 100644 --- a/vllm_omni/inputs/data.py +++ b/vllm_omni/inputs/data.py @@ -263,7 +263,7 @@ class OmniDiffusionSamplingParams: # Scheduler parameters – ``None`` means "not explicitly set by the caller"; # each pipeline's ``forward()`` decides its own model-specific default. num_inference_steps: int | None = None - guidance_scale: float = 0.0 + guidance_scale: float | None = None guidance_scale_provided: bool = False guidance_scale_2: float | None = None guidance_scale_2_provided: bool = False