端侧推理

概述

MindSpore Predict是一个轻量级的深度神经网络推理引擎,提供了将MindSpore训练出的模型在端侧进行推理的功能。本教程介绍MindSpore Predict的编译方法和使用指南。

编译方法

用户需要自行编译,这里介绍在Ubuntu环境下进行交叉编译的具体步骤。

环境要求如下:

  • 硬件要求

    • 内存1GB以上

    • 硬盘空间10GB以上

  • 系统要求

    • 系统:Ubuntu = 16.04.02LTS(验证可用)

    • 内核:4.4.0-62-generic(验证可用)

  • 软件依赖

numpy, decorator和scipy可以通过pip安装,参考命令如下。

  1. Copypip3 install numpy==1.16 decorator scipy

编译步骤如下:

  • 配置环境变量。
  1. Copyexport LLVM_PATH={$LLVM_PATH}/clang+llvm-8.0.0-x86_64-linux-gnu-ubuntu-16.04/bin/llvm-config #设定llvm路径
  2. export ANDROID_NDK={$NDK_PATH}/android-ndk-r16b #设定ndk路径
  • 从代码仓下载源码。
  1. Copygit clone https://gitee.com/mindspore/mindspore.git
  • 在源码根目录下,执行如下命令编译MindSpore Predict。-I为编译MindSpore Predict的编译参数,-I的参数为目标端侧平台,目前仅支持安卓arm64平台。
  1. Copysh build.sh -I arm64
  • 获取编译结果。

进入源码的predict/output目录,即可查看生成的压缩包,包名为MSPredict-{版本号}-{HOST平台}_{DEVICE平台}.tar.gz,例如:MSPredict-0.1.0-linux_aarch64.tar.gz。 该压缩包包含以下目录:

  • include:MindSpore Predict的头文件。

  • lib:MindSpore Predict的动态库。

端侧推理使用

在APP的APK工程中使用MindSpore对进行模型推理时,模型推理前需要对输入进行必要的前处理,比如将图片转换成MindSpore推理要求的tensor格式、对图片进行resize等处理。在MindSpore完成模型推理后,对模型推理的结果进行后处理,并将处理的输出发送给APP应用。

本章主要描述用户如何使用MindSpore进行模型推理,APK工程的搭建和模型推理的前后处理,不在此列举。

MindSpore进行端侧模型推理的步骤如下。

生成端侧模型文件

  • 加载训练完毕所生成的CheckPoint文件至定义好的网络中。
  1. Copyparam_dict = load_checkpoint(ckpoint_file_name=ckpt_file_path)
  2. load_param_into_net(net, param_dict)
  • 调用export接口,导出端侧模型文件(.ms)。
  1. Copyexport(net, input_data, file_name="./lenet.ms", file_format='LITE')

以LeNet网络为例,生成的端侧模型文件为lenet.ms,完整示例代码lenet.py如下。

  1. Copyimport os
  2. import numpy as np
  3. import mindspore.nn as nn
  4. import mindspore.ops.operations as P
  5. import mindspore.context as context
  6. from mindspore.common.tensor import Tensor
  7. from mindspore.train.serialization import export, load_checkpoint, load_param_into_net
  8.  
  9. class LeNet(nn.Cell):
  10. def __init__(self):
  11. super(LeNet, self).__init__()
  12. self.relu = P.ReLU()
  13. self.batch_size = 32
  14. self.conv1 = nn.Conv2d(1, 6, kernel_size=5, stride=1, padding=0, has_bias=False, pad_mode='valid')
  15. self.conv2 = nn.Conv2d(6, 16, kernel_size=5, stride=1, padding=0, has_bias=False, pad_mode='valid')
  16. self.pool = nn.MaxPool2d(kernel_size=2, stride=2)
  17. self.reshape = P.Reshape()
  18. self.fc1 = nn.Dense(400, 120)
  19. self.fc2 = nn.Dense(120, 84)
  20. self.fc3 = nn.Dense(84, 10)
  21.  
  22. def construct(self, input_x):
  23. output = self.conv1(input_x)
  24. output = self.relu(output)
  25. output = self.pool(output)
  26. output = self.conv2(output)
  27. output = self.relu(output)
  28. output = self.pool(output)
  29. output = self.reshape(output, (self.batch_size, -1))
  30. output = self.fc1(output)
  31. output = self.relu(output)
  32. output = self.fc2(output)
  33. output = self.relu(output)
  34. output = self.fc3(output)
  35. return output
  36.  
  37. if __name__ == '__main__':
  38. context.set_context(mode=context.GRAPH_MODE, device_target="Ascend")
  39. seed = 0
  40. np.random.seed(seed)
  41. origin_data = np.random.uniform(low=0, high=255, size=(32, 1, 32, 32)).astype(np.float32)
  42. origin_data.tofile("lenet.bin")
  43. input_data = Tensor(origin_data)
  44. net = LeNet()
  45. ckpt_file_path = "path_to/lenet.ckpt"
  46.  
  47. is_ckpt_exist = os.path.exists(ckpt_file_path)
  48. if is_ckpt_exist:
  49. param_dict = load_checkpoint(ckpoint_file_name=ckpt_file_path)
  50. load_param_into_net(net, param_dict)
  51. export(net, input_data, file_name="./lenet.ms", file_format='LITE')
  52. print("export model success.")
  53. else:
  54. print("checkpoint file does not exist.")

在端侧实现推理

将.ms模型文件和图片数据作为输入,创建session在端侧实现推理。

../_images/side_infer_process.png

图1:端侧推理时序图

  • 加载.ms模型文件到内存缓冲区,ReadFile函数功能需要用户自行实现。
  1. Copy// read model file
  2. std::string modelPath = "./models/lenet/lenet.ms";
  3. size_t graphSize = 0;
  4.  
  5. /* ReadFile() here is a dummy function */
  6. char *graphBuf = ReadFile(modelPath.c_str(), graphSize);
  • 调用CreateSession接口创建Session,创建完成后可释放内存缓冲区中的模型文件。
  1. Copy// create session
  2. Context ctx;
  3. std::shared_ptr<Session> session = CreateSession(graphBuf, graphSize, ctx);
  4. free(graphBuf);
  • 从内存缓冲区中读取推理的输入数据,调用SetData()接口将输入数据设置到input tensor中。
  1. Copy// load input buffer
  2. size_t inputSize = 0;
  3. std::string imagePath = "./data/input/lenet.bin";
  4. char *inputBuf = ReadFile(imagePath.c_str(), inputSize);
  5.  
  6. //get input tensors
  7. std::vector<Tensor *> inputs = session->GetInput();
  8. //set input buffer
  9. inputs[0]->SetData(inputBuf);
  • 调用Session中的Run()接口执行推理。
  1. Copy// session run
  2. int ret = session->Run(inputs);
  • 调用GetAllOutput()接口获取输出。
  1. Copy// get output
  2. std::map<std::string, std::vector<Tensor *>> outputs = session->GetAllOutput();
  • 调用Tensor的GetData()接口获取输出数据。
  1. Copy// get output data
  2. float *data = nullptr;
  3. for (auto output : outputs) {
  4. auto tensors = output.second;
  5. for (auto tensor : tensors) {
  6. data = (float *)(tensor->GetData());
  7. }
  8. }
  • 推理结束释放input tensor和output tensor。
  1. Copy// free inputs and outputs
  2. for (auto &input : inputs) {
  3. delete input;
  4. }
  5. inputs.clear();
  6. for (auto &output : outputs) {
  7. for (auto &outputTensor : output.second) {
  8. delete outputTensor;
  9. }
  10. }
  11. outputs.clear();

选取LeNet网络,推理输入为“lenet.bin”,完整示例代码lenet.cpp如下。

MindSpore Predict使用FlatBuffers定义模型,解析模型需要使用到FlatBuffers头文件,因此用户需要自行配置FlatBuffers头文件。

具体做法:将MindSpore根目录/third_party/flatbuffers/include下的flatbuffers文件夹拷贝到session.h的同级目录。

  1. Copy#include <string>
  2. #include <vector>
  3. #include "context.h"
  4. #include "session.h"
  5. #include "tensor.h"
  6. #include "errorcode.h"
  7.  
  8. using namespace mindspore::predict;
  9.  
  10. int main() {
  11. std::string modelPath = "./models/lenet/lenet.ms";
  12. std::string imagePath = "./data/input/lenet.bin";
  13.  
  14. // read model file
  15. size_t graphSize = 0;
  16.  
  17. /* ReadFile() here is a dummy function */
  18. char *graphBuf = ReadFile(modelPath.c_str(), graphSize);
  19. if (graphBuf == nullptr) {
  20. return -1;
  21. }
  22.  
  23. // create session
  24. Context ctx;
  25. auto session = CreateSession(graphBuf, graphSize, ctx);
  26. if (session == nullptr) {
  27. free(graphBuf);
  28. return -1;
  29. }
  30. free(graphBuf);
  31.  
  32. // load input buf
  33. size_t inputSize = 0;
  34. char *inputBuf = ReadFile(imagePath.c_str(), inputSize);
  35. if (inputBuf == nullptr) {
  36. return -1;
  37. }
  38.  
  39. auto inputs = session->GetInput();
  40. inputs[0]->SetData(inputBuf);
  41.  
  42. // session run
  43. auto ret = session->Run(inputs);
  44. if (ret != RET_OK) {
  45. printf("run failed, error: %d\n", ret);
  46. for (auto &input : inputs) {
  47. delete input;
  48. }
  49. return -1;
  50. }
  51.  
  52. // get output
  53. auto outputs = session->GetAllOutput();
  54.  
  55. // get output data
  56. float *data = nullptr;
  57. for (auto output : outputs) {
  58. auto tensors = output.second;
  59. for (auto tensor : tensors) {
  60. data = (float *)(tensor->GetData());
  61. //print the contents of the data
  62. for (size_t i = 0; i < tensor->GetElementSize(); ++i) {
  63. printf(" %f ", data[i]);
  64. }
  65. printf("\n");
  66. }
  67. }
  68.  
  69. // free inputs and outputs
  70. for (auto &input : inputs) {
  71. delete input;
  72. }
  73. inputs.clear();
  74. for (auto &output : outputs) {
  75. for (auto &outputTensor : output.second) {
  76. delete outputTensor;
  77. }
  78. }
  79. outputs.clear();
  80. return 0;
  81. }