From 2d1900e01a76819092c7034e4a73f729c98d4202 Mon Sep 17 00:00:00 2001
From: Matthias Hertel <11649409+MatthiasHertel80@users.noreply.github.com>
Date: Thu, 10 Sep 2026 18:51:03 +0200
Subject: [PATCH 01/21] Put the pool section attribute after the array
declarator
armclang rejects alignas after a GNU attribute ("'alignas' attribute
cannot be applied to types"); the attribute after the declarator is
accepted by AC6, GCC and Clang.
---
src/app_main.cpp | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/app_main.cpp b/src/app_main.cpp
index 964e6488..4cf21abf 100644
--- a/src/app_main.cpp
+++ b/src/app_main.cpp
@@ -55,8 +55,8 @@ namespace {
constexpr size_t kMethodPoolSize = APP_METHOD_POOL_SIZE;
constexpr size_t kTempPoolSize = APP_TEMP_POOL_SIZE;
-APP_POOL_ATTRIBUTES alignas(16) uint8_t g_method_pool[kMethodPoolSize];
-APP_POOL_ATTRIBUTES alignas(16) uint8_t g_temp_pool[kTempPoolSize];
+alignas(16) uint8_t g_method_pool[kMethodPoolSize] APP_POOL_ATTRIBUTES;
+alignas(16) uint8_t g_temp_pool[kTempPoolSize] APP_POOL_ATTRIBUTES;
} // namespace
From 7f67bfe368bfccf360d04d414ea6c075ece93a81 Mon Sep 17 00:00:00 2001
From: Matthias Hertel <11649409+MatthiasHertel80@users.noreply.github.com>
Date: Thu, 10 Sep 2026 18:57:15 +0200
Subject: [PATCH 02/21] Make the CI FVP run and the flatbuffers check work on
GitHub runners
The FVP loaded the runner's libpython3.11 and died at start-up because
that build's standard library is not at its baked prefix; PYTHONHOME on
the FVP step points it at a complete Python 3.11. The flatbuffers pin
check used grep -q under pipefail, so grep closing the pipe early failed
the step on Linux and macOS.
---
.github/workflows/build.yml | 14 +++++++++++++-
1 file changed, 13 insertions(+), 1 deletion(-)
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 642d87dc..211b7b56 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -33,6 +33,15 @@ jobs:
- name: Activate Arm tool license
uses: ARM-software/cmsis-actions/armlm@v1
+ # The FVP loads the runner's libpython3.11, whose standard library is
+ # not where that build's prefix says; the model then dies at start-up
+ # with "Fatal Python error: init_fs_encoding". PYTHONHOME (set on the
+ # FVP step only) points it at a complete Python 3.11.
+ - name: Set up Python 3.11 for the FVP
+ uses: actions/setup-python@v5
+ with:
+ python-version: '3.11'
+
- name: Cache packs
uses: actions/cache@v6
with:
@@ -60,6 +69,8 @@ jobs:
run: cbuild ${SOLUTION}.csolution.yml --active ${TARGET}
- name: Run on the FVP
+ env:
+ PYTHONHOME: ${{ env.pythonLocation }}
run: |
IMAGE=$(find out -name "${SOLUTION}.axf" -o -name "${SOLUTION}.elf" | head -1)
echo "Running ${IMAGE}"
@@ -140,5 +151,6 @@ jobs:
# flatbuffers must stay at 24.3.25: tosa-tools wants 25.2.10 and is
# installed --no-deps precisely to stop that. See
# requirements-arm-tosa.txt.
- "${PY}" -m pip show flatbuffers | grep -q "Version: 24.3.25" \
+ # No grep -q: with pipefail, grep closing the pipe early fails the step.
+ "${PY}" -m pip show flatbuffers | grep "Version: 24.3.25" \
|| { echo "::error::flatbuffers pin regressed"; exit 1; }
From 2c51f024555f5334300f1871f9cd92f1c4c9c48b Mon Sep 17 00:00:00 2001
From: Matthias Hertel <11649409+MatthiasHertel80@users.noreply.github.com>
Date: Thu, 10 Sep 2026 19:21:46 +0200
Subject: [PATCH 03/21] Bring the documentation in line with the committed RTE
and the FVP shim
--update-rte is no longer needed on a fresh checkout, the manual FVP run
uses the same shim as the Run and Debug buttons, the wheel pin that has
to match the pack is named, the project layout lists the task drop-in and
the shim, and the MLOps flow page names the six components the generated
layer selects. The Docker contrast is gone with the flow it referred to.
---
README.md | 39 +++++++++++++++++++++-----------
cmsis-executorch.csolution.yml | 2 +-
documentation/mlops-flow.md | 16 +++++++------
documentation/pack-provenance.md | 16 +++++++------
requirements.txt | 2 +-
5 files changed, 46 insertions(+), 29 deletions(-)
diff --git a/README.md b/README.md
index 953db2ec..b3cad053 100644
--- a/README.md
+++ b/README.md
@@ -14,7 +14,7 @@ application.
## What the example demonstrates
-- Exporting an ExecuTorch model for Ethos-U using a Python virtual environment (without requiring Docker).
+- Exporting an ExecuTorch model for Ethos-U from a Python virtual environment.
- The pack [`PyTorch::ExecuTorch`](https://www.keil.arm.com/packs/executorch-pytorch/) links only the required and operator components that the ML model needs.
- Managing the NPU and Vela configuration in the CMSIS solution project rather than duplicating it in the Python exporter.
- A three-step flow with a clean hand-over from the CMSIS-Toolbox to an MLOps system: the toolbox describes the target in a `*.cbuild-mlops.yml` file, a script turns that into the AI layer, and the toolbox builds the application.
@@ -37,6 +37,8 @@ cpackget add PyTorch::ExecuTorch@1.4.0
> [!Note]
> The pack and Python exporter versions must match, as the generated `.pte`
> format is consumed by the runtime supplied in `PyTorch::ExecuTorch@1.4.0`.
+> The matching wheel is `executorch==1.4.0.dev20260728` from the PyTorch
+> nightly index, pinned in `requirements-executorch.txt`.
## Quick start
@@ -112,13 +114,14 @@ version of an existing environment.
#### 1. Generate the MLOps information
```bash
-cbuild setup cmsis-executorch.csolution.yml --active SSE-320-U85 --packs --update-rte
+cbuild setup cmsis-executorch.csolution.yml --active SSE-320-U85 --packs
```
This resolves the packs and the active target and writes
`cmsis-executorch.cbuild-mlops.yml`: the processor, NPU and Vela
-settings of the target, and the location of the AI layer. (`--packs` and
-`--update-rte` are only needed on a fresh checkout.)
+settings of the target, and the location of the AI layer. (`--packs`
+installs missing packs and is only needed on a fresh checkout; the layers'
+RTE configuration is committed, so no `--update-rte` is required.)
#### 2. Create the AI layer
@@ -147,11 +150,15 @@ out/cmsis-executorch/SSE-320-U85/Debug/cmsis-executorch.axf
#### 4. Run on the FVP
```bash
-FVP_Corstone_SSE-320 \
- -f board/Corstone-320/fvp_config.txt \
+.vscode/fvp.sh \
+ -f board/Corstone-320/fvp_config.txt --simlimit 60 \
-a out/cmsis-executorch/SSE-320-U85/Debug/cmsis-executorch.axf
```
+`.vscode/fvp.sh` is the model command the Run and Debug buttons use too; on
+Linux and Windows `FVP_Corstone_SSE-320` can be called directly with the same
+arguments.
+
## How model generation works
The target is described by the `mlops:` node in
@@ -193,9 +200,11 @@ changes the operator set needs nothing more than re-running steps 2 and 3.
## Adapting the example
To use a different model, replace or modify `model/model.py` and update the
-model name or input handling as required. `get_calibration_inputs()` returns
-the samples the quantizer is calibrated with; give it representative data for
-a trained model. Then re-run `create_ai_layer.py` and build.
+model name or input handling as required: `INPUT_SHAPE` and
+`get_model(input_shape)` define the input, and
+`get_calibration_inputs(input_shape, calibration_samples)` returns the samples
+the quantizer is calibrated with; give it representative data for a trained
+model. Then re-run `create_ai_layer.py` and build.
To target another Ethos-U configuration, update the target and `mlops:`
settings in the CMSIS solution and re-run all three steps. The generated Vela
@@ -217,10 +226,13 @@ together. More information is available in
| `create_ai_layer.py` | Exports the model for the target and writes the AI layer |
| `ai_layer/` | Generated: component selection and the embedded model data |
| `setup_venv.py` (`.sh` / `.bat`) | Creates the Python environment for the export |
+| `.vscode.d/tasks.json` | The VS Code tasks (venv setup, Create AI layer) merged by the CMSIS Solution extension |
+| `.vscode/fvp.sh`, `.vscode/fvp.Dockerfile` | The FVP model command used by Run and Debug; runs the model in Docker on macOS |
| `board/Corstone-320/` | Corstone-320 platform support and FVP configuration |
-| `src/app_main.cpp` | Loads the model, runs inference, and prints the result |
-| `src/arm_embedded_module.*` | `EmbeddedModule`: ExecuTorch's `Module` class without the POSIX file loading |
-| `documentation/` | The MLOps flow in detail; where the ExecuTorch pack comes from |
+| `src/app_main.cpp` | Loads the model, runs inference, and prints the result; pool sizes overridable with `APP_METHOD_POOL_SIZE`, `APP_TEMP_POOL_SIZE`, `APP_POOL_SECTION` |
+| `src/arm_embedded_module.*` | `EmbeddedModule`: ExecuTorch's `Module` class without the POSIX file loading (BSD-3-Clause, `src/LICENSE-ExecuTorch`) |
+| `documentation/mlops-flow.md` | The MLOps flow in detail |
+| `documentation/pack-provenance.md` | Where the ExecuTorch pack comes from, how to update it |
## Known limitations
@@ -230,7 +242,8 @@ together. More information is available in
## License
The example code is licensed under Apache-2.0; see `LICENSE`. ExecuTorch and
-`src/arm_embedded_module.*`, which is derived from it, use a BSD-3-Clause license.
+`src/arm_embedded_module.*`, which is derived from it, use a BSD-3-Clause
+license; see `src/LICENSE-ExecuTorch`.
## References
diff --git a/cmsis-executorch.csolution.yml b/cmsis-executorch.csolution.yml
index 9b55cfa6..9a9d9e2e 100644
--- a/cmsis-executorch.csolution.yml
+++ b/cmsis-executorch.csolution.yml
@@ -10,7 +10,7 @@
#
# The `mlops:` node below makes `cbuild setup` emit the *.cbuild-mlops.yml;
# create_ai_layer.py reads the NPU and Vela settings from it, exports the model
-# from PyTorch in a venv (no Docker) and writes the AI layer.
+# from PyTorch in the project's venv and writes the AI layer.
solution:
created-for: CMSIS-Toolbox@2.14.1
cdefault:
diff --git a/documentation/mlops-flow.md b/documentation/mlops-flow.md
index f8b4fa9a..acb7d0a4 100644
--- a/documentation/mlops-flow.md
+++ b/documentation/mlops-flow.md
@@ -8,12 +8,12 @@ propagate that information.
```mermaid
flowchart TD
- A["cmsis-executorch.csolution.yml
mlops: node"] -->|"1. cbuild setup --active SSE-320-U85"| B["cmsis-executorch.cbuild-mlops.yml
npu, vela.options, model.clayer"]
+ A["cmsis-executorch.csolution.yml
mlops: node"] -->|"1. cbuild setup --active <target>"| B["cmsis-executorch.cbuild-mlops.yml
npu, vela.options, model.clayer"]
B -->|"2. create_ai_layer.py"| C["EthosUCompileSpec
quantize, delegate, Vela"]
D["model/model.py
TinyCNN"] --> C
C --> E["ai_layer/model_pte.c
the program as a C array"]
C --> F["ai_layer/ai_layer.clayer.yml
component selection"]
- E --> G["3. cbuild --active SSE-320-U85"]
+ E --> G["3. cbuild --active <target>"]
F --> G
G --> H["cmsis-executorch.axf"]
```
@@ -35,7 +35,7 @@ solution:
clayer: $AI-Layer$
name: TinyCNN
simulator:
- target: SSE-320-U85
+ target: SSE-320-U85 # [@] of the FVP
```
`cbuild setup cmsis-executorch.csolution.yml --active SSE-320-U85`
@@ -96,13 +96,15 @@ for an MLOps system. It reads the file and:
| File | Content |
|------|---------|
-| `ai_layer/ai_layer.clayer.yml` | runtime, Ethos-U backend and the operator components the program uses |
+| `ai_layer/ai_layer.clayer.yml` | runtime, kernel utilities and registration, Ethos-U backend and the operator components the program uses |
| `ai_layer/model_pte.c` / `.h` | the program as a 16-byte-aligned C array, `model_pte` / `model_pte_size` |
| `ai_layer/model.pte` | the program itself, for inspection (not committed) |
-For a fully delegated model the component list is short: the runtime, the
-Ethos-U backend, and the int8 boundary `quantize` / `dequantize` kernels.
-Everything else in the pack is never compiled, let alone linked.
+The clayer and the C array are committed, so a checkout builds without
+Python. For a fully delegated model the component list is short: `Runtime`,
+`Kernel Utils`, `Kernel Registration`, `Backend EthosU`, and the int8 boundary
+`Quantized quantize` / `Quantized dequantize` operators. Everything else in
+the pack is never compiled, let alone linked.
## 3. `cbuild` builds the application
diff --git a/documentation/pack-provenance.md b/documentation/pack-provenance.md
index e09592e6..5eadfe11 100644
--- a/documentation/pack-provenance.md
+++ b/documentation/pack-provenance.md
@@ -127,11 +127,12 @@ in order:
build one yourself (above) if the version is not published.
2. **Update both pins** — `PyTorch::ExecuTorch@` in the csolution and in
the cproject.
-3. **Update the Python pin** in `requirements-executorch.txt` to the matching
- `executorch` version, and check the new release's `install_requirements.py`
- for the `torch` and `torchao` versions it expects. Update
- `requirements.txt` (torch) accordingly. The headers of the three
- `requirements*.txt` files explain why the pins are split the way they are.
+3. **Update the Python pins**: `executorch` and `torchao` in
+ `requirements-executorch.txt`, `torch` in `requirements.txt`, following the
+ new release's `install_requirements.py`; `requirements-arm-tosa.txt` (the
+ TOSA serializer and its flatbuffers pin) only changes when the Arm backend
+ asks for it. The headers of the three files explain why the pins are split
+ the way they are.
4. **Rebuild the venv, the AI layer and the project:**
```bash
@@ -144,5 +145,6 @@ in order:
A changed operator set simply shows up in the regenerated
`ai_layer/ai_layer.clayer.yml`.
-Finally, update the version wherever it appears in prose: `README.md`
-(Prerequisites) and this page.
+Finally, update the version wherever it appears in prose: the README
+(Prerequisites and the pack-version note), the csolution header comment and
+this page.
diff --git a/requirements.txt b/requirements.txt
index 44adbf55..412778de 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,4 +1,4 @@
-# Python packages for the model export that resolve from PyPI (venv, no Docker).
+# Python packages for the model export that resolve from PyPI.
#
# torch is pinned here rather than left to executorch: executorch deliberately
# declares no torch dependency ("consumers bring their own" -- see
From 6c1cc652e9eb943fe66ef47175e863473361f711 Mon Sep 17 00:00:00 2001
From: Matthias Hertel <11649409+MatthiasHertel80@users.noreply.github.com>
Date: Thu, 10 Sep 2026 19:22:44 +0200
Subject: [PATCH 04/21] Correct the Corstone-320 layer comments and heap
metadata
The region header claimed pack defaults that were this example's own
values; every Default line now states the SSE_320_BSP value. The layer
advertised a 768 kB heap while the scatter file reserves the 96 kB from
regions_SSE-320.h. The FVP configuration no longer refers to scripts and
tools this repository does not have.
---
board/Corstone-320/Board-U85.clayer.yml | 2 +-
board/Corstone-320/fvp_config.txt | 17 +++++-----
board/Corstone-320/regions_SSE-320.h | 43 +++++++++++++++----------
3 files changed, 36 insertions(+), 26 deletions(-)
diff --git a/board/Corstone-320/Board-U85.clayer.yml b/board/Corstone-320/Board-U85.clayer.yml
index b948a5be..67982cd1 100644
--- a/board/Corstone-320/Board-U85.clayer.yml
+++ b/board/Corstone-320/Board-U85.clayer.yml
@@ -16,7 +16,7 @@ layer:
- STDIN
- STDOUT
- STDERR
- - Heap: 786432
+ - Heap: 98304 # __HEAP_SIZE in regions_SSE-320.h
define:
- CMSIS_target_header: \"Corstone-320.h\"
diff --git a/board/Corstone-320/fvp_config.txt b/board/Corstone-320/fvp_config.txt
index 747268a4..7d357098 100644
--- a/board/Corstone-320/fvp_config.txt
+++ b/board/Corstone-320/fvp_config.txt
@@ -2,14 +2,14 @@
# instance.parameter=value #(type, mode) default = 'def value' : description : [min..max]
#------------------------------------------------------------------------------
mps4_board.subsystem.ethosu.num_macs=256 # (int , init-time) default = '0x400' : Number of 8x8 MACs performed per cycle (128, 256, 512, 1024, or 2048). : [0x80:0x800]
- # NOTE: must match the AOT compile target. model/aot_model_*.py
- # uses target="ethos-u85-256" so this FVP must run with
- # num_macs=256, otherwise the Vela bin stream rejects the
- # NPU config at runtime ("npu.macs_per_cc != optimizer.macs_per_cc").
+ # NOTE: must match the Vela target the model was compiled for:
+ # --accelerator-config ethos-u85-256 in cmsis-executorch.cbuild-mlops.yml
+ # (from the mlops: node of the csolution), otherwise the command
+ # stream is rejected at runtime ("npu.macs_per_cc != optimizer.macs_per_cc").
mps4_board.subsystem.cpu0.INITSVTOR=0x12000000 # (int , init-time) default = '0x11000000' : Secure vector-table offset at reset
mps4_board.subsystem.iotss3_systemcontrol.INITSVTOR_RST=0x12000000 # (int, init-time) default = '0x11000000' : Reset value for INITSVTOR. Should match cpu0.INITSVTOR
- # NOTE: the application's vector table (__Vectors) is linked to BRAM at
- # 0x12000000 (see gcc_linker_script.ld.src), but the FVP default points
+ # NOTE: the application's vector table (__Vectors) is linked to FPGA SRAM at
+ # 0x12000000 (__ROM0_BASE in regions_SSE-320.h), but the FVP default points
# at 0x11000000 where nothing is loaded. The first boot works anyway
# because the ELF loader sets the PC directly -- but any debugger reset
# reboots from INITSVTOR, fetches a garbage MSP/PC and locks the core up
@@ -29,8 +29,9 @@ mps4_board.telnetterminal5.start_telnet=0 # (bool , init-time) defa
mps4_board.uart0.out_file=- # (string, init-time) default = '' : Output file to hold data written by the UART (use '-' to send all output to stdout)
mps4_board.uart0.unbuffered_output=1 # (bool , init-time) default = '0' : Unbuffered output
# NOTE: without this the FVP block-buffers uart0 stdout when it is a
- # pipe (docker run without -t, redirected fvp-gdb logs) and printf
- # output only appears when the FVP exits.
+ # pipe and output only appears when the FVP exits. This example's
+ # stdio goes through semihosting, so uart0 only matters for code
+ # that writes to it directly.
mps4_board.uart0.shutdown_on_eot=1 # (bool , init-time) default = '0' : Shutdown simulation when a EOT (ASCII 4) char is transmitted (useful for regression tests when semihosting is not available)
mps4_board.visualisation.disable-visualisation=1 # (bool , init-time) default = '0' : Enable/disable visualisation
vis_hdlcd.disable_visualisation=1 # (bool , init-time) default = '0' : Enable/disable visualisation
diff --git a/board/Corstone-320/regions_SSE-320.h b/board/Corstone-320/regions_SSE-320.h
index 2733c8ec..fa441955 100644
--- a/board/Corstone-320/regions_SSE-320.h
+++ b/board/Corstone-320/regions_SSE-320.h
@@ -1,3 +1,8 @@
+// Copyright 2026 Arm Limited and/or its affiliates.
+// SPDX-License-Identifier: Apache-2.0
+//
+// Memory regions of the Corstone-320 FVP for this example. Every region that
+// differs from the SSE_320_BSP pack default says so in its comment.
#ifndef REGIONS_SSE_320_H
#define REGIONS_SSE_320_H
@@ -14,9 +19,10 @@
// Base address
// Defines base address of memory region.
// Contains Startup and Vector Table
-// The 128 KB boot ROM is far too small for the ExecuTorch runtime +
-// kernels, so code/rodata (and the Ethos-U cache buffer) are placed in
-// the 2 MB FPGA SRAM. To boot on the FVP, set INITSVTOR to this base.
+// Pack default: 0x11000000 (boot ROM). Its 128 KB are far too small for
+// the ExecuTorch runtime + kernels, so code and rodata are placed in the
+// 2 MB FPGA SRAM. To boot on the FVP, set INITSVTOR to this base
+// (fvp_config.txt does).
#define __ROM0_BASE FPGA_SRAM_S_BASE
// Region size [bytes]
// Defines size of memory region.
@@ -26,33 +32,33 @@
// __ROM1
// Base address
// Defines base address of memory region.
-// Default: FPGA_SRAM_S_BASE
+// Pack default: 0x01000000 (FPGA SRAM, non-secure alias)
#define __ROM1_BASE FPGA_SRAM_S_BASE
// Region size [bytes]
// Defines size of memory region.
-// Default: FPGA_SRAM_S_SIZE
+// Pack default: 0x00200000
#define __ROM1_SIZE FPGA_SRAM_S_SIZE
//
// __ROM2
// Base address
// Defines base address of memory region.
-// Default: DDR4_3_S_BASE
+// Pack default: 0 (region unused)
#define __ROM2_BASE DDR4_3_S_BASE
// Region size [bytes]
// Defines size of memory region.
-// Default: DDR4_3_S_SIZE
+// Pack default: 0
#define __ROM2_SIZE DDR4_3_S_SIZE
//
// __ROM3
// Base address
// Defines base address of memory region.
-// Default: QSPI_FLASH_S_BASE
+// Pack default: 0 (region unused)
#define __ROM3_BASE QSPI_FLASH_S_BASE
// Region size [bytes]
// Defines size of memory region.
-// Default: QSPI_FLASH_S_SIZE
+// Pack default: 0
#define __ROM3_SIZE QSPI_FLASH_S_SIZE
//
@@ -63,50 +69,53 @@
// __RAM0
// Base address
// Defines base address of memory region.
-// Default: DDR4_1_S_BASE
+// Pack default: 0x10000000 (ITCM). 256 MB DDR4 here holds .data/.bss, the two
+// 4 MB inference pools, the Ethos-U cache buffer, heap and stack
#define __RAM0_BASE DDR4_1_S_BASE
// Region size [bytes]
// Defines size of memory region.
-// Default: DDR4_1_S_SIZE
+// Pack default: 0x00008000
#define __RAM0_SIZE DDR4_1_S_SIZE
//
// __RAM1
// Base address
// Defines base address of memory region.
-// Default: SRAM_VM0_S_BASE
+// Pack default: 0x12000000 (FPGA SRAM)
#define __RAM1_BASE SRAM_VM0_S_BASE
// Region size [bytes]
// Defines size of memory region.
-// Default: SRAM_VM0_S_SIZE
+// Pack default: 0x00200000
#define __RAM1_SIZE SRAM_VM0_S_SIZE
//
// __RAM2
// Base address
// Defines base address of memory region.
-// Default: SRAM_VM1_S_BASE
+// Pack default: 0x30000000 (DTCM)
#define __RAM2_BASE SRAM_VM1_S_BASE
// Region size [bytes]
// Defines size of memory region.
-// Default: SRAM_VM1_S_SIZE
+// Pack default: 0x00008000
#define __RAM2_SIZE SRAM_VM1_S_SIZE
//
// __RAM3
// Base address
// Defines base address of memory region.
-// Default: DTCM_S_BASE
+// Pack default: 0x31000000 (SRAM VM0)
#define __RAM3_BASE DTCM_S_BASE
// Region size [bytes]
// Defines size of memory region.
-// Default: DTCM_S_SIZE
+// Pack default: 0x00400000
#define __RAM3_SIZE DTCM_S_SIZE
//
//
// Stack / Heap Configuration
+// Pack defaults: 0x600 stack, 0xC00 heap. The runner's EmbeddedModule
+// keeps its method table and planned buffers on the heap.
// Stack Size (in Bytes) <0x0-0xFFFFFFFF:8>
// Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
#define __STACK_SIZE 0x00001000
From ac99905adcc76326971b93bdfc48af7995b8c1cd Mon Sep 17 00:00:00 2001
From: Matthias Hertel <11649409+MatthiasHertel80@users.noreply.github.com>
Date: Thu, 10 Sep 2026 19:22:44 +0200
Subject: [PATCH 05/21] Ship the BSD license of EmbeddedModule and tidy
generator and runner comments
The arm_embedded_module headers pointed at a LICENSE in the repository
root, which is Apache-2.0; src/LICENSE-ExecuTorch now carries the BSD
text. The generator's docstrings name every component it selects and why
the tensor extension is not among them, and the generated layer's Re-run
line names the argument the script needs.
---
ai_layer/ai_layer.clayer.yml | 4 ++--
cmsis-executorch.cproject.yml | 3 ++-
create_ai_layer.py | 16 ++++++++++-----
model/model.py | 2 +-
setup_venv.py | 5 ++++-
src/LICENSE-ExecuTorch | 37 +++++++++++++++++++++++++++++++++++
src/app_main.cpp | 2 +-
src/arm_embedded_module.cpp | 4 ++--
src/arm_embedded_module.hpp | 4 ++--
9 files changed, 62 insertions(+), 15 deletions(-)
create mode 100644 src/LICENSE-ExecuTorch
diff --git a/ai_layer/ai_layer.clayer.yml b/ai_layer/ai_layer.clayer.yml
index 3216d0cf..c7683445 100644
--- a/ai_layer/ai_layer.clayer.yml
+++ b/ai_layer/ai_layer.clayer.yml
@@ -1,6 +1,6 @@
# Generated by create_ai_layer.py from cmsis-executorch.cbuild-mlops.yml -- do not edit.
-# Re-run `python create_ai_layer.py` after changing model/model.py or the
-# csolution's mlops: node.
+# Re-run `python create_ai_layer.py cmsis-executorch.cbuild-mlops.yml` after changing
+# model/model.py or the csolution's mlops: node.
layer:
type: AI
description: TinyCNN int8 image classifier for Ethos-U85
diff --git a/cmsis-executorch.cproject.yml b/cmsis-executorch.cproject.yml
index 976a94fe..8b37c4bf 100644
--- a/cmsis-executorch.cproject.yml
+++ b/cmsis-executorch.cproject.yml
@@ -27,7 +27,8 @@ project:
files:
- file: ./src/app_main.cpp
# EmbeddedModule: ExecuTorch's Module class without the POSIX file
- # loading, so it builds for bare metal (BSD-3-Clause, from Meta/Arm).
+ # loading, so it builds for bare metal (BSD-3-Clause, see
+ # src/LICENSE-ExecuTorch).
- file: ./src/arm_embedded_module.cpp
- file: ./src/arm_embedded_module.hpp
diff --git a/create_ai_layer.py b/create_ai_layer.py
index 3ab70277..6f1fdd3c 100644
--- a/create_ai_layer.py
+++ b/create_ai_layer.py
@@ -17,8 +17,9 @@
Ethos-U, compile with Vela) and writes the complete AI layer into the
directory of the clayer named under `model.clayer`:
- ai_layer.clayer.yml runtime, Ethos-U backend and the operator components
- the exported program actually uses
+ ai_layer.clayer.yml runtime, kernel utilities and registration, Ethos-U
+ backend and the operator components the exported
+ program actually uses
model_pte.c / .h the ExecuTorch program as a C array
model.pte the program itself, for inspection
@@ -139,7 +140,12 @@ def export_model(spec) -> bytes:
def components(pte: bytes, pack: Path) -> tuple[list[str], list[str]]:
- """Runtime + backend + one operator component per operator the .pte uses."""
+ """Runtime, kernel utils and registration, backend, plus one operator component per operator the .pte uses.
+
+ The pack's "Extension Tensor" is not selected: its tensor_ptr_maker.cpp
+ needs std::random_device, which the LLVM embedded toolchain lacks; the
+ runner wraps its input in a TensorImpl instead.
+ """
pdsc = next(pack.glob("*.pdsc"))
available = set(re.findall(r'Csub="([^"]+)"', pdsc.read_text()))
family = {"aten": "Portable", "quantized_decomposed": "Quantized", "cortex_m": "Cortex-M"}
@@ -187,8 +193,8 @@ def c_array(pte: bytes) -> str:
def clayer(mlops: dict, runtime: list[str], operators: list[str], mlops_file: Path) -> str:
lines = [
f"# Generated by create_ai_layer.py from {mlops_file.name} -- do not edit.",
- "# Re-run `python create_ai_layer.py` after changing model/model.py or the",
- "# csolution's mlops: node.",
+ f"# Re-run `python create_ai_layer.py {mlops_file.name}` after changing",
+ "# model/model.py or the csolution's mlops: node.",
"layer:",
" type: AI",
f" description: {mlops.get('description', mlops['model'].get('name', 'AI layer'))}",
diff --git a/model/model.py b/model/model.py
index 9af63cdc..40c06722 100644
--- a/model/model.py
+++ b/model/model.py
@@ -1,6 +1,6 @@
# Copyright 2026 Arm Limited and/or its affiliates.
# SPDX-License-Identifier: Apache-2.0
-"""The example model: a tiny int8 CNN classifier for Ethos-U85.
+"""The example model: a tiny CNN classifier, quantized to int8 for Ethos-U85 by create_ai_layer.py.
Small enough to export in seconds and fully partition onto the NPU, but real
enough to exercise conv / relu / pool / linear through Vela. Input is a single
diff --git a/setup_venv.py b/setup_venv.py
index 326e6c1a..152eeceb 100755
--- a/setup_venv.py
+++ b/setup_venv.py
@@ -147,7 +147,10 @@ def main() -> int:
"--python",
metavar="VERSION",
type=python_version,
- help="Python version for uv, e.g. 3.12 or 3.12.10 (needs --uv; default: this interpreter)",
+ help=(
+ "Python version for uv, e.g. 3.12 or 3.12.10 (needs --uv; default: the "
+ "interpreter running this script, or uv's own pick through the wrappers)"
+ ),
)
parser.add_argument(
"--recreate",
diff --git a/src/LICENSE-ExecuTorch b/src/LICENSE-ExecuTorch
new file mode 100644
index 00000000..c16e5965
--- /dev/null
+++ b/src/LICENSE-ExecuTorch
@@ -0,0 +1,37 @@
+BSD License
+
+For "ExecuTorch" software
+
+Copyright (c) Meta Platforms, Inc. and affiliates.
+Copyright 2023 Arm Limited and/or its affiliates.
+Copyright (c) Qualcomm Innovation Center, Inc.
+Copyright (c) 2023 Apple Inc.
+Copyright (c) 2024 MediaTek Inc.
+Copyright 2023 NXP
+Copyright (c) 2025 Samsung Electronics Co. LTD
+Copyright (c) Intel Corporation
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ * Neither the name Meta nor the names of its contributors may be used to
+ endorse or promote products derived from this software without specific
+ prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/src/app_main.cpp b/src/app_main.cpp
index 4cf21abf..a173a014 100644
--- a/src/app_main.cpp
+++ b/src/app_main.cpp
@@ -107,7 +107,7 @@ extern "C" int app_main(void) {
}
printf("Test_result: PASS\n");
- printf("\x04"); // EOT stops the FVP simulation
+ printf("\x04"); // EOT stops the FVP (uart0.shutdown_on_eot); a board just sees a 0x04
fflush(stdout);
return 0;
}
diff --git a/src/arm_embedded_module.cpp b/src/arm_embedded_module.cpp
index 96bebe78..bf32ab20 100644
--- a/src/arm_embedded_module.cpp
+++ b/src/arm_embedded_module.cpp
@@ -2,8 +2,8 @@
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
- * This source code is licensed under the BSD-style license found in the
- * LICENSE file in the root directory of this source tree.
+ * This source code is licensed under the BSD-style license found in
+ * src/LICENSE-ExecuTorch (the ExecuTorch license).
*
* Customization for embedded systems by Arm.
*/
diff --git a/src/arm_embedded_module.hpp b/src/arm_embedded_module.hpp
index 5ba324bc..40eb74fc 100644
--- a/src/arm_embedded_module.hpp
+++ b/src/arm_embedded_module.hpp
@@ -2,8 +2,8 @@
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
- * This source code is licensed under the BSD-style license found in the
- * LICENSE file in the root directory of this source tree.
+ * This source code is licensed under the BSD-style license found in
+ * src/LICENSE-ExecuTorch (the ExecuTorch license).
*
* Customization for embedded systems by Arm.
*/
From d83e983df00387502fb2243c81b2bbdf12af34b3 Mon Sep 17 00:00:00 2001
From: Matthias Hertel <11649409+MatthiasHertel80@users.noreply.github.com>
Date: Thu, 10 Sep 2026 19:22:44 +0200
Subject: [PATCH 06/21] Tidy CI, .gitignore and the FVP shim header
CI no longer passes --update-rte (the RTE configuration is committed) and
keys the pack cache on the csolution so a pack bump refreshes it. The
.gitignore loses patterns for tools and directories this repository never
had and gains the FVP log the README and CI write. The shim's header shows
the arguments the launch config really passes and the Windows advice that
the README gives.
---
.github/workflows/build.yml | 4 ++--
.gitignore | 20 ++------------------
.vscode/fvp.sh | 5 +++--
3 files changed, 7 insertions(+), 22 deletions(-)
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 211b7b56..79809e06 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -45,7 +45,7 @@ jobs:
- name: Cache packs
uses: actions/cache@v6
with:
- key: cmsis-packs
+ key: cmsis-packs-${{ hashFiles('*.csolution.yml') }}
path: /home/runner/.cache/arm/packs
# torch + executorch is a multi-gigabyte install and otherwise dominates
@@ -60,7 +60,7 @@ jobs:
run: ./setup_venv.sh
- name: Generate the MLOps information
- run: cbuild setup ${SOLUTION}.csolution.yml --active ${TARGET} --packs --update-rte
+ run: cbuild setup ${SOLUTION}.csolution.yml --active ${TARGET} --packs
- name: Create the AI layer
run: python3 create_ai_layer.py ${SOLUTION}.cbuild-mlops.yml
diff --git a/.gitignore b/.gitignore
index 25b7e898..b7d43238 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,10 +16,8 @@ RTE/_*/
*.cbuild-idx.yml
*.cbuild-pack.yml
*.cbuild-mlops.yml
-uv2csolution.log
*.update@*
.cmd.jlink
-*.cprj
# Compiler-generated dependencies
*.d
@@ -68,27 +66,13 @@ uv2csolution.log
*.pdb
*.dwo
-# Kernel module build outputs
-*.mod*
-*.cmd
-.tmp_versions/
-modules.order
-Module.symvers
-Mkfile.old
-dkms.conf
-
# Regenerated by the CMSIS Solution / clangd extensions on every build, with
# absolute paths for the machine that built it -- so it cannot be committed in
# a portable form. Not committing it loses nothing: it is generated for you.
.clangd
-# SDS playback recordings and FVP logs
-**/*.p.sds
-**/*.p.sds.bak
-**/sdsio.log
-
-# Local support-ticket material (logs, screenshots); never part of the example.
-support-cases/
+# FVP output captured by CI and by the manual run in the README
+fvp_stdout.log
# Editor config. The CMSIS Solution extension regenerates tasks.json,
# settings.json and cmsis.json; only the FVP run/debug wiring is kept: the
diff --git a/.vscode/fvp.sh b/.vscode/fvp.sh
index eb45a426..a10d35f8 100755
--- a/.vscode/fvp.sh
+++ b/.vscode/fvp.sh
@@ -5,7 +5,7 @@
# server, in the latter case with
#
# -D --plugin $AVH_FVP_PLUGINS/GDBServer.so -C GDBServer.port=3333 \
-# -f board/Corstone-320/fvp_config.txt -a .elf
+# -f board/Corstone-320/fvp_config.txt --simlimit 60 -a .hex
#
# It does two things the bare model command cannot:
#
@@ -15,7 +15,8 @@
# * runs the model in Docker on macOS, where Arm publishes no FVP build, with
# the GDB port forwarded to the host so arm-none-eabi-gdb can reach it.
#
-# On Linux and Windows (git-bash/MSYS) it just execs the real model.
+# On Linux it just execs the real model. On Windows, where the extension cannot
+# run a bash script, point the csolution's `model:` at FVP_Corstone_SSE-320.exe.
set -euo pipefail
MODEL="${FVP_MODEL:-FVP_Corstone_SSE-320}"
From fa6c2725e31e2ff382537912d835f4a6ffec2094 Mon Sep 17 00:00:00 2001
From: Matthias Hertel <11649409+MatthiasHertel80@users.noreply.github.com>
Date: Thu, 10 Sep 2026 20:19:52 +0200
Subject: [PATCH 07/21] Move to ExecuTorch 1.4.1
ExecuTorch 1.4.1 is on PyPI together with torch 2.13.0 and torchao
0.18.0, so the nightly index and requirements-executorch.txt go; every
pin lives in requirements.txt. The pack pin follows. The generated layer
now names the pack version cbuild setup resolved, and the generator reads
that version by the csolution's exact pin: the cbuild-pack.yml lock keeps
earlier resolutions, and an unversioned selector in the layer left the
previous pack listed next to the pinned one. Same 8832-byte program and
logits as with 1.4.0, verified on the FVP.
---
README.md | 8 +++----
ai_layer/ai_layer.clayer.yml | 2 +-
cmsis-executorch.cproject.yml | 2 +-
cmsis-executorch.csolution.yml | 6 +++---
create_ai_layer.py | 36 ++++++++++++++++++++++++--------
documentation/pack-provenance.md | 18 +++++++---------
requirements-executorch.txt | 17 ---------------
requirements.txt | 20 +++++++++---------
setup_venv.py | 13 +++---------
9 files changed, 57 insertions(+), 65 deletions(-)
delete mode 100644 requirements-executorch.txt
diff --git a/README.md b/README.md
index b3cad053..70a921a7 100644
--- a/README.md
+++ b/README.md
@@ -31,14 +31,14 @@ application.
The pack [`PyTorch::ExecuTorch`](https://www.keil.arm.com/packs/executorch-pytorch/) can be optionally installed manually with:
```bash
-cpackget add PyTorch::ExecuTorch@1.4.0
+cpackget add PyTorch::ExecuTorch@1.4.1
```
> [!Note]
> The pack and Python exporter versions must match, as the generated `.pte`
-> format is consumed by the runtime supplied in `PyTorch::ExecuTorch@1.4.0`.
-> The matching wheel is `executorch==1.4.0.dev20260728` from the PyTorch
-> nightly index, pinned in `requirements-executorch.txt`.
+> format is consumed by the runtime supplied in `PyTorch::ExecuTorch@1.4.1`.
+> The matching wheel is `executorch==1.4.1` from PyPI, pinned in
+> `requirements.txt`.
## Quick start
diff --git a/ai_layer/ai_layer.clayer.yml b/ai_layer/ai_layer.clayer.yml
index c7683445..1322ac2c 100644
--- a/ai_layer/ai_layer.clayer.yml
+++ b/ai_layer/ai_layer.clayer.yml
@@ -6,7 +6,7 @@ layer:
description: TinyCNN int8 image classifier for Ethos-U85
packs:
- - pack: PyTorch::ExecuTorch
+ - pack: PyTorch::ExecuTorch@1.4.1
define:
- ET_LOG_ENABLED: 0
diff --git a/cmsis-executorch.cproject.yml b/cmsis-executorch.cproject.yml
index 8b37c4bf..5128d15f 100644
--- a/cmsis-executorch.cproject.yml
+++ b/cmsis-executorch.cproject.yml
@@ -7,7 +7,7 @@
# the Board layer brings the board bring-up.
project:
packs:
- - pack: PyTorch::ExecuTorch@1.4.0
+ - pack: PyTorch::ExecuTorch@1.4.1
- pack: ARM::CMSIS
misc:
diff --git a/cmsis-executorch.csolution.yml b/cmsis-executorch.csolution.yml
index 9a9d9e2e..43ac3739 100644
--- a/cmsis-executorch.csolution.yml
+++ b/cmsis-executorch.csolution.yml
@@ -18,9 +18,9 @@ solution:
packs:
# Pinned exactly: the pack's C++ runtime and the Python exporter pinned in
- # requirements-executorch.txt must be the same ExecuTorch version, or the
- # .pte fails to load. `cbuild --packs` fetches it from the public index.
- - pack: PyTorch::ExecuTorch@1.4.0
+ # requirements.txt must be the same ExecuTorch version, or the .pte fails
+ # to load. `cbuild setup --packs` fetches it from the public index.
+ - pack: PyTorch::ExecuTorch@1.4.1
- pack: ARM::CMSIS
- pack: ARM::CMSIS-NN
- pack: ARM::CMSIS-Compiler
diff --git a/create_ai_layer.py b/create_ai_layer.py
index 6f1fdd3c..72c57f4a 100644
--- a/create_ai_layer.py
+++ b/create_ai_layer.py
@@ -66,19 +66,36 @@ def pack_root() -> Path:
return Path(os.environ.get("XDG_CACHE_HOME", Path.home() / ".cache")) / "arm/packs"
-def executorch_pack(mlops_file: Path) -> Path:
- """Directory of the ExecuTorch pack version that cbuild setup resolved."""
+def executorch_version(mlops_file: Path) -> str:
+ """The ExecuTorch pack version cbuild setup resolved, from .cbuild-pack.yml.
+
+ The file is a lock file that keeps earlier resolutions, so an unversioned
+ selector can still point at an older pack; the entry selected by the
+ csolution's exact pin (PyTorch::ExecuTorch@) is the one in use.
+ """
import yaml
pack_file = mlops_file.with_name(mlops_file.name.replace(".cbuild-mlops.yml", ".cbuild-pack.yml"))
+ fallback = None
for entry in yaml.safe_load(pack_file.read_text())["cbuild-pack"]["resolved-packs"]:
name, _, version = entry["resolved-pack"].partition("@")
- if name == PACK and "selected-by-pack" in entry:
- vendor, _, pack = PACK.partition("::")
- return pack_root() / vendor / pack / version
+ selectors = entry.get("selected-by-pack", [])
+ if name != PACK or not selectors:
+ continue
+ if f"{PACK}@{version}" in selectors:
+ return version
+ fallback = fallback or version
+ if fallback:
+ return fallback
sys.exit(f"{pack_file}: {PACK} is not among the resolved packs")
+def executorch_pack(version: str) -> Path:
+ """Directory of the installed ExecuTorch pack of that version."""
+ vendor, _, pack = PACK.partition("::")
+ return pack_root() / vendor / pack / version
+
+
def compile_spec(mlops: dict, mlops_dir: Path):
"""EthosUCompileSpec from the npu: and vela: nodes of the cbuild-mlops.yml."""
from executorch.backends.arm.ethosu import EthosUCompileSpec
@@ -190,7 +207,7 @@ def c_array(pte: bytes) -> str:
"""
-def clayer(mlops: dict, runtime: list[str], operators: list[str], mlops_file: Path) -> str:
+def clayer(mlops: dict, runtime: list[str], operators: list[str], mlops_file: Path, version: str) -> str:
lines = [
f"# Generated by create_ai_layer.py from {mlops_file.name} -- do not edit.",
f"# Re-run `python create_ai_layer.py {mlops_file.name}` after changing",
@@ -200,7 +217,7 @@ def clayer(mlops: dict, runtime: list[str], operators: list[str], mlops_file: Pa
f" description: {mlops.get('description', mlops['model'].get('name', 'AI layer'))}",
"",
" packs:",
- f" - pack: {PACK}",
+ f" - pack: {PACK}@{version}",
"",
" define:",
" - ET_LOG_ENABLED: 0",
@@ -235,13 +252,14 @@ def main() -> None:
layer_dir = layer_file.parent
pte = export_model(compile_spec(mlops, mlops_file.parent))
- runtime, operators = components(pte, executorch_pack(mlops_file))
+ version = executorch_version(mlops_file)
+ runtime, operators = components(pte, executorch_pack(version))
layer_dir.mkdir(parents=True, exist_ok=True)
(layer_dir / "model.pte").write_bytes(pte)
(layer_dir / f"{SYMBOL}.c").write_text(c_array(pte), newline="\n")
(layer_dir / f"{SYMBOL}.h").write_text(HEADER, newline="\n")
- layer_file.write_text(clayer(mlops, runtime, operators, mlops_file), newline="\n")
+ layer_file.write_text(clayer(mlops, runtime, operators, mlops_file, version), newline="\n")
print(f"[ai_layer] {len(pte)} byte program, operators: {operators}")
print(f"[ai_layer] wrote {layer_file}")
diff --git a/documentation/pack-provenance.md b/documentation/pack-provenance.md
index 5eadfe11..e27c0420 100644
--- a/documentation/pack-provenance.md
+++ b/documentation/pack-provenance.md
@@ -11,14 +11,14 @@ The pack is published as an asset of the matching ExecuTorch GitHub release,
which is also what its `.pdsc` declares as its download location:
```xml
-https://github.com/pytorch/executorch/releases/download/v1.4.0/
+https://github.com/pytorch/executorch/releases/download/v1.4.1/
```
So the normal acquisition routes work, and `cbuild setup ... --packs` takes
care of it on a fresh clone. To install it by hand:
```bash
-cpackget add PyTorch::ExecuTorch@1.4.0
+cpackget add PyTorch::ExecuTorch@1.4.1
```
The version is pinned exactly, in `cmsis-executorch.csolution.yml` and
@@ -26,10 +26,10 @@ The version is pinned exactly, in `cmsis-executorch.csolution.yml` and
```yaml
packs:
- - pack: PyTorch::ExecuTorch@1.4.0
+ - pack: PyTorch::ExecuTorch@1.4.1
```
-Both must agree. The pin is exact rather than a `@^1.4.0` range because the
+Both must agree. The pin is exact rather than a `@^1.4.1` range because the
pack's C++ runtime and the Python exporter have to be the *same* ExecuTorch
version — see [Moving to a new ExecuTorch version](#moving-to-a-new-executorch-version).
@@ -127,12 +127,10 @@ in order:
build one yourself (above) if the version is not published.
2. **Update both pins** — `PyTorch::ExecuTorch@` in the csolution and in
the cproject.
-3. **Update the Python pins**: `executorch` and `torchao` in
- `requirements-executorch.txt`, `torch` in `requirements.txt`, following the
- new release's `install_requirements.py`; `requirements-arm-tosa.txt` (the
- TOSA serializer and its flatbuffers pin) only changes when the Arm backend
- asks for it. The headers of the three files explain why the pins are split
- the way they are.
+3. **Update the Python pins**: `executorch`, `torch` and `torchao` in
+ `requirements.txt`, following the new release's `install_requirements.py`;
+ `requirements-arm-tosa.txt` (the TOSA serializer and its flatbuffers pin)
+ only changes when the Arm backend's `requirements-arm-tosa.txt` does.
4. **Rebuild the venv, the AI layer and the project:**
```bash
diff --git a/requirements-executorch.txt b/requirements-executorch.txt
deleted file mode 100644
index 17496629..00000000
--- a/requirements-executorch.txt
+++ /dev/null
@@ -1,17 +0,0 @@
-# ExecuTorch and torchao. Separate from requirements.txt because these resolve
-# from the PyTorch nightly index, and pointing pip at that index for the whole
-# install would let it prefer a nightly `torch` build over the PyPI release
-# pinned in requirements.txt. setup_venv.py installs this file on its own.
-#
-# ExecuTorch 1.4.0 is not on PyPI yet (the newest published release is 1.3.1).
-# These wheels are cut from the same release/1.4 branch as the
-# PyTorch::ExecuTorch@1.4.0 pack, and are prebuilt for Linux, macOS and Windows.
-# Once 1.4.0 is on PyPI, `executorch==1.4.0` can move into requirements.txt.
---index-url https://download.pytorch.org/whl/nightly/cpu
---extra-index-url https://pypi.org/simple
-executorch==1.4.0.dev20260728
-
-# The Arm quantizer imports torchao. ExecuTorch 1.4 pins the nightly below
-# (TORCHAO_NIGHTLY_VERSION in install_requirements.py); PyPI is still on 0.17.0,
-# so this one cannot come from requirements.txt yet.
-torchao==0.18.0.dev20260715
diff --git a/requirements.txt b/requirements.txt
index 412778de..f4a091bc 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,15 +1,15 @@
-# Python packages for the model export that resolve from PyPI.
+# Python packages for the model export, all from PyPI. setup_venv.py installs
+# this file first and then requirements-arm-tosa.txt (which needs --no-deps).
#
-# torch is pinned here rather than left to executorch: executorch deliberately
-# declares no torch dependency ("consumers bring their own" -- see
-# _base_dependencies() in executorch's setup.py), so the pin has to live on our
-# side. ExecuTorch 1.4 targets torch 2.13.0; see install_requirements.py on the
-# release/1.4 branch. Earlier torch releases are not compatible with 1.4.
+# executorch, torch and torchao must match: a .pte exported by one ExecuTorch
+# version only loads in the runtime of the same version, and that runtime is
+# the PyTorch::ExecuTorch@1.4.1 pack pinned in the csolution. ExecuTorch 1.4.1
+# targets torch 2.13.0 and torchao 0.18.0 (install_requirements.py on the
+# release/1.4 branch); prebuilt wheels exist for Linux, macOS and Windows,
+# Python 3.10 to 3.14.
+executorch==1.4.1
torch==2.13.0
+torchao==0.18.0
# Vela, the Ethos-U compiler. Same pin as executorch's `ethos_u` extra.
ethos-u-vela==5.1.0
-
-# executorch and torchao come from the PyTorch nightly index and therefore live
-# in requirements-executorch.txt; the TOSA serializer needs --no-deps and lives
-# in requirements-arm-tosa.txt. setup_venv.py installs all three in order.
diff --git a/setup_venv.py b/setup_venv.py
index 152eeceb..d6a7d28c 100755
--- a/setup_venv.py
+++ b/setup_venv.py
@@ -112,9 +112,7 @@ def check_venv_python(python: Path, requested: tuple[int, ...] | None) -> None:
def pip(python: Path, *args: str, uv: str | None = None, env: dict[str, str] | None = None) -> None:
if uv:
- # Match pip's selection across PyPI and the PyTorch nightly index:
- # uv's default first-index strategy can hide the pinned nightly wheels.
- cmd = [uv, "pip", *args, "--python", str(python), "--index-strategy", "unsafe-best-match"]
+ cmd = [uv, "pip", *args, "--python", str(python)]
else:
cmd = [str(python), "-m", "pip", *args]
print(f"+ {' '.join(cmd)}", flush=True)
@@ -195,15 +193,10 @@ def main() -> int:
if not uv:
pip(python, "install", "--upgrade", "pip")
- # Pass 1: everything that resolves from PyPI. Kept free of any index
- # directive so pip cannot prefer a nightly torch over the pinned release.
+ # Pass 1: everything that resolves from PyPI.
pip(python, "install", "-r", str(HERE / "requirements.txt"), uv=uv)
- # Pass 2: executorch + torchao from the PyTorch nightly index (see the
- # file header of requirements-executorch.txt).
- pip(python, "install", "-r", str(HERE / "requirements-executorch.txt"), uv=uv)
-
- # Pass 3: the TOSA serializer, without dependencies. See the header of
+ # Pass 2: the TOSA serializer, without dependencies. See the header of
# requirements-arm-tosa.txt for why --no-deps is load-bearing.
# CMAKE_POLICY_VERSION_MINIMUM keeps an sdist fallback building under CMake 4.
env = dict(os.environ, CMAKE_POLICY_VERSION_MINIMUM="3.5")
From c1cf977a43b5766f3eae73d4deba1835f399aaf5 Mon Sep 17 00:00:00 2001
From: Matthias Hertel <11649409+MatthiasHertel80@users.noreply.github.com>
Date: Thu, 10 Sep 2026 20:38:28 +0200
Subject: [PATCH 08/21] Build with AC6, GCC and CLANG in CI
The Corstone-320 job becomes a toolchain matrix and runs the FVP for
each image; the LLVM embedded toolchain joins vcpkg-configuration.json.
Only AC6 was exercised before, which is how a component that does not
build with the LLVM toolchain could go unnoticed.
---
.github/workflows/build.yml | 17 +-
.../SSE-320-FVP/gcc_linker_script.ld.src | 308 ++++++++++++++++++
vcpkg-configuration.json | 3 +-
3 files changed, 321 insertions(+), 7 deletions(-)
create mode 100644 board/Corstone-320/RTE/Device/SSE-320-FVP/gcc_linker_script.ld.src
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 79809e06..ee5ecc66 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -2,7 +2,7 @@
# SPDX-License-Identifier: Apache-2.0
#
# CI for the three-step flow (cbuild setup, create_ai_layer.py, cbuild)
-# followed by an FVP run.
+# followed by an FVP run, with each of the three toolchains.
name: Build and Run
on:
@@ -17,12 +17,17 @@ env:
TARGET: SSE-320-U85
jobs:
- # Full build plus an FVP run. Linux only: the FVP needs an Arm user-based
- # license, so matrixing this across hosts is not cheap.
+ # Full build plus an FVP run, once per toolchain. Linux only: the FVP needs
+ # an Arm user-based license, so matrixing this across hosts is not cheap.
build-and-run:
- name: Build and run on Corstone-320
+ name: Build and run on Corstone-320 (${{ matrix.toolchain }})
runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ toolchain: [AC6, GCC, CLANG]
+
steps:
- name: Checkout repo
uses: actions/checkout@v7
@@ -66,7 +71,7 @@ jobs:
run: python3 create_ai_layer.py ${SOLUTION}.cbuild-mlops.yml
- name: Build
- run: cbuild ${SOLUTION}.csolution.yml --active ${TARGET}
+ run: cbuild ${SOLUTION}.csolution.yml --active ${TARGET} --toolchain ${{ matrix.toolchain }}
- name: Run on the FVP
env:
@@ -89,7 +94,7 @@ jobs:
if: always()
uses: actions/upload-artifact@v4
with:
- name: corstone-320-build
+ name: corstone-320-${{ matrix.toolchain }}
path: |
fvp_stdout.log
out/**/*.axf
diff --git a/board/Corstone-320/RTE/Device/SSE-320-FVP/gcc_linker_script.ld.src b/board/Corstone-320/RTE/Device/SSE-320-FVP/gcc_linker_script.ld.src
new file mode 100644
index 00000000..7cd986d8
--- /dev/null
+++ b/board/Corstone-320/RTE/Device/SSE-320-FVP/gcc_linker_script.ld.src
@@ -0,0 +1,308 @@
+/*
+ * Copyright (c) 2023 Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the License); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* ----------------------------------------------------------------------------
+ Stack seal size definition
+ *----------------------------------------------------------------------------*/
+#if defined (__ARM_FEATURE_CMSE) && (__ARM_FEATURE_CMSE == 3U)
+#define __STACKSEAL_SIZE ( 8 )
+#else
+#define __STACKSEAL_SIZE ( 0 )
+#endif
+
+/* ----------------------------------------------------------------------------
+ Memory definition
+ *----------------------------------------------------------------------------*/
+MEMORY
+{
+ ROM0 (rx) : ORIGIN = __ROM0_BASE, LENGTH = __ROM0_SIZE
+#if __ROM1_SIZE > 0
+ ROM1 (rx) : ORIGIN = __ROM1_BASE, LENGTH = __ROM1_SIZE
+#endif
+#if __ROM2_SIZE > 0
+ ROM2 (rx) : ORIGIN = __ROM2_BASE, LENGTH = __ROM2_SIZE
+#endif
+#if __ROM3_SIZE > 0
+ ROM3 (rx) : ORIGIN = __ROM3_BASE, LENGTH = __ROM3_SIZE
+#endif
+
+ RAM0 (rwx) : ORIGIN = __RAM0_BASE, LENGTH = __RAM0_SIZE
+#if __RAM1_SIZE > 0
+ RAM1 (rwx) : ORIGIN = __RAM1_BASE, LENGTH = __RAM1_SIZE
+#endif
+#if __RAM2_SIZE > 0
+ RAM2 (rwx) : ORIGIN = __RAM2_BASE, LENGTH = __RAM2_SIZE
+#endif
+#if __RAM3_SIZE > 0
+ RAM3 (rwx) : ORIGIN = __RAM3_BASE, LENGTH = __RAM3_SIZE
+#endif
+}
+
+/* Linker script to place sections and symbol values. Should be used together
+ * with other linker script that defines memory regions FLASH and RAM.
+ * It references following symbols, which must be defined in code:
+ * Reset_Handler : Entry of reset handler
+ *
+ * It defines following symbols, which code can use without definition:
+ * __exidx_start
+ * __exidx_end
+ * __copy_table_start__
+ * __copy_table_end__
+ * __zero_table_start__
+ * __zero_table_end__
+ * __etext (deprecated)
+ * __data_start__
+ * __preinit_array_start
+ * __preinit_array_end
+ * __init_array_start
+ * __init_array_end
+ * __fini_array_start
+ * __fini_array_end
+ * __data_end__
+ * __bss_start__
+ * __bss_end__
+ * __noinit_start
+ * __noinit_end
+ * __end__
+ * end
+ * __HeapLimit
+ * __StackLimit
+ * __StackTop
+ * __stack
+ */
+ENTRY(Reset_Handler)
+
+SECTIONS
+{
+ .text :
+ {
+ KEEP(*(.vectors))
+ *(.text*)
+
+ KEEP(*(.init))
+ KEEP(*(.fini))
+
+ /* .ctors */
+ *crtbegin.o(.ctors)
+ *crtbegin?.o(.ctors)
+ *(EXCLUDE_FILE(*crtend?.o *crtend.o) .ctors)
+ *(SORT(.ctors.*))
+ *(.ctors)
+
+ /* .dtors */
+ *crtbegin.o(.dtors)
+ *crtbegin?.o(.dtors)
+ *(EXCLUDE_FILE(*crtend?.o *crtend.o) .dtors)
+ *(SORT(.dtors.*))
+ *(.dtors)
+
+ *(.rodata*)
+
+ KEEP(*(.eh_frame*))
+ } > ROM0
+
+#if defined (__ARM_FEATURE_CMSE) && (__ARM_FEATURE_CMSE == 3U)
+ .gnu.sgstubs :
+ {
+ . = ALIGN(32);
+ } > ROM0
+#endif
+
+ .ARM.extab :
+ {
+ *(.ARM.extab* .gnu.linkonce.armextab.*)
+ } > ROM0
+
+ __exidx_start = .;
+ .ARM.exidx :
+ {
+ *(.ARM.exidx* .gnu.linkonce.armexidx.*)
+ } > ROM0
+ __exidx_end = .;
+
+ .copy.table :
+ {
+ . = ALIGN(4);
+ __copy_table_start__ = .;
+
+ LONG (LOADADDR(.data))
+ LONG (ADDR(.data))
+ LONG (SIZEOF(.data) / 4)
+
+ /* Add each additional data section here */
+/*
+ LONG (LOADADDR(.data2))
+ LONG (ADDR(.data2))
+ LONG (SIZEOF(.data2) / 4)
+*/
+ __copy_table_end__ = .;
+ } > ROM0
+
+ .zero.table :
+ {
+ . = ALIGN(4);
+ __zero_table_start__ = .;
+
+/* .bss initialization to zero is already done during C Run-Time Startup.
+ LONG (ADDR(.bss))
+ LONG (SIZEOF(.bss) / 4)
+*/
+
+ /* Add each additional bss section here */
+/*
+ LONG (ADDR(.bss2))
+ LONG (SIZEOF(.bss2) / 4)
+*/
+ __zero_table_end__ = .;
+ } > ROM0
+
+ /*
+ * This __etext variable is kept for backward compatibility with older,
+ * ASM based startup files.
+ */
+ PROVIDE(__etext = LOADADDR(.data));
+
+ .data : ALIGN(4)
+ {
+ __data_start__ = .;
+ *(vtable)
+ *(.data)
+ *(.data.*)
+
+ . = ALIGN(4);
+ /* preinit data */
+ PROVIDE_HIDDEN (__preinit_array_start = .);
+ KEEP(*(.preinit_array))
+ PROVIDE_HIDDEN (__preinit_array_end = .);
+
+ . = ALIGN(4);
+ /* init data */
+ PROVIDE_HIDDEN (__init_array_start = .);
+ KEEP(*(SORT(.init_array.*)))
+ KEEP(*(.init_array))
+ PROVIDE_HIDDEN (__init_array_end = .);
+
+ . = ALIGN(4);
+ /* finit data */
+ PROVIDE_HIDDEN (__fini_array_start = .);
+ KEEP(*(SORT(.fini_array.*)))
+ KEEP(*(.fini_array))
+ PROVIDE_HIDDEN (__fini_array_end = .);
+
+ KEEP(*(.jcr*))
+ . = ALIGN(4);
+ /* All data end */
+ __data_end__ = .;
+
+ } > RAM0 AT > ROM0
+
+ /*
+ * Secondary data section, optional
+ *
+ * Remember to add each additional data section
+ * to the .copy.table above to assure proper
+ * initialization during startup.
+ */
+/*
+ .data2 : ALIGN(4)
+ {
+ . = ALIGN(4);
+ __data2_start__ = .;
+ *(.data2)
+ *(.data2.*)
+ . = ALIGN(4);
+ __data2_end__ = .;
+
+ } > RAM1 AT > ROM0
+*/
+
+ .bss :
+ {
+ . = ALIGN(4);
+ __bss_start__ = .;
+ *(.bss)
+ *(.bss.*)
+ *(COMMON)
+ . = ALIGN(4);
+ __bss_end__ = .;
+ } > RAM0 AT > RAM0
+
+ /*
+ * Secondary bss section, optional
+ *
+ * Remember to add each additional bss section
+ * to the .zero.table above to assure proper
+ * initialization during startup.
+ */
+/*
+ .bss2 :
+ {
+ . = ALIGN(4);
+ __bss2_start__ = .;
+ *(.bss2)
+ *(.bss2.*)
+ . = ALIGN(4);
+ __bss2_end__ = .;
+ } > RAM1 AT > RAM1
+*/
+
+ /* This section contains data that is not initialized during load,
+ or during the application's initialization sequence. */
+ .noinit (NOLOAD) :
+ {
+ . = ALIGN(4);
+ __noinit_start = .;
+ *(.noinit)
+ *(.noinit.*)
+ . = ALIGN(4);
+ __noinit_end = .;
+ } > RAM0
+
+ .heap (NOLOAD) :
+ {
+ . = ALIGN(8);
+ __end__ = .;
+ PROVIDE(end = .);
+ . = . + __HEAP_SIZE;
+ . = ALIGN(8);
+ __HeapLimit = .;
+ } > RAM0
+
+ .stack (ORIGIN(RAM0) + LENGTH(RAM0) - __STACK_SIZE - __STACKSEAL_SIZE) (NOLOAD) :
+ {
+ . = ALIGN(8);
+ __StackLimit = .;
+ . = . + __STACK_SIZE;
+ . = ALIGN(8);
+ __StackTop = .;
+ } > RAM0
+ PROVIDE(__stack = __StackTop);
+
+#if __STACKSEAL_SIZE > 0
+ .stackseal (ORIGIN(RAM0) + LENGTH(RAM0) - __STACKSEAL_SIZE) (NOLOAD) :
+ {
+ . = ALIGN(8);
+ __StackSeal = .;
+ . = . + 8;
+ . = ALIGN(8);
+ } > RAM0
+#endif
+
+ /* Check if data + heap + stack exceeds RAM limit */
+ ASSERT(__StackLimit >= __HeapLimit, "region RAM overflowed with stack")
+}
diff --git a/vcpkg-configuration.json b/vcpkg-configuration.json
index 8f33fd6c..bb4bcb62 100644
--- a/vcpkg-configuration.json
+++ b/vcpkg-configuration.json
@@ -9,9 +9,10 @@
"requires": {
"arm:tools/open-cmsis-pack/cmsis-toolbox": "2.14.1",
"arm:compilers/arm/arm-none-eabi-gcc": "14.3.1",
+ "arm:compilers/arm/llvm-embedded": "21.1.1",
"arm:tools/kitware/cmake": "4.2.1",
"arm:tools/ninja-build/ninja": "1.13.2",
"arm:models/arm/avh-fvp": "11.32.23",
"arm:compilers/arm/armclang": "6.24.0"
}
-}
\ No newline at end of file
+}
From ad410fd127497250236a5c9a3e90c79890d0c5ff Mon Sep 17 00:00:00 2001
From: Matthias Hertel <11649409+MatthiasHertel80@users.noreply.github.com>
Date: Thu, 10 Sep 2026 20:46:30 +0200
Subject: [PATCH 09/21] Pin the LLVM embedded toolchain 22.1.0
The Linux x86 build of ATfE 21.1.1 crashes in its register coalescer on
the quantize and dequantize kernels; 22.1.0 compiles them.
---
vcpkg-configuration.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/vcpkg-configuration.json b/vcpkg-configuration.json
index bb4bcb62..68cb2129 100644
--- a/vcpkg-configuration.json
+++ b/vcpkg-configuration.json
@@ -9,7 +9,7 @@
"requires": {
"arm:tools/open-cmsis-pack/cmsis-toolbox": "2.14.1",
"arm:compilers/arm/arm-none-eabi-gcc": "14.3.1",
- "arm:compilers/arm/llvm-embedded": "21.1.1",
+ "arm:compilers/arm/llvm-embedded": "22.1.0",
"arm:tools/kitware/cmake": "4.2.1",
"arm:tools/ninja-build/ninja": "1.13.2",
"arm:models/arm/avh-fvp": "11.32.23",
From 7fd381d3b9d4dc1f4f68ae885f8f72b337bc3c49 Mon Sep 17 00:00:00 2001
From: Matthias Hertel <11649409+MatthiasHertel80@users.noreply.github.com>
Date: Thu, 10 Sep 2026 20:54:42 +0200
Subject: [PATCH 10/21] Name the Cortex-M85 FPU explicitly for Clang
CMSIS-Toolbox 2.14.1 passes -mfpu=fpv5-sp-d16 to Clang for the
Cortex-M85 (and M55), a single-precision FPU on cores with a
double-precision FPU and MVE; LLVM 21.1.1 and 22.1.0 crash in the
register coalescer on the quantize and dequantize kernels with that
combination. A later -mfpu on the command line wins, so the csolution
adds -mfpu=fp-armv8-fullfp16-d16 for Clang; newer toolboxes already
leave the FPU to -mcpu.
---
cmsis-executorch.csolution.yml | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/cmsis-executorch.csolution.yml b/cmsis-executorch.csolution.yml
index 43ac3739..acb57444 100644
--- a/cmsis-executorch.csolution.yml
+++ b/cmsis-executorch.csolution.yml
@@ -50,6 +50,17 @@ solution:
config-file: board/Corstone-320/fvp_config.txt
args: "--simlimit 60"
+ misc:
+ # CMSIS-Toolbox 2.14.1 passes -mfpu=fpv5-sp-d16 to Clang for the Cortex-M85,
+ # a single-precision FPU on a core with a double-precision one and MVE;
+ # LLVM crashes on some kernels with that combination. The later option
+ # wins, so name the M85's FPU explicitly (newer toolboxes leave it to -mcpu).
+ - for-compiler: CLANG
+ C-CPP:
+ - -mfpu=fp-armv8-fullfp16-d16
+ ASM:
+ - -mfpu=fp-armv8-fullfp16-d16
+
build-types:
- type: Debug
debug: on
From 5a3d825db5d8b2d6ab130408b9c4f27633d970c2 Mon Sep 17 00:00:00 2001
From: Matthias Hertel <11649409+MatthiasHertel80@users.noreply.github.com>
Date: Fri, 11 Sep 2026 10:41:01 +0200
Subject: [PATCH 11/21] Act on the instruction audit: venv selection, Vela ini
path, docs
create_ai_layer.py re-executes in .venv whenever the running interpreter
is not that venv; testing whether torch imports kept the export on a host
interpreter that happens to have torch. The Vela ini goes into the
compile spec as a relative path so the exported program does not embed
the checkout location. The pack-build recipe carries the CMake configure,
all generator arguments, the structural validator and a non-interactive
install; its checks now fail instead of echoing. Comments and the README
no longer attribute stdout to a UART, the flatbuffers pin to the
executorch wheel, or shutdown to uart0, and the adaptation section names
the runner's fixed input shape.
---
.github/workflows/build.yml | 6 ++--
.vscode/fvp.Dockerfile | 5 +--
README.md | 11 ++++--
board/Corstone-320/Board-U85.clayer.yml | 4 +--
create_ai_layer.py | 38 ++++++++++++--------
documentation/pack-provenance.md | 48 ++++++++++++++++++++-----
model/model.py | 4 ++-
requirements-arm-tosa.txt | 13 ++++---
src/app_main.cpp | 2 +-
9 files changed, 90 insertions(+), 41 deletions(-)
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index ee5ecc66..3b3666be 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -106,7 +106,7 @@ jobs:
# license needed, so this is cheap to matrix. Without this job, Windows
# support regresses on the first refactor and nobody notices.
venv-cross-platform:
- name: Model export venv (${{ matrix.os }})
+ name: Export environment check, import only (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
@@ -153,8 +153,8 @@ jobs:
PY=.venv/bin/python
fi
"${PY}" -m pip list | grep -iE "torch|executorch|vela|tosa|flatbuffers"
- # flatbuffers must stay at 24.3.25: tosa-tools wants 25.2.10 and is
- # installed --no-deps precisely to stop that. See
+ # flatbuffers must stay at 24.3.25 (the Arm backend's pin): tosa-tools
+ # wants 25.2.10 and is installed --no-deps precisely to stop that. See
# requirements-arm-tosa.txt.
# No grep -q: with pipefail, grep closing the pipe early fails the step.
"${PY}" -m pip show flatbuffers | grep "Version: 24.3.25" \
diff --git a/.vscode/fvp.Dockerfile b/.vscode/fvp.Dockerfile
index 462ba198..993bbb03 100644
--- a/.vscode/fvp.Dockerfile
+++ b/.vscode/fvp.Dockerfile
@@ -2,8 +2,9 @@
#
# The image is the Linux build of the very same release the rest of the project
# pins in vcpkg-configuration.json (arm:models/arm/avh-fvp), so the model, the
-# Ethos-U85 plugin and plugins/GDBServer.so are bit-identical to what a Linux or
-# Windows developer gets from vcpkg. .vscode/fvp.sh builds it on first use.
+# Ethos-U85 plugin and plugins/GDBServer.so are the same release and
+# configuration a Linux or Windows developer gets from vcpkg (the binaries
+# differ per host). .vscode/fvp.sh builds it on first use.
FROM ubuntu:22.04
ARG DEBIAN_FRONTEND=noninteractive
diff --git a/README.md b/README.md
index 70a921a7..15d929d4 100644
--- a/README.md
+++ b/README.md
@@ -52,9 +52,11 @@ The example can be built and run entirely in Keil Studio for VS Code.
**(uv)** variant of the task uses [uv](https://docs.astral.sh/uv/) instead
of pip and can download the Python version it asks for.)
4. Select **Terminal > Run Task > Create AI layer**. This exports the model for
- the NPU of the active target and writes the `ai_layer/` directory. (The
+ the NPU of the active target and writes the `ai_layer/` directory. It reads
+ `cmsis-executorch.cbuild-mlops.yml`, which the extension writes when the
+ solution is loaded or built (`cbuild setup` on the command line). The
repository ships a generated layer, so this step is only needed after
- changing the model or the target.)
+ changing the model or the target.
5. Use the CMSIS action buttons to build the application, then select **Run** or
**Debug**. Keil Studio starts the Corstone-320 FVP automatically. On macOS,
where Arm ships no FVP build, `.vscode/fvp.sh` runs the model in Docker:
@@ -204,7 +206,10 @@ model name or input handling as required: `INPUT_SHAPE` and
`get_model(input_shape)` define the input, and
`get_calibration_inputs(input_shape, calibration_samples)` returns the samples
the quantizer is calibrated with; give it representative data for a trained
-model. Then re-run `create_ai_layer.py` and build.
+model. The runner in `src/app_main.cpp` builds its input tensor with the same
+fixed shape and prints the output as ten floats, so a model with another input
+shape or output needs matching changes there. Then re-run `create_ai_layer.py`
+and build.
To target another Ethos-U configuration, update the target and `mlops:`
settings in the CMSIS solution and re-run all three steps. The generated Vela
diff --git a/board/Corstone-320/Board-U85.clayer.yml b/board/Corstone-320/Board-U85.clayer.yml
index 67982cd1..3ba21104 100644
--- a/board/Corstone-320/Board-U85.clayer.yml
+++ b/board/Corstone-320/Board-U85.clayer.yml
@@ -2,8 +2,8 @@
# SPDX-License-Identifier: Apache-2.0
#
# Corstone-320 (SSE-320) board bring-up for the Ethos-U85 NPU, trimmed to what
-# a headless ExecuTorch inference runner needs: device startup, a UART-backed
-# stdout, and the Ethos-U driver. Audio/video vStreams and VIO are omitted.
+# a headless ExecuTorch inference runner needs: device startup, stdout over
+# semihosting, and the Ethos-U driver. Audio/video vStreams and VIO are omitted.
layer:
type: Board
description: Corstone-320 board setup for headless Ethos-U85 inference
diff --git a/create_ai_layer.py b/create_ai_layer.py
index 72c57f4a..d231470c 100644
--- a/create_ai_layer.py
+++ b/create_ai_layer.py
@@ -41,20 +41,25 @@
def run_in_venv() -> None:
- """Re-run under .venv when torch is not importable from this interpreter."""
- try:
- import torch # noqa: F401
- except ImportError:
- venv = HERE / ".venv"
- python = venv / ("Scripts/python.exe" if os.name == "nt" else "bin/python")
- # sys.prefix is the venv directory when running inside it (comparing
- # interpreter paths does not work: venv symlinks resolve to the base).
- if not python.is_file() or Path(sys.prefix).resolve() == venv.resolve():
- sys.exit(
- f"torch is not installed for {sys.executable}.\n"
- "Create the venv first: ./setup_venv.sh (Linux/macOS) or setup_venv.bat (Windows)"
- )
- sys.exit(subprocess.run([str(python), __file__, *sys.argv[1:]]).returncode)
+ """Re-run under the project's .venv unless this interpreter already is it.
+
+ Deciding by "does torch import" is not enough: a torch installed for the
+ host interpreter would keep the export outside the environment with the
+ pinned executorch. sys.prefix is the venv directory when running inside it
+ (comparing interpreter paths does not work: venv symlinks resolve to the
+ base interpreter).
+ """
+ venv = HERE / ".venv"
+ if Path(sys.prefix).resolve() == venv.resolve():
+ return
+ python = venv / ("Scripts/python.exe" if os.name == "nt" else "bin/python")
+ if not python.is_file():
+ sys.exit(
+ f"{venv} does not exist.\n"
+ "Create it first: ./setup_venv.sh (Linux/macOS) or setup_venv.bat (Windows)"
+ )
+ print(f"[ai_layer] running in {python}", flush=True)
+ sys.exit(subprocess.run([str(python), __file__, *sys.argv[1:]]).returncode)
def pack_root() -> Path:
@@ -117,7 +122,10 @@ def option(name: str) -> str | None:
"memory_mode": option("memory-mode"),
}
if vela.get("ini"):
- kwargs["config_ini"] = str(mlops_dir / vela["ini"])
+ # ExecuTorch stores the path in the compile spec, and the spec ends up
+ # in the .pte. A path relative to the working directory keeps the
+ # program identical between checkouts; Vela resolves it from there.
+ kwargs["config_ini"] = os.path.relpath(mlops_dir / vela["ini"])
print(f"[ai_layer] Vela: {kwargs}")
return EthosUCompileSpec(**kwargs)
diff --git a/documentation/pack-provenance.md b/documentation/pack-provenance.md
index e27c0420..6fb19ab9 100644
--- a/documentation/pack-provenance.md
+++ b/documentation/pack-provenance.md
@@ -85,10 +85,34 @@ git submodule update --init --recursive
> `flatc` ships inside the executorch wheel — after `./setup_venv.sh` it is at
> `.venv/bin/flatc`.
-Then run the generator; the pack lands in `pack-output/`:
+The cross-compile that produces those headers is a plain CMake build of the
+runtime for Cortex-M; the host executor runner is not needed and its link
+fails without the Ethos-U driver, so leave it off:
```bash
-backends/arm/cmsis_pack/scripts/build_pack.sh
+cmake -S . -B cmake-out-arm \
+ -DCMAKE_TOOLCHAIN_FILE=examples/arm/ethos-u-setup/arm-none-eabi-gcc.cmake \
+ -DEXECUTORCH_BUILD_ARM_BAREMETAL=ON \
+ -DEXECUTORCH_BUILD_KERNELS_QUANTIZED=ON \
+ -DEXECUTORCH_BUILD_FLATC=ON \
+ -DEXECUTORCH_BUILD_EXECUTOR_RUNNER=OFF \
+ -DPYTHON_EXECUTABLE=/path/to/this/example/.venv/bin/python
+cmake --build cmake-out-arm --config Release -j4
+```
+
+`arm-none-eabi-gcc` must be on `PATH` (the one from `vcpkg-configuration.json`
+does); `PYTHON_EXECUTABLE` points at this example's `.venv`, which has the
+Python packages the generators import.
+
+Then run the generator with all four arguments (it refuses to run without
+them); the pack lands in the output directory:
+
+```bash
+backends/arm/cmsis_pack/scripts/build_pack.sh \
+ --executorch-root "$PWD" \
+ --build-dir cmake-out-arm \
+ --version 1.4.1-local \
+ --output-dir pack-output
```
### Verifying a pack you built
@@ -96,24 +120,30 @@ backends/arm/cmsis_pack/scripts/build_pack.sh
Before trusting a freshly built pack, check the parts that go missing quietly:
```bash
-PACK=pack-output/PyTorch.ExecuTorch.
+VERSION=1.4.1-local
+PACK=pack-output/PyTorch.ExecuTorch.$VERSION
+set -e
# 1. The bundled flatbuffers headers must be present.
-test -d "$PACK/include/flatbuffers" || echo "MISSING: include/flatbuffers"
+test -d "$PACK/include/flatbuffers"
# 2. The flatc-generated headers must be present.
for h in program_generated.h scalar_type_generated.h; do
- find "$PACK/include" -name "$h" | grep -q . || echo "MISSING: $h"
+ find "$PACK/include" -name "$h" | grep .
done
-# 3. The component list should still cover the operators the model uses.
-grep -c '.pack
+cpackget add --agree-embedded-license pack-output/PyTorch.ExecuTorch.$VERSION.pack
+cpackget list | grep "PyTorch::ExecuTorch@$VERSION" # cpackget exits 0 even when it declined
```
## Moving to a new ExecuTorch version
diff --git a/model/model.py b/model/model.py
index 40c06722..d9d54a64 100644
--- a/model/model.py
+++ b/model/model.py
@@ -35,7 +35,9 @@ def forward(self, x: torch.Tensor) -> torch.Tensor:
def get_model(input_shape: tuple[int, ...] = INPUT_SHAPE) -> nn.Module:
# Fixed seed: the example uses untrained (random) weights, and a fixed seed
- # keeps the generated AI layer identical from one export to the next.
+ # keeps the exported program identical from one export to the next (the
+ # C array also embeds the Vela options, so the same working directory is
+ # part of that guarantee).
torch.manual_seed(0)
return TinyCNN(input_shape).eval()
diff --git a/requirements-arm-tosa.txt b/requirements-arm-tosa.txt
index 7adedf7b..38668ea4 100644
--- a/requirements-arm-tosa.txt
+++ b/requirements-arm-tosa.txt
@@ -3,11 +3,14 @@
# the export flow actually needs (upstream also lists the model-explorer viewer
# and pytest-timeout, which are development tools).
#
-# setup_venv.py installs this file with `pip install --no-dependencies`, and
-# that is deliberate: tosa-tools 2026.5.0 requires flatbuffers==25.2.10, while
-# executorch pins flatbuffers==24.3.25 below. Letting pip resolve tosa-tools'
-# dependencies would silently upgrade flatbuffers and break serialization.
-# Installing executorch[ethos_u] instead of these files has the same problem.
+# setup_venv.py installs this file with `pip install --no-deps`, and that is
+# deliberate: tosa-tools 2026.5.0 declares flatbuffers==25.2.10, numpy<2.2 and
+# the extras jsonschema and semver, while the Arm backend's own
+# requirements-arm-tosa.txt pins flatbuffers==24.3.25 (the executorch wheel
+# itself leaves flatbuffers unversioned). Letting pip resolve tosa-tools'
+# dependencies would upgrade flatbuffers and break serialization. `pip check`
+# therefore reports these four unmet requirements on purpose; the export path
+# uses none of them. Installing executorch[ethos_u] instead has the same problem.
#
# tosa-tools provides the `tosa_serializer` module.
ml_dtypes == 0.5.1
diff --git a/src/app_main.cpp b/src/app_main.cpp
index a173a014..54d1e758 100644
--- a/src/app_main.cpp
+++ b/src/app_main.cpp
@@ -107,7 +107,7 @@ extern "C" int app_main(void) {
}
printf("Test_result: PASS\n");
- printf("\x04"); // EOT stops the FVP (uart0.shutdown_on_eot); a board just sees a 0x04
+ printf("\x04"); // EOT: ends the FVP run (semihosting exit); a board just sees a 0x04
fflush(stdout);
return 0;
}
From 4bde4e53d73c57b1387b168c73a259e23a3e8cbd Mon Sep 17 00:00:00 2001
From: Matthias Hertel <11649409+MatthiasHertel80@users.noreply.github.com>
Date: Fri, 11 Sep 2026 11:32:12 +0200
Subject: [PATCH 12/21] Track the generated RTE context folders as well
RTE/ is no longer partly ignored: the per-context RTE_Components.h and
Pre_Include_Global.h the toolbox writes are committed next to the layers'
configuration files.
---
.gitignore | 5 +--
RTE/_Debug_SSE-320-U85/Pre_Include_Global.h | 25 +++++++++++
RTE/_Debug_SSE-320-U85/RTE_Components.h | 47 +++++++++++++++++++++
3 files changed, 74 insertions(+), 3 deletions(-)
create mode 100644 RTE/_Debug_SSE-320-U85/Pre_Include_Global.h
create mode 100644 RTE/_Debug_SSE-320-U85/RTE_Components.h
diff --git a/.gitignore b/.gitignore
index b7d43238..0a0cfc37 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,11 +7,10 @@ __pycache__/
# generated AI layer; the C array in ai_layer/model_pte.c is what gets built.
*.pte
-# CMSIS-Toolbox build outputs. The per-context RTE/__/ folders
-# are generated; the layers' RTE/ configuration files are sources.
+# CMSIS-Toolbox build outputs. RTE/ is tracked in full: the layers' configuration
+# files and the per-context RTE/__/ headers the toolbox writes.
out/
tmp/
-RTE/_*/
.cmsis/
*.cbuild-idx.yml
*.cbuild-pack.yml
diff --git a/RTE/_Debug_SSE-320-U85/Pre_Include_Global.h b/RTE/_Debug_SSE-320-U85/Pre_Include_Global.h
new file mode 100644
index 00000000..37e190f9
--- /dev/null
+++ b/RTE/_Debug_SSE-320-U85/Pre_Include_Global.h
@@ -0,0 +1,25 @@
+/*
+ * CSOLUTION generated file: DO NOT EDIT!
+ * Generated by: csolution version 2.14.1+p38-gf512b381
+ *
+ * Project: 'cmsis-executorch.Debug+SSE-320-U85'
+ * Target: 'Debug+SSE-320-U85'
+ */
+
+#ifndef PRE_INCLUDE_GLOBAL_H
+#define PRE_INCLUDE_GLOBAL_H
+
+/* ARM::Machine Learning:NPU Support:Ethos-U Driver&Generic U85@1.26.2 */
+// enabling global pre includes
+ #define ETHOSU_ARCH u85
+/* PyTorch::Machine Learning:ExecuTorch:Backend EthosU@1.4.1 */
+#define EXECUTORCH_BUILD_ARM_BAREMETAL 1
+ #define ET_USE_ETHOS_U_BACKEND 1
+/* PyTorch::Machine Learning:ExecuTorch:Runtime@1.4.1 */
+/* ExecuTorch global configuration */
+ #define C10_USING_CUSTOM_GENERATED_MACROS
+ #define FLATBUFFERS_MAX_ALIGNMENT 1024
+ #define FLATBUFFERS_LOCALE_INDEPENDENT 0
+
+
+#endif /* PRE_INCLUDE_GLOBAL_H */
diff --git a/RTE/_Debug_SSE-320-U85/RTE_Components.h b/RTE/_Debug_SSE-320-U85/RTE_Components.h
new file mode 100644
index 00000000..571f80b4
--- /dev/null
+++ b/RTE/_Debug_SSE-320-U85/RTE_Components.h
@@ -0,0 +1,47 @@
+/*
+ * CSOLUTION generated file: DO NOT EDIT!
+ * Generated by: csolution version 2.14.1+p38-gf512b381
+ *
+ * Project: 'cmsis-executorch.Debug+SSE-320-U85'
+ * Target: 'Debug+SSE-320-U85'
+ */
+
+#ifndef RTE_COMPONENTS_H
+#define RTE_COMPONENTS_H
+
+
+/*
+ * Define the Device Header File:
+ */
+#define CMSIS_device_header "SSE320.h"
+
+/* ARM::CMSIS-Compiler:STDERR:Custom@1.1.0 */
+#define RTE_CMSIS_Compiler_STDERR /* CMSIS-Compiler STDERR */
+#define RTE_CMSIS_Compiler_STDERR_Custom /* CMSIS-Compiler STDERR: Custom */
+/* ARM::CMSIS-Compiler:STDIN:Custom@1.1.0 */
+#define RTE_CMSIS_Compiler_STDIN /* CMSIS-Compiler STDIN */
+#define RTE_CMSIS_Compiler_STDIN_Custom /* CMSIS-Compiler STDIN: Custom */
+/* ARM::CMSIS-Compiler:STDOUT:Custom@1.1.0 */
+#define RTE_CMSIS_Compiler_STDOUT /* CMSIS-Compiler STDOUT */
+#define RTE_CMSIS_Compiler_STDOUT_Custom /* CMSIS-Compiler STDOUT: Custom */
+/* ARM::Device:Native Driver:SysCounter@1.0.1 */
+#define RTE_SYSCOUNTER 1
+/* ARM::Device:Native Driver:Timeout@1.0.1 */
+#define RTE_TIMEOUT 1
+/* ARM::Machine Learning:NPU Support:Ethos-U Driver&Generic U85@1.26.2 */
+#define RTE_ETHOS_U_CORE_DRIVER
+/* PyTorch::Machine Learning:ExecuTorch Operators:Quantized dequantize@1.4.1 */
+#define RTE_ML_EXECUTORCH_OP_QUANTIZED_DEQUANTIZE /* ExecuTorch op_dequantize */
+/* PyTorch::Machine Learning:ExecuTorch Operators:Quantized quantize@1.4.1 */
+#define RTE_ML_EXECUTORCH_OP_QUANTIZED_QUANTIZE /* ExecuTorch op_quantize */
+/* PyTorch::Machine Learning:ExecuTorch:Backend EthosU@1.4.1 */
+#define RTE_ML_EXECUTORCH_BACKEND_ETHOS_U /* ExecuTorch Ethos-U Backend (Cortex-M host) */
+/* PyTorch::Machine Learning:ExecuTorch:Kernel Registration@1.4.1 */
+#define RTE_ML_EXECUTORCH_KERNEL_REGISTRATION /* ExecuTorch Kernel Registration */
+/* PyTorch::Machine Learning:ExecuTorch:Kernel Utils@1.4.1 */
+#define RTE_ML_EXECUTORCH_KERNEL_UTILS /* ExecuTorch Kernel Utils */
+/* PyTorch::Machine Learning:ExecuTorch:Runtime@1.4.1 */
+#define RTE_ML_EXECUTORCH_RUNTIME /* ExecuTorch Runtime */
+
+
+#endif /* RTE_COMPONENTS_H */
From 4dd4b91b95b04f7ed0b650b919a3c1641db998fb Mon Sep 17 00:00:00 2001
From: Matthias Hertel <11649409+MatthiasHertel80@users.noreply.github.com>
Date: Fri, 11 Sep 2026 14:59:58 +0200
Subject: [PATCH 13/21] Refuse Python 3.14 in the venv setup: tosa-tools
2026.5.0 has no 3.14 wheels
ExecuTorch 1.4.1 allows Python <3.15, but the tosa-tools 2026.5.0 it pins
for the Arm backend only ships wheels for CPython 3.10 to 3.13. On 3.14
pip fails with "No matching distribution found for tosa-tools==2026.5.0"
long after the venv was created. Check the bound up front and document it.
---
README.md | 2 +-
requirements-arm-tosa.txt | 3 ++-
requirements.txt | 2 +-
setup_venv.py | 15 +++++++++------
setup_venv.sh | 2 +-
5 files changed, 14 insertions(+), 10 deletions(-)
diff --git a/README.md b/README.md
index 15d929d4..3860c0dd 100644
--- a/README.md
+++ b/README.md
@@ -22,7 +22,7 @@ application.
## Prerequisites
-- Python `>=3.10,<3.15`.
+- Python `>=3.10,<3.14` (tosa-tools 2026.5.0 has no 3.14 wheels).
- [Keil Studio for VS Code](https://marketplace.visualstudio.com/items?itemName=Arm.keil-studio-pack) from the VS Code marketplace.
- Tools listed in [`vcpkg-configuration.json`](./vcpkg-configuration.json).
- Keil Studio manages the required license; the free Keil MDK Community edition can be used for evaluation.
diff --git a/requirements-arm-tosa.txt b/requirements-arm-tosa.txt
index 38668ea4..22ab389c 100644
--- a/requirements-arm-tosa.txt
+++ b/requirements-arm-tosa.txt
@@ -12,7 +12,8 @@
# therefore reports these four unmet requirements on purpose; the export path
# uses none of them. Installing executorch[ethos_u] instead has the same problem.
#
-# tosa-tools provides the `tosa_serializer` module.
+# tosa-tools provides the `tosa_serializer` module. 2026.5.0 ships wheels for
+# CPython 3.10 to 3.13 only; setup_venv.py refuses 3.14 for that reason.
ml_dtypes == 0.5.1
flatbuffers == 24.3.25
tosa-tools == 2026.5.0
diff --git a/requirements.txt b/requirements.txt
index f4a091bc..1fd6d11f 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -6,7 +6,7 @@
# the PyTorch::ExecuTorch@1.4.1 pack pinned in the csolution. ExecuTorch 1.4.1
# targets torch 2.13.0 and torchao 0.18.0 (install_requirements.py on the
# release/1.4 branch); prebuilt wheels exist for Linux, macOS and Windows,
-# Python 3.10 to 3.14.
+# Python 3.10 to 3.14 (the export venv stays below 3.14: see requirements-arm-tosa.txt).
executorch==1.4.1
torch==2.13.0
torchao==0.18.0
diff --git a/setup_venv.py b/setup_venv.py
index d6a7d28c..4e987584 100755
--- a/setup_venv.py
+++ b/setup_venv.py
@@ -22,11 +22,14 @@
HERE = Path(__file__).resolve().parent
VENV_DIR = HERE / ".venv"
-# ExecuTorch 1.4 declares requires-python = ">=3.10,<3.15" in its pyproject.toml.
-# Check it up front: without this, an unsupported interpreter fails much later
-# with a resolver error that says nothing about the Python version.
+# ExecuTorch 1.4 declares requires-python = ">=3.10,<3.15" in its pyproject.toml,
+# but the tosa-tools 2026.5.0 it pins for the Arm backend (see
+# requirements-arm-tosa.txt) only ships wheels up to CPython 3.13, so on 3.14
+# pip reports "No matching distribution found for tosa-tools==2026.5.0".
+# Check the version up front: without this, an unsupported interpreter fails
+# much later with a resolver error that says nothing about the Python version.
MIN_PYTHON = (3, 10)
-MAX_PYTHON_EXCLUSIVE = (3, 15)
+MAX_PYTHON_EXCLUSIVE = (3, 14)
def venv_python(venv_dir: Path) -> Path:
@@ -83,7 +86,7 @@ def python_version(value: str) -> tuple[int, ...]:
raise argparse.ArgumentTypeError("expected a Python version such as 3.12 or 3.12.10")
version = tuple(int(n) for n in value.split("."))
if not (MIN_PYTHON <= version[:2] < MAX_PYTHON_EXCLUSIVE):
- raise argparse.ArgumentTypeError("ExecuTorch needs Python >=3.10,<3.15")
+ raise argparse.ArgumentTypeError("ExecuTorch needs Python >=3.10,<3.14")
return version
@@ -99,7 +102,7 @@ def check_venv_python(python: Path, requested: tuple[int, ...] | None) -> None:
version = tuple(int(n) for n in have.split("."))
if not (MIN_PYTHON <= version[:2] < MAX_PYTHON_EXCLUSIVE):
sys.exit(
- f"error: {python} uses Python {have}; ExecuTorch needs >=3.10,<3.15. "
+ f"error: {python} uses Python {have}; ExecuTorch needs >=3.10,<3.14. "
"Re-run with --recreate and a supported Python version."
)
if requested and version[: len(requested)] != requested:
diff --git a/setup_venv.sh b/setup_venv.sh
index f18f2203..395680c7 100755
--- a/setup_venv.sh
+++ b/setup_venv.sh
@@ -10,7 +10,7 @@
set -euo pipefail
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
use_uv=false
-uv_python='>=3.10,<3.15'
+uv_python='>=3.10,<3.14'
previous=''
for arg in "$@"; do
if [[ "$previous" == --python ]]; then
From e5ad3e312ca8cdddb70d83ea3bb6092fa646537b Mon Sep 17 00:00:00 2001
From: Matthias Hertel <11649409+MatthiasHertel80@users.noreply.github.com>
Date: Wed, 30 Sep 2026 16:18:41 +0200
Subject: [PATCH 14/21] Keep uv below Python 3.14 in setup_venv.bat and the
task prompt
setup_venv.py and setup_venv.sh refuse 3.14 since tosa-tools 2026.5.0 has
no wheels for it; the Windows wrapper's default range for uv and the prompt
of the uv setup task still said <3.15.
---
.vscode.d/tasks.json | 2 +-
setup_venv.bat | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/.vscode.d/tasks.json b/.vscode.d/tasks.json
index 7a0c7e75..fbf6c989 100644
--- a/.vscode.d/tasks.json
+++ b/.vscode.d/tasks.json
@@ -46,7 +46,7 @@
{
"id": "setupPythonVersion",
"type": "promptString",
- "description": "Python version for uv (>=3.10,<3.15), e.g. 3.12 or 3.12.10",
+ "description": "Python version for uv (>=3.10,<3.14), e.g. 3.12 or 3.12.10",
"default": "3.12"
}
]
diff --git a/setup_venv.bat b/setup_venv.bat
index 73225e0c..c89e3180 100644
--- a/setup_venv.bat
+++ b/setup_venv.bat
@@ -9,7 +9,7 @@ REM and setup_venv.sh differ. With --uv, uv supplies the interpreter, e.g.
REM setup_venv.bat --uv --python 3.12
setlocal
set "SETUP_USE_UV="
-set "SETUP_UV_PYTHON=>=3.10,<3.15"
+set "SETUP_UV_PYTHON=>=3.10,<3.14"
REM SHIFT only changes numbered arguments; %* still forwards the original list.
:scan_args
if "%~1"=="" goto launch
From 5445c14a0a81f2d226af50791fdce8c94f1e229b Mon Sep 17 00:00:00 2001
From: Christophe Favergeon
Date: Fri, 11 Sep 2026 08:49:19 +0200
Subject: [PATCH 15/21] Correct python path for create_ai command
---
.vscode.d/tasks.json | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.vscode.d/tasks.json b/.vscode.d/tasks.json
index fbf6c989..f53c3464 100644
--- a/.vscode.d/tasks.json
+++ b/.vscode.d/tasks.json
@@ -31,10 +31,10 @@
"label": "Create AI layer",
"detail": "Export model/model.py for the NPU in cmsis-executorch.cbuild-mlops.yml and write ai_layer/ (run a CMSIS build or 'cbuild setup' first)",
"type": "shell",
- "command": "python3",
+ "command": "${workspaceFolder}/.venv/bin/python",
"args": ["create_ai_layer.py", "cmsis-executorch.cbuild-mlops.yml"],
"windows": {
- "command": "python"
+ "command": "${workspaceFolder}\\.venv\\Scripts\\python.exe"
},
"options": {
"cwd": "${workspaceFolder}"
From 568ac825893bcc02ce7fa69035edb81fc0f3018c Mon Sep 17 00:00:00 2001
From: Matthias Hertel <11649409+MatthiasHertel80@users.noreply.github.com>
Date: Wed, 30 Sep 2026 15:25:15 +0200
Subject: [PATCH 16/21] Pass every Vela option of the mlops: node to Vela
create_ai_layer.py picked --accelerator-config, --system-config and
--memory-mode out of vela.options of the cbuild-mlops.yml with a regular
expression and dropped the rest, so `vela: misc:` in the csolution had no
effect and nothing said so. The options are now tokenized; the three that
are arguments of EthosUCompileSpec go there and every other one reaches
Vela as an extra flag. An option given twice, and --config,
--output-format and --output-dir, which the export sets itself, end the
script with a message.
Without misc: the exported program is byte for byte the one in ai_layer/.
---
README.md | 7 +++---
create_ai_layer.py | 50 +++++++++++++++++++++++++++++++------
documentation/mlops-flow.md | 4 ++-
3 files changed, 49 insertions(+), 12 deletions(-)
diff --git a/README.md b/README.md
index 3860c0dd..a1e95bad 100644
--- a/README.md
+++ b/README.md
@@ -180,8 +180,8 @@ mlops:
`cbuild setup --active SSE-320-U85` resolves it into
`cmsis-executorch.cbuild-mlops.yml`, which contains the processor, NPU
-and Vela options. `create_ai_layer.py` reads those options and passes them to
-ExecuTorch's `EthosUCompileSpec`, so the target configuration is never
+and Vela options. `create_ai_layer.py` reads those options and passes all of
+them to ExecuTorch's `EthosUCompileSpec`, so the target configuration is never
duplicated in Python. The script then writes:
- `ai_layer/ai_layer.clayer.yml`: the CMSIS components required by the model.
@@ -213,7 +213,8 @@ and build.
To target another Ethos-U configuration, update the target and `mlops:`
settings in the CMSIS solution and re-run all three steps. The generated Vela
-options then follow that configuration automatically. Moving to a different
+options then follow that configuration automatically; `vela: misc:` takes any
+further Vela option, for example `--optimise Size`. Moving to a different
board or reference platform also requires the corresponding device pack, board
support, memory layout, and FVP configuration.
diff --git a/create_ai_layer.py b/create_ai_layer.py
index d231470c..aeab3392 100644
--- a/create_ai_layer.py
+++ b/create_ai_layer.py
@@ -31,6 +31,7 @@
import os
import re
+import shlex
import subprocess
import sys
from pathlib import Path
@@ -101,6 +102,42 @@ def executorch_pack(version: str) -> Path:
return pack_root() / vendor / pack / version
+# Vela options that are arguments of EthosUCompileSpec, and the ones the export
+# sets itself: the configuration file comes from vela.ini of the cbuild-mlops.yml.
+SPEC_OPTIONS = ("accelerator-config", "system-config", "memory-mode")
+EXPORT_OPTIONS = ("config", "output-format", "output-dir")
+
+
+def vela_options(options: str) -> tuple[dict[str, str], list[str]]:
+ """Split vela.options of the cbuild-mlops.yml: the EthosUCompileSpec arguments, and every other option.
+
+ The other options are the `misc:` of the csolution's mlops: node (for
+ example `--optimise Size`); they reach Vela as extra flags, written as
+ `--name=value`.
+ """
+ tokens = shlex.split(options)
+ spec, extra = {}, []
+ while tokens:
+ token = tokens.pop(0)
+ if not token.startswith("--"):
+ sys.exit(f"vela options: unexpected '{token}' in '{options}'")
+ name, has_value, value = token[2:].partition("=")
+ if not has_value and tokens and not tokens[0].startswith("-"):
+ value = tokens.pop(0)
+ if name in EXPORT_OPTIONS:
+ sys.exit(
+ f"vela options: --{name} is set by the export; "
+ "the configuration file is vela: ini: of the mlops: node"
+ )
+ if name in SPEC_OPTIONS:
+ if name in spec:
+ sys.exit(f"vela options: --{name} is given twice in '{options}'")
+ spec[name] = value
+ else:
+ extra.append(f"--{name}={value}" if value else f"--{name}")
+ return spec, extra
+
+
def compile_spec(mlops: dict, mlops_dir: Path):
"""EthosUCompileSpec from the npu: and vela: nodes of the cbuild-mlops.yml."""
from executorch.backends.arm.ethosu import EthosUCompileSpec
@@ -109,17 +146,14 @@ def compile_spec(mlops: dict, mlops_dir: Path):
if not npu:
sys.exit("the solution's mlops: node names no NPU; this example needs an Ethos-U")
vela = mlops.get("vela", {})
- options = vela.get("options", "")
-
- def option(name: str) -> str | None:
- found = re.search(rf"--{name}[= ](\S+)", options)
- return found.group(1) if found else None
+ spec, extra = vela_options(vela.get("options", ""))
- target = option("accelerator-config") or f"{npu['type'].lower()}-{npu.get('macs', 256)}"
+ target = spec.get("accelerator-config") or f"{npu['type'].lower()}-{npu.get('macs', 256)}"
kwargs = {
"target": target,
- "system_config": option("system-config"),
- "memory_mode": option("memory-mode"),
+ "system_config": spec.get("system-config"),
+ "memory_mode": spec.get("memory-mode"),
+ "extra_flags": extra,
}
if vela.get("ini"):
# ExecuTorch stores the path in the compile spec, and the spec ends up
diff --git a/documentation/mlops-flow.md b/documentation/mlops-flow.md
index acb7d0a4..a8cd8739 100644
--- a/documentation/mlops-flow.md
+++ b/documentation/mlops-flow.md
@@ -86,7 +86,9 @@ for an MLOps system. It reads the file and:
1. builds ExecuTorch's `EthosUCompileSpec` from `npu:` and `vela:` -- the
accelerator (`ethos-u85-256`), system config and memory mode come from
- there, so the Python code contains no NPU configuration;
+ there, and every other option in `vela.options` (the `misc:` of the
+ csolution's `vela:` node) reaches Vela as an extra flag, so the Python code
+ contains no NPU configuration;
2. exports `model/model.py`: quantizes it, delegates the whole graph to the
Ethos-U and compiles it with Vela;
3. reads the operators the resulting program still calls on the CPU and looks
From 3b965027c1718a29bb5a959ae172f1af02243eb0 Mon Sep 17 00:00:00 2001
From: Matthias Hertel <11649409+MatthiasHertel80@users.noreply.github.com>
Date: Wed, 30 Sep 2026 15:28:32 +0200
Subject: [PATCH 17/21] Move to ExecuTorch 1.5.1
The PyTorch::ExecuTorch pack 1.5.1 is in the public pack index and
executorch 1.5.1 is on PyPI. It targets torch 2.14.0 (torch_pin.py at the
v1.5.1 tag) and keeps torchao 0.18, ethos-u-vela 5.1.0 and tosa-tools
2026.5.0, so requirements-arm-tosa.txt is unchanged. The pack pins in the
csolution and the cproject follow, and the generated layer and the RTE
context headers name the new pack version.
The exported program is 8784 bytes instead of 8832.
---
README.md | 8 +++---
RTE/_Debug_SSE-320-U85/Pre_Include_Global.h | 4 +--
RTE/_Debug_SSE-320-U85/RTE_Components.h | 12 ++++-----
ai_layer/ai_layer.clayer.yml | 2 +-
ai_layer/model_pte.c | 27 +++++++++------------
cmsis-executorch.cproject.yml | 2 +-
cmsis-executorch.csolution.yml | 2 +-
documentation/pack-provenance.md | 17 +++++++------
requirements.txt | 13 +++++-----
setup_venv.py | 2 +-
10 files changed, 44 insertions(+), 45 deletions(-)
diff --git a/README.md b/README.md
index a1e95bad..940e44d0 100644
--- a/README.md
+++ b/README.md
@@ -31,13 +31,13 @@ application.
The pack [`PyTorch::ExecuTorch`](https://www.keil.arm.com/packs/executorch-pytorch/) can be optionally installed manually with:
```bash
-cpackget add PyTorch::ExecuTorch@1.4.1
+cpackget add PyTorch::ExecuTorch@1.5.1
```
> [!Note]
> The pack and Python exporter versions must match, as the generated `.pte`
-> format is consumed by the runtime supplied in `PyTorch::ExecuTorch@1.4.1`.
-> The matching wheel is `executorch==1.4.1` from PyPI, pinned in
+> format is consumed by the runtime supplied in `PyTorch::ExecuTorch@1.5.1`.
+> The matching wheel is `executorch==1.5.1` from PyPI, pinned in
> `requirements.txt`.
## Quick start
@@ -73,7 +73,7 @@ Ethos-U version info:
Arch: v2.0.0
MACs/cc: 256
Cmd stream: v1
-ExecuTorch Ethos-U85 example: 8832 byte model
+ExecuTorch Ethos-U85 example: 8784 byte model
Output: 10 element(s): 0.0079 0.0459 0.0475 -0.0475 0.0791 0.0411 -0.0285 -0.0744 -0.2246 -0.0016
Test_result: PASS
```
diff --git a/RTE/_Debug_SSE-320-U85/Pre_Include_Global.h b/RTE/_Debug_SSE-320-U85/Pre_Include_Global.h
index 37e190f9..83d53110 100644
--- a/RTE/_Debug_SSE-320-U85/Pre_Include_Global.h
+++ b/RTE/_Debug_SSE-320-U85/Pre_Include_Global.h
@@ -12,10 +12,10 @@
/* ARM::Machine Learning:NPU Support:Ethos-U Driver&Generic U85@1.26.2 */
// enabling global pre includes
#define ETHOSU_ARCH u85
-/* PyTorch::Machine Learning:ExecuTorch:Backend EthosU@1.4.1 */
+/* PyTorch::Machine Learning:ExecuTorch:Backend EthosU@1.5.1 */
#define EXECUTORCH_BUILD_ARM_BAREMETAL 1
#define ET_USE_ETHOS_U_BACKEND 1
-/* PyTorch::Machine Learning:ExecuTorch:Runtime@1.4.1 */
+/* PyTorch::Machine Learning:ExecuTorch:Runtime@1.5.1 */
/* ExecuTorch global configuration */
#define C10_USING_CUSTOM_GENERATED_MACROS
#define FLATBUFFERS_MAX_ALIGNMENT 1024
diff --git a/RTE/_Debug_SSE-320-U85/RTE_Components.h b/RTE/_Debug_SSE-320-U85/RTE_Components.h
index 571f80b4..a80a87c2 100644
--- a/RTE/_Debug_SSE-320-U85/RTE_Components.h
+++ b/RTE/_Debug_SSE-320-U85/RTE_Components.h
@@ -30,17 +30,17 @@
#define RTE_TIMEOUT 1
/* ARM::Machine Learning:NPU Support:Ethos-U Driver&Generic U85@1.26.2 */
#define RTE_ETHOS_U_CORE_DRIVER
-/* PyTorch::Machine Learning:ExecuTorch Operators:Quantized dequantize@1.4.1 */
+/* PyTorch::Machine Learning:ExecuTorch Operators:Quantized dequantize@1.5.1 */
#define RTE_ML_EXECUTORCH_OP_QUANTIZED_DEQUANTIZE /* ExecuTorch op_dequantize */
-/* PyTorch::Machine Learning:ExecuTorch Operators:Quantized quantize@1.4.1 */
+/* PyTorch::Machine Learning:ExecuTorch Operators:Quantized quantize@1.5.1 */
#define RTE_ML_EXECUTORCH_OP_QUANTIZED_QUANTIZE /* ExecuTorch op_quantize */
-/* PyTorch::Machine Learning:ExecuTorch:Backend EthosU@1.4.1 */
+/* PyTorch::Machine Learning:ExecuTorch:Backend EthosU@1.5.1 */
#define RTE_ML_EXECUTORCH_BACKEND_ETHOS_U /* ExecuTorch Ethos-U Backend (Cortex-M host) */
-/* PyTorch::Machine Learning:ExecuTorch:Kernel Registration@1.4.1 */
+/* PyTorch::Machine Learning:ExecuTorch:Kernel Registration@1.5.1 */
#define RTE_ML_EXECUTORCH_KERNEL_REGISTRATION /* ExecuTorch Kernel Registration */
-/* PyTorch::Machine Learning:ExecuTorch:Kernel Utils@1.4.1 */
+/* PyTorch::Machine Learning:ExecuTorch:Kernel Utils@1.5.1 */
#define RTE_ML_EXECUTORCH_KERNEL_UTILS /* ExecuTorch Kernel Utils */
-/* PyTorch::Machine Learning:ExecuTorch:Runtime@1.4.1 */
+/* PyTorch::Machine Learning:ExecuTorch:Runtime@1.5.1 */
#define RTE_ML_EXECUTORCH_RUNTIME /* ExecuTorch Runtime */
diff --git a/ai_layer/ai_layer.clayer.yml b/ai_layer/ai_layer.clayer.yml
index 1322ac2c..87928945 100644
--- a/ai_layer/ai_layer.clayer.yml
+++ b/ai_layer/ai_layer.clayer.yml
@@ -6,7 +6,7 @@ layer:
description: TinyCNN int8 image classifier for Ethos-U85
packs:
- - pack: PyTorch::ExecuTorch@1.4.1
+ - pack: PyTorch::ExecuTorch@1.5.1
define:
- ET_LOG_ENABLED: 0
diff --git a/ai_layer/model_pte.c b/ai_layer/model_pte.c
index dd1ffd5c..6de9411d 100644
--- a/ai_layer/model_pte.c
+++ b/ai_layer/model_pte.c
@@ -3,10 +3,10 @@ __attribute__((aligned(16))) const unsigned char model_pte[] = {
0x24, 0x00, 0x00, 0x00, 0x45, 0x54, 0x31, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00, 0x14, 0x00, 0x10, 0x00, 0x0c, 0x00,
0x08, 0x00, 0x04, 0x00, 0x10, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00,
- 0x2c, 0x00, 0x00, 0x00, 0x8c, 0x1a, 0x00, 0x00, 0x8c, 0x1a, 0x00, 0x00, 0xf0, 0xe1, 0xff, 0xff,
+ 0x2c, 0x00, 0x00, 0x00, 0x8c, 0x1a, 0x00, 0x00, 0x8c, 0x1a, 0x00, 0x00, 0x20, 0xe2, 0xff, 0xff,
0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x20, 0xe0, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x72, 0xdf, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x50, 0x1a, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x50, 0xe0, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0xa2, 0xdf, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x50, 0x1a, 0x00, 0x00,
0x76, 0x65, 0x6c, 0x61, 0x5f, 0x62, 0x69, 0x6e, 0x5f, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x63, 0x6d, 0x64, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@@ -430,23 +430,20 @@ __attribute__((aligned(16))) const unsigned char model_pte[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00,
0x28, 0x00, 0x24, 0x00, 0x20, 0x00, 0x1c, 0x00, 0x18, 0x00, 0x14, 0x00, 0x10, 0x00, 0x0c, 0x00,
- 0x08, 0x00, 0x04, 0x00, 0x16, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00,
- 0x28, 0x02, 0x00, 0x00, 0xb8, 0x02, 0x00, 0x00, 0x90, 0x03, 0x00, 0x00, 0x94, 0x03, 0x00, 0x00,
- 0x98, 0x03, 0x00, 0x00, 0x20, 0x06, 0x00, 0x00, 0x6c, 0x07, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x04, 0x00, 0x16, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00,
+ 0xf8, 0x01, 0x00, 0x00, 0x88, 0x02, 0x00, 0x00, 0x60, 0x03, 0x00, 0x00, 0x64, 0x03, 0x00, 0x00,
+ 0x68, 0x03, 0x00, 0x00, 0xf0, 0x05, 0x00, 0x00, 0x3c, 0x07, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x72, 0xfd, 0xff, 0xff, 0x0c, 0x00, 0x00, 0x00,
- 0xd0, 0x01, 0x00, 0x00, 0xd0, 0x01, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x88, 0x01, 0x00, 0x00,
- 0xd0, 0x00, 0x00, 0x00, 0xa4, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x38, 0xfa, 0xff, 0xff, 0x08, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xa6, 0xfd, 0xff, 0xff,
+ 0x0c, 0x00, 0x00, 0x00, 0x9c, 0x01, 0x00, 0x00, 0x9c, 0x01, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
+ 0x54, 0x01, 0x00, 0x00, 0x9c, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x68, 0xfa, 0xff, 0xff, 0x08, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00,
0x0d, 0x00, 0x00, 0x00, 0x65, 0x74, 0x68, 0x6f, 0x73, 0x2d, 0x75, 0x38, 0x35, 0x2d, 0x32, 0x35,
0x36, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x00, 0x00,
- 0x64, 0xfa, 0xff, 0xff, 0x08, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
+ 0x94, 0xfa, 0xff, 0xff, 0x08, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
0x46, 0x61, 0x6c, 0x73, 0x65, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x70, 0x72, 0x65, 0x73,
0x65, 0x72, 0x76, 0x65, 0x5f, 0x69, 0x6f, 0x5f, 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x7a, 0x61,
- 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x9c, 0xfa, 0xff, 0xff, 0x08, 0x00, 0x00, 0x00,
- 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x6f, 0x75, 0x70, 0x75,
- 0x74, 0x5f, 0x72, 0x65, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x77, 0x6f, 0x72, 0x6b, 0x61, 0x72,
- 0x6f, 0x75, 0x6e, 0x64, 0x00, 0x00, 0x00, 0x00, 0xcc, 0xfa, 0xff, 0xff, 0x08, 0x00, 0x00, 0x00,
+ 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, 0x00, 0xcc, 0xfa, 0xff, 0xff, 0x08, 0x00, 0x00, 0x00,
0x0c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x76, 0x65, 0x6c, 0x61, 0x0d, 0x00, 0x00, 0x00,
0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x00, 0x00, 0x00,
0xf4, 0xfa, 0xff, 0xff, 0x08, 0x00, 0x00, 0x00, 0x98, 0x00, 0x00, 0x00, 0x8d, 0x00, 0x00, 0x00,
diff --git a/cmsis-executorch.cproject.yml b/cmsis-executorch.cproject.yml
index 5128d15f..62ac0773 100644
--- a/cmsis-executorch.cproject.yml
+++ b/cmsis-executorch.cproject.yml
@@ -7,7 +7,7 @@
# the Board layer brings the board bring-up.
project:
packs:
- - pack: PyTorch::ExecuTorch@1.4.1
+ - pack: PyTorch::ExecuTorch@1.5.1
- pack: ARM::CMSIS
misc:
diff --git a/cmsis-executorch.csolution.yml b/cmsis-executorch.csolution.yml
index acb57444..8f034c95 100644
--- a/cmsis-executorch.csolution.yml
+++ b/cmsis-executorch.csolution.yml
@@ -20,7 +20,7 @@ solution:
# Pinned exactly: the pack's C++ runtime and the Python exporter pinned in
# requirements.txt must be the same ExecuTorch version, or the .pte fails
# to load. `cbuild setup --packs` fetches it from the public index.
- - pack: PyTorch::ExecuTorch@1.4.1
+ - pack: PyTorch::ExecuTorch@1.5.1
- pack: ARM::CMSIS
- pack: ARM::CMSIS-NN
- pack: ARM::CMSIS-Compiler
diff --git a/documentation/pack-provenance.md b/documentation/pack-provenance.md
index 6fb19ab9..5cf2144a 100644
--- a/documentation/pack-provenance.md
+++ b/documentation/pack-provenance.md
@@ -11,14 +11,14 @@ The pack is published as an asset of the matching ExecuTorch GitHub release,
which is also what its `.pdsc` declares as its download location:
```xml
-https://github.com/pytorch/executorch/releases/download/v1.4.1/
+https://github.com/pytorch/executorch/releases/download/v1.5.1/
```
So the normal acquisition routes work, and `cbuild setup ... --packs` takes
care of it on a fresh clone. To install it by hand:
```bash
-cpackget add PyTorch::ExecuTorch@1.4.1
+cpackget add PyTorch::ExecuTorch@1.5.1
```
The version is pinned exactly, in `cmsis-executorch.csolution.yml` and
@@ -26,10 +26,10 @@ The version is pinned exactly, in `cmsis-executorch.csolution.yml` and
```yaml
packs:
- - pack: PyTorch::ExecuTorch@1.4.1
+ - pack: PyTorch::ExecuTorch@1.5.1
```
-Both must agree. The pin is exact rather than a `@^1.4.1` range because the
+Both must agree. The pin is exact rather than a `@^1.5.1` range because the
pack's C++ runtime and the Python exporter have to be the *same* ExecuTorch
version — see [Moving to a new ExecuTorch version](#moving-to-a-new-executorch-version).
@@ -63,7 +63,7 @@ ExecuTorch tree at `backends/arm/cmsis_pack/scripts/build_pack.sh`.
```bash
git clone https://github.com/pytorch/executorch.git
cd executorch
-git checkout release/1.4 # or the tag matching your target version
+git checkout release/1.5 # or the tag matching your target version
git submodule update --init --recursive
```
@@ -111,7 +111,7 @@ them); the pack lands in the output directory:
backends/arm/cmsis_pack/scripts/build_pack.sh \
--executorch-root "$PWD" \
--build-dir cmake-out-arm \
- --version 1.4.1-local \
+ --version 1.5.1-local \
--output-dir pack-output
```
@@ -120,7 +120,7 @@ backends/arm/cmsis_pack/scripts/build_pack.sh \
Before trusting a freshly built pack, check the parts that go missing quietly:
```bash
-VERSION=1.4.1-local
+VERSION=1.5.1-local
PACK=pack-output/PyTorch.ExecuTorch.$VERSION
set -e
@@ -158,7 +158,8 @@ in order:
2. **Update both pins** — `PyTorch::ExecuTorch@` in the csolution and in
the cproject.
3. **Update the Python pins**: `executorch`, `torch` and `torchao` in
- `requirements.txt`, following the new release's `install_requirements.py`;
+ `requirements.txt`, following the new release's `torch_pin.py` and
+ `install_requirements.py`;
`requirements-arm-tosa.txt` (the TOSA serializer and its flatbuffers pin)
only changes when the Arm backend's `requirements-arm-tosa.txt` does.
4. **Rebuild the venv, the AI layer and the project:**
diff --git a/requirements.txt b/requirements.txt
index 1fd6d11f..75f8ff19 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -3,12 +3,13 @@
#
# executorch, torch and torchao must match: a .pte exported by one ExecuTorch
# version only loads in the runtime of the same version, and that runtime is
-# the PyTorch::ExecuTorch@1.4.1 pack pinned in the csolution. ExecuTorch 1.4.1
-# targets torch 2.13.0 and torchao 0.18.0 (install_requirements.py on the
-# release/1.4 branch); prebuilt wheels exist for Linux, macOS and Windows,
-# Python 3.10 to 3.14 (the export venv stays below 3.14: see requirements-arm-tosa.txt).
-executorch==1.4.1
-torch==2.13.0
+# the PyTorch::ExecuTorch@1.5.1 pack pinned in the csolution. ExecuTorch 1.5.1
+# targets torch 2.14.0 (torch_pin.py at the v1.5.1 tag) and torchao 0.18
+# (the wheel requires torchao>=0.18.0,<0.19); prebuilt wheels exist for Linux,
+# macOS and Windows, Python 3.10 to 3.14 (the export venv stays below 3.14:
+# see requirements-arm-tosa.txt).
+executorch==1.5.1
+torch==2.14.0
torchao==0.18.0
# Vela, the Ethos-U compiler. Same pin as executorch's `ethos_u` extra.
diff --git a/setup_venv.py b/setup_venv.py
index 4e987584..0d79bc06 100755
--- a/setup_venv.py
+++ b/setup_venv.py
@@ -22,7 +22,7 @@
HERE = Path(__file__).resolve().parent
VENV_DIR = HERE / ".venv"
-# ExecuTorch 1.4 declares requires-python = ">=3.10,<3.15" in its pyproject.toml,
+# ExecuTorch 1.5 declares requires-python = ">=3.10,<3.15" in its pyproject.toml,
# but the tosa-tools 2026.5.0 it pins for the Arm backend (see
# requirements-arm-tosa.txt) only ships wheels up to CPython 3.13, so on 3.14
# pip reports "No matching distribution found for tosa-tools==2026.5.0".
From 7c1d2398e8407a00526c8f6c45a8e9ebbc0ed5e3 Mon Sep 17 00:00:00 2001
From: Matthias Hertel <11649409+MatthiasHertel80@users.noreply.github.com>
Date: Wed, 30 Sep 2026 15:29:31 +0200
Subject: [PATCH 18/21] Take the model input as NHWC
TinyCNN took its image as NCHW. The Ethos-U computes on NHWC feature
maps, so the command stream began with an operation that only reorders
the input. The model now takes NHWC, the layout of a camera frame, and
permutes it to NCHW itself; Vela folds that into the first convolution.
The layout conversion is gone from the command stream, the program is
8720 bytes instead of 8784, and the Ethos-U scratch 5376 bytes instead
of 8704. The runner declares its input as 1x16x16x3; its ramp now fills
the image in that order, so the logits differ from the ones before.
---
README.md | 9 ++-
ai_layer/model_pte.c | 158 +++++++++++++++++++++----------------------
model/model.py | 12 ++--
src/app_main.cpp | 6 +-
4 files changed, 95 insertions(+), 90 deletions(-)
diff --git a/README.md b/README.md
index 940e44d0..22bcc473 100644
--- a/README.md
+++ b/README.md
@@ -73,8 +73,8 @@ Ethos-U version info:
Arch: v2.0.0
MACs/cc: 256
Cmd stream: v1
-ExecuTorch Ethos-U85 example: 8784 byte model
-Output: 10 element(s): 0.0079 0.0459 0.0475 -0.0475 0.0791 0.0411 -0.0285 -0.0744 -0.2246 -0.0016
+ExecuTorch Ethos-U85 example: 8720 byte model
+Output: 10 element(s): -0.0142 0.0206 0.0601 -0.0886 0.0791 0.0522 -0.0301 -0.0775 -0.2800 -0.0095
Test_result: PASS
```
@@ -211,6 +211,11 @@ fixed shape and prints the output as ten floats, so a model with another input
shape or output needs matching changes there. Then re-run `create_ai_layer.py`
and build.
+Give an image model its input as NHWC, the layout of a camera frame, as
+`model/model.py` does: the model permutes it to the NCHW its convolutions take,
+and Vela folds that into the first convolution. With an NCHW input the NPU
+first reorders the image in an operation of its own.
+
To target another Ethos-U configuration, update the target and `mlops:`
settings in the CMSIS solution and re-run all three steps. The generated Vela
options then follow that configuration automatically; `vela: misc:` takes any
diff --git a/ai_layer/model_pte.c b/ai_layer/model_pte.c
index 6de9411d..75bd1634 100644
--- a/ai_layer/model_pte.c
+++ b/ai_layer/model_pte.c
@@ -3,78 +3,74 @@ __attribute__((aligned(16))) const unsigned char model_pte[] = {
0x24, 0x00, 0x00, 0x00, 0x45, 0x54, 0x31, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00, 0x14, 0x00, 0x10, 0x00, 0x0c, 0x00,
0x08, 0x00, 0x04, 0x00, 0x10, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00,
- 0x2c, 0x00, 0x00, 0x00, 0x8c, 0x1a, 0x00, 0x00, 0x8c, 0x1a, 0x00, 0x00, 0x20, 0xe2, 0xff, 0xff,
+ 0x2c, 0x00, 0x00, 0x00, 0x4c, 0x1a, 0x00, 0x00, 0x4c, 0x1a, 0x00, 0x00, 0x60, 0xe2, 0xff, 0xff,
0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x50, 0xe0, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0xa2, 0xdf, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x50, 0x1a, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x90, 0xe0, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0xe2, 0xdf, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x10, 0x1a, 0x00, 0x00,
0x76, 0x65, 0x6c, 0x61, 0x5f, 0x62, 0x69, 0x6e, 0x5f, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x63, 0x6d, 0x64, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xb4, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x43, 0x4f, 0x50, 0x31, 0x01, 0x00, 0x10, 0x00, 0x18, 0x01, 0x00, 0x20, 0x00, 0x70, 0x00, 0x20,
- 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x02, 0x00, 0xf8, 0x00,
+ 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x02, 0x00, 0xe5, 0x00,
+ 0x30, 0x01, 0x00, 0x00, 0x30, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0x01, 0x01, 0x00,
+ 0x31, 0x40, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x32, 0x40, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00,
+ 0x32, 0x01, 0x01, 0x00, 0x10, 0x00, 0x00, 0x00, 0x24, 0x40, 0x22, 0x20, 0x23, 0x72, 0xc9, 0x02,
0x05, 0x01, 0x01, 0x00, 0x0f, 0x01, 0x01, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00,
0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x03, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x01, 0x02, 0x00, 0x0c, 0x01, 0x02, 0x00,
- 0x0a, 0x01, 0x0f, 0x00, 0x04, 0x01, 0x0f, 0x00, 0x05, 0x40, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
- 0x04, 0x40, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x06, 0x40, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x09, 0x01, 0x00, 0x00, 0x07, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
- 0x03, 0x01, 0x00, 0x00, 0x02, 0x01, 0x00, 0x00, 0x14, 0x01, 0x41, 0x19, 0x1f, 0x01, 0x01, 0x00,
+ 0x03, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x01, 0x0f, 0x00, 0x0c, 0x01, 0x0f, 0x00,
+ 0x0a, 0x01, 0x0f, 0x00, 0x04, 0x01, 0x02, 0x00, 0x05, 0x40, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
+ 0x04, 0x40, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x40, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x09, 0x01, 0x00, 0x00, 0x07, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x01, 0x00,
+ 0x03, 0x01, 0x01, 0x00, 0x02, 0x01, 0x01, 0x00, 0x14, 0x01, 0x41, 0x00, 0x1f, 0x01, 0x01, 0x00,
0x10, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x12, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x12, 0x01, 0x02, 0x00, 0x11, 0x01, 0x0f, 0x00, 0x13, 0x01, 0x0f, 0x00, 0x1b, 0x01, 0x0f, 0x00,
+ 0x12, 0x01, 0x0f, 0x00, 0x11, 0x01, 0x0f, 0x00, 0x13, 0x01, 0x07, 0x00, 0x1b, 0x01, 0x0f, 0x00,
0x1c, 0x01, 0x0f, 0x00, 0x1a, 0x01, 0x0f, 0x00, 0x15, 0x40, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
0x14, 0x40, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x16, 0x40, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
- 0x18, 0x01, 0x00, 0x00, 0x21, 0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x22, 0x01, 0x00, 0x00,
- 0x25, 0x01, 0x00, 0x00, 0x26, 0x01, 0x80, 0xff, 0x27, 0x01, 0x7f, 0x00, 0x24, 0x40, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x16, 0x01, 0x0f, 0x00, 0x15, 0x01, 0x0f, 0x00, 0x17, 0x01, 0x0f, 0x00,
- 0x24, 0x01, 0x00, 0x01, 0x2f, 0x01, 0x00, 0x00, 0x05, 0x00, 0x03, 0x00, 0x30, 0x01, 0x00, 0x00,
- 0x30, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0x01, 0x01, 0x00, 0x31, 0x40, 0x00, 0x00,
- 0x00, 0x20, 0x00, 0x00, 0x32, 0x40, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x32, 0x01, 0x01, 0x00,
- 0x10, 0x00, 0x00, 0x00, 0x24, 0x40, 0x22, 0x20, 0x23, 0x72, 0xc9, 0x02, 0x05, 0x01, 0x41, 0x00,
- 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x01, 0x0f, 0x00, 0x0c, 0x01, 0x0f, 0x00,
- 0x04, 0x01, 0x02, 0x00, 0x06, 0x40, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00,
- 0x01, 0x01, 0x01, 0x00, 0x03, 0x01, 0x01, 0x00, 0x02, 0x01, 0x01, 0x00, 0x14, 0x01, 0x41, 0x00,
- 0x10, 0x40, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x12, 0x01, 0x0f, 0x00, 0x13, 0x01, 0x07, 0x00,
0x18, 0x01, 0x80, 0xff, 0x21, 0x01, 0x02, 0x00, 0x20, 0x01, 0x02, 0x00, 0x22, 0x01, 0x04, 0x00,
- 0x2e, 0x01, 0x00, 0x00, 0x28, 0x01, 0x01, 0x00, 0x20, 0x40, 0x00, 0x00, 0xa0, 0x20, 0x00, 0x00,
+ 0x2e, 0x01, 0x00, 0x00, 0x28, 0x01, 0x01, 0x00, 0x20, 0x40, 0x00, 0x00, 0xa0, 0x13, 0x00, 0x00,
0x21, 0x40, 0x00, 0x00, 0x60, 0x01, 0x00, 0x00, 0x29, 0x01, 0x01, 0x00, 0x22, 0x40, 0x00, 0x00,
- 0x00, 0x20, 0x00, 0x00, 0x23, 0x40, 0x00, 0x00, 0xa0, 0x00, 0x00, 0x00, 0x16, 0x01, 0x07, 0x00,
- 0x24, 0x01, 0x00, 0x02, 0x11, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00,
- 0x00, 0x10, 0x00, 0x00, 0x04, 0x01, 0x07, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
- 0x03, 0x01, 0x00, 0x00, 0x02, 0x01, 0x00, 0x00, 0x14, 0x01, 0x41, 0x01, 0x10, 0x40, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x12, 0x01, 0x07, 0x00, 0x11, 0x01, 0x07, 0x00, 0x1b, 0x01, 0x07, 0x00,
- 0x1c, 0x01, 0x07, 0x00, 0x1a, 0x01, 0x07, 0x00, 0x15, 0x40, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
- 0x16, 0x40, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x18, 0x01, 0x00, 0x00, 0x21, 0x01, 0x01, 0x00,
- 0x20, 0x01, 0x01, 0x00, 0x22, 0x01, 0x03, 0x00, 0x24, 0x40, 0x00, 0x20, 0x01, 0x00, 0x00, 0x00,
- 0x15, 0x01, 0x07, 0x00, 0x24, 0x01, 0x00, 0x01, 0x05, 0x00, 0x00, 0x00, 0x24, 0x40, 0x26, 0x20,
- 0x9f, 0x71, 0x5e, 0x18, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x01, 0x07, 0x00,
- 0x0c, 0x01, 0x07, 0x00, 0x0a, 0x01, 0x07, 0x00, 0x05, 0x40, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
- 0x06, 0x40, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x09, 0x01, 0x80, 0xff, 0x00, 0x01, 0x01, 0x00,
- 0x01, 0x01, 0x01, 0x00, 0x03, 0x01, 0x01, 0x00, 0x02, 0x01, 0x01, 0x00, 0x14, 0x01, 0x41, 0x00,
- 0x10, 0x40, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x13, 0x01, 0x0f, 0x00, 0x18, 0x01, 0x80, 0xff,
- 0x21, 0x01, 0x02, 0x00, 0x20, 0x01, 0x02, 0x00, 0x22, 0x01, 0x04, 0x00, 0x28, 0x01, 0x00, 0x00,
- 0x20, 0x40, 0x00, 0x00, 0xa0, 0x02, 0x00, 0x00, 0x21, 0x40, 0x00, 0x00, 0xc0, 0x05, 0x00, 0x00,
- 0x29, 0x01, 0x00, 0x00, 0x22, 0x40, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x24, 0x01, 0x00, 0x02,
- 0x02, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0x01, 0x0f, 0x00,
- 0x09, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x03, 0x01, 0x00, 0x00,
- 0x02, 0x01, 0x00, 0x00, 0x14, 0x01, 0x01, 0x31, 0x10, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x12, 0x01, 0x03, 0x00, 0x11, 0x01, 0x03, 0x00, 0x1b, 0x01, 0x0f, 0x00, 0x1c, 0x01, 0x0f, 0x00,
- 0x1a, 0x01, 0x03, 0x00, 0x15, 0x40, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x14, 0x40, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x16, 0x40, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x18, 0x01, 0x00, 0x00,
+ 0x00, 0x13, 0x00, 0x00, 0x23, 0x40, 0x00, 0x00, 0xa0, 0x00, 0x00, 0x00, 0x25, 0x01, 0x00, 0x00,
+ 0x26, 0x01, 0x80, 0xff, 0x27, 0x01, 0x7f, 0x00, 0x16, 0x01, 0x07, 0x00, 0x15, 0x01, 0x0f, 0x00,
+ 0x17, 0x01, 0x0f, 0x00, 0x24, 0x01, 0x00, 0x02, 0x2f, 0x01, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x05, 0x01, 0x41, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x01, 0x07, 0x00, 0x05, 0x40, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x04, 0x40, 0x00, 0x00,
+ 0x10, 0x00, 0x00, 0x00, 0x06, 0x40, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x03, 0x01, 0x00, 0x00, 0x02, 0x01, 0x00, 0x00, 0x14, 0x01, 0x41, 0x01,
+ 0x10, 0x40, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x12, 0x01, 0x07, 0x00, 0x11, 0x01, 0x07, 0x00,
+ 0x1b, 0x01, 0x07, 0x00, 0x1c, 0x01, 0x07, 0x00, 0x1a, 0x01, 0x07, 0x00, 0x15, 0x40, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x16, 0x40, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x18, 0x01, 0x00, 0x00,
0x21, 0x01, 0x01, 0x00, 0x20, 0x01, 0x01, 0x00, 0x22, 0x01, 0x03, 0x00, 0x24, 0x40, 0x00, 0x20,
- 0x01, 0x00, 0x00, 0x00, 0x16, 0x01, 0x03, 0x00, 0x15, 0x01, 0x03, 0x00, 0x24, 0x01, 0x00, 0x01,
- 0x05, 0x00, 0x00, 0x00, 0x24, 0x40, 0x29, 0x20, 0x8d, 0xa0, 0x86, 0x34, 0x05, 0x01, 0x01, 0x00,
- 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x01, 0x00, 0x00, 0x0c, 0x01, 0x00, 0x00,
- 0x0a, 0x01, 0x00, 0x00, 0x04, 0x01, 0xff, 0x00, 0x05, 0x40, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
- 0x04, 0x40, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x06, 0x40, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x09, 0x01, 0x80, 0xff, 0x14, 0x01, 0x01, 0x00, 0x10, 0x40, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
- 0x12, 0x01, 0x00, 0x00, 0x11, 0x01, 0x00, 0x00, 0x13, 0x01, 0x09, 0x00, 0x1b, 0x01, 0x00, 0x00,
- 0x1c, 0x01, 0x00, 0x00, 0x1a, 0x01, 0x00, 0x00, 0x15, 0x40, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
- 0x14, 0x40, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x18, 0x01, 0x4d, 0x00, 0x21, 0x01, 0x00, 0x00,
- 0x20, 0x01, 0x00, 0x00, 0x22, 0x01, 0x00, 0x00, 0x20, 0x40, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00,
- 0x21, 0x40, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x22, 0x40, 0x00, 0x00, 0x60, 0x08, 0x00, 0x00,
- 0x16, 0x01, 0x00, 0x00, 0x24, 0x01, 0x00, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff,
+ 0x01, 0x00, 0x00, 0x00, 0x15, 0x01, 0x07, 0x00, 0x24, 0x01, 0x00, 0x01, 0x05, 0x00, 0x00, 0x00,
+ 0x24, 0x40, 0x28, 0x20, 0xc1, 0xc4, 0x79, 0x61, 0x00, 0x40, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00,
+ 0x0b, 0x01, 0x07, 0x00, 0x0c, 0x01, 0x07, 0x00, 0x0a, 0x01, 0x07, 0x00, 0x05, 0x40, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x06, 0x40, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x09, 0x01, 0x80, 0xff,
+ 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x01, 0x00, 0x03, 0x01, 0x01, 0x00, 0x02, 0x01, 0x01, 0x00,
+ 0x14, 0x01, 0x41, 0x00, 0x10, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x01, 0x0f, 0x00,
+ 0x18, 0x01, 0x80, 0xff, 0x21, 0x01, 0x02, 0x00, 0x20, 0x01, 0x02, 0x00, 0x22, 0x01, 0x04, 0x00,
+ 0x28, 0x01, 0x00, 0x00, 0x20, 0x40, 0x00, 0x00, 0xa0, 0x02, 0x00, 0x00, 0x21, 0x40, 0x00, 0x00,
+ 0xc0, 0x05, 0x00, 0x00, 0x29, 0x01, 0x00, 0x00, 0x22, 0x40, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00,
+ 0x24, 0x01, 0x00, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x01, 0x0f, 0x00, 0x09, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x03, 0x01, 0x00, 0x00, 0x02, 0x01, 0x00, 0x00, 0x14, 0x01, 0x01, 0x31, 0x10, 0x40, 0x00, 0x00,
+ 0x00, 0x04, 0x00, 0x00, 0x12, 0x01, 0x03, 0x00, 0x11, 0x01, 0x03, 0x00, 0x1b, 0x01, 0x0f, 0x00,
+ 0x1c, 0x01, 0x0f, 0x00, 0x1a, 0x01, 0x03, 0x00, 0x15, 0x40, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
+ 0x14, 0x40, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x16, 0x40, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x18, 0x01, 0x00, 0x00, 0x21, 0x01, 0x01, 0x00, 0x20, 0x01, 0x01, 0x00, 0x22, 0x01, 0x03, 0x00,
+ 0x24, 0x40, 0x00, 0x20, 0x01, 0x00, 0x00, 0x00, 0x16, 0x01, 0x03, 0x00, 0x15, 0x01, 0x03, 0x00,
+ 0x24, 0x01, 0x00, 0x01, 0x05, 0x00, 0x00, 0x00, 0x24, 0x40, 0x29, 0x20, 0x3b, 0xa1, 0x86, 0x34,
+ 0x05, 0x01, 0x01, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x0b, 0x01, 0x00, 0x00,
+ 0x0c, 0x01, 0x00, 0x00, 0x0a, 0x01, 0x00, 0x00, 0x04, 0x01, 0xff, 0x00, 0x05, 0x40, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x00, 0x04, 0x40, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x06, 0x40, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x09, 0x01, 0x80, 0xff, 0x14, 0x01, 0x01, 0x00, 0x10, 0x40, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x12, 0x01, 0x00, 0x00, 0x11, 0x01, 0x00, 0x00, 0x13, 0x01, 0x09, 0x00,
+ 0x1b, 0x01, 0x00, 0x00, 0x1c, 0x01, 0x00, 0x00, 0x1a, 0x01, 0x00, 0x00, 0x15, 0x40, 0x00, 0x00,
+ 0x0a, 0x00, 0x00, 0x00, 0x14, 0x40, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x18, 0x01, 0x4d, 0x00,
+ 0x21, 0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x22, 0x01, 0x00, 0x00, 0x20, 0x40, 0x00, 0x00,
+ 0x00, 0x09, 0x00, 0x00, 0x21, 0x40, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x22, 0x40, 0x00, 0x00,
+ 0x60, 0x08, 0x00, 0x00, 0x16, 0x01, 0x00, 0x00, 0x24, 0x01, 0x00, 0x02, 0x02, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x5f, 0x98, 0xff, 0xff, 0x23, 0x72, 0xc9, 0x02, 0x22, 0x00, 0x45, 0xbd, 0xff, 0xff, 0xb9, 0x1d,
@@ -109,16 +105,16 @@ __attribute__((aligned(16))) const unsigned char model_pte[] = {
0x14, 0x63, 0xf4, 0x94, 0xc4, 0xc2, 0x40, 0x9a, 0xbb, 0x12, 0x12, 0xfe, 0xa7, 0x91, 0xa9, 0x17,
0x37, 0x1f, 0x53, 0x1b, 0x49, 0xfe, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x0f, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
- 0x68, 0x13, 0x00, 0x00, 0x9f, 0x71, 0x5e, 0x18, 0x26, 0x00, 0x01, 0x4b, 0x00, 0x00, 0xd1, 0xbb,
- 0x93, 0x18, 0x26, 0x00, 0x80, 0xe5, 0xff, 0xff, 0xef, 0x3d, 0x28, 0x31, 0x27, 0x00, 0xa0, 0x3a,
- 0x00, 0x00, 0x19, 0xbb, 0xe0, 0x30, 0x27, 0x00, 0xdd, 0xeb, 0x00, 0x00, 0xc3, 0x94, 0x2d, 0x62,
- 0x28, 0x00, 0x35, 0x62, 0xff, 0xff, 0x0b, 0x5b, 0xf4, 0x5f, 0x28, 0x00, 0xb3, 0xf2, 0xff, 0xff,
- 0x15, 0xe2, 0x05, 0x30, 0x27, 0x00, 0x06, 0xa5, 0xff, 0xff, 0x2d, 0xdb, 0x45, 0x0c, 0x25, 0x00,
- 0xf4, 0xd1, 0x00, 0x00, 0x67, 0xa2, 0xe5, 0x30, 0x27, 0x00, 0xb7, 0xcb, 0x00, 0x00, 0x49, 0x9e,
- 0x54, 0x62, 0x28, 0x00, 0x7f, 0xf9, 0xff, 0xff, 0x07, 0x61, 0x1f, 0x62, 0x28, 0x00, 0x0e, 0xad,
- 0xff, 0xff, 0x31, 0x8e, 0x72, 0x18, 0x26, 0x00, 0x69, 0x6c, 0xff, 0xff, 0xf9, 0xbd, 0x3e, 0x0c,
- 0x25, 0x00, 0xb6, 0xac, 0x00, 0x00, 0xdd, 0x24, 0xf2, 0x60, 0x28, 0x00, 0x85, 0xef, 0xff, 0xff,
- 0xbb, 0x83, 0xc8, 0x61, 0x28, 0x00, 0x4b, 0xe3, 0x00, 0x00, 0xa9, 0x82, 0x3d, 0x18, 0x26, 0x00,
+ 0x68, 0x13, 0x00, 0x00, 0xc1, 0xc4, 0x79, 0x61, 0x28, 0x00, 0x01, 0x4b, 0x00, 0x00, 0x85, 0xed,
+ 0x4e, 0x62, 0x28, 0x00, 0x80, 0xe5, 0xff, 0xff, 0x1f, 0x7a, 0x50, 0x62, 0x28, 0x00, 0xa0, 0x3a,
+ 0x00, 0x00, 0x3b, 0xba, 0xe0, 0x30, 0x27, 0x00, 0xdd, 0xeb, 0x00, 0x00, 0x05, 0x93, 0x2d, 0x62,
+ 0x28, 0x00, 0x35, 0x62, 0xff, 0xff, 0x57, 0x59, 0xf4, 0x5f, 0x28, 0x00, 0xb3, 0xf2, 0xff, 0xff,
+ 0x3b, 0xe1, 0x05, 0x30, 0x27, 0x00, 0x06, 0xa5, 0xff, 0xff, 0xa9, 0xd7, 0x2e, 0x62, 0x28, 0x00,
+ 0xf4, 0xd1, 0x00, 0x00, 0x89, 0xa1, 0xe5, 0x30, 0x27, 0x00, 0xb7, 0xcb, 0x00, 0x00, 0x45, 0x4e,
+ 0x2a, 0x31, 0x27, 0x00, 0x7f, 0xf9, 0xff, 0xff, 0x49, 0x5f, 0x1f, 0x62, 0x28, 0x00, 0x0e, 0xad,
+ 0xff, 0xff, 0x07, 0x37, 0xca, 0x61, 0x28, 0x00, 0x69, 0x6c, 0xff, 0xff, 0x05, 0xf7, 0xfa, 0x30,
+ 0x27, 0x00, 0xb6, 0xac, 0x00, 0x00, 0x25, 0x23, 0xf2, 0x60, 0x28, 0x00, 0x85, 0xef, 0xff, 0xff,
+ 0xff, 0x81, 0xc8, 0x61, 0x28, 0x00, 0x4b, 0xe3, 0x00, 0x00, 0xeb, 0x08, 0xf6, 0x60, 0x28, 0x00,
0xc8, 0x23, 0x5c, 0x01, 0x0c, 0x00, 0xdd, 0xe4, 0x51, 0x20, 0x93, 0x44, 0x1d, 0x4a, 0x00, 0xe0,
0xba, 0xeb, 0x25, 0xc1, 0x9f, 0x95, 0x85, 0x03, 0x00, 0xad, 0xce, 0x82, 0xa8, 0x66, 0x08, 0x0c,
0x5c, 0xf5, 0xe5, 0xcb, 0xa9, 0xd9, 0xc2, 0xd4, 0x96, 0x00, 0x50, 0xd4, 0xc3, 0xef, 0xd2, 0x75,
@@ -211,13 +207,13 @@ __attribute__((aligned(16))) const unsigned char model_pte[] = {
0x9a, 0xfa, 0x0d, 0x00, 0x35, 0xfc, 0xd3, 0x31, 0x9c, 0xfc, 0xea, 0x65, 0xf0, 0xbf, 0xcb, 0x9c,
0xc0, 0xb2, 0x44, 0x04, 0x71, 0xf5, 0xff, 0x78, 0x2a, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0f, 0xf0,
- 0xbb, 0xf7, 0xfe, 0xff, 0x8d, 0xa0, 0x86, 0x34, 0x29, 0x00, 0xd8, 0x70, 0x01, 0x00, 0xa7, 0x3f,
- 0x36, 0x34, 0x29, 0x00, 0x56, 0x05, 0x01, 0x00, 0x81, 0x58, 0xbc, 0x68, 0x2a, 0x00, 0x31, 0xf9,
- 0xfe, 0xff, 0xcf, 0xea, 0xef, 0x68, 0x2a, 0x00, 0xcb, 0xa8, 0x00, 0x00, 0x49, 0x3e, 0x33, 0x34,
- 0x29, 0x00, 0x2c, 0x9e, 0x00, 0x00, 0xcd, 0x98, 0x1a, 0x69, 0x2a, 0x00, 0x94, 0xeb, 0xfe, 0xff,
- 0x1d, 0x76, 0x82, 0x34, 0x29, 0x00, 0x8d, 0xf0, 0xfe, 0xff, 0x4b, 0x61, 0xbe, 0x68, 0x2a, 0x00,
- 0x4c, 0xc0, 0xfe, 0xff, 0x5b, 0xfb, 0xc7, 0x68, 0x2a, 0x00, 0x15, 0xac, 0x00, 0x00, 0xeb, 0x67,
- 0x2e, 0x1a, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xbb, 0xf7, 0xfe, 0xff, 0x3b, 0xa1, 0x86, 0x34, 0x29, 0x00, 0xd8, 0x70, 0x01, 0x00, 0x15, 0x90,
+ 0x0d, 0x0d, 0x27, 0x00, 0x56, 0x05, 0x01, 0x00, 0x77, 0x16, 0x2f, 0x1a, 0x28, 0x00, 0x31, 0xf9,
+ 0xfe, 0xff, 0x2b, 0xec, 0xef, 0x68, 0x2a, 0x00, 0xcb, 0xa8, 0x00, 0x00, 0x7b, 0x9f, 0x19, 0x1a,
+ 0x28, 0x00, 0x2c, 0x9e, 0x00, 0x00, 0x45, 0x53, 0x23, 0x0d, 0x27, 0x00, 0x94, 0xeb, 0xfe, 0xff,
+ 0xcb, 0x76, 0x82, 0x34, 0x29, 0x00, 0x8d, 0xf0, 0xfe, 0xff, 0x53, 0x31, 0x5f, 0x34, 0x29, 0x00,
+ 0x4c, 0xc0, 0xfe, 0xff, 0x5b, 0xfe, 0x63, 0x34, 0x29, 0x00, 0x15, 0xac, 0x00, 0x00, 0x07, 0xa1,
+ 0xb9, 0x68, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@@ -415,17 +411,17 @@ __attribute__((aligned(16))) const unsigned char model_pte[] = {
0xff, 0x84, 0x38, 0xff, 0xff, 0xff, 0x0f, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x73, 0x63, 0x72, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x00, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x69, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x03, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x00, 0x10, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x76, 0x65, 0x6c, 0x61, 0x5f, 0x65, 0x6e, 0x64, 0x5f, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00,
@@ -498,7 +494,7 @@ __attribute__((aligned(16))) const unsigned char model_pte[] = {
0x00, 0x00, 0x00, 0x02, 0x32, 0xfe, 0xff, 0xff, 0x80, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xf0, 0xfe, 0xff, 0xff, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x4a, 0xfe, 0xff, 0xff,
0x4d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0xff, 0xff, 0xff, 0x08, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x04, 0x62, 0xfe, 0xff, 0xff, 0x00, 0x00, 0x00, 0x20, 0x31, 0xeb, 0x59, 0x3f,
+ 0x00, 0x00, 0x00, 0x04, 0x62, 0xfe, 0xff, 0xff, 0x00, 0x00, 0x00, 0x40, 0x31, 0xeb, 0x59, 0x3f,
0x00, 0x00, 0x00, 0x00, 0x24, 0xff, 0xff, 0xff, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05,
0x02, 0xff, 0xff, 0xff, 0x10, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x06, 0x92, 0xfe, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
@@ -520,13 +516,13 @@ __attribute__((aligned(16))) const unsigned char model_pte[] = {
0x12, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, 0x78, 0xff, 0xff, 0xff, 0x00, 0x0c, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x03, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x08, 0x00, 0x0a, 0x00,
+ 0x10, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x08, 0x00, 0x0a, 0x00,
0x09, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x05, 0x12, 0x00,
0x12, 0x00, 0x11, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00,
0x12, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00,
0x00, 0x06, 0x06, 0x00, 0x08, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x03, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x08, 0x00, 0x0c, 0x00,
+ 0x10, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x08, 0x00, 0x0c, 0x00,
0x08, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00,
0x39, 0x00, 0x00, 0x00, 0x5b, 0x31, 0x2c, 0x20, 0x7b, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3a,
0x20, 0x6e, 0x75, 0x6c, 0x6c, 0x2c, 0x20, 0x22, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x22,
diff --git a/model/model.py b/model/model.py
index d9d54a64..8af0e024 100644
--- a/model/model.py
+++ b/model/model.py
@@ -4,19 +4,22 @@
Small enough to export in seconds and fully partition onto the NPU, but real
enough to exercise conv / relu / pool / linear through Vela. Input is a single
-16x16 RGB image (NCHW); output is a 10-class logit vector.
+16x16 RGB image (NHWC); output is a 10-class logit vector.
"""
import torch
from torch import nn
-INPUT_SHAPE = (1, 3, 16, 16)
+# NHWC, the layout of a camera frame, which the Ethos-U reads as it is. An NCHW
+# input costs one more NPU operation that reorders it before the first
+# convolution.
+INPUT_SHAPE = (1, 16, 16, 3)
class TinyCNN(nn.Module):
def __init__(self, input_shape: tuple[int, ...] = INPUT_SHAPE, num_classes: int = 10) -> None:
super().__init__()
- _, channels, height, width = input_shape
+ _, height, width, channels = input_shape
self.features = nn.Sequential(
nn.Conv2d(channels, 8, kernel_size=3, padding=1),
nn.ReLU(),
@@ -28,7 +31,8 @@ def __init__(self, input_shape: tuple[int, ...] = INPUT_SHAPE, num_classes: int
self.classifier = nn.Linear(16 * (height // 4) * (width // 4), num_classes)
def forward(self, x: torch.Tensor) -> torch.Tensor:
- x = self.features(x)
+ # The convolutions take NCHW. Vela folds the permutation into the first one.
+ x = self.features(x.permute(0, 3, 1, 2))
x = torch.flatten(x, 1)
return self.classifier(x)
diff --git a/src/app_main.cpp b/src/app_main.cpp
index 54d1e758..b7fc869f 100644
--- a/src/app_main.cpp
+++ b/src/app_main.cpp
@@ -71,14 +71,14 @@ extern "C" int app_main(void) {
std::make_unique(kMethodPoolSize, g_method_pool),
std::make_unique(kTempPoolSize, g_temp_pool));
- // TinyCNN takes one 16x16 RGB image in NCHW order (see model/model.py),
+ // TinyCNN takes one 16x16 RGB image in NHWC order (see model/model.py),
// filled here with a deterministic ramp. The tensor only wraps the buffer;
// no tensor extension (and its std::random_device) is needed for that.
- alignas(16) static float input_data[3 * 16 * 16];
+ alignas(16) static float input_data[16 * 16 * 3];
for (size_t i = 0; i < sizeof(input_data) / sizeof(input_data[0]); ++i) {
input_data[i] = static_cast(i % 32) / 32.0f - 0.5f;
}
- std::array sizes{1, 3, 16, 16};
+ std::array sizes{1, 16, 16, 3};
std::array dim_order{0, 1, 2, 3};
TensorImpl input_impl(ScalarType::Float, sizes.size(), sizes.data(), input_data, dim_order.data());
Tensor input(&input_impl);
From 649f2f4e0804e6f195bbc7651785f2044009d8eb Mon Sep 17 00:00:00 2001
From: Joachim Krech <8290187+jkrech@users.noreply.github.com>
Date: Tue, 6 Oct 2026 12:46:09 +0200
Subject: [PATCH 19/21] Run the FVP directly on Windows and Linux, require
CMSIS-Toolbox 2.15.0 (#27)
The target-set's model: is FVP_Corstone_SSE-320 again, so Run and Debug work on Windows and Linux with the launch.json the extension generates; the Docker shim and its launch configuration (now .vscode/launch.json.mac) are the macOS setup, described in the README. CMSIS-Toolbox 2.15.0 no longer passes -mfpu=fpv5-sp-d16 to Clang for the Cortex-M85, so the CLANG misc: workaround goes; vcpkg pins the toolbox at 2.15.0.
---
.gitignore | 11 ++++++-----
.vscode/fvp.sh | 13 +++++++------
.vscode/{launch.json => launch.json.mac} | 4 ++++
README.md | 22 +++++++++++-----------
cmsis-executorch.csolution.yml | 24 ++++++------------------
documentation/mlops-flow.md | 2 +-
vcpkg-configuration.json | 2 +-
7 files changed, 36 insertions(+), 42 deletions(-)
rename .vscode/{launch.json => launch.json.mac} (92%)
diff --git a/.gitignore b/.gitignore
index 0a0cfc37..b978bff9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -74,12 +74,13 @@ tmp/
fvp_stdout.log
# Editor config. The CMSIS Solution extension regenerates tasks.json,
-# settings.json and cmsis.json; only the FVP run/debug wiring is kept: the
-# model shim + its Dockerfile, and launch.json, whose Arm-FVP launch config is
-# "manual" (see the comment in it). The leading negation overrides a global
-# excludes file that ignores every .vscode directory.
+# settings.json, cmsis.json and launch.json; only the macOS FVP wiring is kept:
+# the model shim + its Dockerfile, and launch.json.mac, an Arm-FVP launch
+# config that waits for the model in Docker (see the comment in it). The
+# leading negation overrides a global excludes file that ignores every .vscode
+# directory.
!.vscode/
.vscode/*
!.vscode/fvp.sh
!.vscode/fvp.Dockerfile
-!.vscode/launch.json
+!.vscode/launch.json.mac
diff --git a/.vscode/fvp.sh b/.vscode/fvp.sh
index a10d35f8..918d6608 100755
--- a/.vscode/fvp.sh
+++ b/.vscode/fvp.sh
@@ -1,8 +1,9 @@
#!/usr/bin/env bash
-# Stand-in for the FVP_Corstone_SSE-320 executable, referenced from the
-# `debugger: model:` node of the SSE-320-U85 target-set. The CMSIS Solution
-# extension runs it both as the "CMSIS Run" task and as the gdbtarget debug
-# server, in the latter case with
+# Stand-in for the FVP_Corstone_SSE-320 executable on macOS, where the
+# `debugger: model:` node of the SSE-320-U85 target-set points at it (see the
+# README). The CMSIS Solution extension runs it both as the "CMSIS Run" task
+# and as the gdbtarget debug server (.vscode/launch.json.mac), in the latter
+# case with
#
# -D --plugin $AVH_FVP_PLUGINS/GDBServer.so -C GDBServer.port=3333 \
# -f board/Corstone-320/fvp_config.txt --simlimit 60 -a .hex
@@ -15,8 +16,8 @@
# * runs the model in Docker on macOS, where Arm publishes no FVP build, with
# the GDB port forwarded to the host so arm-none-eabi-gdb can reach it.
#
-# On Linux it just execs the real model. On Windows, where the extension cannot
-# run a bash script, point the csolution's `model:` at FVP_Corstone_SSE-320.exe.
+# On Linux it just execs the real model. Windows and Linux need no shim: the
+# csolution's `model:` names FVP_Corstone_SSE-320 directly.
set -euo pipefail
MODEL="${FVP_MODEL:-FVP_Corstone_SSE-320}"
diff --git a/.vscode/launch.json b/.vscode/launch.json.mac
similarity index 92%
rename from .vscode/launch.json
rename to .vscode/launch.json.mac
index 5aa8d67f..03b36379 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json.mac
@@ -2,6 +2,10 @@
"version": "0.2.0",
"configurations": [
{
+ // macOS only: copy this file to .vscode/launch.json after pointing
+ // the csolution's `debugger: model:` at .vscode/fvp.sh. On Windows
+ // and Linux the extension generates a working launch.json itself.
+ //
// Generated by the CMSIS Solution extension from its Arm-FVP adapter
// template, then switched to "manual" because two "target" fields
// differ from the generated ones and would be lost on regeneration:
diff --git a/README.md b/README.md
index 22bcc473..54bcdf6d 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@ This example shows how to deploy and run an
[ExecuTorch](https://github.com/pytorch/executorch) model on an Arm Ethos-U NPU.
The pack [`PyTorch::ExecuTorch`](https://www.keil.arm.com/packs/executorch-pytorch/)
provides the source code components to build the ExecuTorch runtime, required operators, and Ethos-U backend.
-The build process uses the [CMSIS-Toolbox 2.14.1](https://open-cmsis-pack.github.io/cmsis-toolbox/) or higher.
+The build process uses the [CMSIS-Toolbox 2.15.0](https://open-cmsis-pack.github.io/cmsis-toolbox/) or higher.
This example application targets the Arm Corstone-320 reference platform with
an Ethos-U85 NPU. It demonstrates the same overall workflow used for other
@@ -59,11 +59,12 @@ The example can be built and run entirely in Keil Studio for VS Code.
changing the model or the target.
5. Use the CMSIS action buttons to build the application, then select **Run** or
**Debug**. Keil Studio starts the Corstone-320 FVP automatically. On macOS,
- where Arm ships no FVP build, `.vscode/fvp.sh` runs the model in Docker:
- Docker Desktop must be running, and the first Run or Debug builds the
- container image (about 100 MB download). On Windows, set `model:` in the
- csolution's target-set back to `FVP_Corstone_SSE-320` (the shim is a bash
- script).
+ where Arm ships no FVP build, run the model in Docker instead: set `model:`
+ in the csolution's target-set to `${workspaceFolder}/.vscode/fvp.sh` and
+ copy `.vscode/launch.json.mac` to `.vscode/launch.json` (its debug
+ configuration waits for the model to come up in the container). Docker
+ Desktop must be running, and the first Run or Debug builds the container
+ image (about 100 MB download).
A successful run prints the Ethos-U configuration, output logits, and a pass
result:
@@ -152,14 +153,13 @@ out/cmsis-executorch/SSE-320-U85/Debug/cmsis-executorch.axf
#### 4. Run on the FVP
```bash
-.vscode/fvp.sh \
+FVP_Corstone_SSE-320 \
-f board/Corstone-320/fvp_config.txt --simlimit 60 \
-a out/cmsis-executorch/SSE-320-U85/Debug/cmsis-executorch.axf
```
-`.vscode/fvp.sh` is the model command the Run and Debug buttons use too; on
-Linux and Windows `FVP_Corstone_SSE-320` can be called directly with the same
-arguments.
+This is the model command the Run and Debug buttons use too. On macOS, call
+`.vscode/fvp.sh` with the same arguments; it runs the model in Docker.
## How model generation works
@@ -238,7 +238,7 @@ together. More information is available in
| `ai_layer/` | Generated: component selection and the embedded model data |
| `setup_venv.py` (`.sh` / `.bat`) | Creates the Python environment for the export |
| `.vscode.d/tasks.json` | The VS Code tasks (venv setup, Create AI layer) merged by the CMSIS Solution extension |
-| `.vscode/fvp.sh`, `.vscode/fvp.Dockerfile` | The FVP model command used by Run and Debug; runs the model in Docker on macOS |
+| `.vscode/fvp.sh`, `.vscode/fvp.Dockerfile`, `.vscode/launch.json.mac` | macOS only: runs the FVP in Docker, and the debug configuration that waits for it |
| `board/Corstone-320/` | Corstone-320 platform support and FVP configuration |
| `src/app_main.cpp` | Loads the model, runs inference, and prints the result; pool sizes overridable with `APP_METHOD_POOL_SIZE`, `APP_TEMP_POOL_SIZE`, `APP_POOL_SECTION` |
| `src/arm_embedded_module.*` | `EmbeddedModule`: ExecuTorch's `Module` class without the POSIX file loading (BSD-3-Clause, `src/LICENSE-ExecuTorch`) |
diff --git a/cmsis-executorch.csolution.yml b/cmsis-executorch.csolution.yml
index 8f034c95..89a55385 100644
--- a/cmsis-executorch.csolution.yml
+++ b/cmsis-executorch.csolution.yml
@@ -12,7 +12,7 @@
# create_ai_layer.py reads the NPU and Vela settings from it, exports the model
# from PyTorch in the project's venv and writes the AI layer.
solution:
- created-for: CMSIS-Toolbox@2.14.1
+ created-for: CMSIS-Toolbox@2.15.0
cdefault:
compiler: AC6
@@ -41,26 +41,14 @@ solution:
debugger:
name: Arm-FVP
# The model is its own GDB server (plugins/GDBServer.so, MDK FVP
- # models 11.32.23+). fvp.sh resolves that plugin when VS Code was
- # not started from a `vcpkg activate` shell, line-buffers the
- # model's stdout so the debug adapter sees the "Listening" banner,
- # and on macOS runs the model in Docker (Arm ships no macOS build).
- # On Windows point this straight at FVP_Corstone_SSE-320.exe.
- model: ${workspaceFolder}/.vscode/fvp.sh
+ # models 11.32.23+). On Windows and Linux the FVP from vcpkg runs
+ # as it is. On macOS, where Arm ships no FVP build, set this to
+ # ${workspaceFolder}/.vscode/fvp.sh, which runs the model in
+ # Docker, and copy .vscode/launch.json.mac to .vscode/launch.json.
+ model: FVP_Corstone_SSE-320
config-file: board/Corstone-320/fvp_config.txt
args: "--simlimit 60"
- misc:
- # CMSIS-Toolbox 2.14.1 passes -mfpu=fpv5-sp-d16 to Clang for the Cortex-M85,
- # a single-precision FPU on a core with a double-precision one and MVE;
- # LLVM crashes on some kernels with that combination. The later option
- # wins, so name the M85's FPU explicitly (newer toolboxes leave it to -mcpu).
- - for-compiler: CLANG
- C-CPP:
- - -mfpu=fp-armv8-fullfp16-d16
- ASM:
- - -mfpu=fp-armv8-fullfp16-d16
-
build-types:
- type: Debug
debug: on
diff --git a/documentation/mlops-flow.md b/documentation/mlops-flow.md
index a8cd8739..dd46e9a3 100644
--- a/documentation/mlops-flow.md
+++ b/documentation/mlops-flow.md
@@ -61,7 +61,7 @@ cbuild-mlops:
output:
- file: out/cmsis-executorch/SSE-320-U85/Debug/cmsis-executorch.axf
type: elf
- model: ${workspaceFolder}/.vscode/fvp.sh
+ model: FVP_Corstone_SSE-320
config-file: board/Corstone-320/fvp_config.txt
```
diff --git a/vcpkg-configuration.json b/vcpkg-configuration.json
index 68cb2129..e9340040 100644
--- a/vcpkg-configuration.json
+++ b/vcpkg-configuration.json
@@ -7,7 +7,7 @@
}
],
"requires": {
- "arm:tools/open-cmsis-pack/cmsis-toolbox": "2.14.1",
+ "arm:tools/open-cmsis-pack/cmsis-toolbox": "2.15.0",
"arm:compilers/arm/arm-none-eabi-gcc": "14.3.1",
"arm:compilers/arm/llvm-embedded": "22.1.0",
"arm:tools/kitware/cmake": "4.2.1",
From 2e4ddc209d8075c6b7964797d5479f1eee03bf8e Mon Sep 17 00:00:00 2001
From: Matthias Hertel
Date: Tue, 6 Oct 2026 12:52:40 +0200
Subject: [PATCH 20/21] Use the implicit simulator target and model parameters
in mlops: (#28)
From the preview branch, now that CMSIS-Toolbox 2.15.0 is released: the toolbox detects the simulator target from the Arm-FVP target-set, so simulator: goes, and passes extra keys under model: through to *.cbuild-mlops.yml. The NHWC input shape and the number of calibration samples move there; model_params() in create_ai_layer.py hands them to model/model.py. The exported program is unchanged.
---
README.md | 15 ++++++++++-----
cmsis-executorch.csolution.yml | 12 ++++++++----
create_ai_layer.py | 26 ++++++++++++++++++++++----
documentation/mlops-flow.md | 12 +++++++++---
4 files changed, 49 insertions(+), 16 deletions(-)
diff --git a/README.md b/README.md
index 54bcdf6d..64c8fb69 100644
--- a/README.md
+++ b/README.md
@@ -176,13 +176,18 @@ mlops:
model:
clayer: $AI-Layer$
name: TinyCNN
+ input-shape: 1x16x16x3
+ calibration-samples: 2
```
`cbuild setup --active SSE-320-U85` resolves it into
`cmsis-executorch.cbuild-mlops.yml`, which contains the processor, NPU
-and Vela options. `create_ai_layer.py` reads those options and passes all of
-them to ExecuTorch's `EthosUCompileSpec`, so the target configuration is never
-duplicated in Python. The script then writes:
+and Vela options, and the FVP target-set as the `simulator:` to test on.
+`create_ai_layer.py` reads those options and passes all of them to ExecuTorch's
+`EthosUCompileSpec`, so the target configuration is never duplicated in
+Python. The two extra keys under `model:` are parameters of the model itself;
+the toolbox passes them through and the script hands them to `model/model.py`.
+The script then writes:
- `ai_layer/ai_layer.clayer.yml`: the CMSIS components required by the model.
- `ai_layer/model_pte.c` and `model_pte.h`: the ExecuTorch program embedded as a C array.
@@ -202,8 +207,8 @@ changes the operator set needs nothing more than re-running steps 2 and 3.
## Adapting the example
To use a different model, replace or modify `model/model.py` and update the
-model name or input handling as required: `INPUT_SHAPE` and
-`get_model(input_shape)` define the input, and
+model name or input handling as required: `get_model(input_shape)` builds the
+model for the `input-shape` of the `mlops:` node, and
`get_calibration_inputs(input_shape, calibration_samples)` returns the samples
the quantizer is calibrated with; give it representative data for a trained
model. The runner in `src/app_main.cpp` builds its input tensor with the same
diff --git a/cmsis-executorch.csolution.yml b/cmsis-executorch.csolution.yml
index 89a55385..ba2d260b 100644
--- a/cmsis-executorch.csolution.yml
+++ b/cmsis-executorch.csolution.yml
@@ -59,17 +59,21 @@ solution:
# MLOps information: exported to cmsis-executorch.cbuild-mlops.yml by
# `cbuild setup ... --active SSE-320-U85`. Retarget the NPU here and
- # create_ai_layer.py picks it up on its next run.
+ # create_ai_layer.py picks it up on its next run. The simulator target comes
+ # from the Arm-FVP target-set, and the extra keys under model: are passed
+ # through to create_ai_layer.py.
# https://open-cmsis-pack.github.io/cmsis-toolbox/build-overview/#mlops-information
mlops:
description: TinyCNN int8 image classifier for Ethos-U85
npu:
- type: Ethos-U85
+ type: Ethos-U85 # explicit: the SSE-320 device pack declares no NPU
vela:
system: Ethos_U85_SYS_DRAM_Mid # system-config from the Vela config
memory: Shared_Sram # memory-mode from the Vela config
model:
clayer: $AI-Layer$
name: TinyCNN
- simulator:
- target: SSE-320-U85
+ # Model parameters for create_ai_layer.py: the NHWC input shape and the
+ # number of samples the quantizer is calibrated with.
+ input-shape: 1x16x16x3
+ calibration-samples: 2
diff --git a/create_ai_layer.py b/create_ai_layer.py
index aeab3392..e55cce24 100644
--- a/create_ai_layer.py
+++ b/create_ai_layer.py
@@ -15,7 +15,9 @@
of the csolution. This script reads the NPU and Vela settings from it, exports
the PyTorch model in model/model.py for that NPU (quantize, delegate to
Ethos-U, compile with Vela) and writes the complete AI layer into the
-directory of the clayer named under `model.clayer`:
+directory of the clayer named under `model.clayer`. The extra keys of the
+`model:` node (input-shape, calibration-samples) are model parameters that
+CMSIS-Toolbox passes through to this script:
ai_layer.clayer.yml runtime, kernel utilities and registration, Ethos-U
backend and the operator components the exported
@@ -164,7 +166,22 @@ def compile_spec(mlops: dict, mlops_dir: Path):
return EthosUCompileSpec(**kwargs)
-def export_model(spec) -> bytes:
+def model_params(mlops: dict) -> dict:
+ """The extra keys of the model: node, as keyword arguments for model/model.py."""
+ params = {}
+ for key, value in mlops["model"].items():
+ if key == "input-shape":
+ params["input_shape"] = tuple(int(d) for d in re.split(r"[x,]", str(value)))
+ elif key == "calibration-samples":
+ params["calibration_samples"] = int(value)
+ elif key not in ("clayer", "name"):
+ print(f"[ai_layer] warning: ignoring unknown model key {key!r}", file=sys.stderr)
+ if params:
+ print(f"[ai_layer] model parameters: {params}")
+ return params
+
+
+def export_model(spec, params: dict) -> bytes:
"""Quantize model/model.py, delegate it to the Ethos-U and return the .pte."""
import torch
from executorch.backends.arm.ethosu import EthosUPartitioner
@@ -175,7 +192,8 @@ def export_model(spec) -> bytes:
sys.path.insert(0, str(HERE / "model"))
from model import get_calibration_inputs, get_model
- model, samples = get_model(), get_calibration_inputs()
+ model = get_model(**{k: v for k, v in params.items() if k == "input_shape"})
+ samples = get_calibration_inputs(**params)
example = (samples[0],)
graph = torch.export.export(model, example).module()
@@ -293,7 +311,7 @@ def main() -> None:
layer_file = mlops_file.parent / mlops["model"]["clayer"]
layer_dir = layer_file.parent
- pte = export_model(compile_spec(mlops, mlops_file.parent))
+ pte = export_model(compile_spec(mlops, mlops_file.parent), model_params(mlops))
version = executorch_version(mlops_file)
runtime, operators = components(pte, executorch_pack(version))
diff --git a/documentation/mlops-flow.md b/documentation/mlops-flow.md
index dd46e9a3..ed93f9bb 100644
--- a/documentation/mlops-flow.md
+++ b/documentation/mlops-flow.md
@@ -34,17 +34,21 @@ solution:
model:
clayer: $AI-Layer$
name: TinyCNN
- simulator:
- target: SSE-320-U85 # [@] of the FVP
+ input-shape: 1x16x16x3 # passed through to create_ai_layer.py
+ calibration-samples: 2
```
+CMSIS-Toolbox detects the `simulator:` target (and, for a board, the
+`hardware:` target) from the target-sets, and passes the extra keys under
+`model:` through unchanged.
+
`cbuild setup cmsis-executorch.csolution.yml --active SSE-320-U85`
resolves it for the active target and writes
`cmsis-executorch.cbuild-mlops.yml`:
```yaml
cbuild-mlops:
- generated-by: csolution version 2.14.1+p38-gf512b381
+ generated-by: csolution version 2.15.1+p3-gf46d68bf
description: TinyCNN int8 image classifier for Ethos-U85
processor:
type: Cortex-M85
@@ -55,6 +59,8 @@ cbuild-mlops:
model:
clayer: ai_layer/ai_layer.clayer.yml
name: TinyCNN
+ input-shape: 1x16x16x3
+ calibration-samples: 2
simulator:
active: SSE-320-U85
cbuild-run: out/cmsis-executorch+SSE-320-U85.cbuild-run.yml
From 2a25f70c79213f43638bd665e5192c994100fa1a Mon Sep 17 00:00:00 2001
From: Joachim Krech <8290187+jkrech@users.noreply.github.com>
Date: Tue, 6 Oct 2026 14:43:22 +0200
Subject: [PATCH 21/21] Update README.md
MD schema compliance fix.
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 64c8fb69..1e35f0fa 100644
--- a/README.md
+++ b/README.md
@@ -235,7 +235,7 @@ together. More information is available in
## Project layout
| Path | Purpose |
-|------|---------|
+| ------ | --------- |
| `cmsis-executorch.csolution.yml` | Solution, target, and MLOps configuration |
| `cmsis-executorch.cproject.yml` | Application project: sources plus the Board and AI layers |
| `model/model.py` | Example TinyCNN model |