From f4395bd17c712a85fef1537475ca9a0aba5ad5b9 Mon Sep 17 00:00:00 2001 From: Duncan Tourolle Date: Sat, 29 Aug 2026 12:02:03 +0200 Subject: [PATCH] Run the face tests on the tablet, where the NEON kernel actually runs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The similarity scan picks its dot product per machine, and the NEON one is the kernel that ships to the phone and the tablet — and the one a desktop cargo test never executes. A wrong lane index or a mishandled tail there is a silent wrong answer on exactly the devices nobody runs the suite on, which is a poor place for the only untested code path. dr-face carries no weights and touches no display, so its tests are a plain ARM64 binary that runs under adb shell with nothing installed. The script builds it against the SDK's newest NDK, pushes it, runs it and cleans up. It checks for the device first, so a tablet that is not plugged in costs a second rather than the two minutes it takes to compile for it. Not wired into CI, which has no device attached. Co-Authored-By: Claude Opus 5 (1M context) --- docs/faces.md | 5 +++ tools/face-tests-on-device.sh | 78 +++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+) create mode 100755 tools/face-tests-on-device.sh diff --git a/docs/faces.md b/docs/faces.md index 56abafb..2ee00c5 100644 --- a/docs/faces.md +++ b/docs/faces.md @@ -674,6 +674,11 @@ app builds for has it, and the explicit `vfmaq` matters because LLVM will not fu add on its own. The portable loop remains the definition the others are tested against. All three produce the same 1,531,969 pairs. +The NEON kernel is the one a desktop `cargo test` never executes, so +`tools/face-tests-on-device.sh` runs the suite on an attached device: `dr-face` carries no weights +and touches no display, so its tests are a plain ARM64 binary that runs under `adb shell` with +nothing installed. Worth running whenever the kernels change. + **Where a regroup's time actually goes**, on that library, because the answer moved twice while it was being looked at: diff --git a/tools/face-tests-on-device.sh b/tools/face-tests-on-device.sh new file mode 100755 index 0000000..121b7b1 --- /dev/null +++ b/tools/face-tests-on-device.sh @@ -0,0 +1,78 @@ +#!/usr/bin/env bash +# Run dr-face's test suite on a connected Android device. +# +# ./tools/face-tests-on-device.sh [extra cargo test args…] +# +# ## Why this exists +# +# `dr-face`'s similarity scan picks its dot product per machine (docs/faces.md +# §9): AVX2 where the CPU has it, **NEON on aarch64**, and a portable loop +# otherwise. The NEON kernel is the one that runs on the phone and the tablet, +# and it is the one a desktop `cargo test` never executes — a wrong lane index +# or a mishandled tail there would be a silent wrong answer on exactly the +# devices nobody runs the test suite on. +# +# `the_fastest_kernel_agrees_with_the_portable_one` is written to catch that, +# and this is how it gets to run on the hardware it is about. Everything else +# in the suite comes along for free: `dr-face` carries no weights and touches +# no display, so its tests are a plain ARM64 binary that runs under `adb +# shell` with nothing installed. +# +# Not part of CI, which has no device attached. Run it when the kernels change. +set -euo pipefail + +TARGET=aarch64-linux-android +API=26 +DEST=/data/local/tmp/dr_face_tests + +ndk="${ANDROID_NDK_HOME:-}" +if [[ -z "$ndk" ]]; then + # The newest NDK the SDK has, which is what the app is built with. + ndk=$(find "${ANDROID_HOME:-$HOME/Android/Sdk}/ndk" -maxdepth 1 -mindepth 1 -type d 2>/dev/null | + sort -V | tail -1) +fi +[[ -n "$ndk" && -d "$ndk" ]] || { + echo "no NDK found — set ANDROID_NDK_HOME" >&2 + exit 1 +} + +clang="$ndk/toolchains/llvm/prebuilt/linux-x86_64/bin/$TARGET$API-clang" +[[ -x "$clang" ]] || { + echo "no $TARGET$API-clang in $ndk" >&2 + exit 1 +} + +rustup target list --installed | grep -qx "$TARGET" || rustup target add "$TARGET" + +# Told to the device before the build, so a missing tablet costs seconds rather +# than the two minutes it takes to compile for it. +adb devices | grep -qw device || { + echo "no device attached — plug the tablet in and enable USB debugging" >&2 + exit 1 +} + +echo "building dr-face tests for $TARGET…" +binary=$( + CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER="$clang" \ + CC_aarch64_linux_android="$clang" \ + cargo test -p dr-face --lib --release --target "$TARGET" --no-run --message-format=json "$@" | + python3 -c ' +import json, sys +for line in sys.stdin: + m = json.loads(line) + if m.get("profile", {}).get("test") and m.get("executable"): + print(m["executable"]) +' | tail -1 +) +[[ -n "$binary" ]] || { + echo "cargo produced no test binary" >&2 + exit 1 +} + +echo "pushing $(basename "$binary")…" +adb push "$binary" "$DEST" >/dev/null +adb shell chmod 755 "$DEST" +# `--test-threads` left alone: the scan spawns its own workers and the point is +# to exercise them the way the app will. +adb shell "$DEST" --color never +adb shell rm -f "$DEST"