use crate::error::Result;
use crate::utils::guard::Guarded;
use crate::utils::IntoOption;
use crate::{Array, Stream};
use mlx_internal_macros::generate_macro;
#[allow(clippy::too_many_arguments)]
pub fn conv_general<'a>(
array: impl AsRef<Array>,
weight: impl AsRef<Array>,
strides: impl IntoOption<&'a [i32]>,
padding: impl IntoOption<&'a [i32]>,
kernel_dilation: impl IntoOption<&'a [i32]>,
input_dilation: impl IntoOption<&'a [i32]>,
groups: impl Into<Option<i32>>,
flip: impl Into<Option<bool>>,
) -> Result<Array> {
let stream = Stream::thread_local_or_default();
let strides = strides.into_option().unwrap_or(&[1]);
let padding = padding.into_option().unwrap_or(&[0]);
let kernel_dilation = kernel_dilation.into_option().unwrap_or(&[1]);
let input_dilation = input_dilation.into_option().unwrap_or(&[1]);
let groups = groups.into().unwrap_or(1);
let flip = flip.into().unwrap_or(false);
Array::try_from_op(|res| unsafe {
mlx_sys::mlx_conv_general(
res,
array.as_ref().as_ptr(),
weight.as_ref().as_ptr(),
strides.as_ptr(),
strides.len(),
padding.as_ptr(),
padding.len(),
padding.as_ptr(),
padding.len(),
kernel_dilation.as_ptr(),
kernel_dilation.len(),
input_dilation.as_ptr(),
input_dilation.len(),
groups,
flip,
stream.as_ref().as_ptr(),
)
})
}
#[generate_macro(customize(forwarding_shim = true))]
#[allow(clippy::too_many_arguments)]
#[deprecated(
since = "0.26.0",
note = "use `with_stream` or `with_device` around `conv_general`"
)]
pub fn conv_general_device<'a>(
array: impl AsRef<Array>,
weight: impl AsRef<Array>,
#[optional] strides: impl IntoOption<&'a [i32]>,
#[optional] padding: impl IntoOption<&'a [i32]>,
#[optional] kernel_dilation: impl IntoOption<&'a [i32]>,
#[optional] input_dilation: impl IntoOption<&'a [i32]>,
#[optional] groups: impl Into<Option<i32>>,
#[optional] flip: impl Into<Option<bool>>,
#[optional] stream: impl AsRef<Stream>,
) -> Result<Array> {
crate::with_stream(stream.as_ref(), || {
conv_general(
array,
weight,
strides,
padding,
kernel_dilation,
input_dilation,
groups,
flip,
)
})
}
pub fn conv1d(
array: impl AsRef<Array>,
weight: impl AsRef<Array>,
stride: impl Into<Option<i32>>,
padding: impl Into<Option<i32>>,
dilation: impl Into<Option<i32>>,
groups: impl Into<Option<i32>>,
) -> Result<Array> {
let stream = Stream::thread_local_or_default();
let stride = stride.into().unwrap_or(1);
let padding = padding.into().unwrap_or(0);
let dilation = dilation.into().unwrap_or(1);
let groups = groups.into().unwrap_or(1);
Array::try_from_op(|res| unsafe {
mlx_sys::mlx_conv1d(
res,
array.as_ref().as_ptr(),
weight.as_ref().as_ptr(),
stride,
padding,
dilation,
groups,
stream.as_ref().as_ptr(),
)
})
}
#[generate_macro(customize(forwarding_shim = true))]
#[deprecated(
since = "0.26.0",
note = "use `with_stream` or `with_device` around `conv1d`"
)]
pub fn conv1d_device(
array: impl AsRef<Array>,
weight: impl AsRef<Array>,
#[optional] stride: impl Into<Option<i32>>,
#[optional] padding: impl Into<Option<i32>>,
#[optional] dilation: impl Into<Option<i32>>,
#[optional] groups: impl Into<Option<i32>>,
#[optional] stream: impl AsRef<Stream>,
) -> Result<Array> {
crate::with_stream(stream.as_ref(), || {
conv1d(array, weight, stride, padding, dilation, groups)
})
}
pub fn conv2d(
array: impl AsRef<Array>,
weight: impl AsRef<Array>,
stride: impl Into<Option<(i32, i32)>>,
padding: impl Into<Option<(i32, i32)>>,
dilation: impl Into<Option<(i32, i32)>>,
groups: impl Into<Option<i32>>,
) -> Result<Array> {
let stream = Stream::thread_local_or_default();
let stride = stride.into().unwrap_or((1, 1));
let padding = padding.into().unwrap_or((0, 0));
let dilation = dilation.into().unwrap_or((1, 1));
let groups = groups.into().unwrap_or(1);
Array::try_from_op(|res| unsafe {
mlx_sys::mlx_conv2d(
res,
array.as_ref().as_ptr(),
weight.as_ref().as_ptr(),
stride.0,
stride.1,
padding.0,
padding.1,
dilation.0,
dilation.1,
groups,
stream.as_ref().as_ptr(),
)
})
}
#[generate_macro(customize(forwarding_shim = true))]
#[deprecated(
since = "0.26.0",
note = "use `with_stream` or `with_device` around `conv2d`"
)]
pub fn conv2d_device(
array: impl AsRef<Array>,
weight: impl AsRef<Array>,
#[optional] stride: impl Into<Option<(i32, i32)>>,
#[optional] padding: impl Into<Option<(i32, i32)>>,
#[optional] dilation: impl Into<Option<(i32, i32)>>,
#[optional] groups: impl Into<Option<i32>>,
#[optional] stream: impl AsRef<Stream>,
) -> Result<Array> {
crate::with_stream(stream.as_ref(), || {
conv2d(array, weight, stride, padding, dilation, groups)
})
}
pub fn conv3d(
array: impl AsRef<Array>,
weight: impl AsRef<Array>,
stride: impl Into<Option<(i32, i32, i32)>>,
padding: impl Into<Option<(i32, i32, i32)>>,
dilation: impl Into<Option<(i32, i32, i32)>>,
groups: impl Into<Option<i32>>,
) -> Result<Array> {
let stream = Stream::thread_local_or_default();
let stride = stride.into().unwrap_or((1, 1, 1));
let padding = padding.into().unwrap_or((0, 0, 0));
let dilation = dilation.into().unwrap_or((1, 1, 1));
let groups = groups.into().unwrap_or(1);
Array::try_from_op(|res| unsafe {
mlx_sys::mlx_conv3d(
res,
array.as_ref().as_ptr(),
weight.as_ref().as_ptr(),
stride.0,
stride.1,
stride.2,
padding.0,
padding.1,
padding.2,
dilation.0,
dilation.1,
dilation.2,
groups,
stream.as_ref().as_ptr(),
)
})
}
#[generate_macro(customize(forwarding_shim = true))]
#[deprecated(
since = "0.26.0",
note = "use `with_stream` or `with_device` around `conv3d`"
)]
pub fn conv3d_device(
array: impl AsRef<Array>,
weight: impl AsRef<Array>,
#[optional] stride: impl Into<Option<(i32, i32, i32)>>,
#[optional] padding: impl Into<Option<(i32, i32, i32)>>,
#[optional] dilation: impl Into<Option<(i32, i32, i32)>>,
#[optional] groups: impl Into<Option<i32>>,
#[optional] stream: impl AsRef<Stream>,
) -> Result<Array> {
crate::with_stream(stream.as_ref(), || {
conv3d(array, weight, stride, padding, dilation, groups)
})
}
#[allow(clippy::too_many_arguments)]
pub fn conv_transpose1d(
array: impl AsRef<Array>,
weight: impl AsRef<Array>,
stride: impl Into<Option<i32>>,
padding: impl Into<Option<i32>>,
dilation: impl Into<Option<i32>>,
output_padding: impl Into<Option<i32>>,
groups: impl Into<Option<i32>>,
) -> Result<Array> {
let stream = Stream::thread_local_or_default();
let stride = stride.into().unwrap_or(1);
let padding = padding.into().unwrap_or(0);
let dilation = dilation.into().unwrap_or(1);
let output_padding = output_padding.into().unwrap_or(0);
let groups = groups.into().unwrap_or(1);
Array::try_from_op(|res| unsafe {
mlx_sys::mlx_conv_transpose1d(
res,
array.as_ref().as_ptr(),
weight.as_ref().as_ptr(),
stride,
padding,
dilation,
output_padding,
groups,
stream.as_ref().as_ptr(),
)
})
}
#[allow(clippy::too_many_arguments)]
#[generate_macro(customize(forwarding_shim = true))]
#[deprecated(
since = "0.26.0",
note = "use `with_stream` or `with_device` around `conv_transpose1d`"
)]
pub fn conv_transpose1d_device(
array: impl AsRef<Array>,
weight: impl AsRef<Array>,
#[optional] stride: impl Into<Option<i32>>,
#[optional] padding: impl Into<Option<i32>>,
#[optional] dilation: impl Into<Option<i32>>,
#[optional] output_padding: impl Into<Option<i32>>,
#[optional] groups: impl Into<Option<i32>>,
#[optional] stream: impl AsRef<Stream>,
) -> Result<Array> {
crate::with_stream(stream.as_ref(), || {
conv_transpose1d(
array,
weight,
stride,
padding,
dilation,
output_padding,
groups,
)
})
}
#[allow(clippy::too_many_arguments)]
pub fn conv_transpose2d(
array: impl AsRef<Array>,
weight: impl AsRef<Array>,
stride: impl Into<Option<(i32, i32)>>,
padding: impl Into<Option<(i32, i32)>>,
dilation: impl Into<Option<(i32, i32)>>,
output_padding: impl Into<Option<(i32, i32)>>,
groups: impl Into<Option<i32>>,
) -> Result<Array> {
let stream = Stream::thread_local_or_default();
let stride = stride.into().unwrap_or((1, 1));
let padding = padding.into().unwrap_or((0, 0));
let dilation = dilation.into().unwrap_or((1, 1));
let output_padding = output_padding.into().unwrap_or((0, 0));
let groups = groups.into().unwrap_or(1);
Array::try_from_op(|res| unsafe {
mlx_sys::mlx_conv_transpose2d(
res,
array.as_ref().as_ptr(),
weight.as_ref().as_ptr(),
stride.0,
stride.1,
padding.0,
padding.1,
dilation.0,
dilation.1,
output_padding.0,
output_padding.1,
groups,
stream.as_ref().as_ptr(),
)
})
}
#[allow(clippy::too_many_arguments)]
#[generate_macro(customize(forwarding_shim = true))]
#[deprecated(
since = "0.26.0",
note = "use `with_stream` or `with_device` around `conv_transpose2d`"
)]
pub fn conv_transpose2d_device(
array: impl AsRef<Array>,
weight: impl AsRef<Array>,
#[optional] stride: impl Into<Option<(i32, i32)>>,
#[optional] padding: impl Into<Option<(i32, i32)>>,
#[optional] dilation: impl Into<Option<(i32, i32)>>,
#[optional] output_padding: impl Into<Option<(i32, i32)>>,
#[optional] groups: impl Into<Option<i32>>,
#[optional] stream: impl AsRef<Stream>,
) -> Result<Array> {
crate::with_stream(stream.as_ref(), || {
conv_transpose2d(
array,
weight,
stride,
padding,
dilation,
output_padding,
groups,
)
})
}
#[allow(clippy::too_many_arguments)]
pub fn conv_transpose3d(
array: impl AsRef<Array>,
weight: impl AsRef<Array>,
stride: impl Into<Option<(i32, i32, i32)>>,
padding: impl Into<Option<(i32, i32, i32)>>,
dilation: impl Into<Option<(i32, i32, i32)>>,
output_padding: impl Into<Option<(i32, i32, i32)>>,
groups: impl Into<Option<i32>>,
) -> Result<Array> {
let stream = Stream::thread_local_or_default();
let stride = stride.into().unwrap_or((1, 1, 1));
let padding = padding.into().unwrap_or((0, 0, 0));
let dilation = dilation.into().unwrap_or((1, 1, 1));
let output_padding = output_padding.into().unwrap_or((0, 0, 0));
let groups = groups.into().unwrap_or(1);
Array::try_from_op(|res| unsafe {
mlx_sys::mlx_conv_transpose3d(
res,
array.as_ref().as_ptr(),
weight.as_ref().as_ptr(),
stride.0,
stride.1,
stride.2,
padding.0,
padding.1,
padding.2,
dilation.0,
dilation.1,
dilation.2,
output_padding.0,
output_padding.1,
output_padding.2,
groups,
stream.as_ref().as_ptr(),
)
})
}
#[allow(clippy::too_many_arguments)]
#[generate_macro(customize(forwarding_shim = true))]
#[deprecated(
since = "0.26.0",
note = "use `with_stream` or `with_device` around `conv_transpose3d`"
)]
pub fn conv_transpose3d_device(
array: impl AsRef<Array>,
weight: impl AsRef<Array>,
#[optional] stride: impl Into<Option<(i32, i32, i32)>>,
#[optional] padding: impl Into<Option<(i32, i32, i32)>>,
#[optional] dilation: impl Into<Option<(i32, i32, i32)>>,
#[optional] output_padding: impl Into<Option<(i32, i32, i32)>>,
#[optional] groups: impl Into<Option<i32>>,
#[optional] stream: impl AsRef<Stream>,
) -> Result<Array> {
crate::with_stream(stream.as_ref(), || {
conv_transpose3d(
array,
weight,
stride,
padding,
dilation,
output_padding,
groups,
)
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::{assert_array_eq, tolerances};
use pretty_assertions::assert_eq;
#[test]
fn test_conv1d_complex_device() {
let input_data = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0];
let input_array = Array::from_slice(&input_data, &[1, 5, 2]);
let weight_data = [0.5, 0.0, -0.5, 1.0, 0.0, 1.5, 2.0, 0.0, -2.0, 1.5, 0.0, 1.0];
let weight_array = Array::from_slice(&weight_data, &[2, 3, 2]);
let result = conv1d(
&input_array,
&weight_array,
Some(1), Some(0), Some(1), Some(1), )
.unwrap();
let expected_output = [12.0, 8.0, 17.0, 13.0, 22.0, 18.0];
assert_eq!(result.shape(), &[1, 3, 2]);
assert_eq!(result.as_slice::<f32>(), &expected_output);
}
#[test]
fn test_conv_transpose1d() {
let input = Array::from_slice(&[1.0, 2.0, 3.0], &[1, 3, 1]);
let weights = Array::from_slice(&[1.0, 0.5], &[1, 2, 1]);
let result = conv_transpose1d(
&input,
&weights,
Some(1), Some(0), Some(1), None, Some(1), )
.unwrap();
let expected = [1.0, 2.5, 4.0, 1.5];
assert_eq!(result.shape(), &[1, 4, 1]);
assert_eq!(result.as_slice::<f32>(), &expected);
}
#[test]
fn test_conv2d() {
let input_data = [1.0, 2.0, 3.0, 4.0];
let input_shape = [1, 2, 2, 1]; let input_array = Array::from_slice(&input_data, &input_shape);
let weight_data = [1.0, 0.0, 0.0, 1.0];
let weight_shape = [1, 2, 2, 1]; let weight_array = Array::from_slice(&weight_data, &weight_shape);
let result = conv2d(
&input_array,
&weight_array,
Some((1, 1)), Some((0, 0)), Some((1, 1)), Some(1), )
.unwrap();
let expected_output = 1.0 * 1.0 + 2.0 * 0.0 + 3.0 * 0.0 + 4.0 * 1.0; assert_eq!(result.as_slice::<f32>(), &[expected_output]);
}
#[test]
fn test_conv_transpose2d() {
let input = Array::from_slice(&[1.0, 2.0, 3.0, 4.0], &[1, 2, 2, 1]);
let weights = Array::from_slice(&[1.0, 0.0, 0.0, 1.0], &[1, 2, 2, 1]);
let result = conv_transpose2d(
&input,
&weights,
Some((1, 1)), Some((0, 0)), Some((1, 1)), None, Some(1), )
.unwrap();
let expected = [1.0, 2.0, 0.0, 3.0, 5.0, 2.0, 0.0, 3.0, 4.0];
assert_eq!(result.shape(), &[1, 3, 3, 1]);
assert_eq!(result.as_slice::<f32>(), &expected);
}
#[test]
fn test_conv3d() {
let input_data = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
let input_shape = [1, 2, 2, 2, 1]; let input_array = Array::from_slice(&input_data, &input_shape);
let weight_data = [1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0];
let weight_shape = [1, 2, 2, 2, 1]; let weight_array = Array::from_slice(&weight_data, &weight_shape);
let result = conv3d(
&input_array,
&weight_array,
Some((1, 1, 1)), Some((0, 0, 0)), Some((1, 1, 1)), Some(1), )
.unwrap();
let expected_output = 1.0 * 1.0
+ 2.0 * 0.0
+ 3.0 * 0.0
+ 4.0 * 1.0
+ 5.0 * 0.0
+ 6.0 * 1.0
+ 7.0 * 1.0
+ 8.0 * 0.0; assert_array_eq(
result,
Array::from_slice(&[expected_output], &[1, 1, 1, 1, 1]),
tolerances::EXACT.rtol,
tolerances::EXACT.atol,
);
}
#[test]
fn test_conv_transpose3d() {
let input = Array::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0], &[1, 2, 2, 2, 1]);
let weights =
Array::from_slice(&[1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0], &[1, 2, 2, 2, 1]);
let result = conv_transpose3d(
&input,
&weights,
Some((1, 1, 1)), Some((0, 0, 0)), Some((1, 1, 1)), None, Some(1), )
.unwrap();
assert_eq!(result.shape(), &[1, 3, 3, 3, 1]);
}
#[test]
fn test_conv_wrong_dimensions() {
let input_data = [1.0, 2.0, 3.0, 4.0];
let input_shape = [1, 2, 2, 1]; let input_array = Array::from_slice(&input_data, &input_shape);
let weight_data = [1.0, 0.0, 0.0, 1.0];
let weight_shape = [1, 2, 2]; let weight_array = Array::from_slice(&weight_data, &weight_shape);
let result = conv2d(
&input_array,
&weight_array,
Some((1, 1)), Some((0, 0)), Some((1, 1)), Some(1), );
assert!(result.is_err());
}
#[test]
fn test_conv_invalid_group_size() {
let input_data = [1.0, 2.0, 3.0, 4.0];
let input_shape = [1, 2, 2, 1]; let input_array = Array::from_slice(&input_data, &input_shape);
let weight_data = [1.0, 0.0, 0.0, 1.0];
let weight_shape = [1, 2, 2, 1]; let weight_array = Array::from_slice(&weight_data, &weight_shape);
let result = conv2d(
&input_array,
&weight_array,
Some((1, 1)), Some((0, 0)), Some((1, 1)), Some(2), );
assert!(result.is_err());
}
#[test]
fn test_conv_non_float() {
let input_data = [1, 2, 3, 4];
let input_shape = [1, 2, 2, 1]; let input_array = Array::from_slice(&input_data, &input_shape);
let weight_data = [1, 0, 0, 1];
let weight_shape = [1, 2, 2, 1]; let weight_array = Array::from_slice(&weight_data, &weight_shape);
let result = conv2d(
&input_array,
&weight_array,
Some((1, 1)), Some((0, 0)), Some((1, 1)), Some(1), );
assert!(result.is_err());
}
}