樹莓派4B上基于MNN進行深度學習模型推理

MNN是一個輕量級的深度神經(jīng)網(wǎng)絡(luò)推理引擎,在端側(cè)加載深度神經(jīng)網(wǎng)絡(luò)模型進行推理預測。目前罗珍,MNN已經(jīng)在阿里巴巴的手機淘寶、手機天貓脚粟、優(yōu)酷等20多個App中使用覆旱,覆蓋直播、短視頻核无、搜索推薦扣唱、商品圖像搜索、互動營銷团南、權(quán)益發(fā)放噪沙、安全風控等場景。此外吐根,IoT等場景下也有若干應用正歼。

Documentshttps://www.yuque.com/mnn/en/about
Githubhttps://github.com/alibaba/MNN
Githubhttps://github.com/xiaochus/DeepModelDeploy/tree/main/MNN/cpp

環(huán)境

  • Python 3.7
  • Pytorch 1.7
  • GCC 9.3
  • CMAKE 3.9.1
  • Protobuf 3.14
  • OpenCV 4.5 + Contrib
  • MNN 1.1.0

C++依賴庫安裝:

由于樹莓派平臺是基于ARM芯片的,很多庫都要從源碼編譯拷橘,不能直接使用二進制文件局义。

CMAKE

下載cmake https://cmake.org/

安裝 cmake之前需要確認已經(jīng)安裝make喜爷、gccg++萄唇,用make -v | gcc -v | g++ -v可查看是否已經(jīng)安裝檩帐,如果沒有安裝用apt-get安裝一下:

sudo apt-get install gcc
sudo apt-get install g++
sudo apt-get install make

安裝openSSL

sudo apt-get install openssl
sudo apt-get install libssl-dev

編譯安裝:

./bootstrap
make -j4
sudo make install

查看版本確定成功安裝:

cmake --version
cmake

Protobuf

安裝依賴包:

sudo apt-get install autoconf automake libtool curl unzip

編譯安裝:

./autogen.sh
./configure
make -j4
make check
sudo make install
sudo ldconfig # refresh shared library cache.

查看版本確定成功安裝:

protoc --version
Protobuf

OpenCV

安裝依賴包:

sudo apt-get install build-essential    
sudo apt-get install git libgtk2.0-dev pkg-config libavcodec-dev libavformat-dev libswscale-dev 
sudo apt-get install libtbb2 libtbb-dev libjpeg-dev libpng-dev libtiff-dev libjasper-dev libdc1394-22-dev

libjasper-dev 安裝失敗解決方案:

sudo add-apt-repository "deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ xenial main multiverse restricted universe"
sudo apt update
sudo apt install libjasper1 libjasper-dev

編譯安裝:

mkdir build
sudo cmake -D CMAKE_BUILD_TYPE=Release -D CMAKE_INSTALL_PREFIX=/usr/local -D OPENCV_EXTRA_MODULES_PATH=/home/ubuntu/opencv-4.5.0/opencv_contrib-4.5.0/modules/ ..
sudo make install -j4

MNN

樹莓派4B有一塊500MHzBroadcom VideoCore IV GPU,因此MNN可以嘗試使用OpenCL進行加速:

sudo apt-get install ocl-icd-opencl-dev

編輯CMakeLists另萤,打開OpenCL選項湃密。

option(MNN_OPENCL "Enable OpenCL" ON)

編譯MNN

./schema/generate.sh
mkdir build
cd build
cmake .. -DMNN_BUILD_CONVERTER=true -DMNN_SEP_BUILD=false && make -j4  

模型推理

MNN支持TensorflowCaffe四敞、ONNX等主流模型文件格式勾缭,支持CNNRNN目养、GAN等常用網(wǎng)絡(luò)。
支持 149 個TensorflowOp毒嫡、47 個CaffeOp癌蚁、74 個ONNX Op;各計算設(shè)備支持的MNN Op數(shù):CPU110個兜畸,Metal 55個努释,OpenCL 29個,Vulkan31個咬摇。

導出ONNX模型

由于Depth-Wise模型擁有更小的參數(shù)量伐蒂,更適用于邊緣端的計算需求,這里以imageNet1000分類的mobilenetV2模型為例子肛鹏。

import torch
import torch.onnx as onnx
import torchvision.models as models


if __name__ == '__main__':
    net = models.mobilenet_v2()
    net.eval()

    dummy_input = torch.zeros((1, 3, 224, 224))
    input_names = ["input"]
    outout_names = ["output"]
    onnx.export(net,
                dummy_input,
                "mobilenet_v2.onnx",
                verbose=True,
                opset_version=11,
                input_names=input_names,
                output_names=outout_names,
                dynamic_axes=None
    )

轉(zhuǎn)換為MNN模型

轉(zhuǎn)換onnx模型為mnn模型:

./build/MNNConvert -f ONNX --modelFile mobilenet_v2.onnx --MNNModel mobilenet_v2.mnn --bizCode biz

參數(shù)說明:

Usage:
  MNNConvert [OPTION...]

  -h, --help                    Convert Other Model Format To MNN Model

  -v, --version                 顯示當前轉(zhuǎn)換器版本
  
  -f, --framework arg           需要進行轉(zhuǎn)換的模型類型, ex: [TF,CAFFE,ONNX,TFLITE,MNN]
  
      --modelFile arg           需要進行轉(zhuǎn)換的模型文件名, ex: *.pb,*caffemodel
      
      --prototxt arg            caffe模型結(jié)構(gòu)描述文件, ex: *.prototxt
      
      --MNNModel arg            轉(zhuǎn)換之后保存的MNN模型文件名, ex: *.mnn
      
      --fp16                    將conv/matmul/LSTM的float32參數(shù)保存為float16逸邦,
                                模型將減小一半,精度基本無損
      
      --benchmarkModel          不保存模型中conv/matmul/BN等層的參數(shù)在扰,僅用于benchmark測試
      
      --bizCode arg             MNN模型Flag, ex: MNN
      
      --debug                   使用debug模型顯示更多轉(zhuǎn)換信息
      
      --forTraining             保存訓練相關(guān)算子缕减,如BN/Dropout,default: false
      
      --weightQuantBits arg     arg=2~8芒珠,此功能僅對conv/matmul/LSTM的float32權(quán)值進行量化桥狡,
                                僅優(yōu)化模型大小,加載模型后會解碼為float32皱卓,量化位寬可選2~8裹芝,
                                運行速度和float32模型一致。8bit時精度基本無損娜汁,模型大小減小4倍
                                default: 0嫂易,即不進行權(quán)值量化
      
      --compressionParamsFile arg
                                使用MNN模型壓縮工具箱生成的模型壓縮信息文件
                                
      --saveStaticModel         固定輸入形狀,保存靜態(tài)模型掐禁, default: false
      
      --inputConfigFile arg     保存靜態(tài)模型所需要的配置文件, ex: ~/config.txt
convert

C++ API 推理

code:

#include <iostream>
#include <string>
#include <vector>
#include <cstring>
#include <chrono>
#include <cmath>

#include <opencv2/opencv.hpp>
#include "MNN/Interpreter.hpp"

using namespace std;
using namespace MNN;


int main() {
    string testImagePath = "/home/ubuntu/MNN/test.png";

    string modelFile = "/home/ubuntu/mobilenet_v2.mnn";
    string mode = "fp16";
    string deviceType = "gpu";
    int numThread = 1;

    // build network
    Interpreter* net = Interpreter::createFromFile(modelFile.c_str());

    // build config
    ScheduleConfig config;

    // set cpu thread used
    config.numThread = numThread;

    // set host device
    if (deviceType == "cpu")
        config.type = static_cast<MNNForwardType>(MNN_FORWARD_CPU);
    if (deviceType == "gpu")
        config.type = static_cast<MNNForwardType>(MNN_FORWARD_OPENCL);

    // set precision
    BackendConfig backendConfig;
    if (mode == "fp16")
        backendConfig.precision = static_cast<BackendConfig::PrecisionMode>(BackendConfig::Precision_Low);
    if(mode == "half")
        backendConfig.precision = static_cast<BackendConfig::PrecisionMode>(BackendConfig::Precision_Normal);
    if (mode == "fp32")
        backendConfig.precision = static_cast<BackendConfig::PrecisionMode>(BackendConfig::Precision_High);

    // set power use
    backendConfig.power = static_cast<BackendConfig::PowerMode>(BackendConfig::Power_Normal);
    // set memory use
    backendConfig.memory = static_cast<BackendConfig::MemoryMode>(BackendConfig::Memory_Normal);

    config.backendConfig = &backendConfig;

    // build session use config
    Session* session = net->createSession(config);

    // get input and output node of network
    Tensor* modelInputTensor = net->getSessionInput(session, NULL);
    Tensor* modelOutputTensor = net->getSessionOutput(session, NULL);

    // image preprocess
    cv::Scalar mean = {0.485, 0.456, 0.406};
    cv::Scalar stdv = {1 /0.229, 1 / 0.224, 1 / 0.225};

    cv::Mat img = cv::imread(testImagePath);

    cv::resize(img, img, cv::Size(224, 224));
    img.convertTo(img, CV_32F, 1 / 255.0);
    img = ((img - mean) / stdv);

    Tensor* inputTensor = Tensor::create<float>({1, 224, 224, 3}, NULL, Tensor::TENSORFLOW);
    Tensor* outputTensor = Tensor::create<float>({1, 1000}, NULL, Tensor::CAFFE);

    memcpy(inputTensor->host<float>(), img.data, inputTensor->size());

    // inference
    auto start = chrono::high_resolution_clock::now();

    modelInputTensor->copyFromHostTensor(inputTensor);
    net->runSession(session);
    modelOutputTensor->copyToHostTensor(outputTensor);

    auto end = chrono::high_resolution_clock::now();
    double cost = chrono::duration<double, milli>(end - start).count();

    cout << "device: " << deviceType << ", mode: " << mode << endl;
    cout << "inference time: " << to_string(cost) << "ms" << endl;

    // post-process
    vector<float> confidence;
    confidence.resize(1000);
    memcpy(confidence.data(), outputTensor->host<float>(), outputTensor->size());

    // delete point
    delete inputTensor;
    delete outputTensor;
    delete net;

    return 0;
}

CMake:

cmake_minimum_required(VERSION 3.17)
project(MNN)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")

find_package(OpenCV REQUIRED)

if(OpenCV_FOUND)
    message(STATUS "OpenCV library: ${OpenCV_INSTALL_PATH}")
    message(STATUS "    version: ${OpenCV_VERSION}")
    message(STATUS "    libraries: ${OpenCV_LIBS}")
    message(STATUS "    include path: ${OpenCV_INCLUDE_DIRS}")
else()
    message(FATAL_ERROR "Error! OpenCV not found!")

    set(OpenCV_INCLUDE_DIRS "/usr/local/include/opencv4")
    set(OpenCV_LIBS "/usr/local/lib")
endif()

set(MNN_INCLUDE_DIR "/home/ubuntu/MNN-1.1.0/include")
set(MNN_LIBRARIES "/home/ubuntu/MNN-1.1.0/build/libMNN.so")

message(STATUS "  MNN  libraries: ${MNN_LIBRARIES}")
message(STATUS "  MNN  include path: ${MNN_INCLUDE_DIR}")

add_executable(MNN main.cpp)

target_include_directories(MNN PUBLIC
        ${OpenCV_INCLUDE_DIRS}
        ${MNN_INCLUDE_DIR}
        )
target_link_libraries(MNN PUBLIC
        ${MNN_LIBRARIES}
        ${OpenCV_LIBS}
)

編譯:

mkdir build
cd build
sudo cmake .. & make -j4

性能對比:

ARMv8.2這個指令集架構(gòu)引入了新的fp16運算和 int8 dot指令炬搭,優(yōu)化得當就能大幅加速深度學習框架的推理效率蜈漓。由于樹莓派4B的CPU內(nèi)核為Cortex A72,對應的指令集架構(gòu)是ARMv8-A宫盔,因此在CPU上無法實現(xiàn)fp16int8的加速融虽。MNN本身的fp16只作用在參數(shù)的存儲上,在計算時還是會轉(zhuǎn)換成fp32送入CPU中進行計算灼芭。gpu在調(diào)用opencl后端的時候會出現(xiàn)ERROR CODE : -1001的錯誤有额,暫時還無法進行測試”吮粒可以看到mobilenet_v2在單線程下的速度可以達到90ms左右巍佑,多線程的速度還可以進一步加快。

Device FP32 HALF FP16
cpu 95.56ms 96.74ms 95.18ms
gpu - - -
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末寄悯,一起剝皮案震驚了整個濱河市萤衰,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌猜旬,老刑警劉巖脆栋,帶你破解...
    沈念sama閱讀 211,290評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異洒擦,居然都是意外死亡椿争,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評論 2 385
  • 文/潘曉璐 我一進店門熟嫩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來秦踪,“玉大人,你說我怎么就攤上這事掸茅∫蔚耍” “怎么了?”我有些...
    開封第一講書人閱讀 156,872評論 0 347
  • 文/不壞的土叔 我叫張陵昧狮,是天一觀的道長希坚。 經(jīng)常有香客問我,道長陵且,這世上最難降的妖魔是什么裁僧? 我笑而不...
    開封第一講書人閱讀 56,415評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮慕购,結(jié)果婚禮上聊疲,老公的妹妹穿的比我還像新娘。我一直安慰自己沪悲,他們只是感情好获洲,可當我...
    茶點故事閱讀 65,453評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著殿如,像睡著了一般贡珊。 火紅的嫁衣襯著肌膚如雪最爬。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,784評論 1 290
  • 那天门岔,我揣著相機與錄音爱致,去河邊找鬼。 笑死寒随,一個胖子當著我的面吹牛糠悯,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播妻往,決...
    沈念sama閱讀 38,927評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼互艾,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了讯泣?” 一聲冷哼從身側(cè)響起纫普,我...
    開封第一講書人閱讀 37,691評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎好渠,沒想到半個月后昨稼,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,137評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡晦墙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,472評論 2 326
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了肴茄。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片晌畅。...
    茶點故事閱讀 38,622評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖寡痰,靈堂內(nèi)的尸體忽然破棺而出抗楔,到底是詐尸還是另有隱情,我是刑警寧澤拦坠,帶...
    沈念sama閱讀 34,289評論 4 329
  • 正文 年R本政府宣布连躏,位于F島的核電站,受9級特大地震影響贞滨,放射性物質(zhì)發(fā)生泄漏入热。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,887評論 3 312
  • 文/蒙蒙 一晓铆、第九天 我趴在偏房一處隱蔽的房頂上張望勺良。 院中可真熱鬧,春花似錦骄噪、人聲如沸尚困。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽事甜。三九已至谬泌,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間逻谦,已是汗流浹背掌实。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留跨跨,地道東北人潮峦。 一個月前我還...
    沈念sama閱讀 46,316評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像勇婴,于是被迫代替她去往敵國和親忱嘹。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,490評論 2 348

推薦閱讀更多精彩內(nèi)容