这个地方应该是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的时候,后面继续。