|
1 | | -# Copyright 2024-2025 Arm Limited and/or its affiliates. |
| 1 | +# Copyright 2024-2026 Arm Limited and/or its affiliates. |
2 | 2 | # |
3 | 3 | # This source code is licensed under the BSD-style license found in the |
4 | 4 | # LICENSE file in the root directory of this source tree. |
|
7 | 7 | from typing import Any, Callable, Tuple |
8 | 8 |
|
9 | 9 | import torch |
| 10 | +import torch.nn.functional as F |
10 | 11 | from executorch.backends.arm.quantizer import is_annotated |
| 12 | +from executorch.backends.arm.quantizer.arm_quantizer import ( |
| 13 | + get_symmetric_quantization_config, |
| 14 | + VgfQuantizer, |
| 15 | +) |
| 16 | +from executorch.backends.arm.quantizer.quantization_annotator import annotate_graph |
11 | 17 | from executorch.backends.arm.test.tester.test_pipeline import TosaPipelineINT |
| 18 | +from executorch.backends.arm.vgf import VgfCompileSpec |
12 | 19 | from executorch.backends.test.harness.stages import StageType |
13 | 20 |
|
| 21 | +from torch.export import export |
14 | 22 | from torch.fx.passes.utils.source_matcher_utils import get_source_partitions |
| 23 | +from torchao.quantization.pt2e.quantizer.quantizer import Q_ANNOTATION_KEY |
15 | 24 |
|
16 | 25 |
|
17 | 26 | input_t1 = Tuple[torch.Tensor] # Input x |
@@ -142,3 +151,101 @@ def test_concat_tosa_INT(): |
142 | 151 | torch.concatenate, ((torch.randn(2, 3), torch.randn(2, 3)),), dim=0 |
143 | 152 | ), |
144 | 153 | ) |
| 154 | + |
| 155 | + |
| 156 | +class GridSampleModule(torch.nn.Module): |
| 157 | + def forward(self, x: torch.Tensor, grid: torch.Tensor) -> torch.Tensor: |
| 158 | + return F.grid_sample( |
| 159 | + x, |
| 160 | + grid, |
| 161 | + mode="bilinear", |
| 162 | + padding_mode="zeros", |
| 163 | + align_corners=False, |
| 164 | + ) |
| 165 | + |
| 166 | + |
| 167 | +class GridFloatQuantizationConfig: |
| 168 | + def __init__(self) -> None: |
| 169 | + self.base = get_symmetric_quantization_config() |
| 170 | + |
| 171 | + def get_input_act_qspec(self, node=None, input_node=None): |
| 172 | + if ( |
| 173 | + node is not None |
| 174 | + and input_node is not None |
| 175 | + and node.target == torch.ops.aten.grid_sampler.default |
| 176 | + and input_node == node.args[1] |
| 177 | + ): |
| 178 | + return None |
| 179 | + return self.base.get_input_act_qspec(node, input_node) |
| 180 | + |
| 181 | + def get_output_act_qspec(self, node=None): |
| 182 | + return self.base.get_output_act_qspec(node) |
| 183 | + |
| 184 | + def get_weight_qspec(self, node=None): |
| 185 | + return self.base.get_weight_qspec(node) |
| 186 | + |
| 187 | + def get_bias_qspec(self, node=None): |
| 188 | + return self.base.get_bias_qspec(node) |
| 189 | + |
| 190 | + |
| 191 | +def test_grid_sampler_annotation_keeps_float_grid_when_grid_qspec_is_none(): |
| 192 | + module = GridSampleModule().eval() |
| 193 | + example_inputs = (torch.randn(1, 4, 8, 8), torch.randn(1, 4, 4, 2)) |
| 194 | + gm = export(module, example_inputs).graph_module |
| 195 | + |
| 196 | + annotate_graph(gm, GridFloatQuantizationConfig()) |
| 197 | + |
| 198 | + grid_sampler_node = next( |
| 199 | + node |
| 200 | + for node in gm.graph.nodes |
| 201 | + if node.op == "call_function" |
| 202 | + and node.target == torch.ops.aten.grid_sampler.default |
| 203 | + ) |
| 204 | + image_node = grid_sampler_node.args[0] |
| 205 | + grid_node = grid_sampler_node.args[1] |
| 206 | + annotation = grid_sampler_node.meta[Q_ANNOTATION_KEY] |
| 207 | + |
| 208 | + assert is_annotated(grid_sampler_node) |
| 209 | + assert image_node in annotation.input_qspec_map |
| 210 | + assert grid_node not in annotation.input_qspec_map |
| 211 | + assert annotation.output_qspec is not None |
| 212 | + |
| 213 | + |
| 214 | +def test_grid_sampler_annotation_keeps_default_tosa_grid_float(): |
| 215 | + module = GridSampleModule().eval() |
| 216 | + example_inputs = (torch.randn(1, 4, 8, 8), torch.randn(1, 4, 4, 2)) |
| 217 | + gm = export(module, example_inputs).graph_module |
| 218 | + |
| 219 | + annotate_graph(gm, get_symmetric_quantization_config()) |
| 220 | + |
| 221 | + grid_sampler_node = next( |
| 222 | + node |
| 223 | + for node in gm.graph.nodes |
| 224 | + if node.op == "call_function" |
| 225 | + and node.target == torch.ops.aten.grid_sampler.default |
| 226 | + ) |
| 227 | + grid_node = grid_sampler_node.args[1] |
| 228 | + annotation = grid_sampler_node.meta[Q_ANNOTATION_KEY] |
| 229 | + |
| 230 | + assert grid_node not in annotation.input_qspec_map |
| 231 | + |
| 232 | + |
| 233 | +def test_vgf_quantizer_quantizes_grid_sampler_grid_coords(): |
| 234 | + module = GridSampleModule().eval() |
| 235 | + example_inputs = (torch.randn(1, 4, 8, 8), torch.randn(1, 4, 4, 2)) |
| 236 | + gm = export(module, example_inputs).graph_module |
| 237 | + |
| 238 | + quantizer = VgfQuantizer(VgfCompileSpec("TOSA-1.0+INT")) |
| 239 | + quantizer.set_global(get_symmetric_quantization_config()) |
| 240 | + quantizer.annotate(gm) |
| 241 | + |
| 242 | + grid_sampler_node = next( |
| 243 | + node |
| 244 | + for node in gm.graph.nodes |
| 245 | + if node.op == "call_function" |
| 246 | + and node.target == torch.ops.aten.grid_sampler.default |
| 247 | + ) |
| 248 | + grid_node = grid_sampler_node.args[1] |
| 249 | + annotation = grid_sampler_node.meta[Q_ANNOTATION_KEY] |
| 250 | + |
| 251 | + assert grid_node in annotation.input_qspec_map |
0 commit comments