Skip to content

Commit b2a8550

Browse files
authored
Qualcomm AI Engine Direct - Support Flip & Index_Select (#13906)
### Summary - Support Flip - Support Index_Select ### Test plan - `python backends/qualcomm/tests/test_qnn_delegate.py -k TestQNNQuantizedOperator.test_qnn_backend_flip --model SM8750 --device $DEVICE --build_folder build-android` - `python backends/qualcomm/tests/test_qnn_delegate.py -k TestQNNQuantizedOperator.test_qnn_backend_index_select --model SM8750 --device $DEVICE --build_folder build-android` - `python backends/qualcomm/tests/test_qnn_delegate.py -k TestQNNQuantizedModel.test_qnn_backend_conv2d_flip --model SM8750 --device $DEVICE --build_folder build-android` - `python backends/qualcomm/tests/test_qnn_delegate.py -k TestQNNQuantizedModel.test_qnn_backend_conv2d_slice_copy --model SM8750 --device $DEVICE --build_folder build-android`
1 parent 33c8f76 commit b2a8550

File tree

9 files changed

+284
-5
lines changed

9 files changed

+284
-5
lines changed

backends/qualcomm/_passes/layout_transform.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ class LayoutTransform(ExportPass):
7979
exir_ops.edge.aten.elu.default,
8080
exir_ops.edge.aten.eq.Tensor,
8181
exir_ops.edge.aten.exp.default,
82+
exir_ops.edge.aten.flip.default,
8283
exir_ops.edge.aten.floor.default,
8384
exir_ops.edge.aten.floor_divide.default,
8485
exir_ops.edge.aten.full.default,
@@ -111,6 +112,7 @@ class LayoutTransform(ExportPass):
111112
exir_ops.edge.aten.round.default,
112113
exir_ops.edge.aten.sigmoid.default,
113114
exir_ops.edge.aten.sign.default,
115+
exir_ops.edge.aten.slice_copy.Tensor,
114116
exir_ops.edge.aten.split_with_sizes.default,
115117
exir_ops.edge.aten.split_with_sizes_copy.default,
116118
exir_ops.edge.aten.sqrt.default,

backends/qualcomm/builders/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
op_eq,
3737
op_exp,
3838
op_expand,
39+
op_flip,
3940
op_floor,
4041
op_full,
4142
op_full_like,
@@ -49,6 +50,7 @@
4950
op_hardtanh,
5051
op_index,
5152
op_index_put,
53+
op_index_select,
5254
op_instance_norm,
5355
op_layer_norm,
5456
op_le,
@@ -139,6 +141,7 @@
139141
op_eq,
140142
op_exp,
141143
op_expand,
144+
op_flip,
142145
op_floor,
143146
op_full,
144147
op_full_like,
@@ -152,6 +155,7 @@
152155
op_hardsigmoid,
153156
op_index,
154157
op_index_put,
158+
op_index_select,
155159
op_instance_norm,
156160
op_layer_norm,
157161
op_le,
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
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+
from typing import Dict
7+
8+
import executorch.backends.qualcomm.python.PyQnnWrapperAdaptor as PyQnnWrapper
9+
10+
import numpy as np
11+
import torch
12+
13+
from executorch.backends.qualcomm.utils.constants import QCOM_AXIS_ORDER
14+
15+
from .node_visitor import NodeVisitor
16+
from .node_visitor_manager import register_node_visitor
17+
from .qnn_constants import OpStridedSlice, QNN_OP_PACKAGE_NAME_QTI_AISW
18+
19+
20+
@register_node_visitor
21+
class Flip(NodeVisitor):
22+
target = ["aten.flip.default"]
23+
24+
def __init__(self, *args) -> None:
25+
super().__init__(*args)
26+
27+
def define_node(
28+
self,
29+
node: torch.fx.Node,
30+
nodes_to_wrappers: Dict[torch.fx.Node, PyQnnWrapper.TensorWrapper],
31+
) -> PyQnnWrapper.PyQnnOpWrapper:
32+
input_node = self.get_node(node.args[0])
33+
input_tensor = self.get_tensor(input_node, node)
34+
tensor_type = PyQnnWrapper.Qnn_TensorType_t.QNN_TENSOR_TYPE_NATIVE
35+
36+
input_tensor_wrapper = self.define_tensor(
37+
input_node,
38+
node,
39+
input_tensor,
40+
tensor_type,
41+
nodes_to_wrappers,
42+
)
43+
44+
output_tensor = self.get_tensor(node, node)
45+
output_tensor_wrapper = self.define_tensor(
46+
node,
47+
node,
48+
output_tensor,
49+
PyQnnWrapper.Qnn_TensorType_t.QNN_TENSOR_TYPE_NATIVE,
50+
nodes_to_wrappers,
51+
)
52+
ranges = []
53+
54+
dims = node.args[1]
55+
if QCOM_AXIS_ORDER in node.meta:
56+
dims = [node.meta[QCOM_AXIS_ORDER].index(dim) for dim in dims]
57+
58+
for dim, size in enumerate(output_tensor.shape):
59+
if dim in dims:
60+
ranges.extend([size - 1, -1, -1])
61+
else:
62+
ranges.extend([0, size, 1])
63+
64+
range_shape = [input_tensor.dim(), 3]
65+
stride_slice_op = PyQnnWrapper.PyQnnOpWrapper(
66+
node.name,
67+
QNN_OP_PACKAGE_NAME_QTI_AISW,
68+
OpStridedSlice.op_name,
69+
)
70+
stride_slice_op.AddInputTensors([input_tensor_wrapper])
71+
stride_slice_op.AddOutputTensors([output_tensor_wrapper])
72+
stride_slice_op.AddTensorParam(
73+
OpStridedSlice.param_ranges,
74+
PyQnnWrapper.Qnn_DataType_t.QNN_DATATYPE_INT_32,
75+
len(range_shape),
76+
range_shape,
77+
np.array(ranges, dtype=np.int32),
78+
True,
79+
)
80+
81+
return stride_slice_op
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
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+
from typing import Dict
7+
8+
import executorch.backends.qualcomm.python.PyQnnWrapperAdaptor as PyQnnWrapper
9+
10+
import numpy as np
11+
import torch
12+
from executorch.backends.qualcomm.utils.constants import QCOM_DATA
13+
14+
from .node_visitor import NodeVisitor
15+
from .node_visitor_manager import register_node_visitor
16+
from .qnn_constants import OpGather, QNN_OP_PACKAGE_NAME_QTI_AISW
17+
18+
19+
@register_node_visitor
20+
class IndexSelect(NodeVisitor):
21+
target = ["aten.index_select.default"]
22+
23+
def __init__(self, *args) -> None:
24+
super().__init__(*args)
25+
26+
def define_node(
27+
self,
28+
node: torch.fx.Node,
29+
nodes_to_wrappers: Dict[torch.fx.Node, PyQnnWrapper.TensorWrapper],
30+
) -> PyQnnWrapper.PyQnnOpWrapper:
31+
input_node = self.get_node(node.args[0])
32+
input_tensor = self.get_tensor(input_node, node)
33+
input_tensor_wrapper = self.define_tensor(
34+
input_node,
35+
node,
36+
input_tensor,
37+
PyQnnWrapper.Qnn_TensorType_t.QNN_TENSOR_TYPE_NATIVE,
38+
nodes_to_wrappers,
39+
)
40+
41+
axis = node.args[1]
42+
indices_node = node.args[2]
43+
indices_tensor = self.get_tensor(indices_node, node).to(torch.int32)
44+
assert indices_tensor.size(0) != 0, "Not support empty indices list"
45+
46+
indices_tensor_wrapper = self.define_tensor(
47+
indices_node,
48+
node,
49+
indices_tensor,
50+
PyQnnWrapper.Qnn_TensorType_t.QNN_TENSOR_TYPE_NATIVE,
51+
nodes_to_wrappers,
52+
)
53+
54+
gather_input_tensors = [input_tensor_wrapper, indices_tensor_wrapper]
55+
56+
output_tensor = self.get_tensor(node, node)
57+
output_tensor_wrapper = self.define_tensor(
58+
node,
59+
node,
60+
output_tensor,
61+
PyQnnWrapper.Qnn_TensorType_t.QNN_TENSOR_TYPE_NATIVE,
62+
nodes_to_wrappers,
63+
)
64+
gather_output_tensors = [output_tensor_wrapper]
65+
66+
gather_op = PyQnnWrapper.PyQnnOpWrapper(
67+
node.name,
68+
QNN_OP_PACKAGE_NAME_QTI_AISW,
69+
OpGather.op_name,
70+
)
71+
gather_op.AddInputTensors(gather_input_tensors)
72+
gather_op.AddOutputTensors(gather_output_tensors)
73+
74+
# If support tuple of tensor, need to refine it based on len
75+
gather_op.AddScalarParam(
76+
OpGather.param_axis,
77+
PyQnnWrapper.Qnn_DataType_t.QNN_DATATYPE_INT_32,
78+
{QCOM_DATA: np.int32(axis)},
79+
)
80+
81+
return gather_op

backends/qualcomm/builders/op_slice_copy.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
from typing import cast, Dict
77

88
import executorch.backends.qualcomm.python.PyQnnWrapperAdaptor as PyQnnWrapper
9-
109
import numpy as np
1110
import torch
11+
from executorch.backends.qualcomm.utils.constants import QCOM_AXIS_ORDER
1212

1313
from .node_visitor import NodeVisitor
1414
from .node_visitor_manager import register_node_visitor
@@ -47,8 +47,9 @@ def define_node(
4747
PyQnnWrapper.Qnn_TensorType_t.QNN_TENSOR_TYPE_NATIVE,
4848
nodes_to_wrappers,
4949
)
50-
5150
dim = cast(int, node.args[1])
51+
if QCOM_AXIS_ORDER in node.meta:
52+
dim = node.meta[QCOM_AXIS_ORDER].index(dim)
5253
if dim < 0:
5354
dim = dim % len(input_tensor.shape)
5455

@@ -62,7 +63,6 @@ def define_node(
6263
end = end % input_tensor.shape[dim]
6364
else:
6465
end = input_tensor.shape[dim]
65-
6666
input_tensor_rank = len(input_tensor.shape)
6767
ranges = []
6868
for i in range(input_tensor_rank):

backends/qualcomm/partition/common_defs.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,9 @@
1919
exir_ops.edge.aten.adaptive_max_pool2d.default,
2020
exir_ops.edge.aten.avg_pool3d.default,
2121
exir_ops.edge.aten.div.Tensor_mode,
22-
exir_ops.edge.aten.index_select.default,
2322
exir_ops.edge.aten.log10.default,
2423
exir_ops.edge.aten.log1p.default,
2524
exir_ops.edge.aten.log2.default,
26-
exir_ops.edge.aten.flip.default,
2725
exir_ops.edge.aten.max_pool3d_with_indices.default,
2826
exir_ops.edge.aten.median.default,
2927
exir_ops.edge.aten.median.dim,

backends/qualcomm/quantizer/annotators.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,17 @@ def annotate_clamp(node: Node, quantization_config: QuantizationConfig) -> None:
433433
annotate_single_in_single_out(node, quantization_config)
434434

435435

436+
@register_annotator([torch.ops.aten.index_select.default])
437+
def annotate_index_select(node: Node, quantization_config: QuantizationConfig) -> None:
438+
# args[2] = indices, which should be int
439+
annotate_single_in_single_out(node, quantization_config)
440+
441+
442+
@register_annotator([torch.ops.aten.flip.default])
443+
def annotate_flip(node: Node, quantization_config: QuantizationConfig) -> None:
444+
annotate_single_in_single_out(node, quantization_config)
445+
446+
436447
@register_annotator([torch.ops.aten.floor.default])
437448
def annotate_floor(node: Node, quantization_config: QuantizationConfig) -> None:
438449
annotate_single_in_single_out(node, quantization_config)

backends/qualcomm/tests/models.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -646,6 +646,40 @@ def forward(self, x):
646646
return self.conv_transpose(self.conv(x))
647647

648648

649+
class Conv2dFlip(torch.nn.Module):
650+
def __init__(self):
651+
super().__init__()
652+
self.conv = torch.nn.Conv2d(
653+
in_channels=16,
654+
out_channels=16,
655+
kernel_size=3,
656+
stride=2,
657+
padding=1,
658+
bias=False,
659+
)
660+
self.dims = [1, 3]
661+
662+
def forward(self, x):
663+
x = self.conv(x)
664+
return torch.flip(x, self.dims)
665+
666+
667+
class Conv2dSliceCopy(torch.nn.Module):
668+
def __init__(self):
669+
super().__init__()
670+
self.conv = torch.nn.Conv2d(
671+
in_channels=1,
672+
out_channels=4,
673+
kernel_size=(3, 3),
674+
padding=1,
675+
bias=True,
676+
)
677+
678+
def forward(self, x):
679+
x = self.conv(x)
680+
return x[:, 2:, :, :]
681+
682+
649683
class Conv2dSumReduceDim(torch.nn.Module):
650684
def __init__(self):
651685
super().__init__()
@@ -814,6 +848,15 @@ def forward(self, x):
814848
return torch.special.expm1(x)
815849

816850

851+
class Flip(torch.nn.Module):
852+
def __init__(self):
853+
super().__init__()
854+
self.dims = [0, 2]
855+
856+
def forward(self, x):
857+
return torch.flip(x, self.dims)
858+
859+
817860
class Floor(torch.nn.Module):
818861
def __init__(self):
819862
super().__init__()
@@ -1039,6 +1082,15 @@ def forward(self, input_pos, k_val):
10391082
return k_out + 0
10401083

10411084

1085+
class IndexSelect(torch.nn.Module):
1086+
def __init__(self, dim):
1087+
super().__init__()
1088+
self.dim = dim
1089+
1090+
def forward(self, x, indices):
1091+
return torch.index_select(x, self.dim, indices)
1092+
1093+
10421094
class InstanceNorm2d(torch.nn.Module):
10431095
def __init__(self, n_features, affine=True):
10441096
super().__init__()

0 commit comments

Comments
 (0)