Files
2026-07-13 13:33:03 +08:00

319 lines
15 KiB
C++

//
// OnnxSequenceGRUMerge.cpp
// MNNConverter
//
// Created by MNN on 2021/03/29.
// Copyright © 2018, Alibaba Group Holding Limited
//
#include "MNN_generated.h"
#include "OnnxExtraManager.hpp"
#include "OnnxRNNHelper.hpp"
namespace MNN {
namespace Express {
static VARP _SliceConst(VARP x, const std::vector<int>& starts, const std::vector<int>& sizes) {
auto startVAR = _Const((const void*)starts.data(), {static_cast<int>(starts.size())}, NHWC, halide_type_of<int>());
auto sizeVAR = _Const((const void*)sizes.data(), {static_cast<int>(sizes.size())}, NHWC, halide_type_of<int>());
return _Slice(x, startVAR, sizeVAR);
}
static VARP _computeRecMain(VARP Gate, VARP HI, VARP R_zrh, VARP BR, VARP BRH, int hiddenSize, int direction) {
VARP Z;
VARP R;
VARP HR = _makeConvForRStep(HI, R_zrh, hiddenSize, direction, BR);
auto splitsR = _Split(HR, {3}, 1);
auto splits = _Split(Gate, {3}, 1);
/**
zt = f(Xt*(Wz^T) + Ht-1*(Rz^T) + Wbz + Rbz)
rt = f(Xt*(Wr^T) + Ht-1*(Rr^T) + Wbr + Rbr)
ht = g(Xt*(Wh^T) + (rt (.) Ht-1)*(Rh^T) + Rbh + Wbh) # default, when linear_before_reset = 0
ht = g(Xt*(Wh^T) + (rt (.) (Ht-1*(Rh^T) + Rbh)) + Wbh) # when linear_before_reset != 0
Ht = (1 - zt) (.) ht + zt (.) Ht-1 This operator has optional inputs/outputs. See the doc for more details about the representation of optional arguments. An empty string may be used in the place of an actual argument's name to indicate a missing argument. Trailing optional arguments (those not followed by an argument that is present) may also be simply omitted.
*/
Z = _Sigmoid(splits[0] + splitsR[0]);
R = _Sigmoid(splits[1] + splitsR[1]);
VARP H;
if (nullptr == BRH) {
H = _Tanh(splits[2] + R * splitsR[2]);
} else {
// rnnGRUParam->linearBeforeReset
H = _Tanh(splits[2] + R * splitsR[2] - R * BRH + BRH);
}
H = H - Z *(H-HI);
return H;
}
class OnnxSequenceGRUTransform : public OnnxExtraManager::Transform {
public:
static EXPRP _turnGRU2While(OpT* gru, EXPRP expr) {
auto inputs = expr->inputs();
auto W_zrh = inputs[1];
auto R_zrh = inputs[2];
VARP B_2rzh = nullptr;
if (inputs.size() >= 4 && inputs[3].get() != nullptr) { // X W R B
B_2rzh = inputs[3];
}
VARP O_InitOrigin = nullptr;
if (inputs.size() >= 6) {
O_InitOrigin = inputs[5];
}
bool singleSeq = false;
if (inputs[0]->getInfo() != nullptr && inputs[0]->getInfo()->dim[0] == 1) {
singleSeq = true;
MNN_PRINT("Single SeqLength, don't use while but unrool it\n");
}
VARP BW = nullptr;
VARP BR = nullptr;
VARP BRH = nullptr;
auto rnnGRUParam = gru->main.AsRNNParam();
int directionNumer = rnnGRUParam->isBidirectionalRNN ? 2 : 1;
auto W_info = W_zrh->getInfo();
auto R_info = R_zrh->getInfo();
auto hiddenSize = rnnGRUParam->numUnits;
auto inputSize = W_info->dim[2];
if (nullptr != B_2rzh) {
auto BSplit = _Split(B_2rzh, {2}, 1);
BW = BSplit[0];
BR = BSplit[1];
if (!rnnGRUParam->linearBeforeReset) {
BRH = _Split(BR, {3}, 1)[2];
BRH = _Reshape(BRH, {1, hiddenSize, 1, 1}, NCHW);
BRH.fix(VARP::CONSTANT);
}
}
std::vector<VARP> O_InitGroup;
if (nullptr == O_InitOrigin) {
auto zeroInit = _Const(0.0f, std::vector<int>{1, hiddenSize, 1, 1}, NCHW);
for (int i=0; i<directionNumer; ++i) {
O_InitGroup.emplace_back(zeroInit);
}
} else {
O_InitGroup = _Split(O_InitOrigin, {directionNumer}, 0);
for (int i=0; i<directionNumer; ++i) {
O_InitGroup[i] = _Reshape(O_InitGroup[i], {-1, hiddenSize, 1, 1}, NCHW);
}
}
auto zero = _Unsqueeze(_Scalar<int32_t>(0), {0});
auto one = _Unsqueeze(_Scalar<int32_t>(1), {0});
auto negone = _Unsqueeze(_Scalar<int32_t>(-1), {0});
// GRU Has three component: rzh
auto componentVar = _Unsqueeze(_Scalar<int32_t>(3), {0});
std::vector<VARP> Output;
std::vector<VARP> OLast;
auto inputShape = _Shape(inputs[0], true);
auto seqLengthVar = _Slice(inputShape, _Unsqueeze(_Scalar<int32_t>(0), {0}), one);
auto batchFullVar = _Slice(inputShape, _Unsqueeze(_Scalar<int32_t>(1), {0}), one);
auto hiddenSizeVar = _Unsqueeze(_Scalar<int>(hiddenSize), {0});
for (int i=0; i<directionNumer; ++i) {
// FirstPart: Gate = MatMul(X, W, B) : N * hiddenSize, seqLength * batchSize
// Gate = Conv(Reshape(X, {seqLength * batch, inputSize, 1, 1}))
// Gate: seqLength * batch, N * hiddenSize, 1, 1
VARP FullGate = _makeConvForW(W_zrh, BW, inputs[0], inputSize, i);
if (singleSeq) {
auto H = _computeRecMain(FullGate, O_InitGroup[i], R_zrh, BR, BRH,hiddenSize, i);
Output.emplace_back(_Unsqueeze(H, {0}));
OLast.emplace_back(H);
continue;
}
// Make SubGraph
auto bodyGraphName = gru->name + "_main" + std::to_string(i);
{
auto inputShape = _Input({}, NCHW, halide_type_of<int>());
inputShape->setName("inputshape");
auto batchVar = _Slice(inputShape, _Unsqueeze(_Scalar<int32_t>(1), {0}), one);
auto hiddenSizeVar = _Unsqueeze(_Scalar<int32_t>(hiddenSize), {0});
auto step = _Input({}, NCHW, halide_type_of<int>());
step->setName("i");
VARP GateFull = _Input({-1, -1, 1, 1}, NC4HW4);
GateFull->setName("Gate");
auto size = _Concat({batchVar, hiddenSizeVar * componentVar, one, one}, 0);
VARP start;
if (0 == i) {
start = _Concat({batchVar * step, zero, zero, zero}, 0);
} else {
auto seqLengthVar = _Slice(inputShape, _Unsqueeze(_Scalar<int32_t>(0), {0}), one);
start = _Concat({batchVar * (seqLengthVar - one - step), zero, zero, zero}, 0);
}
auto Gate = _Slice(GateFull, start, size);
VARP Z;
VARP R;
VARP HI = _Input({-1, hiddenSize, 1, 1}, NCHW);
HI->setName("H");
auto H = _computeRecMain(Gate, HI, R_zrh, BR, BRH,hiddenSize, i);
H->setName("O_next");
auto cond = _Input({}, NCHW, halide_type_of<int>());
cond->setName("cond");
std::unique_ptr<OpT> copyOp(new OpT);
copyOp->type = OpType_Identity;
EXPRP copyExpr = Expr::create(copyOp.get(), {H}, 1);
auto OCopy = Variable::create(copyExpr);
OCopy->setName("O_next_copy");
auto outputCond = _Scalar<float>(1.0f);
outputCond->setName("output_cond");
ExecutorScope::Current()->registerSubGraph(bodyGraphName, {outputCond, inputShape, GateFull, H, OCopy}, {step, cond, inputShape, GateFull, HI});
}
// Make Copy Op to fuse three varps
std::unique_ptr<OpT> loopOp(new OpT);
loopOp->type = OpType_While;
loopOp->main.value = new WhileParamT;
loopOp->main.type = OpParameter_WhileParam;
auto whileP = loopOp->main.AsWhileParam();
whileP->body_graph = bodyGraphName;
auto cond = _Scalar<int>(1);
auto whileInputs = std::vector<VARP>{seqLengthVar, cond, inputShape, FullGate, O_InitGroup[i]};
auto whileExpr = Expr::create(loopOp.get(), whileInputs, 4);
auto directionO = Variable::create(whileExpr, 3);
if (1 == i) {
directionO = _Reverse(directionO, _Scalar<int>(0));
}
Output.emplace_back(directionO);
OLast.emplace_back(Variable::create(whileExpr, 2));
}
for (int i=0; i<directionNumer; ++i) {
Output[i] = OnnxExtraManager::_ReshapeF(Output[i], _Concat({seqLengthVar, one, batchFullVar, hiddenSizeVar}, 0));
OLast[i] = OnnxExtraManager::_ReshapeF(OLast[i], _Concat({one, batchFullVar, hiddenSizeVar}, 0));
}
std::unique_ptr<OpT> copyOp(new OpT);
copyOp->type = OpType_Identity;
EXPRP resultExpr;
if (1 == directionNumer) {
resultExpr = Expr::create(copyOp.get(), {Output[0], OLast[0]}, 2);
} else {
auto o0 = _Concat(Output, 1);
auto o1 = _Concat(OLast, 0);
resultExpr = Expr::create(copyOp.get(), {o0, o1}, 2);
}
resultExpr->setName(gru->name);
return resultExpr;
}
virtual EXPRP onExecute(EXPRP expr) const override {
auto inputs = expr->inputs();
auto rnnGRUParam = new MNN::RNNParamT;
std::unique_ptr<OpT> gru(new OpT);
gru->name = expr->name();
gru->type = OpType_RNNSequenceGRU;
gru->main.type = OpParameter_RNNParam;
gru->main.value = rnnGRUParam;
auto extra = expr->get()->main_as_Extra();
auto attr = extra->attr();
if (nullptr != attr) {
for (int i = 0; i < attr->size(); ++i) {
auto attUnit = attr->GetAs<Attribute>(i);
if (attUnit->key()->str() == "hidden_size") {
rnnGRUParam->numUnits = static_cast<int32_t>(attUnit->i());
} else if(attUnit->key()->str() == "direction") {
rnnGRUParam->isBidirectionalRNN = attUnit->s()->str() == "bidirectional";
} else if (attUnit->key()->str() == "linear_before_reset") {
rnnGRUParam->linearBeforeReset = static_cast<bool>(attUnit->i());
}
}
}
rnnGRUParam->keepAllOutputs = true;
// In onnx, the final hidden output(Y_h) and hidden in different sequencial(Y) could be outputed both,
// https://github.com/onnx/onnx/blob/master/docs/Operators.md#outputs-0---2
// todo: detect the next op in DFG, if Y is never used, assign mKeepAllOutputs as false.
auto W_zrh = inputs[1];
auto R_zrh = inputs[2];
VARP B_2rzh = nullptr;
if (inputs.size() >= 4 && inputs[3].get() != nullptr) { // X W R B
B_2rzh = inputs[3];
}
bool biasValid = B_2rzh == nullptr || B_2rzh->readMap<void>() != nullptr;
auto config = Global<modelConfig>::Get();
if (!config->useOriginRNNImpl) {
if (W_zrh->readMap<void>() != nullptr && biasValid && R_zrh->readMap<void>() != nullptr) {
MNN_PRINT("Try to use While to compute GRU for %s, if don't want it, add --useOriginRNNImpl \n", expr->name().c_str());
return _turnGRU2While(gru.get(), expr);
}
}
auto W_info = W_zrh->getInfo();
auto R_info = R_zrh->getInfo();
if (nullptr == W_info || nullptr == R_info) {
MNN_ERROR("Don't GRU for not W / R's shape not valid\n");
return nullptr;
}
auto hiddenSize = rnnGRUParam->numUnits;
auto inputSize = W_info->dim[2];
if (nullptr == B_2rzh) {
int direction = rnnGRUParam->isBidirectionalRNN ? 2 : 1;
B_2rzh = _Const(0.0f, {direction , 6 * hiddenSize}, NCHW);
}
if (nullptr != B_2rzh && nullptr == B_2rzh->readMap<float>()) {
MNN_ERROR("Can't solve GRU because bias is not const\n");
return nullptr;
}
MNN_ASSERT(3 * hiddenSize == W_info->dim[1]);
MNN_ASSERT(3 * hiddenSize == R_info->dim[1]);
MNN_ASSERT(hiddenSize == R_info->dim[2]);
MNN_ASSERT(rnnGRUParam->isBidirectionalRNN + 1 == W_info->dim[0]);
const int forwardParamNumber = 5;
std::vector<VARP> gruInput(1 + forwardParamNumber * (rnnGRUParam->isBidirectionalRNN + 1));
gruInput[0] = inputs[0];
auto W_R = _Concat({W_zrh, R_zrh}, 2);
// forward gru
auto forward_W_R = _Squeeze(_SliceConst(W_R, {0, 0, 0}, {1, 3 * hiddenSize, inputSize + hiddenSize}), {0});
forward_W_R = _Transpose(forward_W_R, {1, 0});
gruInput[1] = _SliceConst(forward_W_R, {0, 0}, {inputSize + hiddenSize , 2 * hiddenSize}); // gateWeight
gruInput[3] = _SliceConst(forward_W_R, {0, 2 * hiddenSize}, {inputSize + hiddenSize, hiddenSize}); // candidateWeight
auto forward_B = _SliceConst(B_2rzh, {0, 0}, {1, 6 * hiddenSize});
gruInput[2] = _SliceConst(forward_B, {0, 0}, {1, 2 * hiddenSize}); // gateBias
gruInput[4] = _SliceConst(forward_B, {0, 2 * hiddenSize}, {1, hiddenSize});// candidateBias
gruInput[5] = _SliceConst(forward_B, {0, 3 * hiddenSize}, {1, 3 * hiddenSize});// recurrentBias
// backward gru
if(rnnGRUParam->isBidirectionalRNN) {
auto backward_W_R = _Squeeze(_SliceConst(W_R, {1, 0, 0}, {1, 3 * hiddenSize, inputSize + hiddenSize}), {0});
backward_W_R = _Transpose(backward_W_R, {1, 0});
gruInput[6] = _SliceConst(backward_W_R, {0, 0}, {inputSize + hiddenSize , 2 * hiddenSize}); // backward gateWeight
gruInput[8] = _SliceConst(backward_W_R, {0, 2 * hiddenSize}, {inputSize + hiddenSize, hiddenSize}); //backward candidateWeight
auto backward_B = _SliceConst(B_2rzh, {1, 0}, {1, 6 * hiddenSize});
gruInput[7] = _SliceConst(backward_B, {0, 0}, {1, 2 * hiddenSize}); // backward gateBias
gruInput[9] = _SliceConst(backward_B, {0, 2 * hiddenSize}, {1, hiddenSize});// backward candidateBias
gruInput[10] = _SliceConst(backward_B, {0, 3 * hiddenSize}, {1, 3 * hiddenSize});// backward recurrentBias
}
// auto sequence_lens = inputs[4]; sequence_lens is ommitted at onnxConverter.cpp
if (inputs.size() > 4 && inputs[4].get() != nullptr) {
MNN_ERROR("Don't support sequence_lens input, all batch have seq_length\n");
return nullptr;
}
if (inputs.size() > 5) { // initial_h exist, shape is [num_directions, batch_size, hidden_size]
gruInput.push_back(inputs[5]);
}
auto gruExpr = Expr::create(gru.get(), gruInput, expr->outputSize());
gruExpr->setName(expr->name());
for (int i = 0; i < expr->outputSize(); ++i) {
Variable::create(gruExpr, i)->setName(expr->outputName(i));
}
return gruExpr;
}
};
static auto gRegister = []() {
OnnxExtraManager::get()->insert("GRU", std::shared_ptr<OnnxExtraManager::Transform>(new OnnxSequenceGRUTransform));
return true;
}();
} // namespace Express
} // namespace MNN