读之前建议先阅读ncnn参数数据的读取load_param,里面有部分内容是相同的
注:为了简单,所贴出来的代码已经将多余代码去掉了
主要涉及以下类:
datareader.cpp
datareader.h
modelbin.cpp
modelbin.h
net.cpp
net.h
模型加载的内容为二进制,datareader.cpp、datareader.h这部分的调用跟load_parma一样,实际上就是对FILE* _fp进行封装,只不过是封装的名字不一样
1、通过DataReaderFromStdio获取到文件的句柄,然后就可以对文件进行读取操作,这个句柄会传给load_model(对FILE* _fp进行封装,以便传入到Layer::load_model,DataReaderFromStdio::read 对数据进行读取)
2、读取时,执行了Layer::load_model(const ModelBin& /mb/),这个layer是所有网络层的父类
3、Layer::load_model(const ModelBin& /mb/)里面调用了Mat ModelBinFromDataReader::load(int w, int type)
4、实际上就是调用了DataReaderFromStdio::read(void* buf, size_t size) const
5、最终就是fread(buf, 1, size, d->fp),也就是读取的数据保存到buf,从d->fp去读
注:
1、上面的ModelBin为虚函数,其实现为ModelBinFromDataReader,所以传入给load_model的是ModelBinFromDataReader mb(dr)
2、由于通过DataReaderFromStdio读取的数据流需转换为ncnn::Mat,再传入给对应的layer,所以这里又把数据封了一层ModelBinFromDataReader,这个主要就是将数据流封装为ncnn::Mat
具体实现的代码如下:
定义了DataReader,没有做相应方法实现
DataReader::DataReader()
{
}
DataReader::~DataReader()
{
}
int DataReader::scan(const char* /*format*/, void* /*p*/) const
{
return 0;
}
size_t DataReader::read(void* /*buf*/, size_t /*size*/) const
{
return 0;
}
size_t DataReader::reference(size_t /*size*/, const void** /*buf*/) const
{
return 0;
}
定义了DataReaderFromStdioPrivate,对FILE* fp 进行封装,仅仅就是封了一层
class DataReaderFromStdioPrivate
{
public:
DataReaderFromStdioPrivate(FILE* _fp)
: fp(_fp)
{
}
FILE* fp;
};
定义了DataReaderFromStdio,继承了DataReader,并传入了DataReaderFromStdioPrivate(也就是传入了FILE* fp)
DataReaderFromStdio::DataReaderFromStdio(FILE* _fp)
: DataReader(), d(new DataReaderFromStdioPrivate(_fp))
{
}
DataReaderFromStdio::~DataReaderFromStdio()
{
delete d;
}
DataReaderFromStdio::DataReaderFromStdio(const DataReaderFromStdio&)
: d(0)
{
}
DataReaderFromStdio& DataReaderFromStdio::operator=(const DataReaderFromStdio&)
{
return *this;
}
int DataReaderFromStdio::scan(const char* format, void* p) const
{
return fscanf(d->fp, format, p);
}
size_t DataReaderFromStdio::read(void* buf, size_t size) const
{
return fread(buf, 1, size, d->fp);
}
最顶层,我们通过调用Net.load_mode(”yolov7.bin”)来调用传入模型,实际上就是调用了下面的方法,可将就是调用了FILE* fp,再将其传入到load_model(fp)
int Net::load_model(const char* modelpath)
{
FILE* fp = fopen(modelpath, "rb");
if (!fp)
{
printf("fopen %s failed", modelpath);
return -1;
}
int ret = load_model(fp);
fclose(fp);
return ret;
}
这里将FILE* fp 封装到DataReaderFromStdio,然后传入到load_model(dr),从上面我们可以知道DataReaderFromStdio就是定义了文件流的逐一获取,关闭流,删除流等方法。
int Net::load_model(FILE* fp)
{
DataReaderFromStdio dr(fp);
return load_model(dr);
}
由于DataReaderFromStdio是继承自DataReader,所以这里的传入参数写为const DataReader& dr
在这里就是逐一获取流并填入到对应的layer里面
layer保存在NetPrivate* const d 里面,最终是通过一个vector进行保存 std::vector<Layer*> layers;
int Net::load_param(const DataReader& dr){
...
d->layers[i] = layer;
...
}
load_model就是通过DataReaderFromStdio读取了.bin文件,逐一往NetPrivate里面的std::vector<Layer*> layers中的Layer去填参数,NetPrivate是用来管理网络层的,包括调用网络来进行前向推理,这里不重点展开,后面再细说。
int load_model(const DataReader& dr)
{
if (d->layers.empty()) // 创建的网络层layer都保存在d->layers,先判断vector是否为空
{
printf("network graph not ready"); // 为空时,没有网络
return -1;
}
int layer_count = (int)d->layers.size(); // 获取网络层一共有多少
// load file
int ret = 0;
ModelBinFromDataReader mb(dr); // 把DataReaderFromStdio 再封了一层,主要是为了将数据封装为ncnn::Mat
for (int i = 0; i < layer_count; i++) // 逐一遍历网络层
{
Layer* layer = d->layers[i]; // 获取一层网络层
//Here we found inconsistent content in the parameter file.
if (!layer) // 说明bin文件与Parma文件内容不一致
{
NCNN_LOGE("load_model error at layer %d, parameter file has inconsistent content.", i);
ret = -1;
break;
}
int lret = layer->load_model(mb);// layer去ModelBinFromDataReader里面拿数据
if (lret != 0)// 如果拿不到数据,说明网络有错
{
NCNN_LOGE("layer load_model %d %s failed", i, layer->name.c_str());
ret = -1;
break;
}
if (layer->support_int8_storage) // 如果模型是用int8保存的,目前int8不支持gpu,所以禁用了vulkan
{
// no int8 gpu support yet
opt.use_vulkan_compute = false;
}
}
// 再次遍历网络layer,这里就不是填数据,而是将opt的参数传入到对应的网络层的featmask里面,
// 然后通过layer->create_pipeline(opt)来使得opt对layer 生效
// 这里opt就是use_vulkan_compute,use_bf16_storage,use_fp16_packed,
// use_fp16_arithmetic等这些参数是true or false
for (int i = 0; i < layer_count; i++)
{
Layer* layer = d->layers[i];
Option opt1 = get_masked_option(opt, layer->featmask); // 对所传入的opt进行预处理
int cret = layer->create_pipeline(opt1);// 使得opt对网络layer生效
if (cret != 0)
{
NCNN_LOGE("layer create_pipeline %d %s failed", i, layer->name.c_str());
ret = -1;
break;
}
}
return ret;
}