tracel-ai/burn · error

ndarray gather_nd requires contiguous data

Error message

ndarray gather_nd requires contiguous data

What it means

gather_nd collects slices of `data` at positions given by `indices`. The implementation flattens `data` via as_slice() for fast indexing, which returns None for non-contiguous arrays, so the expect() panics. The library assumes gather input is stored contiguously in memory.

Source

Thrown at crates/burn-ndarray/src/ops/base.rs:412

    ) -> SharedArray<E> {
        let data_shape: Vec<usize> = data.shape().to_vec();
        let idx_shape: Vec<usize> = indices.shape().to_vec();
        let m = idx_shape.len();
        let k = idx_shape[m - 1];

        // Number of index tuples
        let num_indices: usize = idx_shape[..m - 1].iter().product();
        // Size of each output slice
        let slice_size: usize = data_shape[k..].iter().product();

        // Output shape: idx_shape[..m-1] ++ data_shape[k..]
        let mut out_shape_vec: Vec<usize> = idx_shape[..m - 1].to_vec();
        out_shape_vec.extend_from_slice(&data_shape[k..]);
        let out_total = num_indices * slice_size;

        let data_flat = data
            .as_slice()
            .expect("ndarray gather_nd requires contiguous data");

        let idx_flat = indices
            .as_slice()
            .expect("ndarray gather_nd requires contiguous indices");

        let strides: Vec<usize> = {
            let mut s = vec![0usize; k];
            if k > 0 {
                s[k - 1] = slice_size;
                for i in (0..k - 1).rev() {
                    s[i] = s[i + 1] * data_shape[i + 1];
                }
            }
            s
        };

        let mut output_vec: Vec<E> = vec![0.elem::<E>(); out_total];

View on GitHub (pinned to d16f7ba2ed)

Solutions

  1. Call .contiguous() (or .into_owned()) on the data tensor before gather
  2. Materialize views earlier in the pipeline: avoid gather immediately after transpose/slice ops
  3. If the panic originates inside a burn op, ensure ops call try_into_owned_nocopy()/into_owned() before as_slice()

Example fix

// before
let table = embeddings.slice([0..vocab]).permute([1, 0]);
let picked = table.gather(indices);
// after
let table = embeddings.slice([0..vocab]).permute([1, 0]).contiguous();
let picked = table.gather(indices);
Defensive patterns

Strategy: validation

Validate before calling

fn contiguous_check<E: burn_ndarray::FloatElement>(t: &Tensor<NdArray<E>, D>) -> Tensor<NdArray<E>, D> {
    t.clone().float_into_contiguous() // materialize before gather
}
// let out = contiguous_check(&data).gather(indices);

Prevention

When it happens

Trigger: Calling Tensor::gather (gather_nd) on the ndarray backend where `data` is a non-contiguous view - result of slicing, transposing/permuting, broadcasting, or other zero-copy view ops - making ArrayD::as_slice() return None.

Common situations: Gathering from a transposed activation tensor in a model; gathering rows from a sliced embedding table; passing a broadcasted tensor to gather in ONNX-imported graphs (ONNX GatherND after a broadcast).

Related errors


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