6
6
import numpy as np
7
7
import torch
8
8
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
+ )
10
14
from executorch .backends .nxp .backend .ir .converter .conversion import (
11
15
aten_translator ,
12
16
common ,
13
17
)
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
18
19
from executorch .backends .nxp .backend .ir .converter .node_converter import (
19
20
NodeConverter ,
20
21
Target ,
21
22
)
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
+ )
22
30
from executorch .backends .nxp .backend .ir .converter .quantization_utils import (
23
31
set_quantization_parameters_to_tensor ,
24
32
)
33
+ from executorch .backends .nxp .backend .ir .converter .tensor_utils import tensor_has_data
25
34
from executorch .backends .nxp .backend .ir .lib .tflite .TensorType import TensorType
26
35
from executorch .backends .nxp .backend .ir .tflite_generator import tflite_model
27
36
from executorch .backends .nxp .backend .ir .tflite_generator .builtin_options import (
28
37
conv_2d_options ,
38
+ depthwise_conv_2d_options ,
29
39
)
30
40
from torch .fx import Node
31
41
from torch .nn import Parameter
@@ -48,7 +58,24 @@ def _is_supported_in_IR(
48
58
if output_padding != [0 , 0 ]:
49
59
return False
50
60
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.
52
79
return False
53
80
54
81
if input_tensor_safe (node , 2 ) is None :
@@ -59,69 +86,144 @@ def _is_supported_in_IR(
59
86
60
87
return True
61
88
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
72
92
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 :
84
120
# 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 ]
86
122
87
- if weight_tensor .type == TensorType .FLOAT32 :
123
+ if w .type == TensorType .FLOAT32 :
88
124
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 ]:
90
126
bias_type = np .dtype (np .int32 )
91
127
else :
92
128
# Should never happen.
93
129
raise NotImplementedError (
94
- f"Convolution node with unsupported weight type: { weight_tensor .type } "
130
+ f"Convolution node with unsupported weight type: { w .type } "
95
131
)
96
132
97
- bias_tensor = self .builder .create_zeros_tensor (
133
+ b = self .builder .create_zeros_tensor (
98
134
[output_channels ], "zero_bias" , bias_type , True
99
135
)
100
136
101
137
# 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 )
104
140
bias_scale = input_scale * weight_scale
105
141
bias_zero_point = np .zeros (weight_scale .shape , dtype = np .int64 )
106
142
107
143
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
109
145
)
110
146
111
147
# 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
+ )
114
210
115
- return ops .flatten ()
211
+ return conversion_result . ops_list .flatten ()
116
212
117
213
def convert (self , node : Node ):
118
214
self .assert_convertible (node )
119
215
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 )
123
217
124
218
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.
126
228
127
229
self .builder .append_operators (ops_to_add )
0 commit comments