<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          如何閱讀一個(gè)前向推理框架?以NCNN為例。

          共 16890字,需瀏覽 34分鐘

           ·

          2020-12-24 08:53

          【GiantPandaCV導(dǎo)語(yǔ)】自NCNN開(kāi)源以來(lái),其它廠商的端側(cè)推理框架或者搭載特定硬件芯片的工具鏈層出不窮。如何去繁從簡(jiǎn)的閱讀一個(gè)深度學(xué)習(xí)推理框架十分重要,這篇文章記錄了我是如何閱讀NCNN框架的,希望對(duì)一些不知道如何下手的讀者有一點(diǎn)啟發(fā)。

          0x00. 想法來(lái)源

          CNN從15年的ResNet在ImageNet比賽中大放異彩,到今天各種層出不窮的網(wǎng)絡(luò)結(jié)構(gòu)被提出以解決生活中碰到的各種問(wèn)題。然而,在CNN長(zhǎng)期發(fā)展過(guò)程中,也伴隨著很多的挑戰(zhàn),比如如何調(diào)整算法使得在特定場(chǎng)景或者說(shuō)數(shù)據(jù)集上取得最好的精度,如何將學(xué)術(shù)界出色的算法落地到工業(yè)界,如何設(shè)計(jì)出在邊緣端或者有限硬件條件下的定制化CNN等。前兩天看到騰訊優(yōu)圖的文章:騰訊優(yōu)圖開(kāi)源這三年 ,里面提到了NCNN背后的故事,十分感動(dòng)和佩服,然后我也是白嫖了很多NCNN的算法實(shí)現(xiàn)以及一些調(diào)優(yōu)技巧。所以為了讓很多不太了解NCNN的人能更好的理解騰訊優(yōu)圖這個(gè)"從0到1"的深度學(xué)習(xí)框架,我將結(jié)合我自己擅長(zhǎng)的東西來(lái)介紹我眼中的NCNN它是什么樣的

          0x01. 如何使用NCNN

          這篇文章的重點(diǎn)不是如何跑起來(lái)NCNN的各種Demo,也不是如何使用NCNN來(lái)部署自己的業(yè)務(wù)網(wǎng)絡(luò),這部分沒(méi)有什么比官方wiki介紹得更加清楚的資料了。所以這部分我只是簡(jiǎn)要匯總一些資料,以及說(shuō)明一些我認(rèn)為非常重要的東西。

          官方wiki指路:https://github.com/Tencent/ncnn/wiki

          在NCNN中新建一個(gè)自定義層教程:https://github.com/Ewenwan/MVision/blob/master/CNN/HighPerformanceComputing/example/ncnn_%E6%96%B0%E5%BB%BA%E5%B1%82.md

          NCNN下載編譯以及使用:https://github.com/Ewenwan/MVision/blob/master/CNN/HighPerformanceComputing/example/readme.md

          0x02. 運(yùn)行流程解析

          要了解一個(gè)深度學(xué)習(xí)框架,首先得搞清楚這個(gè)框架是如何通過(guò)讀取一張圖片然后獲得的我們想要的輸出結(jié)果,這個(gè)運(yùn)行流程究竟是長(zhǎng)什么樣的?我們看一下NCNN官方wiki中提供一個(gè)示例代碼:

          #include?
          #include?
          #include?"net.h"

          int?main()
          {
          ?//?opencv讀取輸入圖片
          ????cv::Mat?img?=?cv::imread("image.ppm",?CV_LOAD_IMAGE_GRAYSCALE);
          ????int?w?=?img.cols;
          ????int?h?=?img.rows;

          ????//?減均值以及縮放操作,最后輸入數(shù)據(jù)的值域?yàn)閇-1,1]
          ????ncnn::Mat?in?=?ncnn::Mat::from_pixels_resize(img.data,?ncnn::Mat::PIXEL_GRAY,?w,?h,?60,?60);
          ????float?mean[1]?=?{?128.f?};
          ????float?norm[1]?=?{?1/128.f?};
          ????in.substract_mean_normalize(mean,?norm);
          ?
          ?//?構(gòu)建NCNN的net,并加載轉(zhuǎn)換好的模型
          ????ncnn::Net?net;
          ????net.load_param("model.param");
          ????net.load_model("model.bin");

          ?//?創(chuàng)建網(wǎng)絡(luò)提取器,設(shè)置網(wǎng)絡(luò)輸入,線程數(shù),light模式等等
          ????ncnn::Extractor?ex?=?net.create_extractor();
          ????ex.set_light_mode(true);
          ????ex.set_num_threads(4);
          ????ex.input("data",?in);
          ?//?調(diào)用extract接口,完成網(wǎng)絡(luò)推理,獲得輸出結(jié)果
          ????ncnn::Mat?feat;
          ????ex.extract("output",?feat);

          ????return?0;

          0x02.00 圖像預(yù)處理ncnn::Mat

          可以看到NCNN對(duì)于我們給定的一個(gè)網(wǎng)絡(luò)(首先轉(zhuǎn)換為NCNN的param和bin文件)和輸入,首先執(zhí)行圖像預(yù)處理,這是基于ncnn::Mat這個(gè)數(shù)據(jù)結(jié)構(gòu)完成的。

          其中,from_pixels_resize() 這個(gè)函數(shù)的作用是生成目標(biāo)尺寸大小的網(wǎng)絡(luò)輸入Mat,它的實(shí)現(xiàn)在https://github.com/Tencent/ncnn/blob/b93775a27273618501a15a235355738cda102a38/src/mat_pixel.cpp#L2543。它的內(nèi)部實(shí)際上是根據(jù)傳入的輸入圖像的通道數(shù)完成resize_bilinear_c1/c2/c3/4 即一通道/二通道/三通道/四通道 圖像變形算法,可以看到使用的是雙線性插值算法。這些操作的實(shí)現(xiàn)在https://github.com/Tencent/ncnn/blob/master/src/mat_pixel_resize.cpp#L27。然后經(jīng)過(guò)Resize之后,需要將像素圖像轉(zhuǎn)換成ncnn::Mat。這里調(diào)用的是Mat::from_pixels()這個(gè)函數(shù),它將我們Resize操作之后獲得的像素圖像數(shù)據(jù)(即float*數(shù)據(jù))根據(jù)特定的輸入類型賦值給ncnn::Mat

          接下來(lái),我們講講substract_mean_normalize()這個(gè)函數(shù),它實(shí)現(xiàn)了減均值和歸一化操作,它的實(shí)現(xiàn)在:https://github.com/Tencent/ncnn/blob/master/src/mat.cpp#L34。具體來(lái)說(shuō),這個(gè)函數(shù)根據(jù)均值參數(shù)和歸一化參數(shù)的有無(wú)分成這幾種情況:

          • 有均值參數(shù)
            • 創(chuàng)建 偏置層 ? ncnn::create_layer(ncnn::LayerType::Bias); ?載入層參數(shù) op->load_param(pd); ?3通道
            • 載入層權(quán)重?cái)?shù)據(jù) op->load_model(ncnn::ModelBinFromMatArray(weights)); ?-均值參數(shù)
            • 運(yùn)行層 ? ? ? ?op->forward_inplace(*this);
          • 有歸一化參數(shù)
            • 創(chuàng)建 尺度層 ? ncnn::create_layer(ncnn::LayerType::Scale); ?載入層參數(shù) op->load_param(pd); ?3通道
            • 載入層權(quán)重?cái)?shù)據(jù) op->load_model(ncnn::ModelBinFromMatArray(weights)); ?尺度參數(shù)
            • 運(yùn)行層 ? ? ? ?op->forward_inplace(*this);
          • 有均值和歸一化參數(shù)
            • 創(chuàng)建 尺度層 ? ncnn::create_layer(ncnn::LayerType::Scale); ?載入層參數(shù) op->load_param(pd); ?3通道
            • 載入層權(quán)重?cái)?shù)據(jù) op->load_model(ncnn::ModelBinFromMatArray(weights)); ?-均值參數(shù) 和 尺度參數(shù)
            • 運(yùn)行層 ? ? ? ?op->forward_inplace(*this);

          可以看到NCNN的均值和歸一化操作,是直接利用了它的Bias Layer和Scale Layer來(lái)實(shí)現(xiàn)的,也就是說(shuō)NCNN中的每個(gè)層都可以單獨(dú)拿出來(lái)運(yùn)行我們自己數(shù)據(jù),更加方便我們白嫖

          0x02.01 模型解析ncnn::Net

          param 解析

          完成了圖像預(yù)處理之后,新增了一個(gè)ncnn::Net,然后調(diào)用Net::load_param來(lái)載入網(wǎng)絡(luò)參數(shù)文件 *.proto, 這部分的實(shí)現(xiàn)在https://github.com/Tencent/ncnn/blob/master/src/net.cpp#L115。在講解這個(gè)函數(shù)在的過(guò)程之前,我們先來(lái)一起分析一下NCNN的param文件,舉例如下:

          ??7767517???#?文件頭?魔數(shù)
          ??75?83?????#?層數(shù)量??輸入輸出blob數(shù)量
          ????????????#?下面有75行
          ??Input????????????data?????????????0?1?data?0=227?1=227?2=3
          ??Convolution??????conv1????????????1?1?data?conv1?0=64?1=3?2=1?3=2?4=0?5=1?6=1728
          ??ReLU?????????????relu_conv1???????1?1?conv1?conv1_relu_conv1?0=0.000000
          ??Pooling??????????pool1????????????1?1?conv1_relu_conv1?pool1?0=0?1=3?2=2?3=0?4=0
          ??Convolution??????fire2/squeeze1x1?1?1?pool1?fire2/squeeze1x1?0=16?1=1?2=1?3=1?4=0?5=1?6=1024
          ??...
          ??層類型????????????層名字???輸入blob數(shù)量?輸出blob數(shù)量??輸入blob名字?輸出blob名字???參數(shù)字典
          ??
          ??參數(shù)字典,每一層的意義不一樣:
          ??數(shù)據(jù)輸入層?Input????????????data?????????????0?1?data?0=227?1=227?2=3???圖像寬度×圖像高度×通道數(shù)量
          ??卷積層????Convolution??...???0=64?????1=3??????2=1????3=2?????4=0????5=1????6=1728???????????
          ???????????0輸出通道數(shù)?num_output()?;?1卷積核尺寸?kernel_size();??2空洞卷積參數(shù)?dilation();?3卷積步長(zhǎng)?stride();?
          ?????????? 4卷積填充pad_size();?????? 5卷積偏置有無(wú)bias_term();?? 6卷積核參數(shù)數(shù)量 weight_blob.data_size();
          ??????????????????????????????????????????????????????????????C_OUT?*?C_in?*?W_h?*?W_w?=?64*3*3*3?=?1728
          ??池化層????Pooling??????0=0???????1=3???????2=2????????3=0???????4=0
          ??????????????????????0池化方式:最大值、均值、隨機(jī)?????1池化核大小?kernel_size();?????2池化核步長(zhǎng)?stride();?
          ??????????????????????3池化核填充?pad();???4是否為全局池化?global_pooling();
          ??激活層????ReLU???????0=0.000000?????下限閾值?negative_slope();
          ???????????ReLU6??????0=0.000000?????1=6.000000?上下限
          ??
          ??綜合示例:
          ??0=1?1=2.5?-23303=2,2.0,3.0
          ??
          ??數(shù)組關(guān)鍵字?:?-23300?
          ??-(-23303)?-?23300?=?3?表示該參數(shù)在參數(shù)數(shù)組中的index
          ??后面的第一個(gè)參數(shù)表示數(shù)組元素?cái)?shù)量,2表示包含兩個(gè)元素

          然后官方的wiki中提供了所有網(wǎng)絡(luò)層的詳細(xì)參數(shù)設(shè)置,地址為:https://github.com/Tencent/ncnn/wiki/operation-param-weight-table

          了解了Param的基本含義之后,我們可以來(lái)看一下Net::load_param這個(gè)函數(shù)是在做什么了。

          從函數(shù)實(shí)現(xiàn),我們知道,首先會(huì)遍歷param文件中的所有網(wǎng)絡(luò)層,然后根據(jù)當(dāng)前層的類型調(diào)用create_layer()/ net::create_custom_layer()來(lái)創(chuàng)建網(wǎng)絡(luò)層,然后讀取輸入Blobs和輸出Blobs和當(dāng)前層綁定,再調(diào)用paramDict::load_param(fp)解析當(dāng)前層的特定參數(shù)(參數(shù)字典),按照id=參數(shù)/參數(shù)數(shù)組來(lái)解析。最后,當(dāng)前層調(diào)用layer->load_param(pd)載入解析得到的層特殊參數(shù)即獲得當(dāng)前層特有的參數(shù)。

          核心代碼解析如下:

          //?參數(shù)讀取?程序

          //?讀取字符串格式的?參數(shù)文件
          int?ParamDict::load_param(FILE*?fp)
          {
          ????clear();

          //?????0=100?1=1.250000?-23303=5,0.1,0.2,0.4,0.8,1.0

          ????//?parse?each?key=value?pair
          ????int?id?=?0;
          ????while?(fscanf(fp,?"%d=",?&id)?==?1)//?讀取?等號(hào)前面的?key=========
          ????{
          ????????bool?is_array?=?id?<=?-23300;
          ????????if?(is_array)
          ????????{
          ????????????id?=?-id?-?23300;//?數(shù)組?關(guān)鍵字?-23300??得到該參數(shù)在參數(shù)數(shù)組中的?index
          ????????}
          ????????
          //?是以?-23300?開(kāi)頭表示的數(shù)組===========
          ????????if?(is_array)
          ????????{
          ????????????int?len?=?0;
          ????????????int?nscan?=?fscanf(fp,?"%d",?&len);//?后面的第一個(gè)參數(shù)表示數(shù)組元素?cái)?shù)量,5表示包含兩個(gè)元素
          ????????????if?(nscan?!=?1)
          ????????????{
          ????????????????fprintf(stderr,?"ParamDict?read?array?length?fail\n");
          ????????????????return?-1;
          ????????????}

          ????????????params[id].v.create(len);

          ????????????for?(int?j?=?0;?j?????????????{
          ????????????????char?vstr[16];
          ????????????????nscan?=?fscanf(fp,?",%15[^,\n?]",?vstr);//按格式解析字符串============
          ????????????????if?(nscan?!=?1)
          ????????????????{
          ????????????????????fprintf(stderr,?"ParamDict?read?array?element?fail\n");
          ????????????????????return?-1;
          ????????????????}

          ????????????????bool?is_float?=?vstr_is_float(vstr);//?檢查該字段是否為?浮點(diǎn)數(shù)的字符串

          ????????????????if?(is_float)
          ????????????????{
          ????????????????????float*?ptr?=?params[id].v;
          ????????????????????nscan?=?sscanf(vstr,?"%f",?&ptr[j]);//?轉(zhuǎn)換成浮點(diǎn)數(shù)后存入?yún)?shù)字典中
          ????????????????}
          ????????????????else
          ????????????????{
          ????????????????????int*?ptr?=?params[id].v;
          ????????????????????nscan?=?sscanf(vstr,?"%d",?&ptr[j]);//?轉(zhuǎn)換成?整數(shù)后?存入字典中
          ????????????????}
          ????????????????if?(nscan?!=?1)
          ????????????????{
          ????????????????????fprintf(stderr,?"ParamDict?parse?array?element?fail\n");
          ????????????????????return?-1;
          ????????????????}
          ????????????}
          ????????}
          //?普通關(guān)鍵字=========================
          ????????else
          ????????{
          ????????????char?vstr[16];
          ????????????int?nscan?=?fscanf(fp,?"%15s",?vstr);//?獲取等號(hào)后面的?字符串
          ????????????if?(nscan?!=?1)
          ????????????{
          ????????????????fprintf(stderr,?"ParamDict?read?value?fail\n");
          ????????????????return?-1;
          ????????????}

          ????????????bool?is_float?=?vstr_is_float(vstr);//?判斷是否為浮點(diǎn)數(shù)

          ????????????if?(is_float)
          ????????????????nscan?=?sscanf(vstr,?"%f",?¶ms[id].f);?//?讀入為浮點(diǎn)數(shù)
          ????????????else
          ????????????????nscan?=?sscanf(vstr,?"%d",?¶ms[id].i);//?讀入為整數(shù)
          ????????????if?(nscan?!=?1)
          ????????????{
          ????????????????fprintf(stderr,?"ParamDict?parse?value?fail\n");
          ????????????????return?-1;
          ????????????}
          ????????}

          ????????params[id].loaded?=?1;//?設(shè)置該?參數(shù)以及載入
          ????}

          ????return?0;
          }

          //?讀取?二進(jìn)制格式的?參數(shù)文件===================
          int?ParamDict::load_param_bin(FILE*?fp)
          {
          ????clear();

          //?????binary?0
          //?????binary?100
          //?????binary?1
          //?????binary?1.250000
          //?????binary?3?|?array_bit
          //?????binary?5
          //?????binary?0.1
          //?????binary?0.2
          //?????binary?0.4
          //?????binary?0.8
          //?????binary?1.0
          //?????binary?-233(EOP)

          ????int?id?=?0;
          ????fread(&id,?sizeof(int),?1,?fp);//?讀入一個(gè)整數(shù)長(zhǎng)度的?index

          ????while?(id?!=?-233)//?結(jié)尾
          ????{
          ????????bool?is_array?=?id?<=?-23300;
          ????????if?(is_array)
          ????????{
          ????????????id?=?-id?-?23300;//?數(shù)組關(guān)鍵字對(duì)應(yīng)的?index
          ????????}
          //?是數(shù)組數(shù)據(jù)=======
          ????????if?(is_array)
          ????????{
          ????????????int?len?=?0;
          ????????????fread(&len,?sizeof(int),?1,?fp);//?數(shù)組元素?cái)?shù)量

          ????????????params[id].v.create(len);

          ????????????float*?ptr?=?params[id].v;
          ????????????fread(ptr,?sizeof(float),?len,?fp);//?按浮點(diǎn)數(shù)長(zhǎng)度*數(shù)組長(zhǎng)度?讀取每一個(gè)數(shù)組元素====
          ????????}
          //?是普通數(shù)據(jù)=======
          ????????else
          ????????{
          ????????????fread(¶ms[id].f,?sizeof(float),?1,?fp);//?按浮點(diǎn)數(shù)長(zhǎng)度讀取?該普通字段對(duì)應(yīng)的元素
          ????????}

          ????????params[id].loaded?=?1;

          ????????fread(&id,?sizeof(int),?1,?fp);//?讀取?下一個(gè)?index
          ????}

          ????return?0;
          }

          bin 解析

          解析完param文件,接下來(lái)需要對(duì)bin文件進(jìn)行解析,這部分的實(shí)現(xiàn)在:https://github.com/Tencent/ncnn/blob/master/src/net.cpp#L672。這里執(zhí)行的主要的操作如下:

          • 創(chuàng)建 ModelBinFromStdio 對(duì)象 提供載入?yún)?shù)的接口函數(shù) ModelBinFromStdio::load()根據(jù) 權(quán)重?cái)?shù)據(jù)開(kāi)始的一個(gè)四字節(jié)數(shù)據(jù)類型參數(shù)(float32/float16/int8等) 和 指定的參數(shù)數(shù)量 讀取數(shù)據(jù)到 Mat 并返回Mat, 這個(gè)函數(shù)的實(shí)現(xiàn)在https://github.com/Tencent/ncnn/blob/master/src/modelbin.cpp#L50
          • 根據(jù)load_param 獲取到的網(wǎng)絡(luò)層信息 遍歷每一層 載入每一層的模型數(shù)據(jù) layer->load_model() 每一層特有函數(shù)。
          • 部分層需要 根據(jù)層實(shí)際參數(shù) 調(diào)整運(yùn)行流水線 layer->create_pipeline 例如卷積層和全連接層
          • 量化的網(wǎng)絡(luò)需要融合 Net::fuse_network()

          bin文件的結(jié)構(gòu)如下:

          ????+---------+---------+---------+---------+---------+---------+
          ????|?weight1?|?weight2?|?weight3?|?weight4?|?.......?|?weightN?|
          ????+---------+---------+---------+---------+---------+---------+
          ????^?????????^?????????^?????????^
          ????0x0??????0x80??????0x140?????0x1C0

          ??所有權(quán)重?cái)?shù)據(jù)連接起來(lái), 每個(gè)權(quán)重占 32bit。

          ??權(quán)重?cái)?shù)據(jù)?weight?buffer

          ??[flag]?(optional?可選)
          ??[raw?data]
          ??[padding]?(optional?可選)

          ??????flag?:?unsigned?int,?little-endian,?indicating?the?weight?storage?type,?
          ?????????????0??????????=>?float32,?
          ?????????????0x01306B47?=>?float16,?
          ?????????????其它非0?=>?int8,??如果層實(shí)現(xiàn)顯式強(qiáng)制存儲(chǔ)類型,則可以省略??????
          ??????raw?data?:?原始權(quán)重?cái)?shù)據(jù)、little?endian、float32數(shù)據(jù)或float16數(shù)據(jù)或量化表和索引,具體取決于存儲(chǔ)類型標(biāo)志
          ????? padding : 32位對(duì)齊的填充空間,如果已經(jīng)對(duì)齊,則可以省略。

          感覺(jué)bin解析這部分了解一下就好,如果感興趣可以自己去看看源碼。

          0x02.03 網(wǎng)絡(luò)運(yùn)行 ncnn::Extractor

          至此,我們將網(wǎng)絡(luò)的結(jié)構(gòu)和權(quán)重信息都放到了ncnn::Net這個(gè)結(jié)構(gòu)中,接下來(lái)我們就可以新建網(wǎng)絡(luò)提取器 Extractor Net::create_extractor,它給我們提供了設(shè)置網(wǎng)絡(luò)輸入(Extractor::input),獲取網(wǎng)絡(luò)輸出(Extractor::extract),設(shè)置網(wǎng)絡(luò)運(yùn)行線程參數(shù)(Extractor::set_num_threads)等接口。接下來(lái),我們只需要調(diào)用Extractor::extract運(yùn)行網(wǎng)絡(luò)(net)的前向傳播函數(shù)net->forward_layer就可以獲得最后的結(jié)果了。

          另外,ncnn::Extractor還可以設(shè)置一個(gè)輕模式省內(nèi)存 即set_light_mode(true),原理是net中每個(gè)layer都會(huì)產(chǎn)生blob,除了最后的結(jié)果和多分支中間結(jié)果,大部分blob都不值得保留,開(kāi)啟輕模式可以在運(yùn)算后自動(dòng)回收,省下內(nèi)存。但需要注意的是,一旦開(kāi)啟這個(gè)模式,我們就不能獲得中間層的特征值了,因?yàn)橹虚g層的內(nèi)存在獲得最終結(jié)果之前都被回收掉了。例如:某網(wǎng)絡(luò)結(jié)構(gòu)為 A -> B -> C,在輕模式下,向ncnn索要C結(jié)果時(shí),A結(jié)果會(huì)在運(yùn)算B時(shí)自動(dòng)回收,而B(niǎo)結(jié)果會(huì)在運(yùn)算C時(shí)自動(dòng)回收,最后只保留C結(jié)果,后面再需要C結(jié)果會(huì)直接獲得,滿足大多數(shù)深度網(wǎng)絡(luò)的使用方式

          最后,我們需要明確一下,我們剛才是先創(chuàng)建了ncnn::net,然后我們調(diào)用的ncnn::Extractor作為運(yùn)算實(shí)例,因此運(yùn)算實(shí)例是不受net限制的。換句話說(shuō),雖然我們只有一個(gè)net,但我們可以開(kāi)多個(gè)ncnn::Extractor,這些實(shí)例都是單獨(dú)完成特定網(wǎng)絡(luò)的推理,互不影響。

          這樣我們就大致了解了NCNN的運(yùn)行流程了,更多的細(xì)節(jié)可以關(guān)注NCNN源碼。

          0x03. NCNN源碼目錄分析

          這一節(jié),我們來(lái)分析一下NCNN源碼目錄以便更好的理解整個(gè)工程。src的目錄結(jié)構(gòu)如下:

          • /src 目錄:
            • ./src/layer下是所有的layer定義代碼
            • ./src/layer/arm是arm下的計(jì)算加速的layer
            • ./src/layer/x86是x86下的計(jì)算加速的layer。
            • ./src/layer/mips是mips下的計(jì)算加速的layer。
            • ./src/layer/.h + ./src/layer/.cpp 是各種layer的基礎(chǔ)實(shí)現(xiàn),無(wú)加速。
            • 目錄頂層下是一些基礎(chǔ)代碼,如宏定義,平臺(tái)檢測(cè),mat數(shù)據(jù)結(jié)構(gòu),layer定義,blob定義,net定義等。
            • platform.h.in 平臺(tái)檢測(cè)
            • benchmark.h + benchmark.cpp 測(cè)試各個(gè)模型的執(zhí)行速度
            • allocator.h + allocator.cpp 內(nèi)存池管理,內(nèi)存對(duì)齊
            • paramdict.h + paramdict.cpp 層參數(shù)解析 讀取二進(jìn)制格式、字符串格式、密文格式的參數(shù)文件
            • opencv.h opencv.cpp ?opencv 風(fēng)格的數(shù)據(jù)結(jié)構(gòu) 的 mini實(shí)現(xiàn),包含大小結(jié)構(gòu)體 Size,矩陣框結(jié)構(gòu)體 Rect_ 交集 并集運(yùn)算符重載,點(diǎn)結(jié)構(gòu)體 ? ? Point_,矩陣結(jié)構(gòu)體 ? Mat ? ? 深拷貝 淺拷貝 獲取指定矩形框中的roi 讀取圖像 寫圖像 雙線性插值算法改變大小等等
            • mat.h mat.cpp ? 三維矩陣數(shù)據(jù)結(jié)構(gòu), 在層間傳播的就是Mat數(shù)據(jù),Blob數(shù)據(jù)是工具人,另外包含 substract_mean_normalize(),去均值并歸一化;half2float(),float16 的 data 轉(zhuǎn)換成 float32 的 data; ?copy_make_border(), 矩陣周圍填充; resize_bilinear_image(),雙線性插值等函數(shù)。
            • net.h net.cpp ?ncnn框架接口,包含注冊(cè) 用戶定義的新層Net::register_custom_layer(); 網(wǎng)絡(luò)載入 模型參數(shù) ? Net::load_param(); 載入 ? ? 模型權(quán)重 ? Net::load_model(); 網(wǎng)絡(luò)blob 輸入 Net::input(); ?網(wǎng)絡(luò)前向傳播Net::forward_layer();被Extractor::extract() 執(zhí)行;創(chuàng)建網(wǎng)絡(luò)模型提取器 ? Net::create_extractor(); 模型提取器提取某一層輸出Extractor::extract()等函數(shù)。
            • ...

          源碼目錄除了這些還有很多文件,介于篇幅原因就不再枚舉了,感興趣的可以自行查看源碼。由于我只對(duì)x86和arm端的指令集加速熟悉一些,所以這里再枚舉一下src/layers下面的NCNN支持的層的目錄:

          ├──?absval.cpp???????????????????????//?絕對(duì)值層
          ├──?absval.h
          ├──?argmax.cpp???????????????????????//?最大值層
          ├──?argmax.h
          ├──?arm?============================?arm平臺(tái)下的層
          │???├──?absval_arm.cpp???????????????//?絕對(duì)值層
          │???├──?absval_arm.h
          │???├──?batchnorm_arm.cpp????????????//?批歸一化?去均值除方差
          │???├──?batchnorm_arm.h
          │???├──?bias_arm.cpp?????????????????//?偏置
          │???├──?bias_arm.h
          │???├──?convolution_1x1.h????????????//?1*1?float32?卷積
          │???├──?convolution_1x1_int8.h???????//?1*1?int8????卷積
          │???├──?convolution_2x2.h????????????//?2*2?float32?卷積
          │???├──?convolution_3x3.h????????????//?3*3?float32?卷積
          │???├──?convolution_3x3_int8.h???????//?3*3?int8????卷積
          │???├──?convolution_4x4.h????????????//?4*4?float32?卷積
          │???├──?convolution_5x5.h????????????//?5*5?float32?卷積
          │???├──?convolution_7x7.h????????????//?7*7?float32?卷積
          │???├──?convolution_arm.cpp??????????//?卷積層
          │???├──?convolution_arm.h
          │???├──?convolutiondepthwise_3x3.h??????//?3*3?逐通道?float32?卷積
          │???├──?convolutiondepthwise_3x3_int8.h?//?3*3?逐通道?int8????卷積?
          │???├──?convolutiondepthwise_arm.cpp????//?逐通道卷積
          │???├──?convolutiondepthwise_arm.h
          │???├──?deconvolution_3x3.h?????????????//?3*3?反卷積
          │???├──?deconvolution_4x4.h?????????????//?4*4?反卷積
          │???├──?deconvolution_arm.cpp???????????//?反卷積
          │???├──?deconvolution_arm.h
          │???├──?deconvolutiondepthwise_arm.cpp??//?反逐通道卷積
          │???├──?deconvolutiondepthwise_arm.h
          │???├──?dequantize_arm.cpp??????????????//?反量化
          │???├──?dequantize_arm.h
          │???├──?eltwise_arm.cpp?????????????????//?逐元素操作,product(點(diǎn)乘),?sum(相加減)?和?max(取大值)
          │???├──?eltwise_arm.h
          │???├──?innerproduct_arm.cpp????????????//?即?fully_connected?(fc)layer,?全連接層
          │???├──?innerproduct_arm.h
          │???├──?lrn_arm.cpp?????????????????????//?Local?Response?Normalization,即局部響應(yīng)歸一化層
          │???├──?lrn_arm.h
          │???├──?neon_mathfun.h??????????????????//?neon?數(shù)學(xué)函數(shù)庫(kù)
          │???├──?pooling_2x2.h???????????????????//?2*2?池化層
          │???├──?pooling_3x3.h???????????????????//?3*3?池化層
          │???├──?pooling_arm.cpp?????????????????//?池化層
          │???├──?pooling_arm.h
          │???├──?prelu_arm.cpp???????????????????//?(a*x,x)?前置relu激活層
          │???├──?prelu_arm.h
          │???├──?quantize_arm.cpp????????????????//?量化層
          │???├──?quantize_arm.h
          │???├──?relu_arm.cpp????????????????????//?relu?層?(0,x)
          │???├──?relu_arm.h
          │???├──?scale_arm.cpp???????????????????//?BN層后的?平移和縮放層?scale
          │???├──?scale_arm.h
          │???├──?sigmoid_arm.cpp?????????????????//?sigmod?負(fù)指數(shù)倒數(shù)歸一化?激活層??1/(1?+?e^(-zi))
          │???├──?sigmoid_arm.h
          │???├──?softmax_arm.cpp?????????????????//?softmax?指數(shù)求和歸一化?激活層???e^(zi)?/?sum(e^(zi))
          │???└──?softmax_arm.h
          |
          |
          |================================?普通平臺(tái)?待優(yōu)化=============
          ├──?batchnorm.cpp?????????????//?批歸一化?去均值除方差
          ├──?batchnorm.h
          ├──?bias.cpp??????????????????//?偏置
          ├──?bias.h
          ├──?binaryop.cpp??????????????//?二元操作:?add,sub,?div,?mul,mod等
          ├──?binaryop.h
          ├──?bnll.cpp??????????????????//?binomial?normal?log?likelihood的簡(jiǎn)稱?f(x)=log(1?+?exp(x))??激活層
          ├──?bnll.h
          ├──?clip.cpp??????????????????//?截?cái)?====
          ├──?clip.h
          ├──?concat.cpp????????????????//?通道疊加
          ├──?concat.h
          ├──?convolution.cpp???????????//?普通卷積層
          ├──?convolutiondepthwise.cpp??//?逐通道卷積
          ├──?convolutiondepthwise.h
          ├──?convolution.h?
          ├──?crop.cpp??????????????????//?剪裁層
          ├──?crop.h
          ├──?deconvolution.cpp?????????//?反卷積
          ├──?deconvolutiondepthwise.cpp//?反逐通道卷積
          ├──?deconvolutiondepthwise.h
          ├──?deconvolution.h
          ├──?dequantize.cpp????????????//?反量化
          ├──?dequantize.h
          ├──?detectionoutput.cpp???????//?ssd?的檢測(cè)輸出層================================
          ├──?detectionoutput.h
          ├──?dropout.cpp???????????????//?隨機(jī)失活層?在訓(xùn)練時(shí)由于舍棄了一些神經(jīng)元,因此在測(cè)試時(shí)需要在激勵(lì)的結(jié)果中乘上因子p進(jìn)行縮放.
          ├──?dropout.h
          ├──?eltwise.cpp???????????????//?逐元素操作,?product(點(diǎn)乘),?sum(相加減)?和?max(取大值)
          ├──?eltwise.h
          ├──?elu.cpp???????????????????//?指數(shù)線性單元relu激活層?Prelu?:?(a*x,?x)?---->?Erelu?:?(a*(e^x?-?1),?x)?
          ├──?elu.h
          ├──?embed.cpp?????????????????//?嵌入層,用在網(wǎng)絡(luò)的開(kāi)始層將你的輸入轉(zhuǎn)換成向量
          ├──?embed.h
          ├──?expanddims.cpp????????????//?增加維度
          ├──?expanddims.h
          ├──?exp.cpp???????????????????//?指數(shù)映射
          ├──?exp.h
          ├──?flatten.cpp???????????????//?攤平層
          ├──?flatten.h
          ├──?innerproduct.cpp??????????//?全連接層
          ├──?innerproduct.h
          ├──?input.cpp?????????????????//?數(shù)據(jù)輸入層
          ├──?input.h
          ├──?instancenorm.cpp??????????//?單樣本?標(biāo)準(zhǔn)化?規(guī)范化
          ├──?instancenorm.h
          ├──?interp.cpp????????????????//?插值層?上下采樣等
          ├──?interp.h
          ├──?log.cpp???????????????????//?對(duì)數(shù)層
          ├──?log.h
          ├──?lrn.cpp???????????????????//?Local?Response?Normalization,即局部響應(yīng)歸一化層
          ├──?lrn.h?????????????????????//?對(duì)局部神經(jīng)元的活動(dòng)創(chuàng)建競(jìng)爭(zhēng)機(jī)制,使得其中響應(yīng)比較大的值變得相對(duì)更大,
          |?????????????????????????????//?并抑制其他反饋較小的神經(jīng)元,增強(qiáng)了模型的泛化能力
          ├──?lstm.cpp????????????????
          ├──?lstm.h????????????????????//?lstm?長(zhǎng)短詞記憶層
          ├──?memorydata.cpp????????????//?內(nèi)存數(shù)據(jù)層
          ├──?memorydata.h
          ├──?mvn.cpp
          ├──?mvn.h
          ├──?normalize.cpp?????????????//?歸一化
          ├──?normalize.h
          ├──?padding.cpp???????????????//?填充,警戒線
          ├──?padding.h
          ├──?permute.cpp???????????????//??ssd?特有層?交換通道順序?[bantch_num,?channels,?h,?w]?--->?[bantch_num,?h,?w,?channels]]=========
          ├──?permute.h
          ├──?pooling.cpp???????????????//?池化層
          ├──?pooling.h
          ├──?power.cpp?????????????????//?平移縮放乘方?:?(shift?+?scale?*?x)?^?power
          ├──?power.h
          ├──?prelu.cpp?????????????????//?Prelu??(a*x,x)
          ├──?prelu.h
          ├──?priorbox.cpp??????????????//?ssd?獨(dú)有的層?建議框生成層?L1?loss?擬合============================
          ├──?priorbox.h
          ├──?proposal.cpp??????????????//?faster?rcnn?獨(dú)有的層?建議框生成,將rpn網(wǎng)絡(luò)的輸出轉(zhuǎn)換成建議框========?
          ├──?proposal.h
          ├──?quantize.cpp??????????????//?量化層
          ├──?quantize.h
          ├──?reduction.cpp?????????????//?將輸入的特征圖按照給定的維度進(jìn)行求和或求平均
          ├──?reduction.h
          ├── relu.cpp ?????????????????// relu 激活層:?(0,x)
          ├──?relu.h
          ├──?reorg.cpp?????????????????//?yolov2?獨(dú)有的層,?一拆四層,一個(gè)大矩陣,下采樣到四個(gè)小矩陣=================
          ├──?reorg.h
          ├── reshape.cpp ??????????????//?變形層:?在不改變數(shù)據(jù)的情況下,改變輸入的維度
          ├──?reshape.h
          ├──?rnn.cpp???????????????????//?rnn?循環(huán)神經(jīng)網(wǎng)絡(luò)
          ├──?rnn.h
          ├── roipooling.cpp ???????????// faster Rcnn 獨(dú)有的層, ROI池化層:?輸入m*n?均勻劃分成?a*b個(gè)格子后池化,得到固定長(zhǎng)度的特征向量?==========
          ├──?roipooling.h
          ├──?scale.cpp?????????????????//?bn?層之后的?平移縮放層
          ├──?scale.h
          ├──?shufflechannel.cpp????????//?ShuffleNet?獨(dú)有的層,通道打亂,通道混合層=================================
          ├──?shufflechannel.h
          ├──?sigmoid.cpp???????????????//?負(fù)指數(shù)倒數(shù)歸一化層??1/(1?+?e^(-zi))
          ├──?sigmoid.h
          ├──?slice.cpp?????????????????//?concat的反向操作,?通道分開(kāi)層,適用于多任務(wù)網(wǎng)絡(luò)
          ├──?slice.h
          ├──?softmax.cpp???????????????//?指數(shù)求和歸一化層??e^(zi)?/?sum(e^(zi))
          ├──?softmax.h
          ├── split.cpp ????????????????//?將blob復(fù)制幾份,分別給不同的layer,這些上層layer共享這個(gè)blob。
          ├──?split.h
          ├──?spp.cpp???????????????????//?空間金字塔池化層?1+4+16=21?SPP-NET?獨(dú)有===================================
          ├──?spp.h
          ├── squeeze.cpp ??????????????// squeezeNet獨(dú)有層, Fire Module, 一層conv層變成兩層:squeeze層+expand層, 1*1卷積--->?1*1?+?3*3=======
          ├──?squeeze.h
          ├──?tanh.cpp??????????????????//?雙曲正切激活函數(shù)??(e^(zi)?-?e^(-zi))?/?(e^(zi)?+?e^(-zi))
          ├──?tanh.h
          ├──?threshold.cpp?????????????//?閾值函數(shù)層
          ├──?threshold.h
          ├── tile.cpp ?????????????????//?將blob的某個(gè)維度,擴(kuò)大n倍。比如原來(lái)是1234,擴(kuò)大兩倍變成11223344。
          ├──?tile.h
          ├──?unaryop.cpp???????????????//?一元操作:?abs,?sqrt,?exp,?sin,?cos,conj(共軛)等
          ├──?unaryop.h
          |
          |==============================x86下特殊的優(yōu)化層=====
          ├──?x86
          │???├──?avx_mathfun.h????????????????????//?x86?數(shù)學(xué)函數(shù)
          │???├──?convolution_1x1.h????????????????//?1*1?float32?卷積
          │???├──?convolution_1x1_int8.h???????????//?1×1?int8?卷積
          │???├──?convolution_3x3.h????????????????//?3*3?float32?卷積
          │???├──?convolution_3x3_int8.h???????????//?3×3?int8?卷積
          │???├──?convolution_5x5.h????????????????//?5*5?float32?卷積?
          │???├──?convolutiondepthwise_3x3.h???????//?3*3?float32?逐通道卷積
          │???├──?convolutiondepthwise_3x3_int8.h??//?3*3?int8?逐通道卷積
          │???├──?convolutiondepthwise_x86.cpp?????//??逐通道卷積
          │???├──?convolutiondepthwise_x86.h
          │???├──?convolution_x86.cpp??????????????//??卷積
          │???├──?convolution_x86.h
          │???└──?sse_mathfun.h????????????????????//?sse優(yōu)化?數(shù)學(xué)函數(shù)
          ├──?yolodetectionoutput.cpp??????????????//?yolo-v2?目標(biāo)檢測(cè)輸出層=========================================
          └──?yolodetectionoutput.h

          當(dāng)然還有一些支持的層沒(méi)有列舉到,具體以源碼為準(zhǔn)。

          0x04. NCNN是如何加速的?

          之所以要單獨(dú)列出這部分,是因?yàn)镹CNN作為一個(gè)前向推理框架,推理速度肯定是尤其重要的。所以這一節(jié)我就來(lái)科普一下NCNN為了提升網(wǎng)絡(luò)的運(yùn)行速度做了哪些關(guān)鍵優(yōu)化。我們需要明確一點(diǎn),當(dāng)代CNN的計(jì)算量主要集中在卷積操作上,只要卷積層的速度優(yōu)化到位,那么整個(gè)網(wǎng)絡(luò)的運(yùn)行速度就能獲得極大提升。所以,我們這里先以卷積層為例來(lái)講講NCNN是如何優(yōu)化的。

          在講解之前,先貼出我前面很長(zhǎng)一段時(shí)間學(xué)習(xí)的一些優(yōu)化策略和復(fù)現(xiàn)相關(guān)的文章鏈接,因?yàn)檫@些思路至少一半來(lái)自于NCNN,所以先把鏈接匯總在這里,供需要的小伙伴獲取。

          NCNN中對(duì)卷積的加速過(guò)程(以Arm側(cè)為例)在我看來(lái)有:

          • 無(wú)優(yōu)化
          • 即用即取+共用行
          • Im2Col+GEMM
          • WinoGrad
          • SIMD
          • 內(nèi)聯(lián)匯編
          • 針對(duì)特定架構(gòu)如A53和A55提供更好的指令排布方式,不斷提高硬件利用率

          后面又加入了Pack策略,更好的改善訪存,進(jìn)一步提升速度。

          不得不說(shuō),NCNN的底層優(yōu)化做得還是比較細(xì)致的,所以大家一定要去白嫖 啊。這里列舉的是Arm的優(yōu)化策略,如果是x86或者其它平臺(tái)以實(shí)際代碼為準(zhǔn)。

          下面貼一個(gè)帶注釋的ARM neon優(yōu)化絕對(duì)值層的例子作為結(jié)束吧,首先絕對(duì)值層的普通C++版本如下:

          //?絕對(duì)值層特性:?單輸入,單輸出,可直接對(duì)輸入進(jìn)行修改
          int?AbsVal::forward_inplace(Mat&?bottom_top_blob,?const?Option&?opt)?const
          {
          ????int?w?=?bottom_top_blob.w;???//?矩陣寬度
          ????int?h?=?bottom_top_blob.h;????//?矩陣高度
          ????int?channels?=?bottom_top_blob.c;//?通道數(shù)
          ????int?size?=?w?*?h;//?一個(gè)通道的元素?cái)?shù)量

          ????#pragma?omp?parallel?for?num_threads(opt.num_threads)??//?openmp?并行
          ????for?(int?q=0;?q//?每個(gè)?通道
          ????{
          ????????float*?ptr?=?bottom_top_blob.channel(q);//?當(dāng)前通道數(shù)據(jù)的起始指針

          ????????for?(int?i=0;?i//?遍歷每個(gè)值
          ????????{
          ????????????if?(ptr[i]?0)
          ????????????????ptr[i]?=?-ptr[i];//?小于零取相反數(shù),大于零保持原樣
          ????????????//?ptr[i]?=?ptr[i]?>?0???ptr[i]?:?-ptr[i];
          ????????}
          ????}

          ????return?0;
          }

          ARM neon優(yōu)化版本如下:

          //??arm?內(nèi)聯(lián)匯編
          //?asm(
          //?代碼列表
          //?:?輸出運(yùn)算符列表????????"r"?表示同用寄存器??"m"?表示內(nèi)存地址?"I"?立即數(shù)?
          //?:?輸入運(yùn)算符列表????????"=r"?修飾符?=?表示只寫,無(wú)修飾符表示只讀,+修飾符表示可讀可寫,&修飾符表示只作為輸出
          //?:?被更改資源列表
          //?);
          //?__asm__?__volatile__();?
          //?__volatile__或volatile?是可選的,假如用了它,則是向GCC?聲明不答應(yīng)對(duì)該內(nèi)聯(lián)匯編優(yōu)化,
          //?否則當(dāng)?使用了優(yōu)化選項(xiàng)(-O)進(jìn)行編譯時(shí),GCC 將會(huì)根據(jù)自己的判定決定是否將這個(gè)內(nèi)聯(lián)匯編表達(dá)式中的指令優(yōu)化掉。

          //?換行符和制表符的使用可以使得指令列表看起來(lái)變得美觀。
          int?AbsVal_arm::forward_inplace(Mat&?bottom_top_blob,?const?Option&?opt)?const
          {
          ????int?w?=?bottom_top_blob.w;???//?矩陣寬度
          ????int?h?=?bottom_top_blob.h;????//?矩陣高度
          ????int?channels?=?bottom_top_blob.c;//?通道數(shù)
          ????int?size?=?w?*?h;//?一個(gè)通道的元素?cái)?shù)量

          ????#pragma?omp?parallel?for?num_threads(opt.num_threads)
          ????for?(int?q=0;?q????{
          ????????float*?ptr?=?bottom_top_blob.channel(q);

          #if?__ARM_NEON
          ????????int?nn?=?size?>>?2;?//?128位的寄存器,一次可以操作?4個(gè)float,剩余不夠4個(gè)的,最后面直接c語(yǔ)言執(zhí)行
          ????????int?remain?=?size?-?(nn?<2);//?4*32?=128字節(jié)對(duì)其后?剩余的?float32個(gè)數(shù),?剩余不夠4個(gè)的數(shù)量?
          #else
          ????????int?remain?=?size;
          #endif?//?__ARM_NEON

          /*
          從內(nèi)存中載入:
          v7:
          ???帶了前綴v的就是v7 32bit指令的標(biāo)志;
          ?? ld1表示是順序讀取,還可以取ld2就是跳一個(gè)讀取,ld3、ld4就是跳3、4個(gè)位置讀取,這在RGB分解的時(shí)候賊方便;
          ???后綴是f32表示單精度浮點(diǎn),還可以是s32、s16表示有符號(hào)的32、16位整型值。
          ???這里Q寄存器是用q表示,q5對(duì)應(yīng)d10、d11可以分開(kāi)單獨(dú)訪問(wèn)(注:v8就沒(méi)這么方便了。)
          ???大括號(hào)里面最多只有兩個(gè)Q寄存器。

          ?????"vld1.f32???{q10},?[%3]!????????\n"
          ?????"vld1.s16?{q0,?q1},?[%2]!???????\n"?


          v8:
          ??ARMV8(64位cpu)?NEON寄存器?用?v來(lái)表示?v1.8b?v2.8h??v3.4s?v4.2d
          ??后綴為8b/16b/4h/8h/2s/4s/2d)
          ??大括號(hào)內(nèi)最多支持4個(gè)V寄存器;

          ??"ld1????{v0.4s,?v1.4s,?v2.4s,?v3.4s},?[%2],?#64?\n"???//?4s表示float32
          ??"ld1????{v0.8h,?v1.8h},?[%2],?#32?????\n"
          ??"ld1????{v0.4h,?v1.4h},?[%2],?#32?????\n"?????????????//?4h?表示int16

          */


          #if?__ARM_NEON
          #if?__aarch64__
          //?ARMv8-A?是首款64?位架構(gòu)的ARM?處理器,是移動(dòng)手機(jī)端使用的CPU
          ????????if?(nn?>?0)
          ????????{
          ????????asm?volatile(
          ????????????"0:???????????????????????????????\n"???//?0:?作為標(biāo)志,局部標(biāo)簽
          ????????????"prfm???????pldl1keep,?[%1,?#128]?\n"???//??預(yù)取?128個(gè)字節(jié)?4*32?=?128
          ????????????"ld1????????{v0.4s},?[%1]?????????\n"???//??載入?ptr?指針對(duì)應(yīng)的值,連續(xù)4個(gè)
          ????????????"fabs???????v0.4s,?v0.4s??????????\n"???//??ptr?指針對(duì)應(yīng)的值?連續(xù)4個(gè),使用fabs函數(shù)?進(jìn)行絕對(duì)值操作?4s表示浮點(diǎn)數(shù)
          ????????????"subs???????%w0,?%w0,?#1??????????\n"???//??%0?引用?參數(shù)?nn?操作次數(shù)每次?-1??#1表示1
          ????????????"st1????????{v0.4s},?[%1],?#16????\n"???//??%1?引用?參數(shù)?ptr?指針?向前移動(dòng)?4*4=16字節(jié)
          ????????????"bne????????0b????????????????????\n"???//?如果非0,則向后跳轉(zhuǎn)到?0標(biāo)志處執(zhí)行
          ????????????:?"=r"(nn),?????//?%0?操作次數(shù)
          ??????????????"=r"(ptr)?????//?%1
          ????????????:?"0"(nn),??????//?%0?引用?參數(shù)?nn
          ??????????????"1"(ptr)???????//?%1?引用?參數(shù)?ptr
          ????????????:?"cc",?"memory",?"v0"?/*?可能變化的部分?memory內(nèi)存可能變化*/
          ????????)
          ;
          ????????}
          #else
          //?32位?架構(gòu)處理器=========
          ????????if?(nn?>?0)
          ????????{
          ????????asm?volatile(
          ????????????"0:?????????????????????????????\n"???//?0:?作為標(biāo)志,局部標(biāo)簽
          ????????????"vld1.f32???{d0-d1},?[%1]???????\n"???//?載入?ptr處的值??q0寄存器?=?d0?=?d1
          ????????????"vabs.f32???q0,?q0??????????????\n"???//?abs?絕對(duì)值運(yùn)算
          ????????????"subs???????%0,?#1??????????????\n"???//??%0?引用?參數(shù)?nn?操作次數(shù)每次?-1??#1表示1
          ????????????"vst1.f32???{d0-d1},?[%1]!??????\n"???//?%1?引用?參數(shù)?ptr?指針?向前移動(dòng)?4*4=16字節(jié)
          ????????????"bne????????0b??????????????????\n"???//?如果非0,則向后跳轉(zhuǎn)到?0標(biāo)志處執(zhí)行
          ????????????:?"=r"(nn),?????//?%0
          ??????????????"=r"(ptr)?????//?%1
          ????????????:?"0"(nn),
          ??????????????"1"(ptr)
          ????????????:?"cc",?"memory",?"q0"?????????????????/*?可能變化的部分?memory內(nèi)存可能變化*/
          ????????)
          ;
          ????????}
          #endif?//?__aarch64__
          #endif?//?__ARM_NEON
          ????????for?(;?remain>0;?remain--)?//?剩余不夠4個(gè)的直接c語(yǔ)言執(zhí)行
          ????????{
          ????????????*ptr?=?*ptr?>?0???*ptr?:?-*ptr;

          ????????????ptr++;
          ????????}
          ????}

          ????return?0;
          }

          0x05. 結(jié)語(yǔ)

          介紹到這里就要結(jié)束了,這篇文章只是以我自己的視角看了一遍NCNN,如果有什么錯(cuò)誤或者筆誤歡迎評(píng)論區(qū)指出。在NCNN之后各家廠商紛紛推出了自己的開(kāi)源前向推理框架,例如OpenAILab的Tengine,阿里的MNN,曠視的MegEngine,華為Bolt等等,希望各個(gè)CVer都能多多支持國(guó)產(chǎn)端側(cè)推理框架。

          0x06. 友情鏈接

          • https://github.com/Tencent/ncnn
          • https://github.com/MegEngine/MegEngine
          • https://github.com/alibaba/tengine
          • https://github.com/OAID/Tengine
          • https://github.com/alibaba/MNN
          • https://github.com/Ewenwan/MVision

          為了感謝讀者的長(zhǎng)期支持,今天我們將送出三本由 人民郵電出版社?提供的:《Python修煉之道 數(shù)據(jù)處理與機(jī)器學(xué)習(xí)實(shí)戰(zhàn)》 。點(diǎn)擊下方抽獎(jiǎng)助手參與抽獎(jiǎng)。沒(méi)抽到并且對(duì)本書有興趣的也可以使用下方鏈接進(jìn)行購(gòu)買。

          《Python高手修煉之道》抽獎(jiǎng)鏈接


          歡迎關(guān)注GiantPandaCV, 在這里你將看到獨(dú)家的深度學(xué)習(xí)分享,堅(jiān)持原創(chuàng),每天分享我們學(xué)習(xí)到的新鮮知識(shí)。( ? ?ω?? )?

          有對(duì)文章相關(guān)的問(wèn)題,或者想要加入交流群,歡迎添加BBuf微信:

          二維碼

          為了方便讀者獲取資料以及我們公眾號(hào)的作者發(fā)布一些Github工程的更新,我們成立了一個(gè)QQ群,二維碼如下,感興趣可以加入。

          公眾號(hào)QQ交流群


          瀏覽 64
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日美女逼视频在线播放 | 青娱乐亚洲精品视频在线观看 | 日本一道本高清在线一区二区 | 国产色情 免费 | 男女AA免费视频 |