-
Notifications
You must be signed in to change notification settings - Fork 1k
NXP backend: Enable adaptive_avg_pool2d with new Neutron flow.
#19540
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 |
|---|---|---|
| @@ -1,31 +1,59 @@ | ||
| # 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. | ||
| import logging | ||
|
|
||
| import executorch.backends.nxp.backend.ir.lib.tflite.Padding as tflPadding | ||
| import torch | ||
|
|
||
| from executorch.backends.nxp.backend.data_format import NXP_NODE_FORMAT | ||
| from executorch.backends.nxp.backend.ir.converter.conversion import common | ||
| from executorch.backends.nxp.backend.ir.converter.node_converter import ( | ||
| CustomDelegationOptions, | ||
| NodeConverter, | ||
| ) | ||
| from executorch.backends.nxp.backend.ir.tflite_generator import tflite_model | ||
| from executorch.backends.nxp.backend.ir.tflite_generator.builtin_options import ( | ||
| average_pool_2d_options, | ||
| ) | ||
| from torch import Size | ||
|
|
||
| from executorch.backends.nxp.backend.neutron_target_spec import NeutronTargetSpec | ||
| from torch.fx import Node | ||
| from torch.nn import Parameter | ||
|
|
||
| KernelSize = tuple[int, int] | ||
| Stride = tuple[int, int] | ||
|
|
||
|
|
||
| class AdaptiveAvgPool2dConverter(NodeConverter): | ||
|
|
||
| @staticmethod | ||
| def _get_equivalent_avg_pool_parameters(node: Node) -> tuple[KernelSize, Stride]: | ||
| input_size = node.args[0].meta["val"].shape[2:] # Spatial dims from NCHW shape. | ||
| output_size = node.args[1] | ||
| stride = (input_size[0] // output_size[0], input_size[1] // output_size[1]) | ||
| kernel_size = ( | ||
| input_size[0] - (output_size[0] - 1) * stride[0], | ||
| input_size[1] - (output_size[1] - 1) * stride[1], | ||
| ) | ||
|
|
||
| return kernel_size, stride | ||
|
|
||
| @staticmethod | ||
| def _is_supported_in_IR( | ||
| node: Node, | ||
| parameters_mapping: dict[str, Parameter], | ||
| custom_delegation_options: CustomDelegationOptions, | ||
| ) -> bool: | ||
| if ( | ||
|
Contributor
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. I would move this check to
Collaborator
Author
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. The check is there to make sure our The PR which originally added the adaptive avgpool2d to our backend didn't update the format inference, so I added this check. I believe we should add it to all node converters of operators which require channels last format in NeutronIR. Let's leave it as is in this PR. I have created a ticket to address this: https://jira.sw.nxp.com/browse/EIEX-913 |
||
| format_ := node.meta.get(NXP_NODE_FORMAT) | ||
| ) is None or not format_.is_channels_first(): | ||
| logging.warning( | ||
| "NXP backend: `adaptive_avg_pool_2d` doesn't have the required input format for delegation. " | ||
| "Please run `NodeFormatInference.identify_node_formats()` during lowering or report this issue." | ||
| ) | ||
| return False | ||
|
|
||
| input_size = node.args[0].meta["val"].shape | ||
| output_size = node.args[1] | ||
|
|
||
|
|
@@ -39,30 +67,53 @@ def _is_supported_in_IR( | |
|
|
||
| return True | ||
|
|
||
| # noinspection PyMethodMayBeStatic | ||
| def _convert_adaptive_avg_pool_2d( | ||
| self, input_size: Size, output_size: list[int], t_op: tflite_model.Operator | ||
| ): | ||
| t_op.builtin_options = average_pool_2d_options.AveragePool2D() | ||
| stride = [input_size[-2] // output_size[-2], input_size[-1] // output_size[-1]] | ||
| common.assign_2d_strides(t_op.builtin_options, stride) | ||
| t_op.builtin_options.filter_h = ( | ||
| input_size[-2] - (output_size[-2] - 1) * stride[-2] | ||
| ) | ||
| t_op.builtin_options.filter_w = ( | ||
| input_size[-1] - (output_size[-1] - 1) * stride[-1] | ||
| @staticmethod | ||
| def _is_supported_on_target( | ||
| node: Node, | ||
| neutron_target_spec: NeutronTargetSpec, | ||
| parameters_mapping: dict[str, Parameter], | ||
| custom_delegation_options: CustomDelegationOptions, | ||
| ) -> bool: | ||
| kernel_size, stride = ( | ||
| AdaptiveAvgPool2dConverter._get_equivalent_avg_pool_parameters(node) | ||
| ) | ||
| t_op.builtin_options.padding = tflPadding.Padding.VALID | ||
|
|
||
| # AdaptiveAvgPool2d Node format: (Tensor self, SymInt[2] output_size) | ||
| if custom_delegation_options.use_new_flow_neutron_c: | ||
| # Requirements specified by the new Neutron flow documentation. | ||
|
|
||
| if not NodeConverter.uses_quantization_type_for_io( | ||
| node, | ||
| supported_types=[torch.int8, torch.uint8], | ||
| input_indices=[0], | ||
| output_indices=[0], | ||
| ): | ||
| return False | ||
|
|
||
| if any(k > 4096 for k in kernel_size): | ||
| return False | ||
|
|
||
| if any(s > 4096 for s in stride): | ||
| return False | ||
|
|
||
| return True | ||
|
|
||
| def convert(self, node: Node): | ||
| """Convert '_adaptive_avg_pool2d' operator to TFLite 'AveragePool2D'.""" | ||
| """Convert the '_adaptive_avg_pool2d' operator to NeutronIR 'AveragePool2D'. | ||
| The ExecuTorch schema is: | ||
| _adaptive_avg_pool2d( | ||
| Tensor self, | ||
| SymInt[2] output_size | ||
| ) -> Tensor | ||
| """ | ||
| self.assert_convertible(node) | ||
|
|
||
| input_size = node.args[0].meta["val"].shape | ||
| output_size = node.args[1] | ||
|
|
||
| t_op = self._create_tflite_op_with_io_tensors(node) | ||
| t_op.builtin_options = average_pool_2d_options.AveragePool2D() | ||
|
|
||
| kernel_size, stride = self._get_equivalent_avg_pool_parameters(node) | ||
|
|
||
| common.assign_2d_strides(t_op.builtin_options, stride) | ||
| t_op.builtin_options.filter_h, t_op.builtin_options.filter_w = kernel_size | ||
| t_op.builtin_options.padding = tflPadding.Padding.VALID | ||
|
|
||
| self._convert_adaptive_avg_pool_2d(input_size, output_size, t_op) | ||
| self.builder.append_operators([t_op]) | ||
Uh oh!
There was an error while loading. Please reload this page.