// // FullQuantAndCoding.cpp // MNNConverter // // Created by MNN on 2021/08/11. // Copyright © 2018, Alibaba Group Holding Limited // #include #include "CommonUtils.hpp" #include "MNN/expr/ExprCreator.hpp" #include "core/IDSTEncoder.hpp" using namespace MNN; using namespace MNN::Express; void FullQuantAndCoding(std::unique_ptr& netT, std::unique_ptr& op, Compression::Pipeline& proto, SubGraphProtoT* subgraph) { std::string outputTensorName = subgraph ? subgraph->tensors[op->outputIndexes[0]] : netT->tensorName[op->outputIndexes[0]];; auto opType = op->type; if (opType != MNN::OpType_Convolution && opType != MNN::OpType_ConvolutionDepthwise) { return; } if (op->inputIndexes.size() != 1) { return; } auto findQuantParameters = [&](Compression::Pipeline& proto, std::string outputTensorName) { for (const auto& algo : proto.algo()) { if (algo.type() == Compression::CompressionAlgo::QUANTIZE) { auto quantParams = algo.quant_params(); for (const auto& layerProto : quantParams.layer()) { if (layerProto.output_size() <= 0) { continue; } const std::string& outputName = layerProto.output(0).name(); if ((outputName == outputTensorName) || (outputTensorName == outputName+"__matmul_converted")) { return layerProto; } } } } MNN::Compression::LayerQuantizeParams empty; return empty; }; auto inputIndex = op->inputIndexes[0]; int outputIndex = op->outputIndexes[0]; auto quantParams = findQuantParameters(proto, outputTensorName); if (quantParams.weight_size() == 0) { return; } auto inputParams = quantParams.input(0); auto outputParams = quantParams.output(0); auto weightParams = quantParams.weight(0); auto& tensorDescribe = subgraph ? subgraph->extraTensorDescribe : netT->extraTensorDescribe; auto findInDescribe = [&] (int index) { for (int i = 0; i < tensorDescribe.size(); i++) { if (tensorDescribe[i]->index == index) { return true; } } return false; }; if (!findInDescribe(inputIndex)) { std::unique_ptr inDescribe(new MNN::TensorDescribeT); inDescribe->index = inputIndex; std::unique_ptr inputQuantInfo(new MNN::TensorQuantInfoT); inputQuantInfo->zero = inputParams.zero_point(); inputQuantInfo->scale = inputParams.scales(0); inputQuantInfo->min = inputParams.clamp_min(); inputQuantInfo->max = inputParams.clamp_max(); inputQuantInfo->type = MNN::DataType_DT_INT8; inDescribe->quantInfo = std::move(inputQuantInfo); tensorDescribe.emplace_back(std::move(inDescribe)); } if (!findInDescribe(outputIndex)) { std::unique_ptr outDescribe(new MNN::TensorDescribeT); outDescribe->index = outputIndex; std::unique_ptr outputQuantInfo(new MNN::TensorQuantInfoT); outputQuantInfo->zero = outputParams.zero_point(); outputQuantInfo->scale = outputParams.scales(0); outputQuantInfo->min = outputParams.clamp_min(); outputQuantInfo->max = outputParams.clamp_max(); outputQuantInfo->type = MNN::DataType_DT_INT8; outDescribe->quantInfo = std::move(outputQuantInfo); tensorDescribe.emplace_back(std::move(outDescribe)); } auto convParams = op->main.AsConvolution2D(); auto weightFloat = convParams->weight; auto biasFloat = convParams->bias; auto& common = convParams->common; const int ko = common->outputCount; const int ki = common->inputCount / common->group; const int kh = common->kernelY; const int kw = common->kernelX; const int kernelNum = common->outputCount; int kernelSize = weightFloat.size() / kernelNum; VARP weightVar = _Const(weightFloat.data(), {ko, ki, kh, kw}, NCHW); VARP biasVar = _Const(biasFloat.data(), {ko, 1, 1, 1}, NCHW); VARP inputScaleVar = _Const(inputParams.scales(0), {}, NCHW); VARP outputScaleVar = _Const(outputParams.scales(0), {}, NCHW); float wClampMin = weightParams.clamp_min(); float wClampMax = weightParams.clamp_max(); std::vector weightScaleVector(weightParams.scales().begin(), weightParams.scales().end()); VARP weightScale = _Const(weightScaleVector.data(), {(int)weightScaleVector.size(), 1, 1, 1}, NCHW, halide_type_of()); auto quanWeightTemp = _Round(weightVar * _Reciprocal(weightScale)); auto quanWeightClamp = MNN::Express::_Maximum(_Minimum(quanWeightTemp, _Scalar(wClampMax)), _Scalar(wClampMin)); auto quanWeight = _Cast(quanWeightClamp); auto convScale = _Reshape(_Reciprocal(outputScaleVar), {-1, 1, 1, 1}) * weightScale * inputScaleVar; std::vector quantWeights; std::vector biasData; std::vector scale; { auto info = quanWeight->getInfo(); quantWeights.resize(info->size); auto ptr = quanWeight->readMap(); for (int i = 0; i < quantWeights.size(); i++) { quantWeights[i] = ptr[i]; } } { auto biasinfo = biasVar->getInfo(); biasData.resize(biasinfo->size); auto ptr = biasVar->readMap(); ::memcpy(biasData.data(), ptr, biasData.size() * sizeof(int32_t)); auto info = weightScale->getInfo(); scale.resize(info->size); MNN_ASSERT(scale.size() == biasData.size()); auto ptrScale = weightScale->readMap(); ::memcpy(scale.data(), ptrScale, scale.size() * sizeof(float)); } bool asymmetricQuantFlag = false; std::vector fakeScales(kernelNum, 1.0f); convParams->quanParameter = IDSTEncoder::encode(nullptr, fakeScales, kernelSize, kernelNum, asymmetricQuantFlag, quantWeights.data(), wClampMin); convParams->weight.clear(); convParams->quanParameter->alpha = std::move(scale); convParams->quanParameter->scaleIn = inputParams.scales(0); convParams->quanParameter->scaleOut = outputParams.scales(0); convParams->symmetricQuan.reset(new MNN::QuantizedFloatParamT); convParams->symmetricQuan->method = MNN::QuantizeAlgo(int(quantParams.method())); convParams->symmetricQuan->nbits = outputParams.bits(); convParams->symmetricQuan->zeroPoint = inputParams.zero_point(); convParams->symmetricQuan->outputZeroPoint = outputParams.zero_point(); convParams->symmetricQuan->clampMin = outputParams.clamp_min(); convParams->symmetricQuan->clampMax = outputParams.clamp_max(); convParams->bias = std::move(biasData); // winogradAttr store: // 1. transformed weight and input scale // 2. winograd config (F(2,3)/F(4,3)/F(6,3)/...) if (quantParams.method() == MNN::Compression::LayerQuantizeParams::WinogradAware) { const auto& attr = quantParams.wino_params().units_attr(); convParams->symmetricQuan->winogradAttr.assign(attr.begin(), attr.end()); } }; void fullQuantAndCoding(std::unique_ptr& netT, MNN::Compression::Pipeline proto) { for (auto& op : netT->oplists) { FullQuantAndCoding(netT, op, proto, nullptr); } for (auto& subgraph : netT->subgraphs) { for (auto& op : subgraph->nodes) { FullQuantAndCoding(netT, op, proto, subgraph.get()); } } }