参考:https://github.com/Tencent/ncnn/tree/master/benchmark
①下载安装包:
git clone https://github.com/Tencent/ncnn #ncnn下载
cd ncnn #进入ncnn文件夹
②修改CMakeLists.txt 89-91行,使其生成自己需要的网络结构的可执行文件,例如:
89 add_subdirectory(examples)
90 add_subdirectory(benchmark) #去掉注释符#,从而编译生成benchmark的可执行文件
91 add_subdirectory(src)
③修改ncnn/benchmark/benchncnn.cpp,可以修改的地方有两个,以alexnet为例,一个是初始化路径,一个是主函数。初始化路径在第231-235行,ncnn里提供了几个函数模型的.param文件,在ncnn/benchmark/文件夹下,如果不修改下面路径,则默认指向该文件夹下文件,也可以修改为自己的模型所在路径。
void alexnet_init(ncnn::Net& net)
{
net.load_param("/home/firefly/models/alexnet/alexnet.param");
}
主函数的376行,需要去掉注释:
benchmark("alexnet", alexnet_init, alexnet_run); //去掉该处注释,上下其他的不需要的网络都注释掉
修改完毕就可以保存benchncnn.cpp文件了。
④下一步,生成可执行文件:
mkdir build && cd build #创建新文件夹build用于编译
cmake ..&& make -j #编译
至此,默认的可执行文件benchncnn生成,文件在ncnn/build/benchmark/benchncnn下(其他模型可执行文件在ncnn/build/examples/下)。
①进入ncnn主文件夹,直接运行可执行文件benchncnn。执行方式:./build/benchncnn [loop count] [num threads] [powersave],参数说明见上面benchmark官网链接。例如:
./build/benchncnn 8 2 0 #8表示测试8次取平均,2表示使用2个线程,0表示powersave
运行结果举例:
firefly@firefly:~/ncnn/build/benchmark$ ./benchncnn 8 1 0
loop_count = 8
num_threads = 1
powersave = 0
alexnet min = 379.68 max = 380.86 avg = 380.23
mobilenet min = 115.01 max = 118.82 avg = 116.17
mobilenet_v2 min = 138.04 max = 139.55 avg = 138.72
googlenet min = 880.23 max = 886.85 avg = 883.52
resnet18 min = 911.57 max = 915.61 avg = 913.12
vgg16 min = 3103.47 max = 3140.08 avg = 3127.01
inceptionv3 min = 10261.93 max = 10297.99 avg = 10278.18
resnet50 min = 1857.96 max = 1885.38 avg = 1875.74
注意:前向时间测试的运行方式和模型效果测试方式不一样。例如ncnn/build/examples文件夹下可执行文件的运行方式是:
./build/examples/squeeze 0.jpg #这里的参数是图片
(int8 计算 aarch64 还没有优化,所以很慢)
对于测试前向运行时间,需要提供的是设置参数为int8的.param文件,具体怎么得到这个int8的.param文件,这里提供两种方式。
1)caffe转成int 8 类型的.param文件
参考:https://github.com/Tencent/ncnn/wiki/quantized-int8-inference
①下载caffe to ncnn(int8) 小工具
git clone https://github.com/BUG1989/caffe-int8-convert-tools.git
②生成.table转换文件,这里参数比较多,需要指定.prototxt、.caffemodel以及.table 的名字(自己随意设定),–mean这个参数我是随意设置的,因为测试benchmark里前向运行时间时,–images这个参数我是随便建了一个images文件夹,然后里面放了2张.jpg。ncnn是只需要网络结构,数据是随机初始化的,具体见mat.h里面的各种构造函数。
cd /home/firefly/models/alexnet/
python /home/firefly/caffe-int8-convert-tools/caffe-int8-convert-tool.py --proto=Alexnet_deploy_New.prototxt --model=Alexnet_new.caffemodel --mean 104 117 123 --output=Alexnet.table --images=/home/firefly/images
③生成int8 类型的.param文件
cd /home/firefly/ncnn/build/tools/caffe/
./caffe2ncnn Alexnet.prototxt Alexnet.caffemodel Alexnet-int8.param Alexnet-int8.bin 256 Alexnet.table
运行结果有两个文件生成,Alexnet-int8.param和Alexnet-int8.bin。对于时间测试,用.param就行了。
⑤修改benchncnn.cpp文件
修改ncnn/benchmark/benchncnn.cpp中的alexnet初始化的路径部分,改为自己生成的Alexnet-int8.param路径:
void alexnet_init(ncnn::Net& net)
{
net.load_param("/home/firefly/models/alexnet/Alexnet-int8.param");
}
修改完毕,保存。
⑥编译生成可执行文件
cd build
make -j2
注意:这里不用再重新cmake了。
⑦前向时间测试
./build/benchncnn 8 2 0 #8表示测试8次取平均,2表示使用2个线程,0表示powersave
2)直接修改原float32的.param文件生成int8 .param
对比float32的.param与int8的.param文件:
float32:
7767517
24 24
Input data 0 1 data 0=227 1=227 2=3
Convolution conv1 1 1 data conv1 0=96 1=11 2=1 3=4 4=0 5=1 6=34848
ReLU relu1 1 1 conv1 conv1_relu1
LRN norm1 1 1 conv1_relu1 norm1 0=0 1=5 2=0.000100 3=0.750000
Pooling pool1 1 1 norm1 pool1 0=0 1=3 2=2 3=0 4=0
ConvolutionDepthWise conv2 1 1 pool1 conv2 0=256 1=5 2=1 3=1 4=2 5=1 6=307200 7=2
int8:
7767517
24 24
Input data 0 1 data 0=227 1=227 2=3
Convolution conv1 1 1 data conv1 0=96 1=11 2=1 3=4 4=0 5=1 6=34848 8=1
ReLU relu1 1 1 conv1 conv1_relu1
LRN norm1 1 1 conv1_relu1 norm1 0=0 1=5 2=0.000100 3=0.750000
Pooling pool1 1 1 norm1 pool1 0=0 1=3 2=2 3=0 4=0
ConvolutionDepthWise conv2 1 1 pool1 conv2 0=256 1=5 2=1 3=1 4=2 5=1 6=307200 7=2 8=2
发现除了卷积层,其他的部分参数完全一样。所有的Convolution层最后参数添加了"8=1",ConvolutionDepthWise层添加了“8=2”,所以直接在原float32的所有Conv层后加上8=1或8=2就行了。
ncnn::Mat in(224, 224, 3);
观察Mat的构造函数有:
Mat();
// vec
Mat(int w, size_t elemsize = 4u, Allocator* allocator = 0);
// image
Mat(int w, int h, size_t elemsize = 4u, Allocator* allocator = 0);
// dim
Mat(int w, int h, int c, size_t elemsize = 4u, Allocator* allocator = 0);
// copy
Mat(const Mat& m);
// external vec
Mat(int w, void* data, size_t elemsize = 4u, Allocator* allocator = 0);
// external image
Mat(int w, int h, void* data, size_t elemsize = 4u, Allocator* allocator = 0);
// external dim
Mat(int w, int h, int c, void* data, size_t elemsize = 4u, Allocator* allocator = 0);
// release
~Mat();
// assign
Mat& operator=(const Mat& m);
所以上述Mat in(224, 224, 3)调用的是第三个构造函数,默认的元素大小是4u,也就是32位,想着只需要将这里的第四个参数4u改成1u,就可以初始化测试输入为int8类型的。于是修改benchncnn.cpp为:
ncnn::Mat in(224, 224, 3,1u);
然后保存,make -j2,再测试时间,结果是:
长时间不报错也不出结果,或者报错:
FATAL ERROR! unlocked pool allocator get wild 0x55605e8850
Mat(int w, int h, int c, size_t elemsize = 1u, Allocator* allocator = 0);
报错:
Segmentation fault (core dumped)
或者
quantized int8 weight loaded but use_int8_inference disabled
/caffe/build/tools/upgrade_net_proto_text mobilenet_v2_deploy_prototxt mobilenet_v2_deploy_New.prototxt #生成第一层为input的新网络
转换.table文件时就使用新的.prototxt文件就好了。
例如:
layer {
name: "conv1"
type: "Convolution"
bottom: "data"
top: "conv1"
param {
lr_mult: 1
}
param {
lr_mult: 2
}
convolution_param {
num_output: 20
kernel_size: 5
stride: 1
weight_filler {
type: "xavier"
}
bias_filler {
type: "constant"
}
}
}
相应的ncnn文件部分为:
Convolution conv1 1 1 data conv1 0=20 1=5 2=1 3=1 4=0 5=1 6=500
层类型:Convolution
层名称:conv1
输入数据结构数量:1
输出数据结构数量(top blob):1
网络输入层名:data
网络输出层名:conv1
特殊参数1:0=20,num_output: 20
特殊参数2:1=5,kernel_size: 5
特殊参数3:2=1,dilation=1
特殊参数4:3=1,stride: 1
特殊参数5:4=0,pad=0
特殊参数6:5=1,bias
特殊参数7:6=500,该层的参数量,551*20=500
特殊参数8:8=1表示Convolution层为int8输入,若8=2表示ConvolutionDepthWise层为int8输入。不写默认float32。其他参数见下面源码/home/firefly/ncnn/tools/caffe/caffe2ncnn.cpp:
else if (layer.type() == "Convolution" || layer.type() == "ConvolutionDepthwise" || layer.type() == "DepthwiseConvolution")
{
const caffe::LayerParameter& binlayer = net.layer(netidx);
const caffe::BlobProto& weight_blob = binlayer.blobs(0);
const caffe::ConvolutionParameter& convolution_param = layer.convolution_param();
fprintf(pp, " 0=%d", convolution_param.num_output());
if (convolution_param.has_kernel_w() && convolution_param.has_kernel_h())
{
fprintf(pp, " 1=%d", convolution_param.kernel_w());
fprintf(pp, " 11=%d", convolution_param.kernel_h());
}
else
{
fprintf(pp, " 1=%d", convolution_param.kernel_size(0));
}
fprintf(pp, " 2=%d", convolution_param.dilation_size() != 0 ? convolution_param.dilation(0) : 1);
if (convolution_param.has_stride_w() && convolution_param.has_stride_h())
{
fprintf(pp, " 3=%d", convolution_param.stride_w());
fprintf(pp, " 13=%d", convolution_param.stride_h());
}
else
{
fprintf(pp, " 3=%d", convolution_param.stride_size() != 0 ? convolution_param.stride(0) : 1);
}
if (convolution_param.has_pad_w() && convolution_param.has_pad_h())
{
fprintf(pp, " 4=%d", convolution_param.pad_w());
fprintf(pp, " 14=%d", convolution_param.pad_h());
}
else
{
fprintf(pp, " 4=%d", convolution_param.pad_size() != 0 ? convolution_param.pad(0) : 0);
}
fprintf(pp, " 5=%d", convolution_param.bias_term());
fprintf(pp, " 6=%d", weight_blob.data_size());
int num_group = 1;
if (layer.type() == "ConvolutionDepthwise" || layer.type() == "DepthwiseConvolution")
{
num_group = convolution_param.num_output();
}
else
{
num_group = convolution_param.group();
}
if (num_group != 1)
{
fprintf(pp, " 7=%d", num_group);
}
bool int8_scale_term = false;
std::vector<float> weight_int8scale;
std::vector<float> blob_int8scale;
if (int8scale_table_path)
{
char key[256];
sprintf(key, "%s_param_0", layer.name().c_str());
if (weight_int8scale_table.find(std::string(key)) != weight_int8scale_table.end())
{
weight_int8scale = weight_int8scale_table[std::string(key)];
}
if (blob_int8scale_table.find(layer.name()) != blob_int8scale_table.end())
{
blob_int8scale = blob_int8scale_table[layer.name()];
}
int8_scale_term = !weight_int8scale.empty() && !blob_int8scale.empty();
if (int8_scale_term)
{
if ((int)weight_int8scale.size() == num_group && (int)blob_int8scale.size() == num_group)
{
fprintf(pp, " 8=1");
}
else
{
fprintf(pp, " 8=2");
}
}
}