From 0e8a006bd03aef88f41ba2f218c316857bacd55c 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] 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