huggingface/candle · error

Metal strided to_dtype {left:?} {right:?} not implemented

Error message

Metal strided to_dtype {left:?} {right:?} not implemented

What it means

The strided (non-contiguous) Metal to_dtype path has its own kernel table; a cast between dtype pairs missing from that table triggers this bail. Strided layouts arise from slicing, transposing, or other view ops, so the same cast may work contiguous but fail strided.

Source

Thrown at candle-core/src/metal_backend/mod.rs:650

                (DType::I64, DType::BF16) => "cast_i64_bf16_strided",
                (DType::I64, DType::F16) => "cast_i64_f16_strided",
                (DType::I64, DType::U32) => "cast_i64_u32_strided",
                (DType::I64, DType::U8) => "cast_i64_u8_strided",

                (DType::U32, DType::BF16) => "cast_u32_bf16_strided",
                (DType::U32, DType::F16) => "cast_u32_f16_strided",
                (DType::U32, DType::F32) => "cast_u32_f32_strided",
                (DType::U32, DType::I64) => "cast_u32_i64_strided",
                (DType::U32, DType::U8) => "cast_u32_u8_strided",

                (DType::U8, DType::BF16) => "cast_u8_bf16_strided",
                (DType::U8, DType::F16) => "cast_u8_f16_strided",
                (DType::U8, DType::F32) => "cast_u8_f32_strided",
                (DType::U8, DType::I64) => "cast_u8_i64_strided",
                (DType::U8, DType::U32) => "cast_u8_u32_strided",

                (left, right) => {
                    crate::bail!("Metal strided to_dtype {left:?} {right:?} not implemented")
                }
            };
            candle_metal_kernels::call_cast_strided(
                &device.device,
                &encoder,
                &device.kernels,
                kernel_name,
                layout.dims(),
                src,
                layout.stride(),
                &buffer,
            )
            .map_err(MetalError::from)?;
        }
        Ok(Self::new(buffer, device.clone(), el_count, dtype))
    }

    fn unary_impl<B: UnaryOpT>(&self, layout: &Layout) -> Result<Self> {

View on GitHub (pinned to d5fee525bf)

Solutions

  1. Make the tensor contiguous first: tensor.contiguous()?.to_dtype(dtype)?
  2. Cast before the op that creates the strided layout
  3. Cast on CPU and move back to Metal
  4. Use a dtype pair present in the strided cast table

Example fix

// before
let t = tensor.transpose(0, 1)?.to_dtype(DType::F16)?;
// after
let t = tensor.transpose(0, 1)?.contiguous()?.to_dtype(DType::F16)?;
Defensive patterns

Strategy: fallback

Validate before calling

if !tensor.is_contiguous() {
    tensor = tensor.contiguous()?; // use contiguous cast path
}
tensor = tensor.to_dtype(target)?;

Try / catch

match strided_tensor.to_dtype(target) {
    Ok(t) => t,
    Err(e) if e.to_string().contains("strided to_dtype") => {
        strided_tensor.contiguous()?.to_dtype(target)?
    }
    Err(e) => return Err(e.into()),
}

Prevention

When it happens

Trigger: Calling to_dtype on a non-contiguous Metal tensor (e.g. after transpose/slice/narrow) with a dtype pair absent from the strided cast table.

Common situations: Casting a transposed activation map to fp16 on Metal; converting slices of embedding tables; working with permuted tensors in inference graphs.

Related errors


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