Skip to content
Merged
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
34 changes: 34 additions & 0 deletions .ci/scripts/test_backend.sh
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,23 @@ if [[ "$FLOW" == *cortex_m* ]]; then
backends/cortex_m/test/build_test_runner.sh
fi

if [[ "$FLOW" == *nxp* ]]; then
# Install the eIQ Toolkit Python packages (NSYS simulator and Neutron converter).
pip install -r backends/nxp/requirements-eiq.txt

# Enable the Neutron delegate, portable kernels, pybindings and extensions
# required by the operator test suite.
EXTRA_BUILD_ARGS+=" -DEXECUTORCH_BUILD_NXP_NEUTRON=ON"
EXTRA_BUILD_ARGS+=" -DEXECUTORCH_BUILD_NXP_NEUTRON_RUNNER=ON"
EXTRA_BUILD_ARGS+=" -DEXECUTORCH_BUILD_KERNELS_PORTABLE=ON"
EXTRA_BUILD_ARGS+=" -DEXECUTORCH_BUILD_PYBIND=ON"
EXTRA_BUILD_ARGS+=" -DEXECUTORCH_BUILD_EXTENSION_MODULE=ON"
EXTRA_BUILD_ARGS+=" -DEXECUTORCH_BUILD_EXTENSION_DATA_LOADER=ON"
EXTRA_BUILD_ARGS+=" -DEXECUTORCH_BUILD_EXTENSION_FLAT_TENSOR=ON"
EXTRA_BUILD_ARGS+=" -DEXECUTORCH_BUILD_EXTENSION_TENSOR=ON"
EXTRA_BUILD_ARGS+=" -DEXECUTORCH_BUILD_EXTENSION_NAMED_DATA_MAP=ON"
fi

if [[ "$FLOW" == *openvino* ]]; then
# Setup OpenVINO environment
source .ci/scripts/setup-openvino.sh --nightly
Expand All @@ -117,6 +134,23 @@ else
fi
CMAKE_ARGS="$EXTRA_BUILD_ARGS" ${CONDA_RUN_CMD} $SETUP_SCRIPT --build-tool cmake --build-mode Release --editable true

if [[ "$FLOW" == *nxp* ]]; then
# Install test-time Python requirements (neutron-test helpers, etc.).
pip install -r backends/nxp/requirements-tests-pypi.txt
PYTHON_EXECUTABLE=python bash examples/nxp/setup.sh

# Build nxp_executor_runner as a standalone binary. The cmake-out subproject
# build may produce a differently-linked binary; the standalone build is known
# to work correctly with the NSYS simulator firmware.
mkdir -p examples/nxp/executor_runner/build
pushd examples/nxp/executor_runner/build
cmake -DCMAKE_BUILD_TYPE=Release ..
make -j"$(nproc)" nxp_executor_runner
popd

export NXP_RUNNER_PATH="$(pwd)/examples/nxp/executor_runner/build/nxp_executor_runner"
fi

GOLDEN_DIR="${ARTIFACT_DIR}/golden-artifacts"
export GOLDEN_ARTIFACTS_DIR="${GOLDEN_DIR}"

Expand Down
51 changes: 51 additions & 0 deletions .github/workflows/test-backend-nxp.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: Test NXP Backend

on:
schedule:
- cron: 0 2 * * *
push:
branches:
- main
- release/*
tags:
- ciflow/nightly/*
pull_request:
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref_name }}-${{ github.ref_type == 'branch' && github.sha }}-${{ github.event_name == 'workflow_dispatch' }}-${{ github.event_name == 'schedule' }}
cancel-in-progress: true

jobs:
# Emits PR diff file list; non-PR events emit '*' so the per-job
# `if:` short-circuits via `event_name != 'pull_request'`.
changed-files:
name: Get changed files
uses: ./.github/workflows/_get-changed-files.yml

test-nxp:
needs: changed-files
# Every case in this flow runs the NSYS Neutron simulator, so the full
# eIQ Toolkit install and nxp_executor_runner build are always required.
# Path-gate on pull_request to avoid running on unrelated changes, while
# still running in full on the nightly schedule and on pushes to main.
if: |
github.event_name != 'pull_request' ||
contains(needs.changed-files.outputs.changed-files, 'backends/nxp') ||
contains(needs.changed-files.outputs.changed-files, 'examples/nxp') ||
contains(needs.changed-files.outputs.changed-files, 'backends/test/suite') ||
contains(needs.changed-files.outputs.changed-files, 'backends/test/harness') ||
contains(needs.changed-files.outputs.changed-files, '.ci/scripts/test_backend.sh') ||
contains(needs.changed-files.outputs.changed-files, '.github/workflows/test-backend-nxp.yml') ||
contains(needs.changed-files.outputs.changed-files, '.github/workflows/_test_backend.yml')
uses: ./.github/workflows/_test_backend.yml
with:
backend: nxp
flows: '["nxp_neutron_imxrt700_int8_ptq"]'
# The models suite (torchvision/torchaudio) is excluded until a curated
# list of models known to pass on the Neutron backend is established.
exclude: '[{"flow": "nxp_neutron_imxrt700_int8_ptq", "suite": "models"}]'
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
timeout: 150
run-linux: true
docker-image: ci-image:executorch-ubuntu-22.04-clang12
36 changes: 24 additions & 12 deletions backends/nxp/backend/edge_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -413,18 +413,18 @@ def try_get_arg(node: Node, idx: int) -> Argument | None:
return node.args[idx] if idx < len(node.args) else None


def input_quantization_type(
def input_quantization_parameters(
node: Node, input_index: int | tuple[int, int]
) -> torch.dtype | None:
"""Return the quantization input datatype of the QDQ quantized `node`.
) -> tuple[Scale, ZeroPoint, torch.dtype] | None:
"""Return the input quantization parameters of the QDQ quantized `node`.

:param node: The compute node.
:param input_index: The index into the `node.args`. If a tuple of 2 ints is provided,
`args[input_index[0]][input_index[1]]` is used instead.
:return: The input quantization datatype of the QDQ quantized `node`, or `None` if the graph does not follow the
:return: The input quantization parameters of the QDQ quantized `node`, or `None` if the graph does not follow the
QDQ pattern or some metadata is incomplete or an invalid input index is given.

│ <returned type>
│ <returned parameters>
┌─────▼──────┐
│ Dequantize │
└─────┬──────┘
Expand Down Expand Up @@ -455,17 +455,24 @@ def input_quantization_type(
if (dequantize_input_val := dequantize_node.args[0].meta.get("val")) is None:
return None # Invalid metadata.

return dequantize_input_val.dtype
params = get_quantization_parameters_for(dequantize_node)
dtype = dequantize_input_val.dtype
if params is None or dtype is None:
return None

return *params, dtype


def output_quantization_type(node: Node, output_index: int) -> torch.dtype | None:
"""Return the quantization output datatype of the QDQ quantized `node`.
def output_quantization_parameters(
node: Node, output_index: int
) -> tuple[Scale, ZeroPoint, torch.dtype] | None:
"""Return the output quantization parameters of the QDQ quantized `node`.

:param node: The compute node.
:param output_index: If the `node` has multiple outputs and therefore multiple `getitem` nodes follow it, the
index selects the output. If no `getitem` nodes follow it, the operator
produces only 1 output (most common case), and the value `0` must be used.
:return: The output quantization datatype of the QDQ quantized `node`, or `None` if the graph does not follow the
:return: The output quantization parameters of the QDQ quantized `node`, or `None` if the graph does not follow the
QDQ pattern or some metadata is incomplete or an invalid input index is given.

┌───▼────┐
Expand All @@ -477,10 +484,10 @@ def output_quantization_type(node: Node, output_index: int) -> torch.dtype | Non
┌────▼─────┐ or │ getitem(output_index) │ ...
│ Quantize │ └─────────┬─────────────┘
└────┬─────┘ │ float
│ <returned type> ┌────▼─────┐
│ <returned parameters> ┌────▼─────┐
│ Quantize │
└────┬─────┘
│ <returned type>
│ <returned parameters>
"""
users = list(node.users)
if len(users) == 1 and _is_quantize(quantize_node := users[0]):
Expand Down Expand Up @@ -512,4 +519,9 @@ def output_quantization_type(node: Node, output_index: int) -> torch.dtype | Non
if (quantize_val := quantize_node.meta.get("val")) is None:
return None # Invalid metadata.

return quantize_val.dtype
params = get_quantization_parameters_for(quantize_node)
dtype = quantize_val.dtype
if params is None or dtype is None:
return None

return *params, dtype
13 changes: 8 additions & 5 deletions backends/nxp/backend/ir/converter/node_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
)
from executorch.backends.nxp.backend.data_format import DataFormat, NXP_NODE_FORMAT
from executorch.backends.nxp.backend.edge_helper import (
input_quantization_type,
output_quantization_type,
input_quantization_parameters,
output_quantization_parameters,
)
from executorch.backends.nxp.backend.ir import logger as logger
from executorch.backends.nxp.backend.ir.conversion_context import ConversionContext
Expand Down Expand Up @@ -119,8 +119,9 @@ def _is_supported_in_IR(
"""
pass

@staticmethod
@classmethod

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change was introduced because the HardTanhConverter inherits from ClampConverter and uses its methods while overriding others. So only ClampConverter implements is_supported_on_target, which internally calls get_bounds which is implemented in botch classes differently. Therefore, is_supported_on_target must be a class method.

(It worked before this commit because the ClampConverter actually defined the is_supported_on_target as a class method, even thought its parent class NodeConverter declared is as a static method.)

def _is_supported_on_target(
cls,
node: Node,
neutron_target_spec: NeutronTargetSpec,
parameters_mapping: dict[str, Parameter],
Expand Down Expand Up @@ -390,7 +391,8 @@ def uses_quantization_type_for_inputs(
:return: True, if the `node` is QDQ quantized and has quantization input types in `supported_types`.
"""
return all(
input_quantization_type(node, input_index) in supported_types
(params := input_quantization_parameters(node, input_index)) is not None
and params[2] in supported_types
for input_index in input_indices
)

Expand All @@ -412,7 +414,8 @@ def uses_quantization_type_for_outputs(
:return: True, if the `node` is QDQ quantized and has quantization output types in `supported_types`.
"""
return all(
output_quantization_type(node, output_index) in supported_types
(q_params := output_quantization_parameters(node, output_index)) is not None
and q_params[2] in supported_types
for output_index in output_indices
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ def _is_supported_in_IR(
) -> bool:
return True

@staticmethod
@classmethod
def _is_supported_on_target(
cls,
node: Node,
neutron_target_spec: NeutronTargetSpec,
parameters_mapping: dict[str, Parameter],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,9 @@ def _is_supported_in_IR(

return True

@staticmethod
@classmethod
def _is_supported_on_target(
cls,
node: Node,
neutron_target_spec: NeutronTargetSpec,
parameters_mapping: dict[str, Parameter],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@


class AddTensorConverter(NodeConverter):
@staticmethod
@classmethod
def _is_supported_on_target(
cls,
node: Node,
neutron_target_spec: NeutronTargetSpec,
parameters_mapping: dict[str, Parameter],
Expand All @@ -46,7 +47,7 @@ def _is_supported_in_IR(
if len(node.args) != 2:
return False

if hasattr(node.kwargs, "alpha"):
if node.kwargs.get("alpha", 1) != 1:
return False

return True
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,9 @@ def _is_supported_in_IR(

return True

@staticmethod
@classmethod
def _is_supported_on_target(
cls,
node: Node,
neutron_target_spec: NeutronTargetSpec,
parameters_mapping: dict[str, Parameter],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@

class AmaxConverter(NodeConverter):

@staticmethod
@classmethod
def _is_supported_on_target(
cls,
node: Node,
neutron_target_spec: NeutronTargetSpec,
parameters_mapping: dict[str, Parameter],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@

class AminConverter(NodeConverter):

@staticmethod
@classmethod
def _is_supported_on_target(
cls,
node: Node,
neutron_target_spec: NeutronTargetSpec,
parameters_mapping: dict[str, Parameter],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,9 @@ def _is_supported_in_IR(

return True

@staticmethod
@classmethod
def _is_supported_on_target(
cls,
node: Node,
neutron_target_spec: NeutronTargetSpec,
parameters_mapping: dict[str, Parameter],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,9 @@ def _is_supported_in_IR(

return True

@staticmethod
@classmethod
def _is_supported_on_target(
cls,
node: Node,
neutron_target_spec: NeutronTargetSpec,
parameters_mapping: dict[str, Parameter],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,9 @@ def _all_io_shares_quantization_parameters(node: Node) -> bool:

return True

@staticmethod
@classmethod
def _is_supported_on_target(
cls,
node: Node,
neutron_target_spec: NeutronTargetSpec,
parameters_mapping: dict[str, Parameter],
Expand Down
Loading
Loading