Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions backends/nxp/_passes/remove_getitem_pass.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
# Copyright 2025 NXP
# Copyright 2025-2026 NXP
# 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.

import torch

from executorch.backends.nxp.backend.node_format import NodeFormat, NXP_NODE_FORMAT
from executorch.backends.nxp.backend.data_format import DataFormat, NXP_NODE_FORMAT
from executorch.exir.dialects._ops import ops as exir_ops
from executorch.exir.pass_base import ExportPass, PassResult

Expand Down Expand Up @@ -89,7 +89,7 @@ def call(self, graph_module: torch.fx.GraphModule):
# MODIFIED PART START
# Make sure to preserve the inferred node format.
new_max_wd.meta[NXP_NODE_FORMAT] = node.meta.get(
NXP_NODE_FORMAT, NodeFormat.NONE
NXP_NODE_FORMAT, DataFormat.NONE
)
# MODIFIED PART END

Expand Down
49 changes: 49 additions & 0 deletions backends/nxp/backend/data_format.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Copyright 2023 Martin Pavella
# Copyright 2023-2026 NXP
#
# License: MIT
# See the LICENSE_MIT for more details.
#

from enum import Enum

# Key into the `meta` attribute of nodes, which is mapped to their inferred node format.
NXP_NODE_FORMAT = "nxp_node_format"


class DataFormat(Enum):
CHANNELS_FIRST = 0

CHANNELS_LAST = 10

# The format of TFLite Conv3D weights tensor: [output_channels, input_channels, D, H, W]
CONV_3D_WEIGHT_FORMAT = 11

# Intermediate format between 'Transpose' and 'Reshape' ops when single dimension with value 1
# is added/removed via reshaping
RESHAPE_SINGLE_UNITARY_TRANSPOSITION = 12

# The format of TFLite TransposeConv 2D weights tensor: [M/group, kH, kW, C]
TRANSPOSE_CONV_2D_WEIGHT_FORMAT = 13

# No special format (matrices, vectors, shapes etc.). All tensors with the FORMATLESS format MUST have EXACTLY
# the same shape and data in the NeutronIR model and in the ExecuTorch model.
FORMATLESS = 20

NONE = 30 # Format has not been identified

def is_channels_first(self) -> bool:
return self == DataFormat.CHANNELS_FIRST

def is_channels_last(self) -> bool:
return self == DataFormat.CHANNELS_LAST

@staticmethod
def convert_executorch_format_to_neutron(
executorch_format: "DataFormat",
) -> "DataFormat":
if executorch_format == DataFormat.CHANNELS_FIRST:
return DataFormat.CHANNELS_LAST # Format is converted.

else:
return executorch_format # Other formats remain unchanged.
4 changes: 2 additions & 2 deletions backends/nxp/backend/edge_program_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
from torch.fx import Node
from torch.nn.parameter import Parameter
from executorch.backends.nxp.backend.ir.converter.node_converters.ops_converters import * # noqa F403
from executorch.backends.nxp.backend.data_format import DataFormat, NXP_NODE_FORMAT
from executorch.backends.nxp.backend.neutron_target_spec import NeutronTargetSpec
from executorch.backends.nxp.backend.node_format import NodeFormat, NXP_NODE_FORMAT
from executorch.exir.dialects._ops import ops as exir_ops

# noinspection PyProtectedMember
Expand Down Expand Up @@ -65,7 +65,7 @@ def convert_program(
conversion_config: ConversionConfig = _default_conversion_config,
neutron_target_spec: NeutronTargetSpec = _default_target_spec,
custom_delegation_options: CustomDelegationOptions = _default_delegation_options,
) -> (bytes, dict[str, NodeFormat]):
) -> tuple[bytes, dict[str, DataFormat]]:
"""
Convert ExportedProgram in Edge dialect to IR (TFLite flatbuffers) as bytes.

Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
# Copyright 2024 NXP
# Copyright 2024,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 DataFormat
from executorch.backends.nxp.backend.ir.converter.builder.model_builder import (
ModelBuilder,
)
from executorch.backends.nxp.backend.ir.converter.conversion import translator
from executorch.backends.nxp.backend.ir.tensor_formatting import TensorFormat
from executorch.backends.nxp.backend.ir.tflite_generator import tflite_model
from executorch.backends.nxp.backend.node_format import NodeFormat
from torch.fx import Node
from torch.nn import Parameter

Expand All @@ -20,13 +19,13 @@ class AtenModelBuilderDirector(ModelBuilder):
contains methods related to Edge program nodes conversion.
"""

def append_as_fake_tensor(self, node: Node, node_format: NodeFormat):
def append_as_fake_tensor(self, node: Node, node_format: DataFormat):
"""
Append node into ModelBuilder as tensor without data (FakeTensor). Can be used
for activations and output tensors.

:param node: Node instance.
:param node_format: NodeFormat definition.
:param node_format: DataFormat definition.
"""
if self.tensor_exists(node.name):
return
Expand All @@ -41,17 +40,19 @@ def append_as_fake_tensor(self, node: Node, node_format: NodeFormat):
shape = translator.dims_to_channels_last(shape)

tensor = self.create_empty_tensor(node.name, _type, shape)
tensor.tensor_format = TensorFormat.from_node_format(node_format)
tensor.tensor_format = DataFormat.convert_executorch_format_to_neutron(
node_format
)

def append_as_static_tensor(
self, node: Node, node_format: NodeFormat, tensor: Parameter
self, node: Node, node_format: DataFormat, tensor: Parameter
):
"""
Append node into ModelBuilder as tensor with data (static). Can be used for weights,
permutations etc.

:param node: Node instance.
:param node_format: NodeFormat definition.
:param node_format: DataFormat definition.
:param tensor: Torch Tensor (Parameter) that holds tensor data.
"""
assert not self.tensor_exists(node.name), f"Tensor '{node.name}' already added!"
Expand All @@ -65,7 +66,9 @@ def append_as_static_tensor(
data = translator.convert_data_to_channels_last(data)

tensor = self.create_tensor_for_data(data, node.name)
tensor.tensor_format = TensorFormat.from_node_format(node_format)
tensor.tensor_format = DataFormat.convert_executorch_format_to_neutron(
node_format
)

def append_operators(self, ops_to_add: list[tflite_model.Operator]):
"""
Expand All @@ -88,7 +91,7 @@ def append_operators(self, ops_to_add: list[tflite_model.Operator]):

self.check_and_append_operator(op)

def get_io_formats(self, graph_signature) -> dict[str, dict[str, TensorFormat]]:
def get_io_formats(self, graph_signature) -> dict[str, dict[str, DataFormat]]:
"""Get a mapping from tensor names to their formats.

:param graph_signature: Instance of GraphSignature.
Expand Down
19 changes: 4 additions & 15 deletions backends/nxp/backend/ir/converter/builder/model_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@
#

from copy import deepcopy
from itertools import chain
from typing import List, Optional, Union

import executorch.backends.nxp.backend.ir.converter.conversion.translator as translator
import executorch.backends.nxp.backend.ir.logger as logger
import executorch.backends.nxp.backend.ir.tflite_generator.tflite_model as tflite_model
import numpy as np
from executorch.backends.nxp.backend.data_format import DataFormat
from executorch.backends.nxp.backend.edge_helper import is_channels_last_dim_order
from executorch.backends.nxp.backend.ir.conversion_config import ConversionConfig
from executorch.backends.nxp.backend.ir.converter.builder import (
Expand All @@ -35,7 +35,6 @@
)
from executorch.backends.nxp.backend.ir.lib.tflite.TensorType import TensorType
from executorch.backends.nxp.backend.ir.neutron_ir_post_processing import optimizer
from executorch.backends.nxp.backend.ir.tensor_formatting import TensorFormat
from executorch.backends.nxp.backend.ir.tflite_generator.builtin_options import (
cast_options,
dequantize_options,
Expand Down Expand Up @@ -227,7 +226,7 @@ def channels_first_version_of(self, t_tensor: tflite_model.Tensor):
new_tensor.shape = translator.channels_last_shape_to_channels_first(
t_tensor.shape
)
new_tensor.tensor_format = TensorFormat.CHANNELS_FIRST
new_tensor.tensor_format = DataFormat.CHANNELS_FIRST

perm = translator.create_channels_last_to_channels_first_permutation(
t_tensor.rank
Expand Down Expand Up @@ -398,7 +397,7 @@ def _make_inputs_channels_first(self):
input_tensor, input_tensor.name + "_channels_first"
)
new_input.shape = new_input_shape
new_input.tensor_format = TensorFormat.CHANNELS_FIRST
new_input.tensor_format = DataFormat.CHANNELS_FIRST

transpose = self._create_transpose_operator(
new_input, input_tensor, perm
Expand Down Expand Up @@ -484,14 +483,6 @@ def _keep_one_empty_buffer(self):
# It's safe to replace the buffer.
t.tmp_buffer = empty_buffer

def replace_io_tensor_format_with_node_format(self):
for t in chain(
self.get_sub_graph().inputs.tmp_inputs,
self.get_sub_graph().outputs.tmp_outputs,
):
if isinstance(t.tensor_format, TensorFormat):
t.tensor_format = t.tensor_format.to_equal_node_format()

def finish(self) -> tflite_model.Model:
"""Finalize and optimize the converted TFLite model. Then return it.

Expand All @@ -514,8 +505,6 @@ def finish(self) -> tflite_model.Model:

self._keep_one_empty_buffer()

self.replace_io_tensor_format_with_node_format()

# Remove outputs, which are not produced by any node. Otherwise, there would be errors after inference.
operator_outputs = []
for op in self.get_operators().vector:
Expand Down Expand Up @@ -1584,7 +1573,7 @@ def prepare_dynamic_tensor_for_correct_broadcasting_with_channels_first_tensors(
transpose_output.shape = tflite_model.Shape(
translator.apply_permutation_to(transpose_output.shape.vector, perm)
)
transpose_output.tensor_format = TensorFormat.CHANNELS_LAST
transpose_output.tensor_format = DataFormat.CHANNELS_LAST

transpose = self._create_transpose_operator(
transpose_input, transpose_output, perm
Expand Down
17 changes: 13 additions & 4 deletions backends/nxp/backend/ir/converter/node_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,13 +173,22 @@ def assert_convertible(self, node):
"""Assert that the call `_is_supported_in_IR()` returns `True`. Otherwise, raise an exception and print an
error message.
"""
assert self._is_supported_in_IR(
supported_in_ir = self._is_supported_in_IR(
node,
self.context.parameters_mapping,
self.context.custom_delegation_options,
), (
f"Node `{node}` is not convertible to the intermediate representation. "
"There is an error in the partitioner."
)

supported_on_target = self._is_supported_on_target(
node,
self.neutron_target_spec,
self.context.parameters_mapping,
self.context.custom_delegation_options,
)

assert supported_in_ir and supported_on_target, (
f"Node `{node}` was selected for delegation to Neutron, but it is not convertible to the intermediate "
"representation. There is an error in the NXP partitioner,]. Please report this."
)

@property
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# 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.
Expand All @@ -8,6 +8,7 @@
from executorch.backends.nxp.backend.custom_delegation_options import (
CustomDelegationOptions,
)
from executorch.backends.nxp.backend.data_format import NXP_NODE_FORMAT
from executorch.backends.nxp.backend.edge_helper import previous_non_qdq_node
from executorch.backends.nxp.backend.ir.converter.conversion import translator
from executorch.backends.nxp.backend.ir.converter.conversion.translator import (
Expand All @@ -23,7 +24,6 @@
Concatenation,
)
from executorch.backends.nxp.backend.neutron_target_spec import NeutronTargetSpec
from executorch.backends.nxp.backend.node_format import NXP_NODE_FORMAT
from torch.fx import Node
from torch.fx.passes.infra.partitioner import Partition
from torch.nn import Parameter
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2024-2025 NXP
# Copyright 2024-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.
Expand All @@ -8,6 +8,8 @@

import numpy as np

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.translator import (
apply_permutation_to,
Expand All @@ -27,8 +29,6 @@
pad_v2_options,
)
from executorch.backends.nxp.backend.neutron_target_spec import NeutronTargetSpec

from executorch.backends.nxp.backend.node_format import NXP_NODE_FORMAT
from torch.fx import Node
from torch.nn import Parameter

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
# Copyright 2024-2025 NXP
# Copyright 2024-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 torch
from executorch.backends.nxp.backend.data_format import DataFormat

from executorch.backends.nxp.backend.edge_helper import (
input_tensor,
Expand Down Expand Up @@ -37,7 +38,6 @@
)
from executorch.backends.nxp.backend.ir.converter.tensor_utils import tensor_has_data
from executorch.backends.nxp.backend.ir.lib.tflite.TensorType import TensorType
from executorch.backends.nxp.backend.ir.tensor_formatting import TensorFormat
from executorch.backends.nxp.backend.ir.tflite_generator import tflite_model
from executorch.backends.nxp.backend.ir.tflite_generator.builtin_options import (
conv_2d_options,
Expand Down Expand Up @@ -386,7 +386,7 @@ def _convert_transpose_conv(
w.quantization.quantized_dimension = 0
else:
raise NotImplementedError("Dynamic Transpose Conv weights.")
w.tensor_format = TensorFormat.TRANSPOSE_CONV_2D_WEIGHT_FORMAT
w.tensor_format = DataFormat.TRANSPOSE_CONV_2D_WEIGHT_FORMAT

output_shape_tensor_data = np.asarray(y.shape.vector, dtype=np.int32)
o = self.builder.create_tensor_for_data(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# 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 torch
from executorch.backends.nxp.backend.data_format import NXP_NODE_FORMAT

from executorch.backends.nxp.backend.ir.converter.conversion.translator import (
create_channels_last_to_channels_first_permutation,
Expand All @@ -19,7 +20,6 @@
mean_options,
)
from executorch.backends.nxp.backend.neutron_target_spec import NeutronTargetSpec
from executorch.backends.nxp.backend.node_format import NXP_NODE_FORMAT
from torch.fx import Node
from torch.nn import Parameter

Expand Down
Loading
Loading