Skip to content

Commit 3c47f9c

Browse files
authored
Arm backend: Add support for non-folded quantized BatchNorm (pytorch#21710)
* Support quantized batch norm when it follows an operator that it cannot be folded into. Change-Id: I55ef21492c521cddbd8282ad9bfe17f0e2c573d8 cc @digantdesai @freddan80 @per @zingo @oscarandersson8218 @mansnils @Sebastian-Larsson @robell @rascani Signed-off-by: Tom Allsop <tom.allsop@arm.com>
1 parent f0c211b commit 3c47f9c

4 files changed

Lines changed: 50 additions & 35 deletions

File tree

‎backends/arm/_passes/arm_pass_manager.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -745,6 +745,7 @@ def transform_for_annotation_pipeline(self, graph_module: GraphModule):
745745
ConvertEluFamilyToEluPass(tfa_pass=True),
746746
DecomposeGroupNormPass(tfa_pass=True),
747747
DecomposeLayerNormPass(tfa_pass=True),
748+
DecomposeBatchNormNoStatsPass(tfa_pass=True),
748749
DecomposeVarPass(tfa_pass=True),
749750
DecomposeMeanDimPass(graph_module, self.tosa_spec, tfa_pass=True),
750751
DecomposeAdaptiveAvgPool2dPass(tfa_pass=True),

‎backends/arm/_passes/decompose_batch_norm_no_stats.py‎

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,14 @@ def call(self, graph_module: torch.fx.GraphModule) -> PassResult: # noqa: C901
6161
):
6262
continue
6363

64+
if self.is_tfa_pass and getattr(node.args[0], "target", None) in (
65+
torch.ops.aten.conv1d.default,
66+
torch.ops.aten.conv2d.default,
67+
torch.ops.aten.conv3d.default,
68+
torch.ops.aten.convolution.default,
69+
):
70+
continue
71+
6472
if node.target in (
6573
torch.ops.aten.batch_norm.default,
6674
torch.ops.aten.native_batch_norm.default,
@@ -87,8 +95,17 @@ def call(self, graph_module: torch.fx.GraphModule) -> PassResult: # noqa: C901
8795
bias = args[2] if len(args) > 2 else None
8896
running_mean = args[3]
8997
running_var = args[4]
90-
if len(args) > 6:
91-
eps = args[6]
98+
eps_index = (
99+
7
100+
if node.target
101+
in (
102+
torch.ops.aten.batch_norm.default,
103+
torch.ops.aten.native_batch_norm.default,
104+
)
105+
else 6
106+
)
107+
if len(args) > eps_index:
108+
eps = args[eps_index]
92109

93110
# Determine shapes
94111
val = meta.get("val")
@@ -103,13 +120,20 @@ def call(self, graph_module: torch.fx.GraphModule) -> PassResult: # noqa: C901
103120
weights_shape[channel_axis] = shape[channel_axis]
104121
num_features = shape[channel_axis]
105122

106-
# Ops to use
107-
sub_op = exir_ops.edge.aten.sub.Tensor
108-
view_op = exir_ops.edge.aten.view_copy.default
109-
full_op = exir_ops.edge.aten.full.default
110-
add_op = exir_ops.edge.aten.add.Tensor
111-
rsqrt_op = exir_ops.edge.aten.rsqrt.default
112-
mul_op = exir_ops.edge.aten.mul.Tensor
123+
if self.is_tfa_pass:
124+
sub_op = torch.ops.aten.sub.Tensor
125+
view_op = torch.ops.aten.view_copy.default
126+
full_op = torch.ops.aten.full.default
127+
add_op = torch.ops.aten.add.Tensor
128+
rsqrt_op = torch.ops.aten.rsqrt.default
129+
mul_op = torch.ops.aten.mul.Tensor
130+
else:
131+
sub_op = exir_ops.edge.aten.sub.Tensor
132+
view_op = exir_ops.edge.aten.view_copy.default
133+
full_op = exir_ops.edge.aten.full.default
134+
add_op = exir_ops.edge.aten.add.Tensor
135+
rsqrt_op = exir_ops.edge.aten.rsqrt.default
136+
mul_op = exir_ops.edge.aten.mul.Tensor
113137

114138
# Begin decomposition
115139
with graph_module.graph.inserting_before(node):

‎backends/arm/quantizer/quantizer_support.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ def check_pattern(cls, pattern):
155155
ALL_QPARAM_OP_PATTERNS = (
156156
[(target,) for target in _one_to_one]
157157
+ ACTIVATION_FUNCTION_PATTERNS
158+
+ combo_pattern(BATCH_NORM_OPS)
158159
+ CONV_OP_PATTERNS
159160
+ LINEAR_OP_PATTERNS
160161
+ BINARY_OP_PATTERNS

‎backends/arm/test/ops/test_batch_norm.py‎

Lines changed: 15 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
from executorch.backends.arm.test.tester.test_pipeline import (
1616
EthosU55PipelineINT,
1717
EthosU85PipelineINT,
18-
OpNotSupportedPipeline,
1918
TosaPipelineFP,
2019
TosaPipelineINT,
2120
VgfPipeline,
@@ -87,17 +86,14 @@ def test_native_batch_norm_legit_no_training_tosa_FP(test_data: Tuple):
8786
pipeline.run()
8887

8988

90-
# TODO(MLETORCH-100: Quantized stand-alone batch norms)
91-
def test_native_batch_norm_legit_no_training_tosa_INT_not_delegated():
89+
def test_native_batch_norm_legit_no_training_tosa_INT():
9290
test_data, model_params = test_data_suite["rand_1_3_254_254"]()
93-
OpNotSupportedPipeline[input_t1](
91+
pipeline = TosaPipelineINT[input_t1](
9492
BatchNorm2d(*model_params),
9593
(test_data,),
96-
{
97-
"executorch_exir_dialects_edge__ops_aten__native_batch_norm_legit_no_training_default": 1
98-
},
99-
quantize=True,
100-
).run()
94+
aten_op=[],
95+
)
96+
pipeline.run()
10197

10298

10399
@common.parametrize("test_data", test_data_suite)
@@ -120,31 +116,24 @@ def test_native_batch_norm_legit_no_training_vgf_quant(test_data: Tuple):
120116
pass
121117

122118

123-
# TODO(MLETORCH-100: Quantized stand-alone batch norms)
124-
def test_native_batch_norm_legit_no_training_u55_INT_not_delegated():
119+
def test_native_batch_norm_legit_no_training_u55_INT():
125120
test_data, model_params = test_data_suite["rand_1_3_254_254"]()
126-
OpNotSupportedPipeline[input_t1](
121+
pipeline = EthosU55PipelineINT[input_t1](
127122
BatchNorm2d(*model_params),
128123
(test_data,),
129-
{
130-
"executorch_exir_dialects_edge__ops_aten__native_batch_norm_legit_no_training_default": 1
131-
},
132-
quantize=True,
133-
u55_subset=True,
134-
).run()
124+
aten_ops=[],
125+
)
126+
pipeline.run()
135127

136128

137-
# TODO(MLETORCH-100: Quantized stand-alone batch norms)
138-
def test_native_batch_norm_legit_no_training_u85_INT_not_delegated():
129+
def test_native_batch_norm_legit_no_training_u85_INT():
139130
test_data, model_params = test_data_suite["rand_1_3_254_254"]()
140-
OpNotSupportedPipeline[input_t1](
131+
pipeline = EthosU85PipelineINT[input_t1](
141132
BatchNorm2d(*model_params),
142133
(test_data,),
143-
{
144-
"executorch_exir_dialects_edge__ops_aten__native_batch_norm_legit_no_training_default": 1
145-
},
146-
quantize=True,
147-
).run()
134+
aten_ops=[],
135+
)
136+
pipeline.run()
148137

149138

150139
class BatchNorm2dConv(torch.nn.Module):

0 commit comments

Comments
 (0)