|
4 | 4 | # LICENSE file in the root directory of this source tree. |
5 | 5 |
|
6 | 6 | import torch |
| 7 | + |
7 | 8 | from executorch.backends.nxp.backend.data_format import NXP_NODE_FORMAT |
8 | 9 |
|
9 | 10 | from executorch.backends.nxp.backend.ir.converter.conversion.translator import ( |
10 | 11 | create_channels_last_to_channels_first_permutation, |
11 | 12 | ) |
12 | 13 | from executorch.backends.nxp.backend.ir.converter.node_converter import ( |
13 | 14 | CustomDelegationOptions, |
| 15 | + is_not_qdq_node, |
14 | 16 | NodeConverter, |
15 | 17 | ) |
16 | 18 | from executorch.backends.nxp.backend.ir.converter.node_converters.shared.reduce_utils import ( |
|
21 | 23 | ) |
22 | 24 | from executorch.backends.nxp.backend.neutron_target_spec import NeutronTargetSpec |
23 | 25 | from torch.fx import Node |
| 26 | +from torch.fx.passes.infra.partitioner import Partition |
24 | 27 | from torch.nn import Parameter |
25 | 28 |
|
26 | 29 |
|
27 | 30 | class MeanDimConverter(NodeConverter): |
| 31 | + |
| 32 | + @classmethod |
| 33 | + def supports_partitioning_result( |
| 34 | + cls, |
| 35 | + node: Node, |
| 36 | + partition_list: list[Partition], |
| 37 | + custom_delegation_options: CustomDelegationOptions, |
| 38 | + neutron_target_spec: NeutronTargetSpec, |
| 39 | + parameters_mapping: dict[str, Parameter], |
| 40 | + ) -> bool: |
| 41 | + if custom_delegation_options.use_new_flow_neutron_c: |
| 42 | + dim, keepdim = MeanDimConverter._get_attrs(node) |
| 43 | + input_shape = node.args[0].meta["val"].shape |
| 44 | + |
| 45 | + is_alone_in_partition = cls.is_node_alone_in_partition( |
| 46 | + node, partition_list, filter_fn=is_not_qdq_node |
| 47 | + ) |
| 48 | + |
| 49 | + if ( |
| 50 | + is_alone_in_partition |
| 51 | + and keepdim |
| 52 | + and all(input_shape[d] == 1 for d in dim) |
| 53 | + ): |
| 54 | + # The operator is a no-op, so the Neutron Converter will skip it. If it's the only node in the |
| 55 | + # partition, the graph would end up empty. |
| 56 | + return False |
| 57 | + |
| 58 | + return True |
| 59 | + |
28 | 60 | @staticmethod |
29 | 61 | def _is_supported_on_target( |
30 | 62 | node: Node, |
31 | 63 | neutron_target_spec: NeutronTargetSpec, |
32 | 64 | parameters_mapping: dict[str, Parameter], |
33 | 65 | custom_delegation_options: CustomDelegationOptions, |
34 | 66 | ) -> bool: |
35 | | - keepdim = node.args[2] if len(node.args) >= 3 else False |
36 | | - rank = len(node.args[0].meta["val"].shape) |
37 | | - dim = [MeanDimConverter._to_pos_dim(d, rank) for d in node.args[1]] |
| 67 | + if custom_delegation_options.use_new_flow_neutron_c: |
| 68 | + # Requirements specified by the new Neutron flow documentation. |
| 69 | + |
| 70 | + if not NodeConverter.uses_quantization_type_for_io( |
| 71 | + node, |
| 72 | + supported_types=[torch.int8, torch.uint8], |
| 73 | + input_indices=[0], |
| 74 | + output_indices=[0], |
| 75 | + ): |
| 76 | + return False |
38 | 77 |
|
39 | | - if rank != 4 or not keepdim: |
40 | | - # neutron-converter/src/OperatorC/GlobalAvgPoolPlugin.cpp#74-77 |
41 | | - return False |
| 78 | + return True |
42 | 79 |
|
43 | | - # The `mean.dim` gets converted to AveragePool by the NeutronConverter, so the channels must be a |
44 | | - # multiple of `num_macs`. |
45 | | - # neutron-converter/src/OperatorC/GlobalAvgPoolPlugin.cpp#59-85 |
46 | | - num_macs = neutron_target_spec.get_num_macs() |
47 | | - channels_dim = 1 if node.meta[NXP_NODE_FORMAT].is_channels_first() else -1 |
48 | | - if (node.meta["val"].shape[channels_dim] % num_macs) != 0: |
49 | | - return False |
| 80 | + else: |
| 81 | + # Requirements of the old Neutron flow. |
| 82 | + rank = len(node.args[0].meta["val"].shape) |
| 83 | + dim, keepdim = MeanDimConverter._get_attrs(node) |
| 84 | + dim = [MeanDimConverter._to_pos_dim(d, rank) for d in dim] |
50 | 85 |
|
51 | | - # Neutron only supports reduction over the spatial dimensions H, W. |
52 | | - if node.meta[NXP_NODE_FORMAT].is_channels_first(): |
53 | | - # The input is NCHW. H and W are at indices 2 and 3. |
54 | | - if dim not in [[2, 3], [3, 2]]: |
| 86 | + if rank != 4 or not keepdim: |
| 87 | + # neutron-converter/src/OperatorC/GlobalAvgPoolPlugin.cpp#74-77 |
55 | 88 | return False |
56 | | - else: |
57 | | - # The input is formatless. It can be considered as NHWC, as this is the way Neutron will look at |
58 | | - # the dimensions. So H and W are the middle dimensions. |
59 | | - if dim not in [[1, 2], [2, 1]]: |
| 89 | + |
| 90 | + # The `mean.dim` gets converted to AveragePool by the NeutronConverter, so the channels must be a |
| 91 | + # multiple of `num_macs`. |
| 92 | + # neutron-converter/src/OperatorC/GlobalAvgPoolPlugin.cpp#59-85 |
| 93 | + num_macs = neutron_target_spec.get_num_macs() |
| 94 | + channels_dim = 1 if node.meta[NXP_NODE_FORMAT].is_channels_first() else -1 |
| 95 | + if (node.meta["val"].shape[channels_dim] % num_macs) != 0: |
60 | 96 | return False |
61 | 97 |
|
62 | | - return True |
| 98 | + # Neutron only supports reduction over the spatial dimensions H, W. |
| 99 | + if node.meta[NXP_NODE_FORMAT].is_channels_first(): |
| 100 | + # The input is NCHW. H and W are at indices 2 and 3. |
| 101 | + if dim not in [[2, 3], [3, 2]]: |
| 102 | + return False |
| 103 | + else: |
| 104 | + # The input is formatless. It can be considered as NHWC, as this is the way Neutron will look at |
| 105 | + # the dimensions. So H and W are the middle dimensions. |
| 106 | + if dim not in [[1, 2], [2, 1]]: |
| 107 | + return False |
| 108 | + |
| 109 | + return True |
63 | 110 |
|
64 | 111 | @staticmethod |
65 | 112 | def _is_supported_in_IR( |
@@ -91,15 +138,29 @@ def _normalize_and_to_channel_last_dim(dim: list[int], rank: int) -> list[int]: |
91 | 138 | perm = create_channels_last_to_channels_first_permutation(rank, True) |
92 | 139 | dim = [perm[d] for d in dim] |
93 | 140 |
|
| 141 | + # noinspection PyTypeChecker |
94 | 142 | return dim |
95 | 143 |
|
96 | | - # Mean Dim Node format: (Tensor self, int[1]? dim, bool keepdim=False, *, ScalarType? dtype=None) |
| 144 | + @staticmethod |
| 145 | + def _get_attrs(node: Node) -> tuple[list[int], bool]: |
| 146 | + dim = node.args[1] |
| 147 | + keepdim = node.args[2] if len(node.args) >= 3 else False |
| 148 | + return dim, keepdim |
| 149 | + |
97 | 150 | def convert(self, node: Node): |
98 | | - """Convert 'mean.dim' operator to TFLite 'Mean'.""" |
| 151 | + """Convert the 'mean.dim' operator to NeutronIR 'Mean'. |
| 152 | + The ExecuTorch schema is: |
| 153 | + mean.dim( |
| 154 | + Tensor self, |
| 155 | + int[1]? dim, |
| 156 | + bool keepdim=False, |
| 157 | + *, |
| 158 | + ScalarType? dtype=None |
| 159 | + ) -> Tensor |
| 160 | + """ |
99 | 161 | self.assert_convertible(node) |
100 | 162 |
|
101 | | - dim = node.args[1] |
102 | | - keepdim = node.args[2] if len(node.args) >= 3 else False |
| 163 | + dim, keepdim = self._get_attrs(node) |
103 | 164 |
|
104 | 165 | t_op = self._create_tflite_op_with_io_tensors(node) |
105 | 166 | t_op.builtin_options = mean_options.Mean(keepdim) |
|
0 commit comments