Skip to main content

burn_ir/
builder.rs

1#![allow(missing_docs)]
2
3use alloc::vec::Vec;
4use burn_backend::{
5    DType, Distribution, Shape, Slice, SliceOps, calculate_matmul_output,
6    ops::{
7        conv::{
8            calculate_conv_output_shape, calculate_conv_transpose_output_shape,
9            calculate_pool_output_shape,
10        },
11        unfold::calculate_unfold_shape,
12    },
13    quantization::QuantScheme,
14    tensor::IndexingUpdateOp,
15};
16
17use crate::{ScalarIr, TensorId, TensorIr};
18
19use super::operation::*;
20
21impl CreationOpIr {
22    pub fn create(shape: Shape, dtype: DType, new_id: impl FnOnce() -> TensorId) -> Self {
23        let out = TensorIr::uninit(new_id(), shape, dtype);
24
25        CreationOpIr { out }
26    }
27}
28
29impl InitOperationIr {
30    pub fn create(shape: Shape, dtype: DType, new_id: impl FnOnce() -> TensorId) -> Self {
31        let out = TensorIr::uninit(new_id(), shape, dtype);
32
33        InitOperationIr { out }
34    }
35}
36
37impl RandomOpIr {
38    pub fn create(
39        shape: Shape,
40        dtype: DType,
41        distribution: Distribution,
42        new_id: impl FnOnce() -> TensorId,
43    ) -> Self {
44        let out = TensorIr::uninit(new_id(), shape, dtype);
45
46        RandomOpIr { out, distribution }
47    }
48}
49
50impl FullOpIr {
51    pub fn create(
52        shape: Shape,
53        dtype: DType,
54        value: ScalarIr,
55        new_id: impl FnOnce() -> TensorId,
56    ) -> Self {
57        // TODO: check that ScalarIr dtype matches dtype?
58        let out = TensorIr::uninit(new_id(), shape, dtype);
59
60        FullOpIr { out, value }
61    }
62}
63
64impl CastOpIr {
65    pub fn create(input: TensorIr, dtype: DType, new_id: impl FnOnce() -> TensorId) -> Self {
66        let out = TensorIr::uninit(new_id(), input.shape.clone(), dtype);
67        CastOpIr { input, out }
68    }
69}
70
71impl ShapeOpIr {
72    pub fn expand(input: TensorIr, shape: Shape, new_id: impl FnOnce() -> TensorId) -> Self {
73        let shape = input.shape.expand(shape).unwrap();
74        Self::create(input, shape, new_id)
75    }
76
77    pub fn reshape(input: TensorIr, shape: Shape, new_id: impl FnOnce() -> TensorId) -> Self {
78        let shape = input.shape.reshape(shape).unwrap();
79        Self::create(input, shape, new_id)
80    }
81
82    fn create(input: TensorIr, shape: Shape, new_id: impl FnOnce() -> TensorId) -> Self {
83        let out = TensorIr::uninit(new_id(), shape, input.dtype);
84        ShapeOpIr { input, out }
85    }
86}
87
88// "Lower" specific operations into a binary or unary op representation.
89// Useful when collecting inputs and outputs and don't care about the other semantics.
90impl From<MatmulOpIr> for BinaryOpIr {
91    fn from(value: MatmulOpIr) -> Self {
92        Self {
93            lhs: value.lhs,
94            rhs: value.rhs,
95            out: value.out,
96        }
97    }
98}
99
100impl From<ReduceOpIr> for UnaryOpIr {
101    fn from(value: ReduceOpIr) -> Self {
102        Self {
103            input: value.input,
104            out: value.out,
105        }
106    }
107}
108
109#[derive(Debug)]
110#[allow(missing_docs)]
111pub enum IrError {
112    DTypeMismatch,
113}
114
115fn dtype_compat(lhs: &DType, rhs: &DType) -> bool {
116    let lhs_qfloat = matches!(lhs, DType::QFloat(_));
117    let rhs_qfloat = matches!(rhs, DType::QFloat(_));
118    if lhs_qfloat && (rhs_qfloat || rhs.is_float())
119        || lhs.is_float() && (rhs_qfloat || rhs.is_float())
120    {
121        true
122    } else {
123        lhs == rhs
124    }
125}
126
127fn output_check<'a, I>(inputs: I, compat: impl Fn(&DType, &DType) -> bool) -> Result<DType, IrError>
128where
129    I: IntoIterator<Item = &'a DType>,
130{
131    let mut iter = inputs.into_iter();
132    let first = iter.next().unwrap();
133    for d in iter {
134        if !compat(first, d) {
135            return Err(IrError::DTypeMismatch);
136        }
137    }
138    Ok(*first)
139}
140
141fn output_dtype<'a, I: IntoIterator<Item = &'a DType>>(inputs: I) -> Result<DType, IrError> {
142    output_check(inputs, |a, b| a == b)
143}
144
145fn output_dtype_mixed<'a, I: IntoIterator<Item = &'a DType>>(inputs: I) -> Result<DType, IrError> {
146    output_check(inputs, dtype_compat)
147}
148
149/// Macro to implement `create` constructors for operations with a single output.
150///
151/// Supports shape and dtype validation.
152macro_rules! impl_ir_create {
153    (@create_fn $op:ident { $( $field:ident : $ty:ty ),* $(,)? } , $shape:expr, $dtype:expr) => {
154        #[doc = "Create a new operation IR from the given inputs."]
155        #[doc = "`new_id` should generate a unique `TensorId` for the uninitialized output tensor."]
156        #[allow(clippy::too_many_arguments)]
157        pub fn create($( $field : $ty ),*, new_id: impl FnOnce() -> crate::TensorId) -> $op {
158            let shape = $shape;
159            let dtype = $dtype;
160            let out = TensorIr::uninit(new_id(), shape, dtype);
161            $op { $( $field ),*, out }
162        }
163    };
164
165    // Case: simple op, single `create`
166    (
167        $op:ident { $( $field:ident : $ty:ty ),* $(,)? },
168        shape = $shape:expr,
169        dtype = $dtype:expr
170    ) => {
171        impl $op {
172            impl_ir_create!(@create_fn $op { $( $field : $ty ),* }, $shape, $dtype);
173        }
174    };
175
176    // Case: op with one additional constructor that accepts an explicit output dtype
177    (
178        $op:ident { $( $field:ident : $ty:ty ),* $(,)? },
179        shape = $shape:expr,
180        dtype = $dtype:expr,
181        $fn_name:ident ( $extra:ident : $extra_ty:ty )
182    ) => {
183        impl $op {
184            impl_ir_create!(@create_fn $op { $( $field : $ty ),* }, $shape, $dtype);
185
186            #[doc = "Create a new operation IR from the given inputs and the given output dtype."]
187            #[allow(clippy::too_many_arguments)]
188            pub fn $fn_name($( $field : $ty ),*, $extra: $extra_ty, new_id: impl FnOnce() -> crate::TensorId) -> Self {
189                let shape = $shape;
190                let _ = $dtype; // still validates dtype if needed
191                let out = TensorIr::uninit(new_id(), shape, $extra);
192                $op { $( $field ),*, out }
193            }
194        }
195    };
196}
197
198impl_ir_create!(
199    UnaryOpIr { input: TensorIr },
200    shape = input.shape.clone(),
201    dtype = input.dtype,
202    // Additional constructor for unary comparisons
203    create_comparison(bool_dtype: DType)
204);
205
206impl_ir_create!(
207    BinaryOpIr {
208        lhs: TensorIr,
209        rhs: TensorIr
210    },
211    shape = lhs.shape.broadcast(&rhs.shape).unwrap(),
212    dtype = output_dtype([&lhs.dtype, &rhs.dtype]).unwrap(),
213    // Additional constructor for binary comparisons
214    create_comparison(bool_dtype: DType)
215);
216
217impl_ir_create!(
218    ScalarOpIr {
219        lhs: TensorIr,
220        rhs: ScalarIr
221    },
222    shape = lhs.shape.clone(),
223    dtype = lhs.dtype,
224    // Additional constructor for scalar comparisons
225    create_comparison(bool_dtype: DType)
226);
227
228impl_ir_create!(
229    MatmulOpIr {
230        lhs: TensorIr,
231        rhs: TensorIr
232    },
233    shape = calculate_matmul_output(&lhs.shape, &rhs.shape).unwrap(),
234    dtype = output_dtype_mixed([&lhs.dtype, &rhs.dtype]).unwrap(),
235    // Additional constructor for mixed dtypes
236    create_mixed(out_dtype: DType)
237);
238
239impl_ir_create!(
240    SwapDimsOpIr {
241        input: TensorIr,
242        dim1: usize,
243        dim2: usize
244    },
245    shape = input.shape.clone().swapped(dim1, dim2).unwrap(),
246    dtype = input.dtype
247);
248
249impl_ir_create!(
250    PermuteOpIr { input: TensorIr, axes: Vec<usize> },
251    shape = input.shape.clone().permuted(&axes).unwrap(),
252    dtype = input.dtype
253);
254
255impl_ir_create!(
256    RepeatDimOpIr {
257        tensor: TensorIr,
258        dim: usize,
259        times: usize
260    },
261    shape = tensor.shape.clone().repeat(dim, times).unwrap(),
262    dtype = tensor.dtype
263);
264
265impl_ir_create!(
266    FlipOpIr { input: TensorIr, axes: Vec<usize> },
267    shape = input.shape.clone(), // TODO: check if axes are within the tensor dimensions
268    dtype = input.dtype
269);
270
271impl_ir_create!(
272    CatOpIr { tensors: Vec<TensorIr>, dim: usize },
273    shape = Shape::cat(tensors.iter().map(|t| &t.shape), dim).unwrap(),
274    dtype = output_dtype(tensors.iter().map(|t| &t.dtype)).unwrap()
275);
276
277#[cfg(feature = "distributed")]
278impl_ir_create!(
279    AllReduceOpIr { tensor: TensorIr },
280    shape = tensor.shape.clone(),
281    dtype = tensor.dtype
282);
283
284impl_ir_create!(
285    GatherOpIr {
286        tensor: TensorIr,
287        dim: usize,
288        indices: TensorIr
289    },
290    shape = indices.shape.clone(), // TODO: check dims compat between tensor and indices
291    dtype = tensor.dtype
292);
293
294impl_ir_create!(
295    ScatterOpIr {
296        tensor: TensorIr,
297        dim: usize,
298        indices: TensorIr,
299        value: TensorIr,
300        update: IndexingUpdateOp
301    },
302    shape = tensor.shape.clone(), // TODO: check dims compat between tensor and indices
303    dtype = output_dtype([&tensor.dtype, &value.dtype]).unwrap()
304);
305
306impl_ir_create!(
307    ScatterNdOpIr {
308        data: TensorIr,
309        indices: TensorIr,
310        values: TensorIr,
311        reduction: IndexingUpdateOp
312    },
313    shape = data.shape.clone(),
314    dtype = output_dtype([&data.dtype, &values.dtype]).unwrap()
315);
316
317impl GatherNdOpIr {
318    /// Create a new GatherNd IR operation.
319    pub fn create(
320        data: TensorIr,
321        indices: TensorIr,
322        new_id: impl FnOnce() -> crate::TensorId,
323    ) -> Self {
324        let m = indices.shape.num_dims();
325        let k = indices.shape[m - 1];
326        let mut dims = indices.shape.as_slice()[..m - 1].to_vec();
327        dims.extend_from_slice(&data.shape.as_slice()[k..]);
328        let shape = Shape::from(dims);
329        let dtype = data.dtype;
330        let out = TensorIr::uninit(new_id(), shape, dtype);
331        GatherNdOpIr { data, indices, out }
332    }
333}
334
335impl_ir_create!(
336    ReduceOpIr { input: TensorIr },
337    shape = [1].into(),
338    dtype = input.dtype
339);
340
341fn reduce_output_shape(mut output_shape: Shape, axis: usize, accumulator_len: usize) -> Shape {
342    assert!(output_shape.rank() > axis);
343    output_shape[axis] = accumulator_len;
344    output_shape
345}
346
347impl_ir_create!(
348    ReduceDimOpIr {
349        input: TensorIr,
350        axis: usize,
351        accumulator_len: usize,
352    },
353    shape = reduce_output_shape(input.shape.clone(), axis, accumulator_len),
354    dtype = input.dtype,
355    // Additional constructor for argument reduction
356    create_arg(ind_dtype: DType)
357);
358
359impl_ir_create!(
360    DimOpIr {
361        input: TensorIr,
362        axis: usize
363    },
364    shape = input.shape.clone(), // TODO: check dims within rank
365    dtype = input.dtype
366);
367
368impl_ir_create!(
369    SelectOpIr {
370        tensor: TensorIr,
371        dim: usize,
372        indices: TensorIr
373    },
374    // TODO: shape.select?
375    shape = {
376        let mut s = tensor.shape.clone();
377        s[dim] = indices.shape[0];
378        s
379    },
380    dtype = tensor.dtype
381);
382
383impl_ir_create!(
384    SelectAssignOpIr {
385        tensor: TensorIr,
386        dim: usize,
387        indices: TensorIr,
388        value: TensorIr,
389        update: IndexingUpdateOp
390    },
391    // TODO: check value and indices shape match for dim
392    shape = tensor.shape.clone(),
393    dtype = output_dtype([&tensor.dtype, &value.dtype]).unwrap()
394);
395
396impl_ir_create!(
397    SliceOpIr {
398        tensor: TensorIr,
399        ranges: Vec<Slice>,
400    },
401    shape = tensor.shape.clone().slice(&ranges).unwrap(),
402    dtype = tensor.dtype
403);
404
405impl_ir_create!(
406    SliceAssignOpIr {
407        tensor: TensorIr,
408        ranges: Vec<Slice>,
409        value: TensorIr
410    },
411    // TODO: check slice and value number of elements match
412    shape = tensor.shape.clone(),
413    dtype = output_dtype([&tensor.dtype, &value.dtype]).unwrap()
414);
415
416impl_ir_create!(
417    MaskWhereOpIr {
418        tensor: TensorIr,
419        mask: TensorIr,
420        value: TensorIr
421    },
422    shape = Shape::broadcast_many([&tensor.shape, &mask.shape, &value.shape]).unwrap(),
423    dtype = output_dtype([&tensor.dtype, &value.dtype]).unwrap()
424);
425
426impl_ir_create!(
427    MaskFillOpIr {
428        tensor: TensorIr,
429        mask: TensorIr,
430        value: ScalarIr
431    },
432    shape = tensor.shape.broadcast(&mask.shape).unwrap(),
433    dtype = tensor.dtype
434);
435
436impl_ir_create!(
437    ClampOpIr {
438        tensor: TensorIr,
439        min: ScalarIr,
440        max: ScalarIr
441    },
442    shape = tensor.shape.clone(),
443    dtype = tensor.dtype
444);
445
446impl_ir_create!(
447    AvgPool1dOpIr {
448        x: TensorIr,
449        kernel_size: usize,
450        stride: usize,
451        padding: usize,
452        count_include_pad: bool,
453        ceil_mode: bool
454    },
455    shape = calculate_pool_output_shape(
456        &x.shape,
457        &[kernel_size],
458        &[stride],
459        &[padding],
460        &[1],
461        ceil_mode
462    )
463    .unwrap(),
464    dtype = x.dtype
465);
466
467impl_ir_create!(
468    AvgPool1dBackwardOpIr {
469        x: TensorIr,
470        grad: TensorIr,
471        kernel_size: usize,
472        stride: usize,
473        padding: usize,
474        count_include_pad: bool,
475        ceil_mode: bool
476    },
477    shape = x.shape.clone(),
478    dtype = x.dtype
479);
480
481impl_ir_create!(
482    AvgPool2dOpIr {
483        x: TensorIr,
484        kernel_size: [usize; 2],
485        stride: [usize; 2],
486        padding: [usize; 2],
487        count_include_pad: bool,
488        ceil_mode: bool
489    },
490    shape = calculate_pool_output_shape(
491        &x.shape,
492        &kernel_size,
493        &stride,
494        &padding,
495        &[1, 1],
496        ceil_mode
497    )
498    .unwrap(),
499    dtype = x.dtype
500);
501
502impl_ir_create!(
503    AvgPool2dBackwardOpIr {
504        x: TensorIr,
505        grad: TensorIr,
506        kernel_size: [usize; 2],
507        stride: [usize; 2],
508        padding: [usize; 2],
509        count_include_pad: bool,
510        ceil_mode: bool
511    },
512    shape = x.shape.clone(),
513    dtype = x.dtype
514);
515
516impl_ir_create!(
517    MaxPool1dOpIr {
518        x: TensorIr,
519        kernel_size: usize,
520        stride: usize,
521        padding: usize,
522        dilation: usize,
523        ceil_mode: bool
524    },
525    shape = calculate_pool_output_shape(
526        &x.shape,
527        &[kernel_size],
528        &[stride],
529        &[padding],
530        &[dilation],
531        ceil_mode
532    )
533    .unwrap(),
534    dtype = x.dtype
535);
536
537impl_ir_create!(
538    MaxPool2dOpIr {
539        x: TensorIr,
540        kernel_size: [usize; 2],
541        stride: [usize; 2],
542        padding: [usize; 2],
543        dilation: [usize; 2],
544        ceil_mode: bool
545    },
546    shape = calculate_pool_output_shape(
547        &x.shape,
548        &kernel_size,
549        &stride,
550        &padding,
551        &dilation,
552        ceil_mode
553    )
554    .unwrap(),
555    dtype = x.dtype
556);
557
558impl_ir_create!(
559    MaxPool1dWithIndicesBackwardOpIr {
560        x: TensorIr,
561        grad: TensorIr,
562        indices: TensorIr,
563        kernel_size: usize,
564        stride: usize,
565        padding: usize,
566        dilation: usize,
567        ceil_mode: bool
568    },
569    shape = x.shape.clone(),
570    dtype = x.dtype
571);
572
573impl_ir_create!(
574    MaxPool2dWithIndicesBackwardOpIr {
575        x: TensorIr,
576        grad: TensorIr,
577        indices: TensorIr,
578        kernel_size: [usize; 2],
579        stride: [usize; 2],
580        padding: [usize; 2],
581        dilation: [usize; 2],
582        ceil_mode: bool
583    },
584    shape = x.shape.clone(),
585    dtype = x.dtype
586);
587
588impl_ir_create!(
589    AdaptiveAvgPool1dOpIr {
590        x: TensorIr,
591        output_size: usize
592    },
593    shape = Shape::new([x.shape[0], x.shape[1], output_size]),
594    dtype = x.dtype
595);
596
597impl_ir_create!(
598    AdaptiveAvgPool2dOpIr {
599        x: TensorIr,
600        output_size: [usize; 2]
601    },
602    shape = Shape::new([x.shape[0], x.shape[1], output_size[0], output_size[1]]),
603    dtype = x.dtype
604);
605
606impl_ir_create!(
607    AdaptiveAvgPool1dBackwardOpIr {
608        x: TensorIr,
609        grad: TensorIr,
610    },
611    shape = x.shape.clone(),
612    dtype = x.dtype
613);
614
615impl_ir_create!(
616    AdaptiveAvgPool2dBackwardOpIr {
617        x: TensorIr,
618        grad: TensorIr,
619    },
620    shape = x.shape.clone(),
621    dtype = x.dtype
622);
623
624impl_ir_create!(
625    InterpolateOpIr {
626        x: TensorIr,
627        output_size: [usize; 2],
628        options: InterpolateOptionsIr
629    },
630    shape = Shape::new([x.shape[0], x.shape[1], output_size[0], output_size[1]]),
631    dtype = x.dtype
632);
633
634impl_ir_create!(
635    InterpolateBackwardOpIr {
636        x: TensorIr,
637        grad: TensorIr,
638        output_size: [usize; 2],
639        options: InterpolateOptionsIr
640    },
641    shape = x.shape.clone(),
642    dtype = x.dtype
643);
644
645impl_ir_create!(
646    GridSample2dOpIr {
647        tensor: TensorIr,
648        grid: TensorIr,
649        options: GridSampleOptionsIr
650    },
651    // Input tensor: [N, C, H_in, W_in]
652    // Grid: [N, H_out, W_out, 2]
653    // Output: [N, C, H_out, W_out]
654    shape = Shape::new([
655        tensor.shape[0],
656        tensor.shape[1],
657        grid.shape[1],
658        grid.shape[2]
659    ]),
660    dtype = tensor.dtype
661);
662
663impl_ir_create!(
664    LinearOpIr {
665        x: TensorIr,
666        weight: TensorIr,
667        bias: Option<TensorIr>
668    },
669    shape = {
670        // output: [..., d_output] where x is [..., d_input] and weight is [d_input, d_output]
671        let n = x.shape.num_dims();
672        let mut dims: Vec<usize> = (0..n).map(|i| x.shape[i]).collect();
673        dims[n - 1] = weight.shape[1];
674        Shape::from(dims)
675    },
676    dtype = output_dtype(
677            [
678                Some(&x.dtype),
679                Some(&weight.dtype),
680                bias.as_ref().map(|b| &b.dtype),
681            ]
682            .iter()
683            .filter_map(|&d| d),
684        )
685        .unwrap()
686);
687
688impl_ir_create!(
689    LinearXBackwardOpIr {
690        weight: TensorIr,
691        output_grad: TensorIr,
692    },
693    shape = {
694        // dx = output_grad @ weight^T
695        // output_grad: [..., d_output], weight: [d_input, d_output]
696        // result: [..., d_input]
697        let n = output_grad.shape.num_dims();
698        let mut dims: Vec<usize> = (0..n).map(|i| output_grad.shape[i]).collect();
699        dims[n - 1] = weight.shape[0];
700        Shape::from(dims)
701    },
702    dtype = output_grad.dtype
703);
704
705impl_ir_create!(
706    LinearWeightBackwardOpIr {
707        x: TensorIr,
708        output_grad: TensorIr,
709    },
710    shape = {
711        // dW: [d_input, d_output]
712        let d_input = x.shape[x.shape.num_dims() - 1];
713        let d_output = output_grad.shape[output_grad.shape.num_dims() - 1];
714        Shape::from(alloc::vec![d_input, d_output])
715    },
716    dtype = output_grad.dtype
717);
718
719impl_ir_create!(
720    LinearBiasBackwardOpIr {
721        output_grad: TensorIr,
722    },
723    shape = {
724        // db: [d_output]
725        let d_output = output_grad.shape[output_grad.shape.num_dims() - 1];
726        Shape::from(alloc::vec![d_output])
727    },
728    dtype = output_grad.dtype
729);
730
731impl_ir_create!(
732    Conv1dOpIr {
733        x: TensorIr,
734        weight: TensorIr,
735        bias: Option<TensorIr>,
736        options: Conv1dOptionsIr
737    },
738    shape = calculate_conv_output_shape(
739            &x.shape,
740            &weight.shape,
741            &options.stride,
742            &options.padding,
743            &options.dilation,
744        )
745        .unwrap(),
746    dtype = output_dtype(
747            [
748                Some(&x.dtype),
749                Some(&weight.dtype),
750                bias.as_ref().map(|b| &b.dtype),
751            ]
752            .iter()
753            .filter_map(|&d| d),
754        )
755        .unwrap()
756);
757
758impl_ir_create!(
759    Conv1dXBackwardOpIr {
760        x: TensorIr,
761        weight: TensorIr,
762        output_grad: TensorIr,
763        options: Conv1dOptionsIr
764    },
765    shape = x.shape.clone(),
766    dtype = output_grad.dtype
767);
768
769impl_ir_create!(
770    Conv1dWeightBackwardOpIr {
771        x: TensorIr,
772        weight: TensorIr,
773        output_grad: TensorIr,
774        options: Conv1dOptionsIr
775    },
776    shape = weight.shape.clone(),
777    dtype = output_grad.dtype
778);
779
780impl_ir_create!(
781    Conv1dBiasBackwardOpIr {
782        x: TensorIr,
783        bias: TensorIr,
784        output_grad: TensorIr,
785    },
786    shape = bias.shape.clone(),
787    dtype = output_grad.dtype
788);
789
790impl_ir_create!(
791    Conv2dOpIr {
792        x: TensorIr,
793        weight: TensorIr,
794        bias: Option<TensorIr>,
795        options: Conv2dOptionsIr
796    },
797    shape = calculate_conv_output_shape(
798            &x.shape,
799            &weight.shape,
800            &options.stride,
801            &options.padding,
802            &options.dilation,
803        )
804        .unwrap(),
805    dtype = output_dtype(
806            [
807                Some(&x.dtype),
808                Some(&weight.dtype),
809                bias.as_ref().map(|b| &b.dtype),
810            ]
811            .iter()
812            .filter_map(|&d| d),
813        )
814        .unwrap()
815);
816
817impl_ir_create!(
818    Conv2dXBackwardOpIr {
819        x: TensorIr,
820        weight: TensorIr,
821        output_grad: TensorIr,
822        options: Conv2dOptionsIr
823    },
824    shape = x.shape.clone(),
825    dtype = output_grad.dtype
826);
827
828impl_ir_create!(
829    Conv2dWeightBackwardOpIr {
830        x: TensorIr,
831        weight: TensorIr,
832        output_grad: TensorIr,
833        options: Conv2dOptionsIr
834    },
835    shape = weight.shape.clone(),
836    dtype = output_grad.dtype
837);
838
839impl_ir_create!(
840    Conv2dBiasBackwardOpIr {
841        x: TensorIr,
842        bias: TensorIr,
843        output_grad: TensorIr,
844    },
845    shape = bias.shape.clone(),
846    dtype = output_grad.dtype
847);
848
849impl_ir_create!(
850    Conv3dOpIr {
851        x: TensorIr,
852        weight: TensorIr,
853        bias: Option<TensorIr>,
854        options: Conv3dOptionsIr
855    },
856    shape = calculate_conv_output_shape(
857            &x.shape,
858            &weight.shape,
859            &options.stride,
860            &options.padding,
861            &options.dilation,
862        )
863        .unwrap(),
864    dtype = output_dtype(
865            [
866                Some(&x.dtype),
867                Some(&weight.dtype),
868                bias.as_ref().map(|b| &b.dtype),
869            ]
870            .iter()
871            .filter_map(|&d| d),
872        )
873        .unwrap()
874);
875
876impl_ir_create!(
877    Conv3dXBackwardOpIr {
878        x: TensorIr,
879        weight: TensorIr,
880        output_grad: TensorIr,
881        options: Conv3dOptionsIr
882    },
883    shape = x.shape.clone(),
884    dtype = output_grad.dtype
885);
886
887impl_ir_create!(
888    Conv3dWeightBackwardOpIr {
889        x: TensorIr,
890        weight: TensorIr,
891        output_grad: TensorIr,
892        options: Conv3dOptionsIr
893    },
894    shape = weight.shape.clone(),
895    dtype = output_grad.dtype
896);
897
898impl_ir_create!(
899    Conv3dBiasBackwardOpIr {
900        x: TensorIr,
901        bias: TensorIr,
902        output_grad: TensorIr,
903    },
904    shape = bias.shape.clone(),
905    dtype = output_grad.dtype
906);
907
908impl_ir_create!(
909    DeformConv2dOpIr {
910        x: TensorIr,
911        offset: TensorIr,
912        weight: TensorIr,
913        mask: Option<TensorIr>,
914        bias: Option<TensorIr>,
915        options: DeformableConv2dOptionsIr
916    },
917    shape = calculate_conv_output_shape(
918            &x.shape,
919            &weight.shape,
920            &options.stride,
921            &options.padding,
922            &options.dilation,
923        )
924        .unwrap(),
925    dtype = output_dtype(
926            [
927                Some(&x.dtype),
928                Some(&offset.dtype),
929                Some(&weight.dtype),
930                mask.as_ref().map(|m| &m.dtype),
931                bias.as_ref().map(|b| &b.dtype),
932            ]
933            .iter()
934            .filter_map(|&d| d),
935        )
936        .unwrap()
937);
938
939impl_ir_create!(
940    ConvTranspose1dOpIr {
941        x: TensorIr,
942        weight: TensorIr,
943        bias: Option<TensorIr>,
944        options: ConvTranspose1dOptionsIr
945    },
946    shape = calculate_conv_transpose_output_shape(
947            &x.shape,
948            &weight.shape,
949            &options.stride,
950            &options.padding,
951            &options.padding_out,
952            &options.dilation,
953            options.groups,
954        )
955        .unwrap(),
956    dtype = output_dtype(
957            [
958                Some(&x.dtype),
959                Some(&weight.dtype),
960                bias.as_ref().map(|b| &b.dtype),
961            ]
962            .iter()
963            .filter_map(|&d| d),
964        )
965        .unwrap()
966);
967
968impl_ir_create!(
969    ConvTranspose2dOpIr {
970        x: TensorIr,
971        weight: TensorIr,
972        bias: Option<TensorIr>,
973        options: ConvTranspose2dOptionsIr
974    },
975    shape = calculate_conv_transpose_output_shape(
976            &x.shape,
977            &weight.shape,
978            &options.stride,
979            &options.padding,
980            &options.padding_out,
981            &options.dilation,
982            options.groups,
983        )
984        .unwrap(),
985    dtype = output_dtype(
986            [
987                Some(&x.dtype),
988                Some(&weight.dtype),
989                bias.as_ref().map(|b| &b.dtype),
990            ]
991            .iter()
992            .filter_map(|&d| d),
993        )
994        .unwrap()
995);
996
997impl_ir_create!(
998    ConvTranspose3dOpIr {
999        x: TensorIr,
1000        weight: TensorIr,
1001        bias: Option<TensorIr>,
1002        options: ConvTranspose3dOptionsIr
1003    },
1004    shape = calculate_conv_transpose_output_shape(
1005            &x.shape,
1006            &weight.shape,
1007            &options.stride,
1008            &options.padding,
1009            &options.padding_out,
1010            &options.dilation,
1011            options.groups,
1012        )
1013        .unwrap(),
1014    dtype = output_dtype(
1015            [
1016                Some(&x.dtype),
1017                Some(&weight.dtype),
1018                bias.as_ref().map(|b| &b.dtype),
1019            ]
1020            .iter()
1021            .filter_map(|&d| d),
1022        )
1023        .unwrap()
1024);
1025
1026impl_ir_create!(
1027    UnfoldOpIr {
1028        input: TensorIr,
1029        dim: usize,
1030        size: usize,
1031        step: usize
1032    },
1033    shape = calculate_unfold_shape(input.shape.clone(), dim, size, step),
1034    dtype = input.dtype
1035);
1036
1037impl_ir_create!(
1038    CrossOpIr {
1039        lhs: TensorIr,
1040        rhs: TensorIr,
1041        dim: usize
1042    },
1043    shape = lhs.shape.broadcast(&rhs.shape).unwrap(),
1044    dtype = output_dtype([&lhs.dtype, &rhs.dtype]).unwrap()
1045);
1046
1047impl_ir_create!(
1048    QuantizeOpIr {
1049        tensor: TensorIr,
1050        qparams: QuantizationParametersIr,
1051        scheme: QuantScheme
1052    },
1053    shape = tensor.shape.clone(),
1054    dtype = DType::QFloat(scheme)
1055);
1056
1057impl_ir_create!(
1058    AttentionOpIr {
1059        query: TensorIr,
1060        key: TensorIr,
1061        value: TensorIr,
1062        mask: Option<TensorIr>,
1063        attn_bias: Option<TensorIr>,
1064        options: AttentionOptionsIr,
1065    },
1066    shape = Shape::new([query.shape[0], query.shape[1], query.shape[2], value.shape[3]]),
1067    dtype = query.dtype
1068);
1069
1070impl_ir_create!(
1071    CtcLossOpIr {
1072        log_probs: TensorIr,
1073        targets: TensorIr,
1074        input_lengths: TensorIr,
1075        target_lengths: TensorIr,
1076        blank: usize,
1077    },
1078    shape = Shape::new([log_probs.shape[1]]),
1079    dtype = log_probs.dtype
1080);
1081
1082impl_ir_create!(
1083    CtcLossBackwardOpIr {
1084        log_probs: TensorIr,
1085        targets: TensorIr,
1086        input_lengths: TensorIr,
1087        target_lengths: TensorIr,
1088        grad_loss: TensorIr,
1089        blank: usize,
1090    },
1091    shape = log_probs.shape.clone(),
1092    dtype = log_probs.dtype
1093);
1094
1095impl DequantizeOpIr {
1096    pub fn create(input: TensorIr, dtype: DType, new_id: impl FnOnce() -> TensorId) -> Self {
1097        let out = TensorIr::uninit(new_id(), input.shape.clone(), dtype);
1098
1099        DequantizeOpIr { input, out }
1100    }
1101}
1102
1103// Operations with multiple outputs
1104
1105impl ReduceDimWithIndicesOpIr {
1106    pub fn create(
1107        tensor: TensorIr,
1108        dim: usize,
1109        dtype_indices: DType,
1110        mut new_id: impl FnMut() -> TensorId,
1111    ) -> Self {
1112        let mut shape = tensor.shape.clone();
1113        shape[dim] = 1;
1114        let out = TensorIr::uninit(new_id(), shape.clone(), tensor.dtype);
1115        let out_indices = TensorIr::uninit(new_id(), shape.clone(), dtype_indices);
1116
1117        ReduceDimWithIndicesOpIr {
1118            tensor,
1119            dim,
1120            out,
1121            out_indices,
1122        }
1123    }
1124}
1125
1126impl DeformConv2dBackwardOpIr {
1127    #[allow(clippy::too_many_arguments)]
1128    pub fn create(
1129        x: TensorIr,
1130        offset: TensorIr,
1131        weight: TensorIr,
1132        mask: Option<TensorIr>,
1133        bias: Option<TensorIr>,
1134        out_grad: TensorIr,
1135        options: DeformableConv2dOptionsIr,
1136        mut new_id: impl FnMut() -> TensorId,
1137    ) -> Self {
1138        let dtype = output_dtype(
1139            [
1140                Some(&x.dtype),
1141                Some(&weight.dtype),
1142                mask.as_ref().map(|m| &m.dtype),
1143                bias.as_ref().map(|b| &b.dtype),
1144            ]
1145            .iter()
1146            .filter_map(|&d| d),
1147        )
1148        .unwrap();
1149
1150        let input_grad = TensorIr::uninit(new_id(), x.shape.clone(), dtype);
1151        let offset_grad = TensorIr::uninit(new_id(), offset.shape.clone(), dtype);
1152        let weight_grad = TensorIr::uninit(new_id(), weight.shape.clone(), dtype);
1153        let mask_grad = mask
1154            .as_ref()
1155            .map(|t| TensorIr::uninit(new_id(), t.shape.clone(), dtype));
1156        let bias_grad = bias
1157            .as_ref()
1158            .map(|t| TensorIr::uninit(new_id(), t.shape.clone(), dtype));
1159
1160        DeformConv2dBackwardOpIr {
1161            x,
1162            offset,
1163            weight,
1164            mask,
1165            bias,
1166            out_grad,
1167            options,
1168            input_grad,
1169            offset_grad,
1170            weight_grad,
1171            mask_grad,
1172            bias_grad,
1173        }
1174    }
1175}
1176
1177impl MaxPool1dWithIndicesOpIr {
1178    #[allow(clippy::too_many_arguments)]
1179    pub fn create(
1180        x: TensorIr,
1181        kernel_size: usize,
1182        stride: usize,
1183        padding: usize,
1184        dilation: usize,
1185        ceil_mode: bool,
1186        dtype_indices: DType,
1187        mut new_id: impl FnMut() -> TensorId,
1188    ) -> Self {
1189        let shape = calculate_pool_output_shape(
1190            &x.shape,
1191            &[kernel_size],
1192            &[stride],
1193            &[padding],
1194            &[dilation],
1195            ceil_mode,
1196        )
1197        .unwrap();
1198        let out = TensorIr::uninit(new_id(), shape.clone(), x.dtype);
1199        let out_indices = TensorIr::uninit(new_id(), shape, dtype_indices);
1200
1201        MaxPool1dWithIndicesOpIr {
1202            x,
1203            kernel_size,
1204            stride,
1205            padding,
1206            dilation,
1207            ceil_mode,
1208            out,
1209            out_indices,
1210        }
1211    }
1212}
1213
1214impl MaxPool2dWithIndicesOpIr {
1215    #[allow(clippy::too_many_arguments)]
1216    pub fn create(
1217        x: TensorIr,
1218        kernel_size: [usize; 2],
1219        stride: [usize; 2],
1220        padding: [usize; 2],
1221        dilation: [usize; 2],
1222        ceil_mode: bool,
1223        dtype_indices: DType,
1224        mut new_id: impl FnMut() -> TensorId,
1225    ) -> Self {
1226        let shape = calculate_pool_output_shape(
1227            &x.shape,
1228            &kernel_size,
1229            &stride,
1230            &padding,
1231            &dilation,
1232            ceil_mode,
1233        )
1234        .unwrap();
1235        let out = TensorIr::uninit(new_id(), shape.clone(), x.dtype);
1236        let out_indices = TensorIr::uninit(new_id(), shape, dtype_indices);
1237
1238        MaxPool2dWithIndicesOpIr {
1239            x,
1240            kernel_size,
1241            stride,
1242            padding,
1243            dilation,
1244            ceil_mode,
1245            out,
1246            out_indices,
1247        }
1248    }
1249}