-
Notifications
You must be signed in to change notification settings - Fork 873
feat: added checks for support of activations on target #18102
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,6 +4,7 @@ | |
| # LICENSE file in the root directory of this source tree. | ||
|
|
||
| from abc import ABC, abstractmethod | ||
| from typing import Callable | ||
|
|
||
| import torch | ||
|
|
||
|
|
@@ -181,6 +182,33 @@ def _has_shared_q_params_if_quantized(node: Node) -> bool: | |
| # Node not quantized | ||
| return True | ||
|
|
||
| @staticmethod | ||
| def is_node_alone_in_partition( | ||
| node: Node, partition_list: list[Partition], filter_fn: Callable | ||
| ): | ||
| """Return True if `node` is the only node in its partition for which `filter_fn` | ||
| returns True. | ||
|
|
||
| The function finds the unique partition containing `node` and applies | ||
| `filter_fn` to all nodes in that partition. If only one node passes the | ||
| predicate — and that node is `node` — the function returns True. | ||
|
|
||
| :param node: The torch.Node to check. | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: |
||
| :param partition_list: List of proposed partitions. | ||
| :param filter_fn: Predicate applied to nodes in the partition. | ||
| `node` is considered alone if it is the only node | ||
| for which this predicate returns True. | ||
| """ | ||
| partitions = [p for p in partition_list if node in p.nodes] | ||
| if len(partitions) != 1: | ||
| return False # Should never happen | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When this code was inside the |
||
|
|
||
| partition = partitions[0] | ||
| filtered_partition_nodes = list(filter(filter_fn, partition.nodes)) | ||
| return ( | ||
| len(filtered_partition_nodes) == 1 and filtered_partition_nodes[0] == node | ||
| ) | ||
|
Comment on lines
+185
to
+210
|
||
|
|
||
| def assert_convertible(self, node): | ||
| """Assert that the call `is_supported()` returns `True`. Otherwise, raise an exception and print an | ||
| error message. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,6 +12,9 @@ | |
| from executorch.backends.nxp.backend.ir.lib.tflite.BuiltinOperator import ( | ||
| BuiltinOperator, | ||
| ) | ||
| from executorch.backends.nxp.backend.neutron_operator_support import ( | ||
| activation_supported_on_target, | ||
| ) | ||
| from executorch.backends.nxp.backend.neutron_target_spec import NeutronTargetSpec | ||
| from torch.fx import Node | ||
| from torch.fx.passes.infra.partitioner import Partition | ||
|
|
@@ -77,18 +80,11 @@ def supports_partitioning_result( | |
| bounds = cls._get_clamp_bounds(node) | ||
|
|
||
| if bounds in [cls.SUPPORTED_BOUNDS["Relu"], cls.SUPPORTED_BOUNDS["Relu6"]]: | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: A comment explaining why only |
||
| # If this is the only operator in the partition, NeutronConverter will not create a NeutronNode for some | ||
| # reason. | ||
| clamp_partitions = [p for p in partition_list if node in p.nodes] | ||
| if len(clamp_partitions) != 1: | ||
| return False # Should never happen | ||
|
|
||
| clamp_partition = clamp_partitions[0] | ||
| non_q_dq_partition_nodes = list( | ||
| filter(is_not_qdq_node, clamp_partition.nodes) | ||
| is_alone_in_partition = cls.is_node_alone_in_partition( | ||
| node, partition_list, filter_fn=is_not_qdq_node | ||
| ) | ||
| if len(non_q_dq_partition_nodes) <= 1: | ||
| return False # This would be the only node in the partition, which would cause a crash later on. | ||
| if is_alone_in_partition: | ||
| return activation_supported_on_target(node, neutron_target_spec) | ||
|
|
||
| return True | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,47 +1,108 @@ | ||
| # Copyright 2025 NXP | ||
| # Copyright 2025-2026 NXP | ||
| # | ||
| # This source code is licensed under the BSD-style license found in the | ||
| # LICENSE file in the root directory of this source tree. | ||
|
|
||
| from executorch.backends.nxp.backend.ir.converter.node_converter import ( | ||
| CustomDelegationOptions, | ||
| is_not_qdq_node, | ||
| NodeConverter, | ||
| Partition, | ||
| ) | ||
| from executorch.backends.nxp.backend.ir.lib.tflite.BuiltinOperator import ( | ||
| BuiltinOperator, | ||
| ) | ||
| from executorch.backends.nxp.backend.neutron_operator_support import ( | ||
| activation_supported_on_target, | ||
| ) | ||
| from executorch.backends.nxp.backend.neutron_target_spec import NeutronTargetSpec | ||
| from torch.fx import Node | ||
| from torch.nn import Parameter | ||
|
|
||
|
|
||
| class HardTanhConverter(NodeConverter): | ||
|
|
||
| # Maps possible input parameters of HardTanh to equivalent ReLU-based operators supported by TFLite. | ||
| supported_modes_map = { | ||
| SUPPORTED_MODES_MAP = { | ||
| (0.0, 6.0): BuiltinOperator.RELU6, | ||
| (-1.0, 1.0): BuiltinOperator.RELU_N1_TO_1, | ||
| (0.0, 1.0): BuiltinOperator.RELU_0_TO_1, | ||
| (0.0, float("inf")): BuiltinOperator.RELU, | ||
| } | ||
|
|
||
| # Maps possible modes of HardTanh to equivalent ReLU bounds. | ||
| SUPPORTED_BOUNDS_MAP = { | ||
| "ReluN1To1": (-1.0, 1.0), | ||
| "Relu0To1": (0.0, 1.0), | ||
| "Relu6": (0.0, 6.0), | ||
| "Relu": (0.0, float("inf")), | ||
| } | ||
|
|
||
| @staticmethod | ||
| def _get_hardtanh_bounds(node: Node) -> tuple[int, int]: | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: The return type should be |
||
| args = node.args | ||
|
|
||
| match len(args): | ||
| case 1: | ||
| min_val = -1 | ||
| max_val = 1 | ||
|
|
||
| case 2: | ||
| min_val = args[1] | ||
| max_val = 1 | ||
|
|
||
| case 3: | ||
| min_val = args[1] | ||
| max_val = args[2] | ||
|
|
||
| case _: | ||
| # should not occur | ||
| min_val = 0 | ||
| max_val = 1 | ||
|
Comment on lines
+59
to
+61
|
||
|
|
||
| return min_val, max_val | ||
|
|
||
| @staticmethod | ||
| def _is_supported_in_IR( | ||
| node: Node, | ||
| parameters_mapping: dict[str, Parameter], | ||
| custom_delegation_options: CustomDelegationOptions, | ||
| ) -> bool: | ||
| _, min_value, max_value = node.args | ||
| return (min_value, max_value) in HardTanhConverter.supported_modes_map.keys() | ||
| bounds = HardTanhConverter._get_hardtanh_bounds(node) | ||
| return bounds in HardTanhConverter.SUPPORTED_MODES_MAP.keys() | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: The |
||
|
|
||
| @classmethod | ||
| def supports_partitioning_result( | ||
| cls, | ||
| node: Node, | ||
| partition_list: list[Partition], | ||
| custom_delegation_options: CustomDelegationOptions, | ||
| neutron_target_spec: NeutronTargetSpec, | ||
| parameters_mapping: dict[str, Parameter], | ||
| ) -> bool: | ||
| bounds = HardTanhConverter._get_hardtanh_bounds(node) | ||
|
|
||
| if bounds in [ | ||
| cls.SUPPORTED_BOUNDS_MAP["Relu"], | ||
| cls.SUPPORTED_BOUNDS_MAP["Relu6"], | ||
| ]: | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: A comment explaining why only |
||
| is_alone_in_partition = cls.is_node_alone_in_partition( | ||
| node, partition_list, filter_fn=is_not_qdq_node | ||
| ) | ||
| if is_alone_in_partition: | ||
| return activation_supported_on_target(node, neutron_target_spec) | ||
|
|
||
| return True | ||
|
|
||
| def convert(self, node: Node): | ||
| """Convert 'aten::hardtanh' to it's supported ReLU equivalent.""" | ||
|
|
||
| self.assert_convertible(node) | ||
|
|
||
| t_op = self._create_tflite_op_with_io_tensors(node) | ||
|
|
||
| _, min_value, max_value = node.args | ||
| bounds = HardTanhConverter._get_hardtanh_bounds(node) | ||
|
|
||
| op = self.supported_modes_map[(min_value, max_value)] | ||
| op = self.SUPPORTED_MODES_MAP[bounds] | ||
| t_op.opcode_index = self.builder.op_code_index_for_op_type(op) | ||
|
|
||
| self.builder.append_operators([t_op]) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice!👏🏻