// // SplitBlockQuantConvolution.cpp // MNNConverter // // Created by MNN on 2019/09/05. // Copyright © 2018, Alibaba Group Holding Limited // #include #include #include "../PostTreatUtils.hpp" #include "config.hpp" #include "../Global.hpp" #include "core/FileLoader.hpp" #include "core/ConvolutionCommon.hpp" #include "core/IDSTEncoder.hpp" #include "../../common/CommonUtils.hpp" using namespace MNN; class SplitBlockQuantConvolution : public PostConverter { public: virtual bool onExecute(std::unique_ptr& net) const override { auto& mNet = net; auto config = Global::Get(); FileLoader originWeight((config->modelFile + ".weight").c_str()); std::ofstream dstWeight((config->MNNModel + ".weight").c_str()); int64_t currentDstOffset = 0; for (auto iter = mNet->oplists.begin(); iter != mNet->oplists.end();) { auto op = iter->get(); if (nullptr == op) { iter++; continue; } bool split = false; do { if (op->main.type != OpParameter_Convolution2D || op->type != OpType_Convolution) { break; } auto conv2D = op->main.AsConvolution2D(); if (conv2D->quanParameter == nullptr || conv2D->quanParameter->type != 1) { break; } if (!conv2D->external.empty()) { op->externalPath = config->modelFile + ".weight"; } flatbuffers::FlatBufferBuilder builder; builder.Finish(Op::Pack(builder, op)); auto rawOp = flatbuffers::GetRoot(builder.GetBufferPointer()); auto quanInfo = ConvolutionCommon::load(rawOp, nullptr, false, true); op->externalPath.clear(); originWeight.offset(conv2D->external[0] + conv2D->external[1] + conv2D->external[2]); std::vector bias(conv2D->common->outputCount); originWeight.read((char*)bias.data(), conv2D->external[3]); // Async is 2 int divideNumber = quanInfo->asymmetric ? 2 : 1; auto alphaCount = quanInfo->alpha.size() / divideNumber; auto oc = conv2D->common->outputCount; auto groupCount = alphaCount / oc; if (groupCount <= 1) { break; } auto blockSize = conv2D->common->inputCount * conv2D->common->kernelX * conv2D->common->kernelY / groupCount; // For 4bit, revert to 8bit if (quanInfo->canUseInt4) { auto idxBufSize = quanInfo->weight.size(); auto blob = (int8_t*)MNNMemoryAllocAlign(idxBufSize * 2, 32); auto idxBuf = (unsigned char*)quanInfo->weight.get(); for (int i = 0; i < idxBufSize; i++) { int val = idxBuf[i]; int x1 = val / 16; int x2 = val % 16; blob[2 * i] = x1 - 8; blob[2 * i + 1] = x2 - 8; } quanInfo->weight.set(blob, idxBufSize * 2); quanInfo->canUseInt4 = false; } if (false) { std::vector subalpha(quanInfo->alpha.size()); ::memcpy(subalpha.data(), quanInfo->alpha.get(), quanInfo->alpha.size() * sizeof(float)); conv2D->quanParameter = IDSTEncoder::encode( nullptr, subalpha, blockSize * groupCount, oc, quanInfo->asymmetric, quanInfo->weight.get(), conv2D->quanParameter->aMin, {quanInfo->originBits, true, config->weightQuantScaleBit}); conv2D->external.clear(); conv2D->bias = bias; RemoveAndStoreParam(*iter, &dstWeight, currentDstOffset); split = true; iter++; break; } // Split Convolution std::vector> subConvolutions(groupCount); auto originOutputName = net->tensorName[op->outputIndexes[0]]; auto originOutputIndex = op->outputIndexes[0]; auto originInputIndex = op->inputIndexes[0]; auto originOpName = op->name; for (int i=0; iUnPack()); auto subOp = subConvolutions[i].get(); subOp->externalPath.clear(); subOp->main.AsConvolution2D()->external.clear(); subOp->name = op->name + "_" + std::to_string(i); subOp->outputIndexes[0] = (int)net->tensorName.size(); net->tensorName.emplace_back(originOutputName + "_" + std::to_string(i)); subOp->main.AsConvolution2D()->common->inputCount = conv2D->common->inputCount / groupCount; std::vector subdata(blockSize * oc); std::vector subalpha(oc * divideNumber); for (int y=0; yweight.get() + y * blockSize * groupCount + i * blockSize; auto dst = subdata.data() + blockSize * y; ::memcpy(dst, src, blockSize); ::memcpy(subalpha.data() + y * divideNumber, quanInfo->alpha.get() + y * divideNumber * groupCount + i * divideNumber, divideNumber * sizeof(float)); } subOp->main.AsConvolution2D()->quanParameter = IDSTEncoder::encode( nullptr, subalpha, blockSize, oc, quanInfo->asymmetric, subdata.data(), conv2D->quanParameter->aMin, {quanInfo->originBits, true, config->weightQuantScaleBit}); if (0 == i) { subOp->main.AsConvolution2D()->bias = bias; } else { subOp->main.AsConvolution2D()->bias = std::vector(oc, 0); } RemoveAndStoreParam(subConvolutions[i], &dstWeight, currentDstOffset); } { // Add slice std::unique_ptr slice(new OpT); slice->type = OpType_Slice; slice->name = op->name + "_inputslice"; slice->main.type = OpParameter_Slice; slice->main.value = new SliceT; slice->main.AsSlice()->axis = 1; slice->main.AsSlice()->sourceType = NetSource_TORCH; slice->inputIndexes = {originInputIndex}; for (int i=0; iinputIndexes[0] = (int)net->tensorName.size(); net->tensorName.emplace_back(net->tensorName[subConvolutions[i]->outputIndexes[0]] + "_input"); slice->outputIndexes.emplace_back(subConvolutions[i]->inputIndexes[0]); } iter = net->oplists.insert(iter, std::move(slice)); iter++; } *iter = std::move(subConvolutions[0]); auto lastIndex = iter->get()->outputIndexes[0]; for (int i=1; i add(new OpT); add->type = OpType_BinaryOp; add->main.type = OpParameter_BinaryOp; add->main.value = new BinaryOpT; add->main.AsBinaryOp()->opType = BinaryOpOperation_ADD; add->inputIndexes = {lastIndex, subConvolutions[i]->outputIndexes[0]}; if (i == groupCount - 1) { add->outputIndexes = {originOutputIndex}; add->name = originOpName; } else { add->name = net->tensorName[subConvolutions[i]->outputIndexes[0]] + "_add"; add->outputIndexes = {(int)net->tensorName.size()}; net->tensorName.emplace_back(net->tensorName[subConvolutions[i]->outputIndexes[0]] + "_add"); lastIndex = add->outputIndexes[0]; } iter = net->oplists.insert(iter + 1, std::move(subConvolutions[i])); iter = net->oplists.insert(iter + 1, std::move(add)); } iter++; split = true; } while (false); if (split) { continue; } // Copy External auto paramType = op->main.type; std::vector* external = nullptr; switch (paramType) { case MNN::OpParameter_Convolution2D: external = &op->main.AsConvolution2D()->external; break; case MNN::OpParameter_Scale: external = &op->main.AsScale()->external; break; case MNN::OpParameter_LayerNorm: external = &op->main.AsLayerNorm()->external; break; case MNN::OpParameter_Blob: external = &op->main.AsBlob()->external; break; default: break; } if (nullptr == external || external->empty()) { iter++; continue; } size_t sizeSum = 0; for (int j=1; jsize(); ++j) { sizeSum += external->data()[j]; } originWeight.offset(external->data()[0]); std::vector data(sizeSum); originWeight.read(data.data(), sizeSum); dstWeight.write(data.data(), sizeSum); external->data()[0] = currentDstOffset; currentDstOffset += sizeSum; iter++; } return true; } }; static PostConverterRegister __l("SplitBlockQuantConvolution");