PS:要转载请注明出处,本人版权所有。

PS: 这个只是基于《我自己》的理解,

如果和你的原则及想法相冲突,请谅解,勿喷。

前置说明

  本文作为本人csdn blog的主站的备份。(BlogID=114)

环境说明
  • MLU220 开发板
  • Ubuntu18.04 + MLU270开发主机一台
  • aarch64-linux-gnu-gcc 6.x 交叉编译环境

前言


  阅读本文前,请务必须知以下前置文章概念:

  这里再次回顾一下《寒武纪加速平台(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%+的性能提升,但是这也是令人高兴的事情,毕竟硬件升级之后,好多事情可以达到准实时。

  本系列的基本介绍就这些了,完结撒花~~~ ~~~。

参考文献


打赏、订阅、收藏、丢香蕉、硬币,请关注公众号(攻城狮的搬砖之路)

PS: 请尊重原创,不喜勿喷。

PS: 要转载请注明出处,本人版权所有。

PS: 有问题请留言,看到后我会第一时间回复。

寒武纪加速平台(MLU200系列) 摸鱼指南(四)--- 边缘端实例程序分析的更多相关文章

  1. 寒武纪加速平台(MLU200系列) 摸鱼指南(二)--- 模型移植-环境搭建

    PS:要转载请注明出处,本人版权所有. PS: 这个只是基于<我自己>的理解, 如果和你的原则及想法相冲突,请谅解,勿喷. 前置说明   本文作为本人csdn blog的主站的备份.(Bl ...

  2. 寒武纪加速平台(MLU200系列) 摸鱼指南(一)--- 基本概念及相关介绍

    PS:要转载请注明出处,本人版权所有. PS: 这个只是基于<我自己>的理解, 如果和你的原则及想法相冲突,请谅解,勿喷. 前置说明   本文作为本人csdn blog的主站的备份.(Bl ...

  3. 春节前“摸鱼”指南——SCA命令行工具助你快速构建FaaS服务

    春节将至,身在公司的你是不是已经完全丧失了工作的斗志? 但俗话说得好:"只要心中有沙,办公室也能是马尔代夫." 职场人如何才能做到最大效能地带薪"摸鱼",成为了 ...

  4. JVM系列四:生产环境参数实例及分析【生产环境实例增加中】

    java application项目(非web项目) 改进前: -Xms128m-Xmx128m-XX:NewSize=64m-XX:PermSize=64m-XX:+UseConcMarkSweep ...

  5. [转]JVM系列四:生产环境参数实例及分析【生产环境实例增加中】

    原文地址:http://www.cnblogs.com/redcreen/archive/2011/05/05/2038331.html java application项目(非web项目) 改进前: ...

  6. 【转】让Chrome化身成为摸鱼神器,利用Chorme运行布卡漫画以及其他安卓APK应用教程

    下周就是十一了,无论是学生党还是工作党,大家的大概都会有点心不在焉,为了让大家更好的心不在焉,更好的在十一前最后一周愉快的摸鱼,今天就写一个如何让Chrome(google浏览器)运行安卓APK应用的 ...

  7. Thief-Book 上班摸鱼神器

    Thief-Book 上班摸鱼神器 介绍 Thief-Book 是一款真正的摸鱼神器,可以更加隐秘性大胆的看小说. 隐蔽性 自定义透明背景,随意调整大小,完美融入各种软件界面 快捷性 三个快捷键,实现 ...

  8. AI解决方案:边缘计算和GPU加速平台

    AI解决方案:边缘计算和GPU加速平台 一.适用于边缘 AI 的解决方案 AI 在边缘蓬勃发展.AI 和云原生应用程序.物联网及其数十亿的传感器以及 5G 网络现已使得在边缘大规模部署 AI 成为可能 ...

  9. 菜鸡学C语言之摸鱼村村长

    题目描述 摸鱼村要选村长了! 选村长的规则是村里每个人进行一次投票,票数大于人数一半的成为村长. 然鹅摸鱼村的人都比较懒,你能帮他们写一个程序来找出谁当选村长吗? (每名村民的编号都是一个int范围内 ...

随机推荐

  1. fastdfs单节点部署

    fastdfs单机版搭建 参考链接:https://blog.csdn.net/prcyang/article/details/89946190 搭建步骤 安装依赖 ​ yum -y install ...

  2. C 输入输出函数

    流 就C程序而言,所有的I/O操作只是简单地从程序移入或移出字节的事情.这种字节流便称为流( stream ). 绝大多数流是完全缓存的,这意味着"读取"和"写入&quo ...

  3. 【Spring】IoC容器 - 依赖来源

    前言 上一篇文章已经学习了[依赖注入]相关的知识,这里详细的介绍一下[依赖来源]. 依赖来源 我们把依赖来源分为依赖查找的来源和依赖注入的来源分别讨论. 依赖查找的来源 1. Spring BeanD ...

  4. Scrum Meeting 11

    第11次例会报告 日期:2021年06月01日 会议主要内容概述: 汇报了进度,开始爆肝. 一.进度情况 我们采用日报的形式记录每个人的具体进度,链接Home · Wiki,如下记录仅为保证公开性: ...

  5. 【二食堂】Beta - 项目展示

    项目展示 1. 团队介绍 二食堂很难排队 姓名 介绍 职务 刘享 热爱游戏,尤其是RPG和metrovinia类的游戏. 会C/C++, python, java. 后端 左正 一个普通的大学生,Py ...

  6. 2021.10.12考试总结[NOIP模拟75]

    T1 如何优雅的送分 考虑式子的实际意义.\(2^{f_n}\)实际上就是枚举\(n\)质因子的子集.令\(k\)为这个子集中数的乘积,就可以将式子转化为枚举\(k\),计算\(k\)的贡献. 不难得 ...

  7. 洛谷 P4774 [NOI2018] 屠龙勇士

    链接:P4774 前言: 交了18遍最后发现是多组数据没清空/ll 题意: 其实就是个扩中. 分析过程: 首先发现根据题目描述的选择剑的方式,每条龙对应的剑都是固定的,有查询前驱,后继(在该数不存在前 ...

  8. Python matplotlib 概率论与数理统计 伯努利分布 二项分布

    Python 代码实现 二项分布 import numpy as np import matplotlib.pyplot as plt import math from scipy import st ...

  9. 【Azure 应用服务】App Service for Linux 中实现 WebSocket 功能 (Python SocketIO)

    问题描述 使用 python websockets 模块作为Socket的服务端,发布到App Service for Linux环境后,发现Docker Container无法启动.错误消息为: 2 ...

  10. adb 安装与使用(一)

    一.ADB简介 1. 什么是adb? adb(Android Debug Bridage)是Android sdk的一个工具: adb 是用来连接安卓手机和PC端的桥梁,要有adb作为二者之间的维系, ...