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] =
1931 Some((Op::Fused { members }, self.nodes[parent_idx].inputs.clone()));
1932 changed = true;
1933 }
1934
1935 if !changed {
1936 return (self.clone(), false);
1937 }
1938
1939 let mut old_to_new = vec![0usize; n];
1940 let mut new_count = 0usize;
1941 for i in 0..n {
1942 if !dead[i] {
1943 old_to_new[i] = new_count;
1944 new_count += 1;
1945 }
1946 }
1947
1948 let mut new_graph = Graph::new();
1949 for old_idx in 0..n {
1950 if dead[old_idx] {
1951 continue;
1952 }
1953 let node = &self.nodes[old_idx];
1954 let (op, inputs) =
1955 if let Some((fused_op, fused_inputs)) = node_override[old_idx].clone() {
1956 let mapped = fused_inputs.iter().map(|&i| old_to_new[i]).collect();
1957 (fused_op, mapped)
1958 } else {
1959 let mapped = node.inputs.iter().map(|&i| old_to_new[i]).collect();
1960 (node.op.clone(), mapped)
1961 };
1962 let new_idx = new_graph.nodes.len();
1963 new_graph.nodes.push(GraphNode {
1964 op,
1965 inputs,
1966 dtype: node.dtype,
1967 shape: node.shape.clone(),
1968 });
1969 if let Some(name) = self.names.get(&old_idx) {
1970 new_graph.names.insert(new_idx, name.clone());
1971 }
1972 }
1973
1974 (new_graph, true)
1975 }
1976}
1977
1978pub fn is_fusable_elementwise(op: &Op) -> bool {
1991 matches!(op, Op::Relu | Op::Sigmoid | Op::Silu | Op::Tanh)
1992}
1993
1994fn infer_output_shape(op: &Op, inputs: &[&Shape]) -> Shape {
1999 if let Op::Constant { shape, .. } = op {
2001 return shape.clone();
2002 }
2003 if matches!(op, Op::SequenceEmpty | Op::OptionalHasElement) {
2005 return vec![];
2006 }
2007 let input = inputs[0];
2008 match op {
2009 Op::Input => input.clone(),
2010
2011 Op::Fused { members } => members.iter().fold(input.clone(), |shape, member| {
2012 infer_output_shape(member, &[&shape])
2013 }),
2014
2015 Op::Relu
2017 | Op::Elu { .. }
2018 | Op::Selu
2019 | Op::Celu { .. }
2020 | Op::Gelu
2021 | Op::Mish
2022 | Op::Hardtanh { .. }
2023 | Op::Relu6
2024 | Op::Hardsigmoid
2025 | Op::Hardswish
2026 | Op::Hardshrink { .. }
2027 | Op::LeakyRelu { .. }
2028 | Op::Threshold { .. }
2029 | Op::Softsign
2030 | Op::Softshrink { .. }
2031 | Op::Softplus { .. }
2032 | Op::Sigmoid
2033 | Op::Silu
2034 | Op::Logsigmoid
2035 | Op::Tanh
2036 | Op::Tanhshrink
2037 | Op::Softmax { .. }
2038 | Op::BatchNorm1d { .. }
2039 | Op::BatchNorm2d { .. }
2040 | Op::BatchNorm3d { .. }
2041 | Op::LayerNorm { .. }
2042 | Op::RmsNorm { .. }
2043 | Op::GroupNorm { .. }
2044 | Op::InstanceNorm1d { .. }
2045 | Op::InstanceNorm2d { .. }
2046 | Op::InstanceNorm3d { .. } => input.clone(),
2047
2048 Op::Linear { out_features, .. } => {
2049 let mut out = input[..input.len() - 1].to_vec();
2051 out.push(Some(*out_features));
2052 out
2053 }
2054
2055 Op::Flatten => {
2056 let rest = &input[1..];
2058 let flat: Option<usize> = rest
2059 .iter()
2060 .try_fold(1usize, |acc, dim| dim.map(|d| acc * d));
2061 vec![input[0], flat]
2062 }
2063
2064 Op::Conv1d {
2066 out_channels,
2067 kernel_l,
2068 stride,
2069 padding,
2070 ..
2071 } => {
2072 let l_out = input[2].map(|l| (l + 2 * padding - kernel_l) / stride + 1);
2074 vec![input[0], Some(*out_channels), l_out]
2075 }
2076
2077 Op::Conv2d {
2078 out_channels,
2079 kernel_h,
2080 kernel_w,
2081 stride_h,
2082 stride_w,
2083 padding_h,
2084 padding_w,
2085 ..
2086 }
2087 | Op::Conv2dBnSilu {
2088 out_channels,
2089 kernel_h,
2090 kernel_w,
2091 stride_h,
2092 stride_w,
2093 padding_h,
2094 padding_w,
2095 ..
2096 } => {
2097 let h_out = input[2].map(|h| (h + 2 * padding_h - kernel_h) / stride_h + 1);
2099 let w_out = input[3].map(|w| (w + 2 * padding_w - kernel_w) / stride_w + 1);
2100 vec![input[0], Some(*out_channels), h_out, w_out]
2101 }
2102
2103 Op::Conv3d {
2104 out_channels,
2105 kernel_d,
2106 kernel_h,
2107 kernel_w,
2108 stride_d,
2109 stride_h,
2110 stride_w,
2111 padding_d,
2112 padding_h,
2113 padding_w,
2114 ..
2115 } => {
2116 let d_out = input[2].map(|d| (d + 2 * padding_d - kernel_d) / stride_d + 1);
2118 let h_out = input[3].map(|h| (h + 2 * padding_h - kernel_h) / stride_h + 1);
2119 let w_out = input[4].map(|w| (w + 2 * padding_w - kernel_w) / stride_w + 1);
2120 vec![input[0], Some(*out_channels), d_out, h_out, w_out]
2121 }
2122
2123 Op::AvgPool1d { kernel_l, stride } | Op::MaxPool1d { kernel_l, stride } => {
2125 let l_out = input[2].map(|l| (l - kernel_l) / stride + 1);
2126 vec![input[0], input[1], l_out]
2127 }
2128
2129 Op::LpPool1d {
2130 kernel_l, stride, ..
2131 } => {
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::AvgPool2d {
2137 kernel_h,
2138 kernel_w,
2139 stride_h,
2140 stride_w,
2141 } => {
2142 let h_out = input[2].map(|h| (h - kernel_h) / stride_h + 1);
2143 let w_out = input[3].map(|w| (w - kernel_w) / stride_w + 1);
2144 vec![input[0], input[1], h_out, w_out]
2145 }
2146
2147 Op::MaxPool2d {
2148 kernel_h,
2149 kernel_w,
2150 stride_h,
2151 stride_w,
2152 pad_h,
2153 pad_w,
2154 } => {
2155 let h_out = input[2].map(|h| (h + 2 * pad_h - kernel_h) / stride_h + 1);
2156 let w_out = input[3].map(|w| (w + 2 * pad_w - kernel_w) / stride_w + 1);
2157 vec![input[0], input[1], h_out, w_out]
2158 }
2159
2160 Op::LpPool2d {
2161 kernel_h,
2162 kernel_w,
2163 stride_h,
2164 stride_w,
2165 ..
2166 } => {
2167 let h_out = input[2].map(|h| (h - kernel_h) / stride_h + 1);
2168 let w_out = input[3].map(|w| (w - kernel_w) / stride_w + 1);
2169 vec![input[0], input[1], h_out, w_out]
2170 }
2171
2172 Op::AvgPool3d {
2173 kernel_d,
2174 kernel_h,
2175 kernel_w,
2176 stride_d,
2177 stride_h,
2178 stride_w,
2179 }
2180 | Op::MaxPool3d {
2181 kernel_d,
2182 kernel_h,
2183 kernel_w,
2184 stride_d,
2185 stride_h,
2186 stride_w,
2187 } => {
2188 let d_out = input[2].map(|d| (d - kernel_d) / stride_d + 1);
2189 let h_out = input[3].map(|h| (h - kernel_h) / stride_h + 1);
2190 let w_out = input[4].map(|w| (w - kernel_w) / stride_w + 1);
2191 vec![input[0], input[1], d_out, h_out, w_out]
2192 }
2193
2194 Op::LpPool3d {
2195 kernel_d,
2196 kernel_h,
2197 kernel_w,
2198 stride_d,
2199 stride_h,
2200 stride_w,
2201 ..
2202 } => {
2203 let d_out = input[2].map(|d| (d - kernel_d) / stride_d + 1);
2204 let h_out = input[3].map(|h| (h - kernel_h) / stride_h + 1);
2205 let w_out = input[4].map(|w| (w - kernel_w) / stride_w + 1);
2206 vec![input[0], input[1], d_out, h_out, w_out]
2207 }
2208
2209 Op::UpsampleNearest2d { scale_h, scale_w } => {
2211 let h_out = input[2].map(|h| h * scale_h);
2213 let w_out = input[3].map(|w| w * scale_w);
2214 vec![input[0], input[1], h_out, w_out]
2215 }
2216
2217 Op::ConstantPad1d {
2219 pad_left,
2220 pad_right,
2221 ..
2222 }
2223 | Op::ReflectionPad1d {
2224 pad_left,
2225 pad_right,
2226 }
2227 | Op::ReplicationPad1d {
2228 pad_left,
2229 pad_right,
2230 }
2231 | Op::CircularPad1d {
2232 pad_left,
2233 pad_right,
2234 } => {
2235 let l_out = input[2].map(|l| l + pad_left + pad_right);
2237 vec![input[0], input[1], l_out]
2238 }
2239
2240 Op::ConstantPad2d {
2241 pad_l,
2242 pad_r,
2243 pad_t,
2244 pad_b,
2245 ..
2246 }
2247 | Op::ReflectionPad2d {
2248 pad_l,
2249 pad_r,
2250 pad_t,
2251 pad_b,
2252 }
2253 | Op::ReplicationPad2d {
2254 pad_l,
2255 pad_r,
2256 pad_t,
2257 pad_b,
2258 }
2259 | Op::CircularPad2d {
2260 pad_l,
2261 pad_r,
2262 pad_t,
2263 pad_b,
2264 } => {
2265 let h_out = input[2].map(|h| h + pad_t + pad_b);
2267 let w_out = input[3].map(|w| w + pad_l + pad_r);
2268 vec![input[0], input[1], h_out, w_out]
2269 }
2270
2271 Op::ConstantPad3d {
2272 pad_d1,
2273 pad_d2,
2274 pad_h1,
2275 pad_h2,
2276 pad_w1,
2277 pad_w2,
2278 ..
2279 }
2280 | Op::ReflectionPad3d {
2281 pad_d1,
2282 pad_d2,
2283 pad_h1,
2284 pad_h2,
2285 pad_w1,
2286 pad_w2,
2287 }
2288 | Op::ReplicationPad3d {
2289 pad_d1,
2290 pad_d2,
2291 pad_h1,
2292 pad_h2,
2293 pad_w1,
2294 pad_w2,
2295 }
2296 | Op::CircularPad3d {
2297 pad_d1,
2298 pad_d2,
2299 pad_h1,
2300 pad_h2,
2301 pad_w1,
2302 pad_w2,
2303 } => {
2304 let d_out = input[2].map(|d| d + pad_d1 + pad_d2);
2306 let h_out = input[3].map(|h| h + pad_h1 + pad_h2);
2307 let w_out = input[4].map(|w| w + pad_w1 + pad_w2);
2308 vec![input[0], input[1], d_out, h_out, w_out]
2309 }
2310
2311 Op::Attention { .. } => input.clone(),
2312
2313 Op::Add => input.clone(),
2314
2315 Op::ChannelChunk { chunk_c, .. } => {
2316 vec![input[0], Some(*chunk_c), input[2], input[3]]
2318 }
2319
2320 Op::ChannelCat { c_total } => {
2321 vec![input[0], Some(*c_total), input[2], input[3]]
2323 }
2324
2325 Op::ChannelBiasAdd { .. } => input.to_vec(),
2326
2327 Op::Custom { data } => data.infer_output_shape(inputs),
2328
2329 Op::Abs
2339 | Op::Neg
2340 | Op::Ceil
2341 | Op::Floor
2342 | Op::Round
2343 | Op::Sqrt
2344 | Op::Reciprocal
2345 | Op::Exp
2346 | Op::Log
2347 | Op::Erf
2348 | Op::Sign
2349 | Op::IsNaN
2350 | Op::IsInf { .. }
2351 | Op::Not
2352 | Op::BitwiseNot
2353 | Op::Sin
2354 | Op::Cos
2355 | Op::Tan
2356 | Op::Asin
2357 | Op::Acos
2358 | Op::Atan
2359 | Op::Sinh
2360 | Op::Cosh
2361 | Op::Asinh
2362 | Op::Acosh
2363 | Op::Atanh
2364 | Op::PRelu
2365 | Op::ThresholdedRelu { .. }
2366 | Op::Shrink { .. }
2367 | Op::Clip
2368 | Op::Swish
2369 | Op::LogSoftmax { .. }
2370 | Op::Hardmax { .. }
2371 | Op::Dropout { .. }
2372 | Op::Identity
2373 | Op::LRN { .. }
2374 | Op::MeanVarianceNormalization { .. }
2375 | Op::LpNormalization { .. }
2376 | Op::Pad { .. }
2377 | Op::ReverseSequence { .. }
2378 | Op::Trilu { .. }
2379 | Op::CumSum { .. }
2380 | Op::CumProd { .. }
2381 | Op::QuantizeLinear { .. }
2382 | Op::DequantizeLinear { .. }
2383 | Op::DynamicQuantizeLinear
2384 | Op::Bernoulli { .. }
2385 | Op::RandomUniformLike { .. }
2386 | Op::EyeLike { .. }
2387 | Op::RotaryEmbedding
2388 | Op::MultiHeadAttention { .. }
2389 | Op::FlexAttention { .. }
2390 | Op::LinearAttention { .. }
2391 | Op::CausalConvWithState { .. } => input.clone(),
2392
2393 Op::Mul
2395 | Op::Sub
2396 | Op::Div
2397 | Op::Pow
2398 | Op::Mod { .. }
2399 | Op::ElemMin
2400 | Op::ElemMax
2401 | Op::ElemMean
2402 | Op::ElemSum
2403 | Op::Equal
2404 | Op::Greater
2405 | Op::GreaterOrEqual
2406 | Op::Less
2407 | Op::LessOrEqual
2408 | Op::And
2409 | Op::Or
2410 | Op::Xor
2411 | Op::BitwiseAnd
2412 | Op::BitwiseOr
2413 | Op::BitwiseXor
2414 | Op::BitShift { .. }
2415 | Op::Cast { .. }
2416 | Op::CastLike
2417 | Op::BitCast { .. }
2418 | Op::Where => input.clone(),
2419
2420 Op::Reshape
2423 | Op::Squeeze { .. }
2424 | Op::Unsqueeze { .. }
2425 | Op::Slice
2426 | Op::Gather { .. }
2427 | Op::GatherElements { .. }
2428 | Op::GatherND { .. }
2429 | Op::ScatterElements { .. }
2430 | Op::ScatterND
2431 | Op::Tile
2432 | Op::Expand
2433 | Op::Compress { .. }
2434 | Op::Range
2435 | Op::ConstantOfShape { .. }
2436 | Op::NonZero
2437 | Op::Scatter { .. }
2438 | Op::TensorScatter
2439 | Op::Resize { .. }
2440 | Op::GridSample { .. }
2441 | Op::AffineGrid { .. }
2442 | Op::CenterCropPad { .. } => input.clone(),
2443
2444 Op::Transpose { perm } => {
2445 if perm.is_empty() {
2446 input.iter().rev().cloned().collect()
2447 } else {
2448 perm.iter()
2449 .map(|&i| input.get(i).copied().unwrap_or(None))
2450 .collect()
2451 }
2452 }
2453
2454 Op::Concat { axis } => {
2455 let rank = input.len();
2456 if rank == 0 {
2457 return input.clone();
2458 }
2459 let ax = axis.rem_euclid(rank as i64) as usize;
2460 let mut out = input.clone();
2461 out[ax] = inputs
2463 .iter()
2464 .try_fold(0usize, |acc, s| {
2465 s.get(ax).copied().unwrap_or(None).map(|d| acc + d)
2466 })
2467 .map(Some)
2468 .unwrap_or(None);
2469 out
2470 }
2471
2472 Op::Split { axis, num_outputs } => {
2473 let rank = input.len();
2474 if rank == 0 {
2475 return input.clone();
2476 }
2477 let ax = axis.rem_euclid(rank as i64) as usize;
2478 let mut out = input.clone();
2479 out[ax] = input[ax].map(|d| d / num_outputs.max(&1));
2480 out
2481 }
2482
2483 Op::ShapeOf { start, end } => {
2484 let rank = input.len() as i64;
2485 let s = start.rem_euclid(rank.max(1));
2486 let e = end.rem_euclid(rank.max(1));
2487 vec![Some((e - s).max(0) as usize)]
2488 }
2489
2490 Op::SizeOf => vec![Some(1)],
2491
2492 Op::Gemm {
2493 trans_a, trans_b, ..
2494 } => {
2495 let m = if *trans_a {
2496 input.get(1)
2497 } else {
2498 input.first()
2499 }
2500 .copied()
2501 .unwrap_or(None);
2502 let n = if inputs.len() >= 2 {
2503 let b = inputs[1];
2504 if *trans_b { b.first() } else { b.get(1) }
2505 .copied()
2506 .unwrap_or(None)
2507 } else {
2508 None
2509 };
2510 vec![m, n]
2511 }
2512
2513 Op::MatMul | Op::MatMulInteger | Op::QLinearMatMul => {
2514 if inputs.len() >= 2 && !input.is_empty() {
2515 let other = inputs[1];
2516 let mut out = input[..input.len() - 1].to_vec();
2517 out.push(other.last().copied().unwrap_or(None));
2518 out
2519 } else {
2520 input.clone()
2521 }
2522 }
2523
2524 Op::Einsum { .. }
2525 | Op::Det
2526 | Op::Col2Im { .. }
2527 | Op::ConvInteger { .. }
2528 | Op::DeformConv { .. }
2529 | Op::QLinearConv { .. } => input.clone(),
2530
2531 Op::ConvTranspose {
2532 out_channels,
2533 kernel_h,
2534 kernel_w,
2535 stride_h,
2536 stride_w,
2537 padding_h,
2538 padding_w,
2539 output_padding_h,
2540 output_padding_w,
2541 ..
2542 } => {
2543 let h_out =
2544 input[2].map(|h| (h - 1) * stride_h - 2 * padding_h + kernel_h + output_padding_h);
2545 let w_out =
2546 input[3].map(|w| (w - 1) * stride_w - 2 * padding_w + kernel_w + output_padding_w);
2547 vec![input[0], Some(*out_channels), h_out, w_out]
2548 }
2549
2550 Op::ReduceSum { keepdims, .. }
2551 | Op::ReduceMean { keepdims, .. }
2552 | Op::ReduceMax { keepdims, .. }
2553 | Op::ReduceMin { keepdims, .. }
2554 | Op::ReduceProd { keepdims, .. }
2555 | Op::ReduceL1 { keepdims, .. }
2556 | Op::ReduceL2 { keepdims, .. }
2557 | Op::ReduceLogSum { keepdims, .. }
2558 | Op::ReduceLogSumExp { keepdims, .. }
2559 | Op::ReduceSumSquare { keepdims, .. } => {
2560 if *keepdims {
2563 input.clone()
2564 } else {
2565 vec![Some(1)]
2566 }
2567 }
2568
2569 Op::ArgMax { axis, keepdims, .. } | Op::ArgMin { axis, keepdims, .. } => {
2570 if input.is_empty() {
2571 return vec![];
2572 }
2573 let ax = axis.rem_euclid(input.len() as i64) as usize;
2574 if *keepdims {
2575 let mut out = input.clone();
2576 out[ax] = Some(1);
2577 out
2578 } else {
2579 let mut out = input.clone();
2580 out.remove(ax);
2581 out
2582 }
2583 }
2584
2585 Op::GlobalAvgPool | Op::GlobalMaxPool => {
2586 let mut out = input[..2.min(input.len())].to_vec();
2587 for _ in 2..input.len() {
2588 out.push(Some(1));
2589 }
2590 out
2591 }
2592
2593 Op::Lstm {
2594 hidden_size,
2595 bidirectional,
2596 ..
2597 }
2598 | Op::Gru {
2599 hidden_size,
2600 bidirectional,
2601 ..
2602 }
2603 | Op::Rnn {
2604 hidden_size,
2605 bidirectional,
2606 ..
2607 } => {
2608 let num_dirs: usize = if *bidirectional { 2 } else { 1 };
2609 vec![
2611 input.first().copied().unwrap_or(None),
2612 Some(num_dirs),
2613 input.get(1).copied().unwrap_or(None),
2614 Some(*hidden_size),
2615 ]
2616 }
2617
2618 Op::SpaceToDepth { blocksize } => {
2619 let c_out = input[1].map(|c| c * blocksize * blocksize);
2620 let h_out = input[2].map(|h| h / blocksize);
2621 let w_out = input[3].map(|w| w / blocksize);
2622 vec![input[0], c_out, h_out, w_out]
2623 }
2624
2625 Op::DepthToSpace { 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::RoiAlign {
2633 output_h, output_w, ..
2634 } => {
2635 vec![input[0], input[1], Some(*output_h), Some(*output_w)]
2636 }
2637
2638 Op::MaxUnpool {
2639 kernel_h,
2640 kernel_w,
2641 stride_h,
2642 stride_w,
2643 } => {
2644 let h_out = input[2].map(|h| (h - 1) * stride_h + kernel_h);
2645 let w_out = input[3].map(|w| (w - 1) * stride_w + kernel_w);
2646 vec![input[0], input[1], h_out, w_out]
2647 }
2648
2649 Op::NonMaxSuppression { .. } => vec![None, Some(3)],
2650
2651 Op::TopK { axis, .. } => {
2652 let _ = axis;
2654 input.clone()
2655 }
2656
2657 Op::Unique { .. } => input.clone(),
2658 Op::OneHot { .. } => input.clone(),
2659
2660 Op::NegativeLogLikelihoodLoss { .. } | Op::SoftmaxCrossEntropyLoss { .. } => {
2661 vec![Some(1)]
2662 }
2663
2664 Op::Dft { onesided, .. } => {
2665 if *onesided && input.len() >= 2 {
2667 let mut out = input.clone();
2668 *out.last_mut().unwrap() = None;
2669 out
2670 } else {
2671 input.clone()
2672 }
2673 }
2674
2675 Op::Stft
2676 | Op::MelWeightMatrix
2677 | Op::HannWindow { .. }
2678 | Op::BlackmanWindow { .. }
2679 | Op::HammingWindow { .. } => input.clone(),
2680
2681 Op::SequenceAt
2682 | Op::SequenceConstruct
2683 | Op::SequenceErase
2684 | Op::SequenceInsert
2685 | Op::SequenceLength
2686 | Op::SequenceMap
2687 | Op::SplitToSequence { .. }
2688 | Op::ConcatFromSequence { .. }
2689 | Op::OptionalGetElement
2690 | Op::Loop
2691 | Op::Scan { .. }
2692 | Op::If
2693 | Op::Adagrad
2694 | Op::Adam
2695 | Op::Momentum
2696 | Op::Gradient
2697 | Op::StringNormalizer
2698 | Op::RegexFullMatch { .. }
2699 | Op::StringConcat
2700 | Op::StringSplit
2701 | Op::TfIdfVectorizer
2702 | Op::LabelEncoder
2703 | Op::ArrayFeatureExtractor
2704 | Op::Binarizer { .. }
2705 | Op::TreeEnsemble
2706 | Op::ImageDecoder => input.clone(),
2707
2708 Op::Constant { shape, .. } => shape.clone(),
2710 Op::SequenceEmpty | Op::OptionalHasElement => vec![],
2711 }
2712}
2713
2714#[derive(Clone)]
2722pub struct SymTensor {
2723 pub node_id: usize,
2725 pub graph: Rc<RefCell<Graph>>,
2727 pub dtype: DtypeRepr,
2729 pub shape: Shape,
2732}
2733
2734impl<D: Dtype, const RANK: usize> RankedTensor<D, RANK> for SymTensor {
2737 const SHAPE: [usize; RANK] = [0; RANK];
2738}
2739impl<D: Dtype, const RANK: usize> Tensor<D, RANK> for SymTensor {}
2740
2741impl SymTensor {
2742 pub fn input(dtype: DtypeRepr, shape: Shape) -> (Self, Rc<RefCell<Graph>>) {
2750 let graph = Rc::new(RefCell::new(Graph::new()));
2751 let node_id = graph
2752 .borrow_mut()
2753 .add_node(Op::Input, vec![], dtype, shape.clone());
2754 let tensor = Self {
2755 node_id,
2756 graph: graph.clone(),
2757 dtype,
2758 shape,
2759 };
2760 (tensor, graph)
2761 }
2762
2763 pub fn rank(&self) -> usize {
2765 self.shape.len()
2766 }
2767
2768 fn record(&self, op: Op) -> Self {
2769 let output_shape = infer_output_shape(&op, &[&self.shape]);
2770 self.record_with_shape(op, output_shape)
2771 }
2772
2773 fn record_with_shape(&self, op: Op, shape: Shape) -> Self {
2774 let node_id =
2775 self.graph
2776 .borrow_mut()
2777 .add_node(op, vec![self.node_id], self.dtype, shape.clone());
2778 Self {
2779 node_id,
2780 graph: self.graph.clone(),
2781 dtype: self.dtype,
2782 shape,
2783 }
2784 }
2785
2786 pub fn record_custom(
2792 &self,
2793 data: CustomData,
2794 other_inputs: &[&SymTensor],
2795 dtype: Option<DtypeRepr>,
2796 ) -> Self {
2797 let mut shapes: Vec<&Shape> = vec![&self.shape];
2798 shapes.extend(other_inputs.iter().map(|t| &t.shape));
2799 let output_shape = data.infer_output_shape(&shapes);
2800
2801 let mut input_ids: Vec<usize> = vec![self.node_id];
2802 input_ids.extend(other_inputs.iter().map(|t| t.node_id));
2803
2804 let out_dtype = dtype.unwrap_or(self.dtype);
2805 let node_id = self.graph.borrow_mut().add_node(
2806 Op::Custom { data },
2807 input_ids,
2808 out_dtype,
2809 output_shape.clone(),
2810 );
2811 Self {
2812 node_id,
2813 graph: self.graph.clone(),
2814 dtype: out_dtype,
2815 shape: output_shape,
2816 }
2817 }
2818}
2819
2820impl<D: Dtype, const RANK: usize> Layer<SymTensor> for Linear<D, SymTensor, SymTensor, RANK> {
2827 type Output = SymTensor;
2828 fn call(&self, input: SymTensor) -> SymTensor {
2829 input.record(Op::Linear {
2830 in_features: self.in_features,
2831 out_features: self.out_features,
2832 has_bias: self.has_bias,
2833 })
2834 }
2835}
2836
2837impl<D: Dtype> Layer<SymTensor> for Flatten<D, SymTensor, SymTensor> {
2838 type Output = SymTensor;
2839 fn call(&self, input: SymTensor) -> SymTensor {
2840 input.record(Op::Flatten)
2841 }
2842}
2843
2844impl<D: Dtype, const RANK: usize> Layer<SymTensor> for BatchNorm1d<D, SymTensor, SymTensor, RANK> {
2847 type Output = SymTensor;
2848 fn call(&self, input: SymTensor) -> SymTensor {
2849 input.record(Op::BatchNorm1d {
2850 num_features: self.num_features,
2851 eps: self.eps,
2852 momentum: self.momentum,
2853 affine: self.affine,
2854 track_running_stats: self.track_running_stats,
2855 })
2856 }
2857}
2858
2859impl<D: Dtype, const RANK: usize> Layer<SymTensor> for BatchNorm2d<D, SymTensor, SymTensor, RANK> {
2860 type Output = SymTensor;
2861 fn call(&self, input: SymTensor) -> SymTensor {
2862 input.record(Op::BatchNorm2d {
2863 num_features: self.num_features,
2864 eps: self.eps,
2865 momentum: self.momentum,
2866 affine: self.affine,
2867 track_running_stats: self.track_running_stats,
2868 })
2869 }
2870}
2871
2872impl<D: Dtype, const RANK: usize> Layer<SymTensor> for BatchNorm3d<D, SymTensor, SymTensor, RANK> {
2873 type Output = SymTensor;
2874 fn call(&self, input: SymTensor) -> SymTensor {
2875 input.record(Op::BatchNorm3d {
2876 num_features: self.num_features,
2877 eps: self.eps,
2878 momentum: self.momentum,
2879 affine: self.affine,
2880 track_running_stats: self.track_running_stats,
2881 })
2882 }
2883}
2884
2885impl<D: Dtype, const RANK: usize> Layer<SymTensor> for LayerNorm<D, SymTensor, SymTensor, RANK> {
2886 type Output = SymTensor;
2887 fn call(&self, input: SymTensor) -> SymTensor {
2888 input.record(Op::LayerNorm {
2889 normalized_shape: self.normalized_shape.clone(),
2890 eps: self.eps,
2891 affine: self.affine,
2892 })
2893 }
2894}
2895
2896impl<D: Dtype, const RANK: usize> Layer<SymTensor> for RmsNorm<D, SymTensor, SymTensor, RANK> {
2897 type Output = SymTensor;
2898 fn call(&self, input: SymTensor) -> SymTensor {
2899 input.record(Op::RmsNorm {
2900 normalized_shape: self.normalized_shape.clone(),
2901 eps: self.eps,
2902 affine: self.affine,
2903 })
2904 }
2905}
2906
2907impl<D: Dtype, const RANK: usize> Layer<SymTensor> for GroupNorm<D, SymTensor, SymTensor, RANK> {
2908 type Output = SymTensor;
2909 fn call(&self, input: SymTensor) -> SymTensor {
2910 input.record(Op::GroupNorm {
2911 num_groups: self.num_groups,
2912 num_channels: self.num_channels,
2913 eps: self.eps,
2914 affine: self.affine,
2915 })
2916 }
2917}
2918
2919impl<D: Dtype, const RANK: usize> Layer<SymTensor>
2920 for InstanceNorm1d<D, SymTensor, SymTensor, RANK>
2921{
2922 type Output = SymTensor;
2923 fn call(&self, input: SymTensor) -> SymTensor {
2924 input.record(Op::InstanceNorm1d {
2925 num_features: self.num_features,
2926 eps: self.eps,
2927 momentum: self.momentum,
2928 affine: self.affine,
2929 track_running_stats: self.track_running_stats,
2930 })
2931 }
2932}
2933
2934impl<D: Dtype, const RANK: usize> Layer<SymTensor>
2935 for InstanceNorm2d<D, SymTensor, SymTensor, RANK>
2936{
2937 type Output = SymTensor;
2938 fn call(&self, input: SymTensor) -> SymTensor {
2939 input.record(Op::InstanceNorm2d {
2940 num_features: self.num_features,
2941 eps: self.eps,
2942 momentum: self.momentum,
2943 affine: self.affine,
2944 track_running_stats: self.track_running_stats,
2945 })
2946 }
2947}
2948
2949impl<D: Dtype, const RANK: usize> Layer<SymTensor>
2950 for InstanceNorm3d<D, SymTensor, SymTensor, RANK>
2951{
2952 type Output = SymTensor;
2953 fn call(&self, input: SymTensor) -> SymTensor {
2954 input.record(Op::InstanceNorm3d {
2955 num_features: self.num_features,
2956 eps: self.eps,
2957 momentum: self.momentum,
2958 affine: self.affine,
2959 track_running_stats: self.track_running_stats,
2960 })
2961 }
2962}
2963
2964impl<D: Dtype, const RANK: usize> Layer<SymTensor> for Conv1d<D, SymTensor, SymTensor, RANK> {
2967 type Output = SymTensor;
2968 fn call(&self, input: SymTensor) -> SymTensor {
2969 input.record(Op::Conv1d {
2970 in_channels: self.in_channels,
2971 out_channels: self.out_channels,
2972 kernel_l: self.kernel_l,
2973 stride: self.stride,
2974 padding: self.padding,
2975 has_bias: self.has_bias,
2976 })
2977 }
2978}
2979
2980impl<D: Dtype, const RANK: usize> Layer<SymTensor> for Conv2d<D, SymTensor, SymTensor, RANK> {
2981 type Output = SymTensor;
2982 fn call(&self, input: SymTensor) -> SymTensor {
2983 input.record(Op::Conv2d {
2984 in_channels: self.in_channels,
2985 out_channels: self.out_channels,
2986 kernel_h: self.kernel_h,
2987 kernel_w: self.kernel_w,
2988 stride_h: self.stride_h,
2989 stride_w: self.stride_w,
2990 padding_h: self.padding_h,
2991 padding_w: self.padding_w,
2992 groups: self.groups,
2993 has_bias: self.has_bias,
2994 })
2995 }
2996}
2997
2998impl<D: Dtype, const RANK: usize> Layer<SymTensor> for Conv3d<D, SymTensor, SymTensor, RANK> {
2999 type Output = SymTensor;
3000 fn call(&self, input: SymTensor) -> SymTensor {
3001 input.record(Op::Conv3d {
3002 in_channels: self.in_channels,
3003 out_channels: self.out_channels,
3004 kernel_d: self.kernel_d,
3005 kernel_h: self.kernel_h,
3006 kernel_w: self.kernel_w,
3007 stride_d: self.stride_d,
3008 stride_h: self.stride_h,
3009 stride_w: self.stride_w,
3010 padding_d: self.padding_d,
3011 padding_h: self.padding_h,
3012 padding_w: self.padding_w,
3013 has_bias: self.has_bias,
3014 })
3015 }
3016}
3017
3018impl<D: Dtype, const RANK: usize> Layer<SymTensor> for AvgPool1d<D, SymTensor, SymTensor, RANK> {
3021 type Output = SymTensor;
3022 fn call(&self, input: SymTensor) -> SymTensor {
3023 input.record(Op::AvgPool1d {
3024 kernel_l: self.kernel_l,
3025 stride: self.stride,
3026 })
3027 }
3028}
3029
3030impl<D: Dtype, const RANK: usize> Layer<SymTensor> for AvgPool2d<D, SymTensor, SymTensor, RANK> {
3031 type Output = SymTensor;
3032 fn call(&self, input: SymTensor) -> SymTensor {
3033 input.record(Op::AvgPool2d {
3034 kernel_h: self.kernel_h,
3035 kernel_w: self.kernel_w,
3036 stride_h: self.stride_h,
3037 stride_w: self.stride_w,
3038 })
3039 }
3040}
3041
3042impl<D: Dtype, const RANK: usize> Layer<SymTensor> for AvgPool3d<D, SymTensor, SymTensor, RANK> {
3043 type Output = SymTensor;
3044 fn call(&self, input: SymTensor) -> SymTensor {
3045 input.record(Op::AvgPool3d {
3046 kernel_d: self.kernel_d,
3047 kernel_h: self.kernel_h,
3048 kernel_w: self.kernel_w,
3049 stride_d: self.stride_d,
3050 stride_h: self.stride_h,
3051 stride_w: self.stride_w,
3052 })
3053 }
3054}
3055
3056impl<D: Dtype, const RANK: usize> Layer<SymTensor> for MaxPool1d<D, SymTensor, SymTensor, RANK> {
3057 type Output = SymTensor;
3058 fn call(&self, input: SymTensor) -> SymTensor {
3059 input.record(Op::MaxPool1d {
3060 kernel_l: self.kernel_l,
3061 stride: self.stride,
3062 })
3063 }
3064}
3065
3066impl<D: Dtype, const RANK: usize> Layer<SymTensor> for MaxPool2d<D, SymTensor, SymTensor, RANK> {
3067 type Output = SymTensor;
3068 fn call(&self, input: SymTensor) -> SymTensor {
3069 input.record(Op::MaxPool2d {
3070 kernel_h: self.kernel_h,
3071 kernel_w: self.kernel_w,
3072 stride_h: self.stride_h,
3073 stride_w: self.stride_w,
3074 pad_h: self.padding_h,
3075 pad_w: self.padding_w,
3076 })
3077 }
3078}
3079
3080impl<D: Dtype, const RANK: usize> Layer<SymTensor> for MaxPool3d<D, SymTensor, SymTensor, RANK> {
3081 type Output = SymTensor;
3082 fn call(&self, input: SymTensor) -> SymTensor {
3083 input.record(Op::MaxPool3d {
3084 kernel_d: self.kernel_d,
3085 kernel_h: self.kernel_h,
3086 kernel_w: self.kernel_w,
3087 stride_d: self.stride_d,
3088 stride_h: self.stride_h,
3089 stride_w: self.stride_w,
3090 })
3091 }
3092}
3093
3094impl<D: Dtype, const RANK: usize> Layer<SymTensor> for LpPool1d<D, SymTensor, SymTensor, RANK> {
3095 type Output = SymTensor;
3096 fn call(&self, input: SymTensor) -> SymTensor {
3097 input.record(Op::LpPool1d {
3098 kernel_l: self.kernel_l,
3099 stride: self.stride,
3100 p: self.p,
3101 })
3102 }
3103}
3104
3105impl<D: Dtype, const RANK: usize> Layer<SymTensor> for LpPool2d<D, SymTensor, SymTensor, RANK> {
3106 type Output = SymTensor;
3107 fn call(&self, input: SymTensor) -> SymTensor {
3108 input.record(Op::LpPool2d {
3109 kernel_h: self.kernel_h,
3110 kernel_w: self.kernel_w,
3111 stride_h: self.stride_h,
3112 stride_w: self.stride_w,
3113 p: self.p,
3114 })
3115 }
3116}
3117
3118impl<D: Dtype, const RANK: usize> Layer<SymTensor> for LpPool3d<D, SymTensor, SymTensor, RANK> {
3119 type Output = SymTensor;
3120 fn call(&self, input: SymTensor) -> SymTensor {
3121 input.record(Op::LpPool3d {
3122 kernel_d: self.kernel_d,
3123 kernel_h: self.kernel_h,
3124 kernel_w: self.kernel_w,
3125 stride_d: self.stride_d,
3126 stride_h: self.stride_h,
3127 stride_w: self.stride_w,
3128 p: self.p,
3129 })
3130 }
3131}
3132
3133impl<D: Dtype, const RANK: usize> Layer<SymTensor>
3136 for ConstantPad1d<D, SymTensor, SymTensor, RANK>
3137{
3138 type Output = SymTensor;
3139 fn call(&self, input: SymTensor) -> SymTensor {
3140 input.record(Op::ConstantPad1d {
3141 pad_left: self.pad_left,
3142 pad_right: self.pad_right,
3143 value: self.value,
3144 })
3145 }
3146}
3147
3148impl<D: Dtype, const RANK: usize> Layer<SymTensor>
3149 for ConstantPad2d<D, SymTensor, SymTensor, RANK>
3150{
3151 type Output = SymTensor;
3152 fn call(&self, input: SymTensor) -> SymTensor {
3153 input.record(Op::ConstantPad2d {
3154 pad_l: self.pad_l,
3155 pad_r: self.pad_r,
3156 pad_t: self.pad_t,
3157 pad_b: self.pad_b,
3158 value: self.value,
3159 })
3160 }
3161}
3162
3163impl<D: Dtype, const RANK: usize> Layer<SymTensor>
3164 for ConstantPad3d<D, SymTensor, SymTensor, RANK>
3165{
3166 type Output = SymTensor;
3167 fn call(&self, input: SymTensor) -> SymTensor {
3168 input.record(Op::ConstantPad3d {
3169 pad_d1: self.pad_d1,
3170 pad_d2: self.pad_d2,
3171 pad_h1: self.pad_h1,
3172 pad_h2: self.pad_h2,
3173 pad_w1: self.pad_w1,
3174 pad_w2: self.pad_w2,
3175 value: self.value,
3176 })
3177 }
3178}
3179
3180impl<D: Dtype, const RANK: usize> Layer<SymTensor>
3181 for ReflectionPad1d<D, SymTensor, SymTensor, RANK>
3182{
3183 type Output = SymTensor;
3184 fn call(&self, input: SymTensor) -> SymTensor {
3185 input.record(Op::ReflectionPad1d {
3186 pad_left: self.pad_left,
3187 pad_right: self.pad_right,
3188 })
3189 }
3190}
3191
3192impl<D: Dtype, const RANK: usize> Layer<SymTensor>
3193 for ReflectionPad2d<D, SymTensor, SymTensor, RANK>
3194{
3195 type Output = SymTensor;
3196 fn call(&self, input: SymTensor) -> SymTensor {
3197 input.record(Op::ReflectionPad2d {
3198 pad_l: self.pad_l,
3199 pad_r: self.pad_r,
3200 pad_t: self.pad_t,
3201 pad_b: self.pad_b,
3202 })
3203 }
3204}
3205
3206impl<D: Dtype, const RANK: usize> Layer<SymTensor>
3207 for ReflectionPad3d<D, SymTensor, SymTensor, RANK>
3208{
3209 type Output = SymTensor;
3210 fn call(&self, input: SymTensor) -> SymTensor {
3211 input.record(Op::ReflectionPad3d {
3212 pad_d1: self.pad_d1,
3213 pad_d2: self.pad_d2,
3214 pad_h1: self.pad_h1,
3215 pad_h2: self.pad_h2,
3216 pad_w1: self.pad_w1,
3217 pad_w2: self.pad_w2,
3218 })
3219 }
3220}
3221
3222impl<D: Dtype, const RANK: usize> Layer<SymTensor>
3223 for ReplicationPad1d<D, SymTensor, SymTensor, RANK>
3224{
3225 type Output = SymTensor;
3226 fn call(&self, input: SymTensor) -> SymTensor {
3227 input.record(Op::ReplicationPad1d {
3228 pad_left: self.pad_left,
3229 pad_right: self.pad_right,
3230 })
3231 }
3232}
3233
3234impl<D: Dtype, const RANK: usize> Layer<SymTensor>
3235 for ReplicationPad2d<D, SymTensor, SymTensor, RANK>
3236{
3237 type Output = SymTensor;
3238 fn call(&self, input: SymTensor) -> SymTensor {
3239 input.record(Op::ReplicationPad2d {
3240 pad_l: self.pad_l,
3241 pad_r: self.pad_r,
3242 pad_t: self.pad_t,
3243 pad_b: self.pad_b,
3244 })
3245 }
3246}
3247
3248impl<D: Dtype, const RANK: usize> Layer<SymTensor>
3249 for ReplicationPad3d<D, SymTensor, SymTensor, RANK>
3250{
3251 type Output = SymTensor;
3252 fn call(&self, input: SymTensor) -> SymTensor {
3253 input.record(Op::ReplicationPad3d {
3254 pad_d1: self.pad_d1,
3255 pad_d2: self.pad_d2,
3256 pad_h1: self.pad_h1,
3257 pad_h2: self.pad_h2,
3258 pad_w1: self.pad_w1,
3259 pad_w2: self.pad_w2,
3260 })
3261 }
3262}
3263
3264impl<D: Dtype, const RANK: usize> Layer<SymTensor>
3265 for CircularPad1d<D, SymTensor, SymTensor, RANK>
3266{
3267 type Output = SymTensor;
3268 fn call(&self, input: SymTensor) -> SymTensor {
3269 input.record(Op::CircularPad1d {
3270 pad_left: self.pad_left,
3271 pad_right: self.pad_right,
3272 })
3273 }
3274}
3275
3276impl<D: Dtype, const RANK: usize> Layer<SymTensor>
3277 for CircularPad2d<D, SymTensor, SymTensor, RANK>
3278{
3279 type Output = SymTensor;
3280 fn call(&self, input: SymTensor) -> SymTensor {
3281 input.record(Op::CircularPad2d {
3282 pad_l: self.pad_l,
3283 pad_r: self.pad_r,
3284 pad_t: self.pad_t,
3285 pad_b: self.pad_b,
3286 })
3287 }
3288}
3289
3290impl<D: Dtype, const RANK: usize> Layer<SymTensor>
3291 for CircularPad3d<D, SymTensor, SymTensor, RANK>
3292{
3293 type Output = SymTensor;
3294 fn call(&self, input: SymTensor) -> SymTensor {
3295 input.record(Op::CircularPad3d {
3296 pad_d1: self.pad_d1,
3297 pad_d2: self.pad_d2,
3298 pad_h1: self.pad_h1,
3299 pad_h2: self.pad_h2,
3300 pad_w1: self.pad_w1,
3301 pad_w2: self.pad_w2,
3302 })
3303 }
3304}
3305
3306impl<D: Dtype, const RANK: usize> Layer<SymTensor> for Relu<D, SymTensor, RANK> {
3309 type Output = SymTensor;
3310 fn call(&self, input: SymTensor) -> SymTensor {
3311 input.record(Op::Relu)
3312 }
3313}
3314
3315impl<D: Float, const RANK: usize> Layer<SymTensor> for Elu<D, SymTensor, RANK> {
3316 type Output = SymTensor;
3317 fn call(&self, input: SymTensor) -> SymTensor {
3318 input.record(Op::Elu { alpha: self.alpha })
3319 }
3320}
3321
3322impl<D: Float, const RANK: usize> Layer<SymTensor> for Selu<D, SymTensor, RANK> {
3323 type Output = SymTensor;
3324 fn call(&self, input: SymTensor) -> SymTensor {
3325 input.record(Op::Selu)
3326 }
3327}
3328
3329impl<D: Float, const RANK: usize> Layer<SymTensor> for Celu<D, SymTensor, RANK> {
3330 type Output = SymTensor;
3331 fn call(&self, input: SymTensor) -> SymTensor {
3332 input.record(Op::Celu { alpha: self.alpha })
3333 }
3334}
3335
3336impl<D: Float, const RANK: usize> Layer<SymTensor> for Gelu<D, SymTensor, RANK> {
3337 type Output = SymTensor;
3338 fn call(&self, input: SymTensor) -> SymTensor {
3339 input.record(Op::Gelu)
3340 }
3341}
3342
3343impl<D: Float, const RANK: usize> Layer<SymTensor> for Mish<D, SymTensor, RANK> {
3344 type Output = SymTensor;
3345 fn call(&self, input: SymTensor) -> SymTensor {
3346 input.record(Op::Mish)
3347 }
3348}
3349
3350impl<D: Float, const RANK: usize> Layer<SymTensor> for Hardtanh<D, SymTensor, RANK> {
3351 type Output = SymTensor;
3352 fn call(&self, input: SymTensor) -> SymTensor {
3353 input.record(Op::Hardtanh {
3354 min_val: self.min_val,
3355 max_val: self.max_val,
3356 })
3357 }
3358}
3359
3360impl<D: Float, const RANK: usize> Layer<SymTensor> for Relu6<D, SymTensor, RANK> {
3361 type Output = SymTensor;
3362 fn call(&self, input: SymTensor) -> SymTensor {
3363 input.record(Op::Relu6)
3364 }
3365}
3366
3367impl<D: Float, const RANK: usize> Layer<SymTensor> for Hardsigmoid<D, SymTensor, RANK> {
3368 type Output = SymTensor;
3369 fn call(&self, input: SymTensor) -> SymTensor {
3370 input.record(Op::Hardsigmoid)
3371 }
3372}
3373
3374impl<D: Float, const RANK: usize> Layer<SymTensor> for Hardswish<D, SymTensor, RANK> {
3375 type Output = SymTensor;
3376 fn call(&self, input: SymTensor) -> SymTensor {
3377 input.record(Op::Hardswish)
3378 }
3379}
3380
3381impl<D: Float, const RANK: usize> Layer<SymTensor> for Hardshrink<D, SymTensor, RANK> {
3382 type Output = SymTensor;
3383 fn call(&self, input: SymTensor) -> SymTensor {
3384 input.record(Op::Hardshrink {
3385 lambda: self.lambda,
3386 })
3387 }
3388}
3389
3390impl<D: Float, const RANK: usize> Layer<SymTensor> for LeakyRelu<D, SymTensor, RANK> {
3391 type Output = SymTensor;
3392 fn call(&self, input: SymTensor) -> SymTensor {
3393 input.record(Op::LeakyRelu {
3394 negative_slope: self.negative_slope,
3395 })
3396 }
3397}
3398
3399impl<D: Float, const RANK: usize> Layer<SymTensor> for Threshold<D, SymTensor, RANK> {
3400 type Output = SymTensor;
3401 fn call(&self, input: SymTensor) -> SymTensor {
3402 input.record(Op::Threshold {
3403 threshold: self.threshold,
3404 value: self.value,
3405 })
3406 }
3407}
3408
3409impl<D: Float, const RANK: usize> Layer<SymTensor> for Softsign<D, SymTensor, RANK> {
3410 type Output = SymTensor;
3411 fn call(&self, input: SymTensor) -> SymTensor {
3412 input.record(Op::Softsign)
3413 }
3414}
3415
3416impl<D: Float, const RANK: usize> Layer<SymTensor> for Softshrink<D, SymTensor, RANK> {
3417 type Output = SymTensor;
3418 fn call(&self, input: SymTensor) -> SymTensor {
3419 input.record(Op::Softshrink {
3420 lambda: self.lambda,
3421 })
3422 }
3423}
3424
3425impl<D: Float, const RANK: usize> Layer<SymTensor> for Softplus<D, SymTensor, RANK> {
3426 type Output = SymTensor;
3427 fn call(&self, input: SymTensor) -> SymTensor {
3428 input.record(Op::Softplus {
3429 beta: self.beta,
3430 threshold: self.threshold,
3431 })
3432 }
3433}
3434
3435impl<D: Float, const RANK: usize> Layer<SymTensor> for Sigmoid<D, SymTensor, RANK> {
3436 type Output = SymTensor;
3437 fn call(&self, input: SymTensor) -> SymTensor {
3438 input.record(Op::Sigmoid)
3439 }
3440}
3441
3442impl<D: Float, const RANK: usize> Layer<SymTensor> for Silu<D, SymTensor, RANK> {
3443 type Output = SymTensor;
3444 fn call(&self, input: SymTensor) -> SymTensor {
3445 input.record(Op::Silu)
3446 }
3447}
3448
3449impl<D: Float, const RANK: usize> Layer<SymTensor> for Logsigmoid<D, SymTensor, RANK> {
3450 type Output = SymTensor;
3451 fn call(&self, input: SymTensor) -> SymTensor {
3452 input.record(Op::Logsigmoid)
3453 }
3454}
3455
3456impl<D: Float, const RANK: usize> Layer<SymTensor> for Tanh<D, SymTensor, RANK> {
3457 type Output = SymTensor;
3458 fn call(&self, input: SymTensor) -> SymTensor {
3459 input.record(Op::Tanh)
3460 }
3461}
3462
3463impl<D: Float, const RANK: usize> Layer<SymTensor> for Tanhshrink<D, SymTensor, RANK> {
3464 type Output = SymTensor;
3465 fn call(&self, input: SymTensor) -> SymTensor {
3466 input.record(Op::Tanhshrink)
3467 }
3468}
3469
3470impl<D: Float, const RANK: usize> Layer<SymTensor> for Softmax<D, SymTensor, RANK> {
3471 type Output = SymTensor;
3472 fn call(&self, input: SymTensor) -> SymTensor {
3473 input.record(Op::Softmax { dim: self.dim })
3474 }
3475}
3476
3477#[cfg(test)]
3482mod tests {
3483 use super::*;
3484 use crate::{
3485 nn::{
3486 activation::{relu::Relu, softmax::Softmax},
3487 conv2d::Conv2d,
3488 linear::Linear,
3489 },
3490 sequential,
3491 };
3492
3493 #[test]
3494 fn test_sequential_graph_extraction() {
3495 let (input, graph) = SymTensor::input(DtypeRepr::F32, vec![None, Some(784)]);
3496
3497 let model = sequential![
3498 Linear::<f32, SymTensor, SymTensor, 2>::new(784, 128, true),
3499 Relu::<f32, SymTensor, 2>::new(),
3500 Linear::<f32, SymTensor, SymTensor, 2>::new(128, 10, true),
3501 Softmax::<f32, SymTensor, 2>::new(1)
3502 ];
3503
3504 let _out = Layer::call(&model, input);
3505
3506 let g = graph.borrow();
3507 assert_eq!(g.nodes.len(), 5);
3508 assert!(matches!(g.nodes[0].op, Op::Input));
3509 assert_eq!(g.nodes[0].shape, vec![None, Some(784)]);
3510
3511 assert!(matches!(
3512 g.nodes[1].op,
3513 Op::Linear {
3514 in_features: 784,
3515 out_features: 128,
3516 ..
3517 }
3518 ));
3519 assert_eq!(g.nodes[1].shape, vec![None, Some(128)]);
3520
3521 assert!(matches!(g.nodes[2].op, Op::Relu));
3522 assert_eq!(g.nodes[2].shape, vec![None, Some(128)]);
3523
3524 assert!(matches!(
3525 g.nodes[3].op,
3526 Op::Linear {
3527 in_features: 128,
3528 out_features: 10,
3529 ..
3530 }
3531 ));
3532 assert_eq!(g.nodes[3].shape, vec![None, Some(10)]);
3533
3534 assert!(matches!(g.nodes[4].op, Op::Softmax { dim: 1 }));
3535 assert_eq!(g.nodes[4].shape, vec![None, Some(10)]);
3536 }
3537
3538 #[test]
3539 fn test_topological_sort_linear_chain() {
3540 let (input, graph) = SymTensor::input(DtypeRepr::F32, vec![None, Some(784)]);
3541
3542 let model = sequential![
3543 Linear::<f32, SymTensor, SymTensor, 2>::new(784, 128, true),
3544 Relu::<f32, SymTensor, 2>::new(),
3545 Linear::<f32, SymTensor, SymTensor, 2>::new(128, 10, true),
3546 Softmax::<f32, SymTensor, 2>::new(1)
3547 ];
3548
3549 let _out = Layer::call(&model, input);
3550
3551 let g = graph.borrow();
3552 let order = g.topological_sort();
3553 assert_eq!(order.len(), g.nodes.len());
3554 for (pos, &id) in order.iter().enumerate() {
3555 for &input_id in &g.nodes[id].inputs {
3556 let input_pos = order.iter().position(|&x| x == input_id).unwrap();
3557 assert!(
3558 input_pos < pos,
3559 "producer {input_id} must come before consumer {id}"
3560 );
3561 }
3562 }
3563 }
3564
3565 #[test]
3566 fn test_residual_graph_extraction() {
3567 let (input, graph) = SymTensor::input(DtypeRepr::F32, vec![None, Some(64)]);
3568
3569 let main = Linear::<f32, SymTensor, SymTensor, 2>::new(64, 64, true).call(input.clone());
3570 let main = Relu::<f32, SymTensor, 2>::new().call(main);
3571 let skip = Linear::<f32, SymTensor, SymTensor, 2>::new(64, 64, false).call(input);
3572
3573 assert!(Rc::ptr_eq(&main.graph, &skip.graph));
3574
3575 let g = graph.borrow();
3576 assert_eq!(g.nodes.len(), 4);
3577 assert_eq!(g.nodes[1].inputs, vec![0]);
3578 assert_eq!(g.nodes[3].inputs, vec![0]);
3579 }
3580
3581 #[test]
3582 fn test_conv2d_graph_extraction() {
3583 let (input, graph) =
3584 SymTensor::input(DtypeRepr::F32, vec![None, Some(3), Some(32), Some(32)]);
3585
3586 let conv = Conv2d::<f32, SymTensor, SymTensor, 4>::new(3, 64, (3, 3), (1, 1), (1, 1), true);
3587 let _out = Layer::call(&conv, input);
3588
3589 let g = graph.borrow();
3590 assert_eq!(g.nodes.len(), 2);
3591 assert!(matches!(
3592 g.nodes[1].op,
3593 Op::Conv2d {
3594 in_channels: 3,
3595 out_channels: 64,
3596 kernel_h: 3,
3597 kernel_w: 3,
3598 stride_h: 1,
3599 stride_w: 1,
3600 padding_h: 1,
3601 padding_w: 1,
3602 has_bias: true,
3603 ..
3604 }
3605 ));
3606 assert_eq!(g.nodes[1].shape, vec![None, Some(64), Some(32), Some(32)]);
3607 }
3608
3609 #[test]
3610 fn test_lenet5_shapes() {
3611 let (input, graph) =
3612 SymTensor::input(DtypeRepr::F32, vec![None, Some(1), Some(28), Some(28)]);
3613
3614 use crate::{
3615 nn::{flatten::Flatten, pool::AvgPool2d},
3616 sequential,
3617 };
3618
3619 let model = sequential![
3620 Conv2d::<f32, SymTensor, SymTensor, 4>::new(1, 6, (5, 5), (1, 1), (2, 2), true),
3621 Relu::<f32, SymTensor, 4>::new(),
3622 AvgPool2d::<f32, SymTensor, SymTensor, 4>::new((2, 2), (2, 2)),
3623 Conv2d::<f32, SymTensor, SymTensor, 4>::new(6, 16, (5, 5), (1, 1), (0, 0), true),
3624 Relu::<f32, SymTensor, 4>::new(),
3625 AvgPool2d::<f32, SymTensor, SymTensor, 4>::new((2, 2), (2, 2)),
3626 Flatten::<f32, SymTensor, SymTensor>::new(),
3627 Linear::<f32, SymTensor, SymTensor, 2>::new(400, 120, true),
3628 Relu::<f32, SymTensor, 2>::new(),
3629 Linear::<f32, SymTensor, SymTensor, 2>::new(120, 84, true),
3630 Relu::<f32, SymTensor, 2>::new(),
3631 Linear::<f32, SymTensor, SymTensor, 2>::new(84, 10, true),
3632 Softmax::<f32, SymTensor, 2>::new(1)
3633 ];
3634
3635 let _out = Layer::call(&model, input);
3636
3637 let g = graph.borrow();
3638 assert_eq!(g.nodes.len(), 14);
3639 assert_eq!(g.nodes[0].shape, vec![None, Some(1), Some(28), Some(28)]);
3640 assert_eq!(g.nodes[1].shape, vec![None, Some(6), Some(28), Some(28)]);
3641 assert_eq!(g.nodes[2].shape, vec![None, Some(6), Some(28), Some(28)]);
3642 assert_eq!(g.nodes[3].shape, vec![None, Some(6), Some(14), Some(14)]);
3643 assert_eq!(g.nodes[4].shape, vec![None, Some(16), Some(10), Some(10)]);
3644 assert_eq!(g.nodes[5].shape, vec![None, Some(16), Some(10), Some(10)]);
3645 assert_eq!(g.nodes[6].shape, vec![None, Some(16), Some(5), Some(5)]);
3646 assert_eq!(g.nodes[7].shape, vec![None, Some(400)]);
3647 assert_eq!(g.nodes[8].shape, vec![None, Some(120)]);
3648 assert_eq!(g.nodes[9].shape, vec![None, Some(120)]);
3649 assert_eq!(g.nodes[10].shape, vec![None, Some(84)]);
3650 assert_eq!(g.nodes[11].shape, vec![None, Some(84)]);
3651 assert_eq!(g.nodes[12].shape, vec![None, Some(10)]);
3652 assert_eq!(g.nodes[13].shape, vec![None, Some(10)]);
3653 }
3654
3655 #[test]
3660 fn test_is_fusable_elementwise_allowlist() {
3661 assert!(is_fusable_elementwise(&Op::Relu));
3662 assert!(is_fusable_elementwise(&Op::Sigmoid));
3663 assert!(is_fusable_elementwise(&Op::Silu));
3664 assert!(is_fusable_elementwise(&Op::Tanh));
3665 }
3666
3667 #[test]
3668 fn test_is_fusable_elementwise_excludes_non_allowlisted_ops() {
3669 assert!(!is_fusable_elementwise(&Op::Input));
3670 assert!(!is_fusable_elementwise(&Op::Gelu));
3671 assert!(!is_fusable_elementwise(&Op::Softmax { dim: 1 }));
3672 assert!(!is_fusable_elementwise(&Op::Conv2d {
3673 in_channels: 3,
3674 out_channels: 8,
3675 kernel_h: 3,
3676 kernel_w: 3,
3677 stride_h: 1,
3678 stride_w: 1,
3679 padding_h: 1,
3680 padding_w: 1,
3681 groups: 1,
3682 has_bias: false,
3683 }));
3684 assert!(!is_fusable_elementwise(&Op::BatchNorm2d {
3685 num_features: 8,
3686 eps: 1e-5,
3687 momentum: 0.1,
3688 affine: true,
3689 track_running_stats: true,
3690 }));
3691 assert!(!is_fusable_elementwise(&Op::Linear {
3692 in_features: 4,
3693 out_features: 4,
3694 has_bias: false,
3695 }));
3696 }
3697
3698 #[test]
3699 fn test_infer_output_shape_fused_folds_through_members() {
3700 let shape = vec![None, Some(16)];
3701 let out = infer_output_shape(
3702 &Op::Fused {
3703 members: alloc::vec![Op::Relu, Op::Sigmoid, Op::Silu],
3704 },
3705 &[&shape],
3706 );
3707 assert_eq!(out, shape);
3709 }
3710
3711 fn shape_1d(n: usize) -> Shape {
3712 vec![None, Some(n)]
3713 }
3714
3715 #[test]
3716 fn test_fuse_elementwise_chains_fuses_linear_chain() {
3717 let mut g = Graph::new();
3718 let input = g.add_node(Op::Input, vec![], DtypeRepr::F32, shape_1d(16));
3719 let relu = g.add_node(Op::Relu, vec![input], DtypeRepr::F32, shape_1d(16));
3720 let sigmoid = g.add_node(Op::Sigmoid, vec![relu], DtypeRepr::F32, shape_1d(16));
3721 let _silu = g.add_node(Op::Silu, vec![sigmoid], DtypeRepr::F32, shape_1d(16));
3722
3723 let fused = g.fuse_elementwise_chains();
3724
3725 assert_eq!(fused.nodes.len(), 2);
3727 assert!(matches!(fused.nodes[0].op, Op::Input));
3728 match &fused.nodes[1].op {
3729 Op::Fused { members } => {
3730 assert_eq!(members.len(), 3);
3731 assert!(matches!(members[0], Op::Relu));
3732 assert!(matches!(members[1], Op::Sigmoid));
3733 assert!(matches!(members[2], Op::Silu));
3734 }
3735 other => panic!("expected Op::Fused, got {other:?}"),
3736 }
3737 assert_eq!(fused.nodes[1].inputs, vec![0]);
3739 }
3740
3741 #[test]
3742 fn test_fuse_elementwise_chains_no_fusion_for_single_op() {
3743 let mut g = Graph::new();
3744 let input = g.add_node(Op::Input, vec![], DtypeRepr::F32, shape_1d(16));
3745 g.add_node(Op::Relu, vec![input], DtypeRepr::F32, shape_1d(16));
3746
3747 let fused = g.fuse_elementwise_chains();
3748
3749 assert_eq!(fused.nodes.len(), 2);
3750 assert!(matches!(fused.nodes[1].op, Op::Relu));
3751 }
3752
3753 #[test]
3754 fn test_fuse_elementwise_chains_stops_at_non_fusable_op() {
3755 let mut g = Graph::new();
3756 let input = g.add_node(Op::Input, vec![], DtypeRepr::F32, shape_1d(4));
3757 let relu = g.add_node(Op::Relu, vec![input], DtypeRepr::F32, shape_1d(4));
3758 let conv = g.add_node(
3759 Op::Conv2d {
3760 in_channels: 4,
3761 out_channels: 4,
3762 kernel_h: 3,
3763 kernel_w: 3,
3764 stride_h: 1,
3765 stride_w: 1,
3766 padding_h: 1,
3767 padding_w: 1,
3768 groups: 1,
3769 has_bias: false,
3770 },
3771 vec![relu],
3772 DtypeRepr::F32,
3773 shape_1d(4),
3774 );
3775 g.add_node(Op::Sigmoid, vec![conv], DtypeRepr::F32, shape_1d(4));
3776
3777 let fused = g.fuse_elementwise_chains();
3778
3779 assert_eq!(fused.nodes.len(), 4);
3782 assert!(matches!(fused.nodes[1].op, Op::Relu));
3783 assert!(matches!(fused.nodes[2].op, Op::Conv2d { .. }));
3784 assert!(matches!(fused.nodes[3].op, Op::Sigmoid));
3785 }
3786
3787 #[test]
3788 fn test_fuse_elementwise_chains_no_fusion_when_multiple_consumers() {
3789 let mut g = Graph::new();
3790 let input = g.add_node(Op::Input, vec![], DtypeRepr::F32, shape_1d(4));
3791 let relu = g.add_node(Op::Relu, vec![input], DtypeRepr::F32, shape_1d(4));
3792 g.add_node(Op::Sigmoid, vec![relu], DtypeRepr::F32, shape_1d(4));
3795 g.add_node(Op::Silu, vec![relu], DtypeRepr::F32, shape_1d(4));
3796
3797 let fused = g.fuse_elementwise_chains();
3798
3799 assert_eq!(fused.nodes.len(), 4);
3800 assert!(matches!(fused.nodes[1].op, Op::Relu));
3801 assert!(matches!(fused.nodes[2].op, Op::Sigmoid));
3802 assert!(matches!(fused.nodes[3].op, Op::Silu));
3803 }
3804
3805 #[test]
3806 fn test_fuse_elementwise_chains_rewires_downstream_consumer() {
3807 let mut g = Graph::new();
3808 let input = g.add_node(Op::Input, vec![], DtypeRepr::F32, shape_1d(4));
3809 let relu = g.add_node(Op::Relu, vec![input], DtypeRepr::F32, shape_1d(4));
3810 let sigmoid = g.add_node(Op::Sigmoid, vec![relu], DtypeRepr::F32, shape_1d(4));
3811 g.add_node(
3814 Op::Conv2d {
3815 in_channels: 4,
3816 out_channels: 4,
3817 kernel_h: 1,
3818 kernel_w: 1,
3819 stride_h: 1,
3820 stride_w: 1,
3821 padding_h: 0,
3822 padding_w: 0,
3823 groups: 1,
3824 has_bias: false,
3825 },
3826 vec![sigmoid],
3827 DtypeRepr::F32,
3828 shape_1d(4),
3829 );
3830
3831 let fused = g.fuse_elementwise_chains();
3832
3833 assert_eq!(fused.nodes.len(), 3);
3834 let fused_idx = 1;
3835 assert!(matches!(fused.nodes[fused_idx].op, Op::Fused { .. }));
3836 assert_eq!(fused.nodes[2].inputs, vec![fused_idx]);
3837 }
3838
3839 #[test]
3840 fn test_fuse_elementwise_chains_grows_past_two_via_fixed_point() {
3841 let mut g = Graph::new();
3845 let input = g.add_node(Op::Input, vec![], DtypeRepr::F32, shape_1d(4));
3846 let relu = g.add_node(Op::Relu, vec![input], DtypeRepr::F32, shape_1d(4));
3847 let sigmoid = g.add_node(Op::Sigmoid, vec![relu], DtypeRepr::F32, shape_1d(4));
3848 let silu = g.add_node(Op::Silu, vec![sigmoid], DtypeRepr::F32, shape_1d(4));
3849 g.add_node(Op::Tanh, vec![silu], DtypeRepr::F32, shape_1d(4));
3850
3851 let fused = g.fuse_elementwise_chains();
3852
3853 assert_eq!(fused.nodes.len(), 2);
3854 match &fused.nodes[1].op {
3855 Op::Fused { members } => {
3856 assert_eq!(members.len(), 4);
3857 assert!(matches!(members[0], Op::Relu));
3858 assert!(matches!(members[1], Op::Sigmoid));
3859 assert!(matches!(members[2], Op::Silu));
3860 assert!(matches!(members[3], Op::Tanh));
3861 }
3862 other => panic!("expected Op::Fused, got {other:?}"),
3863 }
3864 }
3865
3866 #[test]
3867 fn test_optimise_does_not_produce_fused_nodes() {
3868 let mut g = Graph::new();
3872 let input = g.add_node(Op::Input, vec![], DtypeRepr::F32, shape_1d(4));
3873 let relu = g.add_node(Op::Relu, vec![input], DtypeRepr::F32, shape_1d(4));
3874 g.add_node(Op::Sigmoid, vec![relu], DtypeRepr::F32, shape_1d(4));
3875
3876 let optimised = g.optimise();
3877
3878 assert_eq!(optimised.nodes.len(), 3);
3879 for node in &optimised.nodes {
3880 assert!(!matches!(node.op, Op::Fused { .. }));
3881 }
3882 }
3883}