MNN源码阅读之模型转换

模型转换

每一个开源框架,都有自己的模型格式,MNN中支持,CAFFE,TensorFLow,ONNX等格式的网络结构转换成mnn格式。

为了方便大多都会将训练好的网络模型转成ONNX第三方通用的结构,这里主要分析mnn如何将ONNX的结构转换成自己支持的mnn格式。
模型转换的流程:

Created with Raphaël 2.2.0 开始 读取模型 转换成mnn模型 mnn模型写入文件 结束
0. onnx结构

在此之前,需要先了解下onnx的结构。
onnx最外层是model,包含一些基础信息,onnx版本,来源框架/工具,来源工具版本等信息,当然还有最重要的计算图Graph(网络图结构)。
Model成员表

成员名称 解释
ir_version onnx版本
opset_import 模型的操作算计集合。必须支持集合中的所有算子,否则模型无法加载。
producer_name 模型来源框架或者工具,pytorch等
producer_version 来源工具版本
domain 表示模型名称空间或域的反向DNS名称,例如“org.onnx”
doc_string 此模型的可读文档
graph 模型计算图以及权重参数
metadata_props metadata和名称的映射表
training_info 包含训练的一些信息

Graph成员表

成员名称 解释
name 模型计算图名称
node 计算图中的节点列表,基于输入/输出数据依赖性形成一个部分有序的计算图。它是拓扑顺序的。
initializer 一个tensor的列表。当与计算图中输入具有相同名称时,它将为该输入指定默认值。反之,它将指定一个常量值。
doc_string 此模型的可读文档
input 计算图中所有节点的输入
output 计算图所有节点的输出
value_info 用于存储非输入或输出值的类型和shape

Node成员表

成员名称 解释
name 节点名称
input 节点输入,计算图输入,或者initializer或者其他节点的输出
output 节点的输出
op_type 算子操作类型
domain 算子的操作域,
attribute 算子的一些信息,或者不会用于传播的常量
doc_string 可读的文档信息

Attribute的成员表

成员名称 解释
name 属性名称
doc_string 可读的文档信息
type 属性的类型,确定剩余字段中用于保存属性值的字段。
f 32位的浮点值
i 64位整数
s UTF-8字符串
t 一个tensor
g 一个计算图
floats 浮点数组
ints 整型数组
strings 字符串数组
tensors tensor数组
graphs 计算图数组
1. 模型转换

先看模型转换的主要流程。
在这里插入图片描述

int onnx2MNNNet(const std::string inputModel, const std::string bizCode,
                const common::Options& options, std::unique_ptr<MNN::NetT>& netT) {
   
   
    onnx::ModelProto onnxModel;
    // 读取onnx模型
    bool success = onnx_read_proto_from_binary(inputModel.c_str(), &onnxModel);
    DCHECK(success) << "read onnx model failed: " << inputModel;

    LOG(INFO) << "ONNX Model ir version: " << onnxModel.ir_version();

    const auto& onnxGraph = onnxModel.graph();
    const int nodeCount   = onnxGraph.node_size();

    std::shared_ptr<OnnxTmpGraph> onnxTempGraph(new OnnxTmpGraph(&onnxGraph));

    // op_name: name
    // get mnn op pointer conveniently, then manipulate the mnn op
    std::map<std::string, MNN::OpT*> mnnNodesMap;
    // all tensors container
    std::map<std::string, int> tensorsName;
    // find the inputs which do not have initializer
    // initializers是一个list,即是一个权重的tensor列表,并且每个元素都有明确的名字,和输出列表中的名字对应
    const auto& initializers         = onnxTempGraph->mInitializers;    
    // 模型中所有的输入和输出,包括最开始输入的图像以及每个结点的输入输出信息
    const auto& inputs               = onnxTempGraph->mInputs;
    const auto& outputs              = onnxTempGraph->mOutputs;
    const auto& constantNodeToDelete = onnxTempGraph->mConstantNodeToDelete;
    for (const auto& iter : inputs) {
   
   
        bool notHaveInitializer = initializers.find(iter.first) == initializers.end();
        // 找到不在initializers列表中的输入,从下面的代码可以看出,不在initializers中的是输入节点。
        if (notHaveInitializer) {
   
   
            netT->tensorName.push_back(iter.first);
            tensorsName.insert(std::make_pair(iter.first, tensorsName.size()));
        }
    }

    // 把没有initializers的输入节点添加到net中
    for (const auto& iter : tensorsName) {
   
   
        // here tensorsName are true Input node name
        MNN::OpT* MNNOp  = new MNN::OpT;
        MNNOp->name      = iter.first;
        MNNOp->type      = MNN::OpType_Input;
        MNNOp->main.type = MNN::OpParameter_Input;
        auto inputParam  = new MNN::InputT;
        const auto it    = inputs.find(iter.first);
        DCHECK(it != inputs.end()) << "Input Paramter ERROR ==> " << iter.first;
        const auto& tensorInfo = (it->second)->type().tensor_type();
        const int inputDimSize = tensorInfo.shape().dim_size();
        inputParam->dims.resize(inputDimSize);
        for (int i = 0; i < inputDimSize; ++i) {
   
   
            inputParam->dims[i] = tensorInfo.shape().dim(i).dim_value();
        }
        inputParam->dtype   = onnxOpConverter::convertDataType(tensorInfo.elem_type());     // onnx数据类型转换成mnn的数据类型
        inputParam->dformat = MNN::MNN_DATA_FORMAT_NCHW;                                    // 数据格式为NCHW
        MNNOp->outputIndexes.push_back(tensorsName[iter.first]);
        MNNOp->main.value = inputParam;
        mnnNodesMap.insert(std::make_pair(iter.first, MNNOp));
        netT->oplists.emplace_back(MNNOp);
    }

    // onnx的节点导入到mnn的节点中
    for (int i = 0; i < nodeCount; ++i) {
   
   
        const auto& onnxNode = onnxGraph.node(i);
        const auto& opType   = onnxNode.op_type();

        // name maybe null, use the first output name as node-name
        const auto& name = onnxNode.output(0);

        // TODO not to use constantNodeToDelete anymore
        if (constantNodeToDelete.find(name) != constantNodeToDelete.end()) {
   
   
            continue;
        }
        // 找到对应op类型的转换器
        auto opConverter = onnxOpConverterSuit::get()->search(opType);

        MNN::OpT* MNNOp  = new MNN::OpT;
        MNNOp->name      = name;
        MNNOp->type      = opConverter->opType();
        MNNOp->main.type = opConverter->type();
        mnnNodesMap.insert(std::make_pair(name, MNNOp));

        // convert initializer to be Constant node(op) 将权重转换为常量节点
        for (int k = 0; k < onnxNode.input_size(); ++k) {
   
   
            const auto& inputName = onnxNode.input(k);
            const auto it         = initializers.find(inputName);
            if (it != initializers.end() && tensorsName.find(it->first) == tensorsName.end()) {
   
   
                // Create const Op
                MNN::OpT* constOp   = new MNN::OpT;
                constOp->type       = MNN::OpType_Const;
                constOp->main.type  = MNN::OpParameter_Blob;
                constOp->main.value = onnxOpConverter::convertTensorToBlob(it->second);         // onnx的tensor转换为mnn的tensor
                mnnNodesMap.insert(std::make_pair(inputName, constOp));
                auto outputIndex = (int)netT->tensorName.size();
                constOp->name    = it->first;
                constOp->outputIndexes.push_back(outputIndex);
                tensorsName.insert(std::make_pair(it->first, outputIndex));
                netT->tensorName.emplace_back(constOp->name);
                netT->oplists.emplace_back(constOp);
            }
        }

        // TODO, delete the run() args opInitializers   删除一些不在opInitializers中的节点。
        std::vector<const onnx::TensorProto*> opInitializers;
        for (int k = 0; k < onnxNode.input_size(); ++k) {
   
   
            const auto& inputName = onnxNode.input(k);
            const auto it         = initializers.find(inputName);
            if (it != initializers.end()) {
   
   
                opInitializers.push_back(it->second);
            }
        }
        // 执行算子转换
        opConverter->run(MNNOp, &onnxNode, opInitializers);

        netT->oplists.emplace_back(MNNOp);

        const int outputTensorSize = onnxNode.output_size();
        for (int ot = 0; ot < outputTensorSize; ++ot) {
   
   
            netT->tensorName.push_back(onnxNode.output(ot));
            tensorsName.insert(std::make_pair(onnxNode.output(ot), tensorsName.size()))
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值