The APK's ONNX Runtime is the QNN build, which carries no WebGPU, so a non-Qualcomm phone had nothing above the CPU provider. The APK now also carries Microsoft's stock onnxruntime-android 1.29.0 (32 MB) as libonnxruntime_generic.so, and the app offers it after the QNN build. The runtime search stops at a perfect fit, so on a Qualcomm device the QNN build — listed first — is all that is opened, and the generic build never loads beside it. A Qualcomm SoC is read from ro.soc.manufacturer or, before Android 12, from Qualcomm's FastRPC library being present: a Qualcomm device mistaken for another would trade its Hexagon for the generic rung.
107 lines
5.2 KiB
Bash
Executable File
107 lines
5.2 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# Fetch the inference runtime the Android APK carries (docs/dev/inference.md §3).
|
||
#
|
||
# ./tools/fetch-android-runtime.sh [DEST]
|
||
#
|
||
# Three Maven artefacts, the first two pinned to each other by ONNX Runtime's
|
||
# own POM:
|
||
#
|
||
# com.microsoft.onnxruntime:onnxruntime-android-qnn MIT
|
||
# com.qualcomm.qti:qnn-runtime Qualcomm AI Engine Direct SDK licence
|
||
# com.microsoft.onnxruntime:onnxruntime-android MIT
|
||
#
|
||
# The first is ONNX Runtime built with the CPU and QNN providers; the second
|
||
# is Qualcomm's HTP backend — the ARM-side compiler and the per-generation
|
||
# Hexagon "skel" the DSP loads. The third is Microsoft's stock build, which
|
||
# carries WebGPU (Dawn on Vulkan) and the QNN build does not: the generic
|
||
# rung for a phone without a Qualcomm SoC (§3.2). It lands as
|
||
# `libonnxruntime_generic.so` beside the QNN build; the engine opens the QNN
|
||
# build first, and on a Qualcomm device stops there. All three ship as AARs
|
||
# whose `jni/arm64-v8a/` is what an APK's `lib/arm64-v8a/` wants, so this
|
||
# script unpacks exactly that and nothing else, plus the licence texts, which
|
||
# travel with the libraries (§3.1).
|
||
#
|
||
# ## What is and is not taken from the Qualcomm package
|
||
#
|
||
# Every HTP generation has its own skel and stub, 16 MB a pair. `QNN_HTP_ARCHS`
|
||
# names the ones to ship; the default is the generations in devices sold since
|
||
# the 8 Gen 2 (V73 — the MagicPad 2's 8s Gen 3 is one), through the 8 Elite
|
||
# (V79) and its successor (V81). V66–V69 are 2020–2021 silicon and are left
|
||
# out, which is 50 MB the tablet never loads. `libQnnHtpPrepare.so`, the
|
||
# on-device graph compiler, is 84 MB and cannot be left out: it is what turns
|
||
# an int8 ONNX graph into something the DSP runs, once per device (§5).
|
||
#
|
||
# `libQnnGpu.so` and `libQnnDsp*.so` are not taken: the Adreno rung measured
|
||
# slower than the Hexagon everywhere the Hexagon exists (§1.1), and the cDSP
|
||
# path is the pre-HTP generation.
|
||
#
|
||
# ## Why a script and not a checked-in copy
|
||
#
|
||
# 150 MB of vendor binaries in LFS, re-fetched by every clone, for files that
|
||
# Maven serves with checksums. The cache directory keeps them across builds;
|
||
# CI's is the target cache it already mounts.
|
||
set -euo pipefail
|
||
|
||
ORT_VERSION="${ORT_VERSION:-1.29.0}"
|
||
QNN_VERSION="${QNN_VERSION:-2.42.0}"
|
||
QNN_HTP_ARCHS="${QNN_HTP_ARCHS:-73 75 79 81}"
|
||
DEST="${1:-${PWD}/target-android/runtime}"
|
||
MAVEN="https://repo1.maven.org/maven2"
|
||
|
||
fetch() {
|
||
# A Maven artefact and its SHA-1, verified before anything is unpacked.
|
||
local group="$1" artefact="$2" version="$3" out="$4"
|
||
local url="${MAVEN}/${group//.//}/${artefact}/${version}/${artefact}-${version}.aar"
|
||
if [[ -f "${out}" && -f "${out}.sha1" ]] \
|
||
&& [[ "$(sha1sum "${out}" | cut -d' ' -f1)" == "$(cut -c1-40 "${out}.sha1")" ]]; then
|
||
return
|
||
fi
|
||
echo "==> fetching ${artefact} ${version}"
|
||
curl -fsSL -o "${out}.sha1" "${url}.sha1"
|
||
curl -fsSL -o "${out}" "${url}"
|
||
[[ "$(sha1sum "${out}" | cut -d' ' -f1)" == "$(cut -c1-40 "${out}.sha1")" ]] || {
|
||
echo "error: ${artefact}-${version}.aar does not match its published SHA-1" >&2
|
||
rm -f "${out}"
|
||
exit 1
|
||
}
|
||
}
|
||
|
||
mkdir -p "${DEST}/aar" "${DEST}/lib"
|
||
fetch com.microsoft.onnxruntime onnxruntime-android-qnn "${ORT_VERSION}" \
|
||
"${DEST}/aar/onnxruntime-android-qnn-${ORT_VERSION}.aar"
|
||
fetch com.qualcomm.qti qnn-runtime "${QNN_VERSION}" \
|
||
"${DEST}/aar/qnn-runtime-${QNN_VERSION}.aar"
|
||
fetch com.microsoft.onnxruntime onnxruntime-android "${ORT_VERSION}" \
|
||
"${DEST}/aar/onnxruntime-android-${ORT_VERSION}.aar"
|
||
|
||
# The ONNX Runtime POM names the QNN version it was built against; a pair
|
||
# that disagrees loads and then fails at the first graph, which is the kind
|
||
# of failure the probe would only report as "Hexagon failed".
|
||
pom="${DEST}/aar/onnxruntime-android-qnn-${ORT_VERSION}.pom"
|
||
[[ -f "${pom}" ]] || curl -fsSL -o "${pom}" \
|
||
"${MAVEN}/com/microsoft/onnxruntime/onnxruntime-android-qnn/${ORT_VERSION}/onnxruntime-android-qnn-${ORT_VERSION}.pom"
|
||
wanted="$(sed -n '/<artifactId>qnn-runtime<\/artifactId>/{n;s/.*<version>\(.*\)<\/version>.*/\1/p}' "${pom}")"
|
||
if [[ -n "${wanted}" && "${wanted}" != "${QNN_VERSION}" ]]; then
|
||
echo "error: ONNX Runtime ${ORT_VERSION} was built against QNN ${wanted}, not ${QNN_VERSION}" >&2
|
||
exit 1
|
||
fi
|
||
|
||
rm -rf "${DEST}/lib"
|
||
mkdir -p "${DEST}/lib"
|
||
unzip -q -o -j "${DEST}/aar/onnxruntime-android-qnn-${ORT_VERSION}.aar" \
|
||
'jni/arm64-v8a/libonnxruntime.so' -d "${DEST}/lib"
|
||
unzip -q -o -p "${DEST}/aar/onnxruntime-android-${ORT_VERSION}.aar" \
|
||
'jni/arm64-v8a/libonnxruntime.so' > "${DEST}/lib/libonnxruntime_generic.so"
|
||
members=(jni/arm64-v8a/libQnnHtp.so jni/arm64-v8a/libQnnHtpPrepare.so jni/arm64-v8a/libQnnSystem.so)
|
||
for arch in ${QNN_HTP_ARCHS}; do
|
||
members+=("jni/arm64-v8a/libQnnHtpV${arch}Skel.so" "jni/arm64-v8a/libQnnHtpV${arch}Stub.so")
|
||
done
|
||
unzip -q -o -j "${DEST}/aar/qnn-runtime-${QNN_VERSION}.aar" "${members[@]}" -d "${DEST}/lib"
|
||
unzip -q -o -j "${DEST}/aar/qnn-runtime-${QNN_VERSION}.aar" 'LICENSE.pdf' 'NOTICE.txt' -d "${DEST}" 2>/dev/null || true
|
||
mv -f "${DEST}/LICENSE.pdf" "${DEST}/QNN-LICENSE.pdf" 2>/dev/null || true
|
||
mv -f "${DEST}/NOTICE.txt" "${DEST}/QNN-NOTICE.txt" 2>/dev/null || true
|
||
|
||
echo "==> runtime in ${DEST}/lib:"
|
||
du -sh "${DEST}/lib" | cut -f1 | sed 's/^/ /'
|
||
ls "${DEST}/lib" | sed 's/^/ /'
|