ncnn筆記


最近看了ncnn的源碼,代碼風格清爽, 遂想先拋開VULKAN記錄一下它的推理流程。

1. 先看個yolov2 demo

csdn上的帖子https://blog.csdn.net/sinat_31425585/article/details/83243961, 其文末還附了福利直接可用的模型https://download.csdn.net/download/sinat_31425585/10737783,超贊的良心博主。

yolov2.cpp

 //construct net
   ncnn::Net yolov3;
    yolov3.load_param("mobilenetv2_yolov3.param");
    yolov3.load_model("mobilenetv2_yolov3.bin");
  //read in img
    ncnn::Mat in = ncnn::Mat::from_pixels_resize(bgr.data, ncnn::Mat::PIXEL_BGR, bgr.cols, bgr.rows, target_size, target_size);
  //substract mean
    const float mean_vals[3] = {127.5f, 127.5f, 127.5f};
    const float norm_vals[3] = {0.007843f, 0.007843f, 0.007843f};
    in.substract_mean_normalize(mean_vals, norm_vals);
  //construct create_extractor
    ncnn::Extractor ex = yolov3.create_extractor(); 
    ex.set_num_threads(4); 
  //provide input
    ex.input("data", in); 
  //get output
    ncnn::Mat out; ex.extract("detection_out", out);

     清晰的識圖流程:構建網絡, 讀入模型描述文件/權重文件, 讀入圖片做減均值, 構造提取器, 計算出預測數據。 

2. 網絡模型描述文件

/benchmark下有很多網絡的param, 以alexnet為例子

7767517
15 15
Input                    data                     0 1 data 0=227 1=227 2=3
Convolution              conv1                    1 1 data conv1_relu1 0=96 1=11 3=4 5=1 6=34848 9=1
LRN                      norm1                    1 1 conv1_relu1 norm1 2=0.000100
Pooling                  pool1                    1 1 norm1 pool1 1=3 2=2
ConvolutionDepthWise     conv2                    1 1 pool1 conv2_relu2 0=256 1=5 4=2 5=1 6=307200 7=2 9=1
LRN                      norm2                    1 1 conv2_relu2 norm2 2=0.000100
Pooling                  pool2                    1 1 norm2 pool2 1=3 2=2
Convolution              conv3                    1 1 pool2 conv3_relu3 0=384 1=3 4=1 5=1 6=884736 9=1
ConvolutionDepthWise     conv4                    1 1 conv3_relu3 conv4_relu4 0=384 1=3 4=1 5=1 6=663552 7=2 9=1
ConvolutionDepthWise     conv5                    1 1 conv4_relu4 conv5_relu5 0=256 1=3 4=1 5=1 6=442368 7=2 9=1
Pooling                  pool5                    1 1 conv5_relu5 pool5 1=3 2=2
InnerProduct             fc6                      1 1 pool5 fc6_drop6 0=4096 1=1 2=37748736 9=1
InnerProduct             fc7                      1 1 fc6_drop6 fc7_drop7 0=4096 1=1 2=16777216 9=1
InnerProduct             fc8                      1 1 fc7_drop7 fc8 0=1000 1=1 2=4096000
Softmax                  prob                     1 1 fc8 output

line 1:magic number, 有點校驗碼的味道

line 2:   layer_count   blob_count

rest line:  【layer_type】  【layer_name】 【bottom_blob_num】【top_blob_num】【bottom/top_blob_names 】【calculate_parameters】(key_id = value 的形式, 不太常見的是有個比較大的數,如conv中的 6=34848, weight_size, 從文件讀取weights有用)

calculate_parameters: 會被ParamDict解析成字典, 每個layer用ParamDict讀出計算參數。

blob 的管理: blob只是描述tensor的數據結構(存儲name,producer 和consumers的索引),實際數據存儲在extraor中。

3. Net

Extractor是個很好的推理抽象,數據成員就三個:網絡net, 計算的blobs_mats(vector<Mat>) 和 option(配置信息)

調用extract(const char* blob_name, Mat&feat)函數的時候, extractor會去判斷blob_name 指向的Mat是否有值, 有的話直接返回給feat, 沒有就去調用net 的forward_layer函數來計算出這個Mat 再返回。

net->forward_layer(layer_index,  blob_mats, opt)是一個遞歸函數, 下面只截取了部分源碼來表達邏輯

int Net::forward_layer(int layer_index, std::vector<Mat>& blob_mats, Option& opt) const
{
    const Layer* layer = layers[layer_index];
    if (layer->one_blob_only)
    {
        // load bottom blob
        int bottom_blob_index = layer->bottoms[0];
        int top_blob_index = layer->tops[0];
        if (blob_mats[bottom_blob_index].dims == 0)
        {
            int ret = forward_layer(blobs[bottom_blob_index].producer, blob_mats, opt);
            if (ret != 0)
                return ret;
        }

        Mat bottom_blob = blob_mats[bottom_blob_index];
        // forward
        if (opt.lightmode && layer->support_inplace)
        {
            Mat& bottom_top_blob = bottom_blob;
            int ret = layer->forward_inplace(bottom_top_blob, opt);
            if (ret != 0)
                return ret;
            // store top blob
            blob_mats[top_blob_index] = bottom_top_blob;
         }
   }

該函數檢查該layer的bottom_blob_num是否有效,沒有的話就調用上一層的forward_layer。bottom_blob湊齊后調用layer->forward()計算出本層的top_blob(cosumers的bottom_blob)。

再看一眼Net這個類的幾個成員函數和變量:

public:// register custom layer by layer type name
    // return 0 if success
    int register_custom_layer(const char* type, layer_creator_func creator);

    // register custom layer by layer type
    // return 0 if success
    int register_custom_layer(int index, layer_creator_func creator);

    // load network structure from plain param file
    // return 0 if success
    int load_param(FILE* fp);protected:

int forward_layer(int layer_index, std::vector<Mat>& blob_mats, Option& opt) const; protected: std::vector<Blob> blobs; std::vector<Layer*> layers; std::vector<layer_registry_entry> custom_layer_registry;

load_param() 用來初始化填充blobs和layers, forward_layer() 用來調用layer->forward()推數據流動。

下面是load_param()中create_layer的方式,先調用內建layer類中的create_layer()函數, 如果不識別再調用create_custom_layer(),這也不識別那就只有abort()了。create_layer()和 create_custom_layer() 分別去查找layer中定義的layer_registry和net中定義的custom_layer_registry。

        Layer* layer = create_layer(layer_type);
        if (!layer)
        {
            layer = create_custom_layer(layer_type);
        }     

layer_registry_entry結構中裝了layer的name的creator函數, 內建的layer_registry 通過#include "layer_registry.h"來初始化, 而layer_registry.h文件用cmake在編譯時生成, 即相應的layer在CMakeLists中添加。 而custom_layer_registry的話需調用register_custom_layer() 來注冊, 即往custom_layer_registry里push_back一個entry。examples/mobilenetv2ssdlite.cpp 下有一個注冊Noop的例子。

//layer.h
struct layer_registry_entry
{
    // layer type name
    const char* name;
    // layer factory entry
    layer_creator_func creator;
};

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

內建的layer_creator_func 也是cmake生成,定義在layer_declaration.h中, 下面是生成文件中的第一個算子。

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)

//layer.h
#define DEFINE_LAYER_CREATOR(name) \
    ::ncnn::Layer* name##_layer_creator() { return new name; }

 

4. Layer

下面是layer 類的主要成員和函數

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

    // load layer specific weight data from model binary
    // return 0 if success
    virtual int load_model(const ModelBin& mb);

public:
    // implement inference
    // return 0 if success
    virtual int forward(const std::vector<Mat>& bottom_blobs, std::vector<Mat>& top_blobs, const Option& opt = Option()) const;
    virtual int forward(const Mat& bottom_blob, Mat& top_blob, const Option& opt = Option()) const;

    // implement inplace inference
    // return 0 if success
    virtual int forward_inplace(std::vector<Mat>& bottom_top_blobs, const Option& opt = Option()) const;
    virtual int forward_inplace(Mat& bottom_top_blob, const Option& opt = Option()) const;


public:
    // layer type index
    int typeindex;
    // layer type name
    std::string type;
    // layer name
    std::string name;
    // blob index which this layer needs as input
    std::vector<int> bottoms;
    // blob index which this layer produces as output
    std::vector<int> tops;

layer_type的enum也是cmake生成的。在src/layer文件下是實現了很多的算子類, 大部分都只需要繼承Layer函數, 實現

不同的load_param(), load_model() 和forward() 函數就行。

 

先寫這么多入個門, ncnn的重點應該是針對各類算子在移動設備端的優化。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM