1use crate::ElementConversion;
4use core::num::NonZeroUsize;
5
6pub(crate) fn check_nonzero(value: usize, msg: &str) -> usize {
9 NonZeroUsize::new(value).expect(msg);
10 value
11}
12
13#[derive(Debug, Clone, Hash, PartialEq, Eq)]
15pub struct ConvOptions<const N: usize> {
16 pub stride: [usize; N],
18
19 pub padding: [(usize, usize); N],
21
22 pub dilation: [usize; N],
24
25 pub groups: usize,
27}
28
29impl<const N: usize> ConvOptions<N> {
30 pub fn new(
32 stride: [usize; N],
33 padding: [usize; N],
34 dilation: [usize; N],
35 groups: usize,
36 ) -> Self {
37 Self {
38 stride: stride.map(|s| check_nonzero(s, "stride must be non-zero")),
39 padding: padding.map(|padding| (padding, padding)),
40 dilation: dilation.map(|d| check_nonzero(d, "dilation must be non-zero")),
41 groups: check_nonzero(groups, "groups must be non-zero"),
42 }
43 }
44
45 pub fn new_with_padding(
47 stride: [usize; N],
48 padding: [(usize, usize); N],
49 dilation: [usize; N],
50 groups: usize,
51 ) -> Self {
52 Self {
53 stride: stride.map(|s| check_nonzero(s, "stride must be non-zero")),
54 padding,
55 dilation: dilation.map(|d| check_nonzero(d, "dilation must be non-zero")),
56 groups: check_nonzero(groups, "groups must be non-zero"),
57 }
58 }
59
60 pub fn is_asymmetric(&self) -> bool {
62 self.padding.iter().any(|(begin, end)| begin != end)
63 }
64
65 pub fn padding_begin(&self) -> [usize; N] {
67 self.padding.map(|(begin, _)| begin)
68 }
69
70 pub fn padding_end(&self) -> [usize; N] {
72 self.padding.map(|(_, after)| after)
73 }
74
75 pub fn symmetric_padding(&self) -> [usize; N] {
80 assert!(
81 !self.is_asymmetric(),
82 "expected symmetric convolution padding"
83 );
84 self.padding_begin()
85 }
86}
87
88#[deprecated(since = "0.22.0", note = "Use `ConvOptions::new_with_padding` instead")]
93#[derive(Debug, Clone)]
94pub struct PaddedConvOptions<const N: usize> {
95 pub options: ConvOptions<N>,
97 pub padding_end: Option<[usize; N]>,
101}
102
103#[allow(deprecated)]
104impl<const N: usize> PaddedConvOptions<N> {
105 pub fn asymmetric(
107 stride: [usize; N],
108 padding_begin: [usize; N],
109 padding_end: [usize; N],
110 dilation: [usize; N],
111 groups: usize,
112 ) -> Self {
113 let options = ConvOptions::new(stride, padding_begin, dilation, groups);
114 let padding_end = (padding_begin != padding_end).then_some(padding_end);
115 Self {
116 options,
117 padding_end,
118 }
119 }
120
121 pub fn is_asymmetric(&self) -> bool {
123 self.padding_end.is_some()
124 }
125}
126
127#[allow(deprecated)]
128impl<const N: usize> From<PaddedConvOptions<N>> for ConvOptions<N> {
129 fn from(value: PaddedConvOptions<N>) -> Self {
130 let Some(padding_end) = value.padding_end else {
131 return value.options;
132 };
133 let padding_begin = value.options.padding_begin();
134 let padding = core::array::from_fn(|i| (padding_begin[i], padding_end[i]));
135
136 ConvOptions::new_with_padding(
137 value.options.stride,
138 padding,
139 value.options.dilation,
140 value.options.groups,
141 )
142 }
143}
144
145#[allow(deprecated)]
146impl<const N: usize> From<ConvOptions<N>> for PaddedConvOptions<N> {
147 fn from(options: ConvOptions<N>) -> Self {
148 if options.is_asymmetric() {
149 let padding_begin = options.padding_begin();
150 let padding_end = options.padding_end();
151 Self {
152 options: ConvOptions::new(
153 options.stride,
154 padding_begin,
155 options.dilation,
156 options.groups,
157 ),
158 padding_end: Some(padding_end),
159 }
160 } else {
161 Self {
162 options,
163 padding_end: None,
164 }
165 }
166 }
167}
168
169#[derive(Debug, Clone, Hash, PartialEq, Eq)]
171pub struct DeformConvOptions<const N: usize> {
172 pub stride: [usize; N],
174
175 pub padding: [usize; N],
177
178 pub dilation: [usize; N],
180
181 pub weight_groups: usize,
183
184 pub offset_groups: usize,
186}
187
188impl<const N: usize> DeformConvOptions<N> {
189 pub fn new(
191 stride: [usize; N],
192 padding: [usize; N],
193 dilation: [usize; N],
194 weight_groups: usize,
195 offset_groups: usize,
196 ) -> Self {
197 Self {
198 stride: stride.map(|s| check_nonzero(s, "stride must be non-zero")),
199 padding,
200 dilation: dilation.map(|d| check_nonzero(d, "dilation must be non-zero")),
201 weight_groups: check_nonzero(weight_groups, "weight groups must be non-zero"),
202 offset_groups: check_nonzero(offset_groups, "offset groups must be non-zero"),
203 }
204 }
205}
206
207#[derive(Debug, Clone, Hash, PartialEq, Eq)]
209pub struct ConvTransposeOptions<const N: usize> {
210 pub stride: [usize; N],
212
213 pub padding: [usize; N],
215
216 pub padding_out: [usize; N],
218
219 pub dilation: [usize; N],
221
222 pub groups: usize,
224}
225
226impl<const N: usize> ConvTransposeOptions<N> {
227 pub fn new(
229 stride: [usize; N],
230 padding: [usize; N],
231 padding_out: [usize; N],
232 dilation: [usize; N],
233 groups: usize,
234 ) -> Self {
235 Self {
236 stride: stride.map(|s| check_nonzero(s, "stride must be non-zero")),
237 padding,
238 padding_out,
239 dilation: dilation.map(|d| check_nonzero(d, "dilation must be non-zero")),
240 groups: check_nonzero(groups, "groups must be non-zero"),
241 }
242 }
243}
244
245#[derive(Debug, Clone)]
247pub struct UnfoldOptions {
248 pub stride: [usize; 2],
251
252 pub padding: [usize; 2],
254
255 pub dilation: [usize; 2],
257}
258
259impl UnfoldOptions {
260 pub fn new(stride: [usize; 2], padding: [usize; 2], dilation: [usize; 2]) -> Self {
262 Self {
263 stride: stride.map(|s| check_nonzero(s, "stride must be non-zero")),
264 padding,
265 dilation: dilation.map(|d| check_nonzero(d, "dilation must be non-zero")),
266 }
267 }
268}
269
270#[derive(new, Debug, Clone, serde::Deserialize, serde::Serialize)]
272pub enum InterpolateMode {
273 Nearest,
277
278 NearestExact,
281
282 Bilinear,
285
286 Bicubic,
289
290 Lanczos3,
293}
294
295#[derive(Debug, Clone)]
297pub struct InterpolateOptions {
298 pub mode: InterpolateMode,
300 pub align_corners: bool,
303}
304
305impl InterpolateOptions {
306 pub fn new(mode: InterpolateMode) -> Self {
309 Self {
310 mode,
311 align_corners: true,
312 }
313 }
314
315 pub fn with_align_corners(mut self, align_corners: bool) -> Self {
317 self.align_corners = align_corners;
318 self
319 }
320}
321
322#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, serde::Deserialize, serde::Serialize)]
326pub enum GridSamplePaddingMode {
327 #[default]
329 Zeros,
330 Border,
332 Reflection,
334}
335
336#[derive(Debug, Clone)]
338pub struct GridSampleOptions {
339 pub mode: InterpolateMode,
341 pub padding_mode: GridSamplePaddingMode,
343 pub align_corners: bool,
347}
348
349impl Default for GridSampleOptions {
350 fn default() -> Self {
351 Self {
352 mode: InterpolateMode::Bilinear,
353 padding_mode: GridSamplePaddingMode::Zeros,
354 align_corners: false,
355 }
356 }
357}
358
359impl From<InterpolateMode> for GridSampleOptions {
360 fn from(value: InterpolateMode) -> Self {
361 GridSampleOptions::new(value)
362 }
363}
364
365impl GridSampleOptions {
366 pub fn new(mode: InterpolateMode) -> Self {
370 Self {
371 mode,
372 ..Default::default()
373 }
374 }
375
376 pub fn with_padding_mode(mut self, padding_mode: GridSamplePaddingMode) -> Self {
378 self.padding_mode = padding_mode;
379 self
380 }
381
382 pub fn with_align_corners(mut self, align_corners: bool) -> Self {
384 self.align_corners = align_corners;
385 self
386 }
387}
388
389#[derive(Debug, Clone, Copy, PartialEq, serde::Deserialize, serde::Serialize)]
400pub enum PadMode {
401 Constant(f32),
407
408 Reflect,
416
417 Edge,
423}
424
425impl Default for PadMode {
426 fn default() -> Self {
427 PadMode::Constant(0.0)
428 }
429}
430
431impl<E: ElementConversion> From<E> for PadMode {
432 fn from(value: E) -> Self {
433 PadMode::Constant(value.elem())
434 }
435}
436
437#[derive(Debug, Clone, Copy, Default, PartialEq, serde::Deserialize, serde::Serialize)]
439pub struct AttentionModuleOptions {
440 pub scale: Option<f64>,
442
443 pub softcap: Option<f64>,
446
447 pub is_causal: bool,
452}
453
454#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
456pub enum IndexingUpdateOp {
457 Assign,
459 Add,
461 Mul,
463 Min,
465 Max,
467}
468
469#[cfg(test)]
470mod tests {
471 use super::*;
472
473 #[test]
474 fn conv_options_symmetric_constructor() {
475 let options = ConvOptions::new([1, 2], [3, 4], [5, 6], 7);
476
477 assert_eq!(options.padding, [(3, 3), (4, 4)]);
478 assert_eq!(options.padding_begin(), [3, 4]);
479 assert_eq!(options.padding_end(), [3, 4]);
480 assert_eq!(options.symmetric_padding(), [3, 4]);
481 assert!(!options.is_asymmetric());
482 }
483
484 #[test]
485 fn conv_options_explicit_padding_constructor() {
486 let options = ConvOptions::new_with_padding([1, 2], [(3, 4), (5, 6)], [7, 8], 9);
487
488 assert_eq!(options.padding, [(3, 4), (5, 6)]);
489 assert_eq!(options.padding_begin(), [3, 5]);
490 assert_eq!(options.padding_end(), [4, 6]);
491 assert!(options.is_asymmetric());
492 }
493
494 #[test]
495 #[allow(deprecated)]
496 fn padded_conv_options_convert_to_conv_options() {
497 let options: ConvOptions<2> =
498 PaddedConvOptions::asymmetric([1, 2], [3, 5], [4, 6], [7, 8], 9).into();
499
500 assert_eq!(options.padding, [(3, 4), (5, 6)]);
501 assert_eq!(options.stride, [1, 2]);
502 assert_eq!(options.dilation, [7, 8]);
503 assert_eq!(options.groups, 9);
504 }
505
506 #[test]
507 #[allow(deprecated)]
508 fn asymmetric_conv_options_roundtrip_through_padded_options() {
509 let expected = ConvOptions::new_with_padding([1, 2], [(3, 4), (5, 6)], [7, 8], 9);
510 let padded: PaddedConvOptions<2> = expected.clone().into();
511 let actual: ConvOptions<2> = padded.into();
512
513 assert_eq!(actual, expected);
514 }
515
516 #[test]
517 #[should_panic = "expected symmetric convolution padding"]
518 fn conv_options_symmetric_padding_with_asymmetric_options() {
519 let options = ConvOptions::new_with_padding([1], [(1, 2)], [1], 1);
520 let _ = options.symmetric_padding();
521 }
522
523 #[test]
524 #[should_panic = "stride must be non-zero"]
525 fn conv_options_stride_zero() {
526 let _opt = ConvOptions::new([0, 1], [0, 0], [1, 1], 1);
527 }
528
529 #[test]
530 #[should_panic = "dilation must be non-zero"]
531 fn conv_options_dilation_zero() {
532 let _opt = ConvOptions::new([1, 1], [0, 0], [0, 0], 1);
533 }
534
535 #[test]
536 #[should_panic = "groups must be non-zero"]
537 fn conv_options_groups_zero() {
538 let _opt = ConvOptions::new([1, 1], [0, 0], [1, 1], 0);
539 }
540
541 #[test]
542 #[should_panic = "stride must be non-zero"]
543 fn conv_transpose_options_stride_zero() {
544 let _opt = ConvTransposeOptions::new([0, 1], [0, 0], [0, 0], [1, 1], 1);
545 }
546
547 #[test]
548 #[should_panic = "dilation must be non-zero"]
549 fn conv_transpose_options_dilation_zero() {
550 let _opt = ConvTransposeOptions::new([1, 1], [0, 0], [0, 0], [0, 0], 1);
551 }
552
553 #[test]
554 #[should_panic = "groups must be non-zero"]
555 fn conv_transpose_options_groups_zero() {
556 let _opt = ConvTransposeOptions::new([1, 1], [0, 0], [0, 0], [1, 1], 0);
557 }
558
559 #[test]
560 #[should_panic = "stride must be non-zero"]
561 fn deform_conv_options_stride_zero() {
562 let _opt = DeformConvOptions::new([0, 1], [0, 0], [1, 1], 1, 1);
563 }
564
565 #[test]
566 #[should_panic = "dilation must be non-zero"]
567 fn deform_conv_options_dilation_zero() {
568 let _opt = DeformConvOptions::new([1, 1], [0, 0], [0, 0], 1, 1);
569 }
570
571 #[test]
572 #[should_panic = "weight groups must be non-zero"]
573 fn deform_conv_options_weights_groups_zero() {
574 let _opt = DeformConvOptions::new([1, 1], [0, 0], [1, 1], 0, 1);
575 }
576
577 #[test]
578 #[should_panic = "offset groups must be non-zero"]
579 fn deform_conv_options_offset_groups_zero() {
580 let _opt = DeformConvOptions::new([1, 1], [0, 0], [1, 1], 1, 0);
581 }
582
583 #[test]
584 #[should_panic = "stride must be non-zero"]
585 fn unfold_options_stride_zero() {
586 let _opt = UnfoldOptions::new([0, 1], [0, 0], [1, 1]);
587 }
588
589 #[test]
590 #[should_panic = "dilation must be non-zero"]
591 fn unfold_options_dilation_zero() {
592 let _opt = UnfoldOptions::new([1, 1], [0, 0], [0, 0]);
593 }
594}