1use alloc::{collections::BTreeMap, rc::Rc, string::String, sync::Arc, vec, vec::Vec};
18use core::{any::Any, cell::RefCell};
19
20use crate::{
21 dtype::{Dtype, Float, RankedTensor, Tensor},
22 nn::{
23 Layer,
24 activation::{
25 elu::{Celu, Elu, Selu},
26 gelu::{Gelu, Mish},
27 hard::{Hardshrink, Hardsigmoid, Hardswish, Hardtanh, Relu6},
28 misc::{LeakyRelu, Softplus, Softshrink, Softsign, Threshold},
29 relu::Relu,
30 sigmoid::{Logsigmoid, Sigmoid, Silu},
31 softmax::Softmax,
32 tanh::{Tanh, Tanhshrink},
33 },
34 batchnorm::{BatchNorm1d, BatchNorm2d, BatchNorm3d},
35 conv1d::Conv1d,
36 conv2d::Conv2d,
37 conv3d::Conv3d,
38 flatten::Flatten,
39 groupnorm::GroupNorm,
40 instancenorm::{InstanceNorm1d, InstanceNorm2d, InstanceNorm3d},
41 layernorm::LayerNorm,
42 linear::Linear,
43 pad::{
44 CircularPad1d, CircularPad2d, CircularPad3d, ConstantPad1d, ConstantPad2d,
45 ConstantPad3d, ReflectionPad1d, ReflectionPad2d, ReflectionPad3d, ReplicationPad1d,
46 ReplicationPad2d, ReplicationPad3d,
47 },
48 pool::{
49 AvgPool1d, AvgPool2d, AvgPool3d, LpPool1d, LpPool2d, LpPool3d, MaxPool1d, MaxPool2d,
50 MaxPool3d,
51 },
52 rmsnorm::RmsNorm,
53 },
54};
55
56pub mod compiler;
58
59pub type Shape = Vec<Option<usize>>;
67
68#[derive(Copy, Clone, Debug, PartialEq, Eq)]
75pub enum DtypeRepr {
76 Bool,
78 I8,
80 I16,
82 I32,
84 I64,
86 U8,
88 U16,
90 U32,
92 U64,
94 F16,
96 BF16,
98 F32,
100 F64,
102}
103
104pub trait CustomOp: Any + Send + Sync {
110 fn name(&self) -> &str;
112
113 fn infer_output_shape(&self, input_shapes: &[&Shape]) -> Shape;
115
116 fn as_any(&self) -> &dyn Any;
119
120 fn lower(&self) -> Option<(String, String, String, Arc<dyn crate::model::RuntimeOp>)> {
127 None
128 }
129
130 fn lower_backward_source(&self) -> String {
133 String::new()
134 }
135}
136
137#[derive(Clone)]
139pub struct CustomData(pub Arc<dyn CustomOp>);
140
141impl CustomData {
142 pub fn new<T: CustomOp>(op: T) -> Self {
144 Self(Arc::new(op))
145 }
146
147 pub fn name(&self) -> &str {
149 self.0.name()
150 }
151
152 pub fn infer_output_shape(&self, input_shapes: &[&Shape]) -> Shape {
154 self.0.infer_output_shape(input_shapes)
155 }
156
157 pub fn downcast_ref<T: Any>(&self) -> Option<&T> {
159 self.0.as_any().downcast_ref::<T>()
160 }
161}
162
163impl core::fmt::Debug for CustomData {
164 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
165 write!(f, "Custom({})", self.0.name())
166 }
167}
168
169#[derive(Debug, Clone)]
173pub enum Op {
174 Input,
176
177 Linear {
180 in_features: usize,
182 out_features: usize,
184 has_bias: bool,
186 },
187 Flatten,
189
190 BatchNorm1d {
193 num_features: usize,
195 eps: f64,
197 momentum: f64,
199 affine: bool,
201 track_running_stats: bool,
203 },
204 BatchNorm2d {
206 num_features: usize,
208 eps: f64,
210 momentum: f64,
212 affine: bool,
214 track_running_stats: bool,
216 },
217 BatchNorm3d {
219 num_features: usize,
221 eps: f64,
223 momentum: f64,
225 affine: bool,
227 track_running_stats: bool,
229 },
230 LayerNorm {
232 normalized_shape: alloc::vec::Vec<usize>,
234 eps: f64,
236 affine: bool,
238 },
239 RmsNorm {
241 normalized_shape: alloc::vec::Vec<usize>,
243 eps: f64,
245 affine: bool,
247 },
248 GroupNorm {
250 num_groups: usize,
252 num_channels: usize,
254 eps: f64,
256 affine: bool,
258 },
259 InstanceNorm1d {
261 num_features: usize,
263 eps: f64,
265 momentum: f64,
267 affine: bool,
269 track_running_stats: bool,
271 },
272 InstanceNorm2d {
274 num_features: usize,
276 eps: f64,
278 momentum: f64,
280 affine: bool,
282 track_running_stats: bool,
284 },
285 InstanceNorm3d {
287 num_features: usize,
289 eps: f64,
291 momentum: f64,
293 affine: bool,
295 track_running_stats: bool,
297 },
298
299 Conv1d {
302 in_channels: usize,
304 out_channels: usize,
306 kernel_l: usize,
308 stride: usize,
310 padding: usize,
312 has_bias: bool,
314 },
315 Conv2d {
317 in_channels: usize,
319 out_channels: usize,
321 kernel_h: usize,
323 kernel_w: usize,
325 stride_h: usize,
327 stride_w: usize,
329 padding_h: usize,
331 padding_w: usize,
333 groups: usize,
335 has_bias: bool,
337 },
338 Conv3d {
340 in_channels: usize,
342 out_channels: usize,
344 kernel_d: usize,
346 kernel_h: usize,
348 kernel_w: usize,
350 stride_d: usize,
352 stride_h: usize,
354 stride_w: usize,
356 padding_d: usize,
358 padding_h: usize,
360 padding_w: usize,
362 has_bias: bool,
364 },
365
366 Conv2dBnSilu {
375 in_channels: usize,
377 out_channels: usize,
379 kernel_h: usize,
381 kernel_w: usize,
383 stride_h: usize,
385 stride_w: usize,
387 padding_h: usize,
389 padding_w: usize,
391 groups: usize,
393 bn_eps: f64,
396 },
397
398 Fused {
409 members: alloc::vec::Vec<Op>,
411 },
412
413 AvgPool1d {
416 kernel_l: usize,
418 stride: usize,
420 },
421 AvgPool2d {
423 kernel_h: usize,
425 kernel_w: usize,
427 stride_h: usize,
429 stride_w: usize,
431 },
432 AvgPool3d {
434 kernel_d: usize,
436 kernel_h: usize,
438 kernel_w: usize,
440 stride_d: usize,
442 stride_h: usize,
444 stride_w: usize,
446 },
447 MaxPool1d {
449 kernel_l: usize,
451 stride: usize,
453 },
454 MaxPool2d {
456 kernel_h: usize,
458 kernel_w: usize,
460 stride_h: usize,
462 stride_w: usize,
464 pad_h: usize,
466 pad_w: usize,
468 },
469 MaxPool3d {
471 kernel_d: usize,
473 kernel_h: usize,
475 kernel_w: usize,
477 stride_d: usize,
479 stride_h: usize,
481 stride_w: usize,
483 },
484 LpPool1d {
486 kernel_l: usize,
488 stride: usize,
490 p: f64,
492 },
493 LpPool2d {
495 kernel_h: usize,
497 kernel_w: usize,
499 stride_h: usize,
501 stride_w: usize,
503 p: f64,
505 },
506 LpPool3d {
508 kernel_d: usize,
510 kernel_h: usize,
512 kernel_w: usize,
514 stride_d: usize,
516 stride_h: usize,
518 stride_w: usize,
520 p: f64,
522 },
523
524 UpsampleNearest2d {
528 scale_h: usize,
530 scale_w: usize,
532 },
533
534 ConstantPad1d {
537 pad_left: usize,
539 pad_right: usize,
541 value: f64,
543 },
544 ConstantPad2d {
546 pad_l: usize,
548 pad_r: usize,
550 pad_t: usize,
552 pad_b: usize,
554 value: f64,
556 },
557 ConstantPad3d {
559 pad_d1: usize,
561 pad_d2: usize,
563 pad_h1: usize,
565 pad_h2: usize,
567 pad_w1: usize,
569 pad_w2: usize,
571 value: f64,
573 },
574 ReflectionPad1d {
576 pad_left: usize,
578 pad_right: usize,
580 },
581 ReflectionPad2d {
583 pad_l: usize,
585 pad_r: usize,
587 pad_t: usize,
589 pad_b: usize,
591 },
592 ReflectionPad3d {
594 pad_d1: usize,
596 pad_d2: usize,
598 pad_h1: usize,
600 pad_h2: usize,
602 pad_w1: usize,
604 pad_w2: usize,
606 },
607 ReplicationPad1d {
609 pad_left: usize,
611 pad_right: usize,
613 },
614 ReplicationPad2d {
616 pad_l: usize,
618 pad_r: usize,
620 pad_t: usize,
622 pad_b: usize,
624 },
625 ReplicationPad3d {
627 pad_d1: usize,
629 pad_d2: usize,
631 pad_h1: usize,
633 pad_h2: usize,
635 pad_w1: usize,
637 pad_w2: usize,
639 },
640 CircularPad1d {
642 pad_left: usize,
644 pad_right: usize,
646 },
647 CircularPad2d {
649 pad_l: usize,
651 pad_r: usize,
653 pad_t: usize,
655 pad_b: usize,
657 },
658 CircularPad3d {
660 pad_d1: usize,
662 pad_d2: usize,
664 pad_h1: usize,
666 pad_h2: usize,
668 pad_w1: usize,
670 pad_w2: usize,
672 },
673
674 Relu,
677 Elu {
679 alpha: f64,
681 },
682 Selu,
684 Celu {
686 alpha: f64,
688 },
689 Gelu,
691 Mish,
693 Hardtanh {
695 min_val: f64,
697 max_val: f64,
699 },
700 Relu6,
702 Hardsigmoid,
704 Hardswish,
706 Hardshrink {
708 lambda: f64,
710 },
711 LeakyRelu {
713 negative_slope: f64,
715 },
716 Threshold {
718 threshold: f64,
720 value: f64,
722 },
723 Softsign,
725 Softshrink {
727 lambda: f64,
729 },
730 Softplus {
732 beta: f64,
734 threshold: f64,
736 },
737 Sigmoid,
739 Silu,
741 Logsigmoid,
743 Tanh,
745 Tanhshrink,
747 Softmax {
749 dim: usize,
751 },
752
753 Attention {
759 c: usize,
761 num_heads: usize,
763 key_dim: usize,
765 },
766
767 Add,
770 ChannelChunk {
773 c_total: usize,
775 chunk_c: usize,
777 chunk_offset: usize,
779 },
780 ChannelCat {
783 c_total: usize,
785 },
786 ChannelBiasAdd {
789 c: usize,
791 },
792
793 Custom {
796 data: CustomData,
798 },
799
800 Abs,
808 Neg,
810 Ceil,
812 Floor,
814 Round,
816 Sqrt,
818 Reciprocal,
820 Exp,
822 Log,
824 Erf,
826 Sign,
828 IsNaN,
830 IsInf {
832 detect_negative: bool,
834 detect_positive: bool,
836 },
837 Not,
839 BitwiseNot,
841 Sin,
843 Cos,
845 Tan,
847 Asin,
849 Acos,
851 Atan,
853 Sinh,
855 Cosh,
857 Asinh,
859 Acosh,
861 Atanh,
863
864 Mul,
867 Sub,
869 Div,
871 Pow,
873 Mod {
875 fmod: bool,
877 },
878 ElemMin,
880 ElemMax,
882 ElemMean,
884 ElemSum,
886 Equal,
888 Greater,
890 GreaterOrEqual,
892 Less,
894 LessOrEqual,
896 And,
898 Or,
900 Xor,
902 BitwiseAnd,
904 BitwiseOr,
906 BitwiseXor,
908 BitShift {
910 direction: alloc::string::String,
912 },
913
914 Reshape,
917 Transpose {
919 perm: alloc::vec::Vec<usize>,
921 },
922 Squeeze {
924 axes: alloc::vec::Vec<i64>,
926 },
927 Unsqueeze {
929 axes: alloc::vec::Vec<i64>,
931 },
932 Concat {
934 axis: i64,
936 },
937 Split {
939 axis: i64,
941 num_outputs: usize,
943 },
944 Slice,
946 Gather {
948 axis: i64,
950 },
951 GatherElements {
953 axis: i64,
955 },
956 GatherND {
958 batch_dims: i64,
960 },
961 ScatterElements {
963 axis: i64,
965 },
966 ScatterND,
968 Tile,
970 Expand,
972 ShapeOf {
974 start: i64,
976 end: i64,
978 },
979 SizeOf,
981 Identity,
983 Cast {
985 to: DtypeRepr,
987 },
988 CastLike,
990 Where,
992 Compress {
994 axis: i64,
996 },
997 Range,
999 Constant {
1001 dtype: DtypeRepr,
1003 shape: Shape,
1005 },
1006 ConstantOfShape {
1008 dtype: DtypeRepr,
1010 },
1011 Trilu {
1013 upper: bool,
1015 },
1016 BitCast {
1018 to: DtypeRepr,
1020 },
1021 Pad {
1023 mode: alloc::string::String,
1025 },
1026 ReverseSequence {
1028 batch_axis: i64,
1030 time_axis: i64,
1032 },
1033 NonZero,
1035 Scatter {
1037 axis: i64,
1039 },
1040 TensorScatter,
1042
1043 Gemm {
1046 alpha: f64,
1048 beta: f64,
1050 trans_a: bool,
1052 trans_b: bool,
1054 },
1055 MatMul,
1057 MatMulInteger,
1059 Einsum {
1061 equation: alloc::string::String,
1063 },
1064 Det,
1066 QLinearMatMul,
1068
1069 ConvTranspose {
1072 in_channels: usize,
1074 out_channels: usize,
1076 kernel_h: usize,
1078 kernel_w: usize,
1080 stride_h: usize,
1082 stride_w: usize,
1084 padding_h: usize,
1086 padding_w: usize,
1088 output_padding_h: usize,
1090 output_padding_w: usize,
1092 groups: usize,
1094 has_bias: bool,
1096 },
1097 ConvInteger {
1099 groups: usize,
1101 },
1102 DeformConv {
1104 group: usize,
1106 offset_group: usize,
1108 },
1109 QLinearConv {
1111 groups: usize,
1113 },
1114 Col2Im {
1116 kernel_h: usize,
1118 kernel_w: usize,
1120 },
1121 CausalConvWithState {
1124 activation: alloc::string::String,
1126 },
1127
1128 ReduceSum {
1131 keepdims: bool,
1133 noop_with_empty_axes: bool,
1135 },
1136 ReduceMean {
1138 keepdims: bool,
1140 noop_with_empty_axes: bool,
1142 },
1143 ReduceMax {
1145 keepdims: bool,
1147 noop_with_empty_axes: bool,
1149 },
1150 ReduceMin {
1152 keepdims: bool,
1154 noop_with_empty_axes: bool,
1156 },
1157 ReduceProd {
1159 keepdims: bool,
1161 noop_with_empty_axes: bool,
1163 },
1164 ReduceL1 {
1166 keepdims: bool,
1168 noop_with_empty_axes: bool,
1170 },
1171 ReduceL2 {
1173 keepdims: bool,
1175 noop_with_empty_axes: bool,
1177 },
1178 ReduceLogSum {
1180 keepdims: bool,
1182 noop_with_empty_axes: bool,
1184 },
1185 ReduceLogSumExp {
1187 keepdims: bool,
1189 noop_with_empty_axes: bool,
1191 },
1192 ReduceSumSquare {
1194 keepdims: bool,
1196 noop_with_empty_axes: bool,
1198 },
1199 CumSum {
1201 exclusive: bool,
1203 reverse: bool,
1205 },
1206 CumProd {
1208 exclusive: bool,
1210 reverse: bool,
1212 },
1213 ArgMax {
1215 axis: i64,
1217 keepdims: bool,
1219 select_last_index: bool,
1221 },
1222 ArgMin {
1224 axis: i64,
1226 keepdims: bool,
1228 select_last_index: bool,
1230 },
1231 GlobalAvgPool,
1233 GlobalMaxPool,
1235 LpNormalization {
1237 axis: i64,
1239 p: i64,
1241 },
1242 MeanVarianceNormalization {
1244 axes: alloc::vec::Vec<i64>,
1246 },
1247
1248 LogSoftmax {
1251 axis: i64,
1253 },
1254 Hardmax {
1256 axis: i64,
1258 },
1259 PRelu,
1261 ThresholdedRelu {
1263 alpha: f64,
1265 },
1266 Shrink {
1268 lambd: f64,
1270 bias: f64,
1272 },
1273 Clip,
1275 Swish,
1277 MultiHeadAttention {
1279 q_num_heads: usize,
1281 kv_num_heads: usize,
1283 },
1284 FlexAttention {
1288 scale: f64,
1290 },
1291 LinearAttention {
1294 q_num_heads: usize,
1296 kv_num_heads: usize,
1298 update_rule: alloc::string::String,
1300 scale: f64,
1302 },
1303
1304 LRN {
1307 alpha: f64,
1309 beta: f64,
1311 bias: f64,
1313 size: usize,
1315 },
1316
1317 Lstm {
1320 hidden_size: usize,
1322 direction: alloc::string::String,
1324 bidirectional: bool,
1326 },
1327 Gru {
1329 hidden_size: usize,
1331 direction: alloc::string::String,
1333 bidirectional: bool,
1335 },
1336 Rnn {
1338 hidden_size: usize,
1340 direction: alloc::string::String,
1342 bidirectional: bool,
1344 },
1345
1346 Resize {
1349 mode: alloc::string::String,
1351 coordinate_transformation_mode: alloc::string::String,
1353 antialias: bool,
1355 },
1356 GridSample {
1358 mode: alloc::string::String,
1360 padding_mode: alloc::string::String,
1362 align_corners: bool,
1364 },
1365 SpaceToDepth {
1367 blocksize: usize,
1369 },
1370 DepthToSpace {
1372 blocksize: usize,
1374 mode: alloc::string::String,
1376 },
1377 RoiAlign {
1379 output_h: usize,
1381 output_w: usize,
1383 sampling_ratio: i64,
1385 spatial_scale: f64,
1387 },
1388 AffineGrid {
1390 align_corners: bool,
1392 },
1393 MaxUnpool {
1395 kernel_h: usize,
1397 kernel_w: usize,
1399 stride_h: usize,
1401 stride_w: usize,
1403 },
1404 CenterCropPad {
1406 axes: alloc::vec::Vec<i64>,
1408 },
1409 NonMaxSuppression {
1411 center_point_box: bool,
1413 },
1414
1415 TopK {
1418 axis: i64,
1420 largest: bool,
1422 sorted: bool,
1424 },
1425 Unique {
1427 sorted: bool,
1429 },
1430 Dropout {
1432 training_mode: bool,
1434 },
1435 EyeLike {
1437 dtype: Option<DtypeRepr>,
1439 k: i64,
1441 },
1442 OneHot {
1444 axis: i64,
1446 },
1447 Bernoulli {
1449 dtype: Option<DtypeRepr>,
1451 },
1452 RandomUniformLike {
1454 dtype: Option<DtypeRepr>,
1456 high: f64,
1458 low: f64,
1460 },
1461 RotaryEmbedding,
1463
1464 QuantizeLinear {
1467 axis: i64,
1469 saturate: bool,
1471 },
1472 DequantizeLinear {
1474 axis: i64,
1476 },
1477 DynamicQuantizeLinear,
1479
1480 Dft {
1483 inverse: bool,
1485 onesided: bool,
1487 },
1488 Stft,
1490 MelWeightMatrix,
1492 HannWindow {
1494 periodic: bool,
1496 },
1497 BlackmanWindow {
1499 periodic: bool,
1501 },
1502 HammingWindow {
1504 periodic: bool,
1506 },
1507
1508 NegativeLogLikelihoodLoss {
1511 reduction: alloc::string::String,
1513 },
1514 SoftmaxCrossEntropyLoss {
1516 reduction: alloc::string::String,
1518 },
1519
1520 SequenceAt,
1523 SequenceConstruct,
1525 SequenceEmpty,
1527 SequenceErase,
1529 SequenceInsert,
1531 SequenceLength,
1533 SequenceMap,
1535 SplitToSequence {
1537 axis: i64,
1539 keepdims: bool,
1541 },
1542 ConcatFromSequence {
1544 axis: i64,
1546 new_axis: bool,
1548 },
1549 OptionalGetElement,
1551 OptionalHasElement,
1553
1554 Loop,
1557 Scan {
1559 num_scan_inputs: i64,
1561 },
1562 If,
1564
1565 Adagrad,
1568 Adam,
1570 Momentum,
1572 Gradient,
1574
1575 StringNormalizer,
1578 RegexFullMatch {
1580 pattern: alloc::string::String,
1582 },
1583 StringConcat,
1585 StringSplit,
1587 TfIdfVectorizer,
1589 LabelEncoder,
1591
1592 ArrayFeatureExtractor,
1595 Binarizer {
1597 threshold: f64,
1599 },
1600 TreeEnsemble,
1602 ImageDecoder,
1604}
1605
1606#[derive(Debug, Clone)]
1608pub struct GraphNode {
1609 pub op: Op,
1611 pub inputs: Vec<usize>,
1613 pub dtype: DtypeRepr,
1615 pub shape: Shape,
1618}
1619
1620#[derive(Debug, Default, Clone)]
1622pub struct Graph {
1623 pub nodes: Vec<GraphNode>,
1625 pub names: BTreeMap<usize, String>,
1627}
1628
1629impl Graph {
1630 pub fn new() -> Self {
1632 Self::default()
1633 }
1634
1635 pub fn add_node(
1637 &mut self,
1638 op: Op,
1639 inputs: Vec<usize>,
1640 dtype: DtypeRepr,
1641 shape: Shape,
1642 ) -> usize {
1643 let id = self.nodes.len();
1644 self.nodes.push(GraphNode {
1645 op,
1646 inputs,
1647 dtype,
1648 shape,
1649 });
1650 #[cfg(feature = "std")]
1651 if let Some(name) = crate::name_scope::current_scope() {
1652 self.names.insert(id, name);
1653 }
1654 id
1655 }
1656
1657 pub fn topological_sort(&self) -> Vec<usize> {
1660 let n = self.nodes.len();
1661 let mut in_degree = vec![0usize; n];
1662 let mut dependents: Vec<Vec<usize>> = vec![vec![]; n];
1663
1664 for (id, node) in self.nodes.iter().enumerate() {
1665 for &input in &node.inputs {
1666 in_degree[id] += 1;
1667 dependents[input].push(id);
1668 }
1669 }
1670
1671 let mut queue: Vec<usize> = (0..n).filter(|&i| in_degree[i] == 0).collect();
1672 let mut order = Vec::with_capacity(n);
1673
1674 while let Some(id) = queue.pop() {
1675 order.push(id);
1676 for &dep in &dependents[id] {
1677 in_degree[dep] -= 1;
1678 if in_degree[dep] == 0 {
1679 queue.push(dep);
1680 }
1681 }
1682 }
1683
1684 assert_eq!(order.len(), n, "graph contains a cycle");
1685 order
1686 }
1687
1688 pub fn optimise(&self) -> Graph {
1701 let n = self.nodes.len();
1702
1703 let mut n_consumers = vec![0usize; n];
1705 for node in &self.nodes {
1706 for &inp in &node.inputs {
1707 n_consumers[inp] += 1;
1708 }
1709 }
1710
1711 let mut dead = vec![false; n];
1713 let mut node_override: Vec<Option<(Op, Vec<usize>)>> = vec![None; n];
1715
1716 #[allow(clippy::needless_range_loop)]
1719 for silu_idx in 0..n {
1720 if !matches!(self.nodes[silu_idx].op, Op::Silu) {
1721 continue;
1722 }
1723 if self.nodes[silu_idx].inputs.len() != 1 {
1724 continue;
1725 }
1726
1727 let bn_idx = self.nodes[silu_idx].inputs[0];
1728 if !matches!(self.nodes[bn_idx].op, Op::BatchNorm2d { .. }) {
1729 continue;
1730 }
1731 if n_consumers[bn_idx] != 1 {
1732 continue;
1733 }
1734 if self.nodes[bn_idx].inputs.len() != 1 {
1735 continue;
1736 }
1737
1738 let conv_idx = self.nodes[bn_idx].inputs[0];
1739 if !matches!(
1740 self.nodes[conv_idx].op,
1741 Op::Conv2d {
1742 has_bias: false,
1743 ..
1744 }
1745 ) {
1746 continue;
1747 }
1748 if n_consumers[conv_idx] != 1 {
1749 continue;
1750 }
1751
1752 let (
1753 in_channels,
1754 out_channels,
1755 kernel_h,
1756 kernel_w,
1757 stride_h,
1758 stride_w,
1759 padding_h,
1760 padding_w,
1761 groups,
1762 ) = if let Op::Conv2d {
1763 in_channels,
1764 out_channels,
1765 kernel_h,
1766 kernel_w,
1767 stride_h,
1768 stride_w,
1769 padding_h,
1770 padding_w,
1771 groups,
1772 ..
1773 } = self.nodes[conv_idx].op
1774 {
1775 (
1776 in_channels,
1777 out_channels,
1778 kernel_h,
1779 kernel_w,
1780 stride_h,
1781 stride_w,
1782 padding_h,
1783 padding_w,
1784 groups,
1785 )
1786 } else {
1787 unreachable!()
1788 };
1789
1790 let bn_eps = if let Op::BatchNorm2d { eps, .. } = self.nodes[bn_idx].op {
1791 eps
1792 } else {
1793 unreachable!()
1794 };
1795
1796 dead[conv_idx] = true;
1797 dead[bn_idx] = true;
1798 node_override[silu_idx] = Some((
1799 Op::Conv2dBnSilu {
1800 in_channels,
1801 out_channels,
1802 kernel_h,
1803 kernel_w,
1804 stride_h,
1805 stride_w,
1806 padding_h,
1807 padding_w,
1808 groups,
1809 bn_eps,
1810 },
1811 self.nodes[conv_idx].inputs.clone(),
1812 ));
1813 }
1814
1815 let mut old_to_new = vec![0usize; n];
1817 let mut new_count = 0usize;
1818 for i in 0..n {
1819 if !dead[i] {
1820 old_to_new[i] = new_count;
1821 new_count += 1;
1822 }
1823 }
1824
1825 let mut new_graph = Graph::new();
1827 for old_idx in 0..n {
1828 if dead[old_idx] {
1829 continue;
1830 }
1831 let node = &self.nodes[old_idx];
1832 let (op, inputs) =
1833 if let Some((fused_op, fused_inputs)) = node_override[old_idx].clone() {
1834 let mapped = fused_inputs.iter().map(|&i| old_to_new[i]).collect();
1835 (fused_op, mapped)
1836 } else {
1837 let mapped = node.inputs.iter().map(|&i| old_to_new[i]).collect();
1838 (node.op.clone(), mapped)
1839 };
1840 let new_idx = new_graph.nodes.len();
1841 new_graph.nodes.push(GraphNode {
1842 op,
1843 inputs,
1844 dtype: node.dtype,
1845 shape: node.shape.clone(),
1846 });
1847 if let Some(name) = self.names.get(&old_idx) {
1848 new_graph.names.insert(new_idx, name.clone());
1849 }
1850 }
1851
1852 new_graph
1853 }
1854
1855 pub fn fuse_elementwise_chains(&self) -> Graph {
1869 let mut graph = self.clone();
1870 loop {
1871 let (next, changed) = graph.fuse_elementwise_chain_pass();
1872 graph = next;
1873 if !changed {
1874 break;
1875 }
1876 }
1877 graph
1878 }
1879
1880 fn fuse_elementwise_chain_pass(&self) -> (Graph, bool) {
1890 let n = self.nodes.len();
1891
1892 let mut n_consumers = vec![0usize; n];
1893 for node in &self.nodes {
1894 for &inp in &node.inputs {
1895 n_consumers[inp] += 1;
1896 }
1897 }
1898
1899 let mut dead = vec![false; n];
1900 let mut node_override: Vec<Option<(Op, Vec<usize>)>> = vec![None; n];
1901 let mut changed = false;
1902
1903 #[allow(clippy::needless_range_loop)]
1907 for child_idx in 0..n {
1908 if changed {
1909 break;
1910 }
1911 if !is_fusable_elementwise(&self.nodes[child_idx].op) {
1912 continue;
1913 }
1914 if self.nodes[child_idx].inputs.len() != 1 {
1915 continue;
1916 }
1917 let parent_idx = self.nodes[child_idx].inputs[0];
1918 if n_consumers[parent_idx] != 1 {
1919 continue;
1920 }
1921 let parent_op = &self.nodes[parent_idx].op;
1922 let mut members = match parent_op {
1923 Op::Fused { members } => members.clone(),
1924 other if is_fusable_elementwise(other) => alloc::vec![other.clone()],
1925 _ => continue,
1926 };
1927 members.push(self.nodes[child_idx].op.clone());
1928
1929 dead[parent_idx] = true;
1930 node_override[child_idx] = Some((
1931 Op::Fused { members },
1932 self.nodes[parent_idx].inputs.clone(),
1933 ));
1934 changed = true;
1935 }
1936
1937 if !changed {
1938 return (self.clone(), false);
1939 }
1940
1941 let mut old_to_new = vec![0usize; n];
1942 let mut new_count = 0usize;
1943 for i in 0..n {
1944 if !dead[i] {
1945 old_to_new[i] = new_count;
1946 new_count += 1;
1947 }
1948 }
1949
1950 let mut new_graph = Graph::new();
1951 for old_idx in 0..n {
1952 if dead[old_idx] {
1953 continue;
1954 }
1955 let node = &self.nodes[old_idx];
1956 let (op, inputs) =
1957 if let Some((fused_op, fused_inputs)) = node_override[old_idx].clone() {
1958 let mapped = fused_inputs.iter().map(|&i| old_to_new[i]).collect();
1959 (fused_op, mapped)
1960 } else {
1961 let mapped = node.inputs.iter().map(|&i| old_to_new[i]).collect();
1962 (node.op.clone(), mapped)
1963 };
1964 let new_idx = new_graph.nodes.len();
1965 new_graph.nodes.push(GraphNode {
1966 op,
1967 inputs,
1968 dtype: node.dtype,
1969 shape: node.shape.clone(),
1970 });
1971 if let Some(name) = self.names.get(&old_idx) {
1972 new_graph.names.insert(new_idx, name.clone());
1973 }
1974 }
1975
1976 (new_graph, true)
1977 }
1978}
1979
1980pub fn is_fusable_elementwise(op: &Op) -> bool {
1993 matches!(
1994 op,
1995 Op::Relu | Op::Sigmoid | Op::Silu | Op::Tanh
1996 )
1997}
1998
1999fn infer_output_shape(op: &Op, inputs: &[&Shape]) -> Shape {
2004 if let Op::Constant { shape, .. } = op {
2006 return shape.clone();
2007 }
2008 if matches!(op, Op::SequenceEmpty | Op::OptionalHasElement) {
2010 return vec![];
2011 }
2012 let input = inputs[0];
2013 match op {
2014 Op::Input => input.clone(),
2015
2016 Op::Fused { members } => members
2017 .iter()
2018 .fold(input.clone(), |shape, member| {
2019 infer_output_shape(member, &[&shape])
2020 }),
2021
2022 Op::Relu
2024 | Op::Elu { .. }
2025 | Op::Selu
2026 | Op::Celu { .. }
2027 | Op::Gelu
2028 | Op::Mish
2029 | Op::Hardtanh { .. }
2030 | Op::Relu6
2031 | Op::Hardsigmoid
2032 | Op::Hardswish
2033 | Op::Hardshrink { .. }
2034 | Op::LeakyRelu { .. }
2035 | Op::Threshold { .. }
2036 | Op::Softsign
2037 | Op::Softshrink { .. }
2038 | Op::Softplus { .. }
2039 | Op::Sigmoid
2040 | Op::Silu
2041 | Op::Logsigmoid
2042 | Op::Tanh
2043 | Op::Tanhshrink
2044 | Op::Softmax { .. }
2045 | Op::BatchNorm1d { .. }
2046 | Op::BatchNorm2d { .. }
2047 | Op::BatchNorm3d { .. }
2048 | Op::LayerNorm { .. }
2049 | Op::RmsNorm { .. }
2050 | Op::GroupNorm { .. }
2051 | Op::InstanceNorm1d { .. }
2052 | Op::InstanceNorm2d { .. }
2053 | Op::InstanceNorm3d { .. } => input.clone(),
2054
2055 Op::Linear { out_features, .. } => {
2056 let mut out = input[..input.len() - 1].to_vec();
2058 out.push(Some(*out_features));
2059 out
2060 }
2061
2062 Op::Flatten => {
2063 let rest = &input[1..];
2065 let flat: Option<usize> = rest
2066 .iter()
2067 .try_fold(1usize, |acc, dim| dim.map(|d| acc * d));
2068 vec![input[0], flat]
2069 }
2070
2071 Op::Conv1d {
2073 out_channels,
2074 kernel_l,
2075 stride,
2076 padding,
2077 ..
2078 } => {
2079 let l_out = input[2].map(|l| (l + 2 * padding - kernel_l) / stride + 1);
2081 vec![input[0], Some(*out_channels), l_out]
2082 }
2083
2084 Op::Conv2d {
2085 out_channels,
2086 kernel_h,
2087 kernel_w,
2088 stride_h,
2089 stride_w,
2090 padding_h,
2091 padding_w,
2092 ..
2093 }
2094 | Op::Conv2dBnSilu {
2095 out_channels,
2096 kernel_h,
2097 kernel_w,
2098 stride_h,
2099 stride_w,
2100 padding_h,
2101 padding_w,
2102 ..
2103 } => {
2104 let h_out = input[2].map(|h| (h + 2 * padding_h - kernel_h) / stride_h + 1);
2106 let w_out = input[3].map(|w| (w + 2 * padding_w - kernel_w) / stride_w + 1);
2107 vec![input[0], Some(*out_channels), h_out, w_out]
2108 }
2109
2110 Op::Conv3d {
2111 out_channels,
2112 kernel_d,
2113 kernel_h,
2114 kernel_w,
2115 stride_d,
2116 stride_h,
2117 stride_w,
2118 padding_d,
2119 padding_h,
2120 padding_w,
2121 ..
2122 } => {
2123 let d_out = input[2].map(|d| (d + 2 * padding_d - kernel_d) / stride_d + 1);
2125 let h_out = input[3].map(|h| (h + 2 * padding_h - kernel_h) / stride_h + 1);
2126 let w_out = input[4].map(|w| (w + 2 * padding_w - kernel_w) / stride_w + 1);
2127 vec![input[0], Some(*out_channels), d_out, h_out, w_out]
2128 }
2129
2130 Op::AvgPool1d { kernel_l, stride } | Op::MaxPool1d { kernel_l, stride } => {
2132 let l_out = input[2].map(|l| (l - kernel_l) / stride + 1);
2133 vec![input[0], input[1], l_out]
2134 }
2135
2136 Op::LpPool1d {
2137 kernel_l, stride, ..
2138 } => {
2139 let l_out = input[2].map(|l| (l - kernel_l) / stride + 1);
2140 vec![input[0], input[1], l_out]
2141 }
2142
2143 Op::AvgPool2d {
2144 kernel_h,
2145 kernel_w,
2146 stride_h,
2147 stride_w,
2148 } => {
2149 let h_out = input[2].map(|h| (h - kernel_h) / stride_h + 1);
2150 let w_out = input[3].map(|w| (w - kernel_w) / stride_w + 1);
2151 vec![input[0], input[1], h_out, w_out]
2152 }
2153
2154 Op::MaxPool2d {
2155 kernel_h,
2156 kernel_w,
2157 stride_h,
2158 stride_w,
2159 pad_h,
2160 pad_w,
2161 } => {
2162 let h_out = input[2].map(|h| (h + 2 * pad_h - kernel_h) / stride_h + 1);
2163 let w_out = input[3].map(|w| (w + 2 * pad_w - kernel_w) / stride_w + 1);
2164 vec![input[0], input[1], h_out, w_out]
2165 }
2166
2167 Op::LpPool2d {
2168 kernel_h,
2169 kernel_w,
2170 stride_h,
2171 stride_w,
2172 ..
2173 } => {
2174 let h_out = input[2].map(|h| (h - kernel_h) / stride_h + 1);
2175 let w_out = input[3].map(|w| (w - kernel_w) / stride_w + 1);
2176 vec![input[0], input[1], h_out, w_out]
2177 }
2178
2179 Op::AvgPool3d {
2180 kernel_d,
2181 kernel_h,
2182 kernel_w,
2183 stride_d,
2184 stride_h,
2185 stride_w,
2186 }
2187 | Op::MaxPool3d {
2188 kernel_d,
2189 kernel_h,
2190 kernel_w,
2191 stride_d,
2192 stride_h,
2193 stride_w,
2194 } => {
2195 let d_out = input[2].map(|d| (d - kernel_d) / stride_d + 1);
2196 let h_out = input[3].map(|h| (h - kernel_h) / stride_h + 1);
2197 let w_out = input[4].map(|w| (w - kernel_w) / stride_w + 1);
2198 vec![input[0], input[1], d_out, h_out, w_out]
2199 }
2200
2201 Op::LpPool3d {
2202 kernel_d,
2203 kernel_h,
2204 kernel_w,
2205 stride_d,
2206 stride_h,
2207 stride_w,
2208 ..
2209 } => {
2210 let d_out = input[2].map(|d| (d - kernel_d) / stride_d + 1);
2211 let h_out = input[3].map(|h| (h - kernel_h) / stride_h + 1);
2212 let w_out = input[4].map(|w| (w - kernel_w) / stride_w + 1);
2213 vec![input[0], input[1], d_out, h_out, w_out]
2214 }
2215
2216 Op::UpsampleNearest2d { scale_h, scale_w } => {
2218 let h_out = input[2].map(|h| h * scale_h);
2220 let w_out = input[3].map(|w| w * scale_w);
2221 vec![input[0], input[1], h_out, w_out]
2222 }
2223
2224 Op::ConstantPad1d {
2226 pad_left,
2227 pad_right,
2228 ..
2229 }
2230 | Op::ReflectionPad1d {
2231 pad_left,
2232 pad_right,
2233 }
2234 | Op::ReplicationPad1d {
2235 pad_left,
2236 pad_right,
2237 }
2238 | Op::CircularPad1d {
2239 pad_left,
2240 pad_right,
2241 } => {
2242 let l_out = input[2].map(|l| l + pad_left + pad_right);
2244 vec![input[0], input[1], l_out]
2245 }
2246
2247 Op::ConstantPad2d {
2248 pad_l,
2249 pad_r,
2250 pad_t,
2251 pad_b,
2252 ..
2253 }
2254 | Op::ReflectionPad2d {
2255 pad_l,
2256 pad_r,
2257 pad_t,
2258 pad_b,
2259 }
2260 | Op::ReplicationPad2d {
2261 pad_l,
2262 pad_r,
2263 pad_t,
2264 pad_b,
2265 }
2266 | Op::CircularPad2d {
2267 pad_l,
2268 pad_r,
2269 pad_t,
2270 pad_b,
2271 } => {
2272 let h_out = input[2].map(|h| h + pad_t + pad_b);
2274 let w_out = input[3].map(|w| w + pad_l + pad_r);
2275 vec![input[0], input[1], h_out, w_out]
2276 }
2277
2278 Op::ConstantPad3d {
2279 pad_d1,
2280 pad_d2,
2281 pad_h1,
2282 pad_h2,
2283 pad_w1,
2284 pad_w2,
2285 ..
2286 }
2287 | Op::ReflectionPad3d {
2288 pad_d1,
2289 pad_d2,
2290 pad_h1,
2291 pad_h2,
2292 pad_w1,
2293 pad_w2,
2294 }
2295 | Op::ReplicationPad3d {
2296 pad_d1,
2297 pad_d2,
2298 pad_h1,
2299 pad_h2,
2300 pad_w1,
2301 pad_w2,
2302 }
2303 | Op::CircularPad3d {
2304 pad_d1,
2305 pad_d2,
2306 pad_h1,
2307 pad_h2,
2308 pad_w1,
2309 pad_w2,
2310 } => {
2311 let d_out = input[2].map(|d| d + pad_d1 + pad_d2);
2313 let h_out = input[3].map(|h| h + pad_h1 + pad_h2);
2314 let w_out = input[4].map(|w| w + pad_w1 + pad_w2);
2315 vec![input[0], input[1], d_out, h_out, w_out]
2316 }
2317
2318 Op::Attention { .. } => input.clone(),
2319
2320 Op::Add => input.clone(),
2321
2322 Op::ChannelChunk { chunk_c, .. } => {
2323 vec![input[0], Some(*chunk_c), input[2], input[3]]
2325 }
2326
2327 Op::ChannelCat { c_total } => {
2328 vec![input[0], Some(*c_total), input[2], input[3]]
2330 }
2331
2332 Op::ChannelBiasAdd { .. } => input.to_vec(),
2333
2334 Op::Custom { data } => data.infer_output_shape(inputs),
2335
2336 Op::Abs
2346 | Op::Neg
2347 | Op::Ceil
2348 | Op::Floor
2349 | Op::Round
2350 | Op::Sqrt
2351 | Op::Reciprocal
2352 | Op::Exp
2353 | Op::Log
2354 | Op::Erf
2355 | Op::Sign
2356 | Op::IsNaN
2357 | Op::IsInf { .. }
2358 | Op::Not
2359 | Op::BitwiseNot
2360 | Op::Sin
2361 | Op::Cos
2362 | Op::Tan
2363 | Op::Asin
2364 | Op::Acos
2365 | Op::Atan
2366 | Op::Sinh
2367 | Op::Cosh
2368 | Op::Asinh
2369 | Op::Acosh
2370 | Op::Atanh
2371 | Op::PRelu
2372 | Op::ThresholdedRelu { .. }
2373 | Op::Shrink { .. }
2374 | Op::Clip
2375 | Op::Swish
2376 | Op::LogSoftmax { .. }
2377 | Op::Hardmax { .. }
2378 | Op::Dropout { .. }
2379 | Op::Identity
2380 | Op::LRN { .. }
2381 | Op::MeanVarianceNormalization { .. }
2382 | Op::LpNormalization { .. }
2383 | Op::Pad { .. }
2384 | Op::ReverseSequence { .. }
2385 | Op::Trilu { .. }
2386 | Op::CumSum { .. }
2387 | Op::CumProd { .. }
2388 | Op::QuantizeLinear { .. }
2389 | Op::DequantizeLinear { .. }
2390 | Op::DynamicQuantizeLinear
2391 | Op::Bernoulli { .. }
2392 | Op::RandomUniformLike { .. }
2393 | Op::EyeLike { .. }
2394 | Op::RotaryEmbedding
2395 | Op::MultiHeadAttention { .. }
2396 | Op::FlexAttention { .. }
2397 | Op::LinearAttention { .. }
2398 | Op::CausalConvWithState { .. } => input.clone(),
2399
2400 Op::Mul
2402 | Op::Sub
2403 | Op::Div
2404 | Op::Pow
2405 | Op::Mod { .. }
2406 | Op::ElemMin
2407 | Op::ElemMax
2408 | Op::ElemMean
2409 | Op::ElemSum
2410 | Op::Equal
2411 | Op::Greater
2412 | Op::GreaterOrEqual
2413 | Op::Less
2414 | Op::LessOrEqual
2415 | Op::And
2416 | Op::Or
2417 | Op::Xor
2418 | Op::BitwiseAnd
2419 | Op::BitwiseOr
2420 | Op::BitwiseXor
2421 | Op::BitShift { .. }
2422 | Op::Cast { .. }
2423 | Op::CastLike
2424 | Op::BitCast { .. }
2425 | Op::Where => input.clone(),
2426
2427 Op::Reshape
2430 | Op::Squeeze { .. }
2431 | Op::Unsqueeze { .. }
2432 | Op::Slice
2433 | Op::Gather { .. }
2434 | Op::GatherElements { .. }
2435 | Op::GatherND { .. }
2436 | Op::ScatterElements { .. }
2437 | Op::ScatterND
2438 | Op::Tile
2439 | Op::Expand
2440 | Op::Compress { .. }
2441 | Op::Range
2442 | Op::ConstantOfShape { .. }
2443 | Op::NonZero
2444 | Op::Scatter { .. }
2445 | Op::TensorScatter
2446 | Op::Resize { .. }
2447 | Op::GridSample { .. }
2448 | Op::AffineGrid { .. }
2449 | Op::CenterCropPad { .. } => input.clone(),
2450
2451 Op::Transpose { perm } => {
2452 if perm.is_empty() {
2453 input.iter().rev().cloned().collect()
2454 } else {
2455 perm.iter()
2456 .map(|&i| input.get(i).copied().unwrap_or(None))
2457 .collect()
2458 }
2459 }
2460
2461 Op::Concat { axis } => {
2462 let rank = input.len();
2463 if rank == 0 {
2464 return input.clone();
2465 }
2466 let ax = axis.rem_euclid(rank as i64) as usize;
2467 let mut out = input.clone();
2468 out[ax] = inputs
2470 .iter()
2471 .try_fold(0usize, |acc, s| {
2472 s.get(ax).copied().unwrap_or(None).map(|d| acc + d)
2473 })
2474 .map(Some)
2475 .unwrap_or(None);
2476 out
2477 }
2478
2479 Op::Split { axis, num_outputs } => {
2480 let rank = input.len();
2481 if rank == 0 {
2482 return input.clone();
2483 }
2484 let ax = axis.rem_euclid(rank as i64) as usize;
2485 let mut out = input.clone();
2486 out[ax] = input[ax].map(|d| d / num_outputs.max(&1));
2487 out
2488 }
2489
2490 Op::ShapeOf { start, end } => {
2491 let rank = input.len() as i64;
2492 let s = start.rem_euclid(rank.max(1));
2493 let e = end.rem_euclid(rank.max(1));
2494 vec![Some((e - s).max(0) as usize)]
2495 }
2496
2497 Op::SizeOf => vec![Some(1)],
2498
2499 Op::Gemm {
2500 trans_a, trans_b, ..
2501 } => {
2502 let m = if *trans_a {
2503 input.get(1)
2504 } else {
2505 input.first()
2506 }
2507 .copied()
2508 .unwrap_or(None);
2509 let n = if inputs.len() >= 2 {
2510 let b = inputs[1];
2511 if *trans_b { b.first() } else { b.get(1) }
2512 .copied()
2513 .unwrap_or(None)
2514 } else {
2515 None
2516 };
2517 vec![m, n]
2518 }
2519
2520 Op::MatMul | Op::MatMulInteger | Op::QLinearMatMul => {
2521 if inputs.len() >= 2 && !input.is_empty() {
2522 let other = inputs[1];
2523 let mut out = input[..input.len() - 1].to_vec();
2524 out.push(other.last().copied().unwrap_or(None));
2525 out
2526 } else {
2527 input.clone()
2528 }
2529 }
2530
2531 Op::Einsum { .. }
2532 | Op::Det
2533 | Op::Col2Im { .. }
2534 | Op::ConvInteger { .. }
2535 | Op::DeformConv { .. }
2536 | Op::QLinearConv { .. } => input.clone(),
2537
2538 Op::ConvTranspose {
2539 out_channels,
2540 kernel_h,
2541 kernel_w,
2542 stride_h,
2543 stride_w,
2544 padding_h,
2545 padding_w,
2546 output_padding_h,
2547 output_padding_w,
2548 ..
2549 } => {
2550 let h_out =
2551 input[2].map(|h| (h - 1) * stride_h - 2 * padding_h + kernel_h + output_padding_h);
2552 let w_out =
2553 input[3].map(|w| (w - 1) * stride_w - 2 * padding_w + kernel_w + output_padding_w);
2554 vec![input[0], Some(*out_channels), h_out, w_out]
2555 }
2556
2557 Op::ReduceSum { keepdims, .. }
2558 | Op::ReduceMean { keepdims, .. }
2559 | Op::ReduceMax { keepdims, .. }
2560 | Op::ReduceMin { keepdims, .. }
2561 | Op::ReduceProd { keepdims, .. }
2562 | Op::ReduceL1 { keepdims, .. }
2563 | Op::ReduceL2 { keepdims, .. }
2564 | Op::ReduceLogSum { keepdims, .. }
2565 | Op::ReduceLogSumExp { keepdims, .. }
2566 | Op::ReduceSumSquare { keepdims, .. } => {
2567 if *keepdims {
2570 input.clone()
2571 } else {
2572 vec![Some(1)]
2573 }
2574 }
2575
2576 Op::ArgMax { axis, keepdims, .. } | Op::ArgMin { axis, keepdims, .. } => {
2577 if input.is_empty() {
2578 return vec![];
2579 }
2580 let ax = axis.rem_euclid(input.len() as i64) as usize;
2581 if *keepdims {
2582 let mut out = input.clone();
2583 out[ax] = Some(1);
2584 out
2585 } else {
2586 let mut out = input.clone();
2587 out.remove(ax);
2588 out
2589 }
2590 }
2591
2592 Op::GlobalAvgPool | Op::GlobalMaxPool => {
2593 let mut out = input[..2.min(input.len())].to_vec();
2594 for _ in 2..input.len() {
2595 out.push(Some(1));
2596 }
2597 out
2598 }
2599
2600 Op::Lstm {
2601 hidden_size,
2602 bidirectional,
2603 ..
2604 }
2605 | Op::Gru {
2606 hidden_size,
2607 bidirectional,
2608 ..
2609 }
2610 | Op::Rnn {
2611 hidden_size,
2612 bidirectional,
2613 ..
2614 } => {
2615 let num_dirs: usize = if *bidirectional { 2 } else { 1 };
2616 vec![
2618 input.first().copied().unwrap_or(None),
2619 Some(num_dirs),
2620 input.get(1).copied().unwrap_or(None),
2621 Some(*hidden_size),
2622 ]
2623 }
2624
2625 Op::SpaceToDepth { blocksize } => {
2626 let c_out = input[1].map(|c| c * blocksize * blocksize);
2627 let h_out = input[2].map(|h| h / blocksize);
2628 let w_out = input[3].map(|w| w / blocksize);
2629 vec![input[0], c_out, h_out, w_out]
2630 }
2631
2632 Op::DepthToSpace { blocksize, .. } => {
2633 let c_out = input[1].map(|c| c / (blocksize * blocksize));
2634 let h_out = input[2].map(|h| h * blocksize);
2635 let w_out = input[3].map(|w| w * blocksize);
2636 vec![input[0], c_out, h_out, w_out]
2637 }
2638
2639 Op::RoiAlign {
2640 output_h, output_w, ..
2641 } => {
2642 vec![input[0], input[1], Some(*output_h), Some(*output_w)]
2643 }
2644
2645 Op::MaxUnpool {
2646 kernel_h,
2647 kernel_w,
2648 stride_h,
2649 stride_w,
2650 } => {
2651 let h_out = input[2].map(|h| (h - 1) * stride_h + kernel_h);
2652 let w_out = input[3].map(|w| (w - 1) * stride_w + kernel_w);
2653 vec![input[0], input[1], h_out, w_out]
2654 }
2655
2656 Op::NonMaxSuppression { .. } => vec![None, Some(3)],
2657
2658 Op::TopK { axis, .. } => {
2659 let _ = axis;
2661 input.clone()
2662 }
2663
2664 Op::Unique { .. } => input.clone(),
2665 Op::OneHot { .. } => input.clone(),
2666
2667 Op::NegativeLogLikelihoodLoss { .. } | Op::SoftmaxCrossEntropyLoss { .. } => {
2668 vec![Some(1)]
2669 }
2670
2671 Op::Dft { onesided, .. } => {
2672 if *onesided && input.len() >= 2 {
2674 let mut out = input.clone();
2675 *out.last_mut().unwrap() = None;
2676 out
2677 } else {
2678 input.clone()
2679 }
2680 }
2681
2682 Op::Stft
2683 | Op::MelWeightMatrix
2684 | Op::HannWindow { .. }
2685 | Op::BlackmanWindow { .. }
2686 | Op::HammingWindow { .. } => input.clone(),
2687
2688 Op::SequenceAt
2689 | Op::SequenceConstruct
2690 | Op::SequenceErase
2691 | Op::SequenceInsert
2692 | Op::SequenceLength
2693 | Op::SequenceMap
2694 | Op::SplitToSequence { .. }
2695 | Op::ConcatFromSequence { .. }
2696 | Op::OptionalGetElement
2697 | Op::Loop
2698 | Op::Scan { .. }
2699 | Op::If
2700 | Op::Adagrad
2701 | Op::Adam
2702 | Op::Momentum
2703 | Op::Gradient
2704 | Op::StringNormalizer
2705 | Op::RegexFullMatch { .. }
2706 | Op::StringConcat
2707 | Op::StringSplit
2708 | Op::TfIdfVectorizer
2709 | Op::LabelEncoder
2710 | Op::ArrayFeatureExtractor
2711 | Op::Binarizer { .. }
2712 | Op::TreeEnsemble
2713 | Op::ImageDecoder => input.clone(),
2714
2715 Op::Constant { shape, .. } => shape.clone(),
2717 Op::SequenceEmpty | Op::OptionalHasElement => vec![],
2718 }
2719}
2720
2721#[derive(Clone)]
2729pub struct SymTensor {
2730 pub node_id: usize,
2732 pub graph: Rc<RefCell<Graph>>,
2734 pub dtype: DtypeRepr,
2736 pub shape: Shape,
2739}
2740
2741impl<D: Dtype, const RANK: usize> RankedTensor<D, RANK> for SymTensor {
2744 const SHAPE: [usize; RANK] = [0; RANK];
2745}
2746impl<D: Dtype, const RANK: usize> Tensor<D, RANK> for SymTensor {}
2747
2748impl SymTensor {
2749 pub fn input(dtype: DtypeRepr, shape: Shape) -> (Self, Rc<RefCell<Graph>>) {
2757 let graph = Rc::new(RefCell::new(Graph::new()));
2758 let node_id = graph
2759 .borrow_mut()
2760 .add_node(Op::Input, vec![], dtype, shape.clone());
2761 let tensor = Self {
2762 node_id,
2763 graph: graph.clone(),
2764 dtype,
2765 shape,
2766 };
2767 (tensor, graph)
2768 }
2769
2770 pub fn rank(&self) -> usize {
2772 self.shape.len()
2773 }
2774
2775 fn record(&self, op: Op) -> Self {
2776 let output_shape = infer_output_shape(&op, &[&self.shape]);
2777 self.record_with_shape(op, output_shape)
2778 }
2779
2780 fn record_with_shape(&self, op: Op, shape: Shape) -> Self {
2781 let node_id =
2782 self.graph
2783 .borrow_mut()
2784 .add_node(op, vec![self.node_id], self.dtype, shape.clone());
2785 Self {
2786 node_id,
2787 graph: self.graph.clone(),
2788 dtype: self.dtype,
2789 shape,
2790 }
2791 }
2792
2793 pub fn record_custom(
2799 &self,
2800 data: CustomData,
2801 other_inputs: &[&SymTensor],
2802 dtype: Option<DtypeRepr>,
2803 ) -> Self {
2804 let mut shapes: Vec<&Shape> = vec![&self.shape];
2805 shapes.extend(other_inputs.iter().map(|t| &t.shape));
2806 let output_shape = data.infer_output_shape(&shapes);
2807
2808 let mut input_ids: Vec<usize> = vec![self.node_id];
2809 input_ids.extend(other_inputs.iter().map(|t| t.node_id));
2810
2811 let out_dtype = dtype.unwrap_or(self.dtype);
2812 let node_id = self.graph.borrow_mut().add_node(
2813 Op::Custom { data },
2814 input_ids,
2815 out_dtype,
2816 output_shape.clone(),
2817 );
2818 Self {
2819 node_id,
2820 graph: self.graph.clone(),
2821 dtype: out_dtype,
2822 shape: output_shape,
2823 }
2824 }
2825}
2826
2827impl<D: Dtype, const RANK: usize> Layer<SymTensor> for Linear<D, SymTensor, SymTensor, RANK> {
2834 type Output = SymTensor;
2835 fn call(&self, input: SymTensor) -> SymTensor {
2836 input.record(Op::Linear {
2837 in_features: self.in_features,
2838 out_features: self.out_features,
2839 has_bias: self.has_bias,
2840 })
2841 }
2842}
2843
2844impl<D: Dtype> Layer<SymTensor> for Flatten<D, SymTensor, SymTensor> {
2845 type Output = SymTensor;
2846 fn call(&self, input: SymTensor) -> SymTensor {
2847 input.record(Op::Flatten)
2848 }
2849}
2850
2851impl<D: Dtype, const RANK: usize> Layer<SymTensor> for BatchNorm1d<D, SymTensor, SymTensor, RANK> {
2854 type Output = SymTensor;
2855 fn call(&self, input: SymTensor) -> SymTensor {
2856 input.record(Op::BatchNorm1d {
2857 num_features: self.num_features,
2858 eps: self.eps,
2859 momentum: self.momentum,
2860 affine: self.affine,
2861 track_running_stats: self.track_running_stats,
2862 })
2863 }
2864}
2865
2866impl<D: Dtype, const RANK: usize> Layer<SymTensor> for BatchNorm2d<D, SymTensor, SymTensor, RANK> {
2867 type Output = SymTensor;
2868 fn call(&self, input: SymTensor) -> SymTensor {
2869 input.record(Op::BatchNorm2d {
2870 num_features: self.num_features,
2871 eps: self.eps,
2872 momentum: self.momentum,
2873 affine: self.affine,
2874 track_running_stats: self.track_running_stats,
2875 })
2876 }
2877}
2878
2879impl<D: Dtype, const RANK: usize> Layer<SymTensor> for BatchNorm3d<D, SymTensor, SymTensor, RANK> {
2880 type Output = SymTensor;
2881 fn call(&self, input: SymTensor) -> SymTensor {
2882 input.record(Op::BatchNorm3d {
2883 num_features: self.num_features,
2884 eps: self.eps,
2885 momentum: self.momentum,
2886 affine: self.affine,
2887 track_running_stats: self.track_running_stats,
2888 })
2889 }
2890}
2891
2892impl<D: Dtype, const RANK: usize> Layer<SymTensor> for LayerNorm<D, SymTensor, SymTensor, RANK> {
2893 type Output = SymTensor;
2894 fn call(&self, input: SymTensor) -> SymTensor {
2895 input.record(Op::LayerNorm {
2896 normalized_shape: self.normalized_shape.clone(),
2897 eps: self.eps,
2898 affine: self.affine,
2899 })
2900 }
2901}
2902
2903impl<D: Dtype, const RANK: usize> Layer<SymTensor> for RmsNorm<D, SymTensor, SymTensor, RANK> {
2904 type Output = SymTensor;
2905 fn call(&self, input: SymTensor) -> SymTensor {
2906 input.record(Op::RmsNorm {
2907 normalized_shape: self.normalized_shape.clone(),
2908 eps: self.eps,
2909 affine: self.affine,
2910 })
2911 }
2912}
2913
2914impl<D: Dtype, const RANK: usize> Layer<SymTensor> for GroupNorm<D, SymTensor, SymTensor, RANK> {
2915 type Output = SymTensor;
2916 fn call(&self, input: SymTensor) -> SymTensor {
2917 input.record(Op::GroupNorm {
2918 num_groups: self.num_groups,
2919 num_channels: self.num_channels,
2920 eps: self.eps,
2921 affine: self.affine,
2922 })
2923 }
2924}
2925
2926impl<D: Dtype, const RANK: usize> Layer<SymTensor>
2927 for InstanceNorm1d<D, SymTensor, SymTensor, RANK>
2928{
2929 type Output = SymTensor;
2930 fn call(&self, input: SymTensor) -> SymTensor {
2931 input.record(Op::InstanceNorm1d {
2932 num_features: self.num_features,
2933 eps: self.eps,
2934 momentum: self.momentum,
2935 affine: self.affine,
2936 track_running_stats: self.track_running_stats,
2937 })
2938 }
2939}
2940
2941impl<D: Dtype, const RANK: usize> Layer<SymTensor>
2942 for InstanceNorm2d<D, SymTensor, SymTensor, RANK>
2943{
2944 type Output = SymTensor;
2945 fn call(&self, input: SymTensor) -> SymTensor {
2946 input.record(Op::InstanceNorm2d {
2947 num_features: self.num_features,
2948 eps: self.eps,
2949 momentum: self.momentum,
2950 affine: self.affine,
2951 track_running_stats: self.track_running_stats,
2952 })
2953 }
2954}
2955
2956impl<D: Dtype, const RANK: usize> Layer<SymTensor>
2957 for InstanceNorm3d<D, SymTensor, SymTensor, RANK>
2958{
2959 type Output = SymTensor;
2960 fn call(&self, input: SymTensor) -> SymTensor {
2961 input.record(Op::InstanceNorm3d {
2962 num_features: self.num_features,
2963 eps: self.eps,
2964 momentum: self.momentum,
2965 affine: self.affine,
2966 track_running_stats: self.track_running_stats,
2967 })
2968 }
2969}
2970
2971impl<D: Dtype, const RANK: usize> Layer<SymTensor> for Conv1d<D, SymTensor, SymTensor, RANK> {
2974 type Output = SymTensor;
2975 fn call(&self, input: SymTensor) -> SymTensor {
2976 input.record(Op::Conv1d {
2977 in_channels: self.in_channels,
2978 out_channels: self.out_channels,
2979 kernel_l: self.kernel_l,
2980 stride: self.stride,
2981 padding: self.padding,
2982 has_bias: self.has_bias,
2983 })
2984 }
2985}
2986
2987impl<D: Dtype, const RANK: usize> Layer<SymTensor> for Conv2d<D, SymTensor, SymTensor, RANK> {
2988 type Output = SymTensor;
2989 fn call(&self, input: SymTensor) -> SymTensor {
2990 input.record(Op::Conv2d {
2991 in_channels: self.in_channels,
2992 out_channels: self.out_channels,
2993 kernel_h: self.kernel_h,
2994 kernel_w: self.kernel_w,
2995 stride_h: self.stride_h,
2996 stride_w: self.stride_w,
2997 padding_h: self.padding_h,
2998 padding_w: self.padding_w,
2999 groups: self.groups,
3000 has_bias: self.has_bias,
3001 })
3002 }
3003}
3004
3005impl<D: Dtype, const RANK: usize> Layer<SymTensor> for Conv3d<D, SymTensor, SymTensor, RANK> {
3006 type Output = SymTensor;
3007 fn call(&self, input: SymTensor) -> SymTensor {
3008 input.record(Op::Conv3d {
3009 in_channels: self.in_channels,
3010 out_channels: self.out_channels,
3011 kernel_d: self.kernel_d,
3012 kernel_h: self.kernel_h,
3013 kernel_w: self.kernel_w,
3014 stride_d: self.stride_d,
3015 stride_h: self.stride_h,
3016 stride_w: self.stride_w,
3017 padding_d: self.padding_d,
3018 padding_h: self.padding_h,
3019 padding_w: self.padding_w,
3020 has_bias: self.has_bias,
3021 })
3022 }
3023}
3024
3025impl<D: Dtype, const RANK: usize> Layer<SymTensor> for AvgPool1d<D, SymTensor, SymTensor, RANK> {
3028 type Output = SymTensor;
3029 fn call(&self, input: SymTensor) -> SymTensor {
3030 input.record(Op::AvgPool1d {
3031 kernel_l: self.kernel_l,
3032 stride: self.stride,
3033 })
3034 }
3035}
3036
3037impl<D: Dtype, const RANK: usize> Layer<SymTensor> for AvgPool2d<D, SymTensor, SymTensor, RANK> {
3038 type Output = SymTensor;
3039 fn call(&self, input: SymTensor) -> SymTensor {
3040 input.record(Op::AvgPool2d {
3041 kernel_h: self.kernel_h,
3042 kernel_w: self.kernel_w,
3043 stride_h: self.stride_h,
3044 stride_w: self.stride_w,
3045 })
3046 }
3047}
3048
3049impl<D: Dtype, const RANK: usize> Layer<SymTensor> for AvgPool3d<D, SymTensor, SymTensor, RANK> {
3050 type Output = SymTensor;
3051 fn call(&self, input: SymTensor) -> SymTensor {
3052 input.record(Op::AvgPool3d {
3053 kernel_d: self.kernel_d,
3054 kernel_h: self.kernel_h,
3055 kernel_w: self.kernel_w,
3056 stride_d: self.stride_d,
3057 stride_h: self.stride_h,
3058 stride_w: self.stride_w,
3059 })
3060 }
3061}
3062
3063impl<D: Dtype, const RANK: usize> Layer<SymTensor> for MaxPool1d<D, SymTensor, SymTensor, RANK> {
3064 type Output = SymTensor;
3065 fn call(&self, input: SymTensor) -> SymTensor {
3066 input.record(Op::MaxPool1d {
3067 kernel_l: self.kernel_l,
3068 stride: self.stride,
3069 })
3070 }
3071}
3072
3073impl<D: Dtype, const RANK: usize> Layer<SymTensor> for MaxPool2d<D, SymTensor, SymTensor, RANK> {
3074 type Output = SymTensor;
3075 fn call(&self, input: SymTensor) -> SymTensor {
3076 input.record(Op::MaxPool2d {
3077 kernel_h: self.kernel_h,
3078 kernel_w: self.kernel_w,
3079 stride_h: self.stride_h,
3080 stride_w: self.stride_w,
3081 pad_h: self.padding_h,
3082 pad_w: self.padding_w,
3083 })
3084 }
3085}
3086
3087impl<D: Dtype, const RANK: usize> Layer<SymTensor> for MaxPool3d<D, SymTensor, SymTensor, RANK> {
3088 type Output = SymTensor;
3089 fn call(&self, input: SymTensor) -> SymTensor {
3090 input.record(Op::MaxPool3d {
3091 kernel_d: self.kernel_d,
3092 kernel_h: self.kernel_h,
3093 kernel_w: self.kernel_w,
3094 stride_d: self.stride_d,
3095 stride_h: self.stride_h,
3096 stride_w: self.stride_w,
3097 })
3098 }
3099}
3100
3101impl<D: Dtype, const RANK: usize> Layer<SymTensor> for LpPool1d<D, SymTensor, SymTensor, RANK> {
3102 type Output = SymTensor;
3103 fn call(&self, input: SymTensor) -> SymTensor {
3104 input.record(Op::LpPool1d {
3105 kernel_l: self.kernel_l,
3106 stride: self.stride,
3107 p: self.p,
3108 })
3109 }
3110}
3111
3112impl<D: Dtype, const RANK: usize> Layer<SymTensor> for LpPool2d<D, SymTensor, SymTensor, RANK> {
3113 type Output = SymTensor;
3114 fn call(&self, input: SymTensor) -> SymTensor {
3115 input.record(Op::LpPool2d {
3116 kernel_h: self.kernel_h,
3117 kernel_w: self.kernel_w,
3118 stride_h: self.stride_h,
3119 stride_w: self.stride_w,
3120 p: self.p,
3121 })
3122 }
3123}
3124
3125impl<D: Dtype, const RANK: usize> Layer<SymTensor> for LpPool3d<D, SymTensor, SymTensor, RANK> {
3126 type Output = SymTensor;
3127 fn call(&self, input: SymTensor) -> SymTensor {
3128 input.record(Op::LpPool3d {
3129 kernel_d: self.kernel_d,
3130 kernel_h: self.kernel_h,
3131 kernel_w: self.kernel_w,
3132 stride_d: self.stride_d,
3133 stride_h: self.stride_h,
3134 stride_w: self.stride_w,
3135 p: self.p,
3136 })
3137 }
3138}
3139
3140impl<D: Dtype, const RANK: usize> Layer<SymTensor>
3143 for ConstantPad1d<D, SymTensor, SymTensor, RANK>
3144{
3145 type Output = SymTensor;
3146 fn call(&self, input: SymTensor) -> SymTensor {
3147 input.record(Op::ConstantPad1d {
3148 pad_left: self.pad_left,
3149 pad_right: self.pad_right,
3150 value: self.value,
3151 })
3152 }
3153}
3154
3155impl<D: Dtype, const RANK: usize> Layer<SymTensor>
3156 for ConstantPad2d<D, SymTensor, SymTensor, RANK>
3157{
3158 type Output = SymTensor;
3159 fn call(&self, input: SymTensor) -> SymTensor {
3160 input.record(Op::ConstantPad2d {
3161 pad_l: self.pad_l,
3162 pad_r: self.pad_r,
3163 pad_t: self.pad_t,
3164 pad_b: self.pad_b,
3165 value: self.value,
3166 })
3167 }
3168}
3169
3170impl<D: Dtype, const RANK: usize> Layer<SymTensor>
3171 for ConstantPad3d<D, SymTensor, SymTensor, RANK>
3172{
3173 type Output = SymTensor;
3174 fn call(&self, input: SymTensor) -> SymTensor {
3175 input.record(Op::ConstantPad3d {
3176 pad_d1: self.pad_d1,
3177 pad_d2: self.pad_d2,
3178 pad_h1: self.pad_h1,
3179 pad_h2: self.pad_h2,
3180 pad_w1: self.pad_w1,
3181 pad_w2: self.pad_w2,
3182 value: self.value,
3183 })
3184 }
3185}
3186
3187impl<D: Dtype, const RANK: usize> Layer<SymTensor>
3188 for ReflectionPad1d<D, SymTensor, SymTensor, RANK>
3189{
3190 type Output = SymTensor;
3191 fn call(&self, input: SymTensor) -> SymTensor {
3192 input.record(Op::ReflectionPad1d {
3193 pad_left: self.pad_left,
3194 pad_right: self.pad_right,
3195 })
3196 }
3197}
3198
3199impl<D: Dtype, const RANK: usize> Layer<SymTensor>
3200 for ReflectionPad2d<D, SymTensor, SymTensor, RANK>
3201{
3202 type Output = SymTensor;
3203 fn call(&self, input: SymTensor) -> SymTensor {
3204 input.record(Op::ReflectionPad2d {
3205 pad_l: self.pad_l,
3206 pad_r: self.pad_r,
3207 pad_t: self.pad_t,
3208 pad_b: self.pad_b,
3209 })
3210 }
3211}
3212
3213impl<D: Dtype, const RANK: usize> Layer<SymTensor>
3214 for ReflectionPad3d<D, SymTensor, SymTensor, RANK>
3215{
3216 type Output = SymTensor;
3217 fn call(&self, input: SymTensor) -> SymTensor {
3218 input.record(Op::ReflectionPad3d {
3219 pad_d1: self.pad_d1,
3220 pad_d2: self.pad_d2,
3221 pad_h1: self.pad_h1,
3222 pad_h2: self.pad_h2,
3223 pad_w1: self.pad_w1,
3224 pad_w2: self.pad_w2,
3225 })
3226 }
3227}
3228
3229impl<D: Dtype, const RANK: usize> Layer<SymTensor>
3230 for ReplicationPad1d<D, SymTensor, SymTensor, RANK>
3231{
3232 type Output = SymTensor;
3233 fn call(&self, input: SymTensor) -> SymTensor {
3234 input.record(Op::ReplicationPad1d {
3235 pad_left: self.pad_left,
3236 pad_right: self.pad_right,
3237 })
3238 }
3239}
3240
3241impl<D: Dtype, const RANK: usize> Layer<SymTensor>
3242 for ReplicationPad2d<D, SymTensor, SymTensor, RANK>
3243{
3244 type Output = SymTensor;
3245 fn call(&self, input: SymTensor) -> SymTensor {
3246 input.record(Op::ReplicationPad2d {
3247 pad_l: self.pad_l,
3248 pad_r: self.pad_r,
3249 pad_t: self.pad_t,
3250 pad_b: self.pad_b,
3251 })
3252 }
3253}
3254
3255impl<D: Dtype, const RANK: usize> Layer<SymTensor>
3256 for ReplicationPad3d<D, SymTensor, SymTensor, RANK>
3257{
3258 type Output = SymTensor;
3259 fn call(&self, input: SymTensor) -> SymTensor {
3260 input.record(Op::ReplicationPad3d {
3261 pad_d1: self.pad_d1,
3262 pad_d2: self.pad_d2,
3263 pad_h1: self.pad_h1,
3264 pad_h2: self.pad_h2,
3265 pad_w1: self.pad_w1,
3266 pad_w2: self.pad_w2,
3267 })
3268 }
3269}
3270
3271impl<D: Dtype, const RANK: usize> Layer<SymTensor>
3272 for CircularPad1d<D, SymTensor, SymTensor, RANK>
3273{
3274 type Output = SymTensor;
3275 fn call(&self, input: SymTensor) -> SymTensor {
3276 input.record(Op::CircularPad1d {
3277 pad_left: self.pad_left,
3278 pad_right: self.pad_right,
3279 })
3280 }
3281}
3282
3283impl<D: Dtype, const RANK: usize> Layer<SymTensor>
3284 for CircularPad2d<D, SymTensor, SymTensor, RANK>
3285{
3286 type Output = SymTensor;
3287 fn call(&self, input: SymTensor) -> SymTensor {
3288 input.record(Op::CircularPad2d {
3289 pad_l: self.pad_l,
3290 pad_r: self.pad_r,
3291 pad_t: self.pad_t,
3292 pad_b: self.pad_b,
3293 })
3294 }
3295}
3296
3297impl<D: Dtype, const RANK: usize> Layer<SymTensor>
3298 for CircularPad3d<D, SymTensor, SymTensor, RANK>
3299{
3300 type Output = SymTensor;
3301 fn call(&self, input: SymTensor) -> SymTensor {
3302 input.record(Op::CircularPad3d {
3303 pad_d1: self.pad_d1,
3304 pad_d2: self.pad_d2,
3305 pad_h1: self.pad_h1,
3306 pad_h2: self.pad_h2,
3307 pad_w1: self.pad_w1,
3308 pad_w2: self.pad_w2,
3309 })
3310 }
3311}
3312
3313impl<D: Dtype, const RANK: usize> Layer<SymTensor> for Relu<D, SymTensor, RANK> {
3316 type Output = SymTensor;
3317 fn call(&self, input: SymTensor) -> SymTensor {
3318 input.record(Op::Relu)
3319 }
3320}
3321
3322impl<D: Float, const RANK: usize> Layer<SymTensor> for Elu<D, SymTensor, RANK> {
3323 type Output = SymTensor;
3324 fn call(&self, input: SymTensor) -> SymTensor {
3325 input.record(Op::Elu { alpha: self.alpha })
3326 }
3327}
3328
3329impl<D: Float, const RANK: usize> Layer<SymTensor> for Selu<D, SymTensor, RANK> {
3330 type Output = SymTensor;
3331 fn call(&self, input: SymTensor) -> SymTensor {
3332 input.record(Op::Selu)
3333 }
3334}
3335
3336impl<D: Float, const RANK: usize> Layer<SymTensor> for Celu<D, SymTensor, RANK> {
3337 type Output = SymTensor;
3338 fn call(&self, input: SymTensor) -> SymTensor {
3339 input.record(Op::Celu { alpha: self.alpha })
3340 }
3341}
3342
3343impl<D: Float, const RANK: usize> Layer<SymTensor> for Gelu<D, SymTensor, RANK> {
3344 type Output = SymTensor;
3345 fn call(&self, input: SymTensor) -> SymTensor {
3346 input.record(Op::Gelu)
3347 }
3348}
3349
3350impl<D: Float, const RANK: usize> Layer<SymTensor> for Mish<D, SymTensor, RANK> {
3351 type Output = SymTensor;
3352 fn call(&self, input: SymTensor) -> SymTensor {
3353 input.record(Op::Mish)
3354 }
3355}
3356
3357impl<D: Float, const RANK: usize> Layer<SymTensor> for Hardtanh<D, SymTensor, RANK> {
3358 type Output = SymTensor;
3359 fn call(&self, input: SymTensor) -> SymTensor {
3360 input.record(Op::Hardtanh {
3361 min_val: self.min_val,
3362 max_val: self.max_val,
3363 })
3364 }
3365}
3366
3367impl<D: Float, const RANK: usize> Layer<SymTensor> for Relu6<D, SymTensor, RANK> {
3368 type Output = SymTensor;
3369 fn call(&self, input: SymTensor) -> SymTensor {
3370 input.record(Op::Relu6)
3371 }
3372}
3373
3374impl<D: Float, const RANK: usize> Layer<SymTensor> for Hardsigmoid<D, SymTensor, RANK> {
3375 type Output = SymTensor;
3376 fn call(&self, input: SymTensor) -> SymTensor {
3377 input.record(Op::Hardsigmoid)
3378 }
3379}
3380
3381impl<D: Float, const RANK: usize> Layer<SymTensor> for Hardswish<D, SymTensor, RANK> {
3382 type Output = SymTensor;
3383 fn call(&self, input: SymTensor) -> SymTensor {
3384 input.record(Op::Hardswish)
3385 }
3386}
3387
3388impl<D: Float, const RANK: usize> Layer<SymTensor> for Hardshrink<D, SymTensor, RANK> {
3389 type Output = SymTensor;
3390 fn call(&self, input: SymTensor) -> SymTensor {
3391 input.record(Op::Hardshrink {
3392 lambda: self.lambda,
3393 })
3394 }
3395}
3396
3397impl<D: Float, const RANK: usize> Layer<SymTensor> for LeakyRelu<D, SymTensor, RANK> {
3398 type Output = SymTensor;
3399 fn call(&self, input: SymTensor) -> SymTensor {
3400 input.record(Op::LeakyRelu {
3401 negative_slope: self.negative_slope,
3402 })
3403 }
3404}
3405
3406impl<D: Float, const RANK: usize> Layer<SymTensor> for Threshold<D, SymTensor, RANK> {
3407 type Output = SymTensor;
3408 fn call(&self, input: SymTensor) -> SymTensor {
3409 input.record(Op::Threshold {
3410 threshold: self.threshold,
3411 value: self.value,
3412 })
3413 }
3414}
3415
3416impl<D: Float, const RANK: usize> Layer<SymTensor> for Softsign<D, SymTensor, RANK> {
3417 type Output = SymTensor;
3418 fn call(&self, input: SymTensor) -> SymTensor {
3419 input.record(Op::Softsign)
3420 }
3421}
3422
3423impl<D: Float, const RANK: usize> Layer<SymTensor> for Softshrink<D, SymTensor, RANK> {
3424 type Output = SymTensor;
3425 fn call(&self, input: SymTensor) -> SymTensor {
3426 input.record(Op::Softshrink {
3427 lambda: self.lambda,
3428 })
3429 }
3430}
3431
3432impl<D: Float, const RANK: usize> Layer<SymTensor> for Softplus<D, SymTensor, RANK> {
3433 type Output = SymTensor;
3434 fn call(&self, input: SymTensor) -> SymTensor {
3435 input.record(Op::Softplus {
3436 beta: self.beta,
3437 threshold: self.threshold,
3438 })
3439 }
3440}
3441
3442impl<D: Float, const RANK: usize> Layer<SymTensor> for Sigmoid<D, SymTensor, RANK> {
3443 type Output = SymTensor;
3444 fn call(&self, input: SymTensor) -> SymTensor {
3445 input.record(Op::Sigmoid)
3446 }
3447}
3448
3449impl<D: Float, const RANK: usize> Layer<SymTensor> for Silu<D, SymTensor, RANK> {
3450 type Output = SymTensor;
3451 fn call(&self, input: SymTensor) -> SymTensor {
3452 input.record(Op::Silu)
3453 }
3454}
3455
3456impl<D: Float, const RANK: usize> Layer<SymTensor> for Logsigmoid<D, SymTensor, RANK> {
3457 type Output = SymTensor;
3458 fn call(&self, input: SymTensor) -> SymTensor {
3459 input.record(Op::Logsigmoid)
3460 }
3461}
3462
3463impl<D: Float, const RANK: usize> Layer<SymTensor> for Tanh<D, SymTensor, RANK> {
3464 type Output = SymTensor;
3465 fn call(&self, input: SymTensor) -> SymTensor {
3466 input.record(Op::Tanh)
3467 }
3468}
3469
3470impl<D: Float, const RANK: usize> Layer<SymTensor> for Tanhshrink<D, SymTensor, RANK> {
3471 type Output = SymTensor;
3472 fn call(&self, input: SymTensor) -> SymTensor {
3473 input.record(Op::Tanhshrink)
3474 }
3475}
3476
3477impl<D: Float, const RANK: usize> Layer<SymTensor> for Softmax<D, SymTensor, RANK> {
3478 type Output = SymTensor;
3479 fn call(&self, input: SymTensor) -> SymTensor {
3480 input.record(Op::Softmax { dim: self.dim })
3481 }
3482}
3483
3484#[cfg(test)]
3489mod tests {
3490 use super::*;
3491 use crate::{
3492 nn::{
3493 activation::{relu::Relu, softmax::Softmax},
3494 conv2d::Conv2d,
3495 linear::Linear,
3496 },
3497 sequential,
3498 };
3499
3500 #[test]
3501 fn test_sequential_graph_extraction() {
3502 let (input, graph) = SymTensor::input(DtypeRepr::F32, vec![None, Some(784)]);
3503
3504 let model = sequential![
3505 Linear::<f32, SymTensor, SymTensor, 2>::new(784, 128, true),
3506 Relu::<f32, SymTensor, 2>::new(),
3507 Linear::<f32, SymTensor, SymTensor, 2>::new(128, 10, true),
3508 Softmax::<f32, SymTensor, 2>::new(1)
3509 ];
3510
3511 let _out = Layer::call(&model, input);
3512
3513 let g = graph.borrow();
3514 assert_eq!(g.nodes.len(), 5);
3515 assert!(matches!(g.nodes[0].op, Op::Input));
3516 assert_eq!(g.nodes[0].shape, vec![None, Some(784)]);
3517
3518 assert!(matches!(
3519 g.nodes[1].op,
3520 Op::Linear {
3521 in_features: 784,
3522 out_features: 128,
3523 ..
3524 }
3525 ));
3526 assert_eq!(g.nodes[1].shape, vec![None, Some(128)]);
3527
3528 assert!(matches!(g.nodes[2].op, Op::Relu));
3529 assert_eq!(g.nodes[2].shape, vec![None, Some(128)]);
3530
3531 assert!(matches!(
3532 g.nodes[3].op,
3533 Op::Linear {
3534 in_features: 128,
3535 out_features: 10,
3536 ..
3537 }
3538 ));
3539 assert_eq!(g.nodes[3].shape, vec![None, Some(10)]);
3540
3541 assert!(matches!(g.nodes[4].op, Op::Softmax { dim: 1 }));
3542 assert_eq!(g.nodes[4].shape, vec![None, Some(10)]);
3543 }
3544
3545 #[test]
3546 fn test_topological_sort_linear_chain() {
3547 let (input, graph) = SymTensor::input(DtypeRepr::F32, vec![None, Some(784)]);
3548
3549 let model = sequential![
3550 Linear::<f32, SymTensor, SymTensor, 2>::new(784, 128, true),
3551 Relu::<f32, SymTensor, 2>::new(),
3552 Linear::<f32, SymTensor, SymTensor, 2>::new(128, 10, true),
3553 Softmax::<f32, SymTensor, 2>::new(1)
3554 ];
3555
3556 let _out = Layer::call(&model, input);
3557
3558 let g = graph.borrow();
3559 let order = g.topological_sort();
3560 assert_eq!(order.len(), g.nodes.len());
3561 for (pos, &id) in order.iter().enumerate() {
3562 for &input_id in &g.nodes[id].inputs {
3563 let input_pos = order.iter().position(|&x| x == input_id).unwrap();
3564 assert!(
3565 input_pos < pos,
3566 "producer {input_id} must come before consumer {id}"
3567 );
3568 }
3569 }
3570 }
3571
3572 #[test]
3573 fn test_residual_graph_extraction() {
3574 let (input, graph) = SymTensor::input(DtypeRepr::F32, vec![None, Some(64)]);
3575
3576 let main = Linear::<f32, SymTensor, SymTensor, 2>::new(64, 64, true).call(input.clone());
3577 let main = Relu::<f32, SymTensor, 2>::new().call(main);
3578 let skip = Linear::<f32, SymTensor, SymTensor, 2>::new(64, 64, false).call(input);
3579
3580 assert!(Rc::ptr_eq(&main.graph, &skip.graph));
3581
3582 let g = graph.borrow();
3583 assert_eq!(g.nodes.len(), 4);
3584 assert_eq!(g.nodes[1].inputs, vec![0]);
3585 assert_eq!(g.nodes[3].inputs, vec![0]);
3586 }
3587
3588 #[test]
3589 fn test_conv2d_graph_extraction() {
3590 let (input, graph) =
3591 SymTensor::input(DtypeRepr::F32, vec![None, Some(3), Some(32), Some(32)]);
3592
3593 let conv = Conv2d::<f32, SymTensor, SymTensor, 4>::new(3, 64, (3, 3), (1, 1), (1, 1), true);
3594 let _out = Layer::call(&conv, input);
3595
3596 let g = graph.borrow();
3597 assert_eq!(g.nodes.len(), 2);
3598 assert!(matches!(
3599 g.nodes[1].op,
3600 Op::Conv2d {
3601 in_channels: 3,
3602 out_channels: 64,
3603 kernel_h: 3,
3604 kernel_w: 3,
3605 stride_h: 1,
3606 stride_w: 1,
3607 padding_h: 1,
3608 padding_w: 1,
3609 has_bias: true,
3610 ..
3611 }
3612 ));
3613 assert_eq!(g.nodes[1].shape, vec![None, Some(64), Some(32), Some(32)]);
3614 }
3615
3616 #[test]
3617 fn test_lenet5_shapes() {
3618 let (input, graph) =
3619 SymTensor::input(DtypeRepr::F32, vec![None, Some(1), Some(28), Some(28)]);
3620
3621 use crate::{
3622 nn::{flatten::Flatten, pool::AvgPool2d},
3623 sequential,
3624 };
3625
3626 let model = sequential![
3627 Conv2d::<f32, SymTensor, SymTensor, 4>::new(1, 6, (5, 5), (1, 1), (2, 2), true),
3628 Relu::<f32, SymTensor, 4>::new(),
3629 AvgPool2d::<f32, SymTensor, SymTensor, 4>::new((2, 2), (2, 2)),
3630 Conv2d::<f32, SymTensor, SymTensor, 4>::new(6, 16, (5, 5), (1, 1), (0, 0), true),
3631 Relu::<f32, SymTensor, 4>::new(),
3632 AvgPool2d::<f32, SymTensor, SymTensor, 4>::new((2, 2), (2, 2)),
3633 Flatten::<f32, SymTensor, SymTensor>::new(),
3634 Linear::<f32, SymTensor, SymTensor, 2>::new(400, 120, true),
3635 Relu::<f32, SymTensor, 2>::new(),
3636 Linear::<f32, SymTensor, SymTensor, 2>::new(120, 84, true),
3637 Relu::<f32, SymTensor, 2>::new(),
3638 Linear::<f32, SymTensor, SymTensor, 2>::new(84, 10, true),
3639 Softmax::<f32, SymTensor, 2>::new(1)
3640 ];
3641
3642 let _out = Layer::call(&model, input);
3643
3644 let g = graph.borrow();
3645 assert_eq!(g.nodes.len(), 14);
3646 assert_eq!(g.nodes[0].shape, vec![None, Some(1), Some(28), Some(28)]);
3647 assert_eq!(g.nodes[1].shape, vec![None, Some(6), Some(28), Some(28)]);
3648 assert_eq!(g.nodes[2].shape, vec![None, Some(6), Some(28), Some(28)]);
3649 assert_eq!(g.nodes[3].shape, vec![None, Some(6), Some(14), Some(14)]);
3650 assert_eq!(g.nodes[4].shape, vec![None, Some(16), Some(10), Some(10)]);
3651 assert_eq!(g.nodes[5].shape, vec![None, Some(16), Some(10), Some(10)]);
3652 assert_eq!(g.nodes[6].shape, vec![None, Some(16), Some(5), Some(5)]);
3653 assert_eq!(g.nodes[7].shape, vec![None, Some(400)]);
3654 assert_eq!(g.nodes[8].shape, vec![None, Some(120)]);
3655 assert_eq!(g.nodes[9].shape, vec![None, Some(120)]);
3656 assert_eq!(g.nodes[10].shape, vec![None, Some(84)]);
3657 assert_eq!(g.nodes[11].shape, vec![None, Some(84)]);
3658 assert_eq!(g.nodes[12].shape, vec![None, Some(10)]);
3659 assert_eq!(g.nodes[13].shape, vec![None, Some(10)]);
3660 }
3661
3662 #[test]
3667 fn test_is_fusable_elementwise_allowlist() {
3668 assert!(is_fusable_elementwise(&Op::Relu));
3669 assert!(is_fusable_elementwise(&Op::Sigmoid));
3670 assert!(is_fusable_elementwise(&Op::Silu));
3671 assert!(is_fusable_elementwise(&Op::Tanh));
3672 }
3673
3674 #[test]
3675 fn test_is_fusable_elementwise_excludes_non_allowlisted_ops() {
3676 assert!(!is_fusable_elementwise(&Op::Input));
3677 assert!(!is_fusable_elementwise(&Op::Gelu));
3678 assert!(!is_fusable_elementwise(&Op::Softmax { dim: 1 }));
3679 assert!(!is_fusable_elementwise(&Op::Conv2d {
3680 in_channels: 3,
3681 out_channels: 8,
3682 kernel_h: 3,
3683 kernel_w: 3,
3684 stride_h: 1,
3685 stride_w: 1,
3686 padding_h: 1,
3687 padding_w: 1,
3688 groups: 1,
3689 has_bias: false,
3690 }));
3691 assert!(!is_fusable_elementwise(&Op::BatchNorm2d {
3692 num_features: 8,
3693 eps: 1e-5,
3694 momentum: 0.1,
3695 affine: true,
3696 track_running_stats: true,
3697 }));
3698 assert!(!is_fusable_elementwise(&Op::Linear {
3699 in_features: 4,
3700 out_features: 4,
3701 has_bias: false,
3702 }));
3703 }
3704
3705 #[test]
3706 fn test_infer_output_shape_fused_folds_through_members() {
3707 let shape = vec![None, Some(16)];
3708 let out = infer_output_shape(
3709 &Op::Fused {
3710 members: alloc::vec![Op::Relu, Op::Sigmoid, Op::Silu],
3711 },
3712 &[&shape],
3713 );
3714 assert_eq!(out, shape);
3716 }
3717
3718 fn shape_1d(n: usize) -> Shape {
3719 vec![None, Some(n)]
3720 }
3721
3722 #[test]
3723 fn test_fuse_elementwise_chains_fuses_linear_chain() {
3724 let mut g = Graph::new();
3725 let input = g.add_node(Op::Input, vec![], DtypeRepr::F32, shape_1d(16));
3726 let relu = g.add_node(Op::Relu, vec![input], DtypeRepr::F32, shape_1d(16));
3727 let sigmoid = g.add_node(Op::Sigmoid, vec![relu], DtypeRepr::F32, shape_1d(16));
3728 let _silu = g.add_node(Op::Silu, vec![sigmoid], DtypeRepr::F32, shape_1d(16));
3729
3730 let fused = g.fuse_elementwise_chains();
3731
3732 assert_eq!(fused.nodes.len(), 2);
3734 assert!(matches!(fused.nodes[0].op, Op::Input));
3735 match &fused.nodes[1].op {
3736 Op::Fused { members } => {
3737 assert_eq!(members.len(), 3);
3738 assert!(matches!(members[0], Op::Relu));
3739 assert!(matches!(members[1], Op::Sigmoid));
3740 assert!(matches!(members[2], Op::Silu));
3741 }
3742 other => panic!("expected Op::Fused, got {other:?}"),
3743 }
3744 assert_eq!(fused.nodes[1].inputs, vec![0]);
3746 }
3747
3748 #[test]
3749 fn test_fuse_elementwise_chains_no_fusion_for_single_op() {
3750 let mut g = Graph::new();
3751 let input = g.add_node(Op::Input, vec![], DtypeRepr::F32, shape_1d(16));
3752 g.add_node(Op::Relu, vec![input], DtypeRepr::F32, shape_1d(16));
3753
3754 let fused = g.fuse_elementwise_chains();
3755
3756 assert_eq!(fused.nodes.len(), 2);
3757 assert!(matches!(fused.nodes[1].op, Op::Relu));
3758 }
3759
3760 #[test]
3761 fn test_fuse_elementwise_chains_stops_at_non_fusable_op() {
3762 let mut g = Graph::new();
3763 let input = g.add_node(Op::Input, vec![], DtypeRepr::F32, shape_1d(4));
3764 let relu = g.add_node(Op::Relu, vec![input], DtypeRepr::F32, shape_1d(4));
3765 let conv = g.add_node(
3766 Op::Conv2d {
3767 in_channels: 4,
3768 out_channels: 4,
3769 kernel_h: 3,
3770 kernel_w: 3,
3771 stride_h: 1,
3772 stride_w: 1,
3773 padding_h: 1,
3774 padding_w: 1,
3775 groups: 1,
3776 has_bias: false,
3777 },
3778 vec![relu],
3779 DtypeRepr::F32,
3780 shape_1d(4),
3781 );
3782 g.add_node(Op::Sigmoid, vec![conv], DtypeRepr::F32, shape_1d(4));
3783
3784 let fused = g.fuse_elementwise_chains();
3785
3786 assert_eq!(fused.nodes.len(), 4);
3789 assert!(matches!(fused.nodes[1].op, Op::Relu));
3790 assert!(matches!(fused.nodes[2].op, Op::Conv2d { .. }));
3791 assert!(matches!(fused.nodes[3].op, Op::Sigmoid));
3792 }
3793
3794 #[test]
3795 fn test_fuse_elementwise_chains_no_fusion_when_multiple_consumers() {
3796 let mut g = Graph::new();
3797 let input = g.add_node(Op::Input, vec![], DtypeRepr::F32, shape_1d(4));
3798 let relu = g.add_node(Op::Relu, vec![input], DtypeRepr::F32, shape_1d(4));
3799 g.add_node(Op::Sigmoid, vec![relu], DtypeRepr::F32, shape_1d(4));
3802 g.add_node(Op::Silu, vec![relu], DtypeRepr::F32, shape_1d(4));
3803
3804 let fused = g.fuse_elementwise_chains();
3805
3806 assert_eq!(fused.nodes.len(), 4);
3807 assert!(matches!(fused.nodes[1].op, Op::Relu));
3808 assert!(matches!(fused.nodes[2].op, Op::Sigmoid));
3809 assert!(matches!(fused.nodes[3].op, Op::Silu));
3810 }
3811
3812 #[test]
3813 fn test_fuse_elementwise_chains_rewires_downstream_consumer() {
3814 let mut g = Graph::new();
3815 let input = g.add_node(Op::Input, vec![], DtypeRepr::F32, shape_1d(4));
3816 let relu = g.add_node(Op::Relu, vec![input], DtypeRepr::F32, shape_1d(4));
3817 let sigmoid = g.add_node(Op::Sigmoid, vec![relu], DtypeRepr::F32, shape_1d(4));
3818 g.add_node(
3821 Op::Conv2d {
3822 in_channels: 4,
3823 out_channels: 4,
3824 kernel_h: 1,
3825 kernel_w: 1,
3826 stride_h: 1,
3827 stride_w: 1,
3828 padding_h: 0,
3829 padding_w: 0,
3830 groups: 1,
3831 has_bias: false,
3832 },
3833 vec![sigmoid],
3834 DtypeRepr::F32,
3835 shape_1d(4),
3836 );
3837
3838 let fused = g.fuse_elementwise_chains();
3839
3840 assert_eq!(fused.nodes.len(), 3);
3841 let fused_idx = 1;
3842 assert!(matches!(fused.nodes[fused_idx].op, Op::Fused { .. }));
3843 assert_eq!(fused.nodes[2].inputs, vec![fused_idx]);
3844 }
3845
3846 #[test]
3847 fn test_fuse_elementwise_chains_grows_past_two_via_fixed_point() {
3848 let mut g = Graph::new();
3852 let input = g.add_node(Op::Input, vec![], DtypeRepr::F32, shape_1d(4));
3853 let relu = g.add_node(Op::Relu, vec![input], DtypeRepr::F32, shape_1d(4));
3854 let sigmoid = g.add_node(Op::Sigmoid, vec![relu], DtypeRepr::F32, shape_1d(4));
3855 let silu = g.add_node(Op::Silu, vec![sigmoid], DtypeRepr::F32, shape_1d(4));
3856 g.add_node(Op::Tanh, vec![silu], DtypeRepr::F32, shape_1d(4));
3857
3858 let fused = g.fuse_elementwise_chains();
3859
3860 assert_eq!(fused.nodes.len(), 2);
3861 match &fused.nodes[1].op {
3862 Op::Fused { members } => {
3863 assert_eq!(members.len(), 4);
3864 assert!(matches!(members[0], Op::Relu));
3865 assert!(matches!(members[1], Op::Sigmoid));
3866 assert!(matches!(members[2], Op::Silu));
3867 assert!(matches!(members[3], Op::Tanh));
3868 }
3869 other => panic!("expected Op::Fused, got {other:?}"),
3870 }
3871 }
3872
3873 #[test]
3874 fn test_optimise_does_not_produce_fused_nodes() {
3875 let mut g = Graph::new();
3879 let input = g.add_node(Op::Input, vec![], DtypeRepr::F32, shape_1d(4));
3880 let relu = g.add_node(Op::Relu, vec![input], DtypeRepr::F32, shape_1d(4));
3881 g.add_node(Op::Sigmoid, vec![relu], DtypeRepr::F32, shape_1d(4));
3882
3883 let optimised = g.optimise();
3884
3885 assert_eq!(optimised.nodes.len(), 3);
3886 for node in &optimised.nodes {
3887 assert!(!matches!(node.op, Op::Fused { .. }));
3888 }
3889 }
3890}