Skip to content

Commit bf7bbd2

Browse files
NXP backend: Enable prelu with new Neutron flow
1 parent d608b1f commit bf7bbd2

7 files changed

Lines changed: 183 additions & 148 deletions

File tree

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

Lines changed: 7 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
# This source code is licensed under the BSD-style license found in the
44
# LICENSE file in the root directory of this source tree.
55

6-
from executorch.backends.nxp.backend.data_format import NXP_NODE_FORMAT
6+
import torch
7+
78
from executorch.backends.nxp.backend.ir.converter.node_converter import (
89
CustomDelegationOptions,
910
NodeConverter,
@@ -24,38 +25,17 @@ def _is_supported_on_target(
2425
parameters_mapping: dict[str, Parameter],
2526
custom_delegation_options: CustomDelegationOptions,
2627
) -> bool:
27-
node_shape = node.meta["val"].shape
28-
rank = len(node_shape)
29-
30-
# According to Neutron spec., PReLU can be done only on 4D tensors
31-
if rank != 4:
32-
return False
33-
34-
ch_idx, h_idx, w_idx = PReLUConverter._get_channel_spatial_indices(node)
35-
# According to Neutron spec., size of channels must be divisible by num_macs.
36-
num_macs = neutron_target_spec.get_num_macs()
37-
if node_shape[ch_idx] % num_macs != 0:
28+
if not NodeConverter.inputs_satisfy_broadcast_condition(node):
3829
return False
3930

40-
# According to Neutron spec., height * width cannot be greater than a given constant.
41-
if node_shape[w_idx] * node_shape[h_idx] > 4096:
31+
supported_types = [torch.int8, torch.uint8]
32+
if not NodeConverter.uses_quantization_type_for_io(
33+
node, supported_types, [0, 1], [0]
34+
):
4235
return False
4336

4437
return True
4538

46-
@staticmethod
47-
def _get_channel_spatial_indices(node: Node):
48-
if node.meta[NXP_NODE_FORMAT].is_channels_first():
49-
ch_idx = 1
50-
h_idx = 2
51-
w_idx = 3
52-
else:
53-
ch_idx = 3
54-
h_idx = 1
55-
w_idx = 2
56-
57-
return ch_idx, h_idx, w_idx
58-
5939
@staticmethod
6040
def _is_supported_in_IR(
6141
node: Node,

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ def test__basic_nsys_inference_qat(self, mocker, request):
106106
),
107107
pytest.param(
108108
[ModelInputSpec((10,)), ModelInputSpec((1, 1))],
109-
id="2 inputs 2D, num_elems of input == num_elems of output",
109+
id="2 inputs 1D + 2D, num_elems of input == num_elems of output",
110110
),
111111
],
112112
)

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ def test__basic_nsys_inference_qat(self, mocker, request, x_input_shape):
9292
),
9393
pytest.param(
9494
[ModelInputSpec((10,)), ModelInputSpec((1, 1))],
95-
id="2 inputs 2D, num_elems of input == num_elems of output",
95+
id="2 inputs 1D + 2D, num_elems of input == num_elems of output",
9696
),
9797
],
9898
)

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

Lines changed: 156 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,32 @@
99
from executorch.backends.nxp.backend.edge_program_converter import (
1010
EdgeProgramToIRConverter,
1111
)
12-
from executorch.backends.nxp.tests.executors import (
13-
convert_run_compare,
14-
graph_contains_any_of_ops,
12+
from executorch.backends.nxp.tests.executors import graph_contains_any_of_ops
13+
from executorch.backends.nxp.tests.graph_verifier import DetailedGraphVerifier
14+
from executorch.backends.nxp.tests.model_output_comparator import (
15+
AllCloseOutputComparator,
1516
)
1617
from executorch.backends.nxp.tests.models import (
18+
ConvPReLUModule,
1719
LinearPReLUModule,
1820
TwoPartitionPReLUModel,
1921
)
22+
23+
from executorch.backends.nxp.tests.nsys_testing import lower_run_compare
24+
from executorch.backends.nxp.tests.ops_aliases import (
25+
AddMm,
26+
Convolution,
27+
ExecutorchDelegateCall,
28+
GtScalar,
29+
MulTensor,
30+
PermuteCopy,
31+
Prelu,
32+
ViewCopy,
33+
WhereSelf,
34+
)
2035
from torch.export import ExportedProgram
2136
from executorch.backends.nxp.tests.use_qat import * # noqa F403
2237
from executorch.backends.nxp.tests.executorch_pipeline import to_quantized_edge_program
23-
from executorch.exir.dialects._ops import ops as exir_ops
2438

2539

2640
@pytest.fixture(autouse=True)
@@ -29,123 +43,148 @@ def reseed_model_per_test_run():
2943
np.random.seed(23)
3044

3145

32-
# noinspection PyProtectedMember
33-
ExecutorchDelegateCall = torch.ops.higher_order.executorch_call_delegate
34-
35-
36-
@pytest.mark.parametrize(
37-
"input_shape",
38-
[
39-
pytest.param((1, 8, 24, 32), id="4D."),
40-
],
41-
)
42-
def test_prelu_with_linear_quant_conversion(mocker, input_shape):
43-
converter_spy = mocker.spy(EdgeProgramToIRConverter, "convert_program")
44-
45-
# Run conversion
46-
channels = input_shape[-1]
47-
edge_program = to_quantized_edge_program(
48-
LinearPReLUModule(in_features=channels, out_features=channels),
46+
class TestPreluConverter:
47+
# noinspection PyMethodMayBeStatic
48+
def assert_delegated(
49+
self,
50+
model,
4951
input_shape,
50-
).exported_program()
51-
52-
# Capture generated entities
53-
neutron_ir_model, *_ = converter_spy.spy_return
54-
exported_program: ExportedProgram = converter_spy.call_args.args[1]
55-
56-
# Check `prelu` was not decomposed into simpler edge operators
57-
assert not graph_contains_any_of_ops(
58-
exported_program.graph,
52+
mocker,
53+
request,
54+
expected_delegated_ops=None,
55+
use_qat=False,
56+
):
57+
rank = len(input_shape)
58+
graph_verifier = DetailedGraphVerifier(
59+
mocker,
60+
expected_delegated_ops=expected_delegated_ops
61+
or {
62+
Prelu: 1,
63+
AddMm: 1,
64+
PermuteCopy: 1,
65+
ViewCopy: 0 if rank == 2 else 2,
66+
},
67+
expected_non_delegated_ops={},
68+
)
69+
comparator = AllCloseOutputComparator(atol=1)
70+
71+
lower_run_compare(
72+
model,
73+
input_shape,
74+
graph_verifier,
75+
request,
76+
output_comparator=comparator,
77+
remove_quant_io_ops=True,
78+
use_qat=use_qat,
79+
)
80+
81+
@pytest.mark.parametrize(
82+
"input_shape",
5983
[
60-
exir_ops.edge.aten.gt.Scalar,
61-
exir_ops.edge.aten.mul.Tensor,
62-
exir_ops.edge.aten.where.self,
84+
pytest.param((1,), id="1D."),
85+
pytest.param(
86+
(36, 487),
87+
id="2D incorrect results.",
88+
marks=pytest.mark.xfail(
89+
reason="AIR-14737: incorrect results", strict=True
90+
),
91+
),
92+
pytest.param(
93+
(87, 842),
94+
id="2D incorrect results alt.",
95+
marks=pytest.mark.xfail(
96+
reason="AIR-14737: incorrect results", strict=True
97+
),
98+
),
99+
pytest.param((7, 83), id="2D."),
100+
pytest.param(
101+
(1, 43, 183),
102+
id="3D incorrect results alt.",
103+
marks=pytest.mark.xfail(
104+
reason="AIR-14737: incorrect results", strict=True
105+
),
106+
),
107+
pytest.param((1, 43, 93), id="3D."),
108+
pytest.param((1, 4, 7, 8), id="4D."),
109+
pytest.param((1, 4, 3, 4, 14), id="5D."),
63110
],
64111
)
65-
66-
assert graph_contains_any_of_ops(
67-
exported_program.graph,
68-
[exir_ops.edge.aten.prelu.default],
69-
)
70-
71-
# Check `prelu` was delegated
72-
assert not graph_contains_any_of_ops(
73-
edge_program.graph,
74-
[exir_ops.edge.aten.prelu.default],
75-
)
76-
77-
input_data = (
78-
(2 * np.random.random(input_shape).astype(np.float32) - 1) * 50
79-
).astype(np.int8)
80-
81-
convert_run_compare(exported_program, input_data, tfl_model=neutron_ir_model)
82-
83-
84-
@pytest.mark.parametrize(
85-
"input_shape",
86-
[
87-
pytest.param((1, 8, 24, 32), id="4D."),
88-
],
89-
)
90-
def test_prelu_2_partitions(mocker, input_shape):
91-
# TODO (Martin) Add a channels last dim order variant of this test to verify correct partitioning.
92-
# Run conversion
93-
edge_program = to_quantized_edge_program(
94-
TwoPartitionPReLUModel(), [input_shape, input_shape]
95-
).exported_program()
96-
97-
# Check `prelu` was delegated
98-
assert not graph_contains_any_of_ops(
99-
edge_program.graph,
100-
[exir_ops.edge.aten.prelu.default],
101-
)
102-
103-
# Check there are two partitions
104-
edge_nodes = list(edge_program.graph.nodes)
105-
assert sum(n.target == ExecutorchDelegateCall for n in edge_nodes) == 2
106-
107-
108-
@pytest.mark.parametrize(
109-
"input_shape",
110-
[
111-
pytest.param((1,), id="1D not supported."),
112-
pytest.param((1, 8), id="2D not supported."),
113-
pytest.param((1, 8, 16), id="3D not supported."),
114-
pytest.param((1, 8, 16, 32, 64), id="5D not supported."),
115-
pytest.param((1, 8, 16, 31), id="channels must be divisible by NUM_MACS"),
116-
pytest.param((1, 8, 1024, 8), id="width*height is too big (limit 4096)"),
117-
],
118-
)
119-
def test_prelu__no_delegation__unsupported_conversion(mocker, input_shape):
120-
# Run conversion
121-
channels = input_shape[-1]
122-
edge_program = to_quantized_edge_program(
123-
LinearPReLUModule(in_features=channels, out_features=channels),
124-
input_shape,
125-
).exported_program()
126-
127-
# Check `prelu` was not delegated (only `linear` was)
128-
edge_nodes = list(edge_program.graph.nodes)
129-
assert sum(n.target == ExecutorchDelegateCall for n in edge_nodes) == 1
130-
131-
# Check `prelu` was decomposed into simpler edge operators
132-
assert graph_contains_any_of_ops(
133-
edge_program.graph,
134-
[
135-
exir_ops.edge.aten.gt.Scalar,
136-
],
137-
)
138-
139-
assert graph_contains_any_of_ops(
140-
edge_program.graph,
141-
[
142-
exir_ops.edge.aten.mul.Tensor,
143-
],
144-
)
145-
146-
assert graph_contains_any_of_ops(
147-
edge_program.graph,
112+
def test__basic_nsys_inference(self, mocker, request, input_shape):
113+
channels = input_shape[-1]
114+
model = LinearPReLUModule(in_features=channels, out_features=channels)
115+
116+
converter_spy = mocker.spy(EdgeProgramToIRConverter, "convert_program")
117+
118+
self.assert_delegated(model, input_shape, mocker, request)
119+
120+
# Capture generated entities
121+
exported_program: ExportedProgram = converter_spy.call_args.args[1]
122+
123+
# Check `prelu` was not decomposed into simpler edge operators
124+
assert not graph_contains_any_of_ops(
125+
exported_program.graph,
126+
[
127+
GtScalar,
128+
MulTensor,
129+
WhereSelf,
130+
],
131+
)
132+
133+
def test__basic_nsys_inference_qat(self, mocker, request):
134+
input_shape = (2, 4, 6, 7)
135+
channels = input_shape[-1]
136+
model = LinearPReLUModule(in_features=channels, out_features=channels)
137+
138+
self.assert_delegated(model, input_shape, mocker, request, use_qat=True)
139+
140+
def test__num_parameters_param(self, mocker, request):
141+
input_shape = (1, 43, 93)
142+
channels = input_shape[-1]
143+
num_parameters = input_shape[1]
144+
model = LinearPReLUModule(
145+
in_features=channels, out_features=channels, num_parameters=num_parameters
146+
)
147+
148+
self.assert_delegated(model, input_shape, mocker, request)
149+
150+
def test_prelu_2_partitions(self):
151+
input_shape = (1, 8, 24, 32)
152+
# Run conversion
153+
edge_program = to_quantized_edge_program(
154+
TwoPartitionPReLUModel(), [input_shape, input_shape]
155+
).exported_program()
156+
157+
# Check `prelu` was delegated
158+
assert not graph_contains_any_of_ops(
159+
edge_program.graph,
160+
[Prelu],
161+
)
162+
163+
# Check there are two partitions
164+
edge_nodes = list(edge_program.graph.nodes)
165+
assert sum(n.target == ExecutorchDelegateCall for n in edge_nodes) == 2
166+
167+
@pytest.mark.parametrize(
168+
"input_shape",
148169
[
149-
exir_ops.edge.aten.where.self,
170+
pytest.param((1, 8, 42, 24), id="4D."),
171+
pytest.param(
172+
(1, 8, 42, 21),
173+
id="4D incorrect results.",
174+
marks=pytest.mark.xfail(
175+
reason="AIR-14737: incorrect results", strict=True
176+
),
177+
),
150178
],
151179
)
180+
def test__w_conv(self, mocker, request, input_shape):
181+
channels = input_shape[1]
182+
model = ConvPReLUModule(in_channels=channels)
183+
expected_delegated_ops = {
184+
Prelu: 1,
185+
Convolution: 1,
186+
}
187+
188+
self.assert_delegated(
189+
model, input_shape, mocker, request, expected_delegated_ops
190+
)

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ def test__basic_nsys_inference_qat(self, mocker, request):
106106
),
107107
pytest.param(
108108
[ModelInputSpec((10,)), ModelInputSpec((1, 1))],
109-
id="2 inputs 2D, num_elems of input == num_elems of output",
109+
id="2 inputs 1D + 2D, num_elems of input == num_elems of output",
110110
),
111111
],
112112
)

0 commit comments

Comments
 (0)