Skip to content

Commit

Permalink
[Android] Add ERNIE Tiny android support (#4342)
Browse files Browse the repository at this point in the history
* [Android] init ERNIE Tiny android codes

* [Android] init ERNIE Tiny android codes

* [Android] init ERNIE Tiny android codes

* [Android] init ERNIE Tiny JNI codes

* [Android] init ERNIE Tiny JNI codes

* [Android] Add ERNIE Tiny App codes

* [Android] Add ERNIE Tiny App codes

* [Android] remove UIE codes

* [Android] Update perf_jni.h

* [Android] Update AndroidManifest.xml

* [Android] Update android/README.md

* [Android] Update android/README.md

* [Android] Update android/README.md

* [Android] Update android/README.md

* [Android] Update android/README.md

* [Android] Update android/README.md

* [Android] Update android/README.md

* [Android] remove un-needed deps

* [Android] remove un-needed c++ deps

* [Android] fixed android/README.md typos

* [Token] Add added_tokens_path to java & jni

* [Token] Add added_tokens_path to java & jni

* [Doc] fixed ERNIE 3.0 Tiny Android docs typos

* [Doc] fixed ERNIE 3.0 Tiny Android docs typos
  • Loading branch information
DefTruth committed Jan 11, 2023
1 parent 22de327 commit bb88615
Show file tree
Hide file tree
Showing 82 changed files with 4,034 additions and 0 deletions.
23 changes: 23 additions & 0 deletions model_zoo/ernie-tiny/deploy/android/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
.DS_Store
.idea
.gradle
.cxx
cache
build
app/cache
app/libs/*
app/.cxx
app/build
app/src/main/assets/models/*
app/.gradle
app/.idea
ernie_tiny/cache
ernie_tiny/libs/fastdeploy*
ernie_tiny/.cxx
ernie_tiny/build
ernie_tiny/src/main/assets/models/*
ernie_tiny/.gradle
ernie_tiny/.idea
ui/build
ui/.gradle
ui/cache
240 changes: 240 additions & 0 deletions model_zoo/ernie-tiny/deploy/android/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
# FastDeploy ERNIE 3.0 Tiny 模型Android部署示例

本目录提供了快速完成在 Android 的车载语音场景下的口语理解(Spoken Language Understanding,SLU)任务的部署示例。

## 环境准备

1. 在本地环境安装好 Android Studio 工具,详细安装方法请见[Android Stuido 官网](https://developer.android.com/studio)
2. 准备一部 Android 手机,并开启 USB 调试模式。开启方法: `手机设置 -> 查找开发者选项 -> 打开开发者选项和 USB 调试模式`

## App示例编译和使用步骤

1. ERNIE 3.0 Tiny Android 部署示例位于`PaddleNLP/model_zoo/ernie-tiny/deploy/android`目录
2. 用 Android Studio 打开 ernie-tiny/deploy/android 工程
3. 手机连接电脑,打开 USB 调试和文件传输模式,并在 Android Studio 上连接自己的手机设备(手机需要开启允许从 USB 安装软件权限)

<img width="1440" alt="image" src="https://user-images.githubusercontent.com/31974251/210742200-596f1585-9d4c-46e4-acae-5956a798c7ce.png">


工程内容说明:
```bash
.
├── README.md
├── app # App示例代码
├── build.gradle
├── ernie_tiny # ERNIE Tiny JNI & Java封装代码
# ... # 一些和gradle相关的工程配置文件
├── local.properties
└── ui # 一些辅助用的UI代码
```

> **注意:**
>> 如果您在导入项目、编译或者运行过程中遇到 NDK 配置错误的提示,请打开 ` File > Project Structure > SDK Location`,修改 `Andriod NDK location` 为您本机配置的 NDK 所在路径。本工程默认使用的NDK版本为20.
>> 如果您是通过 Android Studio 的 SDK Tools 下载的 NDK (见本章节"环境准备"),可以直接点击下拉框选择默认路径。
>> 还有一种 NDK 配置方法,你可以在 `local.properties` 文件中手动完成 NDK 路径配置,如下图所示
>> 如果以上步骤仍旧无法解决 NDK 配置错误,请尝试根据 Android Studio 官方文档中的[更新 Android Gradle 插件](https://developer.android.com/studio/releases/gradle-plugin?hl=zh-cn#updating-plugin)章节,尝试更新Android Gradle plugin版本。

4. 点击 Run 按钮,自动编译 APP 并安装到手机。(该过程会自动下载预编译的 FastDeploy Android 库 以及 模型文件,需要联网)
成功后效果如下,图一:APP 安装到手机;图二: APP 打开后的效果,输入文本后点击"开始分析意图"后即会自动进行意图识别和槽位分析;图三:APP设置选项,点击右上角的设置图片,可以设置不同选项进行体验。

| APP 效果 | APP 演示 | APP设置项 |
| --- | --- | --- |
| <img width="300" height="500" alt="image" src="https://user-images.githubusercontent.com/31974251/210737269-0fe175c9-7b87-40b3-8249-1c6378e4a5e9.jpg"> | <img width="300" height="500" alt="image" src="https://user-images.githubusercontent.com/31974251/210737041-0e79bd6b-90a8-42b0-8f66-97868386d9f3.gif"> | <img width="300" height="500" alt="image" src="https://user-images.githubusercontent.com/31974251/210774693-b199734c-442c-48ef-90ea-4d2e72ff7304.jpg"> |

## ERNIE Tiny Java SDK 说明和使用

本工程除了可以直接编译 App 体验之外,还可以编译 ERNIE 3.0 Tiny 的`Java SDK`,方便用户开箱即用。 如下图所示,编译 Java SDK 的步骤为:
- 先在 Android Studio 中打开`ernie_tiny/build.gradle`工程文件;
- 选择 Build->Make Module 'android:ernietiny';
-`ernie_tiny/build/outputs/aar`目录中获取编译后得到的 SDK,即`ernie_tiny-debug.aar`.

<img width="1073" alt="image" src="https://user-images.githubusercontent.com/31974251/210746163-41d39478-8d5d-4138-9f76-75ff5c25c295.png">

在获取到`ernie_tiny-debug.aar`后,可将其拷贝到您自己的工程中进行使用。在 Android Studio 中的配置步骤如下:
(1)首先,将`ernie_tiny-debug.aar`拷贝到您 Android 工程的libs目录下。
```bash
├── build.gradle
├── libs
│   └── ernie_tiny-debug.aar
├── proguard-rules.pro
└── src
```
(2)在您的 Android 工程中的 build.gradle 引入 ERNIE 3.0 Tiny SDK,如下
```groovy
dependencies {
implementation fileTree(include: ['ernie_tiny-debug.aar'], dir: 'libs')
implementation 'com.android.support:appcompat-v7:28.0.0'
// ...
}
```

### ERNIE 3.0 Tiny Java API说明如下

- ERNIE 3.0 Tiny `Predictor` 初始化 API: 模型初始化 API 包含两种方式,方式一是通过构造函数直接初始化;方式二是,通过调用 init 函数,在合适的程序节点进行初始化。ERNIE 3.0 Tiny Predictor 初始化参数说明如下:
- modelFile: String, paddle 格式的模型文件路径,如 infer_model.pdmodel
- paramFile: String, paddle 格式的参数文件路径,如 infer_model.pdiparams
- vocabFile: String, 词表文件,如 vocab.txt 每一行包含一个词
- slotLabelsFile: String, 槽位标签文件,如 slots_label.txt
- intentLabelsFile: String, 意图标签文件,如 intent_label.txt 每一行包含一个标签
- addedTokensFile: String, 额外词表文件,如 added_tokens.json,json文件
- runtimeOption: RuntimeOption,可选参数,模型初始化 option。如果不传入该参数则会使用默认的运行时选项。
- maxLength: 最大序列长度,默认为16

```java
public Predictor(); // 空构造函数,之后可以调用init初始化
public Predictor(String modelFile, String paramsFile, String vocabFile, String slotLabelsFile, String intentLabelsFile, String addedTokensFile);
public Predictor(String modelFile, String paramsFile, String vocabFile, String slotLabelsFile, String intentLabelsFile, String addedTokensFile, RuntimeOption runtimeOption, int maxLength);
public boolean init(String modelFile, String paramsFile, String vocabFile, String slotLabelsFile, String intentLabelsFile, String addedTokensFile, RuntimeOption runtimeOption, int maxLength);
```

- ERNIE 3.0 Tiny `Predictor` 预测 API:Predictor 提供 predict 接口对输出的文本进行意图识别。
```java
public IntentDetAndSlotFillResult[] predict(String[] texts);
```

- ERNIE 3.0 Tiny Predictor 资源释放 API: 调用 release() API 可以释放模型资源,返回 true 表示释放成功,false 表示失败;调用 initialized() 可以判断 Predictor 是否初始化成功,true 表示初始化成功,false 表示失败。
```java
public boolean release(); // 释放native资源
public boolean initialized(); // 检查Predictor是否初始化成功
```

- RuntimeOption设置说明

```java
public class RuntimeOption {
public void enableLiteFp16(); // 开启fp16精度推理
public void disableLiteFP16(); // 关闭fp16精度推理(默认关闭)
public void enableLiteInt8(); // 开启int8精度推理(需要先准备好量化模型)
public void disableLiteInt8(); // 关闭int8精度推理(默认关闭)
public void setCpuThreadNum(int threadNum); // 设置线程数
public void setLitePowerMode(LitePowerMode mode); // 设置能耗模式
public void setLitePowerMode(String modeStr); // 通过字符串形式设置能耗模式
}
```

- 意图和槽位识别结果`IntentDetAndSlotFillResult`说明

```java
public class IntentDetAndSlotFillResult {
public String mStr; // 可用于debug的字符串 拼接了意图识别和槽位识别的结果
public boolean mInitialized = false;
public IntentDetResult mIntentResult; // 意图识别结果
public SlotFillResult[] mSlotResult; // 槽位识别结果

static class IntentDetResult {
public String mIntentLabel; // 意图识别结果文本标签
public float mIntentConfidence; // 意图识别结果置信度
}
static class SlotFillResult {
public String mSlotLabel; // 槽位识别结果文本标签
public String mEntity; // 槽位识别的实体
public int[] mPos; // [2] // 在原始文本对应的位置 [start,end]
}
}
```

- ERNIE 3.0 Tiny `Predictor` FP32/FP16 推理示例

```java
import com.baidu.paddle.paddlenlp.ernie_tiny.RuntimeOption;
import com.baidu.paddle.paddlenlp.ernie_tiny.Predictor;
import com.baidu.paddle.paddlenlp.ernie_tiny.IntentDetAndSlotFillResult;
import android.app.Activity;

// 以下为伪代码
class TestERNIETiny extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Predictor predictor = new Predictor();

// 设置模型文件和标签文件
String modelFile = "ernie-tiny/infer_model.pdmodel";
String paramsFile = "ernie-tiny/infer_model.pdiparams";
String vocabFile = "ernie-tiny/vocab.txt";
String slotLabelsFile = "ernie-tiny/slots_label.txt";
String intentLabelsFile = "ernie-tiny/intent_label.txt";
String addedTokensFile = "ernie-tiny/added_tokens.json";

// RuntimeOption 设置
RuntimeOption option = new RuntimeOption();
option.setCpuThreadNum(2); // 设置线程数
option.enableLiteFp16(); // 是否开启FP16精度推理

// Predictor初始化
predictor.init(modelFile, paramsFile, vocabFile, slotLabelsFile, intentLabelsFile, addedTokensFile, option, 16);

// 进行意图识别和槽位分析
String[] inputTexts = new String[]{"来一首周华健的花心", "播放我们都一样", "到信阳市汽车配件城"};

IntentDetAndSlotFillResult[] results = predictor.predict(inputTexts);
}
}
```

- ERNIE 3.0 Tiny `Predictor` Int8 量化模型推理示例

```java
import com.baidu.paddle.paddlenlp.ernie_tiny.RuntimeOption;
import com.baidu.paddle.paddlenlp.ernie_tiny.Predictor;
import com.baidu.paddle.paddlenlp.ernie_tiny.IntentDetAndSlotFillResult;
import android.app.Activity;

// 以下为伪代码
class TestERNIETiny extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Predictor predictor = new Predictor();

// 设置模型文件和标签文件
String modelFile = "ernie-tiny-clip-qat-embedding-int8/infer_model.pdmodel";
String paramsFile = "ernie-tiny-clip-qat-embedding-int8/infer_model.pdiparams";
String vocabFile = "ernie-tiny-clip-qat-embedding-int8/vocab.txt";
String slotLabelsFile = "ernie-tiny-clip-qat-embedding-int8/slots_label.txt";
String intentLabelsFile = "ernie-tiny-clip-qat-embedding-int8/intent_label.txt";
String addedTokensFile = "ernie-tiny-clip-qat-embedding-int8/added_tokens.json";

// RuntimeOption 设置
RuntimeOption option = new RuntimeOption();
option.setCpuThreadNum(2); // 设置线程数
option.enableLiteInt8(); // 开启int8精度推理(需要先准备好量化模型)

// Predictor初始化
predictor.init(modelFile, paramsFile, vocabFile, slotLabelsFile, intentLabelsFile, addedTokensFile, option, 16);

// 进行意图识别和槽位分析
String[] inputTexts = new String[]{"来一首周华健的花心", "播放我们都一样", "到信阳市汽车配件城"};

IntentDetAndSlotFillResult[] results = predictor.predict(inputTexts);
}
}
```

更详细的用法请参考 [ERNIETinyMainActivity](./app/src/main/java/com/baidu/paddle/paddlenlp/app/ernie_tiny/ERNIETinyMainActivity.java) 中的用法

## 替换 App 示例中的 ERNIE 3.0 Tiny 模型

替换 App 示例中的模型的步骤非常简单,模型所在的位置为 `app/src/main/assets/models`。替换模型之前请确保您的模型目录中包含 vocab.txt、slots_label.txt、intent_label.txt以及added_token.json 等意图识别和槽位分析所需要的词表和标签文件。替换模型的步骤为:
- 将您的 ERNIE 3.0 Tiny 模型放在 `app/src/main/assets/models` 目录下;
- 修改 `app/src/main/res/values/strings.xml` 中模型路径的默认值,如:

```xml
<!-- 将这个路径指修改成您的模型,如 models/ernie-tiny -->
<string name="ERNIE_TINY_MODEL_DIR_DEFAULT">models/ernie-tiny</string>
```

## 关于 ERNIE 3.0 Tiny JNI 的封装

如果您对 ERNIE 3.0 Tiny JNI 封装的实现感兴趣,可以参考 [ernie_tiny_jni/predictor_jni.cc](./ernie_tiny/src/main/cpp/ernie_tiny_jni/predictor_jni.cc), 关于如何使用 JNI 进行模型封装并和 Java 通信,可以参考 [FastDeploy/use_cpp_sdk_on_android.md](https://github.com/PaddlePaddle/FastDeploy/blob/develop/docs/cn/faq/use_cpp_sdk_on_android.md) 文档中的说明。

## 相关文档

[ERNIE 3.0 Tiny 模型详细介绍](../../README.md)

[ERNIE 3.0 Tiny 模型C++部署方法](../cpp/README.md)

[ERNIE 3.0 Tiny 模型Python部署方法](../python/README.md)

[FastDeploy SDK 安装文档](https://github.com/PaddlePaddle/FastDeploy/blob/develop/docs/cn/build_and_install/download_prebuilt_libraries.md)
91 changes: 91 additions & 0 deletions model_zoo/ernie-tiny/deploy/android/app/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
apply plugin: 'com.android.application'

android {
compileSdk 28

defaultConfig {
applicationId 'com.baidu.paddle.paddlenlp.app'
minSdkVersion 15
//noinspection ExpiredTargetSdkVersion
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}

buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}

dependencies {
implementation project(path: ':ernie_tiny')
implementation project(path: ':ui')
//noinspection GradleCompatible
implementation 'com.android.support:appcompat-v7:28.0.0'
//noinspection GradleDependency
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
implementation 'com.android.support:design:28.0.0'
implementation 'org.jetbrains:annotations:15.0'
//noinspection GradleDependency
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}

def FD_MODEL = [
[
'src' : 'https://bj.bcebos.com/paddlehub/fastdeploy/ernie-tiny.tgz',
'dest': 'src/main/assets/models'
],
[
'src' : 'https://bj.bcebos.com/paddlehub/fastdeploy/ernie-tiny-clip.tgz',
'dest': 'src/main/assets/models'
],
[
'src' : 'https://bj.bcebos.com/paddlehub/fastdeploy/ernie-tiny-clip-qat.tgz',
'dest': 'src/main/assets/models'
],
[
'src' : 'https://bj.bcebos.com/paddlehub/fastdeploy/ernie-tiny-clip-qat-embedding-int8.tgz',
'dest': 'src/main/assets/models'
]

]

task downloadAndExtractModels(type: DefaultTask) {
doFirst {
println "[INFO] Downloading and extracting models ..."
}
doLast {
String cachePath = "cache"
if (!file("${cachePath}").exists()) {
mkdir "${cachePath}"
}
FD_MODEL.eachWithIndex { model, index ->
String[] modelPaths = model.src.split("/")
String modelName = modelPaths[modelPaths.length - 1]
String modelPrefix = modelName.substring(0, modelName.length() - 4)
boolean copyFiles = false
if (!file("${model.dest}/${modelPrefix}").exists()) {
// Download the target model if not exists
if (!file("${cachePath}/${modelName}").exists()) {
println "[INFO] Downloading ${model.src} -> ${cachePath}/${modelName}"
ant.get(src: model.src, dest: file("${cachePath}/${modelName}"))
}
copyFiles = true
}
if (copyFiles) {
println "[INFO] Taring ${cachePath}/${modelName} -> ${model.dest}/${modelPrefix}"
copy { from(tarTree("${cachePath}/${modelName}")) into("${model.dest}") }
} else {
println "[INFO] ${model.dest}/${modelPrefix} already exists!"
}
}
}
}

preBuild.dependsOn downloadAndExtractModels
21 changes: 21 additions & 0 deletions model_zoo/ernie-tiny/deploy/android/app/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
Loading

0 comments on commit bb88615

Please sign in to comment.