Run ExecuTorch and validate the output
ExecuTorchSegmenter creates the input tensor from the shape in
model_spec.json, runs the model, and rejects an unexpected output shape:
Tensor inputTensor = Tensor.fromBlob(input.tensor, spec.inputShape);
Tensor outputTensor = module.forward(EValue.from(inputTensor))[0].toTensor();
if (!Arrays.equals(outputTensor.shape(), spec.outputShape)) {
throw new IllegalArgumentException("Unexpected model output shape");
}
Tensor.fromBlob() associates the Java float array with the shape expected by
the exported program. EValue.from() converts the tensor to the value type
used for ExecuTorch method arguments. Module.forward() invokes the exported
forward method and returns an array of EValue outputs; [0].toTensor()
selects the segmentation-logit tensor. Class selection, mask resizing, and
cutout creation remain application code.
The shape check prevents the postprocessor from applying NCHW indexing to an incompatible output.
Create the cutout
For flattened NCHW output, all pixels for one class are contiguous. The score
for a class and pixel is therefore read at classIndex * pixels + pixel:
for (int classIndex = 1; classIndex < classes; classIndex++) {
float score = logits[classIndex * pixels + pixel];
if (score > bestScore) {
bestScore = score;
bestClass = classIndex;
}
}
After counting the winning class at every pixel, MaskPostprocessor chooses
the largest non-background class. It removes the letterbox padding and sets
alpha to 255 for the selected class and 0 elsewhere:
int tensorIndex = (y + padTop) * tensorWidth + x + padLeft;
alphaMask[contentIndex] =
squareMask[tensorIndex] == selectedClass ? (byte) 255 : 0;
Keep runtime work off the UI thread
MainActivity runs model loading and inference on a single background
executor. Only the UI update is sent back to the main thread:
executor.execute(() -> {
CutoutResult result = segmenter.cutout(selectedBitmap);
runOnUiThread(() -> {
lastResult = result;
displayLastResult();
});
});
When the Activity is destroyed, Module.close() is queued after any active
inference and the executor is shut down. This releases the native runtime
without closing it while a model call is still running.
Module.close() releases the native runtime resources owned by the Java
Module. It must run after outstanding inference work has completed so that
an active forward() call does not lose the native state it is using.
You should now understand how you can use the same ExecuTorch preparation workflow from the Edge Linux modules and apply it to an Android application. The model is still prepared ahead of time, exported, lowered, and packaged as a .pte artifact. What changes is how the application packages the artifact, loads it, prepares input tensors, runs inference, handles output, and keeps the UI responsive.
Course complete
Nice work
You completed Optimize and deploy AI inference with ExecuTorch on Arm.