{"record":{"id":"20e3c2ce96473b08","repo":"huggingface/candle","slug":"unexpected-num-channels-in-groupnorm-n-channels","errorCode":null,"errorMessage":"unexpected num-channels in GroupNorm ({n_channels} <> {}","messagePattern":"unexpected num-channels in GroupNorm \\((.+?) <> (.+?)","errorType":"exception","errorClass":"candle::Error","httpStatus":null,"severity":"error","filePath":"candle-nn/src/group_norm.rs","lineNumber":48,"sourceCode":"            weight,\n            bias,\n            eps,\n            num_channels,\n            num_groups,\n        })\n    }\n}\n\nimpl crate::Module for GroupNorm {\n    fn forward(&self, x: &Tensor) -> Result<Tensor> {\n        let x_shape = x.dims();\n        if x_shape.len() <= 2 {\n            candle::bail!(\"input rank for GroupNorm should be at least 3\");\n        }\n        let (b_sz, n_channels) = (x_shape[0], x_shape[1]);\n        let hidden_size = x_shape[2..].iter().product::<usize>() * n_channels / self.num_groups;\n        if n_channels != self.num_channels {\n            candle::bail!(\n                \"unexpected num-channels in GroupNorm ({n_channels} <> {}\",\n                self.num_channels\n            )\n        }\n        let x_dtype = x.dtype();\n        let internal_dtype = match x_dtype {\n            DType::F16 | DType::BF16 => DType::F32,\n            d => d,\n        };\n        let x = x.reshape((b_sz, self.num_groups, hidden_size))?;\n        let x = x.to_dtype(internal_dtype)?;\n        let mean_x = (x.sum_keepdim(2)? / hidden_size as f64)?;\n        let x = x.broadcast_sub(&mean_x)?;\n        let norm_x = (x.sqr()?.sum_keepdim(2)? / hidden_size as f64)?;\n        let x_normed = x.broadcast_div(&(norm_x + self.eps)?.sqrt()?)?;\n        let mut w_dims = vec![1; x_shape.len()];\n        w_dims[1] = n_channels;\n        let weight = self.weight.reshape(w_dims.clone())?;","sourceCodeStart":30,"sourceCodeEnd":66,"githubUrl":"https://github.com/huggingface/candle/blob/d5fee525bfde3273eb7c9b75fd2bc4937be867ca/candle-nn/src/group_norm.rs#L30-L66","documentation":"During forward, GroupNorm checks that the input's channel dimension (dims[1]) matches the num_channels the layer was constructed with. This bail fires when they differ. Note the message itself has a malformed format string (a stray '{}') but the data reported is the actual channel count vs the configured one.","triggerScenarios":"Calling forward on a GroupNorm built for N channels with an input tensor whose second dimension is a different channel count — e.g. GroupNorm configured for 64 channels receiving a tensor with 128 channels.","commonSituations":"Reusing a norm layer across architecture stages with different widths; loading a checkpoint whose channel count differs from the freshly built model; model config edited (width multiplier) without updating the norm layers; wrong tensor passed to forward.","solutions":["Construct GroupNorm with num_channels equal to the input's dim 1 — derive it from the preceding layer's out_channels.","If loading weights, verify the GroupNorm weight shape (weight.dims()[0]) matches the model's channel count for that layer.","Check the tensor order: a transposed tensor (channels last) will present the wrong dim at index 1; permute before forward.","Fix the layer wiring if a GroupNorm instance is shared between two branches with different channel widths; create one per width."],"exampleFix":"// before\nlet norm = GroupNorm::new(w, b, 64, 32, 1e-5)?;\nlet y = norm.forward(&conv_out)?; // conv_out dims [B, 128, H, W]\n// after\nlet c = conv_out.dims()[1];\nlet norm = GroupNorm::new(w, b, c, 32, 1e-5)?;\nlet y = norm.forward(&conv_out)?;","handlingStrategy":"validation","validationCode":"let c_in = x.dims()[1];\nif c_in != norm.num_channels() {\n    return Err(anyhow!(\"input has {c_in} channels, GroupNorm built for {}\", norm.num_channels()));\n}","typeGuard":"fn channels_match(norm: &candle_nn::GroupNorm, x: &Tensor) -> bool {\n    x.rank() >= 2 && x.dims()[1] == norm.num_channels()\n}","tryCatchPattern":"let y = match norm.forward(&x) {\n    Ok(y) => y,\n    Err(e) if e.to_string().contains(\"num-channels\") => {\n        let c = x.dims()[1];\n        let (w, b) = rebuild_norm_weights(c)?; // or permute x if channels-last\n        GroupNorm::new(w, b, c, 32, 1e-5)?.forward(&x)?\n    }\n    Err(e) => return Err(e.into()),\n};","preventionTips":["Build GroupNorm layers in code from the previous layer's channel count, not duplicated constants","When loading checkpoints, verify each GroupNorm weight shape matches the rebuilt layer","If tensors are channels-last, permute to channels-first before norm layers","Never share one GroupNorm instance between branches of different widths"],"tags":["tensor","shape","normalization","rust"],"backgroundTag":"channel-count-mismatch","analyzedSha":"d5fee525bfde3273eb7c9b75fd2bc4937be867ca","analyzedAt":"2026-09-02T00:15:47.023Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-09T06:17:21.866Z"}