Skip to content

Commit 10b5018

Browse files
authored
XNNPACKQuantizer: apply module filters to ops only, not weight nodes (#24056)
### Summary Annotators that include weight/bias `get_attr` nodes in the partition (conv, conv_relu, conv_bn, linear_relu, ...) ran `filter_fn` over every node. Those nodes have no `nn_module_stack`, so `set_module_type(torch.nn.Conv2d, ...)` and `set_module_name` never matched a conv in an exported graph. The filter now checks only the main op (conv/linear/add/mul), like the linear annotator, so a trailing relu is fused under its op's config. ### Test plan `python -m unittest backends.xnnpack.test.quantizer.test_xnnpack_quantizer backends.xnnpack.test.quantizer.test_pt2e_quantization` (new `test_set_module_type_conv2d` and `test_set_module_type_conv2d_relu` fail without the fix; `test_gru` fails before and after on macOS with NoQEngine). cc @GregoryComer @digantdesai @cbilgin @JakeStevens @nil-is-all
1 parent d15a629 commit 10b5018

2 files changed

Lines changed: 77 additions & 8 deletions

File tree

‎backends/xnnpack/quantizer/xnnpack_quantizer_utils.py‎

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ def _annotate_linear_relu(
223223
if _is_annotated(partition):
224224
continue
225225

226-
if filter_fn and any(not filter_fn(n) for n in partition):
226+
if filter_fn and not filter_fn(linear_node):
227227
continue
228228

229229
linear_node.meta[Q_ANNOTATION_KEY] = QuantizationAnnotation(
@@ -310,7 +310,7 @@ def _do_annotate_conv(
310310
if _is_annotated(partition) or skip:
311311
continue
312312

313-
if filter_fn and any(not filter_fn(n) for n in partition):
313+
if filter_fn and not filter_fn(conv_node):
314314
continue
315315

316316
conv_node.meta[Q_ANNOTATION_KEY] = QuantizationAnnotation(
@@ -373,7 +373,7 @@ def _do_annotate_conv_relu(
373373
if is_conv_transpose and groups != 1:
374374
continue
375375

376-
if filter_fn and any(not filter_fn(n) for n in partition):
376+
if filter_fn and not filter_fn(conv_node):
377377
continue
378378

379379
conv_node.meta[Q_ANNOTATION_KEY] = QuantizationAnnotation(
@@ -604,7 +604,7 @@ def _conv_bn(x, conv_weight, conv_bias, bn_weight, bn_bias, bn_rm, bn_rv):
604604
partition.append(bias_node)
605605
if _is_annotated(partition):
606606
continue
607-
if filter_fn and any(not filter_fn(n) for n in partition):
607+
if filter_fn and not filter_fn(conv_node):
608608
continue
609609

610610
# Annotate conv inputs and pattern output
@@ -786,7 +786,7 @@ def _annotate_add_relu( # noqa: C901
786786
if _is_annotated(partition):
787787
continue
788788

789-
if filter_fn and any(not filter_fn(n) for n in partition):
789+
if filter_fn and not filter_fn(add_node):
790790
continue
791791

792792
if _is_input_non_float_tensor(add_node):
@@ -845,7 +845,7 @@ def _annotate_add(
845845
if _is_annotated(partition):
846846
continue
847847

848-
if filter_fn and any(not filter_fn(n) for n in partition):
848+
if filter_fn and not filter_fn(add_node):
849849
continue
850850

851851
if _is_input_non_float_tensor(add_node):
@@ -916,7 +916,7 @@ def _annotate_mul_relu( # noqa: C901
916916
if _is_annotated(partition):
917917
continue
918918

919-
if filter_fn and any(not filter_fn(n) for n in partition):
919+
if filter_fn and not filter_fn(mul_node):
920920
continue
921921

922922
if _is_input_non_float_tensor(mul_node):
@@ -975,7 +975,7 @@ def _annotate_mul(
975975
if _is_annotated(partition):
976976
continue
977977

978-
if filter_fn and any(not filter_fn(n) for n in partition):
978+
if filter_fn and not filter_fn(mul_node):
979979
continue
980980

981981
if _is_input_non_float_tensor(mul_node):

‎backends/xnnpack/test/quantizer/test_xnnpack_quantizer.py‎

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,75 @@ def forward(self, x):
480480
]
481481
self._test_quantizer(m, example_inputs, quantizer, node_occurrence, node_list)
482482

483+
def test_set_module_type_conv2d(self):
484+
# The weight/bias of a conv are get_attr nodes without nn_module_stack,
485+
# so the module type filter must only be checked on the conv itself.
486+
class M(torch.nn.Module):
487+
def __init__(self) -> None:
488+
super().__init__()
489+
self.conv = torch.nn.Conv2d(3, 8, 3)
490+
self.linear = torch.nn.Linear(6, 6)
491+
492+
def forward(self, x):
493+
return self.linear(self.conv(x))
494+
495+
m = M().eval()
496+
example_inputs = (torch.randn(1, 3, 8, 8),)
497+
quantizer = XNNPACKQuantizer()
498+
quantization_config = get_symmetric_quantization_config(is_per_channel=True)
499+
quantizer.set_module_type(torch.nn.Conv2d, quantization_config)
500+
node_occurrence = {
501+
# conv weight only; the linear is not quantized
502+
torch.ops.quantized_decomposed.dequantize_per_channel.default: 1,
503+
# input and output of the conv
504+
torch.ops.quantized_decomposed.quantize_per_tensor.default: 2,
505+
torch.ops.quantized_decomposed.dequantize_per_tensor.default: 2,
506+
}
507+
node_list = [
508+
torch.ops.quantized_decomposed.quantize_per_tensor.default,
509+
torch.ops.quantized_decomposed.dequantize_per_tensor.default,
510+
torch.ops.aten.conv2d.default,
511+
torch.ops.quantized_decomposed.quantize_per_tensor.default,
512+
torch.ops.quantized_decomposed.dequantize_per_tensor.default,
513+
torch.ops.aten.linear.default,
514+
]
515+
self._test_quantizer(m, example_inputs, quantizer, node_occurrence, node_list)
516+
517+
def test_set_module_type_conv2d_relu(self):
518+
# The relu is called in the parent's forward, so only the conv carries
519+
# Conv2d in its nn_module_stack. The relu is fused under the conv's config.
520+
class M(torch.nn.Module):
521+
def __init__(self) -> None:
522+
super().__init__()
523+
self.conv = torch.nn.Conv2d(3, 8, 3)
524+
self.linear = torch.nn.Linear(6, 6)
525+
526+
def forward(self, x):
527+
return self.linear(torch.nn.functional.relu(self.conv(x)))
528+
529+
m = M().eval()
530+
example_inputs = (torch.randn(1, 3, 8, 8),)
531+
quantizer = XNNPACKQuantizer()
532+
quantization_config = get_symmetric_quantization_config(is_per_channel=True)
533+
quantizer.set_module_type(torch.nn.Conv2d, quantization_config)
534+
node_occurrence = {
535+
# conv weight only; the linear is not quantized
536+
torch.ops.quantized_decomposed.dequantize_per_channel.default: 1,
537+
# conv input and relu output, nothing between conv and relu
538+
torch.ops.quantized_decomposed.quantize_per_tensor.default: 2,
539+
torch.ops.quantized_decomposed.dequantize_per_tensor.default: 2,
540+
}
541+
node_list = [
542+
torch.ops.quantized_decomposed.quantize_per_tensor.default,
543+
torch.ops.quantized_decomposed.dequantize_per_tensor.default,
544+
torch.ops.aten.conv2d.default,
545+
torch.ops.aten.relu.default,
546+
torch.ops.quantized_decomposed.quantize_per_tensor.default,
547+
torch.ops.quantized_decomposed.dequantize_per_tensor.default,
548+
torch.ops.aten.linear.default,
549+
]
550+
self._test_quantizer(m, example_inputs, quantizer, node_occurrence, node_list)
551+
483552
def test_set_module_name_with_underscores(self) -> None:
484553
"""Test that if a module name has an underscore, we can still quantize it"""
485554

0 commit comments

Comments
 (0)