33# This source code is licensed under the BSD-style license found in the
44# LICENSE file in the root directory of this source tree.
55
6+ from copy import copy
7+
8+ import numpy as np
69from executorch .backends .nxp .backend .edge_helper import try_get_arg
710from executorch .backends .nxp .backend .ir .converter .node_converter import (
11+ _is_dequant_node ,
812 CustomDelegationOptions ,
913 is_not_qdq_node ,
1014 NodeConverter ,
1115)
1216from executorch .backends .nxp .backend .ir .lib .tflite .BuiltinOperator import (
1317 BuiltinOperator ,
1418)
19+ from executorch .backends .nxp .backend .ir .tflite_generator import tflite_model
20+ from executorch .backends .nxp .backend .ir .tflite_generator .builtin_options import (
21+ maximum_options ,
22+ minimum_options ,
23+ )
1524from executorch .backends .nxp .backend .neutron_operator_support import (
1625 activation_supported_on_target ,
1726)
2130from torch .nn import Parameter
2231
2332
33+ def _is_convertible_to_relu (node ):
34+ bounds = ClampConverter ._get_clamp_bounds (node )
35+
36+ # Only some specific bounds are supported on the target hardware.
37+ if bounds not in ClampConverter .SUPPORTED_BOUNDS .values ():
38+ return False
39+
40+ return True
41+
42+
2443class ClampConverter (NodeConverter ):
2544 SUPPORTED_BOUNDS = {
2645 "ReluN1To1" : (- 1 , 1 ),
@@ -48,7 +67,7 @@ def _get_clamp_bounds(clamp_node: Node) -> tuple[float | None, float | None]:
4867 def _is_supported_in_IR (
4968 node : Node ,
5069 parameters_mapping : dict [str , Parameter ],
51- custom_delegation_options : CustomDelegationOptions ,
70+ _ : CustomDelegationOptions ,
5271 ) -> bool :
5372 # No NeutronIR-specific restrictions.
5473 return True
@@ -58,22 +77,19 @@ def _is_supported_on_target(
5877 node : Node ,
5978 neutron_target_spec : NeutronTargetSpec ,
6079 parameters_mapping : dict [str , Parameter ],
61- custom_delegation_options : CustomDelegationOptions ,
80+ _ : CustomDelegationOptions ,
6281 ) -> bool :
63- bounds = ClampConverter ._get_clamp_bounds (node )
64-
65- # Only some specific bounds are supported on the target hardware.
66- if bounds not in ClampConverter .SUPPORTED_BOUNDS .values ():
67- return False
82+ if neutron_target_spec .use_new_flow_neutron_c :
83+ return True
6884
69- return True
85+ return _is_convertible_to_relu ( node )
7086
7187 @classmethod
7288 def supports_partitioning_result (
7389 cls ,
7490 node : Node ,
7591 partition_list : list [Partition ],
76- custom_delegation_options : CustomDelegationOptions ,
92+ _ : CustomDelegationOptions ,
7793 neutron_target_spec : NeutronTargetSpec ,
7894 parameters_mapping : dict [str , Parameter ],
7995 ) -> bool :
@@ -91,6 +107,15 @@ def supports_partitioning_result(
91107
92108 return True
93109
110+ @staticmethod
111+ def propagate_quantization (from_node , to_node ):
112+ to_node .quantization = copy (from_node .quantization )
113+
114+ @staticmethod
115+ def _quantize_value (value , zp , scale , quant_min , quant_max ):
116+ rescaled_value = round (value / scale ) + zp
117+ return np .clip (rescaled_value , quant_min , quant_max )
118+
94119 def convert (self , node : Node ):
95120 """Convert the `aten.clamp.default` operator to Neutron IR `Relu*` operators.
96121 The schema is:
@@ -101,13 +126,57 @@ def convert(self, node: Node):
101126 ) -> Tensor
102127 """
103128 self .assert_convertible (node )
129+ to_relu = _is_convertible_to_relu (node )
104130
105131 bounds = self ._get_clamp_bounds (node )
106-
107132 t_op = self ._create_tflite_op_with_io_tensors (node )
108133
109- # noinspection PyTypeChecker,PyUnboundLocalVariable
110- t_op .opcode_index = self .builder .op_code_index_for_op_type (
111- self .BOUNDS_TO_NEUTRON_IR_OP [bounds ]
134+ if not self .neutron_target_spec .use_new_flow_neutron_c or to_relu :
135+ # noinspection PyTypeChecker,PyUnboundLocalVariable
136+ t_op .opcode_index = self .builder .op_code_index_for_op_type (
137+ self .BOUNDS_TO_NEUTRON_IR_OP [bounds ]
138+ )
139+ self .builder .append_operators ([t_op ])
140+ return
141+
142+ q_node = node .args [0 ]
143+ assert _is_dequant_node (q_node )
144+ _ , scale , zp , quant_min , quant_max , _ = q_node .args
145+
146+ x = t_op .tmp_inputs [0 ]
147+ y = t_op .tmp_outputs [0 ]
148+
149+ if x .quantization is not None and y .quantization is None :
150+ self .propagate_quantization (x , y )
151+
152+ if x .quantization != y .quantization :
153+ raise AssertionError (
154+ "Input and output quantization should be same in order to convert to max/min."
155+ )
156+
157+ max_y = self .builder .duplicate_tensor (x )
158+
159+ min_value , max_value = bounds
160+ min_value = self ._quantize_value (min_value , zp , scale , quant_min , quant_max )
161+ max_value = self ._quantize_value (max_value , zp , scale , quant_min , quant_max )
162+
163+ min_tensor = self .builder .create_tensor_for_data (
164+ np .array ([min_value ], np .int8 ), "min"
165+ )
166+ self .propagate_quantization (x , min_tensor )
167+ max_tensor = self .builder .create_tensor_for_data (
168+ np .array ([max_value ], np .int8 ), "max"
112169 )
113- self .builder .append_operators ([t_op ])
170+ self .propagate_quantization (x , max_tensor )
171+
172+ max_op = tflite_model .Operator (builtin_options = maximum_options .Maximum ())
173+ max_op .tmp_inputs = [x , max_tensor ]
174+ max_op .tmp_outputs = [max_y ]
175+
176+ min_op = tflite_model .Operator (builtin_options = minimum_options .Minimum ())
177+ min_op .tmp_inputs = [max_y , min_tensor ]
178+ min_op .tmp_outputs = [y ]
179+
180+ self .propagate_quantization (x , max_y )
181+
182+ self .builder .append_operators ([max_op , min_op ])
0 commit comments