invoke-ai/InvokeAI · error · ValueError
Must provide the same number of `block_out_channels` as `dow
Error message
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}. What it means
The hotfixed ControlNetModel.__init__ in invokeai/backend/util/hotfixes.py requires block_out_channels and down_block_types to be equal-length tuples, mirroring diffusers' UNet/ControlNet config validation. Each down_block_type (e.g. 'CrossAttnDownBlock2D', 'DownBlock2D') needs a matching channel count. Mismatched lengths mean the block stack cannot be constructed, so a ValueError is raised at model instantiation.
Source
Thrown at invokeai/backend/util/hotfixes.py:162
conditioning_embedding_out_channels: Optional[Tuple[int]] = (16, 32, 96, 256),
global_pool_conditions: bool = False,
addition_embed_type_num_heads=64,
):
super().__init__()
# 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)View on GitHub (pinned to 0b6a024f2f)
Solutions
- Make len(block_out_channels) equal len(down_block_types) — add or remove entries so every down block has a channel count.
- Load the original model's config.json and use its exact block_out_channels/down_block_types rather than editing by hand.
- Build the config programmatically, e.g. derive block_out_channels from down_block_types length.
- Re-download the model config if the file was truncated or corrupted.
Example fix
// before
ControlNetModel(
down_block_types=("CrossAttnDownBlock2D", "CrossAttnDownBlock2D", "DownBlock2D", "DownBlock2D"),
block_out_channels=(320, 640, 1280),
)
// after
ControlNetModel(
down_block_types=("CrossAttnDownBlock2D", "CrossAttnDownBlock2D", "DownBlock2D", "DownBlock2D"),
block_out_channels=(320, 640, 1280, 1280),
) Defensive patterns
Strategy: validation
Validate before calling
def validate_controlnet_config(config: dict) -> None:
d = len(config['down_block_types'])
o = len(config['block_out_channels'])
if d != o:
raise ValueError(
f'block_out_channels ({o}) must match down_block_types ({d})'
)
validate_controlnet_config(config_dict)
ControlNetModel.from_config(config_dict) Type guard
def has_matching_block_lengths(config: dict) -> bool:
n = len(config.get('down_block_types', []))
return (
isinstance(config.get('block_out_channels'), (list, tuple))
and len(config['block_out_channels']) == n
) Try / catch
try:
model = ControlNetModel.from_config(config_dict)
except ValueError as e:
if 'block_out_channels' in str(e) and 'down_block_types' in str(e):
n = len(config_dict['down_block_types'])
config_dict['block_out_channels'] = (
list(config_dict['block_out_channels']) + [1280] * (n - len(config_dict['block_out_channels']))
)[:n]
model = ControlNetModel.from_config(config_dict)
else:
raise Prevention
- Never hand-edit block_out_channels without updating down_block_types (and vice versa).
- Load configs from the model's original config.json instead of retyping them.
- Write a config sanity check that runs before ControlNetModel instantiation in tests.
- When extending a model depth-first, extend both lists together in a single helper function.
When it happens
Trigger: Instantiating ControlNetModel (or ControlNetModel.from_config) with block_out_channels=(320,640,1280) but down_block_types of length 4 (or vice versa), typically from a hand-edited config.json or a programmatically built config dict.
Common situations: Hand-writing a ControlNet config for a custom architecture; copying config fields from one model into another with a different depth; omitting entries when extending down_block_types; a corrupted/partially downloaded config.json for a model.
Related errors
- Must provide the same number of `only_cross_attention` as `d
- Must provide the same number of `num_attention_heads` as `do
- {self.__class__} has the config param `addition_embed_type`
- `encoder_hid_dim` has to be defined when `encoder_hid_dim_ty
- encoder_hid_dim_type: {encoder_hid_dim_type} must be None, '
AI-assisted analysis of invoke-ai/InvokeAI@0b6a024f2f (2026-08-29).
Data as JSON: /api/errors/c82933bf04370b1d.
Report an issue: GitHub.