树莓派4B上基于MNN进行深度学习模型推理

MNN是一个轻量级的深度神经网络推理引擎,在端侧加载深度神经网络模型进行推理预测。目前,MNN已经在阿里巴巴的手机淘宝、手机天猫、优酷等20多个App中使用,覆盖直播、短视频、搜索推荐、商品图像搜索、互动营销、权益发放、安全风控等场景。此外,IoT等场景下也有若干应用。

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

环境

  • 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之前需要确认已经安装makegccg++,用make -v | gcc -v | g++ -v可查看是否已经安装,如果没有安装用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支持TensorflowCaffeONNX等主流模型文件格式,支持CNNRNNGAN等常用网络。
支持 149 个TensorflowOp、47 个CaffeOp、74 个ONNX Op;各计算设备支持的MNN Op数:CPU110个,Metal 55个,OpenCL 29个,Vulkan31个。

导出ONNX模型

由于Depth-Wise模型拥有更小的参数量,更适用于边缘端的计算需求,这里以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
    )

转换为MNN模型

转换onnx模型为mnn模型:

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

参数说明:

Usage:
  MNNConvert [OPTION...]

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

  -v, --version                 显示当前转换器版本
  
  -f, --framework arg           需要进行转换的模型类型, ex: [TF,CAFFE,ONNX,TFLITE,MNN]
  
      --modelFile arg           需要进行转换的模型文件名, ex: *.pb,*caffemodel
      
      --prototxt arg            caffe模型结构描述文件, ex: *.prototxt
      
      --MNNModel arg            转换之后保存的MNN模型文件名, ex: *.mnn
      
      --fp16                    将conv/matmul/LSTM的float32参数保存为float16,
                                模型将减小一半,精度基本无损
      
      --benchmarkModel          不保存模型中conv/matmul/BN等层的参数,仅用于benchmark测试
      
      --bizCode arg             MNN模型Flag, ex: MNN
      
      --debug                   使用debug模型显示更多转换信息
      
      --forTraining             保存训练相关算子,如BN/Dropout,default: false
      
      --weightQuantBits arg     arg=2~8,此功能仅对conv/matmul/LSTM的float32权值进行量化,
                                仅优化模型大小,加载模型后会解码为float32,量化位宽可选2~8,
                                运行速度和float32模型一致。8bit时精度基本无损,模型大小减小4倍
                                default: 0,即不进行权值量化
      
      --compressionParamsFile arg
                                使用MNN模型压缩工具箱生成的模型压缩信息文件
                                
      --saveStaticModel         固定输入形状,保存静态模型, default: false
      
      --inputConfigFile arg     保存静态模型所需要的配置文件, 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这个指令集架构引入了新的fp16运算和 int8 dot指令,优化得当就能大幅加速深度学习框架的推理效率。由于树莓派4B的CPU内核为Cortex A72,对应的指令集架构是ARMv8-A,因此在CPU上无法实现fp16int8的加速。MNN本身的fp16只作用在参数的存储上,在计算时还是会转换成fp32送入CPU中进行计算。gpu在调用opencl后端的时候会出现ERROR CODE : -1001的错误,暂时还无法进行测试。可以看到mobilenet_v2在单线程下的速度可以达到90ms左右,多线程的速度还可以进一步加快。

Device FP32 HALF FP16
cpu 95.56ms 96.74ms 95.18ms
gpu - - -
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容