这个地方应该是ncnn除了优化(int8量化和汇编代码优化)之外,最难理解的部分了。前面已经讲了如果将param文件中特殊参数解析成参数字典ParamDict,而在net中,layer会根据参数字典,这里如果我们直接看layer载入参数的代码,可能就很懵逼了:

// layer载入param
        int lr = layer->load_param(pd);

        转到layer层的load_param接口可以看到:

// 载入参数:参数列表
int Layer::load_param(const ParamDict& /*pd*/)
{
    return 0;
}

        这里并没有实现,在layer.h头文件中有:

// load layer specific parameter from parsed dict
    // return 0 if success
    virtual int load_param(const ParamDict& pd);

        load_param实际上是一个虚函数,熟悉C++的同学应该知道,调用虚函数时,实际调用的是继承类的版本,那么到底如何调用的?,我们可以往回看,有这样一段代码:

// 创建layer
        Layer* layer = create_layer(layer_type);
        // layer_type不是默认类型
        if (!layer)
        {   
            // 从自定义layer读取
            layer = create_custom_layer(layer_type);
        }
        // 如果自定义layer中也不存在当前类型layer
        if (!layer)
        {
            fprintf(stderr, "layer %s not exists or registered\n", layer_type);
            clear();
            return -1;
        }

        回到layer.cpp文件中,可以看到,代码中先找到当前层layer类型对应层注册器中类型的索引index。

// 将string对应layer类型转换成对应index
int layer_to_index(const char* type)
{
    for (int i=0; i<layer_registry_entry_count; i++)
    {
        if (strcmp(type, layer_registry[i].name) == 0)
            return i;
    }

    return -1;
}

// 根据字符串layer类型创建layer
Layer* create_layer(const char* type)
{
    int index = layer_to_index(type);
    if (index == -1)
        return 0;

    return create_layer(index);
}

        然后,根据index创建layer:

// 创建layer
Layer* create_layer(int index)
{
    // index不能超过索引范围
    if (index < 0 || index >= layer_registry_entry_count)
        return 0;

    // 创建layer构造器
    layer_creator_func layer_creator = layer_registry[index].creator;
    // layer构造器创建失败
    if (!layer_creator)
        return 0;

    // 构造layer
    Layer* layer = layer_creator();
    // 设置layer的类型index
    layer->typeindex = index;
    return layer;
}

            这里有个layer_registry,定义为:

static const layer_registry_entry layer_registry[] =
{
#include "layer_registry.h"
};

        而这个"layer_registry.h"文件是在build项目的时候自动产生的,部分内容如下:

// Layer Registry header
//
// This file is auto-generated by cmake, don't edit it.

#if NCNN_STRING
{"AbsVal",AbsVal_final_layer_creator},
#else
{AbsVal_final_layer_creator},
#endif
#if NCNN_STRING
{"ArgMax",0},
#else
{0},
#endif
#if NCNN_STRING
{"BatchNorm",BatchNorm_final_layer_creator},
#else
{BatchNorm_final_layer_creator},
#endif
#if NCNN_STRING
{"Bias",Bias_final_layer_creator},
#else
{Bias_final_layer_creator},
#endif

        而layer_registry_entry的结构为:

// layer factory function
typedef Layer* (*layer_creator_func)();

struct layer_registry_entry
{
#if NCNN_STRING
    // layer type name
    const char* name;
#endif // NCNN_STRING
    // layer factory entry
    layer_creator_func creator;
};

        我们代入一组参数进去就是:

name = "AbsVal";
layer_creator_func = AbsVal_final_layer_creator;

        这里layer_creator_func定义为:

typedef Layer* (*layer_creator_func)();

        那么,layer_creator_func AbsVal_final_layer_creator转换过去就是: 

Layer* AbsVal_final_layer_creator()

        在layer.h文件最下面还有一个定义:

// ##字符串连接
#define DEFINE_LAYER_CREATOR(name) \
    ::ncnn::Layer* name##_layer_creator() { return new name; }

        我们在absval.cpp文件中可以看到 DEFINE_LAYER_CREATOR(AbsVal),相当于就是声明了一个函数:

// #define DEFINE_LAYER_CREATOR(name) \
//    ::ncnn::Layer* name##_layer_creator() { return new name; }
// 由上面这段代码可知,DEFINE_LAYER_CREATOR(AbsVal)等价于:
::ncnn::Layer* AbsVal_layer_creator() { return new AbsVal; }

        上面那句话相当于就是new了一个AbsVal层,但是这里还是对应不起来,上面的是 AbsVal_final_layer_creator(),这里声明的是AbsVal_layer_creator(),这里就涉及到ncnn还有一层继承,使用cmake编译ncnn项目后,除了生成了layer_registry.h文件之外,还生成了一个layer_declaration.h文件,打开这个文件,一切就清楚了:

// Layer Declaration header
//
// This file is auto-generated by cmake, don't edit it.

#include "layer/absval.h"
namespace ncnn {
class AbsVal_final : virtual public AbsVal
{
public:
    virtual int create_pipeline(const Option& opt) {
        { int ret = AbsVal::create_pipeline(opt); if (ret) return ret; }
        return 0;
    }
    virtual int destroy_pipeline(const Option& opt) {
        { int ret = AbsVal::destroy_pipeline(opt); if (ret) return ret; }
        return 0;
    }
};
DEFINE_LAYER_CREATOR(AbsVal_final)
} // namespace ncnn

#include "layer/batchnorm.h"
namespace ncnn {
class BatchNorm_final : virtual public BatchNorm
{
public:
    virtual int create_pipeline(const Option& opt) {
        { int ret = BatchNorm::create_pipeline(opt); if (ret) return ret; }
        return 0;
    }
    virtual int destroy_pipeline(const Option& opt) {
        { int ret = BatchNorm::destroy_pipeline(opt); if (ret) return ret; }
        return 0;
    }
};
DEFINE_LAYER_CREATOR(BatchNorm_final)
} // namespace ncnn

         AbsVal_final层继承了AbsVal层,如果当前操作系统不是linux系统,就会将create_pipeline()和destroy_pipeline()抽象出来,具体调用时,就调用对应优化了的代码。那么layer载入ParamDict具体实现就对应于各个layer的载入流程了。

       这篇博客就写到这里吧,具体载入过程,等后面解析operation的时候,后面继续。