ncnn源码阅读笔记(四)

2019-10-11  本文已影响0人  半笔闪

load_model

前几篇算是把网络结构参数的载入说完了,本篇开始网络权重参数载入load_model。
在net.cpp中有:

int Net::load_model(const char* modelpath)
{
    //打开xxx.bin文件
    FILE* fp = fopen(modelpath, "rb");
    if (!fp)
    {
        fprintf(stderr, "fopen %s failed\n", modelpath);
        return -1;
    }
    //调用int Net::load_model(FILE* fp)
    int ret = load_model(fp);
    fclose(fp);
    return ret;
}

可以看到最终还是调用了int Net::load_model(FILE* fp)函数,那就来看看这个函数:

int Net::load_model(FILE* fp)
{
     //如果layer为空
    if (layers.empty())
    {
        fprintf(stderr, "network graph not ready\n");
        return -1;
    }

    // load file
    int ret = 0;
    //从传入的参数中读取模型权重参数,也就是从xxx.bin中读取
    ModelBinFromStdio mb(fp);
    //这里回忆一下net.h中的成员属性layers,一个用来存储layer的vector
    for (size_t i=0; i<layers.size(); i++)
    {
        //遍历layers中的每一个layer
        Layer* layer = layers[i];
        
        //Here we found inconsistent content in the parameter file.
        //如果layer为空,说明没有构造或构造的和网络结构文件不一致
        if (!layer){
            fprintf(stderr, "load_model error at layer %d, parameter file has inconsistent content.\n", (int)i);
            ret = -1;
            break;
        }
        //载入模型权重
        int lret = layer->load_model(mb);
        //返回为0是成功载入
        if (lret != 0)
        {
            fprintf(stderr, "layer load_model %d failed\n", (int)i);
            ret = -1;
            break;
        }
        //根据设置的option参数创建流程管道,opt对象的类型是option类,在layer.h中可以找到声明,opt对象主要作用是配置一些设置(比如线程数,是否使用vulkan加速、是否使用gpu等等设置)
        int cret = layer->create_pipeline(opt);
        //返回0说明创建pipeline成功
        if (cret != 0)
        {
            fprintf(stderr, "layer create_pipeline %d failed\n", (int)i);
            ret = -1;
            break;
        }
    }
    //网络复用
    fuse_network();

    return ret;
}

这里有三个点:

ModelBinFromStdio mb(fp);                  //xxx.bin文件的解析
int lret = layer->load_model(mb);          //把解析出的内容载入到创建好的layer中
int cret = layer->create_pipeline(opt);   //根据opt的设置创建好执行管道

可以看到xxx.bin文件是一个二进制文件(打开是乱码的),ModelBinFromStdio类型的声明在modelbin.h中:

class ModelBinFromStdio : public ModelBin
{
public:
    // construct from file
    ModelBinFromStdio(FILE* binfp);

    virtual Mat load(int w, int type) const;

protected:
    FILE* binfp;
};

可以看到ModelBinFromStdio继承了ModelBin,而ModelBin的声明也在modelbin.h中:

class ModelBin
{
public:
    virtual ~ModelBin();
    // element type
    // 0 = auto
    // 1 = float32
    // 2 = float16
    // 3 = int8
    //一维的向量载入
    virtual Mat load(int w, int type) const = 0;
    //二维的图片载入
    virtual Mat load(int w, int h, int type) const;
    // 三维的dim载入
    virtual Mat load(int w, int h, int c, int type) const;
};

可以看到只有一个多态的load函数,类型是ncnn自己的Mat类型。
在modelbin.cpp中中:

ModelBinFromStdio::ModelBinFromStdio(FILE* _binfp) : binfp(_binfp)
{
}

ModelBinFromStdio::ModelBinFromStdio的初始化列表binfp,在.h文件中可以看到binfp是一个FILE*类型的对象。
在modelbin.cpp中load的实现就很简单了,根据多态的load传入不同的参数,声明一维、二维或三维的Mat,然后把从xxx.bin中解析出的数据放入Mat返回回去就行了。
至于layer->load_model(mb),我们指定layer其实是解析完模型网络结构的具体的layer,比如卷积Convolution,也就是这个调用其实调用的是layer的子类Convolution层的load_model()函数。所以来看下Convolution层的load_model()函数(在src/layer/convolution.cpp里)

int Convolution::load_model(const ModelBin& mb)
{
     //看到这里其实就是把ModelBinFromStdio mb的load函数里解析并返回的模型参数的Mat赋值给这里的weight_data 
    weight_data = mb.load(weight_data_size, 0);
    if (weight_data.empty())
        return -100;

    if (bias_term)
    {
        bias_data = mb.load(num_output, 1);
        if (bias_data.empty())
            return -100;
    }

    if (int8_scale_term)
    {
        weight_data_int8_scales = mb.load(num_output, 1);
        bottom_blob_int8_scale = mb.load(1, 1)[0];
    }

    return 0;
}

最后是int cret = layer->create_pipeline(opt),

int Layer::create_pipeline(const Option& /*opt*/)
{
    return 0;
}

同样的,举例卷积,create_pipeline也是由src/layer/convolution.cpp里重写的int Convolution::create_pipeline(const Option& opt)来执行。

上一篇下一篇

猜你喜欢

热点阅读