Skip to content

Commit a1bd208

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

8 files changed

Lines changed: 213 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_fill import DecomposeFill
@@ -80,6 +81,7 @@
8081
DecomposeBinaryAlpha,
8182
DecomposeCDist,
8283
DecomposeColIm,
84+
DecomposeDivMode,
8385
DecomposeEinsum,
8486
DecomposeExpM1,
8587
DecomposeFill,
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: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
DecomposeBinaryAlpha,
2626
DecomposeCDist,
2727
DecomposeColIm,
28+
DecomposeDivMode,
2829
DecomposeEinsum,
2930
DecomposeExpM1,
3031
DecomposeFill,
@@ -125,6 +126,7 @@ def get_default_pass_activations(cls):
125126
(DecomposeAny, True),
126127
(DecomposeAtan2, True),
127128
(DecomposeColIm, True),
129+
(DecomposeDivMode, True),
128130
(DecomposeFill, True),
129131
(DecomposeLogVariants, True),
130132
(DecomposeMaxPool3d, True),
@@ -161,6 +163,7 @@ def get_annotation_passes(cls):
161163
DecomposeAtan2,
162164
DecomposeBinaryAlpha,
163165
DecomposeCDist,
166+
DecomposeDivMode,
164167
DecomposeMaxPool3d,
165168
DecomposePad,
166169
DecomposeScaledDotProductAttention,
@@ -274,6 +277,7 @@ def get_passes_dependency_for_capture_program(cls):
274277
DecomposeAny: [RemoveRedundancy],
275278
DecomposeAtan2: [RemoveRedundancy],
276279
DecomposeColIm: [FoldQDQ],
280+
DecomposeDivMode: [RemoveRedundancy],
277281
DecomposeFill: [RemoveRedundancy],
278282
DecomposeLinalgVectorNorm: [RemoveRedundancy],
279283
DecomposeLogVariants: [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: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1013,6 +1013,25 @@ 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+
1025+
class DivScalarMode(torch.nn.Module):
1026+
def __init__(self, scalar=2.0, rounding_mode=None):
1027+
super().__init__()
1028+
self.scalar = scalar
1029+
self.rounding_mode = rounding_mode
1030+
1031+
def forward(self, x):
1032+
return torch.div(x, self.scalar, rounding_mode=self.rounding_mode)
1033+
1034+
10161035
class DrawGraphModel(torch.nn.Module):
10171036
def __init__(self):
10181037
super().__init__()

backends/qualcomm/tests/test_qnn_delegate.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -625,6 +625,52 @@ def test_qnn_backend_cumsum(self):
625625
index += 1
626626
self.lower_module_and_test_output(module, sample_input)
627627

628+
def test_qnn_backend_div_mode(self):
629+
test_comb = [
630+
{
631+
QCOM_MODULE: [
632+
DivMode(rounding_mode=None), # noqa: F405
633+
DivMode(rounding_mode="trunc"), # noqa: F405
634+
DivMode(rounding_mode="floor"), # noqa: F405
635+
],
636+
QCOM_SAMPLE_INPUTS: [
637+
(
638+
torch.tensor([7.0, 5.0, -3.0, 8.0, 1.0, 9.0]).reshape(2, 3),
639+
torch.tensor([2.0, 3.0, 2.0, 5.0, 4.0, 2.0]).reshape(2, 3),
640+
),
641+
],
642+
},
643+
]
644+
645+
index = 0
646+
for comb in test_comb:
647+
for module in comb[QCOM_MODULE]:
648+
for sample_input in comb[QCOM_SAMPLE_INPUTS]:
649+
with self.subTest(i=index):
650+
index += 1
651+
self.lower_module_and_test_output(module, sample_input)
652+
653+
def test_qnn_backend_div_scalar_mode(self):
654+
test_comb = [
655+
{
656+
QCOM_MODULE: [
657+
DivScalarMode(scalar=2.0, rounding_mode="trunc"), # noqa: F405
658+
DivScalarMode(scalar=3.0, rounding_mode="floor"), # noqa: F405
659+
],
660+
QCOM_SAMPLE_INPUTS: [
661+
(torch.tensor([7.0, 5.0, -3.0, 8.0, 1.0, 9.0]).reshape(2, 3),),
662+
],
663+
},
664+
]
665+
666+
index = 0
667+
for comb in test_comb:
668+
for module in comb[QCOM_MODULE]:
669+
for sample_input in comb[QCOM_SAMPLE_INPUTS]:
670+
with self.subTest(i=index):
671+
index += 1
672+
self.lower_module_and_test_output(module, sample_input)
673+
628674
def test_qnn_backend_einsum_outer_product(self):
629675
module = EinsumOuterProduct() # noqa: F405
630676
x = torch.randn(5)
@@ -3346,6 +3392,54 @@ def test_qnn_backend_cumsum(self):
33463392
module = self.get_qdq_module(module, sample_input)
33473393
self.lower_module_and_test_output(module, sample_input)
33483394

3395+
def test_qnn_backend_div_mode(self):
3396+
test_comb = [
3397+
{
3398+
QCOM_MODULE: [
3399+
DivMode(rounding_mode=None), # noqa: F405
3400+
DivMode(rounding_mode="trunc"), # noqa: F405
3401+
DivMode(rounding_mode="floor"), # noqa: F405
3402+
],
3403+
QCOM_SAMPLE_INPUTS: [
3404+
(
3405+
torch.tensor([7.0, 5.0, -3.0, 8.0, 1.0, 9.0]).reshape(2, 3),
3406+
torch.tensor([2.0, 3.0, 2.0, 5.0, 4.0, 2.0]).reshape(2, 3),
3407+
),
3408+
],
3409+
},
3410+
]
3411+
3412+
index = 0
3413+
for comb in test_comb:
3414+
for module in comb[QCOM_MODULE]:
3415+
for sample_input in comb[QCOM_SAMPLE_INPUTS]:
3416+
with self.subTest(i=index):
3417+
index += 1
3418+
qdq_module = self.get_qdq_module(module, sample_input)
3419+
self.lower_module_and_test_output(qdq_module, sample_input)
3420+
3421+
def test_qnn_backend_div_scalar_mode(self):
3422+
test_comb = [
3423+
{
3424+
QCOM_MODULE: [
3425+
DivScalarMode(scalar=2.0, rounding_mode="trunc"), # noqa: F405
3426+
DivScalarMode(scalar=3.0, rounding_mode="floor"), # noqa: F405
3427+
],
3428+
QCOM_SAMPLE_INPUTS: [
3429+
(torch.tensor([7.0, 5.0, -3.0, 8.0, 1.0, 9.0]).reshape(2, 3),),
3430+
],
3431+
},
3432+
]
3433+
3434+
index = 0
3435+
for comb in test_comb:
3436+
for module in comb[QCOM_MODULE]:
3437+
for sample_input in comb[QCOM_SAMPLE_INPUTS]:
3438+
with self.subTest(i=index):
3439+
index += 1
3440+
qdq_module = self.get_qdq_module(module, sample_input)
3441+
self.lower_module_and_test_output(qdq_module, sample_input)
3442+
33493443
def test_qnn_backend_einsum_outer_product(self):
33503444
module = EinsumOuterProduct() # noqa: F405
33513445
x = torch.randn(5)

0 commit comments

Comments
 (0)