Files
alibaba--mnn/source/backend/qnn/execution/QNNBinary.cpp
T
2026-07-13 13:33:03 +08:00

228 lines
11 KiB
C++

//
// QNNBinary.cpp
// MNN
//
// Created by MNN on b'2025/04/10'.
// Copyright © 2018, Alibaba Group Holding Limited
//
#include "QNNBinary.hpp"
#include <algorithm>
namespace MNN {
namespace QNN {
#ifdef ENABLE_QNN_ONLINE_FINALIZE
static bool needChangeInputOrder(const std::string& binaryTypeName) {
std::set<std::string> NoneedChangeSet = {"ElementWiseAdd", "ElementWiseMultiply", "ElementWiseMinimum",
"ElementWiseMaximum", "ElementWiseOr", "ElementWiseEqual",
"ElementWiseNotEqual"};
return NoneedChangeSet.find(binaryTypeName) == NoneedChangeSet.end();
}
ErrorCode QNNBinary::onEncode(const std::vector<Tensor *> &inputs, const std::vector<Tensor *> &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<Tensor *> &inputs, const std::vector<Tensor *> &outputs, int fullIndex) {
std::vector<uint32_t> shape = getNHWCShape(inputs[fullIndex]);
int idleIndex = 1 - fullIndex;
Qnn_DataType_t dataType = mBackend->getNativeTensor(inputs[fullIndex])->v1.dataType;
std::vector<uint32_t> 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<Tensor *> &inputs, const std::vector<Tensor *> &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<uint32_t> idleShape = getNHWCShape(inputs[idleIndex]);
std::vector<uint32_t> 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<uint32_t> 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<uint32_t> 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<Tensor*>& inputs, const std::vector<Tensor*>& outputs, const MNN::Op* op,
Backend* backend) const override {
MNN_ASSERT(inputs.size() == 2 && outputs.size() == 1);
std::map<BinaryOpOperation, std::string> 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<EltwiseType, std::string> 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<BinaryOpOperation>(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