ncnn源码阅读笔记(四)
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)来执行。