From 22641129129928202519e98bd524fb16b19bff45 Mon Sep 17 00:00:00 2001 From: Devam0311 Date: Tue, 1 Sep 2026 15:59:11 +0530 Subject: [PATCH] Fix controlnet_pooled_projections tensor handling in SD3 ControlNet pipelines Both SD3 ControlNet pipelines resolved the public `controlnet_pooled_projections` argument with `controlnet_pooled_projections or pooled_prompt_embeds`. `or` evaluates the truthiness of the left operand, so passing any real pooled projection raises `RuntimeError: Boolean value of Tensor with more than one value is ambiguous`, making the argument unusable. In the text-to-image pipeline, replace the `or` with an explicit `None` check. In the inpainting pipeline the `else` branch is only reachable when the value is not `None`, so it could only ever re-assign the value to itself or raise; drop it. Add a regression test that passes a tensor with a ControlNet configured with `force_zeros_for_pooled_projection=False`, which is the path that raised. Ref #13611 (Issue 3), #9686 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01XBYeq5vB4DNZDEaqUsroKR --- .../pipeline_stable_diffusion_3_controlnet.py | 4 +-- ...table_diffusion_3_controlnet_inpainting.py | 2 -- .../controlnet_sd3/test_controlnet_sd3.py | 30 +++++++++++++++++++ 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/src/diffusers/pipelines/controlnet_sd3/pipeline_stable_diffusion_3_controlnet.py b/src/diffusers/pipelines/controlnet_sd3/pipeline_stable_diffusion_3_controlnet.py index 4530a424adb4..52ac7627c3c2 100644 --- a/src/diffusers/pipelines/controlnet_sd3/pipeline_stable_diffusion_3_controlnet.py +++ b/src/diffusers/pipelines/controlnet_sd3/pipeline_stable_diffusion_3_controlnet.py @@ -1137,8 +1137,8 @@ def __call__( if controlnet_config.force_zeros_for_pooled_projection: # instantx sd3 controlnet used zero pooled projection controlnet_pooled_projections = torch.zeros_like(pooled_prompt_embeds) - else: - controlnet_pooled_projections = controlnet_pooled_projections or pooled_prompt_embeds + elif controlnet_pooled_projections is None: + controlnet_pooled_projections = pooled_prompt_embeds if controlnet_config.joint_attention_dim is not None: controlnet_encoder_hidden_states = prompt_embeds diff --git a/src/diffusers/pipelines/controlnet_sd3/pipeline_stable_diffusion_3_controlnet_inpainting.py b/src/diffusers/pipelines/controlnet_sd3/pipeline_stable_diffusion_3_controlnet_inpainting.py index d2890d55811c..2929d12c9691 100644 --- a/src/diffusers/pipelines/controlnet_sd3/pipeline_stable_diffusion_3_controlnet_inpainting.py +++ b/src/diffusers/pipelines/controlnet_sd3/pipeline_stable_diffusion_3_controlnet_inpainting.py @@ -1269,8 +1269,6 @@ def __call__( if controlnet_pooled_projections is None: controlnet_pooled_projections = torch.zeros_like(pooled_prompt_embeds) - else: - controlnet_pooled_projections = controlnet_pooled_projections or pooled_prompt_embeds # 4. Prepare timesteps if XLA_AVAILABLE: diff --git a/tests/pipelines/controlnet_sd3/test_controlnet_sd3.py b/tests/pipelines/controlnet_sd3/test_controlnet_sd3.py index 5fa6770dffdf..02200f4da87f 100644 --- a/tests/pipelines/controlnet_sd3/test_controlnet_sd3.py +++ b/tests/pipelines/controlnet_sd3/test_controlnet_sd3.py @@ -212,6 +212,36 @@ def test_controlnet_sd35(self): # fmt: on self._run_and_check_slice(components, expected_slice) + def test_controlnet_pooled_projections_accepts_tensor(self): + # Regression: when the ControlNet does not force zeroed pooled projections, the pipeline + # resolved this argument with `controlnet_pooled_projections or pooled_prompt_embeds`. + # `or` takes the truthiness of the tensor, which raises "Boolean value of Tensor with more + # than one value is ambiguous" for any real pooled projection. See huggingface/diffusers#9686. + components = self.get_dummy_components() + torch.manual_seed(0) + components["controlnet"] = SD3ControlNetModel( + sample_size=32, + patch_size=1, + in_channels=8, + num_layers=1, + attention_head_dim=8, + num_attention_heads=4, + joint_attention_dim=32, + caption_projection_dim=32, + pooled_projection_dim=64, + out_channels=8, + qk_norm="rms_norm", + force_zeros_for_pooled_projection=False, + ) + pipe = self.get_pipeline(**components).to(torch_device, dtype=torch.float32) + + inputs = self.get_dummy_inputs() + inputs["controlnet_pooled_projections"] = torch.zeros((1, 64), device=torch_device) + + image = pipe(**inputs).images + + assert image.shape == (1, *self.output_shape) + class TestStableDiffusion3ControlNetPipelineMemory(StableDiffusion3ControlNetPipelineTesterConfig, MemoryTesterMixin): """Memory optimization tests (CPU offload, group offload, layerwise casting) for the SD3 ControlNet pipeline."""