Files
alibaba--mnn/tools/converter/source/optimizer/merge/ConvDeQuantizeLinearFuseToConvInt8.cpp
2026-07-13 13:33:03 +08:00

727 lines
33 KiB
C++

//
// ConvQuantizeDequantizeLinearFuseToConvInt8.cpp
// MNNConverter
//
// Created by MNN on 2020/07/28.
// Copyright © 2018, Alibaba Group Holding Limited
//
#include "../TemplateMerge.hpp"
#include "MNN/expr/MathOp.hpp"
#include "MNN/expr/NeuralNetWorkOp.hpp"
#include "MNN_generated.h"
#include "MNN_compression.pb.h"
#include <fstream>
namespace MNN {
namespace Express {
static VARP _ReshapeF(VARP x, VARP shape, MNN::MNN_DATA_FORMAT format) {
MNN_ASSERT(nullptr != x);
std::unique_ptr<OpT> reshape(new OpT);
reshape->type = OpType_Reshape;
reshape->main.type = OpParameter_Reshape;
reshape->main.value = new ReshapeT;
reshape->main.AsReshape()->dimType = format;
return (Variable::create(Expr::create(reshape.get(), {x, shape})));
}
static VARP _ConvertF(VARP input, MNN::MNN_DATA_FORMAT format) {
std::unique_ptr<OpT> convert(new OpT);
convert->type = OpType_ConvertTensor;
convert->main.type = OpParameter_TensorConvertInfo;
convert->main.value = new TensorConvertInfoT;
convert->main.AsTensorConvertInfo()->source = MNN_DATA_FORMAT_NC4HW4;
convert->main.AsTensorConvertInfo()->dest = format;
return (Variable::create(Expr::create(convert.get(), {input})));
}
static bool matchConvInt8ToOther(EXPRP expr, int i) { // convint8->quant->cast->dequant->other
// check op type not convint8.
if (nullptr == expr->get()) {
return false;
}
if (expr->get()->type() == OpType_ConvInt8 || expr->get()->type() == OpType_Cast || expr->get()->type() == OpType_Int8ToFloat || expr->get()->type() == OpType_FloatToInt8 || expr->get()->type() == OpType_Const || expr->get()->type() == OpType_DepthwiseConvInt8 || expr->get()->type() == OpType_MatMul) {
return false;
}
// check dequantize linear
VARP dequant_var = expr->inputs().at(i);
EXPRP dequant_expr = dequant_var->expr().first;
if (!dequant_expr->get() || dequant_expr->get()->type() != OpType_Int8ToFloat) {
return false;
}
if (dequant_expr->inputs().size() != 5) {
return false;
}
// check cast
VARP cast_var = dequant_expr->inputs().at(0);
EXPRP cast_expr = cast_var->expr().first;
if (!cast_expr->get() || cast_expr->get()->type() != OpType_Cast) {
return false;
}
// check quantize linear
VARP quan_var = cast_expr->inputs().at(0);
EXPRP quan_expr = quan_var->expr().first;
if (!quan_expr->get() || quan_expr->get()->type() != OpType_FloatToInt8) {
return false;
}
if (quan_expr->inputs().size() != 5) {
return false;
}
// check convInt8
VARP conv_var = quan_expr->inputs().at(0);
EXPRP conv_expr = conv_var->expr().first;
if (!conv_expr->get() || (conv_expr->get()->type() != OpType_ConvInt8 && conv_expr->get()->type() != OpType_DepthwiseConvInt8 && conv_expr->get()->type() != OpType_ReLU && conv_expr->get()->type() != OpType_ReLU6)) {
return false;
}
if (conv_expr->get()->type() == OpType_ReLU || conv_expr->get()->type() == OpType_ReLU6) {
conv_var = conv_expr->inputs().at(0);
conv_expr = conv_var->expr().first;
if (!conv_expr->get() || (conv_expr->get()->type() != OpType_ConvInt8 && conv_expr->get()->type() != OpType_DepthwiseConvInt8)) {
return false;
}
}
return true;
}
static VARP transformConvInt8ToOther(EXPRP expr, int i) { // convint8->quant->cast->dequant->other => convInt8(float output)->other
auto dequant_var = expr->inputs()[i];
auto dequant_expr = dequant_var->expr().first;
auto cast_var = dequant_expr->inputs().at(0);
auto cast_expr = cast_var->expr().first;
auto quan_var = cast_expr->inputs().at(0);
auto quan_expr = quan_var->expr().first;
auto conv_var = quan_expr->inputs().at(0);
auto conv_expr = conv_var->expr().first;
auto convInt8Input = conv_expr->inputs().at(0);
bool hasRelu = false, hasRelu6 = false;
if (conv_expr->get()->type() == OpType_ReLU || conv_expr->get()->type() == OpType_ReLU6) {
hasRelu = conv_expr->get()->type() == OpType_ReLU ? true : false;
hasRelu6 = conv_expr->get()->type() == OpType_ReLU6 ? true : false;
conv_expr = convInt8Input->expr().first;
convInt8Input = conv_expr->inputs().at(0);
}
// change old convInt8 to return a float value, which is input to expr;
std::unique_ptr<Convolution2DT> newConvInt8(new MNN::Convolution2DT);
std::unique_ptr<OpT> oldConvOp(conv_expr->get()->UnPack());
auto oldConvParams = oldConvOp->main.AsConvolution2D();
float output_zero = oldConvParams->symmetricQuan->outputZeroPoint;
float output_scale = oldConvParams->quanParameter->scaleOut;
float input_scale = oldConvParams->quanParameter->scaleIn;
float input_zero = oldConvParams->symmetricQuan->zeroPoint;
newConvInt8->common.reset(new MNN::Convolution2DCommonT);
newConvInt8->common = std::move(oldConvParams->common);
newConvInt8->common->relu = hasRelu;
newConvInt8->common->relu6 = hasRelu6;
newConvInt8->symmetricQuan.reset(new QuantizedFloatParamT);
newConvInt8->symmetricQuan = std::move(oldConvParams->symmetricQuan);
//newConvInt8->symmetricQuan->outputDataType = MNN::DataType_DT_FLOAT;
newConvInt8->quanParameter.reset(new IDSTQuanT);
newConvInt8->bias = std::move(oldConvParams->bias);
newConvInt8->quanParameter = std::move(oldConvParams->quanParameter);
std::unique_ptr<OpT> conv_op(new OpT);
conv_op->name = conv_expr->name();
conv_op->type = OpType_ConvInt8;
conv_op->main.type = OpParameter_Convolution2D;
conv_op->main.value = newConvInt8.release();
convInt8Input->writeScaleMap(input_scale, input_zero);
auto newconv_expr = Expr::create(conv_op.get(), {convInt8Input});
newconv_expr->setName(conv_expr->name());
auto newconv_var = Variable::create(newconv_expr);
newconv_var->setName(conv_expr->outputName(0));
newconv_var->writeScaleMap(output_scale, output_zero);
if (conv_expr->inputs().size() == 5) { // Process matmul output
auto config = Global<modelConfig>::Get();
auto format = MNN::MNN_DATA_FORMAT_NCHW;
if (config->model == modelConfig::TFLITE || config->model == modelConfig::TENSORFLOW) {
format = MNN_DATA_FORMAT_NHWC;
}
// expr->inputs = {input, concat, needSqueezeA, needSqueezeB, transposeA}
auto concat_var = conv_expr->inputs().at(1);
bool needSqueezeA = conv_expr->inputs().at(2)->readMap<float>()[0] > 0.f;
bool needSqueezeB = conv_expr->inputs().at(3)->readMap<float>()[0] > 0.f;
auto output = _ConvertF(newconv_var, format);
output->writeScaleMap(output_scale, output_zero);
VARP reshapeVar = _ReshapeF(output, concat_var, format);
reshapeVar->writeScaleMap(output_scale, output_zero);
if (needSqueezeA) {
reshapeVar = _Squeeze(reshapeVar, {0});
reshapeVar->writeScaleMap(output_scale, output_zero);
}
if (needSqueezeB) {
reshapeVar = _Squeeze(reshapeVar, {1});
reshapeVar->writeScaleMap(output_scale, output_zero);
}
reshapeVar->setName(expr->outputName(0) + "__matmul_cvt_convInt8_reshape");
Expr::replace(conv_expr, reshapeVar->expr().first);
return reshapeVar;
}
Expr::replace(conv_expr, newconv_expr);
return newconv_var;
}
static bool matchOtherToOther (EXPRP expr, int i) { // ohter->quant->cast->dequant->other
// check op type not convint8.
if (nullptr == expr->get()) {
return false;
}
if (expr->get()->type() == OpType_ConvInt8 || expr->get()->type() == OpType_Cast || expr->get()->type() == OpType_Int8ToFloat || expr->get()->type() == OpType_FloatToInt8 || expr->get()->type() == OpType_Const || expr->get()->type() == OpType_DepthwiseConvInt8 || expr->get()->type() == OpType_MatMul) {
return false;
}
// check dequantize linear
VARP dequant_var = expr->inputs().at(i);
EXPRP dequant_expr = dequant_var->expr().first;
if (!dequant_expr->get() || dequant_expr->get()->type() != OpType_Int8ToFloat) {
return false;
}
if (dequant_expr->inputs().size() != 5) {
return false;
}
// check cast
VARP cast_var = dequant_expr->inputs().at(0);
EXPRP cast_expr = cast_var->expr().first;
if (!cast_expr->get() || cast_expr->get()->type() != OpType_Cast) {
return false;
}
// check quantize linear
VARP quan_var = cast_expr->inputs().at(0);
EXPRP quan_expr = quan_var->expr().first;
if (!quan_expr->get() || quan_expr->get()->type() != OpType_FloatToInt8) {
return false;
}
if (quan_expr->inputs().size() != 5) {
return false;
}
// check other
VARP other_var = quan_expr->inputs().at(0);
EXPRP other_expr = other_var->expr().first;
if (!other_expr->get()) {
return false;
}
if (other_expr->get()->type() == OpType_ConvInt8 || other_expr->get()->type() == OpType_Cast || other_expr->get()->type() == OpType_Int8ToFloat || other_expr->get()->type() == OpType_FloatToInt8 || other_expr->get()->type() == OpType_Const || other_expr->get()->type() == OpType_DepthwiseConvInt8) {
return false;
}
return true;
}
static VARP transformOtherToOther (EXPRP expr, int i) { // ohter->quant->cast->dequant->other => other->other
auto dequant_var = expr->inputs()[i];
auto dequant_expr = dequant_var->expr().first;
auto cast_var = dequant_expr->inputs().at(0);
auto cast_expr = cast_var->expr().first;
auto quan_var = cast_expr->inputs().at(0);
auto quan_expr = quan_var->expr().first;
auto input_var = quan_expr->inputs().at(0);
float scale = quan_expr->inputs().at(2)->readMap<float>()[0];
float zero = quan_expr->inputs().at(3)->readMap<float>()[0];
input_var->writeScaleMap(scale, zero);
return input_var;
}
static VARP buildInputForMatmulInt8 (VARP input, VARP transposeA, VARP SqueezeA, int num_input) {
auto transposeAType = transposeA->expr().first;
auto transposeAInfo = transposeA->getInfo();
if (!transposeAInfo) {
return input;
}
if (transposeAInfo) {
if (!transposeAInfo->dim.empty()) {
return input;
}
}
VARP newInput = std::move(input);
auto format = MNN::MNN_DATA_FORMAT_NCHW;
auto inputL = _Unsqueeze(_Scalar<int>(num_input), {0});
inputL.fix(VARP::CONSTANT);
VARP inputE;
float needSqueezeA = SqueezeA->readMap<float>()[0];
if (needSqueezeA != 0) {
newInput = _Unsqueeze(newInput, {0});
}
auto rank = _Rank(newInput);
auto inputShape = _Shape(newInput, NCHW);
if (transposeA->readMap<float>()[0]) {
inputE = _Slice(inputShape, _Unsqueeze(rank - _Scalar<int>(1), {0}), _Unsqueeze(_Scalar<int>(1), {0}));
newInput = _ReshapeF(newInput, _Concat({_Unsqueeze(_Scalar<int>(-1), {0}), inputL, inputE, _Unsqueeze(_Scalar<int>(1), {0})}, 0), format);
} else {
newInput = _ReshapeF(newInput, _Concat({_Unsqueeze(_Scalar<int>(-1), {0}), inputL, _Unsqueeze(_Scalar<int>(1), {0}), _Unsqueeze(_Scalar<int>(1), {0})}, 0), format);
}
return newInput;
}
static EXPRP buildNewConvExpr(EXPRP oldConvExpr, VARP convInput, std::vector<bool> updateInfo = {}) {
std::unique_ptr<Convolution2DT> newConvInt8(new MNN::Convolution2DT);
std::unique_ptr<OpT> oldConvOp(oldConvExpr->get()->UnPack());
auto oldConvParams = oldConvOp->main.AsConvolution2D();
newConvInt8->common.reset(new MNN::Convolution2DCommonT);
newConvInt8->common = std::move(oldConvParams->common);
newConvInt8->symmetricQuan.reset(new QuantizedFloatParamT);
newConvInt8->symmetricQuan = std::move(oldConvParams->symmetricQuan);
newConvInt8->quanParameter.reset(new IDSTQuanT);
newConvInt8->quanParameter = std::move(oldConvParams->quanParameter);
newConvInt8->bias = std::move(oldConvParams->bias);
if (updateInfo.size() > 0) {
newConvInt8->common->relu = updateInfo[0] ? true : false;
}
if (updateInfo.size() > 1) {
newConvInt8->common->relu6 = updateInfo[1] ? true : false;
}
if (updateInfo.size() > 2) {
newConvInt8->symmetricQuan->outputDataType = updateInfo[2] ? DataType_DT_FLOAT : DataType_DT_INT8;
}
float input_scale = newConvInt8->quanParameter->scaleIn;
float input_zero = newConvInt8->symmetricQuan->zeroPoint;
convInput->writeScaleMap(input_scale, input_zero);
std::unique_ptr<OpT> conv_op(new OpT);
conv_op->name = oldConvExpr->name();
conv_op->type = oldConvOp->type;
conv_op->main.type = OpParameter_Convolution2D;
conv_op->main.value = newConvInt8.release();
auto new_conv_expr = Expr::create(conv_op.get(), {convInput});
return new_conv_expr;
}
static auto gRegister = []() { // convInt8->(relu)->quant->cast->dequant->convInt8
auto matchConvInt8ToConvInt8 = [](EXPRP expr) {
// check convInt8
if (nullptr == expr->get()) {
return false;
}
if (expr->get()->type() != OpType_ConvInt8 && expr->get()->type() != OpType_DepthwiseConvInt8) {
return false;
}
// check dequantize linear
VARP dequant_var = expr->inputs().at(0);
EXPRP dequant_expr = dequant_var->expr().first;
if (!dequant_expr->get() || dequant_expr->get()->type() != OpType_Int8ToFloat) {
return false;
}
if (dequant_expr->inputs().size() != 5) {
return false;
}
// check cast
VARP cast_var = dequant_expr->inputs().at(0);
EXPRP cast_expr = cast_var->expr().first;
if (!cast_expr->get() || cast_expr->get()->type() != OpType_Cast) {
return false;
}
// check quantize linear
VARP quan_var = cast_expr->inputs().at(0);
EXPRP quan_expr = quan_var->expr().first;
if (!quan_expr->get() || quan_expr->get()->type() != OpType_FloatToInt8) {
return false;
}
if (quan_expr->inputs().size() != 5) {
return false;
}
// check convInt8
VARP conv_var = quan_expr->inputs().at(0);
EXPRP conv_expr = conv_var->expr().first;
if (!conv_expr->get()) {
return false;
}
if (conv_expr->get()->type() != OpType_PReLU && conv_expr->get()->type() != OpType_ReLU && conv_expr->get()->type() != OpType_ReLU6 && conv_expr->get()->type() != OpType_ConvInt8 && conv_expr->get()->type() != OpType_DepthwiseConvInt8) {
return false;
}
if (conv_expr->get()->type() == OpType_PReLU || conv_expr->get()->type() == OpType_ReLU || conv_expr->get()->type() == OpType_ReLU6) {
VARP conv_var_0 = conv_expr->inputs().at(0);
EXPRP conv_expr_0 = conv_var_0->expr().first;
if (!conv_expr_0->get()) {
return false;
}
if (conv_expr_0->get()->type() != OpType_ConvInt8 && conv_expr_0->get()->type() != OpType_DepthwiseConvInt8) {
return false;
}
}
return true;
};
auto transformConvInt8ToConvInt8 = [](EXPRP expr) {
auto dequant_var = expr->inputs()[0];
auto dequant_expr = dequant_var->expr().first;
auto cast_var = dequant_expr->inputs().at(0);
auto cast_expr = cast_var->expr().first;
auto quan_var = cast_expr->inputs().at(0);
auto quan_expr = quan_var->expr().first;
auto convInt8Input = quan_expr->inputs().at(0);
/* conv params*/
std::unique_ptr<Convolution2DT> newConvInt8(new MNN::Convolution2DT);
std::unique_ptr<OpT> oldConvOp(expr->get()->UnPack());
auto oldConvParams = oldConvOp->main.AsConvolution2D();
float input_scale = oldConvParams->quanParameter->scaleIn;
float input_zero = oldConvParams->symmetricQuan->zeroPoint;
/* check */
auto conv_var = quan_expr->inputs().at(0);
conv_var->writeScaleMap(input_scale, input_zero);
EXPRP conv_expr = conv_var->expr().first;
VARP first_conv_input_var = conv_expr->inputs().at(0);
if (conv_expr->get()->type() == OpType_PReLU || conv_expr->get()->type() == OpType_ReLU || conv_expr->get()->type() == OpType_ReLU6) {
auto relu_expr = conv_expr;
bool relu_ = relu_expr->get()->type() == OpType_ReLU ? true: false;
bool relu6_ = relu_expr->get()->type() == OpType_ReLU6 ? true: false;
VARP conv_var_0 = relu_expr->inputs().at(0);
conv_expr = conv_var_0->expr().first;
first_conv_input_var = conv_expr->inputs().at(0);
auto newFirstConvExpr = buildNewConvExpr(conv_expr, first_conv_input_var, {relu_, relu6_}); // write scale for first_conv_input_var
Expr::replace(conv_expr, newFirstConvExpr);
convInt8Input = Variable::create(conv_expr);
conv_var = convInt8Input;
conv_var->writeScaleMap(input_scale, input_zero);
} else {
auto newFirstConvExpr = buildNewConvExpr(conv_expr, first_conv_input_var); // Just write scale for first_conv_input_var, do not update conv info.
Expr::replace(conv_expr, newFirstConvExpr);
convInt8Input = Variable::create(conv_expr);
conv_var = convInt8Input;
conv_var->writeScaleMap(input_scale, input_zero);
}
if (conv_expr->inputs().size() == 5) {
// Process matmul output
auto config = Global<modelConfig>::Get();
auto format = MNN::MNN_DATA_FORMAT_NCHW;
if (config->model == modelConfig::TFLITE || config->model == modelConfig::TENSORFLOW) {
format = MNN_DATA_FORMAT_NHWC;
}
// expr->inputs = {input, concat, needSqueezeA, needSqueezeB, transposeA}
auto concat_var = conv_expr->inputs().at(1);
bool needSqueezeA = conv_expr->inputs().at(2)->readMap<float>()[0] > 0.f;
bool needSqueezeB = conv_expr->inputs().at(3)->readMap<float>()[0] > 0.f;
auto output = _ConvertF(conv_var, format);
output->writeScaleMap(input_scale, input_zero);
VARP reshapeVar = _ReshapeF(output, concat_var, format);
reshapeVar->writeScaleMap(input_scale, input_zero);
if (needSqueezeA) {
reshapeVar = _Squeeze(reshapeVar, {0});
}
if (needSqueezeB) {
reshapeVar = _Squeeze(reshapeVar, {1});
}
reshapeVar->setName(conv_expr->outputName(0) + "__matmul_cvt_convInt8_reshape");
Expr::replace(conv_expr, reshapeVar->expr().first);
convInt8Input = reshapeVar;
convInt8Input->writeScaleMap(input_scale, input_zero);
}
if (expr->inputs().size() == 5) {
auto matmulop = expr->get();
auto count_input = matmulop->main_as_Convolution2D()->common()->inputCount();
convInt8Input = buildInputForMatmulInt8(convInt8Input, expr->inputs().at(4), expr->inputs().at(2), count_input);
convInt8Input->writeScaleMap(input_scale, input_zero);
}
newConvInt8->common.reset(new MNN::Convolution2DCommonT);
newConvInt8->common = std::move(oldConvParams->common);
newConvInt8->symmetricQuan.reset(new QuantizedFloatParamT);
newConvInt8->symmetricQuan = std::move(oldConvParams->symmetricQuan);
newConvInt8->quanParameter.reset(new IDSTQuanT);
newConvInt8->quanParameter = std::move(oldConvParams->quanParameter);
newConvInt8->bias = std::move(oldConvParams->bias);
float scaleout = newConvInt8->quanParameter->scaleOut;
float zeroout = newConvInt8->symmetricQuan->outputZeroPoint;
std::unique_ptr<OpT> conv_op(new OpT);
conv_op->name = expr->name();
conv_op->type = oldConvOp->type;
conv_op->main.type = OpParameter_Convolution2D;
conv_op->main.value = newConvInt8.release();
auto new_conv_expr = Expr::create(conv_op.get(), {convInt8Input});
if (expr->inputs().size() == 5) {
new_conv_expr = Expr::create(conv_op.get(), {convInt8Input, expr->inputs()[1], expr->inputs()[2], expr->inputs()[3], expr->inputs()[4]});
}
new_conv_expr->setName(expr->name());
auto new_conv_var = Variable::create(new_conv_expr);
new_conv_var->writeScaleMap(scaleout, zeroout);
Expr::replace(expr, new_conv_expr);
return true;
};
auto matchOtherToConvInt8 = [](EXPRP expr) { // otherOp->quant->cast->dequant->convint8
// check op type is convint8.
if (nullptr == expr->get()) {
return false;
}
if (expr->get()->type() != OpType_ConvInt8 && expr->get()->type() != OpType_DepthwiseConvInt8) {
return false;
}
// check dequantize linear
VARP dequant_var = expr->inputs().at(0);
EXPRP dequant_expr = dequant_var->expr().first;
if (!dequant_expr->get() || dequant_expr->get()->type() != OpType_Int8ToFloat) {
return false;
}
if (dequant_expr->inputs().size() != 5) {
return false;
}
// check cast
VARP cast_var = dequant_expr->inputs().at(0);
EXPRP cast_expr = cast_var->expr().first;
if (!cast_expr->get() || cast_expr->get()->type() != OpType_Cast) {
return false;
}
// check quantize linear
VARP quan_var = cast_expr->inputs().at(0);
EXPRP quan_expr = quan_var->expr().first;
if (!quan_expr->get() || (quan_expr->get()->type() != OpType_FloatToInt8 && quan_expr->get()->type() != OpType_ConvertTensor)) {
return false;
}
if (quan_expr->get()->type() == OpType_FloatToInt8 && quan_expr->inputs().size() != 5) {
return false;
}
// check other
VARP other_var = quan_expr->inputs().at(0);
EXPRP other_expr = other_var->expr().first;
if (!other_expr->get()) {
return true;
}
if (other_expr->get()->type() == OpType_ConvInt8 || other_expr->get()->type() == OpType_Cast || other_expr->get()->type() == OpType_Int8ToFloat || other_expr->get()->type() == OpType_FloatToInt8 || other_expr->get()->type() == OpType_Const || other_expr->get()->type() == OpType_DepthwiseConvInt8) {
return false;
}
return true;
};
auto transformOtherToConvInt8 = [](EXPRP expr) {
auto dequant_var = expr->inputs()[0];
auto dequant_expr = dequant_var->expr().first;
auto cast_var = dequant_expr->inputs().at(0);
auto cast_expr = cast_var->expr().first;
auto quan_var = cast_expr->inputs().at(0);
auto quan_expr = quan_var->expr().first;
auto convInt8Input = quan_expr->inputs().at(0);
auto other_var = convInt8Input;
if (expr->inputs().size() == 5) {
// [input,concat,squeezeA,squeezeB,transposeA]
auto matmulop = expr->get();
auto count_input = matmulop->main_as_Convolution2D()->common()->inputCount();
convInt8Input = buildInputForMatmulInt8(convInt8Input, expr->inputs().at(4), expr->inputs().at(2), count_input);
convInt8Input->setName(expr->name() + "__matmul_converted_input");
}
std::unique_ptr<Convolution2DT> newConvInt8(new MNN::Convolution2DT);
std::unique_ptr<OpT> oldConvOp(expr->get()->UnPack());
auto oldConvParams = oldConvOp->main.AsConvolution2D();
float input_scale = oldConvParams->quanParameter->scaleIn;
float output_scale = oldConvParams->quanParameter->scaleOut;
float input_zero = static_cast<float>(oldConvParams->symmetricQuan->zeroPoint);
float output_zero = static_cast<float>(oldConvParams->symmetricQuan->outputZeroPoint);
newConvInt8->common.reset(new MNN::Convolution2DCommonT);
newConvInt8->common = std::move(oldConvParams->common);
newConvInt8->symmetricQuan.reset(new QuantizedFloatParamT);
newConvInt8->symmetricQuan = std::move(oldConvParams->symmetricQuan);
newConvInt8->bias = std::move(oldConvParams->bias);
newConvInt8->quanParameter.reset(new IDSTQuanT);
newConvInt8->quanParameter = std::move(oldConvParams->quanParameter);
std::unique_ptr<OpT> conv_op(new OpT);
conv_op->name = expr->name();
conv_op->type = oldConvOp->type;
conv_op->main.type = OpParameter_Convolution2D;
conv_op->main.value = newConvInt8.release();
other_var->writeScaleMap(input_scale, input_zero);
convInt8Input->writeScaleMap(input_scale, input_zero);
auto conv_expr = Expr::create(conv_op.get(), {convInt8Input});
if (expr->inputs().size() == 5) {
conv_expr = Expr::create(conv_op.get(), {convInt8Input, expr->inputs()[1], expr->inputs()[2], expr->inputs()[3], expr->inputs()[4]});
}
auto conv_var = Variable::create(conv_expr);
conv_var->writeScaleMap(output_scale, output_zero);
conv_expr->setName(expr->name());
Expr::replace(expr, conv_expr);
return true;
};
// X to otherOp
auto matchXToOther = [](EXPRP expr) { // X->quant->cast->dequant->other
// check op type not convint8.
if (nullptr == expr->get()) {
return false;
}
if (expr->get()->type() != OpType_Cast) {
return false;
}
auto castparam = expr->get()->main_as_CastParam();
if (castparam->dstT() != MNN::DataType_DT_UINT8) {
return false;
}
auto quantExpr = expr->inputs()[0]->expr().first;
if (quantExpr->get()->type() != OpType_FloatToInt8) {
return false;
}
return true;
};
auto transformXToOther = [](EXPRP expr) { // X->quant->cast->dequant->output_other => X->output_other
auto quantExpr = expr->inputs()[0]->expr().first;
// generate a new oher op.
std::unique_ptr<OpT> oldOtherOp(quantExpr->get()->UnPack());
auto newop_expr = Expr::create(oldOtherOp.get(), quantExpr->inputs());
newop_expr->setName(expr->name());
Expr::replace(expr, newop_expr);
return true;
};
// endding op->X
auto matchXToEnd= [](EXPRP expr) { // otherOp->quant->cast->dequant->convint8
if (nullptr == expr->get()) {
return false;
}
if (expr->get()->type() == OpType_Const || expr->get()->type() == OpType_TrainableParam) {
return false;
}
// check op type is Int8ToFloat.
if (expr->get()->type() != OpType_Int8ToFloat) {
return false;
}
// check op is the last op.
if (expr->outputs().size() != 0) {
return false;
}
// check cast
VARP cast_var = expr->inputs().at(0);
EXPRP cast_expr = cast_var->expr().first;
if (!cast_expr->get() || cast_expr->get()->type() != OpType_Cast) {
return false;
}
// check FloatToInt8
VARP quan_var = cast_expr->inputs().at(0);
EXPRP quan_expr = quan_var->expr().first;
if (!quan_expr->get() || quan_expr->get()->type() != OpType_FloatToInt8) {
return false;
}
// check X
VARP X_var = quan_expr->inputs().at(0);
EXPRP X_expr = X_var->expr().first;
if (!X_expr->get() || X_expr->get()->type() == OpType_FloatToInt8 || X_expr->get()->type() == OpType_Const || X_expr->get()->type() == OpType_Cast || X_expr->get()->type() == OpType_Int8ToFloat) {
return false;
}
if (X_expr->get()->type() == OpType_ConvInt8) {
return true;
}
if (X_expr->get()->type() == OpType_Reshape) {
auto convert_var = X_expr->inputs().at(0);
auto convert_expr = convert_var->expr().first;
if (convert_expr->get() && convert_expr->get()->type() == OpType_ConvertTensor) {
auto convint8_var = convert_expr->inputs().at(0);
auto convint8_expr = convint8_var->expr().first;
if (convint8_expr->get() && convint8_expr->get()->type() == OpType_ConvInt8) {
return true;
}
}
if (convert_expr->get() && convert_expr->get()->type() == OpType_ConvInt8) {
return true;
}
}
return true;
};
auto transformXToEnd = [](EXPRP expr) {
auto cast_var = expr->inputs()[0];
auto cast_expr = cast_var->expr().first;
auto quan_var = cast_expr->inputs().at(0);
auto quan_expr = quan_var->expr().first;
auto X_var = quan_expr->inputs().at(0);
auto X_expr = X_var->expr().first;
bool convInt8End = X_expr->get()->type() == OpType_ConvInt8;
if (convInt8End) {
auto convInt8Input = X_expr->inputs().at(0);
std::unique_ptr<Convolution2DT> newConvInt8(new MNN::Convolution2DT);
std::unique_ptr<OpT> oldConvOp(X_expr->get()->UnPack());
auto oldConvParams = oldConvOp->main.AsConvolution2D();
newConvInt8->common.reset(new MNN::Convolution2DCommonT);
newConvInt8->common = std::move(oldConvParams->common);
newConvInt8->symmetricQuan.reset(new QuantizedFloatParamT);
newConvInt8->symmetricQuan = std::move(oldConvParams->symmetricQuan);
newConvInt8->quanParameter.reset(new IDSTQuanT);
//newConvInt8->symmetricQuan->outputDataType = DataType_DT_FLOAT; // If convInt8 is the last op, float value is the torch-fx model's output.
newConvInt8->bias = std::move(oldConvParams->bias);
newConvInt8->quanParameter = std::move(oldConvParams->quanParameter);
float output_scale = newConvInt8->quanParameter->scaleOut;
float output_zero = newConvInt8->symmetricQuan->outputZeroPoint;
std::unique_ptr<OpT> conv_op(new OpT);
conv_op->name = X_expr->name();
conv_op->type = oldConvOp->type;
conv_op->main.type = OpParameter_Convolution2D;
conv_op->main.value = newConvInt8.release();
auto conv_expr = Expr::create(conv_op.get(), {convInt8Input});
auto conv_var = Variable::create(conv_expr);
conv_var->writeScaleMap(output_scale, output_zero);
if (X_expr->inputs().size() == 5) {
// Process matmul output
auto config = Global<modelConfig>::Get();
auto format = MNN::MNN_DATA_FORMAT_NCHW;
if (config->model == modelConfig::TFLITE || config->model == modelConfig::TENSORFLOW) {
format = MNN_DATA_FORMAT_NHWC;
}
conv_var->setName(X_expr->outputName(0));
// newconv_var->setName(conv_expr->outputName(0));
// expr->inputs = {input, concat, needSqueezeA, needSqueezeB, transposeA}
auto concat_var = X_expr->inputs().at(1);
bool needSqueezeA = X_expr->inputs().at(2)->readMap<float>()[0] > 0.f;
bool needSqueezeB = X_expr->inputs().at(3)->readMap<float>()[0] > 0.f;
auto output = _ConvertF(conv_var, format);
output->writeScaleMap(output_scale, output_zero);
VARP reshapeVar = _ReshapeF(output, concat_var, format);
reshapeVar->writeScaleMap(output_scale, output_zero);
if (needSqueezeA) {
reshapeVar = _Squeeze(reshapeVar, {0});
reshapeVar->writeScaleMap(output_scale, output_zero);
}
if (needSqueezeB) {
reshapeVar = _Squeeze(reshapeVar, {1});
reshapeVar->writeScaleMap(output_scale, output_zero);
}
reshapeVar->setName(expr->name());
Expr::replace(expr, reshapeVar->expr().first);
return true;
}
conv_expr->setName(expr->name());
Expr::replace(expr, conv_expr);
return true;
}
float output_scale = quan_expr->get()->main_as_QuantizedFloatParam()->tensorScale()->data()[0];
float output_zero = quan_expr->get()->main_as_QuantizedFloatParam()->floatzeros()->data()[0];
// directly return the op output.
std::unique_ptr<OpT> oldOtherOp(X_expr->get()->UnPack());
auto newop_expr = Expr::create(oldOtherOp.get(), X_expr->inputs());
newop_expr->setName(expr->name());
auto newop_var = Variable::create(newop_expr);
newop_var->writeScaleMap(output_scale, output_zero);
Expr::replace(expr, newop_expr);
return true;
};
TemplateMerge::getInstance("Merge").insertTemplate("ConvInt8ToConvInt8", matchConvInt8ToConvInt8, transformConvInt8ToConvInt8,
PASS_PRIORITY_MIDDLE);
TemplateMerge::getInstance("Merge").insertTemplate("OtherOpToConvInt8", matchOtherToConvInt8, transformOtherToConvInt8,
PASS_PRIORITY_MIDDLE);
TemplateMerge::getInstance("Merge").insertTemplate("XToOtherOp", matchXToOther, transformXToOther,
PASS_PRIORITY_MIDDLE);
TemplateMerge::getInstance("Merge").insertTemplate("XToEndOp", matchXToEnd, transformXToEnd,
PASS_PRIORITY_MIDDLE);
return true;
}();
}
} // namespace MNN