ncnn模型数据的读取load_model

2023-12-20 13:09:48

读之前建议先阅读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);
}

二、定义文件流读取后逐一填写到layer里面的类

最顶层,我们通过调用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;
}

文章来源:https://blog.csdn.net/qq_27149279/article/details/135103570
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。