# step1 create a new empty class ```cpp // mylayer.h #include "layer.h" using namespace ncnn; // a new layer type called MyLayer class MyLayer : public Layer { }; // mylayer.cpp #include "mylayer.h" DEFINE_LAYER_CREATOR(MyLayer) ``` # step2 declare layer parameters and weights ```cpp // mylayer.h #include "layer.h" using namespace ncnn; class MyLayer : public Layer { private: int channels;// new code float gamma;// new code Mat weight;// new code }; // mylayer.cpp #include "mylayer.h" DEFINE_LAYER_CREATOR(MyLayer) ``` # step3 implement load functions for parameters and weights ```cpp // mylayer.h #include "layer.h" using namespace ncnn; class MyLayer : public Layer { public: virtual int load_param(const ParamDict& pd);// new code virtual int load_model(const ModelBin& mb);// new code private: int channels; float eps; Mat gamma_data; }; // mylayer.cpp #include "mylayer.h" DEFINE_LAYER_CREATOR(MyLayer) // new routine for loading parameters int MyLayer::load_param(const ParamDict& pd) { // details about the relations with param file // https://github.com/Tencent/ncnn/wiki/param-and-model-file-structure // channels = pd.get(0, 0);// parse 0= entry, default value 0 eps = pd.get(1, 0.001f);// parse 1= entry, default value 0.001f return 0;// return zero if success } // new routine for loading weights int MyLayer::load_model(const ModelBin& mb) { // details about the relations with model file // https://github.com/Tencent/ncnn/wiki/param-and-model-file-structure // // read weights with length of channels * sizeof(float) // the second argument explains as follows // 0 judge the value type automatically, you may get float or float16 or uint8 etc // depends on the model storage and the supporting target hardware // 1 read float values anyway // 2 read float16 values anyway // 3 read uint8 values anyway gamma_data = mb.load(channels, 1); if (gamma_data.empty()) return -100;// return non-zero on error, -100 indicates out-of-memory return 0;// return zero if success } ``` # step4 determine forward behavior ```cpp // mylayer.h #include "layer.h" using namespace ncnn; class MyLayer : public Layer { public: MyLayer();// new code virtual int load_param(const ParamDict& pd); virtual int load_model(const ModelBin& mb); private: int channels; float eps; Mat gamma_data; }; // mylayer.cpp #include "mylayer.h" DEFINE_LAYER_CREATOR(MyLayer) // new routine for setting forward behavior MyLayer::MyLayer() { // one input and one output // typical one_blob_only type: Convolution, Pooling, ReLU, Softmax ... // typical non-one_blob_only type: Eltwise, Split, Concat, Slice ... one_blob_only = true; // do not change the blob size, modify data in-place // typical support_inplace type: ReLU, Sigmoid ... // typical non-support_inplace type: Convolution, Pooling ... support_inplace = true; } int MyLayer::load_param(const ParamDict& pd) { channels = pd.get(0, 0); eps = pd.get(1, 0.001f); // you could alter the behavior based on loaded parameter // if (eps == 0.001f) // { // one_blob_only = false; // support_inplace = false; // } return 0; } int MyLayer::load_model(const ModelBin& mb) { gamma_data = mb.load(channels, 1); if (gamma_data.empty()) return -100; // you could alter the behavior based on loaded weight // if (gamma_data[0] == 0.f) // { // one_blob_only = false; // support_inplace = false; // } return 0; } ``` # step5 choose proper interface based on forward behavior ```cpp // The base class Layer defines four interfaces for each forward behavior combination // 1 virtual int forward(const std::vector& bottom_blobs, std::vector& top_blobs, const Option& opt) const; // 2 virtual int forward(const Mat& bottom_blob, Mat& top_blob, const Option& opt) const; // 3 virtual int forward_inplace(std::vector& bottom_top_blobs, const Option& opt) const; // 4 virtual int forward_inplace(Mat& bottom_top_blob, const Option& opt) const; ``` **must** = layer must implement this function **optional** = layer may implement this function for optimal performance sometimes the graph inference path cannot call forward_inplace directly due to data sharing, in this situation the non-inplace forward routine will be used, which deep-copy the input blob and call inplace forward on it if the optional routine is not implemented. Thus, you could avoid this deep-copy by process input to output on-the-fly. |one_blob_only|support_inplace|1|2|3|4| |---|---|---|---|---|---| |false|false|must| | | | |false|true|optional| |must| | |true|false| |must| | | |true|true| |optional| |must| # step6 implement forward function ```cpp // mylayer.h #include "layer.h" using namespace ncnn; class MyLayer : public Layer { public: MyLayer(); virtual int load_param(const ParamDict& pd); virtual int load_model(const ModelBin& mb); virtual int forward(const Mat& bottom_blob, Mat& top_blob, const Option& opt) const;// new code, optional virtual int forward_inplace(Mat& bottom_top_blob, const Option& opt) const;// new code private: int channels; float eps; Mat gamma_data; }; // mylayer.cpp #include "mylayer.h" DEFINE_LAYER_CREATOR(MyLayer) MyLayer::MyLayer() { one_blob_only = true; support_inplace = true; } int MyLayer::load_param(const ParamDict& pd) { channels = pd.get(0, 0); eps = pd.get(1, 0.001f); return 0; } int MyLayer::load_model(const ModelBin& mb) { gamma_data = mb.load(channels, 1); if (gamma_data.empty()) return -100; return 0; } // optional new routine for layer forward function, non-inplace version int MyLayer::forward(const Mat& bottom_blob, Mat& top_blob, const Option& opt) const { // check input dims, return non-zero on error if (bottom_blob.c != channels) return -1; // x = (x + eps) * gamma_per_channel int w = bottom_blob.w; int h = bottom_blob.h; size_t elemsize = bottom_blob.elemsize; int size = w * h; top_blob.create(w, h, channels, elemsize, opt.blob_allocator); if (top_blob.empty()) return -100;// return non-zero on error, -100 indicates out-of-memory #pragma omp parallel for num_threads(opt.num_threads) for (int q=0; q