NCNN下benchmark里模型前向运行时间测试指南(以alexnet为例,包括float 32和int 8)

参考:https://github.com/Tencent/ncnn/tree/master/benchmark

1. NCNN安装

①下载安装包:

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/下)。

2. Alexnet前向运行时间测试(float32)

①进入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    #这里的参数是图片

3. Alexnet前向运行时间测试(int8)

(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就行了。

附1. 弯路:

  1. 直接通过修改benchncnn.cpp来强行变成int8:
    在benchncnn.cpp的xxx_run函数里,有:
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
  1. 直接修改构造函数
    把构造函数里的默认元素大小改为1u:
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
  1. 生成.table文件的时候报错,说.prototxt第一层必须为input层,原因是有两个版本的prototxt文件,生成一下新的含有input层的就好。(需要使用caffe里的小工具)
    解决:
/caffe/build/tools/upgrade_net_proto_text mobilenet_v2_deploy_prototxt mobilenet_v2_deploy_New.prototxt  #生成第一层为input的新网络

转换.table文件时就使用新的.prototxt文件就好了。

附2. (.param参数说明(见/home/firefly/ncnn/tools/caffe/caffe2ncnn.cpp)) :

例如:

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");
            }
        }
    }

你可能感兴趣的:(Computer,Vision)