當您點進這篇文章,我想肯定不需要過多的去向您介紹華為海思35xx系列芯片的型號參數或者強大之處。另外這個教程也是建立已經配置好環境,并掌握Ruyi Studio的基本使用前提下的。如果還沒有跑過其中的一些sample,網上也有一些教程,推薦看劉山老師的博客
作者:Hanson
首發知乎
疫情期間天天打游戲,感覺一陣罪惡,就將以前的做過的東西分享一下,希望能幫助到其他人。之后也會開源一些海思上的模型和inference代碼,比如retinaface等,歡迎關注
1.簡介
海思35xx系列芯片對比起nvidia TX2、Intel Movidius神經計算棒等一眾邊緣計算產品,有其驚艷的地方,因其集成了強大的算力模塊,集成度和功能模塊齊全,最重要的是成本低,成為了安防行業的首選芯片。但是也有一些麻煩的地方,主要是在于其開發難度的提高,大家都是摸著石頭過河(3288老玩家轉行也是能體會到痛苦的)。在轉自己的模型時,坑比想象的要多,并且海思官方SDK也存在一些錯誤之處,讓人很難捉摸,所以有時候需要自己多去獨立思考。這次我記錄了在轉換人臉識別模型mobilefacenet下了比較坑的三個點,畢竟是個新玩意兒,多半是版本發布時候不統一造成的:
- CNN_convert_bin_and_print_featuremap.py 代碼出現錯誤,cfg中的【image_list】這個字段并沒有在代碼中出現,代碼中只有【image_file】,因此需要修改這一地方;
- CNN_convert_bin_and_print_featuremap.py和Get Caffe Output這里的預處理方式都是先乘以【data_scale】,再減均值【mean_file】,而在量化生成 .mk 文件時卻是先減均值再乘以scale的
- 量化需要使用多張圖片,而CNN_convert_bin_and_print_featuremap.py各層產生的feature僅僅是一張圖片,這在做【Vector Comparision】時候就難以清楚的明白到底最后mk文件是第幾張圖像
2.目錄結構
3.mobilefacenet.cfg文件的配置
可以從github上下載mxnet2caffe的mobilefacenet模型,
首先要修改mobilefacenet.prototxt的輸入層以符合NNIE caffe網絡的結構標準
更改后如下:
而量化mk使用的【mean_file】pixel_mean.txt是特別需要注意的
我從agedb_30人臉數據庫里面挑選了10張圖像來做量化處理,為什么需要多張量化,請參考文章Int8量化-介紹(一),我們選擇【10.jpg】來做 【Vector Comparision】,其實就是imageList.txt里的排列在最后的那張圖片
具體配置如下:
[prototxt_file] ./mark_prototxt/mobilefacenet_mark_nnie_20190723102335.prototxt
[caffemodel_file] ./data/face/mobilefacenet.caffemodel
[batch_num] 256
[net_type] 0
[sparse_rate] 0
[compile_mode] 0
[is_simulation] 0
[log_level] 3
[instruction_name] ./data/face/mobilefacenet_inst
[RGB_order] RGB
[data_scale] 0.0078125
[internal_stride] 16
[image_list] ./data/face/images/imageList20190723102419.txt
[image_type] 1
[mean_file] ./data/face/pixel_mean.txt
[norm_type] 5
4.生成NNIE mk模型
Start [RuyiStudio Wk NNIE Mapper] [E:/Code/nnie/windows/RuyiStudio-2.0.31/workspace/HeilsFace/mobilefacenet.cfg] HeilsFace (2019-07-23 10:48:17)
Mapper Version 1.1.2.0_B050 (NNIE_1.1) 1812171743151709
begin net parsing....
.end net parsing
begin prev optimizing....
....end prev optimizing....
begin net quantalizing(GPU)....
....................**********************************************************
WARNING: file: Inference::computeNonlinearQuantizationDelta line: 92
data containing only zeros; set max value to 1e-6.
**********************************************************
WARNING: file: Inference::computeNonlinearQuantizationDelta line: 92
data containing only zeros; set max value to 1e-6.
.......................................
end quantalizing
begin optimizing....
.end optimizing
begin NNIE[0] mem allocation....
...end NNIE[0] memory allocating
begin NNIE[0] instruction generating....
.............end NNIE[0] instruction generating
begin parameter compressing....
.end parameter compressing
begin compress index generating....
end compress index generating
begin binary code generating....
...................................................................................
...................................................................................
..................................................................................
...................................................................................
.............end binary code generating
begin quant files writing....
end quant files writing
===============E:/Code/nnie/windows/RuyiStudio-2.0.31/workspace/HeilsFace/mobilefacenet.cfg Successfully!===============
結束之后會生成:
- mobilefacenet_inst.wk文件
- mapper_quant文件夾,里面有量化輸出的結果,如圖 Fig.4.1,也就是./data/face/images/10.jpg
Fig.4.1 [image_list]./data/face/images/imageList20190723102419.txt
記住,mk量化過程在【mapper_quant】文件夾中生成的features是最后一張圖片的inference結果,這也是文章最開始說的第三個存在問題的地方
5.Vector Comparision
這一步,主要就是對比量化前后模型輸出的精度損失,最重要的就是要debug一遍CNN_convert_bin_and_print_featuremap.py
因為這個腳本里確實藏了很多雷,我們先要比較原框架原模型inference的結果與這一腳本得出來的結果是否一致,如果存在不一致的情況,需要去核查一遍原因
文章開篇說到的第一個問題點 CNN_convert_bin_and_print_featuremap.py 中加載了mobilefacenet.cfg文件,但腳本中并不存在【image_list】這個字段,取而代之的是【image_file】這個字段
生成NNIE mk中,mobliefacenet.cfg 的【image_list】:
Fig.5.1 生成NNIE mk中的mobliefacenet.cfg
CNN_convert_bin_and_print_featuremap.py 中加載.cfg代碼片段:
因此需要根據實際情況修改 mobliefacenet.cfg ,這里最好是復制一份新的,舊的用于生成NNIE wk,在復制后的mobliefacenet.cfg中修改一下:
另外,我們需要特別注意預處理這一個環節,如文章開篇所闡述的第二點
我們注意到這里,data是uint8類型的array,是先乘以了【data_scale】的,也就是說和NNIE 生成wk中的操作順序是不一致的。
(data - 128.0) 0.0078125 <==> data 0.0078125 - 1
因此這里需要做的修改就是需要將【mean_file】pixel_mean.txt修改為
修改完以上,然后直接運行代碼,將最終模型提取的features fc1_output0_128_caffe.linear.float和caffe_forward.py中的進行比對,如果以上都沒問題,可以看到結果是幾乎一致的
caffe_forward.py生成的結果:
[-0.82475293 -0.33066949 -0.9848339 2.44199681 0.41715512 0.67809981 0.29879519 1.14293635 -0.42905819 0.32940909 -1.20455348 1.01217067 0.83146936 -0.84349883 -1.49177814 -0.91509151 -1.39441037 0.00413842 0.97043389 -1.77688181 0.28639579 -1.06645989 -0.8570649 -2.09743094 -0.1394622 -1.15035641 -0.81590587 -3.93798804 -0.35600579 1.90367532 1.27935755 -2.07778478 -0.42563218 0.06624207 1.02597868 -0.52002895 -0.905873 -0.41364694 -1.40032899 -1.37654066 0.03066693 -0.18659458 -1.53931415 -0.55896652 2.42570448 -0.3044413 0.18183242 0.50442797 -2.36735368 -0.12376076 0.15200013 0.13939141 0.56305337 -0.10047323 1.50704932 0.05429612 -1.97527623 -0.75790995 1.89399767 0.56089604
-2.34883094 0.22600658 1.00399816 -0.55099922 1.77083731 0.10722937 2.21140814 0.06182361 0.03354079 0.97481596 -2.00423741 0.73168194 -1.79977489 -0.85182911 -0.06020565 -0.14835797 -1.93012297 -3.09269047 -0.60087907 -1.02915597 1.40985525 1.85411906 -1.21282506 -2.53264689 -0.63467324 -1.15255475 -0.59994221 0.21181655 1.30336523 -1.73625863 0.00861333 0.99906266 1.90666902 0.51179212 0.62143475 1.01997399 -1.65181398 1.55190873 0.43448481 -0.85371047 -0.68216199 1.28038061 0.4629558 -0.59671575 1.00122356 1.74233603 1.50384009 0.49827856 0.67030573 -1.20388556 1.00168729 -0.71768999 1.06416941 -2.55346298 -1.85579956 -2.18774438 -1.79652691 1.50856853 2.10628557 1.12313557 2.76396179 0.60242128 0.0550903 -1.31998527 -0.6896565 -0.07160443 1.21242583 -1.06733179]
CNN_convert_bin_and_print_featuremap.py生成的結果(由于特征值太多,就不一一打印出來了):
然后在生成,并進行【Vector Comparision】,量化終于成功了
6.NNIE mobilefacenet板上特征提取
做完了模型的量化,就可以進行仿真或者是在板子上進行實際測試了,這一步的坑并不是很多,主要還是得靠一些編程技巧了,建議熟悉C語言,這部分要熟悉sample代碼,如果說非常熟悉c/c++混編,也可以使用c++。
1. 修改例程
這里我參考了博客,其寫法幾乎一致,如下Fig.6.1 Fig.6.2是我所修改的代碼片段,找到smp/a7_linux/mpp/sample/svp/nnie/sample/sample_nnie.c中該函數
void SAMPLE_SVP_NNIE_Cnn(void)
只用修改了該函數的前后兩處代碼
Fig.6.1 函數開頭修改pcSrcFile和pcModeName
Fig.6.2 函數結尾增加輸出層的打印信息
我們調用了 SAMPLE_SVP_NNIE_PrintReportResult 函數輸出兩個結果報表文件,結果分析當中會用到
seg0_layer38_output0_inst.linear.hex
seg0_layer3605_output0_inst.linear.hex
整段函數代碼參見文章末尾【附錄】
2. bgr文件的生成
注意到上文中我使用了pcSrcFile,這也是例程中主流的格式bgr,那么我們一般的圖片都是.jpeg格式的,為了更好的利用NNIE,所以就需要利用opencv來轉化以下。
首先.bgr文件是可以由opencv Mat轉換的,但完成轉換代碼的編寫之前我們必須清楚像素的空間排列順序。注意,以下轉換代碼簡單采用像素復制,并沒有考慮優化,運行會比較慢!參考博客
.bgr ==> BBBBBB...GGGGGG...RRRRRR
cv::Mat ==> BGRBGRBGR...BGRBGRBGR
.bgr --> cv::Mat
Fig.6.3 .bgr 轉 mat
/*bgr格式 轉 cv::Mat代碼 */
int bgr2mat(cv::Mat& img, int width, int height, int channel, const char* pth)
{
if (pth)
{
FILE* fp;
unsigned char *img_data = NULL;
unsigned char *img_data_conv = NULL;
img_data = (unsigned char*)malloc(sizeof(unsigned char) * width * height * channel);
//unsigned char img_data[300 * 300 * 3];
img_data_conv = (unsigned char*)malloc(sizeof(unsigned char) * width * height * channel);
fp = fopen(pth, "rb");
if (!fp)
{
return 0;
}
fread(img_data, 1, width * height * channel, fp);
fclose(fp);
for (size_t k = 0; k < channel; k++)
for (size_t i = 0; i < height; i++)
for (size_t j = 0; j < width; j++)
img_data_conv[channel * (i * width + j) + k] = img_data[k * height * width + i * width + j];
img = cv::Mat(height, width, CV_8UC3, img_data_conv);
//free(img_data_conv);
//img_data_conv = NULL;
free(img_data);
img_data = NULL;
return 1;
}
return 0;
}
cv::Mat -->.bgr
Fig.6.4 mat轉.bgr
/*cv::Mat 轉 bgr格式代碼 */
int mat2bgr(cv::Mat& img, const char* bgr_path)
{
if (bgr_path)
{
FILE* fp = fopen(bgr_path, "wb");
int step = img.step;
int h = img.rows;
int w = img.cols;
int c = img.channels();
std::cout << step<< std::endl;
for (int k = 0; k < c; k++)
for (int i = 0; i < h; i++)
for (int j = 0; j < w; j++)
{
//兩種寫法
//fwrite(&img.data[i*step + j * c + k], sizeof(uint8_t), 1, fp);
fwrite(&img.data[c*(i * w + j) + k], sizeof(uint8_t), 1, fp);
}
fclose(fp);
//cv::Mat tmp;
//bgr2mat(tmp, w, h, 3, bgr_path);
//cv::imshow("tmp", tmp);
//cv::waitKey(0);
return 1;
}
return 0;
}
3. 模型額外問題
pc上運行
E:/Code/nnie/software/sample_simulator/Release/sample_simulator.exe
板上運行
/nfsroot/Hi3516CV500_SDK_V2.0.1.0/smp/a7_linux/mpp/sample/svp/nnie # ./sample_nnie_main 4
可能會出現如下(Fig.6.5,Fig.6.6)錯誤,原因是生成NNIE wk文件的mapper工具有版本要求,下面錯誤當中使用的nnie mapper 版本是V1.1.2.0,而指令仿真或者是板上的SDK是V1.2的,解決辦法就是使用nnie mapper V1.2版本重新生成一下wk模型,如(Fig.6.7),生成inst/chip.wk的時間比較久,在我機器上大概要2個小時,因為inst.wk實際上是需要進行參數壓縮和二進制代碼生成,這可能也是inst.mk比func.wk文件大的原因(如Fig.6.8),而生成func.wk的時間會比較短,建議在PC上調試的時候選擇func/simulation模型
Fig.6.5 PC運行仿真例程sample_simulator會出現該log
Fig.6.6 板上測試SDK修改的例程
Fig.6.7 改變工程依賴的NNIE版本為指定芯片
Fig.6.8 模型尺寸比較
4. 運行結果及分析
修改完sample_nnie.c中的代碼后,在宿主機上進行make,然后到海思板子上運行可執行文件即可
Fig.6.9 板上運行結果
拷貝出生成的兩個打印報表文件到Ruyi studio,進行比對測試
seg0_layer38_output0_inst.linear.hex
seg0_layer3605_output0_inst.linear.hex
如Fig.6.10,Fig.6.11,雖然說板上和仿真情況下還是會有一定的差別,但總體的誤差是比較小的,基本可以接受,如果無法接受,可以嘗試int16模型
Fig.6.10 量化模型在板子上的輸出結果和pc上的結果比對(cosine similarity > 99.6)
Fig.6.11 無量化caffe輸出與板上量化輸出比對(cosine similarity > 99.1)
7.附錄
void SAMPLE_SVP_NNIE_Cnn(void)
{
HI_CHAR *pcSrcFile = "./data/nnie_image/rgb_planar/10.bgr";
HI_CHAR *pcModelName = "./data/nnie_model/face/mobilefacenet_inst.wk";
HI_U32 u32PicNum = 1;
HI_S32 s32Ret = HI_SUCCESS;
SAMPLE_SVP_NNIE_CFG_S stNnieCfg = {0};
SAMPLE_SVP_NNIE_INPUT_DATA_INDEX_S stInputDataIdx = {0};
SAMPLE_SVP_NNIE_PROCESS_SEG_INDEX_S stProcSegIdx = {0};
/*Set configuration parameter*/
stNnieCfg.pszPic= pcSrcFile;
stNnieCfg.u32MaxInputNum = u32PicNum; //max input image num in each batch
stNnieCfg.u32MaxRoiNum = 0;
stNnieCfg.aenNnieCoreId[0] = SVP_NNIE_ID_0;//set NNIE core
s_stCnnSoftwareParam.u32TopN = 5;
/*Sys init*/
SAMPLE_COMM_SVP_CheckSysInit();
/*CNN Load model*/
SAMPLE_SVP_TRACE_INFO("Cnn Load model!/n");
s32Ret = SAMPLE_COMM_SVP_NNIE_LoadModel(pcModelName,&s_stCnnModel);
SAMPLE_SVP_CHECK_EXPR_GOTO(HI_SUCCESS != s32Ret,CNN_FAIL_0,SAMPLE_SVP_ERR_LEVEL_ERROR,
"Error,SAMPLE_COMM_SVP_NNIE_LoadModel failed!/n");
/*CNN parameter initialization*/
/*Cnn software parameters are set in SAMPLE_SVP_NNIE_Cnn_SoftwareParaInit,
if user has changed net struct, please make sure the parameter settings in
SAMPLE_SVP_NNIE_Cnn_SoftwareParaInit function are correct*/
SAMPLE_SVP_TRACE_INFO("Cnn parameter initialization!/n");
s_stCnnNnieParam.pstModel = &s_stCnnModel.stModel;
s32Ret = SAMPLE_SVP_NNIE_Cnn_ParamInit(&stNnieCfg,&s_stCnnNnieParam,&s_stCnnSoftwareParam);
SAMPLE_SVP_CHECK_EXPR_GOTO(HI_SUCCESS != s32Ret,CNN_FAIL_0,SAMPLE_SVP_ERR_LEVEL_ERROR,
"Error,SAMPLE_SVP_NNIE_Cnn_ParamInit failed!/n");
/*record tskBuf*/
s32Ret = HI_MPI_SVP_NNIE_AddTskBuf(&(s_stCnnNnieParam.astForwardCtrl[0].stTskBuf));
SAMPLE_SVP_CHECK_EXPR_GOTO(HI_SUCCESS != s32Ret,CNN_FAIL_0,SAMPLE_SVP_ERR_LEVEL_ERROR,
"Error,HI_MPI_SVP_NNIE_AddTskBuf failed!/n");
/*Fill src data*/
SAMPLE_SVP_TRACE_INFO("Cnn start!/n");
stInputDataIdx.u32SegIdx = 0;
stInputDataIdx.u32NodeIdx = 0;
s32Ret = SAMPLE_SVP_NNIE_FillSrcData(&stNnieCfg,&s_stCnnNnieParam,&stInputDataIdx);
SAMPLE_SVP_CHECK_EXPR_GOTO(HI_SUCCESS != s32Ret,CNN_FAIL_1,SAMPLE_SVP_ERR_LEVEL_ERROR,
"Error,SAMPLE_SVP_NNIE_FillSrcData failed!/n");
/*NNIE process(process the 0-th segment)*/
stProcSegIdx.u32SegIdx = 0;
s32Ret = SAMPLE_SVP_NNIE_Forward(&s_stCnnNnieParam,&stInputDataIdx,&stProcSegIdx,HI_TRUE);
SAMPLE_SVP_CHECK_EXPR_GOTO(HI_SUCCESS != s32Ret,CNN_FAIL_1,SAMPLE_SVP_ERR_LEVEL_ERROR,
"Error,SAMPLE_SVP_NNIE_Forward failed!/n");
/*Software process*/
/*if user has changed net struct, please make sure SAMPLE_SVP_NNIE_Cnn_GetTopN
function's input datas are correct*/
s32Ret = SAMPLE_SVP_NNIE_Cnn_GetTopN(&s_stCnnNnieParam,&s_stCnnSoftwareParam);
SAMPLE_SVP_CHECK_EXPR_GOTO(HI_SUCCESS != s32Ret,CNN_FAIL_1,SAMPLE_SVP_ERR_LEVEL_ERROR,
"Error,SAMPLE_SVP_NNIE_CnnGetTopN failed!/n");
/*Print result*/
SAMPLE_SVP_TRACE_INFO("Cnn result:/n");
s32Ret = SAMPLE_SVP_NNIE_Cnn_PrintResult(&(s_stCnnSoftwareParam.stGetTopN),
s_stCnnSoftwareParam.u32TopN);
SAMPLE_SVP_CHECK_EXPR_GOTO(HI_SUCCESS != s32Ret,CNN_FAIL_1,SAMPLE_SVP_ERR_LEVEL_ERROR,
"Error,SAMPLE_SVP_NNIE_Cnn_PrintResult failed!/n");
/*Print results*/
{
printf("features:/n{/n");
printf("stride: %d/n",s_stCnnNnieParam.astSegData[0].astDst[0].u32Stride);
printf("blob type :%d/n",s_stCnnNnieParam.astSegData[0].astDst[0].enType);
printf("{/n/tc :%d", s_stCnnNnieParam.astSegData[0].astDst[0].unShape.stWhc.u32Chn);
printf("/n/th :%d", s_stCnnNnieParam.astSegData[0].astDst[0].unShape.stWhc.u32Height);
printf("/n/tw :%d /n}/n", s_stCnnNnieParam.astSegData[0].astDst[0].unShape.stWhc.u32Width);
HI_S32* ps32Score = (HI_S32* )((HI_U8* )s_stCnnNnieParam.astSegData[0].astDst[0].u64VirAddr);
printf("blobs fc1:/n[");
for(HI_U32 i = 0; i < 128; i++)
{
printf("%f ,",*(ps32Score + i) / 4096.f);
}
printf("]/n}/n");
}
s32Ret = SAMPLE_SVP_NNIE_PrintReportResult(&s_stCnnNnieParam);
SAMPLE_SVP_CHECK_EXPR_GOTO(HI_SUCCESS != s32Ret, CNN_FAIL_1, SAMPLE_SVP_ERR_LEVEL_ERROR,"Error,SAMPLE_SVP_NNIE_PrintReportResult failed!");
CNN_FAIL_1:
/*Remove TskBuf*/
s32Ret = HI_MPI_SVP_NNIE_RemoveTskBuf(&(s_stCnnNnieParam.astForwardCtrl[0].stTskBuf));
SAMPLE_SVP_CHECK_EXPR_GOTO(HI_SUCCESS != s32Ret,CNN_FAIL_0,SAMPLE_SVP_ERR_LEVEL_ERROR,
"Error,HI_MPI_SVP_NNIE_RemoveTskBuf failed!/n");
CNN_FAIL_0:
SAMPLE_SVP_NNIE_Cnn_Deinit(&s_stCnnNnieParam,&s_stCnnSoftwareParam,&s_stCnnModel);
SAMPLE_COMM_SVP_CheckSysExit();
}
更多模型芯片端部署,請關注嵌入式AI專欄
審核編輯 黃昊宇
-
嵌入式系統
+關注
關注
41文章
3679瀏覽量
131336
發布評論請先 登錄
Deepseek海思SD3403邊緣計算AI產品系統
基于帶NNIE神經網絡海思3559A方案邊緣計算主板開發及接口定義
深圳專業回收海思IC 收購海思芯片
海思芯片大量收購,高價回收海思IC
潤和海思3559A開發版例程編譯
潤和海思3559A開發版nnie例程運行過程中系統崩潰
帶你玩轉OpenHarmony AI-基于海思NNIE的AI能力自定義
海思AI芯片方案學習(二十三)nnie上進行圖像數據預處理(Normalize)的五種方式

海思AI芯片方案學習(二十二)如何在ubuntu18.0.4上跑通nnie mapper

海思AI芯片(Hi3519A/3559A)方案學習(十五)基于nnie引擎進行推理的仿真代碼淺析

海思AI芯片學習(十)將yolov3 darknet模型轉換為caffemodel

海思NNIE之PFPLD訓練與量化

海思NNIE之RetinaFace量化部署

評論