Skip to content

Commit dcd0dbb

Browse files
committed
Qualcomm AI Engine Direct - Adding QNN backend support for div.Tensor_mode core ATen op
1 parent fb3f6eb commit dcd0dbb

9 files changed

Lines changed: 161 additions & 1 deletion

File tree

backends/qualcomm/_passes/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from .decompose_binary_alpha import DecomposeBinaryAlpha
2020
from .decompose_cdist import DecomposeCDist
2121
from .decompose_col_im import DecomposeColIm
22+
from .decompose_div_mode import DecomposeDivMode
2223
from .decompose_einsum import DecomposeEinsum
2324
from .decompose_expm1 import DecomposeExpM1
2425
from .decompose_floor_divide import DecomposeFloorDivide
@@ -77,6 +78,7 @@
7778
DecomposeBinaryAlpha,
7879
DecomposeCDist,
7980
DecomposeColIm,
81+
DecomposeDivMode,
8082
DecomposeEinsum,
8183
DecomposeExpM1,
8284
DecomposeFloorDivide,
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# Copyright (c) Qualcomm Innovation Center, Inc.
2+
# All rights reserved
3+
#
4+
# This source code is licensed under the BSD-style license found in the
5+
# LICENSE file in the root directory of this source tree.
6+
7+
import torch
8+
from executorch.exir.dialects._ops import ops as exir_ops
9+
from executorch.exir.dialects.edge._ops import EdgeOpOverload
10+
from executorch.exir.pass_base import ExportPass, PassResult
11+
12+
from .utils import copy_meta
13+
14+
15+
class DecomposeDivMode(ExportPass):
16+
"""
17+
Decompose aten.div.Tensor_mode into supported primitives.
18+
19+
div(x, y, rounding_mode=None) -> div(x, y)
20+
div(x, y, rounding_mode="trunc") -> trunc(div(x, y))
21+
div(x, y, rounding_mode="floor") -> floor(div(x, y))
22+
23+
Note: div.Scalar_mode is handled by LiftConstantScalarOperands which converts it to div.Tensor_mode before this pass runs.
24+
"""
25+
26+
def __init__(self):
27+
super(DecomposeDivMode, self).__init__()
28+
self.targets = {
29+
torch.ops.aten.div.Tensor_mode,
30+
exir_ops.edge.aten.div.Tensor_mode,
31+
}
32+
33+
def call(self, graph_module: torch.fx.GraphModule):
34+
graph = graph_module.graph
35+
36+
for node in list(graph.nodes):
37+
if node.op == "call_function" and node.target in self.targets:
38+
is_edge = isinstance(node.target, EdgeOpOverload)
39+
meta = node.meta
40+
41+
x_node = node.args[0]
42+
y_node = node.args[1]
43+
44+
rounding_mode = node.kwargs.get("rounding_mode", None)
45+
if rounding_mode is None and len(node.args) > 2:
46+
rounding_mode = node.args[2]
47+
48+
div_op = (
49+
exir_ops.edge.aten.div.Tensor
50+
if is_edge
51+
else torch.ops.aten.div.Tensor
52+
)
53+
54+
with graph.inserting_before(node):
55+
# Step 1: div_result = div(x, y)
56+
div_node = graph.create_node(
57+
"call_function", div_op, (x_node, y_node)
58+
)
59+
div_node.meta = copy_meta(meta)
60+
61+
# Step 2: Apply rounding mode if needed
62+
if rounding_mode == "trunc":
63+
trunc_op = (
64+
exir_ops.edge.aten.trunc.default
65+
if is_edge
66+
else torch.ops.aten.trunc.default
67+
)
68+
result_node = graph.create_node(
69+
"call_function", trunc_op, (div_node,)
70+
)
71+
result_node.meta = copy_meta(meta)
72+
elif rounding_mode == "floor":
73+
floor_op = (
74+
exir_ops.edge.aten.floor.default
75+
if is_edge
76+
else torch.ops.aten.floor.default
77+
)
78+
result_node = graph.create_node(
79+
"call_function", floor_op, (div_node,)
80+
)
81+
result_node.meta = copy_meta(meta)
82+
else:
83+
# rounding_mode=None: plain division
84+
result_node = div_node
85+
86+
for user in node.users.copy():
87+
user.replace_input_with(node, result_node)
88+
89+
graph.eliminate_dead_code()
90+
graph_module.recompile()
91+
return PassResult(graph_module, True)

backends/qualcomm/_passes/lift_constant_scalar_operands.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ class TensorOpInfo:
4343
# For below cases, refer to LiftAddTensor Model in UT for sample
4444
aten.add.Tensor: TensorOpInfo(aten.add.Tensor, False, False),
4545
aten.div.Scalar: TensorOpInfo(aten.div.Tensor, False, False),
46+
aten.div.Scalar_mode: TensorOpInfo(aten.div.Tensor_mode, False, False),
4647
aten.mul.Scalar: TensorOpInfo(aten.mul.Tensor, False, False),
4748
aten.rsub.Scalar: TensorOpInfo(aten.rsub.Tensor, False, False),
4849
aten.sub.Scalar: TensorOpInfo(aten.sub.Tensor, False, False),

backends/qualcomm/_passes/qnn_pass_manager.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
DecomposeBinaryAlpha,
2525
DecomposeCDist,
2626
DecomposeColIm,
27+
DecomposeDivMode,
2728
DecomposeEinsum,
2829
DecomposeExpM1,
2930
DecomposeFloorDivide,
@@ -109,6 +110,7 @@ def get_capture_program_passes():
109110
(DecomposeAny, True),
110111
(DecomposeAtan2, True),
111112
(DecomposeColIm, True),
113+
(DecomposeDivMode, True),
112114
(DecomposeLogVariants, True),
113115
(DecomposeMaxPool3d, True),
114116
(DecomposeMinMaxDim, True),
@@ -234,6 +236,7 @@ def transform_for_annotation_pipeline(self, graph_module: GraphModule):
234236
self.add_pass(DecomposeAtan2())
235237
self.add_pass(DecomposeBinaryAlpha())
236238
self.add_pass(DecomposeCDist())
239+
self.add_pass(DecomposeDivMode())
237240
self.add_pass(DecomposeMaxPool3d(quantization_capture=True))
238241
self.add_pass(DecomposePad())
239242
self.add_pass(DecomposeScaledDotProductAttention())

backends/qualcomm/_passes/utils.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ def get_passes_dependency_for_capture_program():
6969
DecomposeAny,
7070
DecomposeAtan2,
7171
DecomposeColIm,
72+
DecomposeDivMode,
7273
DecomposeLinalgVectorNorm,
7374
DecomposeLogVariants,
7475
DecomposeMaxPool3d,
@@ -103,6 +104,7 @@ def get_passes_dependency_for_capture_program():
103104
DecomposeAny: [RemoveRedundancy],
104105
DecomposeAtan2: [RemoveRedundancy],
105106
DecomposeColIm: [FoldQDQ],
107+
DecomposeDivMode: [RemoveRedundancy],
106108
DecomposeLinalgVectorNorm: [RemoveRedundancy],
107109
DecomposeLogVariants: [RemoveRedundancy],
108110
DecomposeMaxPool3d: [RemoveRedundancy],

backends/qualcomm/builders/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,8 @@ The following PyTorch operators are supported through decomposition or annotatio
503503
| `aten.atan2.default`, `aten.atan2.out` | `DecomposeAtan2` |
504504
| `aten.add` (with alpha), `aten.sub` (with alpha) | `DecomposeBinaryAlpha` |
505505
| `aten.cdist` | `DecomposeCDist` |
506+
| `aten.div.Tensor_mode` | `DecomposeDivMode` |
507+
| `aten.div.Scalar_mode` | `LiftConstantScalarOperands``DecomposeDivMode` |
506508
| `aten.im2col`, `aten.col2im` | `DecomposeColIm` |
507509
| `aten.einsum` | `DecomposeEinsum` |
508510
| `aten.special_expm1` | `DecomposeExpM1` |

backends/qualcomm/partition/common_defs.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020

2121
to_be_implemented_operator = [
2222
exir_ops.edge.aten.adaptive_max_pool3d.default,
23-
exir_ops.edge.aten.div.Tensor_mode,
2423
exir_ops.edge.aten.max_pool3d_with_indices.default,
2524
exir_ops.edge.aten.median.default,
2625
exir_ops.edge.aten.median.dim,

backends/qualcomm/tests/models.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1013,6 +1013,15 @@ def forward(self, x):
10131013
return x / 10
10141014

10151015

1016+
class DivMode(torch.nn.Module):
1017+
def __init__(self, rounding_mode=None):
1018+
super().__init__()
1019+
self.rounding_mode = rounding_mode
1020+
1021+
def forward(self, x, y):
1022+
return torch.div(x, y, rounding_mode=self.rounding_mode)
1023+
1024+
10161025
class DrawGraphModel(torch.nn.Module):
10171026
def __init__(self):
10181027
super().__init__()

backends/qualcomm/tests/test_qnn_delegate.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -630,6 +630,31 @@ def test_qnn_backend_cumsum(self):
630630
index += 1
631631
self.lower_module_and_test_output(module, sample_input)
632632

633+
def test_qnn_backend_div_mode(self):
634+
test_comb = [
635+
{
636+
QCOM_MODULE: [
637+
DivMode(rounding_mode=None), # noqa: F405
638+
DivMode(rounding_mode="trunc"), # noqa: F405
639+
DivMode(rounding_mode="floor"), # noqa: F405
640+
],
641+
QCOM_SAMPLE_INPUTS: [
642+
(
643+
torch.tensor([7.0, 5.0, -3.0, 8.0, 1.0, 9.0]).reshape(2, 3),
644+
torch.tensor([2.0, 3.0, 2.0, 5.0, 4.0, 2.0]).reshape(2, 3),
645+
),
646+
],
647+
},
648+
]
649+
650+
index = 0
651+
for comb in test_comb:
652+
for module in comb[QCOM_MODULE]:
653+
for sample_input in comb[QCOM_SAMPLE_INPUTS]:
654+
with self.subTest(i=index):
655+
index += 1
656+
self.lower_module_and_test_output(module, sample_input)
657+
633658
def test_qnn_backend_einsum_outer_product(self):
634659
module = EinsumOuterProduct() # noqa: F405
635660
x = torch.randn(5)
@@ -3225,6 +3250,32 @@ def test_qnn_backend_cumsum(self):
32253250
module = self.get_qdq_module(module, sample_input)
32263251
self.lower_module_and_test_output(module, sample_input)
32273252

3253+
def test_qnn_backend_div_mode(self):
3254+
test_comb = [
3255+
{
3256+
QCOM_MODULE: [
3257+
DivMode(rounding_mode=None), # noqa: F405
3258+
DivMode(rounding_mode="trunc"), # noqa: F405
3259+
DivMode(rounding_mode="floor"), # noqa: F405
3260+
],
3261+
QCOM_SAMPLE_INPUTS: [
3262+
(
3263+
torch.tensor([7.0, 5.0, -3.0, 8.0, 1.0, 9.0]).reshape(2, 3),
3264+
torch.tensor([2.0, 3.0, 2.0, 5.0, 4.0, 2.0]).reshape(2, 3),
3265+
),
3266+
],
3267+
},
3268+
]
3269+
3270+
index = 0
3271+
for comb in test_comb:
3272+
for module in comb[QCOM_MODULE]:
3273+
for sample_input in comb[QCOM_SAMPLE_INPUTS]:
3274+
with self.subTest(i=index):
3275+
index += 1
3276+
qdq_module = self.get_qdq_module(module, sample_input)
3277+
self.lower_module_and_test_output(qdq_module, sample_input)
3278+
32283279
def test_qnn_backend_einsum_outer_product(self):
32293280
module = EinsumOuterProduct() # noqa: F405
32303281
x = torch.randn(5)

0 commit comments

Comments
 (0)