从端到端打通模型端侧部署流程(MNN)

2023-10-26

MNN框架:

在这里插入图片描述

MNN的官方介绍:

官方文档
有疑问一定要先查这里!

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

MNN的架构:

在这里插入图片描述
MNN可以分为Converter和Interpreter两部分。

Converter由Frontends和Graph Optimize构成。前者负责支持不同的训练框架,MNN当前支持Tensorflow(Lite)、Caffe和ONNX(PyTorch/MXNet的模型可先转为ONNX模型再转到MNN);后者通过算子融合、算子替代、布局调整等方式优化图。

Interpreter由Engine和Backends构成。前者负责模型的加载、计算图的调度;后者包含各计算设备下的内存分配、Op实现。在Engine和Backends中,MNN应用了多种优化方案,包括在卷积和反卷积中应用Winograd算法、在矩阵乘法中应用Strassen算法、低精度计算、Neon优化、手写汇编、多线程优化、内存复用、异构计算等。

问题解决:

MNN作为开开源时间并不长的框架,社区的建设相对于NCNN来讲毕竟还是有所欠缺,好在官方已经在努力不断建设社区的内容,建立了很多钉钉群,相关的问题可以先在github里提交issue之后再去钉钉群中解答。具体的链接在MNN的Github的主页里可以找到,请点击这里。一些常见的使用问题可以在这里找到相关的解答

MNN使用样例:

物体分类样例
车道检测样例
直线检测
人脸识别
中文字OCR
其他案例汇总

MNN部署:

一般流程:

由于有了之前我们部署NCNN的经验,大致上的端侧部署的原理都是相通的,NCNN是以模型推理器(Extractor)的形式来处理数据,加载模型,而到了MNN则是以解释器(Interpreter)的形式来加载数据和模型,处理数据的。

使用MNN推理时,有两个层级的抽象,分别是解释器Interpreter会话Session。Interpreter是模型数据的持有者;Session通过Interpreter创建,是推理数据的持有者。多个推理可以共用同一个模型,即,多个Session可以共用一个Interpreter。

一个标准的运行流程包括以下几部分:
创建会话->输入数据->运行会话->获取输出

创建会话:

  • 创建Interpreter(持有模型数据)
  • 创建Session(持有推理数据)
  • 调度配置ScheduleConfig(一般情况下,不需要额外设置调度配置,函数会根据模型结构自动识别出调度路径、输入输出)
  • 后端配置BackendConfig(内存、功耗和精度偏好)
  • 输入数据:

MNN输入数据是按照tensor的方式输入的,这块包括两大部分,数据的输入和预处理。

Tensor
Interpreter上提供了两个用于获取输入Tensor的方法:getSessionInput用于获取单个输入tensor,
getSessionInputAll用于获取输入tensor映射。

  • 图像处理:

下面是官方给的图像处理的标准处理:

auto input  = net->getSessionInput(session, NULL);
auto output = net->getSessionOutput(session, NULL);

auto dims  = input->shape();
int bpp    = dims[1]; 
int size_h = dims[2];
int size_w = dims[3];

auto inputPatch = argv[2];
FREE_IMAGE_FORMAT f = FreeImage_GetFileType(inputPatch);
FIBITMAP* bitmap = FreeImage_Load(f, inputPatch);
auto newBitmap = FreeImage_ConvertTo32Bits(bitmap);
auto width = FreeImage_GetWidth(newBitmap);
auto height = FreeImage_GetHeight(newBitmap);
FreeImage_Unload(bitmap);

Matrix trans;
//Dst -> [0, 1]
trans.postScale(1.0/size_w, 1.0/size_h);
//Flip Y  (因为 FreeImage 解出来的图像排列是Y方向相反的)
trans.postScale(1.0,-1.0, 0.0, 0.5);
//[0, 1] -> Src
trans.postScale(width, height);

ImageProcess::Config config;
config.filterType = NEAREST;
float mean[3] = {103.94f, 116.78f, 123.68f};
float normals[3] = {0.017f,0.017f,0.017f};
::memcpy(config.mean, mean, sizeof(mean));
::memcpy(config.normal, normals, sizeof(normals));
config.sourceFormat = RGBA;
config.destFormat = BGR;

std::shared_ptr<ImageProcess> pretreat(ImageProcess::create(config));
pretreat->setMatrix(trans);
pretreat->convert((uint8_t*)FreeImage_GetScanLine(newBitmap, 0), width, height, 0, input);
net->runSession(session);

从代码可以看到,用到了Freeimage的库,鉴于我们大部分使用的还是opencv,这里也给一个替换的教程阿里MNN移动端部署框架,将FreeImage更换为opencv的实现

运行会话:

这一块比较简单,一般来讲直接调用即可。
runSession(Session* session) const;

获取输出:

输出这块也是用的tensor

auto outputTensor = interpreter->getSessionOutput(session, NULL);
auto nchwTensor = new Tensor(outputTensor, Tensor::CAFFE);
outputTensor->copyToHostTensor(nchwTensor);
auto score = nchwTensor->host<float>()[0];
auto index = nchwTensor->host<float>()[1];
// ...
delete nchwTensor;

然后把相关的指针赋值给输出的数组或者mat即可。

流程汇总

接下来为流程的汇总调用。

#include <jni.h>
#include <string>
#include <android/log.h>
#include <MNN/MNNDefine.h>
#include <MNN/Interpreter.hpp>
#include <MNN/Tensor.hpp>

#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, "MNNExample", __VA_ARGS__)

static MNN::Interpreter *interpreter = nullptr;
static MNN::Session *session = nullptr;
static MNN::Tensor *inputTensor = nullptr;
static MNN::Tensor *outputTensor = nullptr;

extern "C" {
JNIEXPORT jint JNICALL
Java_com_example_mnnexample_MNNRunner_init(JNIEnv *env, jobject instance, jstring modelPath_) {
    // 获取模型路径
    const char *modelPath = env->GetStringUTFChars(modelPath_, 0);

    // 创建解释器
    interpreter = MNN::Interpreter::createFromFile(modelPath);
    MNN::ScheduleConfig config;
    config.numThread = 1; // 设置线程数
    session = interpreter->createSession(config);

    // 获取输入输出张量
    std::vector<MNN::Tensor *> inputs = interpreter->getSessionInputAll(session);
    std::vector<MNN::Tensor *> outputs = interpreter->getSessionOutputAll(session);
    inputTensor = inputs[0];
    outputTensor = outputs[0];

    // 释放字符串
    env->ReleaseStringUTFChars(modelPath_, modelPath);

    return 0;
}

JNIEXPORT jfloatArray JNICALL
Java_com_example_mnnexample_MNNRunner_predict(JNIEnv *env, jobject instance,
                                              jfloatArray data_, jint width, jint height,
                                              jint channel) {
    // 获取传入的数据
    jfloat *data = env->GetFloatArrayElements(data_, NULL);
    if (data == NULL) {
        return NULL;
    }

    // 输入张量的形状为 NHWC
    inputTensor->resize({1, height, width, channel});
    inputTensor->copyFromHostFloat(data);

    // 运行模型
    interpreter->runSession(session);

    // 获取输出张量数据
    std::vector<int> dims = outputTensor->shape();
    jfloatArray output = env->NewFloatArray(dims[1]);
    outputTensor->copyToHostFloat(output);

    // 释放内存
    env->ReleaseFloatArrayElements(data_, data, 0);

    return output;
}

JNIEXPORT void JNICALL
Java_com_example_mnnexample_MNNRunner_release(JNIEnv *env, jobject instance) {
    // 释放资源
    inputTensor->release();
    outputTensor->release();
    interpreter->releaseSession(session);
    delete interpreter;
}
}

其中,Java_com_example_mnnexample_MNNRunner_init函数用于初始化模型解释器,Java_com_example_mnnexample_MNNRunner_predict函数用于运行模型,并返回输出结果,Java_com_example_mnnexample_MNNRunner_release函数用于释放资源。

需要注意的是,安卓JNI中访问Java类成员变量或者调用Java类的函数,需要使用env->GetObjectClass(instance)获取Java类对象。

总结

相对来讲,目前我们过了两个端侧部署框架NCNN和MNN,

NCNN突出的特点在于:

  1. 开源较早,社区成熟,案例较多,问题容易解决。
  2. 易用性较强,相对于MNN会更容易上手,很多操作都直接封装到NCNN内部,方便操作。
  3. 相对来讲速度会稍慢,模型优化的部分会少一些。

MNN的突出特点在于:

  1. 网上的案例相对较少,问题解决较困难一点。
  2. 易用性稍差,需要较高的学习成本,使用时需要搭配CV的库。
  3. 支持训练蒸馏量化,速度较快
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

从端到端打通模型端侧部署流程(MNN) 的相关文章

随机推荐

  • shell中 >&2含义

    echo this is a test gt 2 gt 2 也就是把结果输出到和标准错误一样 之前如果有定义标准错误重定向到某file文件 那么标准输出也重定向到这个file文件 其中 的意思 可以看成是 The same as 与 一样
  • webpack入门

    webpack入门 webpack简介 模块打包器 项目构建工具 自动化构建工具 将多种类型资源之间的依赖关系构建成统一的静态资源 打包上线部署 js css等 但不包括html 因为它认为html不算模块 四个核心概念 入口entry 输
  • springboot整合shiro实现认证授权源码

    shiro admin 介绍 springboot整合shiro实现前后端分离架构 swagger文档协调前端开发 源码地址 https gitee com liujinxin ark shiro admin 软件架构 架构说明 sprin
  • 【深度学习】 Python 和 NumPy 系列教程(一):Python基本数据类型:1、数字(整数、浮点数)及相关运算;2、布尔值

    目录 一 前言 二 实验环境 三 Python基本数据类型 1 数字 a 整数 int b 浮点数 float c 运算 运算符 增强操作符 代码整合 d 运算中的类型转换 e 运算函数abs max min int float 2 布尔值
  • 如何判断对象中是否存在某个键名

    之前遇到过很多这样的问题 如何去判断对象中是否存在某个键 从而对其进行下一步的操作 下面就就给大家介绍一种我目前了解的一种方法 首先你新建了一个新的对象 var obj 顺便复习一下上次讲的forEach循环 function get so
  • TCP 三次握手和四次挥手的面试题

    重新整理了一版 TCP 三次握手和四次挥手的面试题 2023最新版 任 TCP 虐我千百遍 我仍待 TCP 如初恋 巨巨巨巨长的提纲 发车 发车 img TCP 基本认识 TCP 头格式有哪些 我们先来看看 TCP 头的格式 标注颜色的表示
  • python 编码 —— codecs 库

    1 对文件读写 import codecs fout codecs open test html w encoding UTF 8 fout write fout write fout close 很自然地可将其改造为 with 结构 wi
  • 淘宝TDDL数据库分库分表

    淘宝TDDL数据库分库分表 2014 06 04 23 18 3334人阅读 评论 0 收藏 举报 分类 数据库 1 分库分表 而且分库规则非常灵活 2 主键生成策略 目前TDDL提供的id生成主要还是依托数据库来进行的 oracle可以直
  • 八大排序算法-归并排序

    归并排序的定义 是将两个 或两个以上 有序表合并成一个新的有序表 即把待排序序列分为若干个子序列 每个子序列是有序的 然后再把有序子序列合并为整体有序序列 归并排序的基本思想 设r i n 由两个有序子表r i m 和r m 1 n 组成
  • ref绑定到不同元素获取到不同对象

    ref如果绑定在组件中 那么通过this ref refname获取到的是一个组建对象 ref如果绑定在普通的元素中 那么通过this ref refname获取到的是一个元素对象
  • 云呐

    科技大数据时代 企业的信息化规划刻不容缓 固定资产管理系统做为一款企业资产方案系统 可完成对企业资产的系统化管理 充分发挥资产更高的实用价值 固定资产管理系统可将企业內部全部资产融合在一起 根据对固定资产的增加 改动 退出 迁移 删除 使用
  • 2016年4月28日(6985小时时),第一次签合同,里程碑

    这周四 我觉得是个历史性的事件 是个里程碑 说明 锲而不舍 金石可镂 虽然不多 2万
  • win11/ win10 C盘扩容教程

    win11 win10 C 盘扩容教程 1 写在前面 10月5号微软官方正式发布了win11操作系统 作为一名科技星人 我也是第一时间升级体验了一番 如何升级win11我就不多说了 晚上一搜教程非常的多 这里推荐使用win11升级助手升级
  • 合宙ESP32系列

    目录 源文档见 ESP32系列编译文档 LuatOS 文档 本地编译详细步骤 准备环境 准备项目 获取源码 编译前的最后准备 编译 LuatOS SoC通用固件格式soc介绍 定制固件里的库 PS luat conf bsp h问题汇总 源
  • SGL STL源码剖析——迭代器

    SGL STL源码剖析 迭代器 迭代器 迭代器的型别 Traits的作用 迭代器相应的五种型别 type traits 迭代器 在我们使用STL容器的时候 迭代器是非常常见的 STL将容器和算法分开 彼此独立 然后通过迭代器相互关联 迭代器
  • U-Net: Convolutional Networks for Biomedical Image Seg-mentation

    Abstract 深度网络的成功训练需要数千个带注释的训练样本 这是一个很大的共识 在本文中 我们提出了一种网络和训练策略 它依赖于数据增强的强大使用 以更有效地使用可用的带注释的样本 该体系结构由捕获上下文的收缩路径和支持精确本地化的对称
  • 纯源码程序的执行

    QT Creator本身是个IDE安装的时候根据自己需要配置的又有对应的编译器 因此编写普通的程序也不再话下 选择Non Qt Project工程 并在右侧根据自己的需要选择C 应用还是C应用 新工程中工程管理文件和代码如下 执行结果如下
  • 表与表之间的关系

    一 表关系的概念 现实生活中 实体与实体之间肯定是有关系的 如 学生和老师 学生和课程 部门和员工 每个人和自己的身份证号码等 在设计表的时候 就应该体现出来表与表之间的这种关系 表与表之间的三种关系 一对多 最常用的关系 如部门和员工 多
  • 在ubuntu下如何搜索文件?

    1 whereis 文件名 特点 快速 但是是模糊查找 例如 找 whereis mysql 它会把mysql mysql ini mysql 所在的目录都找出来 我一般的查找都用这条命令 2 find name 文件名 特点 准确 但速度
  • 从端到端打通模型端侧部署流程(MNN)

    从端到端打通模型端侧部署流程 MNN MNN框架 MNN的官方介绍 MNN的架构 问题解决 MNN使用样例 MNN部署 一般流程 创建会话 运行会话 获取输出 流程汇总 总结 MNN框架 MNN的官方介绍 官方文档 有疑问一定要先查这里 M