-
Notifications
You must be signed in to change notification settings - Fork 854
NXP backend: added support for aten.bmm
#17670
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
Open
novak-vaclav
wants to merge
1
commit into
pytorch:main
Choose a base branch
from
nxp-upstream:feature/EIEX-709-add-support-for-aten-bmm
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+318
−0
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
94 changes: 94 additions & 0 deletions
94
backends/nxp/backend/ir/converter/node_converters/ops_converters/bmm_converter.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,94 @@ | ||
| # Copyright 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.data_format import NXP_NODE_FORMAT | ||
| from executorch.backends.nxp.backend.edge_helper import input_rank | ||
| from executorch.backends.nxp.backend.ir.converter.conversion import translator | ||
| from executorch.backends.nxp.backend.ir.converter.conversion.common import OpsList | ||
| from executorch.backends.nxp.backend.ir.converter.node_converter import ( | ||
| CustomDelegationOptions, | ||
| NodeConverter, | ||
| ) | ||
| from executorch.backends.nxp.backend.ir.tflite_generator.builtin_options import ( | ||
| batch_mat_mul_options, | ||
| ) | ||
| from executorch.backends.nxp.backend.neutron_target_spec import NeutronTargetSpec | ||
| from torch.fx import Node | ||
| from torch.nn import Parameter | ||
|
|
||
|
|
||
| class BMMConverter(NodeConverter): | ||
| @staticmethod | ||
| def _is_supported_in_IR( | ||
| node: Node, | ||
| parameters_mapping: dict[str, Parameter], | ||
| custom_delegation_options: CustomDelegationOptions, | ||
| ) -> bool: | ||
| if len(node.all_input_nodes) != 2: | ||
| return False | ||
|
|
||
| if input_rank(node, 0) != 3 or input_rank(node, 1) != 3: | ||
| return False | ||
|
|
||
| return True | ||
|
|
||
| @staticmethod | ||
| def _get_channels_last_shape(node: Node) -> list[int]: | ||
| input_shape = node.meta["val"].shape | ||
|
|
||
| if node.meta[NXP_NODE_FORMAT].is_channels_first(): | ||
| input_shape = translator.apply_permutation_to( | ||
| input_shape, | ||
| translator.create_channels_first_to_channels_last_permutation( | ||
| len(input_shape) | ||
| ), | ||
| ) | ||
|
|
||
| return input_shape | ||
|
|
||
| @staticmethod | ||
| def _is_supported_on_target( | ||
| node: Node, | ||
| neutron_target_spec: NeutronTargetSpec, | ||
| parameters_mapping: dict[str, Parameter], | ||
| custom_delegation_options: CustomDelegationOptions, | ||
| ) -> bool: | ||
| _, w1, c1 = BMMConverter._get_channels_last_shape(node.args[0]) | ||
| _, w2, c2 = BMMConverter._get_channels_last_shape(node.args[1]) | ||
|
|
||
| num_macs = neutron_target_spec.get_num_macs() | ||
|
|
||
| # The Neutron converter requires that every dimension participating in a | ||
| # multiplication is divisible by NUM_MACS. If any of the relevant dimensions | ||
| # (w1, c1, w2, c2) violates this constraint, the pattern is not supported. | ||
| if not all(m % num_macs == 0 for m in [w1, c1, w2, c2]): | ||
| return False | ||
|
|
||
| return True | ||
|
|
||
| def convert(self, node: Node): | ||
| """Convert the `aten.bmm` operator to TFLite `BatchMatMul`.""" | ||
| self.assert_convertible(node) | ||
|
|
||
| t_op = self._create_tflite_op_with_io_tensors(node) | ||
|
|
||
| # We set adj_x = adj_y = False because neither the left-hand side (lhs) nor | ||
| # the right-hand side (rhs) needs to be transposed for correct delegation. | ||
| # | ||
| # We also set asymmetric_quantize_inputs = False. This is faster, but it | ||
| # requires that both input tensors are quantized symmetrically. | ||
| t_op.builtin_options = batch_mat_mul_options.BatchMatMul(False, False, False) | ||
|
|
||
| x1 = t_op.tmp_inputs[0] | ||
| x2 = t_op.tmp_inputs[1] | ||
| y = t_op.tmp_outputs[0] | ||
|
|
||
| # Assign the operator its TFLite inputs and outputs | ||
| t_op.tmp_inputs = [x1, x2] | ||
| t_op.tmp_outputs = [y] | ||
|
|
||
| ops = OpsList(middle_op=t_op) | ||
|
|
||
| self.builder.append_operators(ops.flatten()) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
144 changes: 144 additions & 0 deletions
144
backends/nxp/tests/ir/converter/node_converter/test_bmm_converter.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,144 @@ | ||
| # Copyright 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 numpy as np | ||
| import pytest | ||
| import torch | ||
| from executorch.backends.nxp.backend.edge_program_converter import ( | ||
| EdgeProgramToIRConverter, | ||
| ) | ||
| from executorch.backends.nxp.tests.executorch_pipeline import to_quantized_edge_program | ||
| from executorch.backends.nxp.tests.executors import ( | ||
| convert_run_compare, | ||
| graph_contains_any_of_ops, | ||
| ) | ||
| from executorch.backends.nxp.tests.models import BatchMatMulConvModel, BatchMatMulModel | ||
| from executorch.backends.nxp.tests.use_qat import * # noqa F403 | ||
| from executorch.exir.dialects._ops import ops as exir_ops | ||
|
|
||
|
|
||
| @pytest.fixture(autouse=True) | ||
| def reseed_model_per_test_run(): | ||
| torch.manual_seed(23) | ||
| np.random.seed(23) | ||
|
|
||
|
|
||
| # noinspection PyProtectedMember | ||
| ExecutorchDelegateCall = torch.ops.higher_order.executorch_call_delegate | ||
| Bmm = exir_ops.edge.aten.bmm.default | ||
|
|
||
|
|
||
| @pytest.mark.parametrize( | ||
| "input_shape_x1, input_shape_x2", | ||
| [ | ||
| pytest.param((1, 8, 16), (1, 16, 24), id="3D, one batch."), | ||
| pytest.param((4, 8, 16), (4, 16, 24), id="3D, more batches."), | ||
| ], | ||
| ) | ||
| def test_convert_bmm__supported(mocker, input_shape_x1, input_shape_x2): | ||
| model = BatchMatMulModel() | ||
|
|
||
| converter_spy = mocker.spy(EdgeProgramToIRConverter, "convert_program") | ||
| delegated_ep = to_quantized_edge_program( | ||
| model, [input_shape_x1, input_shape_x2], use_qat=use_qat, | ||
| ).exported_program() | ||
novak-vaclav marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| # Make sure the `bmm` was delegated. | ||
| assert graph_contains_any_of_ops(delegated_ep.graph, [ExecutorchDelegateCall]) | ||
| assert not graph_contains_any_of_ops(delegated_ep.graph, [Bmm]) | ||
|
|
||
| # Verify correct behavior of the converted NeutronIR model. | ||
| intermediate_ep = converter_spy.call_args.args[1] | ||
| neutron_ir_model, _ = converter_spy.spy_return | ||
|
|
||
| input_data_1 = ( | ||
| np.random.random(input_shape_x1).astype(np.float32) * 256.0 - 128.0 | ||
| ).astype(np.int8) | ||
| input_data_2 = ( | ||
| np.random.random(input_shape_x2).astype(np.float32) * 256.0 - 128.0 | ||
| ).astype(np.int8) | ||
|
|
||
| # Make sure the tested program contains the `bmm`. | ||
| assert graph_contains_any_of_ops(intermediate_ep.graph, [Bmm]) | ||
|
|
||
| # Verify that the delegated `bmm` node produces correct results | ||
| # The delegated `bmm` runs with a numerical tolerance of atol = 1 | ||
| convert_run_compare( | ||
| intermediate_ep, | ||
| tfl_model=neutron_ir_model, | ||
| input_data={ | ||
| 0: input_data_1, | ||
| 1: input_data_2, | ||
| }, | ||
| atol=1, | ||
| ) | ||
|
|
||
|
|
||
| @pytest.mark.parametrize( | ||
| "input_shape_x1, input_shape_x2", | ||
| [ | ||
| pytest.param((1, 7, 16), (1, 16, 24), id="3D, x1_C not divisible by NUM_MACS."), | ||
| pytest.param( | ||
| (1, 8, 7), (1, 7, 24), id="3D, x1_W (and x2_C) not divisible by NUM_MACS." | ||
| ), | ||
| pytest.param((1, 8, 16), (1, 16, 7), id="3D, x2_W not divisible by NUM_MACS."), | ||
| ], | ||
| ) | ||
| def test_convert_bmm__unsupported(input_shape_x1, input_shape_x2): | ||
| model = BatchMatMulModel() | ||
|
|
||
| delegated_ep = to_quantized_edge_program( | ||
| model, [input_shape_x1, input_shape_x2], use_qat=use_qat, | ||
| ).exported_program() | ||
|
|
||
| # Make sure the `bmm` was NOT delegated. | ||
| assert graph_contains_any_of_ops(delegated_ep.graph, [Bmm]) | ||
|
|
||
|
|
||
| @pytest.mark.parametrize( | ||
| "conv_input_shape, bmm_input_shape", | ||
| [ | ||
| pytest.param((4, 8, 16), (4, 16, 16), id="3D with conv. quant"), | ||
| ], | ||
| ) | ||
| def test_convert_bmm__conv_quant(mocker, conv_input_shape, bmm_input_shape): | ||
| conv_channels = conv_input_shape[1] | ||
| bmm_channels = bmm_input_shape[1] | ||
| model = BatchMatMulConvModel(in_channels=conv_channels, out_channels=bmm_channels) | ||
|
|
||
novak-vaclav marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| converter_spy = mocker.spy(EdgeProgramToIRConverter, "convert_program") | ||
| delegated_ep = to_quantized_edge_program( | ||
| model, [conv_input_shape, bmm_input_shape], use_qat=use_qat, | ||
| ).exported_program() | ||
|
|
||
| # Make sure the `bmm` was delegated. | ||
| assert graph_contains_any_of_ops(delegated_ep.graph, [ExecutorchDelegateCall]) | ||
| assert not graph_contains_any_of_ops(delegated_ep.graph, [Bmm]) | ||
|
|
||
| # Verify correct behavior of the converted NeutronIR model. | ||
| bmm_intermediate_ep = converter_spy.call_args.args[1] | ||
| bmm_neutron_ir_model, _ = converter_spy.spy_return | ||
|
|
||
| bmm_input_data_1 = ( | ||
| np.random.random(bmm_input_shape).astype(np.float32) * 256.0 - 128.0 | ||
| ).astype(np.int8) | ||
| bmm_input_data_2 = ( | ||
| np.random.random(bmm_input_shape).astype(np.float32) * 256.0 - 128.0 | ||
| ).astype(np.int8) | ||
|
|
||
| # Make sure the tested program contains the `bmm`. | ||
| assert graph_contains_any_of_ops(bmm_intermediate_ep.graph, [Bmm]) | ||
|
|
||
| # Verify that the delegated `bmm` node produces correct results | ||
| # The delegated `bmm` runs with a numerical tolerance of atol = 1 | ||
| convert_run_compare( | ||
| bmm_intermediate_ep, | ||
| tfl_model=bmm_neutron_ir_model, | ||
| input_data={ | ||
| 0: bmm_input_data_1, | ||
| 1: bmm_input_data_2, | ||
| }, | ||
| atol=1, | ||
| ) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.