// // QNNBinary.cpp // MNN // // Created by MNN on b'2025/04/10'. // Copyright © 2018, Alibaba Group Holding Limited // #include "QNNBinary.hpp" #include namespace MNN { namespace QNN { #ifdef ENABLE_QNN_ONLINE_FINALIZE static bool needChangeInputOrder(const std::string& binaryTypeName) { std::set NoneedChangeSet = {"ElementWiseAdd", "ElementWiseMultiply", "ElementWiseMinimum", "ElementWiseMaximum", "ElementWiseOr", "ElementWiseEqual", "ElementWiseNotEqual"}; return NoneedChangeSet.find(binaryTypeName) == NoneedChangeSet.end(); } ErrorCode QNNBinary::onEncode(const std::vector &inputs, const std::vector &outputs) { int dim0 = inputs[0]->dimensions(); int dim1 = inputs[1]->dimensions(); int minDim = dim0 > dim1 ? dim1 : dim0; int fullIndex = dim0 > dim1 ? 0 : 1; // Broadcast binary with scalar. // By our experiments, this branch is faster than using Qnn binary operations directly, although Qnn binary operations supports scalar broadcasting. if(dim0 != dim1 && minDim == 0) { return this->onEncodeScalarOptimize(inputs, outputs, fullIndex); } if (dim0 != dim1 && TensorUtils::getDimType(inputs[0]) != TensorUtils::getDimType(inputs[1])) { fullIndex = TensorUtils::getDimType(inputs[0]) != TensorUtils::getDimType(outputs[0]) ? 1 : 0; return this->onEncodeBroadcast(inputs, outputs, fullIndex); } mNodeType = mBinaryTypeName; this->addNodeCommon(inputs, outputs); return NO_ERROR; } ErrorCode QNNBinary::onEncodeScalarOptimize(const std::vector &inputs, const std::vector &outputs, int fullIndex) { std::vector shape = getNHWCShape(inputs[fullIndex]); int idleIndex = 1 - fullIndex; Qnn_DataType_t dataType = mBackend->getNativeTensor(inputs[fullIndex])->v1.dataType; std::vector dim(inputs[fullIndex]->dimensions(), 1); this->createStageTensor("stage_0", dataType, dim, inputs[fullIndex]); // mTempTensorWrappers[0] this->createStageTensor("stage_1", dataType, shape, inputs[fullIndex]); // mTempTensorWrappers[1] this->createParamTensor("multiples", QNN_DATATYPE_UINT_32, {(uint32_t)shape.size()}, shape.data()); // mParamTensorWrappers[0] // Reshape { CLEAR_BEFORE_ADDING_NODE; std::string name = mNodeName + "_Reshape"; mNodeType = "Reshape"; mInputs.push_back(*(mBackend->getNativeTensor(inputs[idleIndex]))); // idle input mOutputs.push_back(*(mTempTensorWrappers[0]->getNativeTensor())); // stage 0 mBackend->addNodeToGraph(mOpConfigVersion, name.c_str(), mPackageName.c_str(), mNodeType.c_str(), mParams, mInputs, mOutputs); } // Tile { CLEAR_BEFORE_ADDING_NODE; std::string name = mNodeName + "_Tile"; mNodeType = "Tile"; mInputs.push_back(*(mTempTensorWrappers[0]->getNativeTensor())); // stage 0 mParams.push_back(*(mParamTensorWrappers[0]->getNativeParam())); // multiples mOutputs.push_back(*(mTempTensorWrappers[1]->getNativeTensor())); // stage 1 mBackend->addNodeToGraph(mOpConfigVersion, name.c_str(), mPackageName.c_str(), mNodeType.c_str(), mParams, mInputs, mOutputs); } // Binary // Ensure correct input order for non-commutative operations (Sub, Div, Pow, etc.) // input0Tensor corresponds to original inputs[0], input1Tensor corresponds to original inputs[1] const auto& input0Tensor = (fullIndex == 0) ? *(mBackend->getNativeTensor(inputs[fullIndex])) : *(mTempTensorWrappers[1]->getNativeTensor()); const auto& input1Tensor = (fullIndex == 0) ? *(mTempTensorWrappers[1]->getNativeTensor()) : *(mBackend->getNativeTensor(inputs[fullIndex])); { CLEAR_BEFORE_ADDING_NODE; mNodeType = mBinaryTypeName; if (needChangeInputOrder(mBinaryTypeName)) { mInputs.push_back(input0Tensor); mInputs.push_back(input1Tensor); } else { mInputs.push_back(*(mBackend->getNativeTensor(inputs[fullIndex]))); // full input mInputs.push_back(*(mTempTensorWrappers[1]->getNativeTensor())); // stage 1 } mOutputs.push_back(*(mBackend->getNativeTensor(outputs[0]))); mBackend->addNodeToGraph(mOpConfigVersion, mNodeName.c_str(), mPackageName.c_str(), mNodeType.c_str(), mParams, mInputs, mOutputs); } return NO_ERROR; } ErrorCode QNNBinary::onEncodeBroadcast(const std::vector &inputs, const std::vector &outputs, int fullIndex) { // Create resources. int idleIndex = 1 - fullIndex; int fullDim = inputs[fullIndex]->dimensions(); int idleDim = inputs[idleIndex]->dimensions(); int offset = fullDim - idleDim; std::vector idleShape = getNHWCShape(inputs[idleIndex]); std::vector permData(fullDim); Qnn_DataType_t dataType = mBackend->getNativeTensor(inputs[fullIndex])->v1.dataType; if(TensorUtils::getDescribe(inputs[idleIndex])->dimensionFormat == MNN_DATA_FORMAT_NCHW){ permData[0] = 0; permData[fullDim - 1] = 1; for(int i = 1; i < fullDim - 1; ++i){ permData[i] = i + 1; } } else{ permData[0] = 0; permData[1] = fullDim - 1; for(int i = 2; i < fullDim; ++i){ permData[i] = i - 1; } } this->createParamTensor("perm", QNN_DATATYPE_UINT_32, {(uint32_t) fullDim}, (void *) permData.data()); // mParamTensorWrappers[0] std::vector shapeStageReshape(fullDim, 1); for (int i = 0; i < idleDim; i++) {shapeStageReshape[i + offset] = idleShape[i];} this->createStageTensor("stage_reshape", dataType, shapeStageReshape, inputs[idleIndex]); // mTempTensorWrappers[0] std::vector shapeStagePerm(fullDim); for (int i = 0; i < fullDim; i++) {shapeStagePerm[i] = shapeStageReshape[permData[i]];} this->createStageTensor("stage_perm", dataType, shapeStagePerm, inputs[idleIndex]); // mTempTensorWrappers[1] // Reshape. this->addNodeCommonReshape("Reshape", *(mBackend->getNativeTensor(inputs[idleIndex])), *(mTempTensorWrappers[0]->getNativeTensor())); // Permute. this->addNodeCommonPermute("Permute", *(mTempTensorWrappers[0]->getNativeTensor()), *(mParamTensorWrappers[0]->getNativeParam()), *(mTempTensorWrappers[1]->getNativeTensor())); // Binary broadcast. // Ensure correct input order for non-commutative operations (Sub, Div, Pow, etc.) { CLEAR_BEFORE_ADDING_NODE; mNodeType = mBinaryTypeName; if (needChangeInputOrder(mBinaryTypeName) && fullIndex == 1) { mInputs.push_back(*(mTempTensorWrappers[1]->getNativeTensor())); mInputs.push_back(*(mBackend->getNativeTensor(inputs[fullIndex]))); } else { mInputs.push_back(*(mBackend->getNativeTensor(inputs[fullIndex]))); mInputs.push_back(*(mTempTensorWrappers[1]->getNativeTensor())); } mOutputs.push_back(*(mBackend->getNativeTensor(outputs[0]))); mBackend->addNodeToGraph(mOpConfigVersion, mNodeName.c_str(), mPackageName.c_str(), mNodeType.c_str(), mParams, mInputs, mOutputs); } return NO_ERROR; } class QNNBinaryCreator : public QnnBackend::Creator { public: virtual QNNCommonExecution * onCreate(const std::vector& inputs, const std::vector& outputs, const MNN::Op* op, Backend* backend) const override { MNN_ASSERT(inputs.size() == 2 && outputs.size() == 1); std::map binaryMap{{BinaryOpOperation_ADD, "ElementWiseAdd"}, {BinaryOpOperation_SUB, "ElementWiseSubtract"}, {BinaryOpOperation_MUL, "ElementWiseMultiply"}, {BinaryOpOperation_DIV, "ElementWiseDivide"}, {BinaryOpOperation_POW, "ElementWisePower"}, {BinaryOpOperation_REALDIV, "ElementWiseDivide"}, {BinaryOpOperation_MINIMUM, "ElementWiseMinimum"}, {BinaryOpOperation_MAXIMUM, "ElementWiseMaximum"}, {BinaryOpOperation_GREATER, "ElementWiseGreater"}, {BinaryOpOperation_GREATER_EQUAL, "ElementWiseGreaterEqual"}, {BinaryOpOperation_LESS, "ElementWiseLess"}, {BinaryOpOperation_FLOORDIV, "ElementWiseFloorDiv"}, {BinaryOpOperation_LESS_EQUAL, "ElementWiseLessEqual"}, {BinaryOpOperation_FLOORMOD, "ElementWiseFmod"}, {BinaryOpOperation_EQUAL, "ElementWiseEqual"}, {BinaryOpOperation_MOD, "ElementWiseMod"}, {BinaryOpOperation_LOGICALOR, "ElementWiseOr"}, {BinaryOpOperation_NOTEQUAL, "ElementWiseNotEqual"}}; std::map eltwiseMap { {EltwiseType_PROD, "ElementWiseMultiply"}, {EltwiseType_SUM, "ElementWiseAdd"}, {EltwiseType_SUB, "ElementWiseSubtract"}, {EltwiseType_MAXIMUM, "ElementWiseMaximum"} }; std::string binaryTypeName; if (op->type() == OpType_BinaryOp) { auto iter = binaryMap.find(static_cast(op->main_as_BinaryOp()->opType())); if (iter == binaryMap.end()) { MNN_ERROR("MNN_QNN: Not supported Binary type.\n"); return nullptr; } binaryTypeName = iter->second; } else { auto iter = eltwiseMap.find(op->main_as_Eltwise()->type()); if (iter == eltwiseMap.end()) { MNN_ERROR("MNN_QNN: Not supported Eltwise type.\n"); return nullptr; } binaryTypeName = iter->second; } return new QNNBinary(backend, op, binaryTypeName); } }; REGISTER_QNN_OP_CREATOR(QNNBinaryCreator, OpType_BinaryOp) REGISTER_QNN_OP_CREATOR(QNNBinaryCreator, OpType_Eltwise) #endif } // end namespace QNN } // end namespace MNN