Add application-specific output handling

The generic runner proves that the .pte artifact executes and that raw outputs can be copied back to the host. A classification application also needs to interpret the output tensor. Instead of using a separate Python decoding script, incorporate this into the C++ runner.

The provided patch scans the 1,000 MobileNetV2 logits, finds the five largest values, and prints the ImageNet labels, class IDs, and raw logits over UART.

First, use the generate_imagenet_labels_header.py script to generate a C++ label table from the JSON file prepared in the MobileNetV2 preparation step:

    

        
        
python module-4-ethos-npu-accelerated-inference/apps/fvp_image_classifier/generate_imagenet_labels_header.py \
  --labels module-4-ethos-npu-accelerated-inference/artifacts/mobilenetv2/imagenet_labels.json \
  --out ~/executorch/examples/arm/executor_runner/imagenet_labels.h

    

The bare-metal application does not include a JSON parser or load the label file at runtime. This helper performs that work on the Linux host. It reads the JSON array and checks that it contains the 1,000 labels expected by MobileNetV2:

    

        
        
labels = json.loads(args.labels.read_text())
if (
    not isinstance(labels, list)
    or len(labels) != 1000
    or not all(isinstance(label, str) for label in labels)
):
    raise ValueError("Expected a JSON list containing 1000 ImageNet label strings")

    

It then writes a C++ header containing a compile-time array. json.dumps() adds quotes and escapes each label before it is written as a C++ string:

    

        
        
lines = [
    "#pragma once",
    "",
    f"static constexpr int kImageNetLabelCount = {len(labels)};",
    "static constexpr const char* kImageNetLabels[kImageNetLabelCount] = {",
]
lines.extend(f"    {json.dumps(label)}," for label in labels)
lines.extend(["};", ""])

args.out.write_text("\n".join(lines))

    

The label order is unchanged, so ImageNet class index 208, for example, can be printed with kImageNetLabels[208]. When the patched runner is compiled, the string table becomes part of the ELF and no label file is needed on the FVP.

Apply the runner patch from the ExecuTorch repository root:

    

        
        
cd ~/executorch
git apply --directory=examples/arm/executor_runner \
  ~/executorch_on_arm/module-4-ethos-npu-accelerated-inference/apps/fvp_image_classifier/arm_executor_runner_topk.patch
cd ~/executorch_on_arm

    

The application code uses a fixed-size top-five array, so it does not need the standard-library sorting algorithms:

    

        
        
for (size_t i = 0; i < num_classes; i++) {
  float value = 0.0f;
  switch (scalar_type) {
    case ScalarType::Float:
      value = output_tensor.const_data_ptr<float>()[i];
      break;
    case ScalarType::Char:
      value = static_cast<float>(output_tensor.const_data_ptr<int8_t>()[i]);
      break;
    default:
      return;
  }

  for (int j = 0; j < kMobileNetV2TopK; j++) {
    if (value > top_values[j]) {
      for (int m = kMobileNetV2TopK - 1; m > j; m--) {
        top_values[m] = top_values[m - 1];
        top_indices[m] = top_indices[m - 1];
      }
      top_values[j] = value;
      top_indices[j] = static_cast<int>(i);
      break;
    }
  }
}

    

The patch calls this function after the model returns its first output tensor. The generated header adds the 1,000 ImageNet names to the application ELF.

    

        
        
Tensor tensor = outputs[i].toTensor();
print_mobilenetv2_top_k(tensor);

    
Previous page Complete and continue