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

233 lines
10 KiB
C++

//
// ConvBNReluFuseToConvInt8.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 "cli.hpp"
#include "../../common/CommonUtils.hpp"
#include <fstream>
namespace MNN {
namespace Express {
static auto gRegister = []() {
auto match = [](EXPRP expr) {
if (nullptr == expr->get()) {
return false;
}
if (expr->get()->type() != OpType_FloatToInt8) {
return false;
}
VARP convert1_var = expr->inputs().at(0);
EXPRP convert1_expr = convert1_var->expr().first;
if (!convert1_expr->get() || convert1_expr->get()->type() != OpType_ConvertTensor) {
return false;
}
VARP conv_var = convert1_expr->inputs().at(0);
EXPRP conv_expr = conv_var->expr().first;
if (!conv_expr->get() || (conv_expr->get()->type() != OpType_Convolution &&
conv_expr->get()->type() != OpType_ConvolutionDepthwise)) {
return false;
}
VARP convert2_var = conv_expr->inputs().at(0);
EXPRP convert2_expr = convert2_var->expr().first;
if (!convert2_expr->get() || convert2_expr->get()->type() != OpType_ConvertTensor) {
return false;
}
VARP quant_var = convert2_expr->inputs().at(0);
EXPRP quant_expr = quant_var->expr().first;
if (!quant_expr->get() || quant_expr->get()->type() != OpType_Int8ToFloat) {
return false;
}
return true;
};
auto transform = [](EXPRP expr) {
auto gConverterConfig = Global<modelConfig>::Get();
std::string compressFileName = gConverterConfig->compressionParamsFile;
auto& proto = gConverterConfig->compressInfo->proto;
auto convert1 = expr->inputs()[0];
auto convert1_expr = convert1->expr().first;
auto convOutput = convert1_expr->inputs()[0];
auto convExpr = convOutput->expr().first;
auto convert2 = convExpr->inputs()[0];
auto convert2_expr = convert2->expr().first;
auto int8ToFloatOutput = convert2_expr->inputs()[0];
auto int8ToFloatExpr = int8ToFloatOutput->expr().first;
auto convInt8Input = int8ToFloatExpr->inputs()[0];
std::unique_ptr<OpT> convOp(convExpr->get()->UnPack());
auto convParams = convOp->main.AsConvolution2D();
auto weightFloat = convParams->weight;
auto biasFloat = convParams->bias;
auto& common = convParams->common;
std::unique_ptr<OpT> int8ToFloatOp(int8ToFloatExpr->get()->UnPack());
float inputScale = int8ToFloatOp->main.AsQuantizedFloatParam()->tensorScale[0];
float inputZeroPoint = int8ToFloatOp->main.AsQuantizedFloatParam()->zeroPoint;
float inputClampMin = int8ToFloatOp->main.AsQuantizedFloatParam()->clampMin;
float inputClampMax = int8ToFloatOp->main.AsQuantizedFloatParam()->clampMax;
MNN::QuantizeAlgo method = int8ToFloatOp->main.AsQuantizedFloatParam()->method;
std::unique_ptr<OpT> floatToInt8Op(expr->get()->UnPack());
float outputScale = 1.f / floatToInt8Op->main.AsQuantizedFloatParam()->tensorScale[0];
float outputZeroPoint = floatToInt8Op->main.AsQuantizedFloatParam()->zeroPoint;
float outputClampMin = floatToInt8Op->main.AsQuantizedFloatParam()->clampMin;
float outputClampMax = floatToInt8Op->main.AsQuantizedFloatParam()->clampMax;
std::vector<int8_t> int8Weight;
std::vector<int32_t> int32Bias;
std::vector<float> scale;
const int ko = common->outputCount;
const int ki = common->inputCount / common->group;
const int kh = common->kernelY;
const int kw = common->kernelX;
VARP weightVar = _Const(weightFloat.data(), {ko, ki, kh, kw}, NCHW);
VARP biasVar = _Const(biasFloat.data(), {ko, 1, 1, 1}, NCHW);
VARP inputScaleVar = _Const(inputScale, {}, NCHW);
VARP outputScaleVar = _Const(outputScale, {}, NCHW);
int nbits = int8ToFloatOp->main.AsQuantizedFloatParam()->nbits;
float max_value = inputClampMax;
// Lower bitwidths (< 8bits) is only used by winograd-aware optimization.
// For winograd-aware, activation has two quantization bitwidths,
// - 7bits:
// Due to fewer accumulation times, 7 bits can be used for Conv1xN.
// In this case, the weight should be limited to 42 in order to
// prevent calculation overflow.
// - 6bits
// For general Conv3x3, only 6 bits can be satisfied, and the weight
// should be limited to 15 in order to prevent overflow.
if (nbits == 7) {
max_value = 42;
}
if (nbits == 6) {
max_value = 15;
}
VARP weightScale;
std::vector<float> weightScaleVector;
int wClampMin = -128, wClampMax = 127;
if (compressFileName != "") {
for (const auto& algo : proto.algo()) {
if (algo.type() == Compression::CompressionAlgo::QUANTIZE) {
auto quant_params = algo.quant_params();
for (const auto& layer_proto : quant_params.layer()) {
const std::string& tensor_name = layer_proto.output(0).name();
if (tensor_name == convExpr->outputName(0)) {
auto weightProto = layer_proto.weight(0);
for (int i = 0; i < weightProto.scales().size(); i++) {
weightScaleVector.emplace_back(weightProto.scales(i));
}
wClampMin = weightProto.clamp_min();
wClampMax = weightProto.clamp_max();
break;
}
}
}
}
weightScale = _Const(weightScaleVector.data(), {(int)weightScaleVector.size(), 1, 1, 1}, NCHW, halide_type_of<float>());
} else {
weightScale = _Maximum(_ReduceMax(_Abs(weightVar), {1, 2, 3}, true), _Scalar<float>(1e-6)) *
_Scalar<float>(1.f / max_value);
}
auto quanWeightTemp = _Round(weightVar * _Reciprocal(weightScale));
auto quanWeightClamp = _Maximum(_Minimum(quanWeightTemp, _Scalar<float>(wClampMax)), _Scalar<float>(wClampMin));
auto quanWeight = _Cast<int8_t>(quanWeightClamp);
auto convScale = _Reshape(_Reciprocal(outputScaleVar), {-1, 1, 1, 1}) * weightScale * inputScaleVar;
auto remains = _ReduceSum(_Scalar<int32_t>(inputZeroPoint) * _Cast<int32_t>(quanWeight), {1, 2, 3}, true);
auto outputZeroPointFused = _Cast<int32_t>(_Scalar<float>(outputZeroPoint) * _Reciprocal(convScale));
auto quanBias = _Cast<int32_t>(biasVar * _Reciprocal(weightScale * inputScaleVar)) - remains + outputZeroPointFused;
{
auto info = quanWeight->getInfo();
int8Weight.resize(info->size);
auto ptr = quanWeight->readMap<int8_t>();
::memcpy(int8Weight.data(), ptr, int8Weight.size() * sizeof(int8_t));
}
{
auto biasinfo = quanBias->getInfo();
int32Bias.resize(biasinfo->size);
auto ptr = quanBias->readMap<int32_t>();
::memcpy(int32Bias.data(), ptr, int32Bias.size() * sizeof(int32_t));
auto info = convScale->getInfo();
scale.resize(info->size);
MNN_ASSERT(scale.size() == int32Bias.size());
auto ptrScale = convScale->readMap<float>();
::memcpy(scale.data(), ptrScale, scale.size() * sizeof(float));
}
std::unique_ptr<Convolution2DT> conv(new MNN::Convolution2DT);
conv->common.reset(new MNN::Convolution2DCommonT);
auto* conv_common = conv->common.get();
conv_common->relu = common->relu || common->relu6;
conv_common->group = common->group;
conv_common->outputCount = common->outputCount;
conv_common->inputCount = common->inputCount;
conv_common->kernelX = kw;
conv_common->kernelY = kh;
conv_common->padX = common->padX;
conv_common->padY = common->padY;
conv_common->dilateX = common->dilateX;
conv_common->dilateY = common->dilateY;
conv_common->strideX = common->strideX;
conv_common->strideY = common->strideY;
conv_common->padMode = common->padMode;
MNN_ASSERT(int8Weight.size() == common->inputCount * (common->outputCount / common->group) * kw * kh);
conv->symmetricQuan.reset(new QuantizedFloatParamT);
bool is_depthwise = common->inputCount == common->outputCount && common->outputCount == common->group;
conv->symmetricQuan->bias = std::move(int32Bias);
conv->symmetricQuan->scale = std::move(scale);
conv->symmetricQuan->weight = std::move(int8Weight);
conv->symmetricQuan->nbits = nbits;
conv->symmetricQuan->zeroPoint = std::move(int8_t(inputZeroPoint));
conv->symmetricQuan->outputZeroPoint = std::move(int8_t(outputZeroPoint));
conv->symmetricQuan->clampMin = std::move(int8_t(outputClampMin));
conv->symmetricQuan->clampMax = std::move(int8_t(outputClampMax));
conv->symmetricQuan->method = method;
std::unique_ptr<OpT> conv_op(new OpT);
conv_op->name = expr->name();
conv_op->type = OpType_ConvInt8;
if (is_depthwise) {
conv_op->type = OpType_DepthwiseConvInt8;
}
conv_op->main.type = OpParameter_Convolution2D;
conv_op->main.value = conv.release();
auto conv_expr = Expr::create(conv_op.get(), {convInt8Input});
conv_expr->setName(convExpr->name());
auto conv_var = Variable::create(conv_expr);
conv_var->setName(convExpr->outputName(0));
Expr::replace(expr, conv_expr);
return true;
};
TemplateMerge::getInstance("Merge").insertTemplate("ConvBNReluFuseToConvInt8", match, transform,
PASS_PRIORITY_MIDDLE);
return true;
}();
}
} // namespace MNN