1use super::{conv, ctc, embedding, linear, pool};
2use crate::ops::unfold::unfold4d_using_conv2d;
3use crate::tensor::{BoolTensor, FloatTensor, IntTensor};
4use crate::{Backend, ElementConversion, TensorMetadata};
5use ruda_core::tensor::Shape;
6
7pub struct LayerNormOutput<B: Backend> {
9 pub output: FloatTensor<B>,
11 pub mean: FloatTensor<B>,
13 pub rstd: FloatTensor<B>,
15}
16
17pub struct LayerNormBackward<B: Backend> {
19 pub input: FloatTensor<B>,
21 pub weight: FloatTensor<B>,
23 pub bias: FloatTensor<B>,
25}
26
27#[derive(new)]
29pub struct Conv2dBackward<B: Backend> {
30 pub x_grad: FloatTensor<B>,
32
33 pub weights_grad: FloatTensor<B>,
35
36 pub bias_grad: Option<FloatTensor<B>>,
38}
39
40#[derive(new)]
42pub struct DeformConv2dBackward<B: Backend> {
43 pub x_grad: FloatTensor<B>,
45
46 pub offset_grad: FloatTensor<B>,
48
49 pub weight_grad: FloatTensor<B>,
51
52 pub mask_grad: Option<FloatTensor<B>>,
54
55 pub bias_grad: Option<FloatTensor<B>>,
57}
58
59#[derive(new)]
61pub struct Conv3dBackward<B: Backend> {
62 pub x_grad: FloatTensor<B>,
64
65 pub weights_grad: FloatTensor<B>,
67
68 pub bias_grad: Option<FloatTensor<B>>,
70}
71
72#[derive(new)]
74pub struct MaxPool1dBackward<B: Backend> {
75 pub x_grad: FloatTensor<B>,
77}
78
79#[derive(new)]
81pub struct MaxPool1dWithIndices<B: Backend> {
82 pub output: FloatTensor<B>,
84
85 pub indices: IntTensor<B>,
87}
88
89#[derive(new)]
91pub struct MaxPool2dBackward<B: Backend> {
92 pub x_grad: FloatTensor<B>,
94}
95
96#[derive(new)]
98pub struct MaxPool2dWithIndices<B: Backend> {
99 pub output: FloatTensor<B>,
101
102 pub indices: IntTensor<B>,
104}
105
106pub use ruda_core::tensor::spatial::{ConvOptions, PaddedConvOptions, DeformConvOptions, ConvTransposeOptions, UnfoldOptions};
107
108pub use ruda_core::tensor::spatial::{InterpolateMode, InterpolateOptions};
109
110pub use ruda_core::tensor::spatial::{GridSampleOptions, GridSamplePaddingMode};
111
112#[derive(Debug, Clone, Copy, PartialEq, serde::Deserialize, serde::Serialize)]
123pub enum PadMode {
124 Constant(f32),
130
131 Reflect,
139
140 Edge,
146}
147
148impl Default for PadMode {
149 fn default() -> Self {
150 PadMode::Constant(0.0)
151 }
152}
153
154impl<E: ElementConversion> From<E> for PadMode {
155 fn from(value: E) -> Self {
156 PadMode::Constant(value.elem())
157 }
158}
159
160#[derive(new)]
162pub struct InterpolateBackward<B: Backend> {
163 pub x_grad: FloatTensor<B>,
165}
166
167pub use ruda_core::tensor::spatial::AttentionModuleOptions;
168
169pub trait ModuleOps<B: Backend> {
171 fn embedding(weights: FloatTensor<B>, indices: IntTensor<B>) -> FloatTensor<B> {
182 embedding::embedding::<B>(weights, indices)
183 }
184
185 fn embedding_backward(
197 weights: FloatTensor<B>,
198 output_grad: FloatTensor<B>,
199 indices: IntTensor<B>,
200 ) -> FloatTensor<B> {
201 embedding::embedding_backward::<B>(weights, output_grad, indices)
202 }
203
204 fn linear(
212 x: FloatTensor<B>,
213 weight: FloatTensor<B>,
214 bias: Option<FloatTensor<B>>,
215 ) -> FloatTensor<B> {
216 linear::linear::<B>(x, weight, bias)
217 }
218 fn linear_x_backward(weight: FloatTensor<B>, output_grad: FloatTensor<B>) -> FloatTensor<B> {
220 linear::linear_x_backward::<B>(weight, output_grad)
221 }
222 fn linear_weight_backward(x: FloatTensor<B>, output_grad: FloatTensor<B>) -> FloatTensor<B> {
224 linear::linear_weight_backward::<B>(x, output_grad)
225 }
226 fn linear_bias_backward(output_grad: FloatTensor<B>) -> FloatTensor<B> {
228 linear::linear_bias_backward::<B>(output_grad)
229 }
230
231 fn conv1d(
239 x: FloatTensor<B>,
240 weight: FloatTensor<B>,
241 bias: Option<FloatTensor<B>>,
242 options: ConvOptions<1>,
243 ) -> FloatTensor<B> {
244 conv::conv1d_from_conv2d::<B>(x, weight, bias, options)
245 }
246 fn conv1d_x_backward(
248 x: FloatTensor<B>,
249 weight: FloatTensor<B>,
250 output_grad: FloatTensor<B>,
251 options: ConvOptions<1>,
252 ) -> FloatTensor<B> {
253 conv::conv1d_x_backward::<B>(x, weight, output_grad, options)
254 }
255 fn conv1d_weight_backward(
257 x: FloatTensor<B>,
258 weight: FloatTensor<B>,
259 output_grad: FloatTensor<B>,
260 options: ConvOptions<1>,
261 ) -> FloatTensor<B> {
262 conv::conv1d_weight_backward::<B>(x, weight, output_grad, options)
263 }
264 fn conv1d_bias_backward(
266 x: FloatTensor<B>,
267 bias: FloatTensor<B>,
268 output_grad: FloatTensor<B>,
269 ) -> FloatTensor<B> {
270 conv::conv1d_bias_backward::<B>(x, bias, output_grad)
271 }
272 fn conv2d(
280 x: FloatTensor<B>,
281 weight: FloatTensor<B>,
282 bias: Option<FloatTensor<B>>,
283 options: ConvOptions<2>,
284 ) -> FloatTensor<B>;
285 fn conv2d_x_backward(
287 x: FloatTensor<B>,
288 weight: FloatTensor<B>,
289 output_grad: FloatTensor<B>,
290 options: ConvOptions<2>,
291 ) -> FloatTensor<B> {
292 conv::conv2d_x_backward::<B>(x, weight, output_grad, options)
293 }
294 fn conv2d_weight_backward(
296 x: FloatTensor<B>,
297 weight: FloatTensor<B>,
298 output_grad: FloatTensor<B>,
299 options: ConvOptions<2>,
300 ) -> FloatTensor<B> {
301 conv::conv2d_weight_backward::<B>(x, weight, output_grad, options)
302 }
303 fn conv2d_bias_backward(
305 x: FloatTensor<B>,
306 bias: FloatTensor<B>,
307 output_grad: FloatTensor<B>,
308 ) -> FloatTensor<B> {
309 conv::conv2d_bias_backward::<B>(x, bias, output_grad)
310 }
311
312 fn deform_conv2d(
320 x: FloatTensor<B>,
321 offset: FloatTensor<B>,
322 weight: FloatTensor<B>,
323 mask: Option<FloatTensor<B>>,
324 bias: Option<FloatTensor<B>>,
325 options: DeformConvOptions<2>,
326 ) -> FloatTensor<B>;
327 fn deform_conv2d_backward(
329 x: FloatTensor<B>,
330 offset: FloatTensor<B>,
331 weight: FloatTensor<B>,
332 mask: Option<FloatTensor<B>>,
333 bias: Option<FloatTensor<B>>,
334 output_grad: FloatTensor<B>,
335 options: DeformConvOptions<2>,
336 ) -> DeformConv2dBackward<B>;
337
338 fn conv3d(
346 x: FloatTensor<B>,
347 weight: FloatTensor<B>,
348 bias: Option<FloatTensor<B>>,
349 options: ConvOptions<3>,
350 ) -> FloatTensor<B>;
351 fn conv3d_x_backward(
353 x: FloatTensor<B>,
354 weight: FloatTensor<B>,
355 output_grad: FloatTensor<B>,
356 options: ConvOptions<3>,
357 ) -> FloatTensor<B> {
358 conv::conv3d_x_backward::<B>(x, weight, output_grad, options)
359 }
360 fn conv3d_weight_backward(
362 x: FloatTensor<B>,
363 weight: FloatTensor<B>,
364 output_grad: FloatTensor<B>,
365 options: ConvOptions<3>,
366 ) -> FloatTensor<B> {
367 conv::conv3d_weight_backward::<B>(x, weight, output_grad, options)
368 }
369 fn conv3d_bias_backward(
371 x: FloatTensor<B>,
372 bias: FloatTensor<B>,
373 output_grad: FloatTensor<B>,
374 ) -> FloatTensor<B> {
375 conv::conv3d_bias_backward::<B>(x, bias, output_grad)
376 }
377 fn conv_transpose1d(
385 x: FloatTensor<B>,
386 weight: FloatTensor<B>,
387 bias: Option<FloatTensor<B>>,
388 options: ConvTransposeOptions<1>,
389 ) -> FloatTensor<B> {
390 conv::conv_transpose1d_from_conv_transpose2d::<B>(x, weight, bias, options)
391 }
392 fn conv_transpose1d_x_backward(
394 weight: FloatTensor<B>,
395 output_grad: FloatTensor<B>,
396 options: ConvTransposeOptions<1>,
397 ) -> FloatTensor<B> {
398 conv::conv_transpose1d_x_backward::<B>(weight, output_grad, options)
399 }
400 fn conv_transpose1d_weight_backward(
402 x: FloatTensor<B>,
403 weight: FloatTensor<B>,
404 output_grad: FloatTensor<B>,
405 options: ConvTransposeOptions<1>,
406 ) -> FloatTensor<B> {
407 conv::conv_transpose1d_weight_backward::<B>(x, weight, output_grad, options)
408 }
409 fn conv_transpose1d_bias_backward(
411 x: FloatTensor<B>,
412 bias: FloatTensor<B>,
413 output_grad: FloatTensor<B>,
414 ) -> FloatTensor<B> {
415 conv::conv_transpose1d_bias_backward::<B>(x, bias, output_grad)
416 }
417
418 fn conv_transpose2d(
426 x: FloatTensor<B>,
427 weight: FloatTensor<B>,
428 bias: Option<FloatTensor<B>>,
429 options: ConvTransposeOptions<2>,
430 ) -> FloatTensor<B>;
431 fn conv_transpose2d_x_backward(
433 weight: FloatTensor<B>,
434 output_grad: FloatTensor<B>,
435 options: ConvTransposeOptions<2>,
436 ) -> FloatTensor<B> {
437 conv::conv_transpose2d_x_backward::<B>(weight, output_grad, options)
438 }
439 fn conv_transpose2d_weight_backward(
441 x: FloatTensor<B>,
442 weight: FloatTensor<B>,
443 output_grad: FloatTensor<B>,
444 options: ConvTransposeOptions<2>,
445 ) -> FloatTensor<B> {
446 conv::conv_transpose2d_weight_backward::<B>(x, weight, output_grad, options)
447 }
448 fn conv_transpose2d_bias_backward(
450 x: FloatTensor<B>,
451 bias: FloatTensor<B>,
452 output_grad: FloatTensor<B>,
453 ) -> FloatTensor<B> {
454 conv::conv_transpose2d_bias_backward::<B>(x, bias, output_grad)
455 }
456
457 fn conv_transpose3d(
465 x: FloatTensor<B>,
466 weight: FloatTensor<B>,
467 bias: Option<FloatTensor<B>>,
468 options: ConvTransposeOptions<3>,
469 ) -> FloatTensor<B>;
470 fn conv_transpose3d_x_backward(
472 weight: FloatTensor<B>,
473 output_grad: FloatTensor<B>,
474 options: ConvTransposeOptions<3>,
475 ) -> FloatTensor<B> {
476 conv::conv_transpose3d_x_backward::<B>(weight, output_grad, options)
477 }
478 fn conv_transpose3d_weight_backward(
480 x: FloatTensor<B>,
481 weight: FloatTensor<B>,
482 output_grad: FloatTensor<B>,
483 options: ConvTransposeOptions<3>,
484 ) -> FloatTensor<B> {
485 conv::conv_transpose3d_weight_backward::<B>(x, weight, output_grad, options)
486 }
487 fn conv_transpose3d_bias_backward(
489 x: FloatTensor<B>,
490 bias: FloatTensor<B>,
491 output_grad: FloatTensor<B>,
492 ) -> FloatTensor<B> {
493 conv::conv_transpose3d_bias_backward::<B>(x, bias, output_grad)
494 }
495
496 fn unfold4d(
503 x: FloatTensor<B>,
504 kernel_size: [usize; 2],
505 options: UnfoldOptions,
506 ) -> FloatTensor<B> {
507 if options.padding == [0, 0] && options.dilation == [1, 1] {
508 let blocks = B::float_unfold(x, 2, kernel_size[0], options.stride[0]);
509 let blocks = B::float_unfold(blocks, 3, kernel_size[1], options.stride[1]);
510
511 let blocks = B::float_permute(blocks, &[0, 1, 4, 5, 2, 3]);
514 let shape = blocks.shape();
515
516 B::float_reshape(
519 blocks,
520 [
521 shape[0],
522 shape[1] * shape[2] * shape[3],
523 shape[4] * shape[5],
524 ]
525 .into(),
526 )
527 } else {
528 unfold4d_using_conv2d::<B>(x, kernel_size, options)
529 }
530 }
531
532 fn avg_pool1d(
538 x: FloatTensor<B>,
539 kernel_size: usize,
540 stride: usize,
541 padding: usize,
542 count_include_pad: bool,
543 ceil_mode: bool,
544 ) -> FloatTensor<B> {
545 pool::avg_pool1d_from_2d::<B>(
546 x,
547 kernel_size,
548 stride,
549 padding,
550 count_include_pad,
551 ceil_mode,
552 )
553 }
554 fn avg_pool1d_backward(
556 x: FloatTensor<B>,
557 grad: FloatTensor<B>,
558 kernel_size: usize,
559 stride: usize,
560 padding: usize,
561 count_include_pad: bool,
562 ceil_mode: bool,
563 ) -> FloatTensor<B> {
564 pool::avg_pool1d_backward_from_2d::<B>(
565 x,
566 grad,
567 kernel_size,
568 stride,
569 padding,
570 count_include_pad,
571 ceil_mode,
572 )
573 }
574 fn avg_pool2d(
580 x: FloatTensor<B>,
581 kernel_size: [usize; 2],
582 stride: [usize; 2],
583 padding: [usize; 2],
584 count_include_pad: bool,
585 ceil_mode: bool,
586 ) -> FloatTensor<B>;
587 fn avg_pool2d_backward(
589 x: FloatTensor<B>,
590 grad: FloatTensor<B>,
591 kernel_size: [usize; 2],
592 stride: [usize; 2],
593 padding: [usize; 2],
594 count_include_pad: bool,
595 ceil_mode: bool,
596 ) -> FloatTensor<B>;
597 fn adaptive_avg_pool2d(x: FloatTensor<B>, output_size: [usize; 2]) -> FloatTensor<B>;
603 fn adaptive_avg_pool2d_backward(x: FloatTensor<B>, grad: FloatTensor<B>) -> FloatTensor<B>;
605 fn adaptive_avg_pool3d(x: FloatTensor<B>, output_size: [usize; 3]) -> FloatTensor<B> {
607 pool::adaptive_avg_pool3d_from_2d::<B>(x, output_size)
608 }
609 fn adaptive_avg_pool3d_backward(x: FloatTensor<B>, grad: FloatTensor<B>) -> FloatTensor<B> {
611 pool::adaptive_avg_pool3d_backward_from_2d::<B>(x, grad)
612 }
613 fn adaptive_avg_pool1d(x: FloatTensor<B>, output_size: usize) -> FloatTensor<B> {
619 pool::adaptive_avg_pool1d_from_2d::<B>(x, output_size)
620 }
621 fn adaptive_avg_pool1d_backward(x: FloatTensor<B>, grad: FloatTensor<B>) -> FloatTensor<B> {
623 pool::adaptive_avg_pool1d_backward_from_2d::<B>(x, grad)
624 }
625 fn max_pool1d(
631 x: FloatTensor<B>,
632 kernel_size: usize,
633 stride: usize,
634 padding: usize,
635 dilation: usize,
636 ceil_mode: bool,
637 ) -> FloatTensor<B> {
638 pool::max_pool1d_from_2d::<B>(x, kernel_size, stride, padding, dilation, ceil_mode)
639 }
640
641 fn max_pool1d_with_indices(
647 x: FloatTensor<B>,
648 kernel_size: usize,
649 stride: usize,
650 padding: usize,
651 dilation: usize,
652 ceil_mode: bool,
653 ) -> MaxPool1dWithIndices<B> {
654 pool::max_pool1d_with_indices_from_2d::<B>(
655 x,
656 kernel_size,
657 stride,
658 padding,
659 dilation,
660 ceil_mode,
661 )
662 }
663 #[allow(clippy::too_many_arguments)]
665 fn max_pool1d_with_indices_backward(
666 x: FloatTensor<B>,
667 kernel_size: usize,
668 stride: usize,
669 padding: usize,
670 dilation: usize,
671 ceil_mode: bool,
672 output_grad: FloatTensor<B>,
673 indices: IntTensor<B>,
674 ) -> MaxPool1dBackward<B> {
675 pool::max_pool1d_with_indices_backward_from_2d::<B>(
676 x,
677 kernel_size,
678 stride,
679 padding,
680 dilation,
681 ceil_mode,
682 output_grad,
683 indices,
684 )
685 }
686
687 fn max_pool2d(
693 x: FloatTensor<B>,
694 kernel_size: [usize; 2],
695 stride: [usize; 2],
696 padding: [usize; 2],
697 dilation: [usize; 2],
698 ceil_mode: bool,
699 ) -> FloatTensor<B>;
700
701 fn max_pool2d_with_indices(
707 x: FloatTensor<B>,
708 kernel_size: [usize; 2],
709 stride: [usize; 2],
710 padding: [usize; 2],
711 dilation: [usize; 2],
712 ceil_mode: bool,
713 ) -> MaxPool2dWithIndices<B>;
714 #[allow(clippy::too_many_arguments)]
716 fn max_pool2d_with_indices_backward(
717 x: FloatTensor<B>,
718 kernel_size: [usize; 2],
719 stride: [usize; 2],
720 padding: [usize; 2],
721 dilation: [usize; 2],
722 ceil_mode: bool,
723 output_grad: FloatTensor<B>,
724 indices: IntTensor<B>,
725 ) -> MaxPool2dBackward<B>;
726
727 fn interpolate(
733 x: FloatTensor<B>,
734 output_size: [usize; 2],
735 options: InterpolateOptions,
736 ) -> FloatTensor<B>;
737
738 fn interpolate_backward(
740 x: FloatTensor<B>,
741 grad: FloatTensor<B>,
742 output_size: [usize; 2],
743 options: InterpolateOptions,
744 ) -> FloatTensor<B>;
745
746 fn attention(
768 query: FloatTensor<B>,
769 key: FloatTensor<B>,
770 value: FloatTensor<B>,
771 mask: Option<BoolTensor<B>>,
772 attn_bias: Option<FloatTensor<B>>,
773 options: AttentionModuleOptions,
774 ) -> FloatTensor<B>;
775
776 fn layer_norm(
792 tensor: FloatTensor<B>,
793 gamma: FloatTensor<B>,
794 beta: Option<FloatTensor<B>>,
795 epsilon: f64,
796 ) -> FloatTensor<B> {
797 Self::layer_norm_default(tensor, gamma, beta, epsilon)
798 }
799
800 fn has_layer_norm_backward() -> bool { false }
802
803 fn layer_norm_with_stats(
805 _tensor: FloatTensor<B>, _gamma: FloatTensor<B>,
806 _beta: Option<FloatTensor<B>>, _epsilon: f64,
807 ) -> LayerNormOutput<B> {
808 unimplemented!("native LayerNorm statistics unavailable")
809 }
810
811 fn layer_norm_backward(
813 _tensor: FloatTensor<B>, _gamma: FloatTensor<B>, _grad: FloatTensor<B>,
814 _mean: FloatTensor<B>, _rstd: FloatTensor<B>,
815 ) -> LayerNormBackward<B> {
816 unimplemented!("native LayerNorm backward unavailable")
817 }
818
819 fn layer_norm_default(
821 tensor: FloatTensor<B>, gamma: FloatTensor<B>,
822 beta: Option<FloatTensor<B>>, epsilon: f64,
823 ) -> FloatTensor<B> {
824 let shape = tensor.shape();
825 let rank = shape.num_dims();
826 let last_dim = rank - 1;
827 let d_model = shape[last_dim];
828
829 let mean = B::float_mean_dim(tensor.clone(), last_dim);
830 let centered = B::float_sub(tensor, mean);
831 let var = B::float_mean_dim(B::float_mul(centered.clone(), centered.clone()), last_dim);
832 let denom = B::float_sqrt(B::float_add_scalar(var, epsilon.into()));
833 let normalized = B::float_div(centered, denom);
834
835 let broadcast_dims: alloc::vec::Vec<usize> = (0..rank)
836 .map(|i| if i == last_dim { d_model } else { 1 })
837 .collect();
838 let gamma_b = B::float_reshape(gamma, Shape::from(broadcast_dims.clone()));
839 let scaled = B::float_mul(normalized, gamma_b);
840
841 match beta {
842 Some(beta) => {
843 let beta_b = B::float_reshape(beta, Shape::from(broadcast_dims));
844 B::float_add(scaled, beta_b)
845 }
846 None => scaled,
847 }
848 }
849
850 fn ctc_loss(
867 log_probs: FloatTensor<B>,
868 targets: IntTensor<B>,
869 input_lengths: IntTensor<B>,
870 target_lengths: IntTensor<B>,
871 blank: usize,
872 ) -> FloatTensor<B> {
873 ctc::ctc_loss_default::<B>(log_probs, targets, input_lengths, target_lengths, blank)
874 }
875
876 fn has_ctc_loss_backward() -> bool {
888 false
889 }
890
891 fn ctc_loss_backward(
911 _log_probs: FloatTensor<B>,
912 _targets: IntTensor<B>,
913 _input_lengths: IntTensor<B>,
914 _target_lengths: IntTensor<B>,
915 _grad_loss: FloatTensor<B>,
916 _blank: usize,
917 ) -> FloatTensor<B> {
918 unreachable!(
919 "ctc_loss_backward called on a backend whose has_ctc_loss_backward() returns false"
920 )
921 }
922
923 fn rfft(
935 signal: FloatTensor<B>,
936 dim: usize,
937 n: Option<usize>,
938 ) -> (FloatTensor<B>, FloatTensor<B>);
939
940 fn irfft(
948 spectrum_re: FloatTensor<B>,
949 spectrum_im: FloatTensor<B>,
950 dim: usize,
951 n: Option<usize>,
952 ) -> FloatTensor<B>;
953}
954
955#[cfg(test)]
956mod tests {
957 use super::*;
958
959 #[test]
960 #[should_panic = "stride must be non-zero"]
961 fn conv_options_stride_zero() {
962 let _opt = ConvOptions::new([0, 1], [0, 0], [1, 1], 1);
963 }
964
965 #[test]
966 #[should_panic = "dilation must be non-zero"]
967 fn conv_options_dilation_zero() {
968 let _opt = ConvOptions::new([1, 1], [0, 0], [0, 0], 1);
969 }
970
971 #[test]
972 #[should_panic = "groups must be non-zero"]
973 fn conv_options_groups_zero() {
974 let _opt = ConvOptions::new([1, 1], [0, 0], [1, 1], 0);
975 }
976
977 #[test]
978 #[should_panic = "stride must be non-zero"]
979 fn conv_transpose_options_stride_zero() {
980 let _opt = ConvTransposeOptions::new([0, 1], [0, 0], [0, 0], [1, 1], 1);
981 }
982
983 #[test]
984 #[should_panic = "dilation must be non-zero"]
985 fn conv_transpose_options_dilation_zero() {
986 let _opt = ConvTransposeOptions::new([1, 1], [0, 0], [0, 0], [0, 0], 1);
987 }
988
989 #[test]
990 #[should_panic = "groups must be non-zero"]
991 fn conv_transpose_options_groups_zero() {
992 let _opt = ConvTransposeOptions::new([1, 1], [0, 0], [0, 0], [1, 1], 0);
993 }
994
995 #[test]
996 #[should_panic = "stride must be non-zero"]
997 fn deform_conv_options_stride_zero() {
998 let _opt = DeformConvOptions::new([0, 1], [0, 0], [1, 1], 1, 1);
999 }
1000
1001 #[test]
1002 #[should_panic = "dilation must be non-zero"]
1003 fn deform_conv_options_dilation_zero() {
1004 let _opt = DeformConvOptions::new([1, 1], [0, 0], [0, 0], 1, 1);
1005 }
1006
1007 #[test]
1008 #[should_panic = "weight groups must be non-zero"]
1009 fn deform_conv_options_weights_groups_zero() {
1010 let _opt = DeformConvOptions::new([1, 1], [0, 0], [1, 1], 0, 1);
1011 }
1012
1013 #[test]
1014 #[should_panic = "offset groups must be non-zero"]
1015 fn deform_conv_options_offset_groups_zero() {
1016 let _opt = DeformConvOptions::new([1, 1], [0, 0], [1, 1], 1, 0);
1017 }
1018
1019 #[test]
1020 #[should_panic = "stride must be non-zero"]
1021 fn unfold_options_stride_zero() {
1022 let _opt = UnfoldOptions::new([0, 1], [0, 0], [1, 1]);
1023 }
1024
1025 #[test]
1026 #[should_panic = "dilation must be non-zero"]
1027 fn unfold_options_dilation_zero() {
1028 let _opt = UnfoldOptions::new([1, 1], [0, 0], [0, 0]);
1029 }
1030}