Skip to content

Commit 28fc8b3

Browse files
NXP backend: NXP backend: Add option to run test reference quantized in Python (#17733)
### Summary NXP tests run models delegated to Neutron using the NSYS simulator. To determine correct output, a reference model is run on the CPU. Before, there were 2 choices for the reference, either non-delegated .pte file running in c++, or the original non-quantized float32 PyTorch model running in Python. This PR adds a 3rd option (to run in quantized edge dialect in Python), as well as easy extension to 2 more options in the future. ### Test plan Unit-tests provided.
1 parent 49781c4 commit 28fc8b3

4 files changed

Lines changed: 90 additions & 31 deletions

File tree

backends/nxp/requirements-eiq.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
--index-url https://eiq.nxp.com/repository
2-
eiq_neutron_sdk==2.2.2
2+
eiq-neutron-sdk==2.2.2
33
eiq_nsys

backends/nxp/tests_models/executors.py

Lines changed: 77 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,15 @@
99
import os.path
1010
import shutil
1111
import subprocess
12+
from enum import Enum
1213
from os import mkdir
1314

1415
import numpy as np
1516
import torch
1617
from executorch.backends.nxp.backend.edge_helper import is_channels_last_dim_order
1718
from executorch.backends.nxp.backend.ir.converter.conversion import translator
1819
from executorch.backends.nxp.neutron_partitioner import NeutronPartitioner
19-
2020
from executorch.backends.nxp.tests_models.config_importer import test_config
21-
2221
from executorch.backends.nxp.tests_models.dataset_creator import RandomDatasetCreator
2322
from executorch.backends.nxp.tests_models.graph_verifier import GraphVerifier
2423
from executorch.backends.nxp.tests_models.model_input_spec import ModelInputSpec
@@ -28,13 +27,15 @@
2827
from executorch.backends.nxp.tests_models.outputs_dir_importer import outputs_dir
2928
from executorch.backends.nxp.tests_models.utils import (
3029
save_pte_program,
30+
to_quantized_edge_program,
3131
to_quantized_executorch_program,
3232
)
3333
from executorch.devtools.visualization.visualization_utils import (
3434
visualize_with_clusters,
3535
)
3636
from pytest_mock import MockerFixture
3737
from torch.export import ExportedProgram
38+
from torch.fx import GraphModule
3839

3940
logger = logging.getLogger(__name__)
4041

@@ -45,6 +46,14 @@
4546
NEUTRON_TEST_PATH = test_config.NEUTRON_TEST_PATH
4647

4748

49+
class ReferenceModel(Enum):
50+
QUANTIZED_EXECUTORCH_CPP = 0
51+
QUANTIZED_EDGE_PYTHON = 1
52+
# QUANTIZED_ATEN_PYTHON = 2 # Not implemented.
53+
# FLOAT_ATEN_PYTHON = 3 # Not implemented.
54+
FLOAT_PYTORCH_PYTHON = 4
55+
56+
4857
def _run_delegated_executorch_program(
4958
model,
5059
test_dir,
@@ -266,14 +275,27 @@ def store_results(
266275
output_array.tofile(bin_file_path)
267276

268277

269-
def _run_pytorch_program(
270-
model,
278+
def _run_python_program(
279+
model: torch.nn.Module | GraphModule,
271280
testing_dataset_dir,
272281
input_spec: list[ModelInputSpec],
273282
output_spec: list[torch.Tensor],
274283
cpu_results_dir,
275284
npu_results_dir,
276285
):
286+
"""Run a model in Python with channels first (contiguous) inputs.
287+
288+
:param model: Any PyTorch/ExecuTorch model runnable directly in python with channels first (contiguous) inputs.
289+
:param testing_dataset_dir: Directory containing testing data. The samples can be channels last (NHWC) or channels
290+
first (NCHW). The format must match the input_spec.dim_order. The data will be
291+
converted to channels first if needed.
292+
:param input_spec: List of ModelInputSpec defining the shape, type, and dimension order of each input.
293+
:param output_spec: List of output tensor specifications.
294+
:param cpu_results_dir: Directory where CPU results will be stored. The structure will match the existing structure
295+
of `npu_results_dir`.
296+
:param npu_results_dir: Directory where NPU results are already stored, to serve as reference directory structure
297+
for `cpu_results_dir`.
298+
"""
277299
all_outputs = []
278300

279301
for input_samples in read_prepared_samples(testing_dataset_dir, input_spec):
@@ -333,7 +355,7 @@ def convert_run_compare(
333355
dataset_creator=None,
334356
output_comparator=None,
335357
mocker: MockerFixture = None,
336-
run_cpu_version_in_pytorch: bool = False,
358+
reference_model: ReferenceModel = ReferenceModel.QUANTIZED_EXECUTORCH_CPP,
337359
use_qat: bool = False,
338360
):
339361
"""
@@ -347,7 +369,7 @@ def convert_run_compare(
347369
:param dataset_creator: Creator that should fill provided `dataset_dir` with model input samples.
348370
:param output_comparator: Comparator of results produced by NPU and CPU runs of the program.
349371
:param dlg_model_verifier: Graph verifier instance.
350-
:param run_cpu_version_in_pytorch: If True, runs CPU version in float32 PyTorch instead of quantized ExecuTorch.
372+
:param reference_model: Version of the model which will be run to obtain reference output data.
351373
:param mocker: Mocker instance used by visualizer.
352374
:param use_qat: If True, applies quantization-aware training before conversion (without the QAT training).
353375
"""
@@ -393,25 +415,55 @@ def convert_run_compare(
393415

394416
output_spec = _get_program_output_spec(delegated_program)
395417

396-
if run_cpu_version_in_pytorch:
397-
_run_pytorch_program(
398-
model,
399-
testing_dataset_dir,
400-
input_spec,
401-
output_spec,
402-
cpu_results_dir,
403-
npu_results_dir,
404-
)
405-
else:
406-
_run_non_delegated_executorch_program(
407-
model,
408-
test_dir,
409-
test_name,
410-
calibration_dataset_dir,
411-
testing_dataset_dir,
412-
input_spec,
413-
cpu_results_dir,
414-
)
418+
match reference_model:
419+
case ReferenceModel.QUANTIZED_EXECUTORCH_CPP:
420+
# Lower to quantized executorch program, export to `.pte` file and run in c++ using
421+
# examples/nxp/executor_runner/nxp_executor_runner.cpp
422+
_run_non_delegated_executorch_program(
423+
model,
424+
test_dir,
425+
test_name,
426+
calibration_dataset_dir,
427+
testing_dataset_dir,
428+
input_spec,
429+
cpu_results_dir,
430+
)
431+
432+
case ReferenceModel.QUANTIZED_EDGE_PYTHON:
433+
# Lower to quantized edge program and run in Python.
434+
non_delegated_edge_program = (
435+
to_quantized_edge_program(
436+
model,
437+
input_spec,
438+
calibration_dataset_dir,
439+
delegate_to_npu=False,
440+
use_qat=use_qat,
441+
)
442+
.exported_program()
443+
.module()
444+
)
445+
_run_python_program(
446+
non_delegated_edge_program,
447+
testing_dataset_dir,
448+
input_spec,
449+
output_spec,
450+
cpu_results_dir,
451+
npu_results_dir,
452+
)
453+
454+
case ReferenceModel.FLOAT_PYTORCH_PYTHON:
455+
# Run the PyTorch nn.Module directly in Python.
456+
_run_python_program(
457+
model,
458+
testing_dataset_dir,
459+
input_spec,
460+
output_spec,
461+
cpu_results_dir,
462+
npu_results_dir,
463+
)
464+
465+
case _:
466+
raise ValueError(f"Unsupported reference model: `{reference_model}`.")
415467

416468
output_tensor_spec = _get_program_output_spec(delegated_program)
417469

backends/nxp/tests_models/test_cifarnet.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@
1010

1111
from executorch.backends.nxp.tests_models.config_importer import test_config
1212
from executorch.backends.nxp.tests_models.dataset_creator import CopyDatasetCreator
13-
from executorch.backends.nxp.tests_models.executors import convert_run_compare
13+
14+
from executorch.backends.nxp.tests_models.executors import (
15+
convert_run_compare,
16+
ReferenceModel,
17+
)
1418
from executorch.backends.nxp.tests_models.graph_verifier import (
1519
BaseGraphVerifier,
1620
NonDelegatedNode,
@@ -57,9 +61,8 @@ def test_cifarnet(mocker, cifar_test_files, channels_last):
5761

5862
non_dlg_nodes = [NonDelegatedNode("aten__softmax_default", 1)]
5963

60-
mse = 2.4e-3 if channels_last else 1e-3
6164
comparator = NumericalStatsOutputComparator(
62-
max_mse_error=mse, is_classification_task=True
65+
max_mse_error=1.0e-3, is_classification_task=True
6366
)
6467
convert_run_compare(
6568
model,
@@ -71,7 +74,11 @@ def test_cifarnet(mocker, cifar_test_files, channels_last):
7174
# Run the channels last reference in PyTorch as the ExecuTorch CPU model contains incorrectly
7275
# lowered channels last convolution weights, which cause incorrect inference results. The issue
7376
# is caused by ExecuTorch (not NXP). https://github.com/pytorch/executorch/issues/16464
74-
run_cpu_version_in_pytorch=channels_last,
77+
reference_model=(
78+
ReferenceModel.QUANTIZED_EDGE_PYTHON
79+
if channels_last
80+
else ReferenceModel.QUANTIZED_EXECUTORCH_CPP
81+
),
7582
)
7683

7784

examples/nxp/setup.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ set -u
88
EIQ_PYPI_URL="${EIQ_PYPI_URL:-https://eiq.nxp.com/repository}"
99

1010
# Install eIQ Neutron dependencies - SDK and simulator
11-
pip install --index-url ${EIQ_PYPI_URL} eiq_neutron_sdk==2.2.2 eiq_nsys
11+
pip install --index-url ${EIQ_PYPI_URL} eiq-neutron-sdk==2.2.2 eiq_nsys
1212

1313
# Get the directory of the current script
1414
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

0 commit comments

Comments
 (0)