tracel-ai/burn · error

expand: cannot expand dimension

Error message

expand: cannot expand dimension {} from {} to {}

What it means

The flex backend's expand (broadcast) op can only grow a dimension whose source size is 1, or keep a dimension whose size already equals the target. If a source dimension has size >1 and differs from the requested target size, no valid stride can describe the result, so the backend panics. This is a shape/broadcastability violation caught at layout-construction time.

Solutions

  1. Check the source tensor's shape with tensor.shape() and confirm every dimension is either equal to the target or of size 1
  2. Fix the target shape so it only prepends new dimensions or grows size-1 dimensions (proper broadcasting)
  3. If you need arbitrary resizing, use an actual resize op (interpolate/upsample) or slice+concat, not expand
  4. Verify the input tensor wasn't produced by an upstream op with an unexpected shape (log shapes before expand)

Example fix

// before: src [4, 2], target [4, 3] -> panic
let out = x.expand([4, 3]);
// after: broadcast only size-1 or matching dims
let x = x.unsqueeze::<2>(); // e.g. [4, 2, 1]
let out = x.expand([4, 2, 3]); // dim of size 1 grows to 3
Defensive patterns

Strategy: validation

Validate before calling

fn assert_broadcastable(src: &[usize], target: &[usize]) -> Result<(), String> {
    if target.len() < src.len() {
        return Err(format!("target rank {} < source rank {}", target.len(), src.len()));
    }
    let skip = target.len() - src.len();
    for (i, (&s, &t)) in src.iter().zip(&target[skip..]).enumerate() {
        if s != t && s != 1 {
            return Err(format!("dim {} (size {}) cannot expand to {}", i, s, t));
        }
    }
    Ok(())
}

Type guard

fn can_expand(src_dim: usize, target_dim: usize) -> bool {
    src_dim == target_dim || src_dim == 1
}

Prevention

When it happens

Trigger: Calling tensor.expand/expand_to (or any broadcasting binary op that routes through expand) with a target shape where an existing dimension of size N>1 is expected to become M, with M != N. E.g. expanding [4, 1] to [4, 3] is fine, but [4, 2] to [4, 3] panics.

Common situations: Manually computed target shapes that don't match the actual input shape (off-by-one or stale constant); mixing tensors whose shapes changed upstream; NumPy-style broadcasting assumptions applied in reverse (expand cannot shrink or resize); feeding a tensor of batch size B into a layer hard-coded for a different batch.

Related errors


AI-assisted analysis of tracel-ai/burn@d16f7ba2ed (2026-09-05). Data as JSON: /api/errors/5f2b613d8c5f2cd6. Report an issue: GitHub.

Appendix: source

Thrown at crates/burn-flex/src/ops/expand.rs:121

    for i in 0..target_ndims {
        let target_dim = target_shape[i];

        if i < dim_diff {
            // New dimension prepended - must be broadcastable from size 1
            new_strides.push(0);
        } else {
            let src_idx = i - dim_diff;
            let src_dim = src_dims[src_idx];
            let src_stride = src_strides[src_idx];

            if src_dim == target_dim {
                // Same size - keep stride
                new_strides.push(src_stride);
            } else if src_dim == 1 {
                // Broadcast dimension - stride becomes 0
                new_strides.push(0);
            } else {
                panic!(
                    "expand: cannot expand dimension {} from {} to {}",
                    i, src_dim, target_dim
                );
            }
        }
    }

    let new_layout = Layout::new(target_shape, new_strides, start_offset);
    FlexTensor::from_arc(tensor.data_arc(), new_layout, dtype)
}

// Tests kept here probe flex-internal expand behavior: stride metadata
// (stride 0 on broadcast dims, preservation of negative strides on
// flipped inputs, preserved start-offset on narrowed inputs) and the
// flex-only `broadcast_binary` helper. Public-API expand coverage for
// transpose/flip/narrow variants lives in
// crates/burn-backend-tests/tests/tensor/{float,int,bool}/ops/expand.rs
// so it runs on every backend.

View on GitHub (pinned to d16f7ba2ed)