huggingface/candle · error · candle::Error

GroupNorm: num_groups ({num_groups}) must divide num_channel

Error message

GroupNorm: num_groups ({num_groups}) must divide num_channels ({num_channels})

What it means

GroupNorm::new validates the layer configuration: the channel count must be evenly divisible by the number of groups, because GroupNorm splits channels into groups before normalizing. This bail fires at construction time when num_channels is not a multiple of num_groups.

Source

Thrown at candle-nn/src/group_norm.rs:25

#[derive(Clone, Debug)]
pub struct GroupNorm {
    weight: Tensor,
    bias: Tensor,
    eps: f64,
    num_channels: usize,
    num_groups: usize,
}

impl GroupNorm {
    pub fn new(
        weight: Tensor,
        bias: Tensor,
        num_channels: usize,
        num_groups: usize,
        eps: f64,
    ) -> Result<Self> {
        if !num_channels.is_multiple_of(num_groups) {
            candle::bail!(
                "GroupNorm: num_groups ({num_groups}) must divide num_channels ({num_channels})"
            )
        }
        Ok(Self {
            weight,
            bias,
            eps,
            num_channels,
            num_groups,
        })
    }
}

impl crate::Module for GroupNorm {
    fn forward(&self, x: &Tensor) -> Result<Tensor> {
        let x_shape = x.dims();
        if x_shape.len() <= 2 {
            candle::bail!("input rank for GroupNorm should be at least 3");

View on GitHub (pinned to d5fee525bf)

Solutions

  1. Pick a num_groups that divides num_channels (e.g. for 48 channels use 2, 3, 4, 6, 8, 12, 16, 24 or 48).
  2. Common convention: num_groups = 32 (as in BigGAN/ADaPT-style nets) only works when channels % 32 == 0; otherwise fall back to fewer groups or LayerNorm (groups == 1).
  3. Compute groups programmatically: let num_groups = gcd(num_channels, desired_groups) or clamp to largest divisor <= 32.
  4. If channels come from a checkpoint, print/inspect the weight shape and derive num_channels from weight.dims()[0].

Example fix

// before
let norm = GroupNorm::new(w, b, 48, 32, 1e-5)?; // 48 % 32 != 0
// after
let num_groups = 48.min(32); let num_groups = (1..=num_groups).rev().find(|g| 48 % g == 0).unwrap();
let norm = GroupNorm::new(w, b, 48, num_groups, 1e-5)?;
Defensive patterns

Strategy: validation

Validate before calling

fn check_group_norm(channels: usize, groups: usize) -> Result<(), String> {
    if channels.is_multiple_of(groups) { Ok(()) } else {
        Err(format!("channels {channels} not divisible by groups {groups}"))
    }
}

Type guard

fn valid_group_norm_config(num_channels: usize, num_groups: usize) -> bool {
    num_channels.is_multiple_of(num_groups) && num_groups >= 1
}

Try / catch

match GroupNorm::new(w.clone(), b.clone(), channels, groups, 1e-5) {
    Ok(gn) => gn,
    Err(e) => {
        let groups = (1..=32).rev().find(|g| channels % g == 0).unwrap_or(1);
        GroupNorm::new(w, b, channels, groups, 1e-5)?
    }
}

Prevention

When it happens

Trigger: Calling candle_nn::GroupNorm::new(weight, bias, num_channels, num_groups, eps) where num_channels % num_groups != 0 (e.g. 48 channels with 5 groups).

Common situations: Copying a config from a paper whose architecture used a different channel count; typo in group count; adapting a pretrained checkpoint whose conv layers have channel counts incompatible with your chosen groups; generic configs where channels come from a previous layer that changed.

Related errors


AI-assisted analysis of huggingface/candle@d5fee525bf (2026-09-02). Data as JSON: /api/errors/2074ec3d1669de2a. Report an issue: GitHub.