diff --git a/backends/samsung/_passes/remove_useless_ops.py b/backends/samsung/_passes/remove_useless_ops.py index c88a2d4a5d8..9c965844749 100644 --- a/backends/samsung/_passes/remove_useless_ops.py +++ b/backends/samsung/_passes/remove_useless_ops.py @@ -15,7 +15,6 @@ class RemoveUselessOpPass(ExportPass): USELESS_OP_SET = { exir_ops.edge.aten._to_copy.default, exir_ops.edge.aten.clone.default, - exir_ops.edge.aten.clone.default, exir_ops.edge.aten.alias.default, exir_ops.edge.aten.lift_fresh_copy.default, exir_ops.edge.dim_order_ops._to_dim_order_copy.default, diff --git a/backends/samsung/_passes/replace_scalar_ops.py b/backends/samsung/_passes/replace_scalar_ops.py index 8ae54b0dc98..22a74c15f61 100644 --- a/backends/samsung/_passes/replace_scalar_ops.py +++ b/backends/samsung/_passes/replace_scalar_ops.py @@ -38,9 +38,16 @@ def call_operator( if op not in self._ops_with_scalar: return super().call_operator(op, args, kwargs, meta) + # For pow operation, convert int scalar to float32 tensor + # because the PowVisitor requires both inputs to be float32 + if op == exir_ops.edge.aten.pow.Tensor_Scalar and isinstance(args[1], int): + args1 = torch.tensor(float(args[1]), dtype=torch.float32) + else: + args1 = torch.tensor(args[1]) + return super().call_operator( op=self._ops_with_scalar.get(op, op), - args=(args[0], torch.tensor(args[1])), + args=(args[0], args1), kwargs=kwargs, meta=meta, ) diff --git a/backends/samsung/builders/__init__.py b/backends/samsung/builders/__init__.py index 14b9a17a6c9..60f97cf60f7 100644 --- a/backends/samsung/builders/__init__.py +++ b/backends/samsung/builders/__init__.py @@ -18,6 +18,7 @@ op_dequantize, op_div, op_embedding, + op_exp, op_expand_copy, op_gelu, op_getitem, @@ -48,6 +49,7 @@ op_select, op_sigmoid, op_sin, + op_skip, op_slice_copy, op_softmax, op_split_with_sizes_copy, @@ -77,6 +79,7 @@ op_dequantize, op_div, op_embedding, + op_exp, op_expand_copy, op_gelu, op_getitem, @@ -107,6 +110,7 @@ op_select, op_sigmoid, op_sin, + op_skip, op_slice_copy, op_softmax, op_split_with_sizes_copy, diff --git a/backends/samsung/builders/node_visitor.py b/backends/samsung/builders/node_visitor.py index 0d2707da8f5..cb7d4db690a 100644 --- a/backends/samsung/builders/node_visitor.py +++ b/backends/samsung/builders/node_visitor.py @@ -31,7 +31,7 @@ def __init__(self, exported_program: ExportedProgram) -> None: def exported_program(self) -> ExportedProgram: return self._exported_program - def define_node(self, node: torch.fx.Node, enn_graph: EnnGraph): + def define_node(self, node: torch.fx.Node, enn_graph: EnnGraph) -> bool: raise NotImplementedError("NodeVisitor must be extended!") def define_tensor( @@ -58,7 +58,9 @@ def define_tensor( if is_param_node(self.exported_program, node): if swap_nc_for_weights: tensor = torch.swapdims(tensor, 0, 1) - const_data = tensor.contiguous().detach().numpy() + if not isinstance(tensor, torch._subclasses.fake_tensor.FakeTensor): + # .numpy() is not supported for tensor subclasses if the tensor is a fake tensor. + const_data = tensor.contiguous().detach().numpy() dims = [1] if len(tensor.size()) == 0 else list(tensor.size()) diff --git a/backends/samsung/builders/op_add.py b/backends/samsung/builders/op_add.py index a6eb79897dd..177f700f7e1 100644 --- a/backends/samsung/builders/op_add.py +++ b/backends/samsung/builders/op_add.py @@ -4,6 +4,7 @@ # This source code is licensed under the BSD-style license found in the # LICENSE file in the root directory of this source tree. +import logging from typing import Dict import torch @@ -26,16 +27,22 @@ def define_node( node: torch.fx.Node, enn_graph: EnnGraph, vals_to_ids: Dict[torch.Tensor, int], - ) -> None: + ) -> bool: input1 = node.args[0] input_id_1 = self.define_tensor(input1, enn_graph, vals_to_ids) params = {} self._update_params_qdtype(node, params) input2 = node.args[1] input_id_2 = self.define_tensor(input2, enn_graph, vals_to_ids) + alpha = node.kwargs.get("alpha", 1.0) + if alpha != 1.0: + logging.warning("Currently, only alpha 1 for add is supported.") + return False output_id = self.define_tensor(node, enn_graph, vals_to_ids) enn_graph.define_op( node.name, "ELTSUM", [input_id_1, input_id_2], [output_id], params ) + + return True diff --git a/backends/samsung/builders/op_avg_pool2d.py b/backends/samsung/builders/op_avg_pool2d.py index bfca8b89b22..529a3156030 100644 --- a/backends/samsung/builders/op_avg_pool2d.py +++ b/backends/samsung/builders/op_avg_pool2d.py @@ -26,7 +26,7 @@ def define_node( node: torch.fx.Node, enn_graph: EnnGraph, vals_to_ids: Dict[torch.Tensor, int], - ) -> None: + ) -> bool: input = node.args[0] input_id = self.define_tensor(input, enn_graph, vals_to_ids) @@ -52,10 +52,6 @@ def define_node( params["explicit_padding"] = explicit_padding self._update_params_qdtype(node, params) - if len(node.args) > 4: - ceil_mode = cast(bool, node.args[4]) - assert not ceil_mode, "Not support ceil_mode = True." - if len(node.args) > 5: params["count_include_pad"] = cast(bool, node.args[5]) else: @@ -68,3 +64,5 @@ def define_node( ), "Not supported divisor_override which is not equal to pooling region." output_id = self.define_tensor(node, enn_graph, vals_to_ids) enn_graph.define_op(node.name, "AVGPOOL2D", [input_id], [output_id], params) + + return True diff --git a/backends/samsung/builders/op_batch_norm.py b/backends/samsung/builders/op_batch_norm.py index e5373a8223a..49840580f4e 100644 --- a/backends/samsung/builders/op_batch_norm.py +++ b/backends/samsung/builders/op_batch_norm.py @@ -25,7 +25,7 @@ def define_node( node: torch.fx.Node, enn_graph: EnnGraph, vals_to_ids: Dict[torch.Tensor, int], - ) -> None: + ) -> bool: all_input_tensors = [] input = node.args[0] input_id = self.define_tensor(input, enn_graph, vals_to_ids) @@ -51,6 +51,12 @@ def define_node( output_id = self.define_tensor(node, enn_graph, vals_to_ids, output_idx=0) + users = list(node.users.keys()) + if len(users) > 0 and users[0].target.__name__ == "getitem": + vals_to_ids[users[0]] = output_id + enn_graph.define_op( node.name, "BatchNormalization", all_input_tensors, [output_id], params ) + + return True diff --git a/backends/samsung/builders/op_bmm.py b/backends/samsung/builders/op_bmm.py index 13e0d19cb14..2ac96a4a1b7 100644 --- a/backends/samsung/builders/op_bmm.py +++ b/backends/samsung/builders/op_bmm.py @@ -26,7 +26,7 @@ def define_node( node: torch.fx.Node, enn_graph: EnnGraph, vals_to_ids: Dict[torch.Tensor, int], - ) -> None: + ) -> bool: input1 = node.args[0] input_id_1 = self.define_tensor(input1, enn_graph, vals_to_ids) @@ -41,3 +41,5 @@ def define_node( enn_graph.define_op( node.name, "BATCH_MATMUL", [input_id_1, input_id_2], [output_id], params ) + + return True diff --git a/backends/samsung/builders/op_cat.py b/backends/samsung/builders/op_cat.py index 09387f2e361..82762cfcdbf 100644 --- a/backends/samsung/builders/op_cat.py +++ b/backends/samsung/builders/op_cat.py @@ -28,7 +28,7 @@ def define_node( node: torch.fx.Node, enn_graph: EnnGraph, vals_to_ids: Dict[torch.Tensor, int], - ) -> None: + ) -> bool: tensors = cast(List[torch.fx.Node], node.args[0]) input_tensor_ids = [] constant_idx = None @@ -48,3 +48,5 @@ def define_node( output_id = self.define_tensor(node, enn_graph, vals_to_ids) enn_graph.define_op(node.name, "CONCAT", input_tensor_ids, [output_id], params) + + return True diff --git a/backends/samsung/builders/op_clamp.py b/backends/samsung/builders/op_clamp.py index 74af83212a5..b69066c3337 100644 --- a/backends/samsung/builders/op_clamp.py +++ b/backends/samsung/builders/op_clamp.py @@ -4,6 +4,7 @@ # This source code is licensed under the BSD-style license found in the # LICENSE file in the root directory of this source tree. +import logging from typing import cast, Dict import torch @@ -11,6 +12,7 @@ NodeVisitor, register_node_visitor, ) +from executorch.backends.samsung.builders.utils import get_tensor from executorch.backends.samsung.serialization.enn_graph_schema import EnnGraph @@ -26,9 +28,13 @@ def define_node( node: torch.fx.Node, enn_graph: EnnGraph, vals_to_ids: Dict[torch.Tensor, int], - ) -> None: + ) -> bool: input = node.args[0] input_id = self.define_tensor(input, enn_graph, vals_to_ids) + input_tensor = get_tensor(self.exported_program, input) + if input_tensor.dtype == torch.int64: + logging.warning("Currently, int64 clip is unsupported!") + return False # The default value of lower bound and upper bound output_min = torch.finfo(torch.float32).min @@ -45,3 +51,5 @@ def define_node( output_id = self.define_tensor(node, enn_graph, vals_to_ids) enn_graph.define_op(node.name, "CLIP", [input_id], [output_id], params) + + return True diff --git a/backends/samsung/builders/op_constant_pad_nd.py b/backends/samsung/builders/op_constant_pad_nd.py index 006f52619ff..bc72305bc13 100644 --- a/backends/samsung/builders/op_constant_pad_nd.py +++ b/backends/samsung/builders/op_constant_pad_nd.py @@ -29,7 +29,7 @@ def define_node( node: torch.fx.Node, enn_graph: EnnGraph, vals_to_ids: Dict[torch.Tensor, int], - ) -> None: + ) -> bool: input = node.args[0] input_id = self.define_tensor(input, enn_graph, vals_to_ids) @@ -54,3 +54,5 @@ def define_node( } self._update_params_qdtype(node, params) enn_graph.define_op(node.name, "PAD", [input_id], [output_id], params) + + return True diff --git a/backends/samsung/builders/op_conv2d.py b/backends/samsung/builders/op_conv2d.py index ab77d8df626..87f76f610bf 100644 --- a/backends/samsung/builders/op_conv2d.py +++ b/backends/samsung/builders/op_conv2d.py @@ -4,6 +4,7 @@ # This source code is licensed under the BSD-style license found in the # LICENSE file in the root directory of this source tree. +import logging from typing import cast, Dict, List import torch @@ -27,7 +28,7 @@ def define_node( node: torch.fx.Node, enn_graph: EnnGraph, vals_to_ids: Dict[torch.Tensor, int], - ) -> None: + ) -> bool: all_input_tensors = [] input = node.args[0] input_id = self.define_tensor(input, enn_graph, vals_to_ids) @@ -52,9 +53,24 @@ def define_node( padding = cast(List[int], node.args[4]) dilation = cast(List[int], node.args[5]) groups = cast(int, node.args[8]) + if is_transpose_conv and groups != 1: + logging.warning("Don't support groups for transpose conv.") + return False + output_padding = cast(List[int], node.args[7]) + if is_transpose_conv and output_padding != [0, 0]: + logging.warning("Don't support output padding for transpose conv.") + return False + if len(padding) < 2: + logging.warning( + "For conv1d decomposed to conv2d(with conv1d params), Conv1dToConv2d pass will update the params." + ) + return True explicit_padding = [padding[0], padding[1], padding[0], padding[1]] input_shape = get_shape(input) + if len(input_shape) > 4: + logging.warning("Currently, only conv2d is supported.") + return False kernel_shape = get_shape(weight_node) params = {} self._update_params_qdtype(node, params) @@ -72,7 +88,7 @@ def define_node( params["explicit_padding"] = explicit_padding params["in_channels"] = input_shape[1] params["out_channels"] = kernel_shape[0] * kernel_shape[1] * groups - params["out_channels"] //= input_shape[1] * input_shape[0] + params["out_channels"] //= input_shape[1] output_id = self.define_tensor(node, enn_graph, vals_to_ids) @@ -87,3 +103,5 @@ def define_node( enn_graph.define_op( node.name, conv_type, all_input_tensors, [output_id], params ) + + return True diff --git a/backends/samsung/builders/op_cos.py b/backends/samsung/builders/op_cos.py index bd746db91cd..f1d4d917b7d 100644 --- a/backends/samsung/builders/op_cos.py +++ b/backends/samsung/builders/op_cos.py @@ -23,9 +23,11 @@ def define_node( node: torch.fx.Node, enn_graph: EnnGraph, vals_to_ids: Dict[torch.Tensor, int], - ) -> None: + ) -> bool: input_id = self.define_tensor(node.args[0], enn_graph, vals_to_ids) output_id = self.define_tensor(node, enn_graph, vals_to_ids) enn_graph.define_op(node.name, "Cos", [input_id], [output_id]) + + return True diff --git a/backends/samsung/builders/op_div.py b/backends/samsung/builders/op_div.py index 8b0e7cdd5af..7afc23220fe 100644 --- a/backends/samsung/builders/op_div.py +++ b/backends/samsung/builders/op_div.py @@ -26,7 +26,7 @@ def define_node( node: torch.fx.Node, enn_graph: EnnGraph, vals_to_ids: Dict[torch.Tensor, int], - ) -> None: + ) -> bool: input1 = node.args[0] input_id_1 = self.define_tensor(input1, enn_graph, vals_to_ids) @@ -40,3 +40,5 @@ def define_node( enn_graph.define_op( node.name, "ELTDIV", [input_id_1, input_id_2], [output_id], params ) + + return True diff --git a/backends/samsung/builders/op_embedding.py b/backends/samsung/builders/op_embedding.py index a500ea051fd..bc28ae4e55e 100644 --- a/backends/samsung/builders/op_embedding.py +++ b/backends/samsung/builders/op_embedding.py @@ -26,7 +26,7 @@ def define_node( node: torch.fx.Node, enn_graph: EnnGraph, vals_to_ids: Dict[torch.Tensor, int], - ) -> None: + ) -> bool: weight_node = node.args[0] weight_id = self.define_tensor(weight_node, enn_graph, vals_to_ids) @@ -40,3 +40,5 @@ def define_node( enn_graph.define_op( node.name, "GATHER", [weight_id, input_id], [output_id], params ) + + return True diff --git a/backends/samsung/builders/op_exp.py b/backends/samsung/builders/op_exp.py new file mode 100644 index 00000000000..63dd9ca3219 --- /dev/null +++ b/backends/samsung/builders/op_exp.py @@ -0,0 +1,33 @@ +# Copyright (c) 2026 Samsung Electronics Co. LTD +# All rights reserved +# +# This source code is licensed under the BSD-style license found in the +# LICENSE file in the root directory of this source tree. + +from typing import Dict + +import torch +from executorch.backends.samsung.builders.node_visitor import ( + NodeVisitor, + register_node_visitor, +) +from executorch.backends.samsung.serialization.enn_graph_schema import EnnGraph + + +@register_node_visitor +class ExpVisitor(NodeVisitor): + target = "aten.exp.default" + + def define_node( + self, + node: torch.fx.Node, + enn_graph: EnnGraph, + vals_to_ids: Dict[torch.Tensor, int], + ) -> bool: + input_id = self.define_tensor(node.args[0], enn_graph, vals_to_ids) + + output_id = self.define_tensor(node, enn_graph, vals_to_ids) + + enn_graph.define_op(node.name, "Exp", [input_id], [output_id]) + + return True diff --git a/backends/samsung/builders/op_expand_copy.py b/backends/samsung/builders/op_expand_copy.py index f4c707b8e62..5fb6b6c5166 100644 --- a/backends/samsung/builders/op_expand_copy.py +++ b/backends/samsung/builders/op_expand_copy.py @@ -4,6 +4,7 @@ # This source code is licensed under the BSD-style license found in the # LICENSE file in the root directory of this source tree. +import logging from typing import cast, Dict, List import torch @@ -27,7 +28,7 @@ def define_node( node: torch.fx.Node, enn_graph: EnnGraph, vals_to_ids: Dict[torch.Tensor, int], - ): + ) -> bool: # inputs input = node.args[0] input_id = self.define_tensor(input, enn_graph, vals_to_ids) @@ -35,6 +36,8 @@ def define_node( in_shape = get_shape(input) sizes = cast(List[int], node.args[1]) expand_dims = self.check_expand_dims(sizes, in_shape) + if expand_dims is None: + return False # output output_id = self.define_tensor(node, enn_graph, vals_to_ids) @@ -53,7 +56,10 @@ def define_node( params, ) else: - raise NotImplementedError("Don't support expanding at more than one axes.") + logging.warning("Don't support expanding at more than one axes.") + return False + + return True def check_expand_dims(self, sizes, in_shape): expand_dims = [] @@ -72,6 +78,8 @@ def check_expand_dims(self, sizes, in_shape): while new_size_index > 0: new_size_index -= 1 - assert sizes[new_size_index] == 1, "Current expand is unsupported!" + if sizes[new_size_index] != 1: + logging.warning("Current expand is unsupported!") + return None return expand_dims diff --git a/backends/samsung/builders/op_gelu.py b/backends/samsung/builders/op_gelu.py index 88417f688f9..6a064194561 100644 --- a/backends/samsung/builders/op_gelu.py +++ b/backends/samsung/builders/op_gelu.py @@ -26,7 +26,7 @@ def define_node( node: torch.fx.Node, enn_graph: EnnGraph, vals_to_ids: Dict[torch.Tensor, int], - ) -> None: + ) -> bool: # input1 input = node.args[0] input_id = self.define_tensor(input, enn_graph, vals_to_ids) @@ -38,3 +38,5 @@ def define_node( self._update_params_qdtype(node, params) enn_graph.define_op(node.name, "GELU", [input_id], [output_id], params) + + return True diff --git a/backends/samsung/builders/op_getitem.py b/backends/samsung/builders/op_getitem.py index 901ec73cf7d..bc7c0441e3c 100644 --- a/backends/samsung/builders/op_getitem.py +++ b/backends/samsung/builders/op_getitem.py @@ -28,5 +28,5 @@ def define_node( node: torch.fx.Node, enn_graph: EnnGraph, vals_to_ids: Dict[torch.Tensor, int], - ) -> None: - return + ) -> bool: + return True diff --git a/backends/samsung/builders/op_group_norm.py b/backends/samsung/builders/op_group_norm.py index 55c7bb6732a..b0509005053 100644 --- a/backends/samsung/builders/op_group_norm.py +++ b/backends/samsung/builders/op_group_norm.py @@ -23,7 +23,7 @@ def define_node( node: torch.fx.Node, enn_graph: EnnGraph, vals_to_ids: Dict[torch.Tensor, int], - ) -> None: + ) -> bool: all_input_tensors = [] input_id = self.define_tensor(node.args[0], enn_graph, vals_to_ids) all_input_tensors.append(input_id) @@ -44,3 +44,5 @@ def define_node( enn_graph.define_op( node.name, "GROUPNORM", all_input_tensors, [output_id], params ) + + return True diff --git a/backends/samsung/builders/op_hardsigmoid.py b/backends/samsung/builders/op_hardsigmoid.py index 3a50d65da41..58cc0a12d5b 100644 --- a/backends/samsung/builders/op_hardsigmoid.py +++ b/backends/samsung/builders/op_hardsigmoid.py @@ -26,10 +26,12 @@ def define_node( node: torch.fx.Node, enn_graph: EnnGraph, vals_to_ids: Dict[torch.Tensor, int], - ) -> None: + ) -> bool: input = node.args[0] input_id = self.define_tensor(input, enn_graph, vals_to_ids) output_id = self.define_tensor(node, enn_graph, vals_to_ids) params = {} self._update_params_qdtype(node, params) enn_graph.define_op(node.name, "HardSigmoid", [input_id], [output_id], params) + + return True diff --git a/backends/samsung/builders/op_hardswish.py b/backends/samsung/builders/op_hardswish.py index 8c30125e8a4..fc9ec134418 100644 --- a/backends/samsung/builders/op_hardswish.py +++ b/backends/samsung/builders/op_hardswish.py @@ -26,10 +26,12 @@ def define_node( node: torch.fx.Node, enn_graph: EnnGraph, vals_to_ids: Dict[torch.Tensor, int], - ) -> None: + ) -> bool: input = node.args[0] input_id = self.define_tensor(input, enn_graph, vals_to_ids) params = {} self._update_params_qdtype(node, params) output_id = self.define_tensor(node, enn_graph, vals_to_ids) enn_graph.define_op(node.name, "HARDSWISH", [input_id], [output_id], params) + + return True diff --git a/backends/samsung/builders/op_hardtanh.py b/backends/samsung/builders/op_hardtanh.py index 7d65e97a566..4c60d7227dc 100644 --- a/backends/samsung/builders/op_hardtanh.py +++ b/backends/samsung/builders/op_hardtanh.py @@ -26,7 +26,7 @@ def define_node( node: torch.fx.Node, enn_graph: EnnGraph, vals_to_ids: Dict[torch.Tensor, int], - ) -> None: + ) -> bool: input = node.args[0] input_id = self.define_tensor(input, enn_graph, vals_to_ids) @@ -40,3 +40,5 @@ def define_node( output_id = self.define_tensor(node, enn_graph, vals_to_ids) enn_graph.define_op(node.name, "CLIP", [input_id], [output_id], params) + + return True diff --git a/backends/samsung/builders/op_index.py b/backends/samsung/builders/op_index.py index b7765e35b3f..0145616de56 100644 --- a/backends/samsung/builders/op_index.py +++ b/backends/samsung/builders/op_index.py @@ -23,7 +23,7 @@ def define_node( node: torch.fx.Node, enn_graph: EnnGraph, vals_to_ids: Dict[torch.Tensor, int], - ) -> None: + ) -> bool: input = node.args[0] input_id = self.define_tensor(input, enn_graph, vals_to_ids) @@ -47,3 +47,5 @@ def define_node( enn_graph.define_op( node.name, "GATHER", [input_id, indices_id], [output_id], params ) + + return True diff --git a/backends/samsung/builders/op_layer_norm.py b/backends/samsung/builders/op_layer_norm.py index 098bc92dc84..937168c36e9 100644 --- a/backends/samsung/builders/op_layer_norm.py +++ b/backends/samsung/builders/op_layer_norm.py @@ -25,7 +25,7 @@ def define_node( node: torch.fx.Node, enn_graph: EnnGraph, vals_to_ids: Dict[torch.Tensor, int], - ) -> None: + ) -> bool: all_input_tensors = [] input_node = node.args[0] input_id = self.define_tensor(input_node, enn_graph, vals_to_ids) @@ -51,3 +51,5 @@ def define_node( enn_graph.define_op( node.name, "LAYERNORM", all_input_tensors, [output_id], params ) + + return True diff --git a/backends/samsung/builders/op_leaky_relu.py b/backends/samsung/builders/op_leaky_relu.py index c7ed37d12e5..28f4a7851d4 100644 --- a/backends/samsung/builders/op_leaky_relu.py +++ b/backends/samsung/builders/op_leaky_relu.py @@ -27,7 +27,7 @@ def define_node( node: torch.fx.Node, enn_graph: EnnGraph, vals_to_ids: Dict[torch.Tensor, int], - ) -> None: + ) -> bool: all_input_tensors = [] input_id = self.define_tensor(node.args[0], enn_graph, vals_to_ids) all_input_tensors.append(input_id) @@ -56,3 +56,5 @@ def define_node( output_id = self.define_tensor(node, enn_graph, vals_to_ids) enn_graph.define_op(node.name, "PRELU", all_input_tensors, [output_id]) + + return True diff --git a/backends/samsung/builders/op_linear.py b/backends/samsung/builders/op_linear.py index 720439de976..dffb6b0108c 100644 --- a/backends/samsung/builders/op_linear.py +++ b/backends/samsung/builders/op_linear.py @@ -27,7 +27,7 @@ def define_node( node: torch.fx.Node, enn_graph: EnnGraph, vals_to_ids: Dict[torch.Tensor, int], - ) -> None: + ) -> bool: all_input_tensors = [] input = node.args[0] input_id = self.define_tensor(input, enn_graph, vals_to_ids) @@ -49,3 +49,5 @@ def define_node( output_id = self.define_tensor(node, enn_graph, vals_to_ids) enn_graph.define_op(node.name, "FC", all_input_tensors, [output_id], params) + + return True diff --git a/backends/samsung/builders/op_log.py b/backends/samsung/builders/op_log.py index 97127dd94ba..a20de9bd95d 100644 --- a/backends/samsung/builders/op_log.py +++ b/backends/samsung/builders/op_log.py @@ -23,10 +23,12 @@ def define_node( node: torch.fx.Node, enn_graph: EnnGraph, vals_to_ids: Dict[torch.Tensor, int], - ) -> None: + ) -> bool: input = node.args[0] input_id = self.define_tensor(input, enn_graph, vals_to_ids) output_id = self.define_tensor(node, enn_graph, vals_to_ids) enn_graph.define_op(node.name, "LOG", [input_id], [output_id]) + + return True diff --git a/backends/samsung/builders/op_log_softmax.py b/backends/samsung/builders/op_log_softmax.py index f2d87601cbb..f26a36e2af7 100644 --- a/backends/samsung/builders/op_log_softmax.py +++ b/backends/samsung/builders/op_log_softmax.py @@ -26,7 +26,7 @@ def define_node( node: torch.fx.Node, enn_graph: EnnGraph, vals_to_ids: Dict[torch.Tensor, int], - ): + ) -> bool: input = node.args[0] input_id = self.define_tensor(input, enn_graph, vals_to_ids) @@ -37,3 +37,5 @@ def define_node( meta_data = {"axis": axis} enn_graph.define_op(node.name, "LOGSOFTMAX", [input_id], [output_id], meta_data) + + return True diff --git a/backends/samsung/builders/op_max_pool2d.py b/backends/samsung/builders/op_max_pool2d.py index 57b716fcb34..bec8da5f683 100644 --- a/backends/samsung/builders/op_max_pool2d.py +++ b/backends/samsung/builders/op_max_pool2d.py @@ -25,7 +25,7 @@ def define_node( node: torch.fx.Node, enn_graph: EnnGraph, vals_to_ids: Dict[torch.Tensor, int], - ) -> None: + ) -> bool: input = node.args[0] input_id = self.define_tensor(input, enn_graph, vals_to_ids) @@ -75,10 +75,6 @@ def define_node( params["dilation_w"] = dilation[1] self._update_params_qdtype(node, params) - if len(node.args) > 5: - ceil_mode = cast(bool, node.args[5]) - assert not ceil_mode, "Not support ceil_mode = True." - if not is_indices: output_id = self.define_tensor( node, @@ -94,3 +90,5 @@ def define_node( ) enn_graph.define_op(node.name, "MAXPOOL2D", [input_id], [output_id], params) + + return True diff --git a/backends/samsung/builders/op_maximum.py b/backends/samsung/builders/op_maximum.py index d3358d736f3..d81dfdd4a35 100644 --- a/backends/samsung/builders/op_maximum.py +++ b/backends/samsung/builders/op_maximum.py @@ -26,7 +26,7 @@ def define_node( node: torch.fx.Node, enn_graph: EnnGraph, vals_to_ids: Dict[torch.Tensor, int], - ) -> None: + ) -> bool: # inputs input_id_1 = self.define_tensor(node.args[0], enn_graph, vals_to_ids) input_id_2 = self.define_tensor(node.args[1], enn_graph, vals_to_ids) @@ -35,3 +35,5 @@ def define_node( output_id = self.define_tensor(node, enn_graph, vals_to_ids) enn_graph.define_op(node.name, "MAXIMUM", [input_id_1, input_id_2], [output_id]) + + return True diff --git a/backends/samsung/builders/op_mean_dim.py b/backends/samsung/builders/op_mean_dim.py index 3d0377703a7..3396102c045 100644 --- a/backends/samsung/builders/op_mean_dim.py +++ b/backends/samsung/builders/op_mean_dim.py @@ -4,6 +4,7 @@ # This source code is licensed under the BSD-style license found in the # LICENSE file in the root directory of this source tree. +import logging from typing import cast, Dict, List import torch @@ -11,6 +12,7 @@ NodeVisitor, register_node_visitor, ) +from executorch.backends.samsung.builders.utils import get_tensor from executorch.backends.samsung.serialization.enn_graph_schema import EnnGraph from executorch.backends.transforms import get_shape @@ -27,7 +29,11 @@ def define_node( node: torch.fx.Node, enn_graph: EnnGraph, vals_to_ids: Dict[torch.Tensor, int], - ) -> None: + ) -> bool: + output_tensor = get_tensor(self.exported_program, node) + if output_tensor.dtype == torch.float64: + logging.warning("float64 for mean has not supported yet.") + return False # input input = node.args[0] input_id = self.define_tensor(input, enn_graph, vals_to_ids) @@ -37,6 +43,9 @@ def define_node( dims = cast(List[int], node.args[1]) reduce_axes = [] in_shape = get_shape(input) + if dims is None: + logging.warning("dims is None for this case.") + return False for dim in dims: reduce_axes.append(dim % len(in_shape)) @@ -47,3 +56,5 @@ def define_node( params = {"keep_dims": keep_dim, "axis": reduce_axes} self._update_params_qdtype(node, params) enn_graph.define_op(node.name, "REDUCEMEAN", [input_id], [output_id], params) + + return True diff --git a/backends/samsung/builders/op_minimum.py b/backends/samsung/builders/op_minimum.py index a32b462d45f..4c612df34c7 100644 --- a/backends/samsung/builders/op_minimum.py +++ b/backends/samsung/builders/op_minimum.py @@ -26,7 +26,7 @@ def define_node( node: torch.fx.Node, enn_graph: EnnGraph, vals_to_ids: Dict[torch.Tensor, int], - ) -> None: + ) -> bool: # inputs input1 = node.args[0] input_id_1 = self.define_tensor(input1, enn_graph, vals_to_ids) @@ -37,3 +37,5 @@ def define_node( output_id = self.define_tensor(node, enn_graph, vals_to_ids) enn_graph.define_op(node.name, "MIN", [input_id_1, input_id_2], [output_id]) + + return True diff --git a/backends/samsung/builders/op_mul.py b/backends/samsung/builders/op_mul.py index 6dd7c0dd9f0..e703ddff253 100644 --- a/backends/samsung/builders/op_mul.py +++ b/backends/samsung/builders/op_mul.py @@ -26,7 +26,7 @@ def define_node( node: torch.fx.Node, enn_graph: EnnGraph, vals_to_ids: Dict[torch.Tensor, int], - ) -> None: + ) -> bool: input1 = node.args[0] input_id_1 = self.define_tensor(input1, enn_graph, vals_to_ids) @@ -41,3 +41,5 @@ def define_node( enn_graph.define_op( node.name, "ELTMUL", [input_id_1, input_id_2], [output_id], params ) + + return True diff --git a/backends/samsung/builders/op_permute.py b/backends/samsung/builders/op_permute.py index 646eac4c06a..42286dddfef 100644 --- a/backends/samsung/builders/op_permute.py +++ b/backends/samsung/builders/op_permute.py @@ -25,13 +25,17 @@ def define_node( node: torch.fx.Node, enn_graph: EnnGraph, vals_to_ids: Dict[torch.Tensor, int], - ) -> None: + ) -> bool: input = node.args[0] input_id = self.define_tensor(input, enn_graph, vals_to_ids) # permutation permute_order = cast(List[int], node.args[1]) + # to prevent negative values + permute_order = [x % len(permute_order) for x in permute_order] params = {"perm": permute_order} output_id = self.define_tensor(node, enn_graph, vals_to_ids) enn_graph.define_op(node.name, "TRANSPOSE", [input_id], [output_id], params) + + return True diff --git a/backends/samsung/builders/op_pixel_shuffle.py b/backends/samsung/builders/op_pixel_shuffle.py index 28259299c81..db0aaaaef08 100644 --- a/backends/samsung/builders/op_pixel_shuffle.py +++ b/backends/samsung/builders/op_pixel_shuffle.py @@ -25,7 +25,7 @@ def define_node( node: torch.fx.Node, enn_graph: EnnGraph, vals_to_ids: Dict[torch.Tensor, int], - ) -> None: + ) -> bool: input_id = self.define_tensor(node.args[0], enn_graph, vals_to_ids) scale_factor = cast(int, node.args[1]) @@ -36,3 +36,5 @@ def define_node( enn_graph.define_op( node.name, "DEPTH_TO_SPACE", [input_id], [output_id], params ) + + return True diff --git a/backends/samsung/builders/op_placeholder.py b/backends/samsung/builders/op_placeholder.py index b4b606f56ea..8c6a89a5eb5 100644 --- a/backends/samsung/builders/op_placeholder.py +++ b/backends/samsung/builders/op_placeholder.py @@ -31,7 +31,9 @@ def define_node( node: torch.fx.Node, enn_graph: EnnGraph, vals_to_ids: Dict[torch.Tensor, int], - ) -> None: + ) -> bool: if is_param_node(self.exported_program, node): return self.define_tensor(node, enn_graph, vals_to_ids) + + return True diff --git a/backends/samsung/builders/op_pow.py b/backends/samsung/builders/op_pow.py index cd6ec7f81ef..d417685126e 100644 --- a/backends/samsung/builders/op_pow.py +++ b/backends/samsung/builders/op_pow.py @@ -4,6 +4,7 @@ # This source code is licensed under the BSD-style license found in the # LICENSE file in the root directory of this source tree. +import logging from typing import Dict import torch @@ -24,15 +25,17 @@ def define_node( node: torch.fx.Node, enn_graph: EnnGraph, vals_to_ids: Dict[torch.Tensor, int], - ) -> None: + ) -> bool: input1 = node.args[0] input2 = node.args[1] input_tensor_1 = get_tensor(self.exported_program, input1) input_tensor_2 = get_tensor(self.exported_program, input2) - assert ( - input_tensor_1.dtype == torch.float32 - and input_tensor_2.dtype == torch.float32 - ), "Requires the two inputs are all float type" + if ( + input_tensor_1.dtype != torch.float32 + or input_tensor_2.dtype != torch.float32 + ): + logging.warning("Requires the two inputs are all float type.") + return False input_id_1 = self.define_tensor(input1, enn_graph, vals_to_ids) input_id_2 = self.define_tensor(input2, enn_graph, vals_to_ids) @@ -40,3 +43,5 @@ def define_node( output_id = self.define_tensor(node, enn_graph, vals_to_ids) enn_graph.define_op(node.name, "POW", [input_id_1, input_id_2], [output_id]) + + return True diff --git a/backends/samsung/builders/op_quantize.py b/backends/samsung/builders/op_quantize.py index dcf30e291f9..771ab419888 100644 --- a/backends/samsung/builders/op_quantize.py +++ b/backends/samsung/builders/op_quantize.py @@ -24,32 +24,39 @@ def define_node( node: torch.fx.Node, enn_graph: EnnGraph, vals_to_ids: Dict[torch.Tensor, int], - ) -> None: + ) -> bool: # input input = node.args[0] input_id = self.define_tensor(input, enn_graph, vals_to_ids) scales = node.args[1] - if isinstance(scales, torch.Tensor): - scales = scales.tolist() - elif not isinstance(scales, list): - scales = torch.tensor(scales).reshape([1]).tolist() zero_points = node.args[2] - if isinstance(zero_points, torch.Tensor): - zero_points = zero_points.tolist() - elif not isinstance(zero_points, list): - zero_points = torch.tensor(zero_points).reshape([1]).tolist() + if not isinstance(scales, torch.fx.Node) and not isinstance( + zero_points, torch.fx.Node + ): + if isinstance(scales, torch.Tensor): + scales = scales.tolist() + elif not isinstance(scales, list): + scales = torch.tensor(scales).reshape([1]).tolist() + if isinstance(zero_points, torch.Tensor): + zero_points = zero_points.tolist() + elif not isinstance(zero_points, list): + zero_points = torch.tensor(zero_points).reshape([1]).tolist() - output_id = self.define_tensor(node, enn_graph, vals_to_ids) + output_id = self.define_tensor(node, enn_graph, vals_to_ids) - params = {"scales": scales, "zero_points": zero_points} + params = {"scales": scales, "zero_points": zero_points} - if node.target in QuantConstants.QUANT_OPS_KEY_MAP: - enn_graph.define_op(node.name, "QUANTIZE", [input_id], [output_id], params) - else: - enn_graph.define_op( - node.name, "DEQUANTIZE", [input_id], [output_id], params - ) + if node.target in QuantConstants.QUANT_OPS_KEY_MAP: + enn_graph.define_op( + node.name, "QUANTIZE", [input_id], [output_id], params + ) + else: + enn_graph.define_op( + node.name, "DEQUANTIZE", [input_id], [output_id], params + ) + + return True @register_node_visitor diff --git a/backends/samsung/builders/op_relu.py b/backends/samsung/builders/op_relu.py index a4a2b6bc4f0..fb2668e46fc 100644 --- a/backends/samsung/builders/op_relu.py +++ b/backends/samsung/builders/op_relu.py @@ -26,7 +26,7 @@ def define_node( node: torch.fx.Node, enn_graph: EnnGraph, vals_to_ids: Dict[torch.Tensor, int], - ) -> None: + ) -> bool: input = node.args[0] input_id = self.define_tensor(input, enn_graph, vals_to_ids) @@ -35,3 +35,5 @@ def define_node( self._update_params_qdtype(node, params) enn_graph.define_op(node.name, "RELU", [input_id], [output_id], params) + + return True diff --git a/backends/samsung/builders/op_reshape.py b/backends/samsung/builders/op_reshape.py index 1f4e85ac059..bb413ed793d 100644 --- a/backends/samsung/builders/op_reshape.py +++ b/backends/samsung/builders/op_reshape.py @@ -7,6 +7,7 @@ NodeVisitor, register_node_visitor, ) +from executorch.backends.samsung.builders.utils import get_tensor from executorch.backends.samsung.serialization.enn_graph_schema import EnnGraph @@ -22,13 +23,18 @@ def define_node( node: torch.fx.Node, enn_graph: EnnGraph, vals_to_ids: Dict[torch.Tensor, int], - ) -> None: + ) -> bool: input = node.args[0] input_id = self.define_tensor(input, enn_graph, vals_to_ids) output_id = self.define_tensor(node, enn_graph, vals_to_ids) - new_shape = node.args[1] + # node.args[1] may contain "sym_size" + tensor = get_tensor(self.exported_program, node) + shape = [1] if len(tensor.size()) == 0 else list(tensor.size()) + enn_graph.define_op( - node.name, "RESHAPE", [input_id], [output_id], {"new_shape": new_shape} + node.name, "RESHAPE", [input_id], [output_id], {"new_shape": shape} ) + + return True diff --git a/backends/samsung/builders/op_rms_norm.py b/backends/samsung/builders/op_rms_norm.py index 6a58d62a5ce..0ff01701d1b 100644 --- a/backends/samsung/builders/op_rms_norm.py +++ b/backends/samsung/builders/op_rms_norm.py @@ -24,7 +24,7 @@ def define_node( node: torch.fx.Node, enn_graph: EnnGraph, vals_to_ids: Dict[torch.Tensor, int], - ) -> None: + ) -> bool: # args of node : ['input', 'normalized_shape', 'weight', 'eps'] input = node.args[0] input_id = self.define_tensor(input, enn_graph, vals_to_ids) @@ -50,3 +50,5 @@ def define_node( enn_graph.define_op( node.name, "RMSNORM", [input_id, gamma_id], [output_id], params ) + + return True diff --git a/backends/samsung/builders/op_rsqrt.py b/backends/samsung/builders/op_rsqrt.py index b3600d41ee2..55e9ddf54f9 100644 --- a/backends/samsung/builders/op_rsqrt.py +++ b/backends/samsung/builders/op_rsqrt.py @@ -26,10 +26,12 @@ def define_node( node: torch.fx.Node, enn_graph: EnnGraph, vals_to_ids: Dict[torch.Tensor, int], - ) -> None: + ) -> bool: input = node.args[0] input_id = self.define_tensor(input, enn_graph, vals_to_ids) output_id = self.define_tensor(node, enn_graph, vals_to_ids) enn_graph.define_op(node.name, "RSQRT", [input_id], [output_id]) + + return True diff --git a/backends/samsung/builders/op_select.py b/backends/samsung/builders/op_select.py index 26f455b2548..3f3550d3cf8 100644 --- a/backends/samsung/builders/op_select.py +++ b/backends/samsung/builders/op_select.py @@ -26,7 +26,7 @@ def define_node( node: torch.fx.Node, enn_graph: EnnGraph, vals_to_ids: Dict[torch.Tensor, int], - ): + ) -> bool: input = node.args[0] input_id = self.define_tensor(input, enn_graph, vals_to_ids) @@ -50,3 +50,5 @@ def define_node( } enn_graph.define_op(node.name, "STRIDEDSLICE", [input_id], [output_id], params) + + return True diff --git a/backends/samsung/builders/op_sigmoid.py b/backends/samsung/builders/op_sigmoid.py index e87973f9a85..aef9d90ec52 100644 --- a/backends/samsung/builders/op_sigmoid.py +++ b/backends/samsung/builders/op_sigmoid.py @@ -23,10 +23,12 @@ def define_node( node: torch.fx.Node, enn_graph: EnnGraph, vals_to_ids: Dict[torch.Tensor, int], - ) -> None: + ) -> bool: input = node.args[0] input_id = self.define_tensor(input, enn_graph, vals_to_ids) output_id = self.define_tensor(node, enn_graph, vals_to_ids) enn_graph.define_op(node.name, "SIGMOID", [input_id], [output_id]) + + return True diff --git a/backends/samsung/builders/op_sin.py b/backends/samsung/builders/op_sin.py index 5fd22e8275e..11dc6bccb46 100644 --- a/backends/samsung/builders/op_sin.py +++ b/backends/samsung/builders/op_sin.py @@ -23,9 +23,11 @@ def define_node( node: torch.fx.Node, enn_graph: EnnGraph, vals_to_ids: Dict[torch.Tensor, int], - ) -> None: + ) -> bool: input_id = self.define_tensor(node.args[0], enn_graph, vals_to_ids) output_id = self.define_tensor(node, enn_graph, vals_to_ids) enn_graph.define_op(node.name, "Sin", [input_id], [output_id]) + + return True diff --git a/backends/samsung/builders/op_skip.py b/backends/samsung/builders/op_skip.py new file mode 100644 index 00000000000..3413e603983 --- /dev/null +++ b/backends/samsung/builders/op_skip.py @@ -0,0 +1,32 @@ +# Copyright (c) 2026 Samsung Electronics Co. LTD +# All rights reserved +# +# This source code is licensed under the BSD-style license found in the +# LICENSE file in the root directory of this source tree. +from typing import Dict + +import torch +from executorch.backends.samsung.builders.node_visitor import ( + NodeVisitor, + register_node_visitor, +) +from executorch.backends.samsung.serialization.enn_graph_schema import EnnGraph + + +@register_node_visitor +class OpSkipVisitor(NodeVisitor): + target = ["sym_size.int", "add", "floordiv"] + """ + do nothing + """ + + def __init__(self, *args) -> None: + super().__init__(*args) + + def define_node( + self, + node: torch.fx.Node, + enn_graph: EnnGraph, + vals_to_ids: Dict[torch.Tensor, int], + ) -> bool: + return True diff --git a/backends/samsung/builders/op_slice_copy.py b/backends/samsung/builders/op_slice_copy.py index e85b6bf60c3..4b837db1b12 100644 --- a/backends/samsung/builders/op_slice_copy.py +++ b/backends/samsung/builders/op_slice_copy.py @@ -27,7 +27,7 @@ def define_node( node: torch.fx.Node, enn_graph: EnnGraph, vals_to_ids: Dict[torch.Tensor, int], - ): + ) -> bool: input = node.args[0] input_id = self.define_tensor(input, enn_graph, vals_to_ids) @@ -61,3 +61,5 @@ def define_node( params = {"begin": begin, "end": end, "strides": strides} enn_graph.define_op(node.name, "STRIDEDSLICE", [input_id], [output_id], params) + + return True diff --git a/backends/samsung/builders/op_softmax.py b/backends/samsung/builders/op_softmax.py index 7f569cea6fc..e96b5638db3 100644 --- a/backends/samsung/builders/op_softmax.py +++ b/backends/samsung/builders/op_softmax.py @@ -26,7 +26,7 @@ def define_node( node: torch.fx.Node, enn_graph: EnnGraph, vals_to_ids: Dict[torch.Tensor, int], - ): + ) -> bool: input = node.args[0] input_id = self.define_tensor(input, enn_graph, vals_to_ids) @@ -37,3 +37,5 @@ def define_node( params = {"axis": axis} self._update_params_qdtype(node, params) enn_graph.define_op(node.name, "SOFTMAX", [input_id], [output_id], params) + + return True diff --git a/backends/samsung/builders/op_split_with_sizes_copy.py b/backends/samsung/builders/op_split_with_sizes_copy.py index b67b5331627..48612ba9a6d 100644 --- a/backends/samsung/builders/op_split_with_sizes_copy.py +++ b/backends/samsung/builders/op_split_with_sizes_copy.py @@ -23,7 +23,7 @@ def define_node( node: torch.fx.Node, enn_graph: EnnGraph, vals_to_ids: Dict[torch.Tensor, int], - ) -> None: + ) -> bool: input = node.args[0] input_id = self.define_tensor(input, enn_graph, vals_to_ids) @@ -39,6 +39,10 @@ def define_node( ) all_output_tensors.append(output_id) + for user in node.users.keys(): + if user.target.__name__ == "getitem" and len(user.args) > 1: + vals_to_ids[user] = all_output_tensors[user.args[1]] + axis = node.args[2] if len(node.args) > 2 else 0 params = {} @@ -46,3 +50,5 @@ def define_node( params["point"] = node.args[1] enn_graph.define_op(node.name, "SPLIT", [input_id], all_output_tensors, params) + + return True diff --git a/backends/samsung/builders/op_sqrt.py b/backends/samsung/builders/op_sqrt.py index 3560542a0bc..77793e4589d 100644 --- a/backends/samsung/builders/op_sqrt.py +++ b/backends/samsung/builders/op_sqrt.py @@ -26,10 +26,12 @@ def define_node( node: torch.fx.Node, enn_graph: EnnGraph, vals_to_ids: Dict[torch.Tensor, int], - ) -> None: + ) -> bool: input = node.args[0] input_id = self.define_tensor(input, enn_graph, vals_to_ids) output_id = self.define_tensor(node, enn_graph, vals_to_ids) enn_graph.define_op(node.name, "SQRT", [input_id], [output_id]) + + return True diff --git a/backends/samsung/builders/op_squeeze.py b/backends/samsung/builders/op_squeeze.py index 82fa17fbc95..ff5286fee28 100644 --- a/backends/samsung/builders/op_squeeze.py +++ b/backends/samsung/builders/op_squeeze.py @@ -26,7 +26,7 @@ def define_node( node: torch.fx.Node, enn_graph: EnnGraph, vals_to_ids: Dict[torch.Tensor, int], - ) -> None: + ) -> bool: input = node.args[0] input_id = self.define_tensor(input, enn_graph, vals_to_ids) @@ -35,3 +35,5 @@ def define_node( params = {"new_shape": [*node.meta["val"].shape]} enn_graph.define_op(node.name, "RESHAPE", [input_id], [output_id], params) + + return True diff --git a/backends/samsung/builders/op_sub.py b/backends/samsung/builders/op_sub.py index 7dc97bfa7ca..c12f4c85f4d 100644 --- a/backends/samsung/builders/op_sub.py +++ b/backends/samsung/builders/op_sub.py @@ -4,6 +4,7 @@ # This source code is licensed under the BSD-style license found in the # LICENSE file in the root directory of this source tree. +import logging from typing import Dict import torch @@ -26,12 +27,16 @@ def define_node( node: torch.fx.Node, enn_graph: EnnGraph, vals_to_ids: Dict[torch.Tensor, int], - ) -> None: + ) -> bool: # inputs input1 = node.args[0] input_id_1 = self.define_tensor(input1, enn_graph, vals_to_ids) input2 = node.args[1] input_id_2 = self.define_tensor(input2, enn_graph, vals_to_ids) + alpha = node.kwargs.get("alpha", 1.0) + if alpha != 1.0: + logging.warning("Currently, only alpha 1 for sub is supported.") + return False # output output_id = self.define_tensor(node, enn_graph, vals_to_ids) @@ -41,3 +46,5 @@ def define_node( enn_graph.define_op( node.name, "SUB", [input_id_1, input_id_2], [output_id], params ) + + return True diff --git a/backends/samsung/builders/op_sum_int_list.py b/backends/samsung/builders/op_sum_int_list.py index 7743e6632dd..a8c5367371c 100644 --- a/backends/samsung/builders/op_sum_int_list.py +++ b/backends/samsung/builders/op_sum_int_list.py @@ -24,7 +24,7 @@ def define_node( node: torch.fx.Node, enn_graph: EnnGraph, vals_to_ids: Dict[torch.Tensor, int], - ) -> None: + ) -> bool: input = node.args[0] input_id = self.define_tensor(input, enn_graph, vals_to_ids) @@ -37,3 +37,5 @@ def define_node( output_id = self.define_tensor(node, enn_graph, vals_to_ids) enn_graph.define_op(node.name, "REDUCESUM", [input_id], [output_id], params) + + return True diff --git a/backends/samsung/builders/op_tanh.py b/backends/samsung/builders/op_tanh.py index 5b002890075..106256c791b 100644 --- a/backends/samsung/builders/op_tanh.py +++ b/backends/samsung/builders/op_tanh.py @@ -23,10 +23,12 @@ def define_node( node: torch.fx.Node, enn_graph: EnnGraph, vals_to_ids: Dict[torch.Tensor, int], - ) -> None: + ) -> bool: input = node.args[0] input_id = self.define_tensor(input, enn_graph, vals_to_ids) output_id = self.define_tensor(node, enn_graph, vals_to_ids) enn_graph.define_op(node.name, "TANH", [input_id], [output_id]) + + return True diff --git a/backends/samsung/builders/op_to_copy.py b/backends/samsung/builders/op_to_copy.py index c770602bb5f..143ab007cba 100644 --- a/backends/samsung/builders/op_to_copy.py +++ b/backends/samsung/builders/op_to_copy.py @@ -28,7 +28,7 @@ def define_node( node: torch.fx.Node, enn_graph: EnnGraph, vals_to_ids: Dict[torch.Tensor, int], - ) -> None: + ) -> bool: memory_format_target = node.kwargs.get("memory_format", torch.contiguous_format) to_contiguous = bool(memory_format_target == torch.contiguous_format) assert to_contiguous, "Don't support other param in _to_copy" @@ -42,3 +42,5 @@ def define_node( params["out_dtype"] = get_map_dtype(out_tensor.dtype) enn_graph.define_op(node.name, "CAST", [input_id], [output_id], params) + + return True diff --git a/backends/samsung/builders/op_topk.py b/backends/samsung/builders/op_topk.py index e4cda0ef148..6a4de9ccc91 100644 --- a/backends/samsung/builders/op_topk.py +++ b/backends/samsung/builders/op_topk.py @@ -24,7 +24,7 @@ def define_node( node: torch.fx.Node, enn_graph: EnnGraph, vals_to_ids: Dict[torch.Tensor, int], - ) -> None: + ) -> bool: input = node.args[0] input_id = self.define_tensor(input, enn_graph, vals_to_ids) @@ -72,3 +72,5 @@ def define_node( raise AssertionError("Not supported sorted = False.") enn_graph.define_op(node.name, "TopK", [input_id], all_output_tensors, params) + + return True diff --git a/backends/samsung/builders/op_unsqueeze.py b/backends/samsung/builders/op_unsqueeze.py index 61fa06e6310..18f7c2d33b2 100644 --- a/backends/samsung/builders/op_unsqueeze.py +++ b/backends/samsung/builders/op_unsqueeze.py @@ -25,7 +25,7 @@ def define_node( node: torch.fx.Node, enn_graph: EnnGraph, vals_to_ids: Dict[torch.Tensor, int], - ) -> None: + ) -> bool: input = node.args[0] input_id = self.define_tensor(input, enn_graph, vals_to_ids) @@ -33,3 +33,5 @@ def define_node( params = {"new_shape": [*node.meta["val"].shape]} enn_graph.define_op(node.name, "RESHAPE", [input_id], [output_id], params) + + return True diff --git a/backends/samsung/builders/op_upsample_bilinear2d.py b/backends/samsung/builders/op_upsample_bilinear2d.py index d4b040460e3..7374e687101 100644 --- a/backends/samsung/builders/op_upsample_bilinear2d.py +++ b/backends/samsung/builders/op_upsample_bilinear2d.py @@ -4,6 +4,7 @@ # This source code is licensed under the BSD-style license found in the # LICENSE file in the root directory of this source tree. +import logging from typing import cast, Dict, List import torch @@ -27,11 +28,14 @@ def define_node( node: torch.fx.Node, enn_graph: EnnGraph, vals_to_ids: Dict[torch.Tensor, int], - ) -> None: + ) -> bool: input = node.args[0] input_id = self.define_tensor(input, enn_graph, vals_to_ids) in_shape = get_shape(input) output_size = cast(List[int], node.args[1]) + if output_size is None: + logging.warning("output is None for this case.") + return False scale_factor = [ output_size[0] * 1.0 / in_shape[-2], output_size[1] * 1.0 / in_shape[-1], @@ -44,10 +48,12 @@ def define_node( params = { "align_corners": align_corners, "upsampling_factor": scale_factor, - "half_pixel_centers": True, + "half_pixel_centers": not align_corners, } self._update_params_qdtype(node, params) output_id = self.define_tensor(node, enn_graph, vals_to_ids) enn_graph.define_op( node.name, "RESIZE_BILINEAR", [input_id], [output_id], params ) + + return True diff --git a/backends/samsung/builders/op_upsample_nearest2d.py b/backends/samsung/builders/op_upsample_nearest2d.py index 9859cd8f07e..6af5402d56c 100644 --- a/backends/samsung/builders/op_upsample_nearest2d.py +++ b/backends/samsung/builders/op_upsample_nearest2d.py @@ -4,6 +4,7 @@ # This source code is licensed under the BSD-style license found in the # LICENSE file in the root directory of this source tree. +import logging from typing import cast, Dict, List import torch @@ -27,11 +28,14 @@ def define_node( node: torch.fx.Node, enn_graph: EnnGraph, vals_to_ids: Dict[torch.Tensor, int], - ) -> None: + ) -> bool: input = node.args[0] input_id = self.define_tensor(input, enn_graph, vals_to_ids) in_shape = get_shape(input) output_size = cast(List[int], node.args[1]) + if output_size is None: + logging.warning("output is None for this case.") + return False scale_factor = [ output_size[0] * 1.0 / in_shape[-2], output_size[1] * 1.0 / in_shape[-1], @@ -50,3 +54,5 @@ def define_node( enn_graph.define_op( node.name, "RESIZE_NEAREST_NEIGHBOR", [input_id], [output_id], params ) + + return True diff --git a/backends/samsung/partition/enn_partitioner.py b/backends/samsung/partition/enn_partitioner.py index 91f496e7a5c..3e450cdf1bd 100644 --- a/backends/samsung/partition/enn_partitioner.py +++ b/backends/samsung/partition/enn_partitioner.py @@ -15,6 +15,7 @@ from executorch.backends.samsung.serialization.compile_options import ( ENN_COMPILE_OPTION_TITLE, ) +from executorch.backends.samsung.serialization.enn_graph_schema import EnnGraph from executorch.backends.samsung.utils.utils import get_compile_spec from executorch.exir.backend.backend_details import CompileSpec from executorch.exir.backend.canonical_partitioners.pattern_op_partitioner import ( @@ -70,9 +71,16 @@ def is_node_supported(self, _, node: torch.fx.Node) -> bool: ]: return False - if node.target in SUPPORTED_OPS or node.target.__name__ in self.node_visitors: + if node.target in SUPPORTED_OPS: return True + if node.target.__name__ in self.node_visitors: + enn_graph = EnnGraph() + vals_to_ids: Dict[torch.fx.Node, int] = {} + return self.node_visitors[node.target.__name__].define_node( + node, enn_graph, vals_to_ids + ) + supported = self.enn_wrapper.IsNodeSupportedByBackend() return supported @@ -91,10 +99,19 @@ def generate_partitions( self, edge_program: torch.export.ExportedProgram ) -> List[Any]: self.op_support_checker = EnnOperatorSupport(edge_program, self.compile_specs) - return generate_partitions_from_list_of_nodes( + partition_list = generate_partitions_from_list_of_nodes( edge_program.graph_module, op_support=self.op_support_checker, ) + if len(partition_list) == 1 and partition_list[0].size() == 1: + first_node = list(partition_list[0].nodes.keys())[0] + # If there is only one partition graph containing a single "aten.clone.default" that is a useless operation, + # the RemoveUselessOpPass will remove this operation and cause a graph error. + # Therefore, we delete this node to prevent this graph error. + # For example, in the test_index_put_in_place_dtype case partition_list is [{aten_clone_default: 2}] + if first_node.target == exir_ops.edge.aten.clone.default: + del partition_list[0] + return partition_list def tag_nodes(self, partitions: List[Partition]) -> None: partition_tags: Dict[str, DelegationSpec] = {} @@ -127,8 +144,6 @@ def ops_to_not_decompose( torch.ops.aten.max_pool2d.default, torch.ops.aten.linear.default, torch.ops.aten._safe_softmax.default, - torch.ops.aten.upsample_bilinear2d.vec, - torch.ops.aten.upsample_nearest2d.vec, torch.ops.aten.prelu.default, torch.ops.aten.layer_norm.default, torch.ops.aten.pixel_shuffle.default, diff --git a/backends/samsung/test/tester/samsung_tester.py b/backends/samsung/test/tester/samsung_tester.py index 258aef191d0..001b7e3bb0d 100644 --- a/backends/samsung/test/tester/samsung_tester.py +++ b/backends/samsung/test/tester/samsung_tester.py @@ -11,8 +11,12 @@ import torch from executorch.backends.samsung.partition.enn_partitioner import EnnPartitioner from executorch.backends.samsung.quantizer.quantizer import EnnQuantizer, Precision +from executorch.backends.samsung.serialization.compile_options import ( + gen_samsung_backend_compile_spec, +) from executorch.backends.samsung.test.utils import RuntimeExecutor from executorch.backends.samsung.test.utils.quant_checkers import get_checker +from executorch.backends.samsung.test.utils.utils import TestConfig from executorch.backends.samsung.utils.export_utils import get_edge_compile_config from executorch.backends.test.harness import Tester as TesterBase from executorch.backends.test.harness.stages import StageType @@ -112,6 +116,7 @@ def run( transform_passes=self.transform_passes, partitioner=self.partitioners, compile_config=self.edge_compile_config, + generate_etrecord=generate_etrecord, ) @@ -145,6 +150,8 @@ def __init__( self.original_module = module self.exported_module = module self.example_inputs = example_inputs + if compile_specs is None: + compile_specs = [gen_samsung_backend_compile_spec(TestConfig.chipset)] self.compile_specs = compile_specs def quantize( @@ -167,9 +174,12 @@ def quantize( def to_edge_transform_and_lower( self, edge_compile_config: Optional[EdgeCompileConfig] = None, + generate_etrecord: bool = False, ): to_edge_transform_and_lower_stage = ToEdgeTransformAndLower( self.compile_specs, edge_compile_config ) - return super().to_edge_transform_and_lower(to_edge_transform_and_lower_stage) + return super().to_edge_transform_and_lower( + to_edge_transform_and_lower_stage, generate_etrecord + ) diff --git a/backends/samsung/test/utils/runtime_executor.py b/backends/samsung/test/utils/runtime_executor.py index 9bc274799d7..2d58444ad99 100644 --- a/backends/samsung/test/utils/runtime_executor.py +++ b/backends/samsung/test/utils/runtime_executor.py @@ -157,7 +157,7 @@ def run_on_device(self) -> Tuple[torch.Tensor]: output_tensor = ( torch.from_numpy(output_array) .view(dtype=model_outputs[idx].dtype) - .view(*model_outputs[idx].shape) + .reshape(model_outputs[idx].shape) ) result.append(output_tensor) diff --git a/backends/samsung/utils/export_utils.py b/backends/samsung/utils/export_utils.py index 22f1833bd18..86512b75dec 100644 --- a/backends/samsung/utils/export_utils.py +++ b/backends/samsung/utils/export_utils.py @@ -37,6 +37,11 @@ def get_edge_compile_config(): exir_ops.edge.aten.layer_norm.default, exir_ops.edge.aten.matmul.default, exir_ops.edge.aten.hardsigmoid.default, + exir_ops.edge.aten.round.decimals, + exir_ops.edge.aten.median.dim, + exir_ops.edge.aten.median.default, + exir_ops.edge.aten.adaptive_max_pool2d.default, + exir_ops.edge.aten.adaptive_max_pool3d.default, ], ) diff --git a/backends/test/suite/flow.py b/backends/test/suite/flow.py index 547c79326e6..7331adc4a49 100644 --- a/backends/test/suite/flow.py +++ b/backends/test/suite/flow.py @@ -227,4 +227,17 @@ def all_flows() -> dict[str, TestFlow]: except Exception as e: logger.info(f"Skipping MLX flow registration: {e}") + try: + from executorch.backends.test.suite.flows.samsung import ( + SAMSUNG_A8W8_TEST_FLOW, + SAMSUNG_TEST_FLOW, + ) + + flows += [ + SAMSUNG_TEST_FLOW, + SAMSUNG_A8W8_TEST_FLOW, + ] + except Exception as e: + logger.info(f"Skipping SAMSUNG flow registration: {e}") + return {f.name: f for f in flows if f is not None} diff --git a/backends/test/suite/flows/samsung.py b/backends/test/suite/flows/samsung.py new file mode 100644 index 00000000000..b43a1f770c2 --- /dev/null +++ b/backends/test/suite/flows/samsung.py @@ -0,0 +1,41 @@ +import logging + +from executorch.backends.samsung.quantizer.quantizer import EnnQuantizer, Precision +from executorch.backends.samsung.test.tester.samsung_tester import SamsungTester +from executorch.backends.test.harness.stages import Quantize +from executorch.backends.test.suite.flow import TestFlow + +logger = logging.getLogger(__name__) +logger.setLevel(logging.INFO) + + +def _create_samsung_flow( + name: str, + quantize: bool = False, + quant_dtype: Precision | None = None, + is_per_channel: bool = True, + is_qat: bool = False, +) -> TestFlow: + if quantize and quant_dtype is None: + raise RuntimeError("Quant dtype must be provided when quantize is true.") + + def create_quantize_stage() -> Quantize: + quantizer = EnnQuantizer() + quantizer.setup_quant_params(quant_dtype, is_per_channel, is_qat) + return Quantize(quantizer=quantizer) + + return TestFlow( + name, + backend="samsung", + tester_factory=SamsungTester, + quantize=quantize, + quantize_stage_factory=create_quantize_stage if quantize else None, + supports_serialize=False, + ) + + +SAMSUNG_TEST_FLOW = _create_samsung_flow("samsung") + +SAMSUNG_A8W8_TEST_FLOW = _create_samsung_flow( + "samsung_a8w8", quantize=True, quant_dtype=Precision.A8W8 +) diff --git a/backends/test/suite/models/test_torchaudio.py b/backends/test/suite/models/test_torchaudio.py index 2287b226c37..b6879162297 100644 --- a/backends/test/suite/models/test_torchaudio.py +++ b/backends/test/suite/models/test_torchaudio.py @@ -62,7 +62,7 @@ def test_conformer(test_runner, dtype: torch.dtype, use_dynamic_shapes: bool): encoder_padding_mask, ) - test_runner.lower_and_run_model(model, inputs) + test_runner.lower_and_run_model(model, inputs, generate_random_test_inputs=False) @pytest.mark.parametrize("dtype", [torch.float32], ids=dtype_to_str) diff --git a/examples/samsung/executor_runner/enn_executor_runner.cpp b/examples/samsung/executor_runner/enn_executor_runner.cpp index bdbff74088c..82a34411ef8 100644 --- a/examples/samsung/executor_runner/enn_executor_runner.cpp +++ b/examples/samsung/executor_runner/enn_executor_runner.cpp @@ -304,23 +304,12 @@ int main(int argc, char** argv) { status = method->execute(); } - // Run the model. - ET_LOG(Info, "Start 1st inference."); - auto before_exec = std::chrono::high_resolution_clock::now(); - status = method->execute(); - auto after_exec = std::chrono::high_resolution_clock::now(); - double interval_1st_infs = - std::chrono::duration_cast( - after_exec - before_exec) - .count() / - 1000.0; - ET_LOG(Info, "Start inference."); - before_exec = std::chrono::high_resolution_clock::now(); + auto before_exec = std::chrono::high_resolution_clock::now(); for (int i = 0; i < FLAGS_num_executions; ++i) { status = method->execute(); } - after_exec = std::chrono::high_resolution_clock::now(); + auto after_exec = std::chrono::high_resolution_clock::now(); double interval_infs = std::chrono::duration_cast( after_exec - before_exec) .count() / @@ -331,11 +320,8 @@ int main(int argc, char** argv) { std::ofstream fout(output_file_name); fout << "init: " + std::to_string(interval_init) << "\nload: " + std::to_string(interval_load) - << "\n1st: " + std::to_string(interval_1st_infs) << "\navg: " + - std::to_string( - (interval_infs + interval_1st_infs) / - ((float)FLAGS_num_executions + 1.f)) + std::to_string(interval_infs / (float)FLAGS_num_executions) << std::endl; fout.close(); }