66import numpy as np
77import torch
88
9- from executorch .backends .nxp .backend .edge_helper import input_tensor , input_tensor_safe
9+ from executorch .backends .nxp .backend .edge_helper import (
10+ input_tensor ,
11+ input_tensor_safe ,
12+ node_is_effectively_static_tensor ,
13+ )
1014from executorch .backends .nxp .backend .ir .converter .conversion import (
1115 aten_translator ,
1216 common ,
1317)
14- from executorch .backends .nxp .backend .ir .converter .conversion .common import (
15- OpsList ,
16- try_get_input ,
17- )
18+ from executorch .backends .nxp .backend .ir .converter .conversion .common import try_get_input
1819from executorch .backends .nxp .backend .ir .converter .node_converter import (
1920 NodeConverter ,
2021 Target ,
2122)
23+ from executorch .backends .nxp .backend .ir .converter .node_converters .shared import (
24+ conv_utils ,
25+ )
26+ from executorch .backends .nxp .backend .ir .converter .node_converters .shared .conv_utils import (
27+ ConvConversionResult ,
28+ ConvParameters ,
29+ )
2230from executorch .backends .nxp .backend .ir .converter .quantization_utils import (
2331 set_quantization_parameters_to_tensor ,
2432)
33+ from executorch .backends .nxp .backend .ir .converter .tensor_utils import tensor_has_data
2534from executorch .backends .nxp .backend .ir .lib .tflite .TensorType import TensorType
2635from executorch .backends .nxp .backend .ir .tflite_generator import tflite_model
2736from executorch .backends .nxp .backend .ir .tflite_generator .builtin_options import (
2837 conv_2d_options ,
38+ depthwise_conv_2d_options ,
2939)
3040from torch .fx import Node
3141from torch .nn import Parameter
@@ -48,7 +58,24 @@ def _is_supported_in_IR(
4858 if output_padding != [0 , 0 ]:
4959 return False
5060
51- if groups != 1 :
61+ if groups == 1 :
62+ # Regular convolution.
63+ pass
64+
65+ elif conv_utils .group_conv_convertible_as_depthwise (
66+ node , groups
67+ ) and node_is_effectively_static_tensor (node .args [1 ], parameters_mapping ):
68+ # Depthwise convolution.
69+ # Only supported if the weights are static, because TFLite `DepthwiseConv2D` uses permuted weights. In case
70+ # the weights are dynamic, a Transpose operator would have to be added, which is not supported on Neutron.
71+ pass
72+
73+ elif conv_utils .group_conv_convertible_into_multiple_convolutions (node , groups ):
74+ # Separable convolution. Currently not supported.
75+ return False
76+
77+ else :
78+ # All conversion options related to the `group` attribute have been checked and none of them can be used.
5279 return False
5380
5481 if input_tensor_safe (node , 2 ) is None :
@@ -59,69 +86,144 @@ def _is_supported_in_IR(
5986
6087 return True
6188
62- def _convert_2d_conv (
63- self , stride , padding , dilation , t_op : tflite_model .Operator
64- ) -> list [tflite_model .Operator ]:
65- ops = OpsList (middle_op = t_op )
66- t_op .builtin_options = conv_2d_options .Conv2D ()
67- common .assign_2d_strides (t_op .builtin_options , stride )
68- common .assign_2d_dilations (t_op .builtin_options , dilation )
69- t_op .builtin_options .padding , explicit_padding = (
70- aten_translator .convert_padding (padding )
71- )
89+ Stride = Padding = Dilation = OutPadding = list [int ]
90+ Transposed = bool
91+ Groups = int
7292
73- if explicit_padding is not None :
74- # Need to prepend a 'Pad' operator, which adds 0s. But these will be included in the computation!
75- ops .add_pre (
76- self .builder .create_pad_operator_before (t_op , 0 , explicit_padding )
77- )
78-
79- input_tensor : tflite_model .Tensor = t_op .tmp_inputs [0 ]
80- weight_tensor : tflite_model .Tensor = t_op .tmp_inputs [1 ]
81- output_tensor : tflite_model .Tensor = t_op .tmp_outputs [0 ]
82-
83- if (bias_tensor := try_get_input (t_op , 2 )) is None :
93+ @staticmethod
94+ def _get_convolution_arguments (
95+ conv_node : Node ,
96+ ) -> (Stride , Padding , Dilation , Transposed , OutPadding , Groups ):
97+ # The arguments of the conv are:
98+ # [x, w, b, stride, padding, dilation, transposed, output padding, groups]
99+ # https://github.com/pytorch/pytorch/blob/v2.6.0/aten/src/ATen/native/Convolution.cpp#L286-L291
100+ _ , _ , _ , stride , padding , dilation , transposed , out_padding , groups = (
101+ conv_node .args
102+ )
103+ return stride , padding , dilation , transposed , out_padding , groups
104+
105+ # noinspection PyPep8Naming
106+ def _convert_unpadded_2D (
107+ self , t_op : tflite_model .Operator , conv_params : ConvParameters
108+ ) -> conv_utils .ConvConversionResult :
109+ """Convert the `aten.convolution` into TFLite. The `padding` and `builtin_options` must be converter by the
110+ caller.
111+ """
112+ common .assign_2d_strides (t_op .builtin_options , conv_params .stride )
113+ common .assign_2d_dilations (t_op .builtin_options , conv_params .dilation )
114+
115+ x : tflite_model .Tensor = t_op .tmp_inputs [0 ]
116+ w : tflite_model .Tensor = t_op .tmp_inputs [1 ]
117+ y : tflite_model .Tensor = t_op .tmp_outputs [0 ]
118+
119+ if (b := try_get_input (t_op , 2 )) is None :
84120 # Operator has no bias. Convolution aten op can omit it, TFLite can't.
85- output_channels = weight_tensor .shape .vector [0 ]
121+ output_channels = w .shape .vector [0 ]
86122
87- if weight_tensor .type == TensorType .FLOAT32 :
123+ if w .type == TensorType .FLOAT32 :
88124 bias_type = np .dtype (np .float32 )
89- elif weight_tensor .type in [TensorType .INT8 , TensorType .UINT8 ]:
125+ elif w .type in [TensorType .INT8 , TensorType .UINT8 ]:
90126 bias_type = np .dtype (np .int32 )
91127 else :
92128 # Should never happen.
93129 raise NotImplementedError (
94- f"Convolution node with unsupported weight type: { weight_tensor .type } "
130+ f"Convolution node with unsupported weight type: { w .type } "
95131 )
96132
97- bias_tensor = self .builder .create_zeros_tensor (
133+ b = self .builder .create_zeros_tensor (
98134 [output_channels ], "zero_bias" , bias_type , True
99135 )
100136
101137 # Compute scale and zero point for bias tensor
102- input_scale = np .array (input_tensor .quantization .scale .vector )
103- weight_scale = np .array (weight_tensor .quantization .scale .vector )
138+ input_scale = np .array (x .quantization .scale .vector )
139+ weight_scale = np .array (w .quantization .scale .vector )
104140 bias_scale = input_scale * weight_scale
105141 bias_zero_point = np .zeros (weight_scale .shape , dtype = np .int64 )
106142
107143 set_quantization_parameters_to_tensor (
108- bias_tensor , bias_scale , bias_zero_point , quantized_dimension = 0
144+ b , bias_scale , bias_zero_point , quantized_dimension = 0
109145 )
110146
111147 # Assign the operator its TFLite inputs and outputs
112- t_op .tmp_inputs = [input_tensor , weight_tensor , bias_tensor ]
113- t_op .tmp_outputs = [output_tensor ]
148+ t_op .tmp_inputs = [x , w , b ]
149+ t_op .tmp_outputs = [y ]
150+
151+ conversion_result = ConvConversionResult (x , w , b , y )
152+ conversion_result .ops_list .middle_op = t_op
153+
154+ return conversion_result
155+
156+ def _convert_2d_conv (
157+ self , t_op : tflite_model .Operator , conv_params : ConvParameters
158+ ) -> list [tflite_model .Operator ]:
159+ if conv_utils .group_conv_convertible_as_depthwise (
160+ t_op , conv_params .groups
161+ ): # Convert to `DepthwiseConv2D`.
162+ t_op .builtin_options = depthwise_conv_2d_options .DepthwiseConv2D ()
163+
164+ conversion_result = self ._convert_unpadded_2D (t_op , conv_params )
165+ t_op .builtin_options .padding , explicit_padding = (
166+ aten_translator .convert_padding (conv_params .padding )
167+ )
168+ if explicit_padding is not None :
169+ # Need to prepend a 'Pad' operator, which adds 0s.
170+ conversion_result .ops_list .add_pre (
171+ self .builder .create_pad_operator_before (t_op , 0 , explicit_padding )
172+ )
173+
174+ # DepthwiseConv2D expects weights in format [kernel_channels, kernel_height, kernel_width, output_channels]
175+ perm = [3 , 1 , 2 , 0 ]
176+ weight_tensor = conversion_result .conv_weight_tensor
177+ if tensor_has_data (weight_tensor ):
178+ # Transpose cloned tensor statically
179+ t_op .tmp_inputs [1 ] = self .builder .create_transposed_tensor (
180+ weight_tensor , perm
181+ )
182+ else :
183+ raise NotImplementedError ("Dynamic Depthwise Conv weights." )
184+
185+ elif conv_utils .group_conv_convertible_into_multiple_convolutions (
186+ t_op , conv_params .groups
187+ ):
188+ t_op .builtin_options = conv_2d_options .Conv2D ()
189+
190+ return conv_utils .create_separated_convolutions_based_on_group (
191+ t_op ,
192+ conv_params ,
193+ self .builder ,
194+ self ._convert_unpadded_2D ,
195+ conv_utils .conv_op_factory ,
196+ )
197+
198+ else :
199+ # Convert to regular `Conv2D`.
200+ t_op .builtin_options = conv_2d_options .Conv2D ()
201+ conversion_result = self ._convert_unpadded_2D (t_op , conv_params )
202+ t_op .builtin_options .padding , explicit_padding = (
203+ aten_translator .convert_padding (conv_params .padding )
204+ )
205+ if explicit_padding is not None :
206+ # Need to prepend a 'Pad' operator, which adds 0s.
207+ conversion_result .ops_list .add_pre (
208+ self .builder .create_pad_operator_before (t_op , 0 , explicit_padding )
209+ )
114210
115- return ops .flatten ()
211+ return conversion_result . ops_list .flatten ()
116212
117213 def convert (self , node : Node ):
118214 self .assert_convertible (node )
119215
120- stride = node .args [3 ]
121- padding = node .args [4 ]
122- dilation = node .args [5 ]
216+ stride , padding , dilation , _ , _ , groups = self ._get_convolution_arguments (node )
123217
124218 t_op = self ._create_tflite_op_with_io_tensors (node )
125- ops_to_add = self ._convert_2d_conv (stride , padding , dilation , t_op )
219+ conv_params = ConvParameters (stride , padding , dilation , groups )
220+
221+ rank = t_op .tmp_inputs [1 ].shape .len ()
222+ if rank == 4 : # Conv2D
223+ ops_to_add = self ._convert_2d_conv (t_op , conv_params )
224+ else :
225+ raise NotImplementedError (
226+ f"{ rank - 2 } D convolution is not supported."
227+ ) # Should never get here.
126228
127229 self .builder .append_operators (ops_to_add )
0 commit comments