Files
DarkRoom/tools/fetch-android-runtime.sh
dtourolle 43402bfe5b Give a phone without a Qualcomm SoC the generic WebGPU runtime
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.
2026-10-04 21:00:15 -04:00

107 lines
5.2 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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/^/ /'