open-mmlab/mmdetection · error · ValueError

NUM_BRANCHES({num_branches}) != NUM_INCHANNELS({len(in_chann

Error message

NUM_BRANCHES({num_branches}) != NUM_INCHANNELS({len(in_channels)})

What it means

The third HRNet branch check: len(in_channels) must equal num_branches, since in_channels lists the input channel count feeding each branch of the stage. Mismatch raises ValueError naming NUM_INCHANNELS.

Source

Thrown at mmdet/models/backbones/hrnet.py:64

        self.fuse_layers = self._make_fuse_layers()
        self.relu = nn.ReLU(inplace=False)

    def _check_branches(self, num_branches, num_blocks, in_channels,
                        num_channels):
        if num_branches != len(num_blocks):
            error_msg = f'NUM_BRANCHES({num_branches}) ' \
                        f'!= NUM_BLOCKS({len(num_blocks)})'
            raise ValueError(error_msg)

        if num_branches != len(num_channels):
            error_msg = f'NUM_BRANCHES({num_branches}) ' \
                        f'!= NUM_CHANNELS({len(num_channels)})'
            raise ValueError(error_msg)

        if num_branches != len(in_channels):
            error_msg = f'NUM_BRANCHES({num_branches}) ' \
                        f'!= NUM_INCHANNELS({len(in_channels)})'
            raise ValueError(error_msg)

    def _make_one_branch(self,
                         branch_index,
                         block,
                         num_blocks,
                         num_channels,
                         stride=1):
        downsample = None
        if stride != 1 or \
                self.in_channels[branch_index] != \
                num_channels[branch_index] * block.expansion:
            downsample = nn.Sequential(
                build_conv_layer(
                    self.conv_cfg,
                    self.in_channels[branch_index],
                    num_channels[branch_index] * block.expansion,
                    kernel_size=1,
                    stride=stride,

View on GitHub (pinned to cfd5d3a985)

Solutions

  1. Set in_channels of stageN equal (as a list) to num_channels of stage(N-1), and its length equal to num_branches
  2. Cross-check every stage dict: num_branches, num_blocks, num_channels, in_channels lengths must all agree
  3. Start from an official HRNet config and adjust widths consistently across stages

Example fix

# before
stage2=dict(num_branches=2, num_blocks=[4,4], num_channels=[48,96], in_channels=[64])
# after
stage2=dict(num_branches=2, num_blocks=[4,4], num_channels=[48,96], in_channels=[64,128])
Defensive patterns

Strategy: validation

Validate before calling

stages = list(extra.values())
for prev, cur in zip(stages, stages[1:]):
    assert cur['num_branches'] == len(cur['in_channels']) == len(prev['num_channels'])

Type guard

def inchannels_ok(stage: dict) -> bool:
    return stage['num_branches'] == len(stage['in_channels'])

Prevention

When it happens

Trigger: num_branches=4 with in_channels=[32,64,128] (len 3); stage2 in_channels not matching stage1's num_channels output; hand-editing multi-stage extra dicts.

Common situations: Changing an earlier stage's num_channels without updating the next stage's in_channels; building HRNet from scratch instead of from a reference config.

Related errors


AI-assisted analysis of open-mmlab/mmdetection@cfd5d3a985 (2026-08-27). Data as JSON: /api/errors/d1ec99ed3a701f71. Report an issue: GitHub.