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
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
# LICENSE file in the root directory of this source tree.

import numpy as np
import torch

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

supported_types = [torch.int8, torch.uint8]
if not NodeConverter.uses_quantization_type_for_io(
node, supported_types, [0], [0]
):
return False

return True

def convert(self, node: Node):
Expand Down
159 changes: 76 additions & 83 deletions backends/nxp/tests/ir/converter/node_converter/test_neg_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,19 @@
# LICENSE file in the root directory of this source tree.

import numpy as np

# noinspection PyUnusedImports
import pytest
import torch

from executorch.backends.nxp.backend.edge_program_converter import (
EdgeProgramToIRConverter,
)
from executorch.backends.nxp.tests.executorch_pipeline import to_quantized_edge_program
from executorch.backends.nxp.tests.executors import (
convert_run_compare,
graph_contains_any_of_ops,
ToChannelFirstPreprocess,
ToChannelLastPreprocess,
from executorch.backends.nxp.tests.dataset_creator import (
LinearRampDatasetCreator,
RandomDatasetCreator,
)
from executorch.exir.dialects._ops import ops as exir_ops
from executorch.backends.nxp.tests.graph_verifier import DetailedGraphVerifier
from executorch.backends.nxp.tests.nsys_testing import lower_run_compare
from executorch.backends.nxp.tests.ops_aliases import Convolution, Neg
from executorch.backends.nxp.tests.use_qat import * # noqa F403


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


# noinspection PyProtectedMember
ExecutorchDelegateCall = torch.ops.higher_order.executorch_call_delegate
Neg = exir_ops.edge.aten.neg.default


class NegModule(torch.nn.Module):

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

def __init__(self):
super().__init__()
self.conv = torch.nn.Conv2d(3, 3, 1)
self.conv = torch.nn.Conv2d(4, 4, 1)
Comment thread
roman-janik-nxp marked this conversation as resolved.

# noinspection PyMethodMayBeStatic
def forward(self, x):
x = self.conv(x)
return -x


@pytest.mark.parametrize(
"input_shape",
[
pytest.param((8,), id="1D"),
pytest.param((4, 2), id="2D"),
pytest.param((1, 2, 3), id="3D"),
pytest.param((1, 2, 3, 4), id="4D"),
],
)
def test_convert_neg(mocker, input_shape):
model = NegModule()

converter_spy = mocker.spy(EdgeProgramToIRConverter, "convert_program")
delegated_ep = to_quantized_edge_program(model, input_shape).exported_program()

# Make sure the `neg` was delegated.
assert graph_contains_any_of_ops(delegated_ep.graph, [ExecutorchDelegateCall])
assert not graph_contains_any_of_ops(delegated_ep.graph, [Neg])

# Verify correct behavior of the converted NeutronIR model.
intermediate_ep = converter_spy.call_args.args[1]
neutron_ir_model, *_ = converter_spy.spy_return

input_data = (
np.random.random(input_shape).astype(np.float32) * 256.0 - 128.0
).astype(np.int8)

# Make sure the tested program contains the `neg`.
assert graph_contains_any_of_ops(intermediate_ep.graph, [Neg])

convert_run_compare(
intermediate_ep,
tfl_model=neutron_ir_model,
input_data=input_data,
)


def test_convert_neg__channels_last(mocker):
model = ConvNegModule()
input_shape = (1, 3, 4, 5)

converter_spy = mocker.spy(EdgeProgramToIRConverter, "convert_program")
delegated_ep = to_quantized_edge_program(
model, input_shape, use_neutron_for_format_conversion=False
).exported_program()

# Make sure the `neg` was delegated.
assert graph_contains_any_of_ops(delegated_ep.graph, [ExecutorchDelegateCall])
assert not graph_contains_any_of_ops(delegated_ep.graph, [Neg])

# Verify correct behavior of the converted NeutronIR model.
intermediate_ep = converter_spy.call_args.args[1]
neutron_ir_model, *_ = converter_spy.spy_return

input_data = (
np.random.random(input_shape).astype(np.float32) * 256.0 - 128.0
).astype(np.int8)

# Make sure the tested program contains the `neg`.
assert graph_contains_any_of_ops(intermediate_ep.graph, [Neg])

convert_run_compare(
intermediate_ep,
tfl_model=neutron_ir_model,
input_data=input_data,
tflite_input_preprocess=ToChannelLastPreprocess(),
tflite_output_preprocess=ToChannelFirstPreprocess(),
class TestNeg:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

You removed tests with multiple different input shapes, and left only 2, both with multiples of num_macs. Why don't you test with more shapes?

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.

I was inspired by Exp operator. As the op is internally Sub, I considered it tested with various shapes there. Do you think I should add them back?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Personally I would definitely test with a similar coverage of shapes as we do for other similar operators. We never know what sort of edge case it might uncover. Or if we ever decide to support the operator some other way (e.g. we decide Mul(x, -1) is more efficient or whatever), relying on the tests of some other operator may be invalid.

@pytest.mark.parametrize(
"input_shape",
[
pytest.param((8,), id="1D"),
pytest.param((4, 2), id="2D"),
pytest.param((1, 2, 6), id="3D"),
pytest.param((1, 5, 3, 4), id="4D"),
],
)
def test__basic_nsys_inference(self, mocker, request, input_shape):
model = NegModule()

graph_verifier = DetailedGraphVerifier(
mocker, expected_delegated_ops={Neg: 1}, expected_non_delegated_ops={}
)

lower_run_compare(
model,
input_shape,
graph_verifier,
request,
dataset_creator=RandomDatasetCreator(low=-1.0, high=1.0),
)

def test__all_possible_values(self, mocker, request, use_qat):
# Use 256 elements so that, after quantization to int8, the input can
Comment thread
roman-janik-nxp marked this conversation as resolved.
# cover the full discrete range [-128, 127].
# The dataset is generated as a linear float ramp and later quantized,
# which effectively exercises all int8 values.
input_shape = (256,)
model = NegModule()

graph_verifier = DetailedGraphVerifier(
mocker, expected_delegated_ops={Neg: 1}, expected_non_delegated_ops={}
)

lower_run_compare(
model,
input_shape,
graph_verifier,
request,
dataset_creator=LinearRampDatasetCreator(low=-1.0, high=1.0),
use_qat=use_qat,
)

def test__channels_first_input(self, mocker, request):
# Use 256 elements so that, after quantization to int8, the input can
# cover the full discrete range [-128, 127].
# The dataset is generated as a linear float ramp and later quantized,
# which effectively exercises all int8 values.
input_shape = (1, 4, 8, 8)
model = ConvNegModule()

graph_verifier = DetailedGraphVerifier(
mocker,
expected_delegated_ops={Neg: 1, Convolution: 1},
expected_non_delegated_ops={},
)

lower_run_compare(
model,
input_shape,
graph_verifier,
request,
dataset_creator=LinearRampDatasetCreator(low=-1.0, high=1.0),
)
1 change: 1 addition & 0 deletions backends/nxp/tests/ops_aliases.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
MaxPool2DWithIndices = exir_ops.edge.aten.max_pool2d_with_indices.default
MeanDim = exir_ops.edge.aten.mean.dim
MulTensor = exir_ops.edge.aten.mul.Tensor
Neg = exir_ops.edge.aten.neg.default
PermuteCopy = exir_ops.edge.aten.permute_copy.default
QuantizePerChannel = exir_ops.edge.quantized_decomposed.quantize_per_channel.default
QuantizePerTensor = exir_ops.edge.quantized_decomposed.quantize_per_tensor.default
Expand Down
Loading