lllyasviel/ControlNet · error · ValueError

provide num_res_blocks either as an int (globally constant)

Error message

provide num_res_blocks either as an int (globally constant) or as a list/tuple (per-level) with the same length as channel_mult

What it means

In ControlNet's LatentDiffusion (cldm.py), num_res_blocks may be an int (same count at every resolution level) or a per-level list whose length must equal channel_mult. This ValueError enforces that 1:1 correspondence.

Source

Thrown at cldm/cldm.py:106

        if num_heads_upsample == -1:
            num_heads_upsample = num_heads

        if num_heads == -1:
            assert num_head_channels != -1, 'Either num_heads or num_head_channels has to be set'

        if num_head_channels == -1:
            assert num_heads != -1, 'Either num_heads or num_head_channels has to be set'

        self.dims = dims
        self.image_size = image_size
        self.in_channels = in_channels
        self.model_channels = model_channels
        if isinstance(num_res_blocks, int):
            self.num_res_blocks = len(channel_mult) * [num_res_blocks]
        else:
            if len(num_res_blocks) != len(channel_mult):
                raise ValueError("provide num_res_blocks either as an int (globally constant) or "
                                 "as a list/tuple (per-level) with the same length as channel_mult")
            self.num_res_blocks = num_res_blocks
        if disable_self_attentions is not None:
            # should be a list of booleans, indicating whether to disable self-attention in TransformerBlocks or not
            assert len(disable_self_attentions) == len(channel_mult)
        if num_attention_blocks is not None:
            assert len(num_attention_blocks) == len(self.num_res_blocks)
            assert all(map(lambda i: self.num_res_blocks[i] >= num_attention_blocks[i], range(len(num_attention_blocks))))
            print(f"Constructor of UNetModel received num_attention_blocks={num_attention_blocks}. "
                  f"This option has LESS priority than attention_resolutions {attention_resolutions}, "
                  f"i.e., in cases where num_attention_blocks[i] > 0 but 2**i not in attention_resolutions, "
                  f"attention will still not be set.")

        self.attention_resolutions = attention_resolutions
        self.dropout = dropout
        self.channel_mult = channel_mult
        self.conv_resample = conv_resample
        self.use_checkpoint = use_checkpoint

View on GitHub (pinned to ed85cd1e25)

Solutions

  1. Match lengths: num_res_blocks=[2,2,2,2] for channel_mult of length 4
  2. Or pass a single int (e.g. num_res_blocks=2) for a constant count
  3. Recount levels after any channel_mult edit

Example fix

# before
channel_mult=(1,2,4,4), num_res_blocks=[2,2,2]
# after
channel_mult=(1,2,4,4), num_res_blocks=[2,2,2,2]
Defensive patterns

Strategy: validation

Validate before calling

if not isinstance(num_res_blocks, int):
    assert len(num_res_blocks) == len(channel_mult), 'num_res_blocks must match channel_mult length'

Type guard

def num_res_blocks_ok(nrb, channel_mult) -> bool:
    return isinstance(nrb, int) or (isinstance(nrb, (list, tuple)) and len(nrb) == len(channel_mult))

Prevention

When it happens

Trigger: Instantiating the ControlUNetModel with num_res_blocks as a list shorter/longer than channel_mult, e.g. channel_mult=(1,2,4,4) with num_res_blocks=[2,2,2].

Common situations: Editing ControlNet configs (train-img2img, control sd v15 yaml) and changing channel_mult without updating num_res_blocks; copying configs between models with different depth.

Related errors


AI-assisted analysis of lllyasviel/ControlNet@ed85cd1e25 (2026-08-27). Data as JSON: /api/errors/703eb54d54d58822. Report an issue: GitHub.