|
| 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, List |
| 7 | + |
| 8 | +import executorch.backends.qualcomm.python.PyQnnManagerAdaptor as PyQnnManager |
| 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 OpReshape, OpStft, OpTranspose, QNN_OP_PACKAGE_NAME_QTI_AISW |
| 17 | +from .utils import get_parameter |
| 18 | + |
| 19 | + |
| 20 | +@register_node_visitor |
| 21 | +class Stft(NodeVisitor): |
| 22 | + """ |
| 23 | + Lowers the real-valued form of aten.stft (return_complex=False), see |
| 24 | + ConvertComplexStft, as Reshape -> Stft -> Transpose [-> Reshape]. |
| 25 | +
|
| 26 | + QNN Stft takes the signal as [batch, signal_length, 1] and produces |
| 27 | + [batch, frames, freq, 2], while torch produces [batch, freq, frames, 2] |
| 28 | + (without the batch dimension for a 1D signal). The window is zero-padded on |
| 29 | + both sides to n_fft, as torch does when win_length < n_fft. |
| 30 | + """ |
| 31 | + |
| 32 | + target = ["aten.stft.default"] |
| 33 | + |
| 34 | + def __init__(self, *args) -> None: |
| 35 | + super().__init__(*args) |
| 36 | + |
| 37 | + def _define_native_tensor( |
| 38 | + self, |
| 39 | + name: str, |
| 40 | + tensor: torch.Tensor, |
| 41 | + source_node: torch.fx.Node, |
| 42 | + node: torch.fx.Node, |
| 43 | + nodes_to_wrappers: Dict[torch.fx.Node, PyQnnManager.TensorWrapper], |
| 44 | + ) -> PyQnnManager.TensorWrapper: |
| 45 | + quant_encoding, quant_configs = self.get_quant_encoding_conf(source_node, node) |
| 46 | + return self.define_custom_tensor_wrapper( |
| 47 | + node_name=name, |
| 48 | + tensor_type=PyQnnManager.Qnn_TensorType_t.QNN_TENSOR_TYPE_NATIVE, |
| 49 | + dtype=self.get_data_type(tensor, quant_configs), |
| 50 | + quant_encoding=quant_encoding, |
| 51 | + quant_configs=quant_configs, |
| 52 | + dims=tensor.size(), |
| 53 | + tensor=tensor, |
| 54 | + is_fake_tensor=True, |
| 55 | + nodes_to_wrappers=nodes_to_wrappers, |
| 56 | + ) |
| 57 | + |
| 58 | + def _define_window( |
| 59 | + self, |
| 60 | + node: torch.fx.Node, |
| 61 | + n_fft: int, |
| 62 | + win_length: int, |
| 63 | + nodes_to_wrappers: Dict[torch.fx.Node, PyQnnManager.TensorWrapper], |
| 64 | + ) -> List[PyQnnManager.TensorWrapper]: |
| 65 | + window_node = node.args[4] if len(node.args) > 4 else None |
| 66 | + if window_node is None: |
| 67 | + return [] |
| 68 | + window_node = self.get_node(window_node) |
| 69 | + window = get_parameter(window_node, self.edge_program) |
| 70 | + left = (n_fft - win_length) // 2 |
| 71 | + window = torch.nn.functional.pad(window, (left, n_fft - win_length - left)) |
| 72 | + return [ |
| 73 | + self.define_tensor( |
| 74 | + window_node, |
| 75 | + node, |
| 76 | + window, |
| 77 | + PyQnnManager.Qnn_TensorType_t.QNN_TENSOR_TYPE_STATIC, |
| 78 | + nodes_to_wrappers, |
| 79 | + ) |
| 80 | + ] |
| 81 | + |
| 82 | + def define_node( |
| 83 | + self, |
| 84 | + node: torch.fx.Node, |
| 85 | + nodes_to_wrappers: Dict[torch.fx.Node, PyQnnManager.TensorWrapper], |
| 86 | + ) -> List[PyQnnManager.PyQnnOpWrapper]: |
| 87 | + args = list(node.args) + [None] * (8 - len(node.args)) |
| 88 | + n_fft = args[1] |
| 89 | + hop_length = args[2] or n_fft // 4 |
| 90 | + win_length = args[3] or n_fft |
| 91 | + onesided = True if args[6] is None else args[6] |
| 92 | + |
| 93 | + input_node = self.get_node(node.args[0]) |
| 94 | + input_tensor = self.get_tensor(input_node, node) |
| 95 | + input_tensor_wrapper = self.define_tensor( |
| 96 | + input_node, |
| 97 | + node, |
| 98 | + input_tensor, |
| 99 | + PyQnnManager.Qnn_TensorType_t.QNN_TENSOR_TYPE_NATIVE, |
| 100 | + nodes_to_wrappers, |
| 101 | + ) |
| 102 | + signal = input_tensor.reshape(-1, input_tensor.shape[-1], 1) |
| 103 | + signal_name = f"{node.name}_signal" |
| 104 | + signal_wrapper = self._define_native_tensor( |
| 105 | + signal_name, signal, input_node, node, nodes_to_wrappers |
| 106 | + ) |
| 107 | + signal_op = PyQnnManager.PyQnnOpWrapper( |
| 108 | + signal_name, QNN_OP_PACKAGE_NAME_QTI_AISW, OpReshape.op_name |
| 109 | + ) |
| 110 | + signal_op.AddInputTensors([input_tensor_wrapper]) |
| 111 | + signal_op.AddOutputTensors([signal_wrapper]) |
| 112 | + |
| 113 | + output_tensor = self.get_tensor(node, node) |
| 114 | + # torch layout with the batch dimension: [batch, freq, frames, 2] |
| 115 | + spectrum = output_tensor.reshape(-1, *output_tensor.shape[-3:]) |
| 116 | + frames_name = f"{node.name}_frames" |
| 117 | + frames_wrapper = self._define_native_tensor( |
| 118 | + frames_name, spectrum.transpose(1, 2), node, node, nodes_to_wrappers |
| 119 | + ) |
| 120 | + stft_op = PyQnnManager.PyQnnOpWrapper( |
| 121 | + frames_name, QNN_OP_PACKAGE_NAME_QTI_AISW, OpStft.op_name |
| 122 | + ) |
| 123 | + stft_op.AddInputTensors( |
| 124 | + [signal_wrapper] |
| 125 | + + self._define_window(node, n_fft, win_length, nodes_to_wrappers) |
| 126 | + ) |
| 127 | + stft_op.AddOutputTensors([frames_wrapper]) |
| 128 | + stft_op.AddScalarParam( |
| 129 | + OpStft.param_frame_step, |
| 130 | + PyQnnManager.Qnn_DataType_t.QNN_DATATYPE_UINT_32, |
| 131 | + {QCOM_DATA: np.uint32(hop_length)}, |
| 132 | + ) |
| 133 | + stft_op.AddScalarParam( |
| 134 | + OpStft.param_frame_length, |
| 135 | + PyQnnManager.Qnn_DataType_t.QNN_DATATYPE_UINT_32, |
| 136 | + {QCOM_DATA: np.uint32(n_fft)}, |
| 137 | + ) |
| 138 | + stft_op.AddScalarParam( |
| 139 | + OpStft.param_onesided, |
| 140 | + PyQnnManager.Qnn_DataType_t.QNN_DATATYPE_BOOL_8, |
| 141 | + {QCOM_DATA: onesided}, |
| 142 | + ) |
| 143 | + |
| 144 | + output_tensor_wrapper = self.define_tensor( |
| 145 | + node, |
| 146 | + node, |
| 147 | + output_tensor, |
| 148 | + PyQnnManager.Qnn_TensorType_t.QNN_TENSOR_TYPE_NATIVE, |
| 149 | + nodes_to_wrappers, |
| 150 | + ) |
| 151 | + is_batched = output_tensor.dim() == 4 |
| 152 | + transpose_name = node.name if is_batched else f"{node.name}_transpose" |
| 153 | + transpose_wrapper = ( |
| 154 | + output_tensor_wrapper |
| 155 | + if is_batched |
| 156 | + else self._define_native_tensor( |
| 157 | + transpose_name, spectrum, node, node, nodes_to_wrappers |
| 158 | + ) |
| 159 | + ) |
| 160 | + perm = [0, 2, 1, 3] |
| 161 | + transpose_op = PyQnnManager.PyQnnOpWrapper( |
| 162 | + transpose_name, QNN_OP_PACKAGE_NAME_QTI_AISW, OpTranspose.op_name |
| 163 | + ) |
| 164 | + transpose_op.AddInputTensors([frames_wrapper]) |
| 165 | + transpose_op.AddOutputTensors([transpose_wrapper]) |
| 166 | + transpose_op.AddTensorParam( |
| 167 | + OpTranspose.param_perm, |
| 168 | + PyQnnManager.Qnn_DataType_t.QNN_DATATYPE_UINT_32, |
| 169 | + 1, |
| 170 | + [len(perm)], |
| 171 | + np.array(perm, dtype=np.uint32), |
| 172 | + True, |
| 173 | + ) |
| 174 | + op_wrapper_list = [signal_op, stft_op, transpose_op] |
| 175 | + |
| 176 | + if not is_batched: |
| 177 | + unbatch_op = PyQnnManager.PyQnnOpWrapper( |
| 178 | + node.name, QNN_OP_PACKAGE_NAME_QTI_AISW, OpReshape.op_name |
| 179 | + ) |
| 180 | + unbatch_op.AddInputTensors([transpose_wrapper]) |
| 181 | + unbatch_op.AddOutputTensors([output_tensor_wrapper]) |
| 182 | + op_wrapper_list.append(unbatch_op) |
| 183 | + return op_wrapper_list |
0 commit comments