55
66import torch
77
8+ from executorch .backends .nxp .backend .data_format import DataFormat
9+ from executorch .backends .nxp .backend .ir .converter .conversion import translator
10+ from executorch .backends .nxp .backend .ir .converter .conversion .common import OpsList
811from executorch .backends .nxp .backend .ir .converter .conversion .translator import (
912 create_channels_last_to_channels_first_permutation ,
1013)
@@ -89,10 +92,15 @@ def _is_supported_in_IR(
8992 def _to_pos_dim (d : int , rank : int ):
9093 return d + rank if d < 0 else d
9194
95+ @staticmethod
96+ def _normalize_dim (dim : list [int ], rank : int ) -> list [int ]:
97+ # convert negative index to positive
98+ return [MeanDimConverter ._to_pos_dim (d , rank ) for d in dim ]
99+
92100 @staticmethod
93101 def _normalize_and_to_channel_last_dim (dim : list [int ], rank : int ) -> list [int ]:
94102 # convert negative index to positive
95- dim = [ MeanDimConverter ._to_pos_dim ( d , rank ) for d in dim ]
103+ dim = MeanDimConverter ._normalize_dim ( dim , rank )
96104
97105 perm = create_channels_last_to_channels_first_permutation (rank , True )
98106 dim = [perm [d ] for d in dim ]
@@ -106,6 +114,114 @@ def _get_attrs(node: Node) -> tuple[list[int], bool]:
106114 keepdim = node .args [2 ] if len (node .args ) >= 3 else False
107115 return dim , keepdim
108116
117+ def _get_dim_and_handle_io_formats (
118+ self , ops : OpsList , dim : list [int ], keep_dim : bool
119+ ):
120+ t_op = ops .middle_op
121+ x = t_op .tmp_inputs [0 ]
122+ y = t_op .tmp_outputs [0 ]
123+
124+ channels_last_input = x .tensor_format .is_channels_last ()
125+ channels_last_output = y .tensor_format .is_channels_last ()
126+ formatless_input = not channels_last_input
127+ formatless_output = not channels_last_output
128+
129+ dim = self ._normalize_dim (dim , x .rank )
130+
131+ if keep_dim :
132+ # The rank is preserved and the io formats should always be equal.
133+ assert (
134+ x .tensor_format == y .tensor_format
135+ ), "NXP backend: There is a bug in `mean.dim` format inference."
136+
137+ # Just adjust the dim to match the input format.
138+ if channels_last_input :
139+ dim = self ._normalize_and_to_channel_last_dim (dim , x .rank )
140+
141+ else :
142+ # `keep_dim = False`, so the output rank != input rank, and the operator changes the tensor format.
143+
144+ if channels_last_input and formatless_output :
145+ if 1 in dim :
146+ # If we are reducing over the channels, the channels dimension gets removed and the output ends up
147+ # exactly equal in channels last and channels first, regardless of which other dimensions are
148+ # removed. Therefore, we can just adjust the `dim` and we don't need to insert any `Transpose` ops.
149+ dim = self ._normalize_and_to_channel_last_dim (dim , x .rank )
150+ elif all (spatial_dim in dim for spatial_dim in range (2 , x .rank )):
151+ # All spatial dims are reduced, leaving only batch and channels (both optionally). So the result is
152+ # equal in channels first and channels last as long as we adjust the `dim` to match a channels last
153+ # input (similarly to the case above).
154+ dim = self ._normalize_and_to_channel_last_dim (dim , x .rank )
155+ else :
156+ # If the channels dimension is preserved, we must transpose the input to channels first (to match
157+ # the edge model) and we must keep the `dim` unchanged (referencing channels first dimensions).
158+ # Otherwise, the output would not match the input.
159+ to_channels_first_perm = (
160+ translator .create_channels_last_to_channels_first_permutation (
161+ x .rank
162+ )
163+ )
164+ ops .add_pre (
165+ self .builder .create_transpose_operator_before (
166+ t_op , 0 , to_channels_first_perm
167+ )
168+ )
169+ t_op .tmp_inputs [0 ].tensor_format = DataFormat .CHANNELS_FIRST
170+
171+ elif formatless_input and channels_last_output :
172+ # We need apply the `mean` with the original `dim`, which will produce a channels first output. Then,
173+ # we need to append a `Transpose` operator to make the output channels last.
174+ to_channels_last_perm = (
175+ translator .create_channels_first_to_channels_last_permutation (
176+ y .rank , True
177+ )
178+ )
179+ ops .add_post (
180+ self .builder .create_transpose_operator_after (
181+ t_op , 0 , to_channels_last_perm
182+ )
183+ )
184+ t_op .tmp_outputs [0 ].tensor_format = DataFormat .CHANNELS_FIRST
185+
186+ elif formatless_input and formatless_output :
187+ # No action needed.
188+ pass
189+
190+ else : # channels_last_input and channels_last_output
191+ # This case cannot currently occur, as it would require the case:
192+ # channels last 4D -> mean -> channels_last 3D
193+ # which cannot currently happen as the 3D conv/pooling/... is supported by adding `view_copy` nodes in
194+ # the edge dialect and converting the node to 4D, and the `view_copy` nodes prevent the propagation of
195+ # the format to the `mean.dim` output.
196+ # Therefore, the implementation cannot be tested. But from experience with other operators, it should
197+ # work correctly. We just need to add 2 `Transpose` ops to make the IO channels first, and keep the
198+ # `dim` unchanged.
199+ to_channels_first_perm = (
200+ translator .create_channels_last_to_channels_first_permutation (
201+ x .rank
202+ )
203+ )
204+ ops .add_pre (
205+ self .builder .create_transpose_operator_before (
206+ t_op , 0 , to_channels_first_perm
207+ )
208+ )
209+ t_op .tmp_inputs [0 ].tensor_format = DataFormat .CHANNELS_FIRST
210+
211+ to_channels_last_perm = (
212+ translator .create_channels_first_to_channels_last_permutation (
213+ y .rank , True
214+ )
215+ )
216+ ops .add_post (
217+ self .builder .create_transpose_operator_after (
218+ t_op , 0 , to_channels_last_perm
219+ )
220+ )
221+ t_op .tmp_outputs [0 ].tensor_format = DataFormat .CHANNELS_FIRST
222+
223+ return dim
224+
109225 def convert (self , node : Node ):
110226 """Convert the 'mean.dim' operator to NeutronIR 'Mean'.
111227 The ExecuTorch schema is:
@@ -123,10 +239,9 @@ def convert(self, node: Node):
123239
124240 t_op = self ._create_tflite_op_with_io_tensors (node )
125241 t_op .builtin_options = mean_options .Mean (keepdim )
126- x = t_op .tmp_inputs [0 ]
127242
128- if x . tensor_format . is_channels_last ():
129- dim = self ._normalize_and_to_channel_last_dim ( dim , x . rank )
243+ ops = OpsList ( middle_op = t_op )
244+ dim = self ._get_dim_and_handle_io_formats ( ops , dim , keepdim )
130245
131246 convert_axes_from_attribute (t_op , self .builder , dim )
132- self .builder .append_operators ([ t_op ] )
247+ self .builder .append_operators (ops . flatten () )
0 commit comments