Build the classification app runner

Build the patched runner into a separate directory:

    

        
        
~/executorch/backends/arm/scripts/build_executor_runner.sh \
  --pte="$PWD/module-4-ethos-npu-accelerated-inference/artifacts/mobilenetv2/mobilenetv2_int8_ethosu.pte" \
  --target=ethos-u85-256 \
  --system_config=Ethos_U85_SYS_DRAM_Mid \
  --memory_mode=Shared_Sram \
  --extra_build_flags="-DSEMIHOSTING=ON -DET_ARM_BAREMETAL_SEMIHOSTING_FILE_ALLOCATOR_POOL_SIZE=0x01000000" \
  --output="$PWD/module-4-ethos-npu-accelerated-inference/artifacts/fvp_runner/mobilenetv2_classifier"

    

The same linker warnings described for the generic runner can appear here. Continue if the build reports Built target arm_executor_runner and prints the generated ELF path.

This build uses the same MobileNetV2 .pte artifact as the FVP runner. The difference is the C++ application code around the model call, so no separate Python decoding script is needed afterward.

Run the classification app

Create the same MobileNetV2 input tensor in a separate run folder:

    

        
        
python module-4-ethos-npu-accelerated-inference/scripts/fvp/create_mobilenetv2_input.py \
  --spec module-4-ethos-npu-accelerated-inference/artifacts/mobilenetv2/model_spec.json \
  --image module-4-ethos-npu-accelerated-inference/artifacts/mobilenetv2/sample.jpg \
  --out module-4-ethos-npu-accelerated-inference/artifacts/fvp_runs/mobilenetv2_classifier/i0.bin

    

Run the patched app runner with a separate semihosting folder:

    

        
        
FVP_Corstone_SSE-320 \
  -C mps4_board.subsystem.ethosu.num_macs=256 \
  -C mps4_board.visualisation.disable-visualisation=1 \
  -C vis_hdlcd.disable_visualisation=1 \
  -C mps4_board.telnetterminal0.start_telnet=0 \
  -C mps4_board.uart0.out_file='-' \
  -C mps4_board.uart0.unbuffered_output=1 \
  -C mps4_board.uart0.shutdown_on_eot=1 \
  -C mps4_board.subsystem.cpu0.semihosting-enable=1 \
  -C mps4_board.subsystem.cpu0.semihosting-stack_base=0 \
  -C mps4_board.subsystem.cpu0.semihosting-heap_limit=0 \
  -C "mps4_board.subsystem.cpu0.semihosting-cwd=$(realpath module-4-ethos-npu-accelerated-inference/artifacts/fvp_runs/mobilenetv2_classifier)" \
  -C "mps4_board.subsystem.cpu0.semihosting-cmd_line=executor_runner -o out -i i0.bin" \
  -a "$(realpath module-4-ethos-npu-accelerated-inference/artifacts/fvp_runner/mobilenetv2_classifier/arm_executor_runner)" \
  --timelimit 600 \
  2>&1 | tee module-4-ethos-npu-accelerated-inference/artifacts/fvp_runs/mobilenetv2_classifier/mobilenetv2_classifier.log

    

The UART log should now include lines similar to:

    

        
        
MobileNetV2 output tensor: scalar_type=Float, numel=1000
MobileNetV2 top-5 predictions:
  1. Labrador retriever (class 208): <logit>
  2. golden retriever (class 207): <logit>
  3. <another ImageNet label> (class <id>): <logit>
  4. <another ImageNet label> (class <id>): <logit>
  5. <another ImageNet label> (class <id>): <logit>

    

Consider the following questions:

  • What application behavior is still missing before this could become a production application?
  • What would need validation on physical hardware?

Optional next steps

Build on a C++ Reference Application

The classification patch shows the small change needed to add MobileNetV2 postprocessing to the upstream runner. For a new application, it is often easier to start from source files that separate the ExecuTorch runtime calls from the model-specific output handling.

An Ethos-U C++ reference application provides that initial structure:

FileResponsibility
app_main.cppInitialize ExecuTorch and connect model outputs to the application
model_runner.cppLoad the .pte, allocate method memory, set the input, execute the method, and retrieve outputs
classification_postprocess.cppConvert the MobileNetV2 output tensor into top-five labels and logits
app_config.hDefine the input count, output index, top-k value, and allocator sizes
CMakeLists.txtLink the application with ExecuTorch, the Ethos-U delegate, Arm kernels, and Corstone-320 startup code

The runtime path is visible in model_runner.cpp:

    

        
        
Result<Program> program = Program::load(&loader);
Result<MethodMeta> method_meta = program->method_meta(*method_name);

MemoryManager memory_manager(
    &method_allocator, &planned_memory, &temporary_allocator);
Result<Method> method =
    program->load_method(*method_name, &memory_manager);

method->set_input(input_tensor, 0);
method->execute();
method->get_outputs(outputs.data(), outputs.size());

    

Program::load() reads the serialized ExecuTorch program. method_meta() provides the tensor and memory-plan information needed before the method is loaded. load_method() prepares the executable method with the supplied allocators. The application then sets the prepared input tensor, calls execute(), and retrieves the output values.

The reference build embeds the .pte, i0.bin, and ImageNet labels in the application ELF instead of reading them through semihosting. This makes the application structure closer to one that could be moved to a physical board. Follow the build and FVP commands in its README after completing the main Module 4 workflow.

To adapt it, keep model_runner.cpp and replace the input preparation, classification_postprocess.cpp, and handle_output() with code for the new model and application. Update the visible allocator sizes after checking the new model’s memory plan and the target memory map.

Run the One-Command Flow

ExecuTorch provides examples/arm/run.sh, which combines model export, quantization, lowering, runner compilation, and FVP execution. Use it to see the upstream end-to-end flow after completing the individual stages above:

    

        
        
cd ~/executorch
source examples/arm/arm-scratch/setup_path.sh
./examples/arm/run.sh \
  --output=mv2_u85 \
  --target=ethos-u85-256 \
  --system_config=Ethos_U85_SYS_DRAM_Mid \
  --memory_mode=Shared_Sram \
  --model_name=mv2

    

To inspect the exported and quantized graphs, add --aot_arm_compiler_flags="--debug". This produces a large amount of terminal output.

Note

The wrapper enables delegation and quantization by default.

This command uses the upstream built-in mv2 model. The FVP runs it with an all-ones dummy tensor rather than an image, so any printed class labels are not meaningful predictions.

The command also uses the model’s example tensor for calibration; it does not reproduce the course’s 100-image Open Images calibration method or artifact names.

Previous page Complete and continue