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

247 lines
7.0 KiB
C++

//
// FuseFmhca.cpp
// MNNConverter
//
// Created by MNN on 2024/01/10.
// Copyright © 2018, Alibaba Group Holding Limited
//
#include <unordered_map>
#include "../TemplateMerge.hpp"
#include "MNN/expr/ExprCreator.hpp"
#include "MNN_generated.h"
#include "MergeHelpers.hpp"
namespace MNN {
namespace Express {
class FuseFmhca {
public:
FuseFmhca();
private:
EXPRP node_q, node_k, node_v;
VARP var_q_weight, var_k_weight, var_v_weight;
VARP fmhca_kv_input_, fmhca_q_input_;
int mNumHeads;
};
EXPRP GetFmhcaBlockAboveNode(EXPRP expr) {
if (!helpers::IsReshape(expr)) {
return nullptr;
}
auto z = expr;
auto x = z->inputs().at(0)->expr().first;
if (!helpers::IsTranspose(x)) {
return nullptr;
}
z = x;
x = z->inputs().at(0)->expr().first;
if (!helpers::IsReshape(x)) {
return nullptr;
}
z = x;
return z;
}
int GetFmhcaNumHeads(EXPRP expr) {
if (!helpers::IsReshape(expr)) {
return 0;
}
auto z = expr;
auto x = z->inputs().at(1)->expr().first;
if (!helpers::IsConcat(x)) {
return 0;
}
z = x;
x = z->inputs().at(2)->expr().first;
if (!helpers::IsConstant(x)) {
return 0;
}
auto var_num_head = z->inputs().at(2);
return var_num_head->readMap<int32_t>()[0];
}
FuseFmhca::FuseFmhca() {
auto match = [this](EXPRP expr) -> bool {
auto config = Global<modelConfig>::Get();
if(!config->transformerFuse) {
return false;
}
// whether reshape
if (!expr->get() || !helpers::IsReshape(expr)) {
return false;
}
EXPRP x, y, z;
// whether transpose
x = expr->inputs().at(0)->expr().first;
if (!expr->get() || !helpers::IsTranspose(x)) {
return false;
}
z = x;
// whether reshape
x = z->inputs().at(0)->expr().first;
if (!helpers::IsReshape(x)) {
return false;
}
z = x;
// whether cast
x = z->inputs().at(0)->expr().first;
if (helpers::IsCast(x)) {
z = x->inputs().at(0)->expr().first;
} else {
z = x;
}
// whether scatternd
while (z->inputs().size() >= 3 && helpers::IsScatterNd(z)) {
z = z->inputs().at(1)->expr().first;
}
// whether Einsum/MatMul
x = z->inputs().at(0)->expr().first;
if (!x->get()) {
return false;
}
x = x->inputs().at(0)->expr().first;
if (helpers::IsMatMul(x)) {
z = x;
} else {
return false;
}
// whether V
auto qk_pre = z->inputs().at(0)->expr().first;
auto v_pre = z->inputs().at(1)->expr().first;
z = GetFmhcaBlockAboveNode(v_pre);
if (z == nullptr) {
return false;
}
mNumHeads = GetFmhcaNumHeads(z);
node_v = z->inputs().at(0)->expr().first;
if (!helpers::IsMatMul(node_v)) {
return false;
}
// whether cast
if (helpers::IsCast(qk_pre)) {
qk_pre = qk_pre->inputs().at(0)->expr().first;
}
z = qk_pre;
// whether softmax
if (!helpers::IsSoftmax(z)) {
return false;
}
//whether matmul
x = z->inputs().at(0)->expr().first;
if (helpers::IsMatMul(x)) {
z = x;
} else {
return false;
}
auto q_pre = z->inputs().at(0)->expr().first;
auto k_pre = z->inputs().at(1)->expr().first;
z = GetFmhcaBlockAboveNode(k_pre);
if (z == nullptr) {
return false;
}
if (mNumHeads != GetFmhcaNumHeads(z)) {
return false;
}
node_k = z->inputs().at(0)->expr().first;
// whether mul(scale)
if (helpers::IsBinaryMul(node_k)) {
node_k = node_k->inputs().at(0)->expr().first;
}
if (!helpers::IsMatMul(node_k)) {
return false;
}
// whether slice
if (helpers::IsSlice(q_pre)) {
q_pre = q_pre->inputs().at(0)->expr().first;
}
z = GetFmhcaBlockAboveNode(q_pre);
if (z == nullptr) {
return false;
}
if (mNumHeads != GetFmhcaNumHeads(z)) {
return false;
}
fmhca_q_input_ = z->inputs().at(0);
node_q = z->inputs().at(0)->expr().first;
if (!helpers::IsMatMul(node_q)) {
return false;
}
// KV -> one source, Q -> different
if (node_k->inputs().at(0)->expr().first != node_v->inputs().at(0)->expr().first || node_q->inputs().at(0)->expr().first == node_k->inputs().at(0)->expr().first) {
return false;
}
fmhca_kv_input_ = node_k->inputs().at(0);
var_q_weight = node_q->inputs().at(1);
var_k_weight = node_k->inputs().at(1);
var_v_weight = node_v->inputs().at(1);
if(!helpers::IsConstant(var_q_weight->expr().first) || !helpers::IsConstant(var_k_weight->expr().first) || !helpers::IsConstant(var_v_weight->expr().first)) {
return false;
}
return true;
};
auto fold = [this](EXPRP expr) -> bool {
auto config = Global<modelConfig>::Get();
auto version = config->targetVersion;
if (version < 2.8f) {
// For target version < 2.8 , don't support fmhca
return false;
}
auto* var_k_weight_info = var_k_weight->getInfo();
auto* var_v_weight_info = var_v_weight->getInfo();
if (!var_k_weight_info || !var_v_weight_info || var_k_weight_info->size != var_v_weight_info->size) {
return false;
}
int size = var_k_weight_info->size;
int C = var_k_weight_info->dim[0];
int H = mNumHeads;
int D = var_k_weight_info->dim[1] / H;
if (var_k_weight_info->dim[1] % H != 0) {
return false;
}
auto output = _MatMul(fmhca_kv_input_, _Reshape(_Concat({_Reshape(var_k_weight, {C, H, D}), _Reshape(var_v_weight, {C, H, D})}, -1), {C, 2*H*D}), false, false);
std::unique_ptr<MNN::FmhcaParamT> param_fmhca(new MNN::FmhcaParamT);
param_fmhca->heads = mNumHeads;
std::unique_ptr<OpT> fmhca_op(new OpT);
fmhca_op->name = "Fmhca_" + expr->name();
fmhca_op->type = OpType_Fmhca;
fmhca_op->main.type = OpParameter_FmhcaParam;
fmhca_op->main.value = param_fmhca.release();
auto fmhca_expr = Variable::create(Expr::create(fmhca_op.get(), {fmhca_q_input_, output}, 1));
fmhca_expr->setName(expr->name());
Expr::replace(expr, fmhca_expr->expr().first);
return true /*modified*/;
};
TemplateMerge::getInstance("Merge").insertTemplate("FuseFmhca", match, fold);
}
//static FuseFmhca g_fuse_Fmhca;
} // namespace Express
} // namespace MNN