1use crate::error::Result;
2use crate::utils::guard::Guarded;
3use crate::utils::IntoOption;
4use crate::{Array, Stream};
5use mlx_internal_macros::generate_macro;
6
7#[allow(clippy::too_many_arguments)]
25pub fn conv_general<'a>(
26 array: impl AsRef<Array>,
27 weight: impl AsRef<Array>,
28 strides: impl IntoOption<&'a [i32]>,
29 padding: impl IntoOption<&'a [i32]>,
30 kernel_dilation: impl IntoOption<&'a [i32]>,
31 input_dilation: impl IntoOption<&'a [i32]>,
32 groups: impl Into<Option<i32>>,
33 flip: impl Into<Option<bool>>,
34) -> Result<Array> {
35 let stream = Stream::thread_local_or_default();
36 let strides = strides.into_option().unwrap_or(&[1]);
37 let padding = padding.into_option().unwrap_or(&[0]);
38 let kernel_dilation = kernel_dilation.into_option().unwrap_or(&[1]);
39 let input_dilation = input_dilation.into_option().unwrap_or(&[1]);
40 let groups = groups.into().unwrap_or(1);
41 let flip = flip.into().unwrap_or(false);
42
43 Array::try_from_op(|res| unsafe {
44 mlx_sys::mlx_conv_general(
45 res,
46 array.as_ref().as_ptr(),
47 weight.as_ref().as_ptr(),
48 strides.as_ptr(),
49 strides.len(),
50 padding.as_ptr(),
51 padding.len(),
52 padding.as_ptr(),
53 padding.len(),
54 kernel_dilation.as_ptr(),
55 kernel_dilation.len(),
56 input_dilation.as_ptr(),
57 input_dilation.len(),
58 groups,
59 flip,
60 stream.as_ref().as_ptr(),
61 )
62 })
63}
64
65#[generate_macro(customize(forwarding_shim = true))]
67#[allow(clippy::too_many_arguments)]
68#[deprecated(
69 since = "0.26.0",
70 note = "use `with_stream` or `with_device` around `conv_general`"
71)]
72pub fn conv_general_device<'a>(
73 array: impl AsRef<Array>,
74 weight: impl AsRef<Array>,
75 #[optional] strides: impl IntoOption<&'a [i32]>,
76 #[optional] padding: impl IntoOption<&'a [i32]>,
77 #[optional] kernel_dilation: impl IntoOption<&'a [i32]>,
78 #[optional] input_dilation: impl IntoOption<&'a [i32]>,
79 #[optional] groups: impl Into<Option<i32>>,
80 #[optional] flip: impl Into<Option<bool>>,
81 #[optional] stream: impl AsRef<Stream>,
82) -> Result<Array> {
83 crate::with_stream(stream.as_ref(), || {
84 conv_general(
85 array,
86 weight,
87 strides,
88 padding,
89 kernel_dilation,
90 input_dilation,
91 groups,
92 flip,
93 )
94 })
95}
96
97pub fn conv1d(
110 array: impl AsRef<Array>,
111 weight: impl AsRef<Array>,
112 stride: impl Into<Option<i32>>,
113 padding: impl Into<Option<i32>>,
114 dilation: impl Into<Option<i32>>,
115 groups: impl Into<Option<i32>>,
116) -> Result<Array> {
117 let stream = Stream::thread_local_or_default();
118 let stride = stride.into().unwrap_or(1);
119 let padding = padding.into().unwrap_or(0);
120 let dilation = dilation.into().unwrap_or(1);
121 let groups = groups.into().unwrap_or(1);
122
123 Array::try_from_op(|res| unsafe {
124 mlx_sys::mlx_conv1d(
125 res,
126 array.as_ref().as_ptr(),
127 weight.as_ref().as_ptr(),
128 stride,
129 padding,
130 dilation,
131 groups,
132 stream.as_ref().as_ptr(),
133 )
134 })
135}
136
137#[generate_macro(customize(forwarding_shim = true))]
139#[deprecated(
140 since = "0.26.0",
141 note = "use `with_stream` or `with_device` around `conv1d`"
142)]
143pub fn conv1d_device(
144 array: impl AsRef<Array>,
145 weight: impl AsRef<Array>,
146 #[optional] stride: impl Into<Option<i32>>,
147 #[optional] padding: impl Into<Option<i32>>,
148 #[optional] dilation: impl Into<Option<i32>>,
149 #[optional] groups: impl Into<Option<i32>>,
150 #[optional] stream: impl AsRef<Stream>,
151) -> Result<Array> {
152 crate::with_stream(stream.as_ref(), || {
153 conv1d(array, weight, stride, padding, dilation, groups)
154 })
155}
156
157pub fn conv2d(
170 array: impl AsRef<Array>,
171 weight: impl AsRef<Array>,
172 stride: impl Into<Option<(i32, i32)>>,
173 padding: impl Into<Option<(i32, i32)>>,
174 dilation: impl Into<Option<(i32, i32)>>,
175 groups: impl Into<Option<i32>>,
176) -> Result<Array> {
177 let stream = Stream::thread_local_or_default();
178 let stride = stride.into().unwrap_or((1, 1));
179 let padding = padding.into().unwrap_or((0, 0));
180 let dilation = dilation.into().unwrap_or((1, 1));
181 let groups = groups.into().unwrap_or(1);
182
183 Array::try_from_op(|res| unsafe {
184 mlx_sys::mlx_conv2d(
185 res,
186 array.as_ref().as_ptr(),
187 weight.as_ref().as_ptr(),
188 stride.0,
189 stride.1,
190 padding.0,
191 padding.1,
192 dilation.0,
193 dilation.1,
194 groups,
195 stream.as_ref().as_ptr(),
196 )
197 })
198}
199
200#[generate_macro(customize(forwarding_shim = true))]
202#[deprecated(
203 since = "0.26.0",
204 note = "use `with_stream` or `with_device` around `conv2d`"
205)]
206pub fn conv2d_device(
207 array: impl AsRef<Array>,
208 weight: impl AsRef<Array>,
209 #[optional] stride: impl Into<Option<(i32, i32)>>,
210 #[optional] padding: impl Into<Option<(i32, i32)>>,
211 #[optional] dilation: impl Into<Option<(i32, i32)>>,
212 #[optional] groups: impl Into<Option<i32>>,
213 #[optional] stream: impl AsRef<Stream>,
214) -> Result<Array> {
215 crate::with_stream(stream.as_ref(), || {
216 conv2d(array, weight, stride, padding, dilation, groups)
217 })
218}
219
220pub fn conv3d(
224 array: impl AsRef<Array>,
225 weight: impl AsRef<Array>,
226 stride: impl Into<Option<(i32, i32, i32)>>,
227 padding: impl Into<Option<(i32, i32, i32)>>,
228 dilation: impl Into<Option<(i32, i32, i32)>>,
229 groups: impl Into<Option<i32>>,
230) -> Result<Array> {
231 let stream = Stream::thread_local_or_default();
232 let stride = stride.into().unwrap_or((1, 1, 1));
233 let padding = padding.into().unwrap_or((0, 0, 0));
234 let dilation = dilation.into().unwrap_or((1, 1, 1));
235 let groups = groups.into().unwrap_or(1);
236
237 Array::try_from_op(|res| unsafe {
238 mlx_sys::mlx_conv3d(
239 res,
240 array.as_ref().as_ptr(),
241 weight.as_ref().as_ptr(),
242 stride.0,
243 stride.1,
244 stride.2,
245 padding.0,
246 padding.1,
247 padding.2,
248 dilation.0,
249 dilation.1,
250 dilation.2,
251 groups,
252 stream.as_ref().as_ptr(),
253 )
254 })
255}
256
257#[generate_macro(customize(forwarding_shim = true))]
259#[deprecated(
260 since = "0.26.0",
261 note = "use `with_stream` or `with_device` around `conv3d`"
262)]
263pub fn conv3d_device(
264 array: impl AsRef<Array>,
265 weight: impl AsRef<Array>,
266 #[optional] stride: impl Into<Option<(i32, i32, i32)>>,
267 #[optional] padding: impl Into<Option<(i32, i32, i32)>>,
268 #[optional] dilation: impl Into<Option<(i32, i32, i32)>>,
269 #[optional] groups: impl Into<Option<i32>>,
270 #[optional] stream: impl AsRef<Stream>,
271) -> Result<Array> {
272 crate::with_stream(stream.as_ref(), || {
273 conv3d(array, weight, stride, padding, dilation, groups)
274 })
275}
276
277#[allow(clippy::too_many_arguments)]
291pub fn conv_transpose1d(
292 array: impl AsRef<Array>,
293 weight: impl AsRef<Array>,
294 stride: impl Into<Option<i32>>,
295 padding: impl Into<Option<i32>>,
296 dilation: impl Into<Option<i32>>,
297 output_padding: impl Into<Option<i32>>,
298 groups: impl Into<Option<i32>>,
299) -> Result<Array> {
300 let stream = Stream::thread_local_or_default();
301 let stride = stride.into().unwrap_or(1);
302 let padding = padding.into().unwrap_or(0);
303 let dilation = dilation.into().unwrap_or(1);
304 let output_padding = output_padding.into().unwrap_or(0);
305 let groups = groups.into().unwrap_or(1);
306
307 Array::try_from_op(|res| unsafe {
308 mlx_sys::mlx_conv_transpose1d(
309 res,
310 array.as_ref().as_ptr(),
311 weight.as_ref().as_ptr(),
312 stride,
313 padding,
314 dilation,
315 output_padding,
316 groups,
317 stream.as_ref().as_ptr(),
318 )
319 })
320}
321
322#[allow(clippy::too_many_arguments)]
324#[generate_macro(customize(forwarding_shim = true))]
325#[deprecated(
326 since = "0.26.0",
327 note = "use `with_stream` or `with_device` around `conv_transpose1d`"
328)]
329pub fn conv_transpose1d_device(
330 array: impl AsRef<Array>,
331 weight: impl AsRef<Array>,
332 #[optional] stride: impl Into<Option<i32>>,
333 #[optional] padding: impl Into<Option<i32>>,
334 #[optional] dilation: impl Into<Option<i32>>,
335 #[optional] output_padding: impl Into<Option<i32>>,
336 #[optional] groups: impl Into<Option<i32>>,
337 #[optional] stream: impl AsRef<Stream>,
338) -> Result<Array> {
339 crate::with_stream(stream.as_ref(), || {
340 conv_transpose1d(
341 array,
342 weight,
343 stride,
344 padding,
345 dilation,
346 output_padding,
347 groups,
348 )
349 })
350}
351
352#[allow(clippy::too_many_arguments)]
367pub fn conv_transpose2d(
368 array: impl AsRef<Array>,
369 weight: impl AsRef<Array>,
370 stride: impl Into<Option<(i32, i32)>>,
371 padding: impl Into<Option<(i32, i32)>>,
372 dilation: impl Into<Option<(i32, i32)>>,
373 output_padding: impl Into<Option<(i32, i32)>>,
374 groups: impl Into<Option<i32>>,
375) -> Result<Array> {
376 let stream = Stream::thread_local_or_default();
377 let stride = stride.into().unwrap_or((1, 1));
378 let padding = padding.into().unwrap_or((0, 0));
379 let dilation = dilation.into().unwrap_or((1, 1));
380 let output_padding = output_padding.into().unwrap_or((0, 0));
381 let groups = groups.into().unwrap_or(1);
382
383 Array::try_from_op(|res| unsafe {
384 mlx_sys::mlx_conv_transpose2d(
385 res,
386 array.as_ref().as_ptr(),
387 weight.as_ref().as_ptr(),
388 stride.0,
389 stride.1,
390 padding.0,
391 padding.1,
392 dilation.0,
393 dilation.1,
394 output_padding.0,
395 output_padding.1,
396 groups,
397 stream.as_ref().as_ptr(),
398 )
399 })
400}
401
402#[allow(clippy::too_many_arguments)]
404#[generate_macro(customize(forwarding_shim = true))]
405#[deprecated(
406 since = "0.26.0",
407 note = "use `with_stream` or `with_device` around `conv_transpose2d`"
408)]
409pub fn conv_transpose2d_device(
410 array: impl AsRef<Array>,
411 weight: impl AsRef<Array>,
412 #[optional] stride: impl Into<Option<(i32, i32)>>,
413 #[optional] padding: impl Into<Option<(i32, i32)>>,
414 #[optional] dilation: impl Into<Option<(i32, i32)>>,
415 #[optional] output_padding: impl Into<Option<(i32, i32)>>,
416 #[optional] groups: impl Into<Option<i32>>,
417 #[optional] stream: impl AsRef<Stream>,
418) -> Result<Array> {
419 crate::with_stream(stream.as_ref(), || {
420 conv_transpose2d(
421 array,
422 weight,
423 stride,
424 padding,
425 dilation,
426 output_padding,
427 groups,
428 )
429 })
430}
431
432#[allow(clippy::too_many_arguments)]
447pub fn conv_transpose3d(
448 array: impl AsRef<Array>,
449 weight: impl AsRef<Array>,
450 stride: impl Into<Option<(i32, i32, i32)>>,
451 padding: impl Into<Option<(i32, i32, i32)>>,
452 dilation: impl Into<Option<(i32, i32, i32)>>,
453 output_padding: impl Into<Option<(i32, i32, i32)>>,
454 groups: impl Into<Option<i32>>,
455) -> Result<Array> {
456 let stream = Stream::thread_local_or_default();
457 let stride = stride.into().unwrap_or((1, 1, 1));
458 let padding = padding.into().unwrap_or((0, 0, 0));
459 let dilation = dilation.into().unwrap_or((1, 1, 1));
460 let output_padding = output_padding.into().unwrap_or((0, 0, 0));
461 let groups = groups.into().unwrap_or(1);
462
463 Array::try_from_op(|res| unsafe {
464 mlx_sys::mlx_conv_transpose3d(
465 res,
466 array.as_ref().as_ptr(),
467 weight.as_ref().as_ptr(),
468 stride.0,
469 stride.1,
470 stride.2,
471 padding.0,
472 padding.1,
473 padding.2,
474 dilation.0,
475 dilation.1,
476 dilation.2,
477 output_padding.0,
478 output_padding.1,
479 output_padding.2,
480 groups,
481 stream.as_ref().as_ptr(),
482 )
483 })
484}
485
486#[allow(clippy::too_many_arguments)]
488#[generate_macro(customize(forwarding_shim = true))]
489#[deprecated(
490 since = "0.26.0",
491 note = "use `with_stream` or `with_device` around `conv_transpose3d`"
492)]
493pub fn conv_transpose3d_device(
494 array: impl AsRef<Array>,
495 weight: impl AsRef<Array>,
496 #[optional] stride: impl Into<Option<(i32, i32, i32)>>,
497 #[optional] padding: impl Into<Option<(i32, i32, i32)>>,
498 #[optional] dilation: impl Into<Option<(i32, i32, i32)>>,
499 #[optional] output_padding: impl Into<Option<(i32, i32, i32)>>,
500 #[optional] groups: impl Into<Option<i32>>,
501 #[optional] stream: impl AsRef<Stream>,
502) -> Result<Array> {
503 crate::with_stream(stream.as_ref(), || {
504 conv_transpose3d(
505 array,
506 weight,
507 stride,
508 padding,
509 dilation,
510 output_padding,
511 groups,
512 )
513 })
514}
515
516#[cfg(test)]
517mod tests {
518 use super::*;
519 use crate::test_utils::{assert_array_eq, tolerances};
520 use pretty_assertions::assert_eq;
521
522 #[test]
523 fn test_conv1d_complex_device() {
524 let input_data = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0];
526 let input_array = Array::from_slice(&input_data, &[1, 5, 2]);
527
528 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];
530 let weight_array = Array::from_slice(&weight_data, &[2, 3, 2]);
531
532 let result = conv1d(
533 &input_array,
534 &weight_array,
535 Some(1), Some(0), Some(1), Some(1), )
540 .unwrap();
541
542 let expected_output = [12.0, 8.0, 17.0, 13.0, 22.0, 18.0];
543 assert_eq!(result.shape(), &[1, 3, 2]);
544 assert_eq!(result.as_slice::<f32>(), &expected_output);
545 }
546
547 #[test]
548 fn test_conv_transpose1d() {
549 let input = Array::from_slice(&[1.0, 2.0, 3.0], &[1, 3, 1]);
551 let weights = Array::from_slice(&[1.0, 0.5], &[1, 2, 1]);
553
554 let result = conv_transpose1d(
555 &input,
556 &weights,
557 Some(1), Some(0), Some(1), None, Some(1), )
563 .unwrap();
564
565 let expected = [1.0, 2.5, 4.0, 1.5];
566 assert_eq!(result.shape(), &[1, 4, 1]);
567 assert_eq!(result.as_slice::<f32>(), &expected);
568 }
569
570 #[test]
571 fn test_conv2d() {
572 let input_data = [1.0, 2.0, 3.0, 4.0];
574 let input_shape = [1, 2, 2, 1]; let input_array = Array::from_slice(&input_data, &input_shape);
576
577 let weight_data = [1.0, 0.0, 0.0, 1.0];
579 let weight_shape = [1, 2, 2, 1]; let weight_array = Array::from_slice(&weight_data, &weight_shape);
581
582 let result = conv2d(
584 &input_array,
585 &weight_array,
586 Some((1, 1)), Some((0, 0)), Some((1, 1)), Some(1), )
591 .unwrap();
592
593 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]);
596 }
597
598 #[test]
599 fn test_conv_transpose2d() {
600 let input = Array::from_slice(&[1.0, 2.0, 3.0, 4.0], &[1, 2, 2, 1]);
602 let weights = Array::from_slice(&[1.0, 0.0, 0.0, 1.0], &[1, 2, 2, 1]);
604
605 let result = conv_transpose2d(
606 &input,
607 &weights,
608 Some((1, 1)), Some((0, 0)), Some((1, 1)), None, Some(1), )
614 .unwrap();
615
616 let expected = [1.0, 2.0, 0.0, 3.0, 5.0, 2.0, 0.0, 3.0, 4.0];
617 assert_eq!(result.shape(), &[1, 3, 3, 1]);
618 assert_eq!(result.as_slice::<f32>(), &expected);
619 }
620
621 #[test]
622 fn test_conv3d() {
623 let input_data = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
625 let input_shape = [1, 2, 2, 2, 1]; let input_array = Array::from_slice(&input_data, &input_shape);
627
628 let weight_data = [1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0];
630 let weight_shape = [1, 2, 2, 2, 1]; let weight_array = Array::from_slice(&weight_data, &weight_shape);
632
633 let result = conv3d(
635 &input_array,
636 &weight_array,
637 Some((1, 1, 1)), Some((0, 0, 0)), Some((1, 1, 1)), Some(1), )
642 .unwrap();
643
644 let expected_output = 1.0 * 1.0
646 + 2.0 * 0.0
647 + 3.0 * 0.0
648 + 4.0 * 1.0
649 + 5.0 * 0.0
650 + 6.0 * 1.0
651 + 7.0 * 1.0
652 + 8.0 * 0.0; assert_array_eq(
654 result,
655 Array::from_slice(&[expected_output], &[1, 1, 1, 1, 1]),
656 tolerances::EXACT.rtol,
657 tolerances::EXACT.atol,
658 );
659 }
660
661 #[test]
662 fn test_conv_transpose3d() {
663 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]);
665 let weights =
667 Array::from_slice(&[1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0], &[1, 2, 2, 2, 1]);
668
669 let result = conv_transpose3d(
670 &input,
671 &weights,
672 Some((1, 1, 1)), Some((0, 0, 0)), Some((1, 1, 1)), None, Some(1), )
678 .unwrap();
679
680 assert_eq!(result.shape(), &[1, 3, 3, 3, 1]);
681 }
682
683 #[test]
684 fn test_conv_wrong_dimensions() {
685 let input_data = [1.0, 2.0, 3.0, 4.0];
686 let input_shape = [1, 2, 2, 1]; let input_array = Array::from_slice(&input_data, &input_shape);
688
689 let weight_data = [1.0, 0.0, 0.0, 1.0];
690 let weight_shape = [1, 2, 2]; let weight_array = Array::from_slice(&weight_data, &weight_shape);
692
693 let result = conv2d(
694 &input_array,
695 &weight_array,
696 Some((1, 1)), Some((0, 0)), Some((1, 1)), Some(1), );
701
702 assert!(result.is_err());
703 }
704
705 #[test]
706 fn test_conv_invalid_group_size() {
707 let input_data = [1.0, 2.0, 3.0, 4.0];
708 let input_shape = [1, 2, 2, 1]; let input_array = Array::from_slice(&input_data, &input_shape);
710
711 let weight_data = [1.0, 0.0, 0.0, 1.0];
712 let weight_shape = [1, 2, 2, 1]; let weight_array = Array::from_slice(&weight_data, &weight_shape);
714
715 let result = conv2d(
716 &input_array,
717 &weight_array,
718 Some((1, 1)), Some((0, 0)), Some((1, 1)), Some(2), );
723
724 assert!(result.is_err());
725 }
726
727 #[test]
728 fn test_conv_non_float() {
729 let input_data = [1, 2, 3, 4];
730 let input_shape = [1, 2, 2, 1]; let input_array = Array::from_slice(&input_data, &input_shape);
732
733 let weight_data = [1, 0, 0, 1];
734 let weight_shape = [1, 2, 2, 1]; let weight_array = Array::from_slice(&weight_data, &weight_shape);
736
737 let result = conv2d(
738 &input_array,
739 &weight_array,
740 Some((1, 1)), Some((0, 0)), Some((1, 1)), Some(1), );
745
746 assert!(result.is_err());
747 }
748}