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

211 lines
10 KiB
C++

//
// SplitBlockQuantConvolution.cpp
// MNNConverter
//
// Created by MNN on 2019/09/05.
// Copyright © 2018, Alibaba Group Holding Limited
//
#include <fstream>
#include <MNN/MNNDefine.h>
#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<MNN::NetT>& net) const override {
auto& mNet = net;
auto config = Global<modelConfig>::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<Op>(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<float> 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<float> 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<std::unique_ptr<OpT>> 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; i<groupCount; ++i) {
subConvolutions[i].reset(rawOp->UnPack());
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<int8_t> subdata(blockSize * oc);
std::vector<float> subalpha(oc * divideNumber);
for (int y=0; y<oc; ++y) {
auto src = quanInfo->weight.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<float>(oc, 0);
}
RemoveAndStoreParam(subConvolutions[i], &dstWeight, currentDstOffset);
}
{
// Add slice
std::unique_ptr<OpT> 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; i<groupCount; ++i) {
subConvolutions[i]->inputIndexes[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<groupCount; ++i) {
std::unique_ptr<OpT> 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<int64_t>* 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; j<external->size(); ++j) {
sizeSum += external->data()[j];
}
originWeight.offset(external->data()[0]);
std::vector<char> data(sizeSum);
originWeight.read(data.data(), sizeSum);
dstWeight.write(data.data(), sizeSum);
external->data()[0] = currentDstOffset;
currentDstOffset += sizeSum;
iter++;
}
return true;
}
};
static PostConverterRegister<SplitBlockQuantConvolution> __l("SplitBlockQuantConvolution");