1- # Copyright 2024-2025 NXP
1+ # Copyright 2024-2026 NXP
22#
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 executorch .backends .nxp .backend .edge_helper import input_rank
6+ import torch
7+
8+ from executorch .backends .nxp .backend .edge_helper import (
9+ input_rank ,
10+ node_is_effectively_static_tensor ,
11+ weights_are_effectively_static ,
12+ )
713from executorch .backends .nxp .backend .ir .converter .conversion .common import OpsList
814from executorch .backends .nxp .backend .ir .converter .node_converter import (
915 CustomDelegationOptions ,
1218from executorch .backends .nxp .backend .ir .tflite_generator .builtin_options import (
1319 fully_connected_options ,
1420)
21+
22+ from executorch .backends .nxp .backend .neutron_target_spec import NeutronTargetSpec
1523from torch .fx import Node
1624from torch .nn import Parameter
1725
1826
27+ # The edge operator signature is: aten.addmm(bias, input, weight, *, beta=1, alpha=1)
28+ MAIN_INPUT_IDX = 1
29+ WEIGHT_IDX = 2
30+ BIAS_IDX = 0
31+
32+
1933class AddMMConverter (NodeConverter ):
2034 """Convert the `aten.addmm` operator to TFLite `FullyConnected` with a bias input."""
2135
@@ -29,12 +43,76 @@ def _is_supported_in_IR(
2943 return False
3044
3145 # The weights must be 2D.
32- if input_rank (node , 2 ) != 2 :
46+ if input_rank (node , WEIGHT_IDX ) != 2 :
47+ return False
48+
49+ alpha , beta = node .kwargs .get ("alpha" , 1 ), node .kwargs .get ("beta" , 1 )
50+ if alpha != 1 or beta != 1 :
51+ # As these cases seem rare, conversion is not implemented for the time being.
52+ return False
53+
54+ # The `aten.addmm` operator allows any bias shape, as long as it is broadcastable with the result of the matrix
55+ # multiplication. That means it supports 4 different shapes: [N, P], [1, P], [P], [1] (provided the MM result
56+ # has shape [N, P]). Out of these 4, Neutron IR allows only [1, P] and [P], both of which are supported on
57+ # Neutron.
58+ bias_shape = list (node .args [BIAS_IDX ].meta ["val" ].shape )
59+ _ , p = node .meta ["val" ].shape
60+ if bias_shape not in [[1 , p ], [p ]]:
61+ return False
62+
63+ return True
64+
65+ @staticmethod
66+ def _is_supported_on_target (
67+ node : Node ,
68+ neutron_target_spec : NeutronTargetSpec ,
69+ parameters_mapping : dict [str , Parameter ],
70+ custom_delegation_options : CustomDelegationOptions ,
71+ ) -> bool :
72+ # Main input and output must be `int8` or `uint8`.
73+ if not NodeConverter .uses_quantization_type_for_io (
74+ node , [torch .int8 , torch .uint8 ], [MAIN_INPUT_IDX ], [0 ]
75+ ):
76+ return False
77+
78+ # Weights must be `int8`.
79+ if not NodeConverter .uses_quantization_type_for_io (
80+ node , [torch .int8 ], [WEIGHT_IDX ], []
81+ ):
82+ return False
83+
84+ # Bias must be `int32`.
85+ if not NodeConverter .uses_quantization_type_for_io (
86+ node , [torch .int32 ], [BIAS_IDX ], []
87+ ):
88+ return False
89+
90+ # Weights must be constant.
91+ if not weights_are_effectively_static (
92+ node , parameters_mapping , weight_index = WEIGHT_IDX
93+ ):
94+ return False
95+
96+ # The bias must be constant.
97+ if not node_is_effectively_static_tensor (
98+ node .args [BIAS_IDX ], parameters_mapping
99+ ):
33100 return False
34101
35102 return True
36103
37104 def convert (self , node : Node ):
105+ """Convert the `aten.addmm` operator to NeutronIR `FullyConnected`.
106+ The schema is:
107+ addmm(
108+ Tensor self,
109+ Tensor mat1,
110+ Tensor mat2,
111+ *,
112+ Scalar beta=1,
113+ Scalar alpha=1
114+ ) -> Tensor
115+ """
38116 self .assert_convertible (node )
39117
40118 t_op = self ._create_tflite_op_with_io_tensors (node )
@@ -47,14 +125,14 @@ def convert(self, node: Node):
47125 w = t_op .tmp_inputs [2 ]
48126 y = t_op .tmp_outputs [0 ]
49127
50- # Assign the operator its TFLite inputs and outputs
128+ # Assign the operator its Neutron IR inputs and outputs
51129 t_op .tmp_inputs = [x , w , bias ]
52130 t_op .tmp_outputs = [y ]
53131
54132 ops = OpsList (middle_op = t_op )
55133
56134 # The `aten.addmm` uses main input with shape [M, N] and the weights have the shape [N, O].
57- # TFLite `FullyConnected` requires the weights to have shape [O, N] (if the main input has shape [M, N]).
135+ # Neutron IR `FullyConnected` requires the weights to have shape [O, N] (if the main input has shape [M, N]).
58136 # Insert a `Transpose` operator to permute the weights to achieve correct conversion. (The `Transpose` will not
59137 # be present in the output model if the weights are static.)
60138 ops .add_pre (self .builder .create_transpose_operator_before (t_op , 1 , [1 , 0 ]))
0 commit comments