Skip to content

Commit 64c13a2

Browse files
committed
Arm backend: Fix grid_sample when Q/DQ have been fused by exir
pytorch#22905 removes some Q/DQ nodes before running the Arm passes. In the FuseGridSamplerFlowOffsetPass pass, we expected to have a Q/DQ pair before the grid_sample, but that pair is now removed at the time of running the exir passes. Change the guard in FuseGridSamplerFlowOffsetPass to check for concat before the grid_sample to account for the fact we now have a cat. Signed-off-by: George Gekov <george.gekov@arm.com> Change-Id: Icd968d84c6ec499626ddf5839a5728afbb00a731
1 parent 89809c4 commit 64c13a2

2 files changed

Lines changed: 79 additions & 9 deletions

File tree

‎backends/arm/test/passes/test_fuse_grid_sampler_flow_offset_pass.py‎

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,49 @@ def test_match_flow_offset_grid_accepts_identity_base_grid_requantization():
334334
assert _match_flow_offset_grid(grid_sampler) == (flow, 0)
335335

336336

337+
def test_match_flow_offset_grid_accepts_folded_identity_base_grid():
338+
grid_sampler, flow = _build_flow_offset_grid_sampler()
339+
grid = grid_sampler.args[1]
340+
assert isinstance(grid, torch.fx.Node)
341+
add = grid.args[0]
342+
assert isinstance(add, torch.fx.Node)
343+
quantize = add.args[0]
344+
assert isinstance(quantize, torch.fx.Node)
345+
dequantize = quantize.args[0]
346+
assert isinstance(dequantize, torch.fx.Node)
347+
base_grid = dequantize.args[0]
348+
assert isinstance(base_grid, torch.fx.Node)
349+
350+
qparams = QuantArgs(0.007870171219110489, 0, -127, 127, torch.int8)
351+
base_grid.meta["input_qparams"] = {0: qparams}
352+
base_grid.meta["output_qparams"] = {0: qparams}
353+
add.replace_input_with(quantize, base_grid)
354+
355+
assert _match_flow_offset_grid(grid_sampler) == (flow, 0)
356+
357+
358+
def test_match_flow_offset_grid_rejects_folded_requantized_base_grid():
359+
grid_sampler, _ = _build_flow_offset_grid_sampler()
360+
grid = grid_sampler.args[1]
361+
assert isinstance(grid, torch.fx.Node)
362+
add = grid.args[0]
363+
assert isinstance(add, torch.fx.Node)
364+
quantize = add.args[0]
365+
assert isinstance(quantize, torch.fx.Node)
366+
dequantize = quantize.args[0]
367+
assert isinstance(dequantize, torch.fx.Node)
368+
base_grid = dequantize.args[0]
369+
assert isinstance(base_grid, torch.fx.Node)
370+
371+
base_grid.meta["input_qparams"] = {
372+
0: QuantArgs(0.007870171219110489, 0, -127, 127, torch.int8)
373+
}
374+
base_grid.meta["output_qparams"] = {0: QuantArgs(0.01, 0, -127, 127, torch.int8)}
375+
add.replace_input_with(quantize, base_grid)
376+
377+
assert _match_flow_offset_grid(grid_sampler) is None
378+
379+
337380
@pytest.mark.parametrize(
338381
"kwargs",
339382
[

‎backends/arm/vgf/_passes/fuse_grid_sampler_flow_offset.py‎

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,12 @@
1010
grid = permute(base_grid + flow[:, i:i + 2], [0, 2, 3, 1])
1111
output = grid_sample(image, grid, bilinear, border, align_corners=True)
1212
13-
The base grid may pass through an identity dequantize/quantize pair. Images
14-
must have shape ``[1, 3|4, H, W]``, flow must have shape ``[1, 4, H, W]``,
15-
and ``i`` must be zero or two. Image and output quantization ranges must be
16-
``[-127, 127]`` because the custom shader stores them in SNORM images. The
17-
flow tensor may use the full int8 range because the shader reads it directly.
13+
The base grid may be direct or pass through an identity dequantize/quantize
14+
pair. Images must have shape ``[1, 3|4, H, W]``, flow must have shape
15+
``[1, 4, H, W]``, and ``i`` must be zero or two. Image and output
16+
quantization ranges must be ``[-127, 127]`` because the custom shader stores
17+
them in SNORM images. The flow tensor may use the full int8 range because the
18+
shader reads it directly.
1819
1920
"""
2021

@@ -133,17 +134,43 @@ def _is_identity_requantization(
133134
return True
134135

135136

137+
def _has_identity_qparams(node: Node) -> bool:
138+
"""Return whether a node preserves one quantization representation.
139+
140+
Args:
141+
node (Node): Candidate node with folded quantization metadata.
142+
143+
Returns:
144+
bool: Whether every input and output qparam is identical.
145+
146+
"""
147+
try:
148+
input_qparams = get_input_qparams(node)
149+
output_qparams = get_output_qparams(node)
150+
except ValueError:
151+
return False
152+
return all(
153+
input_qparam == output_qparam
154+
for input_qparam in input_qparams.values()
155+
for output_qparam in output_qparams.values()
156+
)
157+
158+
136159
def _unwrap_quantized_base_grid(node: Node) -> Node | None:
137-
"""Unwrap an identity dequantize-quantize pair around a base grid.
160+
"""Return a direct base grid or unwrap an identity Q/DQ pair.
138161
139162
Args:
140-
node (Node): Candidate quantize node.
163+
node (Node): Candidate base-grid or quantize node.
141164
142165
Returns:
143-
Node | None: Unwrapped base-grid node, or ``None`` when the wrapper
144-
changes integer values or has an unsupported form.
166+
Node | None: Base-grid node, or ``None`` when the wrapper changes
167+
integer values or has an unsupported form.
145168
146169
"""
170+
# The exir FoldRedundantDequantizeQuantizePass has removed a DQ/Q pair after
171+
# the cat. Ensure the cat has the same input & output qparams before returning.
172+
if _target_is(node, "aten.cat.default") and _has_identity_qparams(node):
173+
return node
147174
if not _target_is(node, "quantized_decomposed.quantize_per_tensor.default"):
148175
return None
149176
dequantize = node.args[0]

0 commit comments

Comments
 (0)