From dd2ea8469e65a135c3360e22d56fbcba6753ddf9 Mon Sep 17 00:00:00 2001 From: sheetalarkadam <100380551+sheetalarkadam@users.noreply.github.com> Date: Thu, 10 Oct 2024 10:37:22 -0700 Subject: [PATCH] Add qnn android package (#22296) ### Description Pre built QNN Android package ### Future Work 1. Setting up CI with Browserstack- onnxruntime_tests and Android test 2. ESRP Release to Maven --- java/build-android.gradle | 29 +++++++++-- .../android/build_aar_and_copy_artifacts.sh | 48 +++++++++++++------ .../github/android/build_aar_package.py | 27 +++++++++++ .../default_qnn_aar_build_settings.json | 19 ++++++++ .../templates/android-java-api-aar.yml | 15 +++++- .../azure-pipelines/templates/c-api-cpu.yml | 15 ++++++ 6 files changed, 134 insertions(+), 19 deletions(-) create mode 100644 tools/ci_build/github/android/default_qnn_aar_build_settings.json diff --git a/java/build-android.gradle b/java/build-android.gradle index fd22fa27e8db9..1410b5388da38 100644 --- a/java/build-android.gradle +++ b/java/build-android.gradle @@ -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 @@ -26,7 +30,11 @@ 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.' @@ -34,6 +42,10 @@ def trainingDescription = 'The onnxruntime-training android package is designed '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 { @@ -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 { @@ -162,6 +175,16 @@ publishing { email = 'onnxruntime@microsoft.com' } } + + 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) + } + } } } } diff --git a/tools/ci_build/github/android/build_aar_and_copy_artifacts.sh b/tools/ci_build/github/android/build_aar_and_copy_artifacts.sh index 88fb578c591b8..690fd55e3811c 100755 --- a/tools/ci_build/github/android/build_aar_and_copy_artifacts.sh +++ b/tools/ci_build/github/android/build_aar_and_copy_artifacts.sh @@ -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 diff --git a/tools/ci_build/github/android/build_aar_package.py b/tools/ci_build/github/android/build_aar_package.py index 036db703ccf27..d6cac4e21bd81 100644 --- a/tools/ci_build/github/android/build_aar_package.py +++ b/tools/ci_build/github/android/build_aar_package.py @@ -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() @@ -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) @@ -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) @@ -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, diff --git a/tools/ci_build/github/android/default_qnn_aar_build_settings.json b/tools/ci_build/github/android/default_qnn_aar_build_settings.json new file mode 100644 index 0000000000000..599c108f830e7 --- /dev/null +++ b/tools/ci_build/github/android/default_qnn_aar_build_settings.json @@ -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" + + ] +} diff --git a/tools/ci_build/github/azure-pipelines/templates/android-java-api-aar.yml b/tools/ci_build/github/azure-pipelines/templates/android-java-api-aar.yml index 73f4620440a6c..f6ec87c754d92 100644 --- a/tools/ci_build/github/azure-pipelines/templates/android-java-api-aar.yml +++ b/tools/ci_build/github/azure-pipelines/templates/android-java-api-aar.yml @@ -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: @@ -88,6 +91,15 @@ 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 \ @@ -95,6 +107,7 @@ jobs: --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}} \ @@ -102,7 +115,7 @@ jobs: -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) diff --git a/tools/ci_build/github/azure-pipelines/templates/c-api-cpu.yml b/tools/ci_build/github/azure-pipelines/templates/c-api-cpu.yml index e99538a595f69..ee91b33c4755c 100644 --- a/tools/ci_build/github/azure-pipelines/templates/c-api-cpu.yml +++ b/tools/ci_build/github/azure-pipelines/templates/c-api-cpu.yml @@ -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: