# 使用Runtime执行推理(Java) `Android` `Java` `推理应用` `模型加载` `数据准备` `中级` `高级` [![查看源文件](../_static/logo_source.png)](https://gitee.com/mindspore/docs/blob/r1.2/tutorials/lite/source_zh_cn/use/runtime_java.md) ## 概述 通过[MindSpore Lite模型转换工具](https://www.mindspore.cn/tutorial/lite/zh-CN/r1.2/use/converter_tool.html)转换成`.ms`模型后,即可在Runtime中执行模型的推理流程。本教程介绍如何使用[JAVA接口](https://www.mindspore.cn/doc/api_java/zh-CN/r1.2/index.html)执行推理。 Android项目中使用MindSpore Lite,可以选择采用[C++ API](https://www.mindspore.cn/doc/api_cpp/zh-CN/r1.2/index.html)或者[Java API](https://www.mindspore.cn/doc/api_java/zh-CN/r1.2/index.html)运行推理框架。Java API与C++ API相比较而言,Java API可以直接在Java Class中调用,用户无需实现JNI层的相关代码,具有更好的便捷性。运行MindSpore Lite推理框架主要包括以下步骤: 1. 模型加载:从文件系统中读取由[模型转换工具](https://www.mindspore.cn/tutorial/lite/zh-CN/r1.2/use/converter_tool.html)转换得到的`.ms`模型,通过Model的[loadModel](https://www.mindspore.cn/doc/api_java/zh-CN/r1.2/model.html#loadmodel)导入模型。 2. 创建配置上下文:创建配置上下文[MSConfig](https://www.mindspore.cn/doc/api_java/zh-CN/r1.2/msconfig.html#msconfig),保存会话所需的一些基本配置参数,用于指导图编译和图执行。主要包括`deviceType`:设备类型、`threadNum`:线程数、`cpuBindMode`:CPU绑定模式、`enable_float16`:是否优先使用Float16算子。 3. 创建会话:创建[LiteSession](https://www.mindspore.cn/doc/api_java/zh-CN/r1.2/lite_session.html#litesession),并调用[init](https://www.mindspore.cn/doc/api_java/zh-CN/r1.2/lite_session.html#init)方法将上一步得到的[MSConfig](https://www.mindspore.cn/doc/api_java/zh-CN/r1.2/msconfig.html#msconfig)配置到会话中。 4. 图编译:在图执行前,需要调用[LiteSession](https://www.mindspore.cn/doc/api_java/zh-CN/r1.2/lite_session.html#litesession)的[compileGraph](https://www.mindspore.cn/doc/api_java/zh-CN/r1.2/lite_session.html#compilegraph)接口进行图编译,主要进行子图切分、算子选型调度。这部分会耗费较多时间,所以建议[LiteSession](https://www.mindspore.cn/doc/api_java/zh-CN/r1.2/lite_session.html#litesession)创建一次,编译一次,多次执行。 5. 输入数据:图执行之前需要向输入Tensor中填充数据。 6. 执行推理:使用[LiteSession](https://www.mindspore.cn/doc/api_java/zh-CN/r1.2/lite_session.html#litesession)的[runGraph](https://www.mindspore.cn/doc/api_java/zh-CN/r1.2/lite_session.html#rungraph)进行模型推理。 7. 获得输出:图执行结束之后,可以通过输出Tensor得到推理结果。 8. 释放内存:无需使用MindSpore Lite推理框架的时候,需要释放已创建的[LiteSession](https://www.mindspore.cn/doc/api_java/zh-CN/r1.2/lite_session.html#litesession)和[model](https://www.mindspore.cn/doc/api_java/zh-CN/r1.2/model.html#model)。 ![img](../images/lite_runtime.png) 快速了解MindSpore Lite执行推理的完整调用流程,请参考[体验MindSpore Lite Java极简Demo](https://www.mindspore.cn/tutorial/lite/zh-CN/r1.2/quick_start/quick_start_java.html)。 ## 引用MindSpore Lite Java库 ### Linux X86项目引用JAR库 采用`Maven`作为构建工具时,可将`mindspore-lite-java.jar`拷贝到根目录下的`lib`目录,并在`pom.xml`中增加jar包的依赖。 ```xml com.mindspore.lite mindspore-lite-java 1.0 system ${project.basedir}/lib/mindspore-lite-java.jar ``` > 运行时需要将`libmindspore-lite.so`以及`libminspore-lite-jni.so`的所在路径添加到`java.library.path`。 ### Android项目引用AAR库 采用`Gradle`作为构建工具时,首先将`mindspore-lite-{version}.aar`文件移动到目标module的`libs`目录,然后在目标module的`build.gradle`的`repositories`中添加本地引用目录,最后在`dependencies`中添加AAR的依赖,具体如下所示。 > 注意mindspore-lite-{version}是AAR的文件名,需要将{version}替换成对应版本信息。 ```groovy repositories { flatDir { dirs 'libs' } } dependencies { implementation fileTree(dir: "libs", include: ['*.aar']) } ``` ## 加载模型 MindSpore Lite进行模型推理时,需要先从文件系统中加载模型转换工具转换后的`.ms`模型,并进行模型解析。Java的[model](https://www.mindspore.cn/doc/api_java/zh-CN/r1.2/model.html#model)类提供了2个[loadModel](https://www.mindspore.cn/doc/api_java/zh-CN/r1.2/model.html#loadmodel)接口,使其可以从`Assets`或其他文件路径中加载模型。 下面[示例代码](https://gitee.com/mindspore/mindspore/blob/r1.2/mindspore/lite/examples/runtime_java/app/src/main/java/com/mindspore/lite/demo/MainActivity.java#L217)将从`Assets`读取`mobilenetv2.ms`模型文件进行模型加载。 ```java // Load the .ms model. Model model = new Model(); String modelPath = "mobilenetv2.ms"; boolean ret = model.loadModel(this.getApplicationContext(), modelPath); ``` >只有`AAR`库才支持从`Assert`加载模型文件的接口。 下面[示例代码](https://gitee.com/mindspore/mindspore/blob/r1.2/mindspore/lite/examples/quick_start_java/src/main/java/com/mindspore/lite/demo/Main.java#L128)将从`modelPath`路径读取模型文件进行模型加载。 ```java Model model = new Model(); boolean ret = model.loadModel(modelPath); ``` ## 创建配置上下文 创建配置上下文[MSConfig](https://www.mindspore.cn/doc/api_java/zh-CN/r1.2/msconfig.html#msconfig),保存会话所需的一些基本配置参数,用于指导图编译和图执行。 MindSpore Lite支持异构推理,推理时的主选后端由[MSConfig](https://www.mindspore.cn/doc/api_java/zh-CN/r1.2/msconfig.html#msconfig)的`deviceType`指定,目前支持CPU和GPU。在进行图编译时,会根据主选后端进行算子选型调度。 MindSpore Lite内置一个进程共享的线程池,推理时通过`threadNum`指定线程池的最大线程数,默认为2线程。 MindSpore Lite支持Float16算子的模式进行推理。`enable_float16`设置为`true`后,将会优先使用Float16算子。 ### 配置使用CPU后端 当需要执行的后端为CPU时,`MSConfig`创建后需要在[init](https://www.mindspore.cn/doc/api_java/zh-CN/r1.2/msconfig.html#init)中配置`DeviceType.DT_CPU`,同时CPU支持设置绑核模式以及是否优先使用Float16算子。 下面[示例代码](https://gitee.com/mindspore/mindspore/blob/r1.2/mindspore/lite/examples/runtime_java/app/src/main/java/com/mindspore/lite/demo/MainActivity.java#L59)演示如何创建CPU后端,同时设定CPU绑核模式为大核优先并且使能Float16推理: ```java MSConfig msConfig = new MSConfig(); boolean ret = msConfig.init(DeviceType.DT_CPU, 2, CpuBindMode.HIGHER_CPU, true); ``` > Float16需要CPU为ARM v8.2架构的机型才能生效,其他不支持的机型和x86平台会自动回退到Float32执行。 ### 配置使用GPU后端 当需要执行的后端为CPU和GPU的异构推理时,`MSConfig`创建后需要在[init](https://www.mindspore.cn/doc/api_java/zh-CN/r1.2/msconfig.html#init)中配置`DeviceType.DT_GPU`,配置后将会优先使用GPU推理。同时是否优先使用Float16算子设置为true后,GPU和CPU都会优先使用Float16算子。 下面[示例代码](https://gitee.com/mindspore/mindspore/blob/r1.2/mindspore/lite/examples/runtime_java/app/src/main/java/com/mindspore/lite/demo/MainActivity.java#L69)演示如何创建CPU与GPU异构推理后端,同时GPU也设定使能Float16推理: ```java MSConfig msConfig = new MSConfig(); boolean ret = msConfig.init(DeviceType.DT_GPU, 2, CpuBindMode.MID_CPU, true); ``` > 目前GPU只能在Android手机端侧运行,所以只有`AAR`库才能支持运行。 ## 创建会话 [LiteSession](https://www.mindspore.cn/doc/api_java/zh-CN/r1.2/lite_session.html#litesession)是推理的主入口,通过[LiteSession](https://www.mindspore.cn/doc/api_java/zh-CN/r1.2/lite_session.html#litesession)可以进行图编译、图执行。创建[LiteSession](https://www.mindspore.cn/doc/api_java/zh-CN/r1.2/lite_session.html#litesession),并调用[init](https://www.mindspore.cn/doc/api_java/zh-CN/r1.2/lite_session.html#init)方法将上一步得到[MSConfig](https://www.mindspore.cn/doc/api_java/zh-CN/r1.2/msconfig.html#msconfig)配置到会话中。[LiteSession](https://www.mindspore.cn/doc/api_java/zh-CN/r1.2/lite_session.html#litesession)初始化之后,[MSConfig](https://www.mindspore.cn/doc/api_java/zh-CN/r1.2/msconfig.html#msconfig)将可以进行释放操作。 下面[示例代码](https://gitee.com/mindspore/mindspore/blob/r1.2/mindspore/lite/examples/runtime_java/app/src/main/java/com/mindspore/lite/demo/MainActivity.java#L86)演示如何创建`LiteSession`的方式: ```java LiteSession session = new LiteSession(); boolean ret = session.init(msConfig); msConfig.free(); ``` ## 图编译 在图执行前,需要调用[LiteSession](https://www.mindspore.cn/doc/api_java/zh-CN/r1.2/lite_session.html#litesession)的[compileGraph](https://www.mindspore.cn/doc/api_java/zh-CN/r1.2/lite_session.html#compilegraph)接口进行图编译,主要进行子图切分、算子选型调度。这部分会耗费较多时间,所以建议[LiteSession](https://www.mindspore.cn/doc/api_java/zh-CN/r1.2/lite_session.html#litesession)创建一次,编译一次,多次执行。 下面[示例代码](https://gitee.com/mindspore/mindspore/blob/r1.2/mindspore/lite/examples/runtime_java/app/src/main/java/com/mindspore/lite/demo/MainActivity.java#L87)演示调用`CompileGraph`进行图编译。 ```java boolean ret = session.compileGraph(model); ``` ## 输入数据 MindSpore Lite Java接口提供`getInputsByTensorName`以及`getInputs`两种方法获得输入Tensor,同时支持`byte[]`或者`ByteBuffer`两种类型的数据,通过[setData](https://www.mindspore.cn/doc/api_java/zh-CN/r1.2/mstensor.html#setdata)设置输入Tensor的数据。 1. 使用[getInputsByTensorName](https://www.mindspore.cn/doc/api_java/zh-CN/r1.2/lite_session.html#getinputsbytensorname)方法,根据模型输入Tensor的名称来获取模型输入Tensor中连接到输入节点的Tensor,下面[示例代码](https://gitee.com/mindspore/mindspore/blob/r1.2/mindspore/lite/examples/runtime_java/app/src/main/java/com/mindspore/lite/demo/MainActivity.java#L151)演示如何调用`getInputsByTensorName`获得输入Tensor并填充数据。 ```java MSTensor inputTensor = session.getInputsByTensorName("2031_2030_1_construct_wrapper:x"); // Set Input Data. inputTensor.setData(inputData); ``` 2. 使用[getInputs](https://www.mindspore.cn/doc/api_java/zh-CN/r1.2/lite_session.html#getinputs)方法,直接获取所有的模型输入Tensor的vector,下面[示例代码](https://gitee.com/mindspore/mindspore/blob/r1.2/mindspore/lite/examples/runtime_java/app/src/main/java/com/mindspore/lite/demo/MainActivity.java#L113)演示如何调用`getInputs`获得输入Tensor并填充数据。 ```java List inputs = session.getInputs(); MSTensor inputTensor = inputs.get(0); // Set Input Data. inputTensor.setData(inputData); ``` > MindSpore Lite的模型输入Tensor中的数据排布必须是`NHWC`。如果需要了解更多数据前处理过程,可参考[实现一个图像分割应用对输入数据进行处理部分](https://www.mindspore.cn/tutorial/lite/zh-CN/r1.2/quick_start/image_segmentation.html#id10)。 ## 执行推理 MindSpore Lite会话在进行图编译以后,即可调用[LiteSession](https://www.mindspore.cn/doc/api_java/zh-CN/r1.2/lite_session.html#litesession)的[runGraph](https://www.mindspore.cn/doc/api_java/zh-CN/r1.2/lite_session.html#rungraph)执行模型推理。 下面示例代码演示调用`runGraph`执行推理。 ```java // Run graph to infer results. boolean ret = session.runGraph(); ``` ## 获得输出 MindSpore Lite在执行完推理后,可以通过输出Tensor得到推理结果。MindSpore Lite提供三种方法来获取模型的输出[MSTensor](https://www.mindspore.cn/doc/api_java/zh-CN/r1.2/mstensor.html),同时支持[getByteData](https://www.mindspore.cn/doc/api_java/zh-CN/r1.2/mstensor.html#getbytedata)、[getFloatData](https://www.mindspore.cn/doc/api_java/zh-CN/r1.2/mstensor.html#getfloatdata)、[getIntData](https://www.mindspore.cn/doc/api_java/zh-CN/r1.2/mstensor.html#getintdata)、[getLongData](https://www.mindspore.cn/doc/api_java/zh-CN/r1.2/mstensor.html#getlongdata)四种方法获得输出数据。 1. 使用[getOutputMapByTensor](https://www.mindspore.cn/doc/api_java/zh-CN/r1.2/lite_session.html#getoutputmapbytensor)方法,直接获取所有的模型输出[MSTensor](https://www.mindspore.cn/doc/api_java/zh-CN/r1.2/mstensor.html#mstensor)的名称和[MSTensor](https://www.mindspore.cn/doc/api_java/zh-CN/r1.2/mstensor.html#mstensor)指针的一个map。下面[示例代码](https://gitee.com/mindspore/mindspore/blob/r1.2/mindspore/lite/examples/runtime_java/app/src/main/java/com/mindspore/lite/demo/MainActivity.java#L191)演示如何调用`getOutputMapByTensor`获得输出Tensor。 ```java Map outTensors = session.getOutputMapByTensor(); Iterator> entries = outTensors.entrySet().iterator(); while (entries.hasNext()) { Map.Entry entry = entries.next(); // Apply infer results. ... } ``` 2. 使用[getOutputByNodeName](https://www.mindspore.cn/doc/api_java/zh-CN/r1.2/lite_session.html#getoutputsbynodename)方法,根据模型输出节点的名称来获取模型输出[MSTensor](https://www.mindspore.cn/doc/api_java/zh-CN/r1.2/mstensor.html#mstensor)中连接到该节点的Tensor的vector。下面[示例代码](https://gitee.com/mindspore/mindspore/blob/r1.2/mindspore/lite/examples/runtime_java/app/src/main/java/com/mindspore/lite/demo/MainActivity.java#L175)演示如何调用`getOutputByTensorName`获得输出Tensor。 ```java MSTensor outTensor = session.getOutputsByNodeName("Default/head-MobileNetV2Head/Softmax-op204"); // Apply infer results. ... ``` 3. 使用[getOutputByTensorName](https://www.mindspore.cn/doc/api_java/zh-CN/r1.2/lite_session.html#getoutputbytensorname)方法,根据模型输出Tensor的名称来获取对应的模型输出[MSTensor](https://www.mindspore.cn/doc/api_java/zh-CN/r1.2/mstensor.html#mstensor)。下面[示例代码](https://gitee.com/mindspore/mindspore/blob/r1.2/mindspore/lite/examples/runtime_java/app/src/main/java/com/mindspore/lite/demo/MainActivity.java#L182)演示如何调用`getOutputByTensorName`获得输出Tensor。 ```java MSTensor outTensor = session.getOutputByTensorName("Default/head-MobileNetV2Head/Softmax-op204"); // Apply infer results. ... ``` ## 释放内存 无需使用MindSpore Lite推理框架时,需要释放已经创建的LiteSession和Model,下列[示例代码](https://gitee.com/mindspore/mindspore/blob/r1.2/mindspore/lite/examples/runtime_java/app/src/main/java/com/mindspore/lite/demo/MainActivity.java#L204)演示如何在程序结束前进行内存释放。 ```java session.free(); model.free(); ``` ## 高级用法 ### 优化运行内存大小 如果对运行时内存有较大的限制,图编译结束之后,调用[Model](https://www.mindspore.cn/doc/api_java/zh-CN/r1.2/model.html#model)的[freeBuffer](https://www.mindspore.cn/doc/api_java/zh-CN/r1.2/model.html#freebuffer)函数,释放MindSpore Lite Model中的MetaGraph,用于减小运行时的内存。一旦调用某个[Model](https://www.mindspore.cn/doc/api_java/zh-CN/r1.2/model.html#model)的[freeBuffer](https://www.mindspore.cn/doc/api_java/zh-CN/r1.2/model.html#freebuffer)后,该[Model](https://www.mindspore.cn/doc/api_java/zh-CN/r1.2/model.html#model)就不能再次图编译。 下面[示例代码](https://gitee.com/mindspore/mindspore/blob/r1.2/mindspore/lite/examples/runtime_java/app/src/main/java/com/mindspore/lite/demo/MainActivity.java#L241)演示如何调用`Model`的`freeBuffer`接口来释放`MetaGraph`减少运行时内存大小。 ```java // Compile graph. ret = session.compileGraph(model); ... // Note: when use model.freeBuffer(), the model can not be compiled. model.freeBuffer(); ``` ### 绑核操作 MindSpore Lite内置线程池支持绑核、解绑操作,通过调用[bindThread](https://www.mindspore.cn/doc/api_java/zh-CN/r1.2/lite_session.html#bindthread)接口,可以将线程池中的工作线程绑定到指定CPU核,用于性能分析。绑核操作与创建[LiteSession](https://www.mindspore.cn/doc/api_java/zh-CN/r1.2/lite_session.html)时用户指定的上下文有关,绑核操作会根据上下文中的绑核策略进行线程与CPU的亲和性设置。 需要注意的是,绑核是一个亲和性操作,不保证一定能绑定到指定的CPU核,会受到系统调度的影响。而且绑核后,需要在执行完代码后进行解绑操作。 下面[示例代码](https://gitee.com/mindspore/mindspore/blob/r1.2/mindspore/lite/examples/runtime_java/app/src/main/java/com/mindspore/lite/demo/MainActivity.java#L164)演示如何在执行推理时绑定大核优先。 ```java boolean ret = msConfig.init(DeviceType.DT_CPU, 2, CpuBindMode.HIGHER_CPU, true); ... session.bindThread(true); // Run Inference. ret = session.runGraph(); session.bindThread(false); ``` > 绑核参数有三种选择:大核优先、中核优先以及不绑核。 > > 判定大核和中核的规则其实是根据CPU核的频率而不是根据CPU的架构,对于没有大中小核之分的CPU架构,在该规则下也可以区分大核和中核。 > > 绑定大核优先是指线程池中的线程从频率最高的核开始绑定,第一个线程绑定在频率最高的核上,第二个线程绑定在频率第二高的核上,以此类推。 > > 对于中核优先,中核的定义是根据经验来定义的,默认设定中核是第三和第四高频率的核,当绑定策略为中核优先时,会优先绑定到中核上,当中核不够用时,会往小核上进行绑定。 ### 输入维度Resize 使用MindSpore Lite进行推理时,如果需要对输入的shape进行Resize,则可以在已完成创建会话`CreateSession`与图编译`CompileGraph`之后调用[LiteSession](https://www.mindspore.cn/doc/api_java/zh-CN/r1.2/lite_session.html)的[resize](https://www.mindspore.cn/doc/api_java/zh-CN/r1.2/lite_session.html#resize)接口,对输入的Tensor重新设置shape。 > 某些网络是不支持可变维度,会提示错误信息后异常退出,比如,模型中有MatMul算子,并且MatMul的一个输入Tensor是权重,另一个输入Tensor是输入时,调用可变维度接口会导致输入Tensor和权重Tensor的Shape不匹配,最终导致推理失败。 下面[示例代码](https://gitee.com/mindspore/mindspore/blob/r1.2/mindspore/lite/examples/runtime_java/app/src/main/java/com/mindspore/lite/demo/MainActivity.java#L164)演示如何对MindSpore Lite的输入Tensor进行[resize](https://www.mindspore.cn/doc/api_java/zh-CN/r1.2/lite_session.html#resize): ```java List inputs = session.getInputs(); int[][] dims = {{1, 300, 300, 3}}; bool ret = session.resize(inputs, dims); ``` ### Session并行 MindSpore Lite支持多个[LiteSession](https://www.mindspore.cn/doc/api_java/zh-CN/r1.2/lite_session.html)并行推理,每个[LiteSession](https://www.mindspore.cn/doc/api_java/zh-CN/r1.2/lite_session.html#litesession)的线程池和内存池都是独立的。但不支持多个线程同时调用单个[LiteSession](https://www.mindspore.cn/doc/api_java/zh-CN/r1.2/lite_session.html#litesession)的[runGraph](https://www.mindspore.cn/doc/api_java/zh-CN/r1.2/lite_session.html#rungraph)接口。 下面[示例代码](https://gitee.com/mindspore/mindspore/blob/r1.2/mindspore/lite/examples/runtime_java/app/src/main/java/com/mindspore/lite/demo/MainActivity.java#L220)演示如何并行执行推理多个[LiteSession](https://www.mindspore.cn/doc/api_java/zh-CN/r1.2/lite_session.html)的过程: ```java session1 = createLiteSession(false); if (session1 != null) { session1Compile = true; } else { Toast.makeText(getApplicationContext(), "session1 Compile Failed.", Toast.LENGTH_SHORT).show(); } session2 = createLiteSession(true); if (session2 != null) { session2Compile = true; } else { Toast.makeText(getApplicationContext(), "session2 Compile Failed.", Toast.LENGTH_SHORT).show(); } ... if (session1Finish && session1Compile) { new Thread(new Runnable() { @Override public void run() { session1Finish = false; runInference(session1); session1Finish = true; } }).start(); } if (session2Finish && session2Compile) { new Thread(new Runnable() { @Override public void run() { session2Finish = false; runInference(session2); session2Finish = true; } }).start(); } ``` MindSpore Lite不支持多线程并行执行单个[LiteSession](https://www.mindspore.cn/doc/api_java/zh-CN/r1.2/lite_session.html)的推理,否则会得到以下错误信息: ```bash ERROR [mindspore/lite/src/lite_session.cc:297] RunGraph] 10 Not support multi-threading ``` ### 查看日志 当推理出现异常的时候,可以通过查看日志信息来定位问题。针对Android平台,采用`Logcat`命令行工具查看MindSpore Lite推理的日志信息,并利用`MS_LITE` 进行筛选。 ```shell logcat -s "MS_LITE" ``` ### 获取版本号 MindSpore Lite提供了[Version](https://www.mindspore.cn/doc/api_java/zh-CN/r1.2/lite_session.html)方法可以获取版本号,包含在`com.mindspore.lite.Version`头文件中,调用该方法可以得到当前MindSpore Lite的版本号。 下面[示例代码](https://gitee.com/mindspore/mindspore/blob/r1.2/mindspore/lite/examples/runtime_java/app/src/main/java/com/mindspore/lite/demo/MainActivity.java#L215)演示如何获取MindSpore Lite的版本号: ```java import com.mindspore.lite.Version; String version = Version.version(); ```