Skip to content

Commit a42c8f4

Browse files
authored
NXP backend: Support for Sigmoid operator conversion (#13041)
### Summary Adds implementation of converter and quantization pattern for `aten.sigmoid` operator. ### Test plan Unit tests that covers Sigmoid operator were added.
1 parent 0248897 commit a42c8f4

File tree

9 files changed

+185
-18
lines changed

9 files changed

+185
-18
lines changed

backends/nxp/backend/edge_program_converter.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
exir_ops.edge.aten.relu.default: ReLUConverter, # noqa F405
4040
exir_ops.edge.aten._softmax.default: SoftmaxConverter, # noqa F405
4141
exir_ops.edge.aten.view_copy.default: ViewCopyConverter, # noqa F405
42+
exir_ops.edge.aten.sigmoid.default: SigmoidConverter, # noqa F405
4243
}
4344

4445

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@
4646
from executorch.backends.nxp.backend.ir.converter.node_converters.ops_converters.relu_converter import (
4747
ReLUConverter,
4848
)
49+
from executorch.backends.nxp.backend.ir.converter.node_converters.ops_converters.sigmoid_converter import (
50+
SigmoidConverter,
51+
)
4952
from executorch.backends.nxp.backend.ir.converter.node_converters.ops_converters.softmax_converter import (
5053
SoftmaxConverter,
5154
)
@@ -72,4 +75,5 @@
7275
"AbsConverter",
7376
"AdaptiveAvgPool2dConverter",
7477
"HardTanhConverter",
78+
"SigmoidConverter",
7579
]

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ def _is_supported_in_IR(
2525
return True
2626

2727
def convert(self, node: Node):
28+
self.assert_convertible(node)
29+
2830
t_op = self._create_tflite_op_with_io_tensors(node)
2931
t_op.opcode_index = self.builder.op_code_index_for_op_type(BuiltinOperator.RELU)
3032

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Copyright 2025 NXP
2+
# All rights reserved.
3+
#
4+
# This source code is licensed under the BSD-style license found in the
5+
# LICENSE file in the root directory of this source tree.
6+
7+
from executorch.backends.nxp.backend.ir.converter.node_converter import (
8+
NodeConverter,
9+
Target,
10+
)
11+
from executorch.backends.nxp.backend.ir.lib.tflite.BuiltinOperator import (
12+
BuiltinOperator,
13+
)
14+
from torch.fx import Node
15+
from torch.nn import Parameter
16+
17+
18+
class SigmoidConverter(NodeConverter):
19+
@staticmethod
20+
def _is_supported_on_target(target: Target) -> bool:
21+
match target:
22+
case Target.RT700:
23+
return True
24+
25+
case _:
26+
return False
27+
28+
@staticmethod
29+
def _is_supported_in_IR(
30+
node: Node, parameters_mapping: dict[str, Parameter]
31+
) -> bool:
32+
return True
33+
34+
def convert(self, node: Node):
35+
self.assert_convertible(node)
36+
37+
t_op = self._create_tflite_op_with_io_tensors(node)
38+
t_op.opcode_index = self.builder.op_code_index_for_op_type(
39+
BuiltinOperator.LOGISTIC
40+
)
41+
42+
self.builder.append_operators([t_op])

backends/nxp/neutron_partitioner.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@ def tag_qdq_clusters(self, nodes: List[torch.fx.Node]):
203203
exir_ops.edge.aten.relu.default: ReLUConverter, # noqa F405
204204
exir_ops.edge.aten._softmax.default: SoftmaxConverter, # noqa F405
205205
exir_ops.edge.aten.view_copy.default: ViewCopyConverter, # noqa F405
206+
exir_ops.edge.aten.sigmoid.default: SigmoidConverter, # noqa F405
206207
}
207208

208209

backends/nxp/quantizer/neutron_quantizer.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
ReluPattern,
3333
ReshapePattern,
3434
SharedSpecPattern,
35+
SigmoidPattern,
3536
SoftMaxPattern,
3637
ViewPattern,
3738
)
@@ -217,6 +218,7 @@ def __init__(self):
217218
NeutronAtenQuantizer(ReluPattern(), static_qconfig),
218219
NeutronAtenQuantizer(ReluInPlacePattern(), static_qconfig),
219220
NeutronAtenQuantizer(ReshapePattern(), static_qconfig),
221+
NeutronAtenQuantizer(SigmoidPattern(), static_qconfig),
220222
NeutronAtenQuantizer(SoftMaxPattern(), static_qconfig),
221223
NeutronAtenQuantizer(ViewPattern(), static_qconfig),
222224
]

backends/nxp/quantizer/patterns.py

Lines changed: 40 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,31 @@ def partition_types(self):
408408
return [torch.ops.aten.view.default]
409409

410410

411+
def get_anchors_for_softmax_like_operators(
412+
fused_partition: List[fx.GraphModule],
413+
) -> PartitionAnchors:
414+
node = fused_partition[0].nodes[-1]
415+
assert len(fused_partition[0].input_nodes) == 1
416+
417+
qspec = FixedQParamsQuantizationSpec(
418+
dtype=torch.int8,
419+
scale=1.0 / 256.0,
420+
zero_point=-128,
421+
quant_min=-128,
422+
quant_max=127,
423+
qscheme=torch.per_tensor_affine,
424+
)
425+
426+
return PartitionAnchors(
427+
inputs=[(node, 0)],
428+
weights=[],
429+
biases=[],
430+
output=[
431+
(node, qspec),
432+
],
433+
)
434+
435+
411436
class SoftMaxPattern(QuantizationPattern):
412437
"""
413438
Quantizer for Softmax operator.
@@ -421,23 +446,20 @@ def partition_types(self) -> List[OpOverload]:
421446
def get_anchors(
422447
self, gm: fx.GraphModule, fused_partition: List[fx.GraphModule]
423448
) -> PartitionAnchors:
424-
node = fused_partition[0].nodes[-1]
425-
assert len(fused_partition[0].input_nodes) == 1
449+
return get_anchors_for_softmax_like_operators(fused_partition)
426450

427-
qspec = FixedQParamsQuantizationSpec(
428-
dtype=torch.int8,
429-
scale=1.0 / 256.0,
430-
zero_point=-128,
431-
quant_min=-128,
432-
quant_max=127,
433-
qscheme=torch.per_tensor_affine,
434-
)
435451

436-
return PartitionAnchors(
437-
inputs=[(node, 0)],
438-
weights=[],
439-
biases=[],
440-
output=[
441-
(node, qspec),
442-
],
443-
)
452+
class SigmoidPattern(QuantizationPattern):
453+
"""
454+
Quantizer for Sigmoid operator.
455+
456+
The quantization of Sigmoid output is fixed to scale 1/256, zero point -128, dtype int8.
457+
"""
458+
459+
def partition_types(self) -> List[OpOverload]:
460+
return [torch.ops.aten.sigmoid.default]
461+
462+
def get_anchors(
463+
self, gm: fx.GraphModule, fused_partition: List[fx.GraphModule]
464+
) -> PartitionAnchors:
465+
return get_anchors_for_softmax_like_operators(fused_partition)
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# Copyright 2025 NXP
2+
# All rights reserved.
3+
#
4+
# This source code is licensed under the BSD-style license found in the
5+
# LICENSE file in the root directory of this source tree.
6+
7+
8+
import numpy as np
9+
import pytest
10+
import torch
11+
12+
from executorch.backends.nxp.backend.edge_program_converter import (
13+
EdgeProgramToIRConverter,
14+
)
15+
from executorch.backends.nxp.tests.executorch_pipeline import to_quantized_edge_program
16+
from executorch.backends.nxp.tests.executors import (
17+
convert_run_compare,
18+
ToNCHWPreprocess,
19+
ToNHWCPreprocess,
20+
)
21+
from executorch.backends.nxp.tests.models import ConvWithSigmoid
22+
from torch import nn
23+
from torch.export import ExportedProgram
24+
25+
26+
@pytest.fixture(autouse=True)
27+
def reseed_model_per_test_run():
28+
torch.manual_seed(23)
29+
np.random.seed(23)
30+
31+
32+
def test_conv_sigmoid(mocker, input_shape: tuple[int] = (1, 3, 112, 112)):
33+
model = ConvWithSigmoid(conv_in_channels=input_shape[1])
34+
35+
converter_spy = mocker.spy(EdgeProgramToIRConverter, "convert_program")
36+
37+
to_quantized_edge_program(model, input_shape).exported_program()
38+
39+
tflite_flatbuffers_model, io_formats = converter_spy.spy_return
40+
exported_program: ExportedProgram = converter_spy.call_args.args[1]
41+
42+
input_data = (np.random.random(input_shape) * 50).astype(np.int8)
43+
convert_run_compare(
44+
exported_program,
45+
tfl_model=tflite_flatbuffers_model,
46+
tflite_input_preprocess=ToNHWCPreprocess(),
47+
tflite_output_preprocess=ToNCHWPreprocess(),
48+
input_data=input_data,
49+
atol=1.0,
50+
)
51+
52+
53+
@pytest.mark.parametrize(
54+
"input_shape",
55+
[
56+
pytest.param((10,), id="Scalar"),
57+
pytest.param((10, 25), id="1D"),
58+
pytest.param((10, 25, 25), id="2D"),
59+
pytest.param((10, 3, 25, 25), id="3D"),
60+
pytest.param((10, 3, 25, 25, 25), id="4D"),
61+
],
62+
)
63+
def test_sigmoid_only(mocker, input_shape):
64+
model = nn.Sigmoid()
65+
66+
converter_spy = mocker.spy(EdgeProgramToIRConverter, "convert_program")
67+
68+
to_quantized_edge_program(model, input_shape).exported_program()
69+
70+
tflite_flatbuffers_model, io_formats = converter_spy.spy_return
71+
exported_program: ExportedProgram = converter_spy.call_args.args[1]
72+
73+
input_data = (np.random.random(input_shape) * 50).astype(np.int8)
74+
convert_run_compare(
75+
exported_program, tfl_model=tflite_flatbuffers_model, input_data=input_data
76+
)

backends/nxp/tests/models.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,23 @@ def forward(self, x):
8585
return self.softmax(x)
8686

8787

88+
class ConvWithSigmoid(torch.nn.Module):
89+
def __init__(self, conv_in_channels: int = 3):
90+
super().__init__()
91+
self.block = torch.nn.Sequential(
92+
torch.nn.Conv2d(
93+
in_channels=conv_in_channels,
94+
out_channels=3,
95+
kernel_size=(2, 2),
96+
stride=(2, 2),
97+
),
98+
torch.nn.Sigmoid(),
99+
)
100+
101+
def forward(self, x):
102+
return self.block(x)
103+
104+
88105
class LinearModule(torch.nn.Module):
89106
def __init__(self, bias: bool):
90107
super().__init__()

0 commit comments

Comments
 (0)