Skip to content

Commit

Permalink
add android demo
Browse files Browse the repository at this point in the history
  • Loading branch information
huangzhengxiang committed Oct 31, 2024
1 parent 06736c3 commit 215a21c
Show file tree
Hide file tree
Showing 124 changed files with 2,042 additions and 7 deletions.
14 changes: 13 additions & 1 deletion TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
- [x] implement `Chat` to organize the workflow of a chatbot APP.
- [x] change `#define FP16_QSCALE 0.25` in `CPUAttention` to ensure Llama3.2 FP16 correctness.
- [x] `chat_demo` tested on ubuntu 22.04, android(including ARM64, ARM82, OPENCL backend).
- [ ] `chat_demo` supports visual model tasks (support Qwen2-VL demo).
- [ ] `transformers/llm/engine/android` gives an text-only chatbot app based on `Qwen2.5-1.5B-Instruct`.
- [ ] `transformers/llm/engine/android` gives an text+image chatbot app based on `Qwen2-VL-2B-Instruct`.

Motivation:
1. Sampler: performance, variety, different user-preferrence.
Expand All @@ -24,7 +27,7 @@ Motivation:
- [x] llm-export convert Qwen2.5-1.5B-Instructx, Qwen2.5-3B-Instructx, Qwen2.5-7B-Instruct (Qwen2.5 language series) https://qwenlm.github.io/zh/blog/qwen2.5/ (<7B: 32K/8K, >=7B: 128K/8K)
- [x] llm-export convert Llama-3.2-1B-Instructx, Llama-3.2-3B-Instruct (Llama-3.2 language series) https://ai.meta.com/blog/llama-3-2-connect-2024-vision-edge-mobile-devices/ (128K)
- [ ] (optional) llm-export convert Llama-3.2-11B-Vision-Instruct (Llama-3.2 Vision series)
- [ ] (optional) llm-export convert Qwen2-VL-2B-Instruct, Qwen2-VL-7B-Instruct (Qwen2-VL series)
- [x] (optional) llm-export convert Qwen2-VL-2B-Instruct, Qwen2-VL-7B-Instruct (Qwen2-VL series)
- [ ] (optional) llm-export convert Qwen2-Audio-7B-Instruct (Qwen2 Audio series)
- [x] implement `Chat` application in transformers/llm/app/chat.
- [x] implement `LocalSampler` module.
Expand All @@ -38,7 +41,16 @@ Motivation:
```bash
python llmexport.py --path ../../../model/Qwen2.5-1.5B-Instruct/ --dst_path ../../../model/qwen2_5-1_5b-instruct-mnn/ --export mnn --quant_bit 4 --quant_block 128

python llmexport.py --path ../../../model/Qwen2-VL-2B-Instruct/ --dst_path ../../../model/qwen2-vl-2b-instruct-mnn/ --export mnn --quant_bit 4 --quant_block 128

python llmexport.py --path ../../../model/Llama-3.2-3B-Instruct/ --dst_path ../../../model/llama3_2-3b-instruct-mnn --export mnn --quant_bit 4 --quant_block 128

adb push ../../model/qwen2_5-1_5b-instruct-mnn/ /data/local/tmp/llm
adb push ../../model/qwen2-vl-2b-instruct-mnn/ /data/local/tmp/llm
adb push ../../model/llama3_2-3b-instruct-mnn/ /data/local/tmp/llm

cd build/phone
adb push chat_demo libllm.so libMNN_CL.so libMNN_Express.so libMNN.so tools/cv/libMNNOpenCV.so /data/local/tmp/llm
```


Expand Down
12 changes: 6 additions & 6 deletions docs/transformers/optimizations.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@
```bash
# windows
set ANDROID_NDK=D:\NDK\android-ndk
cmake ../.. -DCMAKE_TOOLCHAIN_FILE=%ANDROID_NDK%/build/cmake/android.toolchain.cmake -DCMAKE_BUILD_TYPE=Release -DANDROID_ABI="arm64-v8a" -DANDROID_STL=c++_static -DMNN_USE_LOGCAT=false -DCMAKE_CXX_STANDARD=17 -DMNN_USE_SYSTEM_LIB=OFF -DMNN_BUILD_BENCHMARK=ON -DMNN_USE_SSE=OFF -DMNN_SUPPORT_BF16=OFF -DMNN_BUILD_TEST=ON -DANDROID_NATIVE_API_LEVEL=android-21 -DMNN_BUILD_FOR_ANDROID_COMMAND=true -DNATIVE_LIBRARY_OUTPUT=. -DMNN_LOW_MEMORY=ON -DMNN_BUILD_LLM=ON -DMNN_SUPPORT_TRANSFORMER_FUSE=ON -DMNN_BUILD_SHARED_LIBS=ON
cmake ../.. -DCMAKE_TOOLCHAIN_FILE=%ANDROID_NDK%/build/cmake/android.toolchain.cmake -DCMAKE_BUILD_TYPE=Release -DANDROID_ABI="arm64-v8a" -DANDROID_STL=c++_static -DMNN_USE_LOGCAT=false -DCMAKE_CXX_STANDARD=17 -DMNN_USE_SYSTEM_LIB=OFF -DMNN_BUILD_BENCHMARK=ON -DMNN_USE_SSE=OFF -DMNN_SUPPORT_BF16=OFF -DMNN_BUILD_TEST=ON -DANDROID_NATIVE_API_LEVEL=android-21 -DMNN_BUILD_FOR_ANDROID_COMMAND=true -DNATIVE_LIBRARY_OUTPUT=. -DMNN_LOW_MEMORY=ON -DMNN_BUILD_LLM=ON -DMNN_SUPPORT_TRANSFORMER_FUSE=ON -DMNN_BUILD_SHARED_LIBS=ON -DMNN_BUILD_OPENCV=ON -DMNN_IMGCODECS=ON -DLLM_SUPPORT_VISION=ON
# arm v82 & OPENCL
cmake ../.. -DCMAKE_TOOLCHAIN_FILE=%ANDROID_NDK%/build/cmake/android.toolchain.cmake -DCMAKE_BUILD_TYPE=Release -DANDROID_ABI="arm64-v8a" -DANDROID_STL=c++_static -DMNN_USE_LOGCAT=false -DCMAKE_CXX_STANDARD=17 -DMNN_USE_SYSTEM_LIB=OFF -DMNN_BUILD_BENCHMARK=ON -DMNN_USE_SSE=OFF -DMNN_SUPPORT_BF16=OFF -DMNN_BUILD_TEST=ON -DANDROID_NATIVE_API_LEVEL=android-21 -DMNN_BUILD_FOR_ANDROID_COMMAND=true -DNATIVE_LIBRARY_OUTPUT=. -DMNN_LOW_MEMORY=ON -DMNN_BUILD_LLM=ON -DMNN_SUPPORT_TRANSFORMER_FUSE=ON -DMNN_BUILD_SHARED_LIBS=ON -DMNN_ARM82=ON -DMNN_OPENCL=ON
cmake ../.. -DCMAKE_TOOLCHAIN_FILE=%ANDROID_NDK%/build/cmake/android.toolchain.cmake -DCMAKE_BUILD_TYPE=Release -DANDROID_ABI="arm64-v8a" -DANDROID_STL=c++_static -DMNN_USE_LOGCAT=false -DCMAKE_CXX_STANDARD=17 -DMNN_USE_SYSTEM_LIB=OFF -DMNN_BUILD_BENCHMARK=ON -DMNN_USE_SSE=OFF -DMNN_SUPPORT_BF16=OFF -DMNN_BUILD_TEST=ON -DANDROID_NATIVE_API_LEVEL=android-21 -DMNN_BUILD_FOR_ANDROID_COMMAND=true -DNATIVE_LIBRARY_OUTPUT=. -DMNN_LOW_MEMORY=ON -DMNN_BUILD_LLM=ON -DMNN_SUPPORT_TRANSFORMER_FUSE=ON -DMNN_BUILD_SHARED_LIBS=ON -DMNN_BUILD_OPENCV=ON -DMNN_IMGCODECS=ON -DMNN_ARM82=ON -DMNN_OPENCL=ON -DLLM_SUPPORT_VISION=ON

# linux
export ANDROID_NDK=~/NDK/android-ndk
cmake ../.. -DCMAKE_TOOLCHAIN_FILE=${ANDROID_NDK}/build/cmake/android.toolchain.cmake -DCMAKE_BUILD_TYPE=Release -DANDROID_ABI="arm64-v8a" -DANDROID_STL=c++_static -DMNN_USE_LOGCAT=false -DCMAKE_CXX_STANDARD=17 -DMNN_USE_SYSTEM_LIB=OFF -DMNN_BUILD_BENCHMARK=ON -DMNN_USE_SSE=OFF -DMNN_SUPPORT_BF16=OFF -DMNN_BUILD_TEST=ON -DANDROID_NATIVE_API_LEVEL=android-21 -DMNN_BUILD_FOR_ANDROID_COMMAND=true -DNATIVE_LIBRARY_OUTPUT=. -DMNN_LOW_MEMORY=ON -DMNN_BUILD_LLM=ON -DMNN_SUPPORT_TRANSFORMER_FUSE=ON -DMNN_BUILD_SHARED_LIBS=ON
cmake ../.. -DCMAKE_TOOLCHAIN_FILE=${ANDROID_NDK}/build/cmake/android.toolchain.cmake -DCMAKE_BUILD_TYPE=Release -DANDROID_ABI="arm64-v8a" -DANDROID_STL=c++_static -DMNN_USE_LOGCAT=false -DCMAKE_CXX_STANDARD=17 -DMNN_USE_SYSTEM_LIB=OFF -DMNN_BUILD_BENCHMARK=ON -DMNN_USE_SSE=OFF -DMNN_SUPPORT_BF16=OFF -DMNN_BUILD_TEST=ON -DANDROID_NATIVE_API_LEVEL=android-21 -DMNN_BUILD_FOR_ANDROID_COMMAND=true -DNATIVE_LIBRARY_OUTPUT=. -DMNN_LOW_MEMORY=ON -DMNN_BUILD_LLM=ON -DMNN_SUPPORT_TRANSFORMER_FUSE=ON -DMNN_BUILD_SHARED_LIBS=ON -DMNN_BUILD_OPENCV=ON -DMNN_IMGCODECS=ON -DLLM_SUPPORT_VISION=ON
# arm v82 & OPENCL
cmake ../.. -DCMAKE_TOOLCHAIN_FILE=${ANDROID_NDK}/build/cmake/android.toolchain.cmake -DCMAKE_BUILD_TYPE=Release -DANDROID_ABI="arm64-v8a" -DANDROID_STL=c++_static -DMNN_USE_LOGCAT=false -DCMAKE_CXX_STANDARD=17 -DMNN_USE_SYSTEM_LIB=OFF -DMNN_BUILD_BENCHMARK=ON -DMNN_USE_SSE=OFF -DMNN_SUPPORT_BF16=OFF -DMNN_BUILD_TEST=ON -DANDROID_NATIVE_API_LEVEL=android-21 -DMNN_BUILD_FOR_ANDROID_COMMAND=true -DNATIVE_LIBRARY_OUTPUT=. -DMNN_LOW_MEMORY=ON -DMNN_BUILD_LLM=ON -DMNN_SUPPORT_TRANSFORMER_FUSE=ON -DMNN_BUILD_SHARED_LIBS=ON -DMNN_ARM82=ON -DMNN_OPENCL=ON
cmake ../.. -DCMAKE_TOOLCHAIN_FILE=${ANDROID_NDK}/build/cmake/android.toolchain.cmake -DCMAKE_BUILD_TYPE=Release -DANDROID_ABI="arm64-v8a" -DANDROID_STL=c++_static -DMNN_USE_LOGCAT=false -DCMAKE_CXX_STANDARD=17 -DMNN_USE_SYSTEM_LIB=OFF -DMNN_BUILD_BENCHMARK=ON -DMNN_USE_SSE=OFF -DMNN_SUPPORT_BF16=OFF -DMNN_BUILD_TEST=ON -DANDROID_NATIVE_API_LEVEL=android-21 -DMNN_BUILD_FOR_ANDROID_COMMAND=true -DNATIVE_LIBRARY_OUTPUT=. -DMNN_LOW_MEMORY=ON -DMNN_BUILD_LLM=ON -DMNN_SUPPORT_TRANSFORMER_FUSE=ON -DMNN_BUILD_SHARED_LIBS=ON -DMNN_BUILD_OPENCV=ON -DMNN_IMGCODECS=ON -DMNN_ARM82=ON -DMNN_OPENCL=ON -DLLM_SUPPORT_VISION=ON
```

#### 1.2 compile for pc
Expand All @@ -24,9 +24,9 @@ LLM and TRANSFORMER_FUSE shall be set ON: `-DMNN_BUILD_LLM=ON -DMNN_SUPPORT_TRAN
```bash
mkdir build && mkdir build/pc && cd build/pc
# linux
cmake ../.. -DCMAKE_CXX_STANDARD=17 -DMNN_USE_SYSTEM_LIB=OFF -DMNN_BUILD_SHARED_LIBS=ON -DMNN_BUILD_TRAIN=ON -DMNN_BUILD_QUANTOOLS=ON -DMNN_EVALUATION=ON -DMNN_BUILD_CONVERTER=ON -DMNN_PORTABLE_BUILD=ON -DTFMODEL_OPTIMIZE=ON -DMNN_LOW_MEMORY=ON -DMNN_BUILD_LLM=ON -DMNN_SUPPORT_TRANSFORMER_FUSE=ON -DMNN_BUILD_TEST=ON -DMNN_BUILD_OPENCV=ON
cmake ../.. -DCMAKE_CXX_STANDARD=17 -DMNN_USE_SYSTEM_LIB=OFF -DMNN_BUILD_SHARED_LIBS=ON -DMNN_BUILD_TRAIN=ON -DMNN_BUILD_QUANTOOLS=ON -DMNN_EVALUATION=ON -DMNN_BUILD_CONVERTER=ON -DMNN_PORTABLE_BUILD=ON -DTFMODEL_OPTIMIZE=ON -DMNN_LOW_MEMORY=ON -DMNN_BUILD_LLM=ON -DMNN_SUPPORT_TRANSFORMER_FUSE=ON -DMNN_BUILD_TEST=ON -DMNN_SEP_BUILD=ON -DMNN_BUILD_OPENCV=ON -DMNN_IMGCODECS=ON -DLLM_SUPPORT_VISION=ON
# windows
cmake ../.. -DCMAKE_CXX_STANDARD=17 -DMNN_USE_SYSTEM_LIB=OFF -DMNN_BUILD_SHARED_LIBS=ON -DMNN_BUILD_TRAIN=ON -DMNN_BUILD_QUANTOOLS=ON -DMNN_EVALUATION=ON -DMNN_BUILD_CONVERTER=ON -DMNN_PORTABLE_BUILD=ON -DTFMODEL_OPTIMIZE=ON -DMNN_LOW_MEMORY=ON -DMNN_BUILD_LLM=ON -DMNN_SUPPORT_TRANSFORMER_FUSE=ON -DMNN_BUILD_TEST=ON -DMNN_SEP_BUILD=ON -DMNN_BUILD_OPENCV=ON
cmake ../.. -DCMAKE_CXX_STANDARD=17 -DMNN_USE_SYSTEM_LIB=OFF -DMNN_BUILD_SHARED_LIBS=ON -DMNN_BUILD_TRAIN=ON -DMNN_BUILD_QUANTOOLS=ON -DMNN_EVALUATION=ON -DMNN_BUILD_CONVERTER=ON -DMNN_PORTABLE_BUILD=ON -DTFMODEL_OPTIMIZE=ON -DMNN_LOW_MEMORY=ON -DMNN_BUILD_LLM=ON -DMNN_SUPPORT_TRANSFORMER_FUSE=ON -DMNN_BUILD_TEST=ON -DMNN_SEP_BUILD=ON -DMNN_BUILD_OPENCV=ON -DMNN_IMGCODECS=ON -DLLM_SUPPORT_VISION=ON
make -j20

./llm_demo ../../model/qwen1_5-4b-chat-mnn-f/llm_config.json
Expand Down
1 change: 1 addition & 0 deletions transformers/llm/engine/android/app/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
67 changes: 67 additions & 0 deletions transformers/llm/engine/android/app/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
apply plugin: 'com.android.application'

android {
compileSdkVersion 28
buildToolsVersion "29.0.2"
defaultConfig {
applicationId "com.mnn.llm"
minSdkVersion 21
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {
arguments "-DBUILD_JNI=TRUE", "-DANDROID_STL=c++_shared", "-DLLM_SUPPORT_VISION=TRUE", "-DMNN_OPENCL=TRUE"
abiFilters 'arm64-v8a'
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
externalNativeBuild {
cmake {
path file('../../CMakeLists.txt')
}
}
sourceSets {
main {
jniLibs.srcDirs = ['src/main/jni/libs']
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
packagingOptions {
exclude("META-INF/DEPENDENCIES")
exclude("META-INF/LICENSE")
exclude("META-INF/LICENSE.txt")
exclude("META-INF/license.txt")
exclude("META-INF/NOTICE")
exclude("META-INF/NOTICE.txt")
exclude("META-INF/notice.txt")
exclude("META-INF/ASL2.0")
exclude("META-INF/*.kotlin_module")
}
}

dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support:design:28.0.0'
implementation 'uk.co.chrisjenx:calligraphy:2.2.0'
implementation 'com.android.support:support-v4:28.0.0'
implementation 'com.google.android.material:material:1.0.0'
implementation "com.squareup.okhttp3:okhttp:4.11.0"
}
21 changes: 21 additions & 0 deletions transformers/llm/engine/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
29 changes: 29 additions & 0 deletions transformers/llm/engine/android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.mnn.llm">

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name=".MainActivity"
android:screenOrientation= "portrait"
>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".Conversation"
android:screenOrientation= "portrait"
android:theme="@style/AppTheme.Trans">
</activity>
</application>

<uses-permission android:name="android.permission.INTERNET"/>

</manifest>
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.mnn.llm;

import android.content.Context;
//import android.support.annotation.DrawableRes;
//import android.support.v7.app.AppCompatActivity;
//import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuInflater;
import android.widget.ImageView;
import android.widget.TextView;

import androidx.annotation.DrawableRes;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;

import com.mnn.llm.R;
import uk.co.chrisjenx.calligraphy.CalligraphyContextWrapper;

public class BaseActivity extends AppCompatActivity {
Toolbar toolbar;
TextView title;
public final void changeTitle(int toolbarId, String titlePage){
toolbar = (Toolbar) findViewById(toolbarId);
setSupportActionBar(toolbar);

title = (TextView) toolbar.findViewById(R.id.tv_title);
title.setText(titlePage);
getSupportActionBar().setTitle("");
}
public final void setupToolbar(int toolbarId, String titlePage){
toolbar = (Toolbar) findViewById(toolbarId);
setSupportActionBar(toolbar);

title = (TextView) toolbar.findViewById(R.id.tv_title);
title.setText(titlePage);

getSupportActionBar().setTitle("");
}
public void setupToolbarWithUpNav(int toolbarId, String titlePage, @DrawableRes int res){
toolbar = (Toolbar) findViewById(toolbarId);
setSupportActionBar(toolbar);

title = (TextView) toolbar.findViewById(R.id.tv_title);
title.setText(titlePage);

getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setHomeAsUpIndicator(res);
getSupportActionBar().setTitle("");
}
@Override
protected void attachBaseContext(Context newBase) {
super.attachBaseContext(CalligraphyContextWrapper.wrap(newBase));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.mnn.llm;

import java.io.Serializable;

public class Chat implements Serializable {
public native boolean Init(String modelDir);
public native String Submit(String input);
public native byte[] Response();
public native void Done();
public native void Reset();

static {
System.loadLibrary("llm");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package com.mnn.llm;

import android.app.Activity;
import android.content.res.AssetManager;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;


public class Common {
public static String copyAssetResource2File(Activity activity, String assetsDir) throws IOException, InterruptedException {
AssetManager assetManager = activity.getBaseContext().getAssets();
// make output dir
String outDir = activity.getCacheDir() + "/" + assetsDir;
File outPath = new File(outDir);
if (!outPath.exists()) {
outPath.mkdirs();
}
// visit input files and copy
String[] files = assetManager.list(assetsDir);
int nums = files.length;
CopyThread [] threads = new CopyThread[nums];
for (int i = 0; i < nums; i++) {
System.out.println("MNN_DEBUG: " + files[i]);
String assetsFile = files[i];
if (new File(outDir + '/' + assetsFile).exists()) {
continue;
}
threads[i] = new CopyThread(assetManager, assetsDir + '/' + assetsFile, outDir + '/' + assetsFile);
threads[i].start();
}
for (int i = 0; i < nums; i++) {
if (threads[i] != null) {
threads[i].join();
}
}
return outDir;
}
}

class CopyThread extends Thread {
private AssetManager mAsset;
private String mSrcPath;
private String mDstPath;
public CopyThread(AssetManager asset, String src, String dst) {
mAsset = asset;
mSrcPath = src;
mDstPath = dst;
}
public void run() {
try {
InputStream inS = mAsset.open(mSrcPath);
File outF = new File(mDstPath);
FileOutputStream outS = new FileOutputStream(outF);
int byteCount;
byte[] buffer = new byte[1024];
while ((byteCount = inS.read(buffer)) != -1) {
outS.write(buffer, 0, byteCount);
}
outS.flush();
inS.close();
outS.close();
outF.setReadable(true);
} catch (Exception e) {}
}
}
Loading

0 comments on commit 215a21c

Please sign in to comment.