A minimal Kotlin Android app that runs an ExecuTorch
.pte model on-device using the XNNPACK CPU backend. It is wired to load an
Edge Impulse-exported model.
Grab an APK from the Releases page and sideload it — no build required. Each ships a different Edge Impulse model and installs under its own app id, so you can keep all three side by side:
| APK | Model | Input shape | Classes |
|---|---|---|---|
app-classification-debug.apk |
Image classifier | [1, 3, 96, 96] |
lamp, plant, unknown |
app-fomo-debug.apk |
FOMO object detection | [1, 3, 320, 320] |
coffee, lamp |
app-timeseries-debug.apk |
Motion (spectral) | [1, 39] |
idle, snake, updown, wave |
adb install -r app-classification-debug.apkOpen the app and tap Run inference. It runs one forward pass on a placeholder input and shows the output scores — proof the model loads and runs on-device.
app/
src/
main/ <- shared code, UI, launcher icon (no model)
java/com/example/executorchdemo/
MainActivity.kt <- UI + runs one inference on a button tap
EdgeImpulseClassifier.kt <- loads the .pte, runs forward(), reads scores
classification/assets/ <- model.pte + labels.txt + input_shape.txt
fomo/assets/ <- model.pte + labels.txt + input_shape.txt
timeseries/assets/ <- model.pte + labels.txt + input_shape.txt
Each model is a Gradle product flavor (classification, fomo,
timeseries). The bundled assets per flavor are:
model.pte— the ExecuTorch program exported from Edge Impulselabels.txt— one class label per line, in output orderinput_shape.txt— the input tensor shape, e.g.1,3,96,96(NCHW)
The input shape is read at runtime from input_shape.txt, so adding a new model
is just a new flavor + assets — no code change.
ExecuTorch comes from Maven Central (see app/build.gradle.kts):
implementation("org.pytorch:executorch-android:1.0.0")# build every flavor's debug APK
./gradlew assembleDebug
# -> app/build/outputs/apk/<flavor>/debug/app-<flavor>-debug.apk
# build/install a single flavor
./gradlew installClassificationDebug # or installFomoDebug / installTimeseriesDebugIf a flavor has no bundled model you'll see this instead:
Requires the Android SDK (platform 35, build-tools 35) and JDK 17. The SDK
location is read from local.properties (sdk.dir).
- Add a flavor asset folder, e.g.
app/src/<name>/assets/, withmodel.pte,labels.txt, andinput_shape.txt. - Register the flavor in
app/build.gradle.ktsunderproductFlavors. - Replace the all-zeros placeholder input in
MainActivity.runInference()with real, preprocessed data (a normalized camera frame, a sensor window, etc.).