Skip to content

Commit

Permalink
Add qnn android package (#22296)
Browse files Browse the repository at this point in the history
### Description
Pre built QNN Android package


### Future Work
1. Setting up CI with Browserstack- onnxruntime_tests and Android test
2. ESRP Release to Maven
  • Loading branch information
sheetalarkadam authored Oct 10, 2024
1 parent 709368e commit dd2ea84
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 19 deletions.
29 changes: 26 additions & 3 deletions java/build-android.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ def publishDir = System.properties['publishDir']
def minSdkVer = System.properties['minSdkVer']
def targetSdkVer = System.properties['targetSdkVer']
boolean enableTrainingApis = (System.properties['ENABLE_TRAINING_APIS'] ?: "0") == "1"
// Expected format for qnnVersion: major.minor.patch (e.g., 2.26.0)
// QNN package version does not follow Semantic Versioning (SemVer) format.
// For non qnn builds, qnnVersion will be null
def qnnVersion = System.properties['qnnVersion']

// Since Android requires a higher numbers indicating more recent versions
// This function assume ORT version number will be in formart of A.B.C such as 1.7.0
Expand All @@ -26,14 +30,22 @@ project.version = rootProject.file('../VERSION_NUMBER').text.trim()
project.group = "com.microsoft.onnxruntime"

def tmpArtifactId = enableTrainingApis ? project.name + "-training" : project.name
def mavenArtifactId = tmpArtifactId + '-android'
def mavenArtifactId = tmpArtifactId + '-android' + (qnnVersion != null ? '-qnn' : '')

//should the mavenArtifactId be read from the packageName variable as
//that's how it's used in the build_aar_copy_artifacts.sh while copying the artifacts

def defaultDescription = 'ONNX Runtime is a performance-focused inference engine for ONNX (Open Neural Network ' +
'Exchange) models. This package contains the Android (aar) build of ONNX Runtime. It includes support for all ' +
'types and operators, for ONNX format models. All standard ONNX models can be executed with this package.'
def trainingDescription = 'The onnxruntime-training android package is designed to efficiently train and infer a ' +
'wide range of ONNX models on edge devices, such as mobile phones, tablets, and other portable devices with ' +
'a focus on minimizing resource usage and maximizing accuracy.' +
'See https://github.com/microsoft/onnxruntime-training-examples/tree/master/on_device_training for more details.'
def qnnDescription = 'ONNX Runtime is a performance-focused inference engine for ONNX (Open Neural Network ' +
'Exchange) models. This package contains the Android (aar) build of ONNX Runtime with the QNN Execution Provider.' +
'It includes support for all types and operators, for ONNX format models. All standard ONNX models can be executed' +
'with this package.'

buildscript {
repositories {
Expand Down Expand Up @@ -137,8 +149,9 @@ publishing {
artifact sourcesJar

pom {
name = enableTrainingApis ? 'onnxruntime-training' : 'onnx-runtime'
description = enableTrainingApis ? trainingDescription : defaultDescription
name = qnnVersion != null ? 'onnxruntime-qnn' : (enableTrainingApis ? 'onnxruntime-training' : 'onnx-runtime')
description = qnnVersion != null ? qnnDescription : (enableTrainingApis ? trainingDescription : defaultDescription)

url = 'https://microsoft.github.io/onnxruntime/'
licenses {
license {
Expand All @@ -162,6 +175,16 @@ publishing {
email = '[email protected]'
}
}

if (qnnVersion != null) {
println "Modifying the POM XML to include QNN dependency"
withXml {
def dependencynode = asNode().appendNode('dependencies').appendNode('dependency')
dependencynode.appendNode('groupId', 'com.qualcomm.qti')
dependencynode.appendNode('artifactId', 'qnn-runtime')
dependencynode.appendNode('version', qnnVersion)
}
}
}
}
}
Expand Down
48 changes: 33 additions & 15 deletions tools/ci_build/github/android/build_aar_and_copy_artifacts.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,43 @@ export PATH=/opt/python/cp38-cp38/bin:$PATH

ls /build
ls /build/deps

# User inputs
USE_QNN=${1:-0} # by default qnn will not be included in package

# build the AAR package, using the build settings under /home/onnxruntimedev/.build_settings/
# if there is also include_ops_and_types.config exists in the same folder, use it to build with included ops/types
if [ -f "/home/onnxruntimedev/.build_settings/include_ops_and_types.config" ]; then
python3 /onnxruntime_src/tools/ci_build/github/android/build_aar_package.py \
--build_dir /build \
--config $BUILD_CONFIG \
--android_sdk_path /android_home \
--android_ndk_path /ndk_home \
--include_ops_by_config /home/onnxruntimedev/.build_settings/include_ops_and_types.config \
/home/onnxruntimedev/.build_settings/build_settings.json
else
python3 /onnxruntime_src/tools/ci_build/github/android/build_aar_package.py \
--build_dir /build \
--config $BUILD_CONFIG \
--android_sdk_path /android_home \
--android_ndk_path /ndk_home \
/home/onnxruntimedev/.build_settings/build_settings.json

BUILD_SCRIPT="/onnxruntime_src/tools/ci_build/github/android/build_aar_package.py"
BUILD_SETTINGS="/home/onnxruntimedev/.build_settings/build_settings.json"
INCLUDE_OPS_CONFIG="/home/onnxruntimedev/.build_settings/include_ops_and_types.config"

ANDROID_SDK_HOME="/android_home"
ANDROID_NDK_HOME="/ndk_home"
QNN_HOME="/qnn_home"


# Base command for building the AAR package
COMMAND="python3 $BUILD_SCRIPT --build_dir /build --config $BUILD_CONFIG --android_sdk_path $ANDROID_SDK_HOME --android_ndk_path $ANDROID_NDK_HOME $BUILD_SETTINGS"

# Check if the include ops config file exists and modify command if it does
if [ -f "$INCLUDE_OPS_CONFIG" ]; then
COMMAND+=" --include_ops_by_config $INCLUDE_OPS_CONFIG"
fi

# Add qnn path to command
if [ "$USE_QNN" == "1" ]; then
if [ -d "$QNN_HOME" ]; then
COMMAND+=" --qnn_path $QNN_HOME"
else
echo "Error: QNN directory does not exist."
exit 1
fi
fi

# Execute the build command
eval $COMMAND

# Copy the built artifacts to give folder for publishing
BASE_PATH=/build/aar_out/${BUILD_CONFIG}/com/microsoft/onnxruntime/${PACKAGE_NAME}/${ORT_VERSION}
cp ${BASE_PATH}/${PACKAGE_NAME}-${ORT_VERSION}-javadoc.jar /home/onnxruntimedev/.artifacts
Expand Down
27 changes: 27 additions & 0 deletions tools/ci_build/github/android/build_aar_package.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ def _build_aar(args):
build_settings = _parse_build_settings(args)
build_dir = os.path.abspath(args.build_dir)
ops_config_path = os.path.abspath(args.include_ops_by_config) if args.include_ops_by_config else None
qnn_android_build = "--use_qnn" in build_settings["build_params"]

# Setup temp environment for building
temp_env = os.environ.copy()
Expand All @@ -94,6 +95,26 @@ def _build_aar(args):
base_build_command = [sys.executable, BUILD_PY] + build_settings["build_params"] + ["--config=" + build_config]
header_files_path = ""

if qnn_android_build:
qnn_home = args.qnn_path
sdk_file = os.path.join(qnn_home, "sdk.yaml")
qnn_sdk_version = None
with open(sdk_file) as f:
for line in f:
if line.strip().startswith("version:"):
# yaml file has simple key: value format with version as key
qnn_sdk_version = line.split(":", 1)[1].strip()
break

# Note: The QNN package version does not follow Semantic Versioning (SemVer) format.
# only use major.minor.patch version for qnn sdk version and truncate the build_id info if any
# yaml file typically has version like 2.26.0
if qnn_sdk_version:
qnn_sdk_version = ".".join(qnn_sdk_version.split(".")[:3])
base_build_command += ["--qnn_home=" + qnn_home]
else:
raise ValueError("Error: QNN SDK version not found in sdk.yaml file.")

# Build binary for each ABI, one by one
for abi in build_settings["build_abis"]:
abi_build_dir = os.path.join(intermediates_dir, abi)
Expand Down Expand Up @@ -158,6 +179,10 @@ def _build_aar(args):
),
]

# Add qnn specific parameters
if qnn_android_build:
gradle_command.append(f"-DqnnVersion={qnn_sdk_version}")

# clean, build, and publish to a local directory
subprocess.run([*gradle_command, "clean"], env=temp_env, shell=False, check=True, cwd=JAVA_ROOT)
subprocess.run([*gradle_command, "build"], env=temp_env, shell=False, check=True, cwd=JAVA_ROOT)
Expand All @@ -182,6 +207,8 @@ def parse_args():
"--android_ndk_path", type=str, default=os.environ.get("ANDROID_NDK_HOME", ""), help="Path to the Android NDK"
)

parser.add_argument("--qnn_path", type=str, default=os.environ.get("QNN_HOME", ""), help="Path to the QNN SDK")

parser.add_argument(
"--build_dir",
type=str,
Expand Down
19 changes: 19 additions & 0 deletions tools/ci_build/github/android/default_qnn_aar_build_settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"build_abis": [
"arm64-v8a"
],
"android_min_sdk_version": 21,
"android_target_sdk_version": 24,
"build_params": [
"--enable_lto",
"--android",
"--parallel",
"--cmake_generator=Ninja",
"--build_java",
"--build_shared_lib",
"--use_qnn",
"--cmake_extra_defines=onnxruntime_BUILD_UNIT_TESTS=OFF",
"--skip_tests"

]
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ jobs:

- template: use-android-ndk.yml

- ${{ if contains(parameters.packageName, 'qnn') }}:
- template: jobs/download_linux_qnn_sdk.yml

- task: CmdLine@2
displayName: Build Android AAR Packages
inputs:
Expand All @@ -88,21 +91,31 @@ jobs:
cp ${{parameters.buildSettings}} $(Build.BinariesDirectory)/.build_settings/build_settings.json
[ -f "${{parameters.includedOpsConfig}}" ] && \
cp ${{parameters.includedOpsConfig}} $(Build.BinariesDirectory)/.build_settings/include_ops_and_types.config
# Mount qnn volume if building qnn android package
if [[ ${{ parameters.packageName }} == *qnn* ]]; then
QNN_VOLUME="--volume $(QnnSDKRootDir):/qnn_home"
USE_QNN="1"
else
QNN_VOLUME=""
USE_QNN="0"
fi
docker run --rm \
--volume $(Build.SourcesDirectory):/onnxruntime_src \
--volume $(Build.BinariesDirectory):/build \
--volume $ANDROID_HOME:/android_home \
--volume $NDK_HOME:/ndk_home \
--volume $(artifacts_directory):/home/onnxruntimedev/.artifacts \
--volume $(Build.BinariesDirectory)/.build_settings:/home/onnxruntimedev/.build_settings \
$QNN_VOLUME \
-e NIGHTLY_BUILD \
-e BUILD_BUILDNUMBER \
-e BUILD_CONFIG=${{parameters.buildConfig}} \
-e ORT_VERSION=$(OnnxRuntimeVersion) \
-e PUBLISH_EXECUTABLES=${{parameters.publish_executables}} \
-e PACKAGE_NAME=${{parameters.packageName}} \
onnxruntimecpubuild \
/bin/bash /onnxruntime_src/tools/ci_build/github/android/build_aar_and_copy_artifacts.sh
/bin/bash /onnxruntime_src/tools/ci_build/github/android/build_aar_and_copy_artifacts.sh $USE_QNN
workingDirectory: $(Build.SourcesDirectory)


Expand Down
15 changes: 15 additions & 0 deletions tools/ci_build/github/azure-pipelines/templates/c-api-cpu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,27 @@ stages:
job_name_suffix: 'Full'
publish_executables: '1'
enable_code_sign: ${{ parameters.DoEsrp }}
packageName: 'onnxruntime-android'

- template: android-java-api-aar-test.yml
parameters:
artifactName: 'onnxruntime-android-full-aar'
job_name_suffix: 'Full'

- stage: Android_Java_API_AAR_Packaging_QNN
dependsOn: []
jobs:
- template: android-java-api-aar.yml
parameters:
buildConfig: 'Release'
buildSettings: '$(Build.SourcesDirectory)/tools/ci_build/github/android/default_qnn_aar_build_settings.json'
artifactName: 'onnxruntime-android-qnn-aar'
job_name_suffix: 'QNN'
publish_executables: '0'
enable_code_sign: ${{ parameters.DoEsrp }}
packageName: 'onnxruntime-android-qnn'
#TODO: Add test job for QNN Android AAR

- stage: iOS_Full_xcframework
dependsOn: []
jobs:
Expand Down

0 comments on commit dd2ea84

Please sign in to comment.