使用Runtime执行推理(Java)

Android Java 推理应用 模型加载 数据准备 中级 高级

概述

通过MindSpore Lite模型转换工具转换成.ms模型后,即可在Runtime中执行模型的推理流程。本教程介绍如何使用JAVA接口执行推理。

Android项目中使用MindSpore Lite,可以选择采用C++ API或者Java API运行推理框架。Java API与C++ API相比较而言,Java API可以直接在Java Class中调用,用户无需实现JNI层的相关代码,具有更好的便捷性。运行MindSpore Lite推理框架主要包括以下步骤:

  1. 模型加载:从文件系统中读取由模型转换工具转换得到的.ms模型,通过Model的loadModel导入模型。

  2. 创建配置上下文:创建配置上下文MSConfig,保存会话所需的一些基本配置参数,用于指导图编译和图执行。主要包括deviceType:设备类型、threadNum:线程数、cpuBindMode:CPU绑定模式、enable_float16:是否优先使用Float16算子。

  3. 创建会话:创建LiteSession,并调用init方法将上一步得到的MSConfig配置到会话中。

  4. 图编译:在图执行前,需要调用LiteSessioncompileGraph接口进行图编译,主要进行子图切分、算子选型调度。这部分会耗费较多时间,所以建议LiteSession创建一次,编译一次,多次执行。

  5. 输入数据:图执行之前需要向输入Tensor中填充数据。

  6. 执行推理:使用LiteSessionrunGraph进行模型推理。

  7. 获得输出:图执行结束之后,可以通过输出Tensor得到推理结果。

  8. 释放内存:无需使用MindSpore Lite推理框架的时候,需要释放已创建的LiteSessionmodel

img

快速了解MindSpore Lite执行推理的完整调用流程,请参考体验MindSpore Lite Java极简Demo

引用MindSpore Lite Java库

Linux X86项目引用JAR库

采用Maven作为构建工具时,可将mindspore-lite-java.jar拷贝到根目录下的lib目录,并在pom.xml中增加jar包的依赖。

<dependencies>
    <dependency>
        <groupId>com.mindspore.lite</groupId>
        <artifactId>mindspore-lite-java</artifactId>
        <version>1.0</version>
        <scope>system</scope>
        <systemPath>${project.basedir}/lib/mindspore-lite-java.jar</systemPath>
    </dependency>
</dependencies>

运行时需要将libmindspore-lite.so以及libminspore-lite-jni.so的所在路径添加到java.library.path

Android项目引用AAR库

采用Gradle作为构建工具时,首先将mindspore-lite-{version}.aar文件移动到目标module的libs目录,然后在目标module的build.gradlerepositories中添加本地引用目录,最后在dependencies中添加AAR的依赖,具体如下所示。

注意mindspore-lite-{version}是AAR的文件名,需要将{version}替换成对应版本信息。

repositories {
    flatDir {
        dirs 'libs'
    }
}

dependencies {
    implementation fileTree(dir: "libs", include: ['*.aar'])
}

加载模型

MindSpore Lite进行模型推理时,需要先从文件系统中加载模型转换工具转换后的.ms模型,并进行模型解析。Java的model类提供了2个loadModel接口,使其可以从Assets或其他文件路径中加载模型。

下面示例代码将从Assets读取mobilenetv2.ms模型文件进行模型加载。

// Load the .ms model.
Model model = new Model();
String modelPath = "mobilenetv2.ms";
boolean ret = model.loadModel(this.getApplicationContext(), modelPath);

只有AAR库才支持从Assert加载模型文件的接口。

下面示例代码将从modelPath路径读取模型文件进行模型加载。

Model model = new Model();
boolean ret = model.loadModel(modelPath);

创建配置上下文

创建配置上下文MSConfig,保存会话所需的一些基本配置参数,用于指导图编译和图执行。

MindSpore Lite支持异构推理,推理时的主选后端由MSConfigdeviceType指定,目前支持CPU和GPU。在进行图编译时,会根据主选后端进行算子选型调度。

MindSpore Lite内置一个进程共享的线程池,推理时通过threadNum指定线程池的最大线程数,默认为2线程。

MindSpore Lite支持Float16算子的模式进行推理。enable_float16设置为true后,将会优先使用Float16算子。

配置使用CPU后端

当需要执行的后端为CPU时,MSConfig创建后需要在init中配置DeviceType.DT_CPU,同时CPU支持设置绑核模式以及是否优先使用Float16算子。

下面示例代码演示如何创建CPU后端,同时设定CPU绑核模式为大核优先并且使能Float16推理:

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中配置DeviceType.DT_GPU,配置后将会优先使用GPU推理。同时是否优先使用Float16算子设置为true后,GPU和CPU都会优先使用Float16算子。

下面示例代码演示如何创建CPU与GPU异构推理后端,同时GPU也设定使能Float16推理:

MSConfig msConfig = new MSConfig();
boolean ret = msConfig.init(DeviceType.DT_GPU, 2, CpuBindMode.MID_CPU, true);

目前GPU只能在Android手机端侧运行,所以只有AAR库才能支持运行。

创建会话

LiteSession是推理的主入口,通过LiteSession可以进行图编译、图执行。创建LiteSession,并调用init方法将上一步得到MSConfig配置到会话中。LiteSession初始化之后,MSConfig将可以进行释放操作。

下面示例代码演示如何创建LiteSession的方式:

LiteSession session = new LiteSession();
boolean ret = session.init(msConfig);
msConfig.free();

图编译

在图执行前,需要调用LiteSessioncompileGraph接口进行图编译,主要进行子图切分、算子选型调度。这部分会耗费较多时间,所以建议LiteSession创建一次,编译一次,多次执行。

下面示例代码演示调用CompileGraph进行图编译。

boolean ret = session.compileGraph(model);

输入数据

MindSpore Lite Java接口提供getInputsByTensorName以及getInputs两种方法获得输入Tensor,同时支持byte[]或者ByteBuffer两种类型的数据,通过setData设置输入Tensor的数据。

  1. 使用getInputsByTensorName方法,根据模型输入Tensor的名称来获取模型输入Tensor中连接到输入节点的Tensor,下面示例代码演示如何调用getInputsByTensorName获得输入Tensor并填充数据。

    MSTensor inputTensor = session.getInputsByTensorName("2031_2030_1_construct_wrapper:x");
    // Set Input Data.
    inputTensor.setData(inputData);
    
  2. 使用getInputs方法,直接获取所有的模型输入Tensor的vector,下面示例代码演示如何调用getInputs获得输入Tensor并填充数据。

    List<MSTensor> inputs = session.getInputs();
    MSTensor inputTensor = inputs.get(0);
    // Set Input Data.
    inputTensor.setData(inputData);
    

MindSpore Lite的模型输入Tensor中的数据排布必须是NHWC。如果需要了解更多数据前处理过程,可参考实现一个图像分割应用对输入数据进行处理部分

执行推理

MindSpore Lite会话在进行图编译以后,即可调用LiteSessionrunGraph执行模型推理。

下面示例代码演示调用runGraph执行推理。

// Run graph to infer results.
boolean ret = session.runGraph();

获得输出

MindSpore Lite在执行完推理后,可以通过输出Tensor得到推理结果。MindSpore Lite提供三种方法来获取模型的输出MSTensor,同时支持getByteDatagetFloatDatagetIntDatagetLongData四种方法获得输出数据。

  1. 使用getOutputMapByTensor方法,直接获取所有的模型输出MSTensor的名称和MSTensor指针的一个map。下面示例代码演示如何调用getOutputMapByTensor获得输出Tensor。

    Map<String, MSTensor> outTensors = session.getOutputMapByTensor();
    
    Iterator<Map.Entry<String, MSTensor>> entries = outTensors.entrySet().iterator();
    while (entries.hasNext()) {
        Map.Entry<String, MSTensor> entry = entries.next();
        // Apply infer results.
        ...
    }
    
  2. 使用getOutputByNodeName方法,根据模型输出节点的名称来获取模型输出MSTensor中连接到该节点的Tensor的vector。下面示例代码演示如何调用getOutputByTensorName获得输出Tensor。

    MSTensor outTensor = session.getOutputsByNodeName("Default/head-MobileNetV2Head/Softmax-op204");
    // Apply infer results.
    ...
    
  3. 使用getOutputByTensorName方法,根据模型输出Tensor的名称来获取对应的模型输出MSTensor。下面示例代码演示如何调用getOutputByTensorName获得输出Tensor。

    MSTensor outTensor = session.getOutputByTensorName("Default/head-MobileNetV2Head/Softmax-op204");
    // Apply infer results.
    ...
    

释放内存

无需使用MindSpore Lite推理框架时,需要释放已经创建的LiteSession和Model,下列示例代码演示如何在程序结束前进行内存释放。

session.free();
model.free();

高级用法

优化运行内存大小

如果对运行时内存有较大的限制,图编译结束之后,调用ModelfreeBuffer函数,释放MindSpore Lite Model中的MetaGraph,用于减小运行时的内存。一旦调用某个ModelfreeBuffer后,该Model就不能再次图编译。

下面示例代码演示如何调用ModelfreeBuffer接口来释放MetaGraph减少运行时内存大小。

// Compile graph.
ret = session.compileGraph(model);
...
// Note: when use model.freeBuffer(), the model can not be compiled.
model.freeBuffer();

绑核操作

MindSpore Lite内置线程池支持绑核、解绑操作,通过调用bindThread接口,可以将线程池中的工作线程绑定到指定CPU核,用于性能分析。绑核操作与创建LiteSession时用户指定的上下文有关,绑核操作会根据上下文中的绑核策略进行线程与CPU的亲和性设置。

需要注意的是,绑核是一个亲和性操作,不保证一定能绑定到指定的CPU核,会受到系统调度的影响。而且绑核后,需要在执行完代码后进行解绑操作。

下面示例代码演示如何在执行推理时绑定大核优先。

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之后调用LiteSessionresize接口,对输入的Tensor重新设置shape。

某些网络是不支持可变维度,会提示错误信息后异常退出,比如,模型中有MatMul算子,并且MatMul的一个输入Tensor是权重,另一个输入Tensor是输入时,调用可变维度接口会导致输入Tensor和权重Tensor的Shape不匹配,最终导致推理失败。

下面示例代码演示如何对MindSpore Lite的输入Tensor进行resize

List<MSTensor> inputs = session.getInputs();
int[][] dims = {{1, 300, 300, 3}};
bool ret = session.resize(inputs, dims);

Session并行

MindSpore Lite支持多个LiteSession并行推理,每个LiteSession的线程池和内存池都是独立的。但不支持多个线程同时调用单个LiteSessionrunGraph接口。

下面示例代码演示如何并行执行推理多个LiteSession的过程:

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的推理,否则会得到以下错误信息:

ERROR [mindspore/lite/src/lite_session.cc:297] RunGraph] 10 Not support multi-threading

查看日志

当推理出现异常的时候,可以通过查看日志信息来定位问题。针对Android平台,采用Logcat命令行工具查看MindSpore Lite推理的日志信息,并利用MS_LITE 进行筛选。

logcat -s "MS_LITE"

获取版本号

MindSpore Lite提供了Version方法可以获取版本号,包含在com.mindspore.lite.Version头文件中,调用该方法可以得到当前MindSpore Lite的版本号。

下面示例代码演示如何获取MindSpore Lite的版本号:

import com.mindspore.lite.Version;
String version = Version.version();