Skip to content

Commit 34c7933

Browse files
committed
Use default (non-shared) quantization params for HardTanh
1 parent 317fc37 commit 34c7933

File tree

3 files changed

+47
-11
lines changed

3 files changed

+47
-11
lines changed

backends/nxp/quantizer/neutron_quantizer.py

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ def partition_types(self):
169169
return [torch.ops.aten.relu_.default]
170170

171171

172-
class HardTanhPattern(SharedSpecPattern):
172+
class HardTanhPattern(QuantizationPattern):
173173
"""
174174
Quantizer for HardTanh operator. Shared quantization spec is selected, as activation functions usually follows
175175
computation layer.
@@ -178,8 +178,23 @@ class HardTanhPattern(SharedSpecPattern):
178178
def partition_types(self):
179179
return [torch.ops.aten.hardtanh.default]
180180

181+
def get_anchors(
182+
self, gm: fx.GraphModule, fused_partition: List[fx.GraphModule]
183+
) -> PartitionAnchors | None:
184+
node = fused_partition[0].nodes[-1]
185+
186+
return PartitionAnchors(
187+
inputs=[(node, 0)],
188+
weights=[],
189+
biases=[],
190+
output=[(node,)],
191+
)
192+
193+
def replacement_op(self):
194+
assert False
195+
181196

182-
class HardTanhInPlacePattern(SharedSpecPattern):
197+
class HardTanhInPlacePattern(QuantizationPattern):
183198
"""
184199
Quantizer for HardTanh operator with param inplace=True. Shared quantization spec is selected, as activation
185200
functions usually follows computation layer.
@@ -188,6 +203,21 @@ class HardTanhInPlacePattern(SharedSpecPattern):
188203
def partition_types(self):
189204
return [torch.ops.aten.hardtanh_.default]
190205

206+
def get_anchors(
207+
self, gm: fx.GraphModule, fused_partition: List[fx.GraphModule]
208+
) -> PartitionAnchors | None:
209+
node = fused_partition[0].nodes[-1]
210+
211+
return PartitionAnchors(
212+
inputs=[(node, 0)],
213+
weights=[],
214+
biases=[],
215+
output=[(node,)],
216+
)
217+
218+
def replacement_op(self):
219+
assert False
220+
191221

192222
class ReshapePattern(SharedSpecPattern):
193223
"""

backends/nxp/tests/executorch_pipeline.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ def _quantize_model(model, calibration_inputs: list[tuple[torch.Tensor]]):
2828
return m
2929

3030

31+
def get_random_float_data(input_shapes: tuple[int] | list[tuple[int]]):
32+
#TODO(Lukas): Replace with something more robust.
33+
return (torch.randn(input_shapes),) if type(input_shapes) is tuple \
34+
else tuple(torch.randn(input_shape) for input_shape in input_shapes)
35+
36+
3137
def to_quantized_edge_program(model: torch.nn.Module, input_shapes: tuple[int] | list[tuple[int]],
3238
operators_not_to_delegate: list[str] = None, target="imxrt700",
3339
neutron_converter_flavor="wrapper", remove_quant_io_ops=False)\
@@ -36,9 +42,7 @@ def to_quantized_edge_program(model: torch.nn.Module, input_shapes: tuple[int] |
3642
assert all([isinstance(input_shape, tuple) for input_shape in input_shapes]), ("For multiple inputs, provide"
3743
" list[tuple[int]].")
3844

39-
random_tensors = (torch.randn(input_shapes),) if type(input_shapes) is tuple \
40-
else tuple(torch.randn(input_shape) for input_shape in input_shapes)
41-
calibration_inputs = [random_tensors, random_tensors]
45+
calibration_inputs = [get_random_float_data(input_shapes) for _ in range(4)]
4246
example_input = (torch.ones(input_shapes),) if type(input_shapes) is tuple \
4347
else tuple(torch.ones(input_shape) for input_shape in input_shapes)
4448

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

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,15 @@ class Relu6ConvBlock(torch.nn.Module):
2222
def __init__(self, conv_in_channels: int = 3, inplace: bool = False):
2323
super().__init__()
2424
self.block = torch.nn.Sequential(
25-
torch.nn.Conv2d(in_channels=conv_in_channels, out_channels=64, kernel_size=(4, 4)),
26-
torch.nn.ReLU6(inplace=inplace)
25+
torch.nn.Conv2d(in_channels=conv_in_channels, out_channels=64, kernel_size=(4, 4)),
26+
torch.nn.ReLU6(inplace=inplace)
2727
)
2828

2929
def forward(self, x):
3030
return self.block(x)
3131

3232

33-
class CustomHardTanhBlock(torch.nn.Module):
33+
class ConvHardTanhBlock(torch.nn.Module):
3434
def __init__(self,
3535
conv_in_channels: int = 3,
3636
min_act_val: float = -1.,
@@ -70,12 +70,14 @@ def test_relu6_quant(mocker, input_shape: tuple[int], inplace: bool):
7070
atol=1.)
7171

7272

73-
@pytest.mark.parametrize('input_shape', [(1, 3, 128, 128), (1, 3, 256, 256)])
73+
@pytest.mark.parametrize('input_shape', [(1, 3, 16, 16), (1, 3, 32, 32)])
7474
@pytest.mark.parametrize('activation_range', list(HardTanhConverter.supported_modes_map.keys()))
7575
@pytest.mark.parametrize('inplace', [True, False])
7676
def test_custom_hardtanh_quant(mocker, input_shape: tuple[int], activation_range: tuple[int, int], inplace: bool):
77+
# TODO(Lukas): This test suffers from non-ideal testing random quantization, because we always use range <0,1>.
78+
# We should update this (decrease atol) when we have custom calibration dataset definition in place.
7779
min_val, max_val = activation_range
78-
model = CustomHardTanhBlock(
80+
model = ConvHardTanhBlock(
7981
conv_in_channels=input_shape[1],
8082
min_act_val=min_val,
8183
max_act_val=max_val,
@@ -98,4 +100,4 @@ def test_custom_hardtanh_quant(mocker, input_shape: tuple[int], activation_range
98100
tflite_input_preprocess=ToChannelLastPreprocess(),
99101
tflite_output_preprocess=ToChannelFirstPreprocess(),
100102
input_data=input_data,
101-
atol=1.)
103+
atol=2.)

0 commit comments

Comments
 (0)