invoke-ai/InvokeAI · error · ValueError

Must provide the same number of `only_cross_attention` as `d

Error message

Must provide the same number of `only_cross_attention` as `down_block_types`. `only_cross_attention`: {only_cross_attention}. `down_block_types`: {down_block_types}.

What it means

The same hotfixed ControlNetModel.__init__ validates only_cross_attention: unless it is a single bool applied to all blocks, it must be a tuple/list whose length equals down_block_types. If it is a non-bool sequence of the wrong length, per-block attention routing is undefined, so a ValueError is raised.

Source

Thrown at invokeai/backend/util/hotfixes.py:168

        # If `num_attention_heads` is not defined (which is the case for most models)
        # it will default to `attention_head_dim`. This looks weird upon first reading it and it is.
        # The reason for this behavior is to correct for incorrectly named variables that were introduced
        # when this library was created...
        # The incorrect naming was only discovered much ...
        # later in https://github.com/huggingface/diffusers/issues/2011#issuecomment-1547958131
        # Changing `attention_head_dim` to `num_attention_heads` for 40,000+ configurations is too backwards breaking
        # which is why we correct for the naming here.
        num_attention_heads = num_attention_heads or attention_head_dim

        # Check inputs
        if len(block_out_channels) != len(down_block_types):
            raise ValueError(
                f"Must provide the same number of `block_out_channels` as `down_block_types`. \
                    `block_out_channels`: {block_out_channels}. `down_block_types`: {down_block_types}."
            )

        if not isinstance(only_cross_attention, bool) and len(only_cross_attention) != len(down_block_types):
            raise ValueError(
                f"Must provide the same number of `only_cross_attention` as `down_block_types`. \
                    `only_cross_attention`: {only_cross_attention}. `down_block_types`: {down_block_types}."
            )

        if not isinstance(num_attention_heads, int) and len(num_attention_heads) != len(down_block_types):
            raise ValueError(
                f"Must provide the same number of `num_attention_heads` as `down_block_types`. \
                    `num_attention_heads`: {num_attention_heads}. `down_block_types`: {down_block_types}."
            )

        if isinstance(transformer_layers_per_block, int):
            transformer_layers_per_block = [transformer_layers_per_block] * len(down_block_types)

        # input
        conv_in_kernel = 3
        conv_in_padding = (conv_in_kernel - 1) // 2
        self.conv_in = nn.Conv2d(
            in_channels, block_out_channels[0], kernel_size=conv_in_kernel, padding=conv_in_padding

View on GitHub (pinned to 0b6a024f2f)

Solutions

  1. Pass a single bool (e.g. only_cross_attention=False) if the same value applies to all blocks.
  2. Otherwise provide exactly one boolean per down block: len(only_cross_attention) == len(down_block_types).
  3. Derive it programmatically: only_cross_attention = [False] * len(down_block_types).
  4. Compare against the reference model's config.json and copy the field verbatim.

Example fix

// before
ControlNetModel(
    down_block_types=("CrossAttnDownBlock2D", "CrossAttnDownBlock2D", "DownBlock2D", "DownBlock2D"),
    only_cross_attention=(True, False),
)
// after
ControlNetModel(
    down_block_types=("CrossAttnDownBlock2D", "CrossAttnDownBlock2D", "DownBlock2D", "DownBlock2D"),
    only_cross_attention=(True, False, False, False),
)
Defensive patterns

Strategy: validation

Validate before calling

def validate_only_cross_attention(config: dict) -> None:
    oca = config.get('only_cross_attention', False)
    n = len(config['down_block_types'])
    if not isinstance(oca, bool) and len(oca) != n:
        raise ValueError(
            f'only_cross_attention ({len(oca)}) must match down_block_types ({n})'
        )

validate_only_cross_attention(config_dict)

Type guard

def only_cross_attention_is_valid(config: dict) -> bool:
    oca = config.get('only_cross_attention', False)
    if isinstance(oca, bool):
        return True
    return isinstance(oca, (list, tuple)) and len(oca) == len(config.get('down_block_types', []))

Try / catch

try:
    model = ControlNetModel.from_config(config_dict)
except ValueError as e:
    if 'only_cross_attention' in str(e):
        config_dict['only_cross_attention'] = False  # uniform value applies to all blocks
        model = ControlNetModel.from_config(config_dict)
    else:
        raise

Prevention

When it happens

Trigger: Passing only_cross_attention=(True, False) (length 2) to a 4-block ControlNetModel; passing a list produced by slicing or per-block logic that doesn't match the down_block_types length.

Common situations: Migrating configs between models of different block counts; generating only_cross_attention programmatically from a different-length list; typos in hand-edited config.json where a bool was expanded to a partial list.

Related errors


AI-assisted analysis of invoke-ai/InvokeAI@0b6a024f2f (2026-08-29). Data as JSON: /api/errors/38852a7d24c9bdc4. Report an issue: GitHub.