sgl-project/sglang · error · ValueError

ControlNet down and mid residuals must be provided together.

Error message

ControlNet down and mid residuals must be provided together.

What it means

Raised by the Hunyuan3D SD2.1 UNet forward when exactly one of down_block_additional_residuals and mid_block_additional_residual is provided. ControlNet outputs come as a set of down-block residuals plus one mid-block residual; supplying only one side would misalign the residual additions.

Source

Thrown at python/sglang/multimodal_gen/runtime/models/dits/stable_diffusion.py:833

        class_labels: torch.Tensor | None = None,
        timestep_cond: torch.Tensor | None = None,
        attention_mask: torch.Tensor | None = None,
        cross_attention_kwargs: dict[str, Any] | None = None,
        added_cond_kwargs: dict[str, torch.Tensor] | None = None,
        down_block_additional_residuals: tuple[torch.Tensor, ...] | None = None,
        mid_block_additional_residual: torch.Tensor | None = None,
        down_intrablock_additional_residuals: tuple[torch.Tensor, ...] | None = None,
        encoder_attention_mask: torch.Tensor | None = None,
        return_dict: bool = True,
    ) -> StableDiffusionUNetOutput | tuple[torch.Tensor]:
        if timestep_cond is not None or added_cond_kwargs is not None:
            raise ValueError("The Hunyuan3D SD2.1 UNet has no added conditioning.")
        if down_intrablock_additional_residuals is not None:
            raise ValueError("T2I adapter residuals are not supported by Hunyuan3D.")
        if (down_block_additional_residuals is None) != (
            mid_block_additional_residual is None
        ):
            raise ValueError(
                "ControlNet down and mid residuals must be provided together."
            )

        attention_mask = self._attention_bias(attention_mask, sample.dtype)
        encoder_attention_mask = self._attention_bias(
            encoder_attention_mask, sample.dtype
        )
        if self.config.center_input_sample:
            sample = 2 * sample - 1.0

        time_embedding = self._time_embedding(sample, timestep)
        if self.class_embedding is not None:
            if class_labels is None:
                raise ValueError("class_labels are required by this UNet.")
            time_embedding = time_embedding + self.class_embedding(class_labels).to(
                sample.dtype
            )

View on GitHub (pinned to 0132848349)

Solutions

  1. Pass both down_block_additional_residuals and mid_block_additional_residual (or neither)
  2. Check the ControlNet forward returns and feed [:-1] to down and [-1] to mid

Example fix

# before
unet(x, t, encoder_hidden_states=ctx, down_block_additional_residuals=res[:-1])
# after
unet(x, t, encoder_hidden_states=ctx, down_block_additional_residuals=res[:-1], mid_block_additional_residual=res[-1])
Defensive patterns

Strategy: type-guard

Validate before calling

assert (down_res is None) == (mid_res is None), "ControlNet residuals must come in pairs"

Type guard

def controlnet_residuals_paired(down, mid) -> bool:
    return (down is None) == (mid is None)

Prevention

When it happens

Trigger: Passing down_block_additional_residuals without mid_block_additional_residual, or vice versa.

Common situations: Running a ControlNet whose down/residual list was sliced incorrectly, or forgetting to forward controlnet_res[0] and controlnet_res[-1] together.

Related errors


AI-assisted analysis of sgl-project/sglang@0132848349 (2026-08-28). Data as JSON: /api/errors/164a221e0a18aabf. Report an issue: GitHub.