Skip to content

Commit 2af5638

Browse files
NXP backend: Enable Neg with new Neutron flow (pytorch#20451)
### Summary Add tests verifying correct support for `neg` by the Neutron backend using the new Neutron MLIR flow. ### Test plan Unit tests provided. cc @robert-kalmar @JakeStevens @digantdesai @rascani
1 parent f4a6aac commit 2af5638

3 files changed

Lines changed: 84 additions & 83 deletions

File tree

backends/nxp/backend/ir/converter/node_converters/ops_converters/neg_converter.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
# LICENSE file in the root directory of this source tree.
55

66
import numpy as np
7+
import torch
78

89
from executorch.backends.nxp.backend import edge_helper
910
from executorch.backends.nxp.backend.ir.converter.node_converter import (
@@ -42,6 +43,12 @@ def _is_supported_in_IR(
4243
case _:
4344
return False # Everything else is unexpected.
4445

46+
supported_types = [torch.int8, torch.uint8]
47+
if not NodeConverter.uses_quantization_type_for_io(
48+
node, supported_types, [0], [0]
49+
):
50+
return False
51+
4552
return True
4653

4754
def convert(self, node: Node):

backends/nxp/tests/ir/converter/node_converter/test_neg_converter.py

Lines changed: 76 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,19 @@
44
# LICENSE file in the root directory of this source tree.
55

66
import numpy as np
7+
8+
# noinspection PyUnusedImports
79
import pytest
810
import torch
911

10-
from executorch.backends.nxp.backend.edge_program_converter import (
11-
EdgeProgramToIRConverter,
12-
)
13-
from executorch.backends.nxp.tests.executorch_pipeline import to_quantized_edge_program
14-
from executorch.backends.nxp.tests.executors import (
15-
convert_run_compare,
16-
graph_contains_any_of_ops,
17-
ToChannelFirstPreprocess,
18-
ToChannelLastPreprocess,
12+
from executorch.backends.nxp.tests.dataset_creator import (
13+
LinearRampDatasetCreator,
14+
RandomDatasetCreator,
1915
)
20-
from executorch.exir.dialects._ops import ops as exir_ops
16+
from executorch.backends.nxp.tests.graph_verifier import DetailedGraphVerifier
17+
from executorch.backends.nxp.tests.nsys_testing import lower_run_compare
18+
from executorch.backends.nxp.tests.ops_aliases import Convolution, Neg
19+
from executorch.backends.nxp.tests.use_qat import * # noqa F403
2120

2221

2322
@pytest.fixture(autouse=True)
@@ -26,11 +25,6 @@ def reseed_model_per_test_run():
2625
np.random.seed(23)
2726

2827

29-
# noinspection PyProtectedMember
30-
ExecutorchDelegateCall = torch.ops.higher_order.executorch_call_delegate
31-
Neg = exir_ops.edge.aten.neg.default
32-
33-
3428
class NegModule(torch.nn.Module):
3529

3630
def __init__(self):
@@ -45,79 +39,78 @@ class ConvNegModule(torch.nn.Module):
4539

4640
def __init__(self):
4741
super().__init__()
48-
self.conv = torch.nn.Conv2d(3, 3, 1)
42+
self.conv = torch.nn.Conv2d(4, 4, 1)
4943

5044
# noinspection PyMethodMayBeStatic
5145
def forward(self, x):
5246
x = self.conv(x)
5347
return -x
5448

5549

56-
@pytest.mark.parametrize(
57-
"input_shape",
58-
[
59-
pytest.param((8,), id="1D"),
60-
pytest.param((4, 2), id="2D"),
61-
pytest.param((1, 2, 3), id="3D"),
62-
pytest.param((1, 2, 3, 4), id="4D"),
63-
],
64-
)
65-
def test_convert_neg(mocker, input_shape):
66-
model = NegModule()
67-
68-
converter_spy = mocker.spy(EdgeProgramToIRConverter, "convert_program")
69-
delegated_ep = to_quantized_edge_program(model, input_shape).exported_program()
70-
71-
# Make sure the `neg` was delegated.
72-
assert graph_contains_any_of_ops(delegated_ep.graph, [ExecutorchDelegateCall])
73-
assert not graph_contains_any_of_ops(delegated_ep.graph, [Neg])
74-
75-
# Verify correct behavior of the converted NeutronIR model.
76-
intermediate_ep = converter_spy.call_args.args[1]
77-
neutron_ir_model, *_ = converter_spy.spy_return
78-
79-
input_data = (
80-
np.random.random(input_shape).astype(np.float32) * 256.0 - 128.0
81-
).astype(np.int8)
82-
83-
# Make sure the tested program contains the `neg`.
84-
assert graph_contains_any_of_ops(intermediate_ep.graph, [Neg])
85-
86-
convert_run_compare(
87-
intermediate_ep,
88-
tfl_model=neutron_ir_model,
89-
input_data=input_data,
90-
)
91-
92-
93-
def test_convert_neg__channels_last(mocker):
94-
model = ConvNegModule()
95-
input_shape = (1, 3, 4, 5)
96-
97-
converter_spy = mocker.spy(EdgeProgramToIRConverter, "convert_program")
98-
delegated_ep = to_quantized_edge_program(
99-
model, input_shape, use_neutron_for_format_conversion=False
100-
).exported_program()
101-
102-
# Make sure the `neg` was delegated.
103-
assert graph_contains_any_of_ops(delegated_ep.graph, [ExecutorchDelegateCall])
104-
assert not graph_contains_any_of_ops(delegated_ep.graph, [Neg])
105-
106-
# Verify correct behavior of the converted NeutronIR model.
107-
intermediate_ep = converter_spy.call_args.args[1]
108-
neutron_ir_model, *_ = converter_spy.spy_return
109-
110-
input_data = (
111-
np.random.random(input_shape).astype(np.float32) * 256.0 - 128.0
112-
).astype(np.int8)
113-
114-
# Make sure the tested program contains the `neg`.
115-
assert graph_contains_any_of_ops(intermediate_ep.graph, [Neg])
116-
117-
convert_run_compare(
118-
intermediate_ep,
119-
tfl_model=neutron_ir_model,
120-
input_data=input_data,
121-
tflite_input_preprocess=ToChannelLastPreprocess(),
122-
tflite_output_preprocess=ToChannelFirstPreprocess(),
50+
class TestNeg:
51+
@pytest.mark.parametrize(
52+
"input_shape",
53+
[
54+
pytest.param((8,), id="1D"),
55+
pytest.param((4, 2), id="2D"),
56+
pytest.param((1, 2, 6), id="3D"),
57+
pytest.param((1, 5, 3, 4), id="4D"),
58+
],
12359
)
60+
def test__basic_nsys_inference(self, mocker, request, input_shape):
61+
model = NegModule()
62+
63+
graph_verifier = DetailedGraphVerifier(
64+
mocker, expected_delegated_ops={Neg: 1}, expected_non_delegated_ops={}
65+
)
66+
67+
lower_run_compare(
68+
model,
69+
input_shape,
70+
graph_verifier,
71+
request,
72+
dataset_creator=RandomDatasetCreator(low=-1.0, high=1.0),
73+
)
74+
75+
def test__all_possible_values(self, mocker, request, use_qat):
76+
# Use 256 elements so that, after quantization to int8, the input can
77+
# cover the full discrete range [-128, 127].
78+
# The dataset is generated as a linear float ramp and later quantized,
79+
# which effectively exercises all int8 values.
80+
input_shape = (256,)
81+
model = NegModule()
82+
83+
graph_verifier = DetailedGraphVerifier(
84+
mocker, expected_delegated_ops={Neg: 1}, expected_non_delegated_ops={}
85+
)
86+
87+
lower_run_compare(
88+
model,
89+
input_shape,
90+
graph_verifier,
91+
request,
92+
dataset_creator=LinearRampDatasetCreator(low=-1.0, high=1.0),
93+
use_qat=use_qat,
94+
)
95+
96+
def test__channels_first_input(self, mocker, request):
97+
# Use 256 elements so that, after quantization to int8, the input can
98+
# cover the full discrete range [-128, 127].
99+
# The dataset is generated as a linear float ramp and later quantized,
100+
# which effectively exercises all int8 values.
101+
input_shape = (1, 4, 8, 8)
102+
model = ConvNegModule()
103+
104+
graph_verifier = DetailedGraphVerifier(
105+
mocker,
106+
expected_delegated_ops={Neg: 1, Convolution: 1},
107+
expected_non_delegated_ops={},
108+
)
109+
110+
lower_run_compare(
111+
model,
112+
input_shape,
113+
graph_verifier,
114+
request,
115+
dataset_creator=LinearRampDatasetCreator(low=-1.0, high=1.0),
116+
)

backends/nxp/tests/ops_aliases.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
MaxPool2DWithIndices = exir_ops.edge.aten.max_pool2d_with_indices.default
3737
MeanDim = exir_ops.edge.aten.mean.dim
3838
MulTensor = exir_ops.edge.aten.mul.Tensor
39+
Neg = exir_ops.edge.aten.neg.default
3940
PermuteCopy = exir_ops.edge.aten.permute_copy.default
4041
QuantizePerChannel = exir_ops.edge.quantized_decomposed.quantize_per_channel.default
4142
QuantizePerTensor = exir_ops.edge.quantized_decomposed.quantize_per_tensor.default

0 commit comments

Comments
 (0)