寒武纪加速平台(MLU200系列) 摸鱼指南(四)--- 边缘端实例程序分析
PS:要转载请注明出处,本人版权所有。
PS: 这个只是基于《我自己》的理解,
如果和你的原则及想法相冲突,请谅解,勿喷。
前置说明
本文作为本人csdn blog的主站的备份。(BlogID=114)
环境说明
- MLU220 开发板
- Ubuntu18.04 + MLU270开发主机一台
- aarch64-linux-gnu-gcc 6.x 交叉编译环境
前言
阅读本文前,请务必须知以下前置文章概念:
- 《寒武纪加速平台(MLU200系列) 摸鱼指南(一)--- 基本概念及相关介绍》 ( https://blog.csdn.net/u011728480/article/details/121194076 )
- 《寒武纪加速平台(MLU200系列) 摸鱼指南(二)--- 模型移植-环境搭建》 ( https://blog.csdn.net/u011728480/article/details/121320982 )
- 《寒武纪加速平台(MLU200系列) 摸鱼指南(三)--- 模型移植-分割网络实例》 ( https://blog.csdn.net/u011728480/article/details/121456789 )
这里再次回顾一下《寒武纪加速平台(MLU200系列) 摸鱼指南(一)--- 基本概念及相关介绍》的内容,寒武纪加速卡是一个硬件平台,是最底层的存在,在硬件平台之上,有驱动,在驱动之上,有运行时。本文的离线模型就是基于运行时的api进行调用和处理,得到模型结果。
本文作为本系列的终篇,将会从上文的离线模型开始,从头开始搭建我们的离线模型推理代码结构,经过合理的部署,能够在MLU220开发板正常的运行。
若文中引用部分存在侵权,请及时联系我删除。
离线模型推理前置须知
经过前文相关的介绍,我们可以知道我们主要调用的是运行时相关的api。这里存在两套api可以使用,一个是cnrt,一个是easydk。easydk是基于cnrt封装的api,大大简化了离线模型推理的开发流程。但是我们的开发主线是一致的,就是初始化mlu设备,加载模型,预处理,模型推理,后处理,处理结果。
此外,寒武纪还提供了CNStream程序框架,基于EasyDk开发,以pipeline+observer的方式,提供了一个简单易用的框架,如果有兴趣,请查看其官网 https://github.com/Cambricon/CNStream 。
我们其实要用的是EasyDK+CNRT的这种开发方式,构造一个类似CNStream这样的程序。
EasyDK简介与编译
首先其官网是:https://github.com/Cambricon/easydk。
其除了CNToolKit的依赖(neuware)之外,还依赖于glog,gflags,opencv,这些需要提前安装好。至于CNToolKit x86的版本的相关介绍已经在模型移植环境部分介绍过了。
由于我们要开发边缘端离线模型推理程序,一般来说,我们主要还是使用到EasyDK里面的EasyInfer部分的内容。其编译流程的话就如其官方介绍:
- cmake ..
- make
如果熟悉cmake的编译流程的话,其实就知道上面的流程是非常普遍的。对于EasyDK的编译来说,在x86下面进行编译是很简单的,但是如果要进行交叉编译的话,最好只生成核心库,其他的sample和test都关闭。
完成离线模型在MLU270上的运行
还记得在《寒武纪加速平台(MLU200系列) 摸鱼指南(三)--- 模型移植-分割网络实例》一文中,我们可以看到,通过torch_mlu.core.mlu_model.set_core_version('MLU220'),我们可以调整生成的离线模型运行的平台,可以是边缘端MLU220,可以是服务器部署端的MLU270。对的,没有看错,MLU270既可以做模型移植,也可以做离线模型的部署,就如同本系列的开篇所讲,这几个平台仅仅是部署场景的差异。
为什么我们需要在MLU270上调试离线模型呢?因为方便。因为MLU220一般来说都需要交叉编译,特别是相关的依赖库的生成比较麻烦,如果MLU220的板卡也方便调试的话,那也可以直接基于MLU220进行开发和调试。
由于我这边是EasyDK+CNRT混合调用的形式,因此我主要根据其官方的CNRT推理代码进行注释和介绍,并在相关位置标注哪些内容是EasyDK可以一步到位的。这里简单的回答几个疑问。为什么需要EasyDK这个库呢?为什么不直接基于CNRT进行开发?我的回答是根据项目推进的需要做出的选择。关于这种程序的最终形态,其实我还是期待直接基于CNRT开发,因为其有效,但是代价是你必须很熟悉的了解相关的内容。
下面是官方推理代码介绍(可能会在顺序上做一些变更,其官方代码未处理一些返回值,自己编写的时候需要注意,此外,官方的代码有些遗漏的地方,我进行了修改):
/* Copyright (C) [2019] by Cambricon, Inc. */
/* offline_test */
/*
* A test which shows how to load and run an offline model.
* This test consists of one operation --mlp.
*
* This example is used for MLU270 and MLU220.
*
*/
#include "cnrt.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int offline_test(const char *name) {
// This example is used for MLU270 and MLU220. You need to choose the corresponding offline model.
// when generating an offline model, u need cnml and cnrt both
// when running an offline model, u need cnrt only
// 第一步,你必须调用cnrtInit()初始化运行时环境
cnrtInit(0);
// 第二步, 就是检查MLU设备,并设定当前设备,这一步其实是为多设备推理准备的
cnrtDev_t dev;
cnrtGetDeviceHandle(&dev, 0);
cnrtSetCurrentDevice(dev);
// 这里的第一二步其实可能创建一个EasyInfer对象就完成了,而且具备了完整的错误检查。
//
// prepare model name
// load model
// 第三步,就是加载模型
cnrtModel_t model;
cnrtLoadModel(&model, "test.cambricon");
// get model total memory
// 第四步,就是获取模型的一些属性,比如内存占用,并行性这些,这一步是可选的。
int64_t totalMem;
cnrtGetModelMemUsed(model, &totalMem);
printf("total memory used: %ld Bytes\n", totalMem);
// get model parallelism
int model_parallelism;
cnrtQueryModelParallelism(model, &model_parallelism);
printf("model parallelism: %d.\n", model_parallelism);
// 第五步,是建立推理逻辑流,这一步就是根据离线模型,在内存中动态生成模型推理结构。这里的"subnet0"是一个 kernel-func 名字,非固定,但是常见的名字都是subnet0,
// 在.cambricon_twins配套文件中有相关定义。
// 他这个和cuda编程其实比较类似,我们刚刚生成的模型推理结构其实被当做一个内核函数,函数名就是这个。
// load extract function
cnrtFunction_t function;
cnrtCreateFunction(&function);
cnrtExtractFunction(&function, model, "subnet0");
// 第六步,获取输入输出节点个数和输入输出数据size。注意许多网络中有多个输入,也可能有多个输出。在次注意这个概念,后续的操作都在多个输入和多个输出的基础上设计的。
int inputNum, outputNum;
int64_t *inputSizeS, *outputSizeS;
cnrtGetInputDataSize(&inputSizeS, &inputNum, function);
cnrtGetOutputDataSize(&outputSizeS, &outputNum, function);
// prepare data on cpu
// 第7步,申请cpu上的输入输出内存,这里是二维指针哈,代表多个输入输出
void **inputCpuPtrS = (void **)malloc(inputNum * sizeof(void *));
void **outputCpuPtrS = (void **)malloc(outputNum * sizeof(void *));
// allocate I/O data memory on MLU
// 第8步,申请mlu上的输入输出内存,这里是二维指针哈,代表多个输入输出。
// 此时还未真正申请数据内存,只是申请了数据节点的句柄(指针)
void **inputMluPtrS = (void **)malloc(inputNum * sizeof(void *));
void **outputMluPtrS = (void **)malloc(outputNum * sizeof(void *));
// prepare input buffer
// 第9步,分别对第8步的数据节点句柄申请真正的内存空间。这里的inputCpuPtrS和inputMluPtrS是一一对应的,这是内存地址不一样,内存管理者不一样。对输出也是同理。
// malloc是标准c申请堆内存接口
// cnrtMalloc是申请mlu所管理的内存接口,类比cuda的话,可以直接理解为申请显存
for (int i = 0; i < inputNum; i++) {
// converts data format when using new interface model
inputCpuPtrS[i] = malloc(inputSizeS[i]);
// malloc mlu memory
cnrtMalloc(&(inputMluPtrS[i]), inputSizeS[i]);
cnrtMemcpy(inputMluPtrS[i], inputCpuPtrS[i], inputSizeS[i], CNRT_MEM_TRANS_DIR_HOST2DEV);
}
// prepare output buffer
for (int i = 0; i < outputNum; i++) {
outputCpuPtrS[i] = malloc(outputSizeS[i]);
// malloc mlu memory
cnrtMalloc(&(outputMluPtrS[i]), outputSizeS[i]);
}
// 第10步,首先将预处理好的图像数据填充inputCpuPtrS, 拷贝cpu输入数据内存到mlu输入数据内存
cv::Mat _in_img;
_in_img = cv::imread("test.jpg");
_in_img.convertTo(_in_img, CV_32FC3);
for (int i = 0; i < inputNum; i++) {
// 注意这里的memcpy是我添加的,一般来说,模型的数据输入都是fp16或者fp32,但是一般我们的opencv生成的uint8。需要转换成fp32,或者fp16。
// 重要是事情发3次,注意图片的预处理后的数据格式以及模型输入的数据格式,不同的话,需要经过转换,官方提供了cnrtCastDataType来辅助转换过程。
// 重要是事情发3次,注意图片的预处理后的数据格式以及模型输入的数据格式,不同的话,需要经过转换,官方提供了cnrtCastDataType来辅助转换过程。
// 重要是事情发3次,注意图片的预处理后的数据格式以及模型输入的数据格式,不同的话,需要经过转换,官方提供了cnrtCastDataType来辅助转换过程。
::memcpy( inputCpuPtrS[i], _in_img.data, inputSizeS[i]);
cnrtMemcpy(inputMluPtrS[i], inputCpuPtrS[i], inputSizeS[i], CNRT_MEM_TRANS_DIR_HOST2DEV);
}
// 第10步,主要就是图像预处理,将图像数据传输到mlu上面去。
// 第11步,主要是开始设置推理参数
// prepare parameters for cnrtInvokeRuntimeContext
void **param = (void **)malloc(sizeof(void *) * (inputNum + outputNum));
for (int i = 0; i < inputNum; ++i) {
param[i] = inputMluPtrS[i];
}
for (int i = 0; i < outputNum; ++i) {
param[inputNum + i] = outputMluPtrS[i];
}
// 第12步,绑定设备和设置推理context
// setup runtime ctx
cnrtRuntimeContext_t ctx;
cnrtCreateRuntimeContext(&ctx, function, NULL);
// compute offline
cnrtQueue_t queue;
cnrtRuntimeContextCreateQueue(ctx, &queue);
// bind device
cnrtSetRuntimeContextDeviceId(ctx, 0);
cnrtInitRuntimeContext(ctx, NULL);
// 第13步,推理并等待推理结束。
// invoke
cnrtInvokeRuntimeContext(ctx, param, queue, NULL);
// sync
cnrtSyncQueue(queue);
// 第14步,将数据从mlu拷贝回cpu,然后进行后续的后处理
// copy mlu result to cpu
for (int i = 0; i < outputNum; i++) {
cnrtMemcpy(outputCpuPtrS[i], outputMluPtrS[i], outputSizeS[i], CNRT_MEM_TRANS_DIR_DEV2HOST);
}
// 第15步,清理环境。
// free memory space
for (int i = 0; i < inputNum; i++) {
free(inputCpuPtrS[i]);
cnrtFree(inputMluPtrS[i]);
}
for (int i = 0; i < outputNum; i++) {
free(outputCpuPtrS[i]);
cnrtFree(outputMluPtrS[i]);
}
free(inputCpuPtrS);
free(outputCpuPtrS);
free(param);
cnrtDestroyQueue(queue);
cnrtDestroyRuntimeContext(ctx);
cnrtDestroyFunction(function);
cnrtUnloadModel(model);
cnrtDestroy();
return 0;
}
int main() {
printf("mlp offline test\n");
offline_test("mlp");
return 0;
}
下面我简单列出一些EasyDK的操作顺序:
- 上文的第三四五步其实对应的是EasyInfer下面的ModelLoader模块,当初始化ModelLoader模块,并传参给EasyInfer实例,就完成了三四五步的内容。其实前面这些内容都是固定形式的,并不是重点。重点是后面的数据输入、推理、数据输出部分。
- 上文的第6,7,8,9步其实都是在为模型在cpu和mlu上申请相关的内存空间。在EasyDk中有对应的接口直接完成内存申请。
- 注意上文的第10步,是比较重要的一步,包含了图像数据预处理,到图像数据类型转换,再到图像数据输入到mlu内存。
- 上文的第11,12步是为推理准备参数
- 上文的第13步开始推理
- 上文的第14步从mlu内存中拷贝出推理结果到cpu内存,然后进行后处理。
- 上文的第15步,清理环境。
离线模型在MLU220上的部署
前一小节,主要还是完成离线模型推理的程序开发,并在MLU270上运行测试。本小节的主要内容是怎么将我们调好的程序部署到MLU220。
部署到MLU220,我们面临的第一个问题就是交叉编译生成AARCH64的程序。这里包含3个部分依赖,CNToolkit-aarch64,easydk-aarch64,其他第三方库如opencv-aarch64等。这时,我们得到了aarch64的离线推理程序,并配合之前我们转换得到的mlu220版本的离线模型。
当我们把生成好的程序放到mlu220板卡,这个时候,可能程序还是无法跑起来,因为可能驱动未加载,这个时候,建议找到驱动和固件,让mlu220运行起来。然后运行程序即可。
下面是无mlu设备的报错示例:
下面是加载驱动的最后日志:
下面是运行程序的输出:
后记
对于RK3399pro和寒武纪MLU220平台来说,一些出来时间较为长久的模型,由于优化的比较到位,速度相较于RK3399pro可能有个300%+的性能提升。但是对于一些新的模型和一些非经典(非大众)的模型,由于自带的优化或者网络结构的原因,可能只有30%+的性能提升,但是这也是令人高兴的事情,毕竟硬件升级之后,好多事情可以达到准实时。
本系列的基本介绍就这些了,完结撒花~~~ ~~~。
参考文献
- 《寒武纪加速平台(MLU200系列) 摸鱼指南(一)--- 基本概念及相关介绍》 ( https://blog.csdn.net/u011728480/article/details/121194076 )
- 《寒武纪加速平台(MLU200系列) 摸鱼指南(二)--- 模型移植-环境搭建》 ( https://blog.csdn.net/u011728480/article/details/121320982 )
- 《寒武纪加速平台(MLU200系列) 摸鱼指南(三)--- 模型移植-分割网络实例》 ( https://blog.csdn.net/u011728480/article/details/121456789 )
- https://www.cambricon.com/
- https://www.cambricon.com/docs/cnrt/user_guide_html/example/offline_mode.html
- 其他相关保密资料。
打赏、订阅、收藏、丢香蕉、硬币,请关注公众号(攻城狮的搬砖之路)
PS: 请尊重原创,不喜勿喷。
PS: 要转载请注明出处,本人版权所有。
PS: 有问题请留言,看到后我会第一时间回复。
寒武纪加速平台(MLU200系列) 摸鱼指南(四)--- 边缘端实例程序分析的更多相关文章
- 寒武纪加速平台(MLU200系列) 摸鱼指南(二)--- 模型移植-环境搭建
PS:要转载请注明出处,本人版权所有. PS: 这个只是基于<我自己>的理解, 如果和你的原则及想法相冲突,请谅解,勿喷. 前置说明 本文作为本人csdn blog的主站的备份.(Bl ...
- 寒武纪加速平台(MLU200系列) 摸鱼指南(一)--- 基本概念及相关介绍
PS:要转载请注明出处,本人版权所有. PS: 这个只是基于<我自己>的理解, 如果和你的原则及想法相冲突,请谅解,勿喷. 前置说明 本文作为本人csdn blog的主站的备份.(Bl ...
- 春节前“摸鱼”指南——SCA命令行工具助你快速构建FaaS服务
春节将至,身在公司的你是不是已经完全丧失了工作的斗志? 但俗话说得好:"只要心中有沙,办公室也能是马尔代夫." 职场人如何才能做到最大效能地带薪"摸鱼",成为了 ...
- JVM系列四:生产环境参数实例及分析【生产环境实例增加中】
java application项目(非web项目) 改进前: -Xms128m-Xmx128m-XX:NewSize=64m-XX:PermSize=64m-XX:+UseConcMarkSweep ...
- [转]JVM系列四:生产环境参数实例及分析【生产环境实例增加中】
原文地址:http://www.cnblogs.com/redcreen/archive/2011/05/05/2038331.html java application项目(非web项目) 改进前: ...
- 【转】让Chrome化身成为摸鱼神器,利用Chorme运行布卡漫画以及其他安卓APK应用教程
下周就是十一了,无论是学生党还是工作党,大家的大概都会有点心不在焉,为了让大家更好的心不在焉,更好的在十一前最后一周愉快的摸鱼,今天就写一个如何让Chrome(google浏览器)运行安卓APK应用的 ...
- Thief-Book 上班摸鱼神器
Thief-Book 上班摸鱼神器 介绍 Thief-Book 是一款真正的摸鱼神器,可以更加隐秘性大胆的看小说. 隐蔽性 自定义透明背景,随意调整大小,完美融入各种软件界面 快捷性 三个快捷键,实现 ...
- AI解决方案:边缘计算和GPU加速平台
AI解决方案:边缘计算和GPU加速平台 一.适用于边缘 AI 的解决方案 AI 在边缘蓬勃发展.AI 和云原生应用程序.物联网及其数十亿的传感器以及 5G 网络现已使得在边缘大规模部署 AI 成为可能 ...
- 菜鸡学C语言之摸鱼村村长
题目描述 摸鱼村要选村长了! 选村长的规则是村里每个人进行一次投票,票数大于人数一半的成为村长. 然鹅摸鱼村的人都比较懒,你能帮他们写一个程序来找出谁当选村长吗? (每名村民的编号都是一个int范围内 ...
随机推荐
- fastdfs单节点部署
fastdfs单机版搭建 参考链接:https://blog.csdn.net/prcyang/article/details/89946190 搭建步骤 安装依赖 yum -y install ...
- C 输入输出函数
流 就C程序而言,所有的I/O操作只是简单地从程序移入或移出字节的事情.这种字节流便称为流( stream ). 绝大多数流是完全缓存的,这意味着"读取"和"写入&quo ...
- 【Spring】IoC容器 - 依赖来源
前言 上一篇文章已经学习了[依赖注入]相关的知识,这里详细的介绍一下[依赖来源]. 依赖来源 我们把依赖来源分为依赖查找的来源和依赖注入的来源分别讨论. 依赖查找的来源 1. Spring BeanD ...
- Scrum Meeting 11
第11次例会报告 日期:2021年06月01日 会议主要内容概述: 汇报了进度,开始爆肝. 一.进度情况 我们采用日报的形式记录每个人的具体进度,链接Home · Wiki,如下记录仅为保证公开性: ...
- 【二食堂】Beta - 项目展示
项目展示 1. 团队介绍 二食堂很难排队 姓名 介绍 职务 刘享 热爱游戏,尤其是RPG和metrovinia类的游戏. 会C/C++, python, java. 后端 左正 一个普通的大学生,Py ...
- 2021.10.12考试总结[NOIP模拟75]
T1 如何优雅的送分 考虑式子的实际意义.\(2^{f_n}\)实际上就是枚举\(n\)质因子的子集.令\(k\)为这个子集中数的乘积,就可以将式子转化为枚举\(k\),计算\(k\)的贡献. 不难得 ...
- 洛谷 P4774 [NOI2018] 屠龙勇士
链接:P4774 前言: 交了18遍最后发现是多组数据没清空/ll 题意: 其实就是个扩中. 分析过程: 首先发现根据题目描述的选择剑的方式,每条龙对应的剑都是固定的,有查询前驱,后继(在该数不存在前 ...
- Python matplotlib 概率论与数理统计 伯努利分布 二项分布
Python 代码实现 二项分布 import numpy as np import matplotlib.pyplot as plt import math from scipy import st ...
- 【Azure 应用服务】App Service for Linux 中实现 WebSocket 功能 (Python SocketIO)
问题描述 使用 python websockets 模块作为Socket的服务端,发布到App Service for Linux环境后,发现Docker Container无法启动.错误消息为: 2 ...
- adb 安装与使用(一)
一.ADB简介 1. 什么是adb? adb(Android Debug Bridage)是Android sdk的一个工具: adb 是用来连接安卓手机和PC端的桥梁,要有adb作为二者之间的维系, ...