Skip to content

Mobile model tester apps #515

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 14 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .clang-format
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
# Defaults for all languages.
BasedOnStyle: Google

ColumnLimit: 120
# Setting ColumnLimit to 0 so developer choices about where to break lines are maintained.
# Developers are responsible for adhering to the 120 character maximum.
ColumnLimit: 0
SortIncludes: false
DerivePointerAlignment: false

...
15 changes: 15 additions & 0 deletions mobile/examples/model_tester/android/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties
1 change: 1 addition & 0 deletions mobile/examples/model_tester/android/app/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
56 changes: 56 additions & 0 deletions mobile/examples/model_tester/android/app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
}

android {
namespace = "com.onnxruntime.example.modeltester"
compileSdk = 34

defaultConfig {
applicationId = "com.onnxruntime.example.modeltester"
minSdk = 24
targetSdk = 34
versionCode = 1
versionName = "1.0"

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}

buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
externalNativeBuild {
cmake {
path = file("src/main/cpp/CMakeLists.txt")
version = "3.22.1"
}
}
buildFeatures {
viewBinding = true
}
ndkVersion = "28.1.13356709"
}

dependencies {
implementation("androidx.core:core-ktx:1.9.0")
implementation("androidx.appcompat:appcompat:1.7.0")
implementation("com.google.android.material:material:1.12.0")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.2.1")
androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1")
}
21 changes: 21 additions & 0 deletions mobile/examples/model_tester/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
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.onnxruntime.example.modeltester

import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4

import org.junit.Test
import org.junit.runner.RunWith

import org.junit.Assert.*

/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.onnxruntime.example.modeltester", appContext.packageName)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.ModelTester"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html.
# For more examples on how to use CMake, see https://github.com/android/ndk-samples.

# Sets the minimum CMake version required for this project.
cmake_minimum_required(VERSION 3.22.1)

# Declares the project name. The project name can be accessed via ${ PROJECT_NAME},
# Since this is the top level CMakeLists.txt, the project name is also accessible
# with ${CMAKE_PROJECT_NAME} (both CMake variables are in-sync within the top level
# build script scope).
project("modeltester")

set(CMAKE_CXX_STANDARD 20)

# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
#
# In this top level CMakeLists.txt, ${CMAKE_PROJECT_NAME} is used to define
# the target library name; in the sub-module's CMakeLists.txt, ${PROJECT_NAME}
# is preferred for the same purpose.
#
# In order to load a library into your app from Java/Kotlin, you must call
# System.loadLibrary() and pass the name of the library defined here;
# for GameActivity/NativeActivity derived applications, the same library name must be
# used in the AndroidManifest.xml file.
add_library(onnxruntime SHARED IMPORTED)
set_target_properties(onnxruntime PROPERTIES
IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/lib/${ANDROID_ABI}/libonnxruntime.so
INTERFACE_INCLUDE_DIRECTORIES
${CMAKE_SOURCE_DIR}/include)

add_library(${CMAKE_PROJECT_NAME} SHARED
${CMAKE_CURRENT_SOURCE_DIR}/native-lib.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../../../../../common/include/model_runner.h
${CMAKE_CURRENT_SOURCE_DIR}/../../../../../common/model_runner.cpp)

target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/../../../../../common/include)

# Specifies libraries CMake should link to your target library. You
# can link libraries from various origins, such as libraries defined in this
# build script, prebuilt third-party libraries, or Android system libraries.
target_link_libraries(${CMAKE_PROJECT_NAME}
# List libraries link to the target library
android
log
onnxruntime)
116 changes: 116 additions & 0 deletions mobile/examples/model_tester/android/app/src/main/cpp/native-lib.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
#include <android/log.h>

#include <jni.h>

#include <memory>
#include <string>
#include <stdexcept>
#include <vector>

#include "model_runner.h"

namespace util {
struct JstringUtfCharDeleter {
JstringUtfCharDeleter(JNIEnv& env, jstring jstr) : env{env}, jstr{jstr} {}

void operator()(const char* p) {
env.ReleaseStringUTFChars(jstr, p);
}

JNIEnv& env;
jstring jstr;
};

auto MakeUniqueJstringUtfCharPtr(JNIEnv& env, jstring jstr) {
const auto* raw_utf_chars = env.GetStringUTFChars(jstr, nullptr);
return std::unique_ptr<const char, JstringUtfCharDeleter>{
raw_utf_chars, JstringUtfCharDeleter{env, jstr}};
}

std::string JstringToStdString(JNIEnv& env, jstring jstr) {
auto utf_chars = MakeUniqueJstringUtfCharPtr(env, jstr);
return std::string{utf_chars.get()};
}

std::vector<std::string> JstringArrayToStdStrings(JNIEnv& env, jobjectArray jobjs) {
std::vector<std::string> strs;
const auto java_string_class = env.FindClass("java/lang/String");
const auto length = env.GetArrayLength(jobjs);
for (jsize i = 0; i < length; ++i) {
const auto jobj = env.GetObjectArrayElement(jobjs, i);
if (!env.IsInstanceOf(jobj, java_string_class)) {
throw std::runtime_error("jobjectArray element is not a string.");
}
const auto jstr = static_cast<jstring>(jobj);
strs.emplace_back(JstringToStdString(env, jstr));
}
return strs;
}

struct JbyteArrayElementsDeleter {
JbyteArrayElementsDeleter(JNIEnv& env, jbyteArray array) : env{env}, array{array} {}

void operator()(jbyte* p) {
env.ReleaseByteArrayElements(array, p, 0);
}

JNIEnv& env;
jbyteArray array;
};

auto MakeUniqueJbyteArrayElementsPtr(JNIEnv& env, jbyteArray array) {
auto* jbytes_raw = env.GetByteArrayElements(array, nullptr);
return std::unique_ptr<jbyte[], JbyteArrayElementsDeleter>{
jbytes_raw, JbyteArrayElementsDeleter{env, array}};
}
} // namespace util

extern "C" JNIEXPORT jstring JNICALL
Java_com_onnxruntime_example_modeltester_MainActivity_run(JNIEnv* env, jobject thiz,
jbyteArray java_model_bytes,
jint num_iterations,
jstring java_execution_provider_type,
jobjectArray java_execution_provider_option_names,
jobjectArray java_execution_provider_option_values) {
try {
auto model_bytes = util::MakeUniqueJbyteArrayElementsPtr(*env, java_model_bytes);
const size_t model_bytes_length = env->GetArrayLength(java_model_bytes);
auto model_bytes_span = std::span{reinterpret_cast<const std::byte*>(model_bytes.get()),
model_bytes_length};

auto config = model_runner::RunConfig{};
config.model_path_or_bytes = model_bytes_span;
config.num_iterations = num_iterations;

if (java_execution_provider_type != nullptr) {
config.ep.emplace();
config.ep->provider_name = util::JstringToStdString(*env, java_execution_provider_type);

if (java_execution_provider_option_names != nullptr &&
java_execution_provider_option_values != nullptr) {
const auto option_names = util::JstringArrayToStdStrings(*env, java_execution_provider_option_names);
const auto option_values = util::JstringArrayToStdStrings(*env, java_execution_provider_option_values);
if (option_names.size() != option_values.size()) {
throw std::runtime_error("Execution provider option names and values must have the same size.");
}
for (size_t i = 0; i < option_names.size(); ++i) {
config.ep->provider_options.emplace(option_names[i], option_values[i]);
}
}
}

auto result = model_runner::Run(config);

auto summary = model_runner::GetRunSummary(config, result);

return env->NewStringUTF(summary.c_str());
} catch (const std::exception& e) {
const auto java_exception_class = env->FindClass("java/lang/RuntimeException");
env->ThrowNew(java_exception_class, e.what());

__android_log_print(ANDROID_LOG_ERROR, "com.onnxruntime.example.modeltester",
"Error: %s", e.what());

return nullptr;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.onnxruntime.example.modeltester

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.onnxruntime.example.modeltester.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {

private lateinit var binding: ActivityMainBinding

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)

val modelResourceId = R.raw.model
val modelBytes = resources.openRawResource(modelResourceId).readBytes()

val summary = run(modelBytes, 10, null, null, null)

binding.sampleText.text = summary
}

/**
* A native method that is implemented by the 'modeltester' native library,
* which is packaged with this application.
*/
external fun run(modelBytes: ByteArray,
numIterations: Int,
executionProviderType: String?,
executionProviderOptionNames: Array<String>?,
executionProviderOptionValues: Array<String>?,
): String

companion object {
// Used to load the 'modeltester' library on application startup.
init {
System.loadLibrary("modeltester")
}
}
}
Loading
Loading