diff --git a/backends/cortex_m/CMakeLists.txt b/backends/cortex_m/CMakeLists.txt index af970f61e45..8c8255b7b1b 100644 --- a/backends/cortex_m/CMakeLists.txt +++ b/backends/cortex_m/CMakeLists.txt @@ -23,7 +23,7 @@ include(FetchContent) # CMSIS-NN configuration with dynamic path detection set(CMSIS_NN_VERSION - "098d54a61e3e04e2b60e9010e871eef036ac5ae3" + "d933672e7ca97eec70ef43230baee7b20c2a28ae" CACHE STRING "CMSIS-NN version to download" ) set(CMSIS_NN_LOCAL_PATH diff --git a/backends/cortex_m/passes/__init__.py b/backends/cortex_m/passes/__init__.py index 19665f37083..b1b67add413 100644 --- a/backends/cortex_m/passes/__init__.py +++ b/backends/cortex_m/passes/__init__.py @@ -3,6 +3,36 @@ # This source code is licensed under the BSD-style license found in the # LICENSE file in the root directory of this source tree. +from importlib.util import find_spec + + +def _missing_dependencies_error(missing: str) -> ModuleNotFoundError: + return ModuleNotFoundError( + "Cortex-M backend dependencies are not installed " + f"(missing: {missing}). Install ExecuTorch with " + "`pip install executorch[cortex_m]`, or if building from source run " + "`examples/arm/setup.sh --i-agree-to-the-contained-eula`." + ) + + +def _ensure_cortex_m_dependencies() -> None: + required_modules = { + "cmsis_nn": "cmsis_nn", + } + missing_packages = [] + for module_name, package_name in required_modules.items(): + try: + if find_spec(module_name) is None: + missing_packages.append(package_name) + except (ImportError, ValueError): + missing_packages.append(package_name) + + if missing_packages: + raise _missing_dependencies_error(", ".join(missing_packages)) + + +_ensure_cortex_m_dependencies() + from .activation_fusion_pass import ActivationFusionPass # noqa from .clamp_hardswish_pass import ClampHardswishPass # noqa from .convert_to_cortex_m_pass import ConvertToCortexMPass # noqa diff --git a/backends/cortex_m/requirements-cortex-m.txt b/backends/cortex_m/requirements-cortex-m.txt new file mode 100644 index 00000000000..bc3d28c7b23 --- /dev/null +++ b/backends/cortex_m/requirements-cortex-m.txt @@ -0,0 +1,8 @@ +# Copyright 2026 Arm Limited and/or its affiliates. +# +# This source code is licensed under the BSD-style license found in the +# LICENSE file in the root directory of this source tree. + +# These dependencies need to match pyproject.toml + +cmsis_nn @ git+https://github.com/ARM-software/CMSIS-NN.git@d933672e7ca97eec70ef43230baee7b20c2a28ae diff --git a/backends/cortex_m/test/misc/test_cmsis_pybind.py b/backends/cortex_m/test/misc/test_cmsis_pybind.py new file mode 100644 index 00000000000..f85a4bacece --- /dev/null +++ b/backends/cortex_m/test/misc/test_cmsis_pybind.py @@ -0,0 +1,33 @@ +# Copyright 2026 Arm Limited and/or its 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. + +import importlib + +import pytest + + +def _import_cmsis_nn(): + try: + return importlib.import_module("cmsis_nn") + except Exception as exc: + pytest.fail(f"Failed to resolve cmsis_nn: {exc}") + + +def test_cmsis_nn_convolve_wrapper_buffer_size() -> None: + cmsis_nn = _import_cmsis_nn() + + buf_size = cmsis_nn.convolve_wrapper_buffer_size( + cmsis_nn.Backend.MVE, + cmsis_nn.DataType.A8W8, + input_nhwc=[1, 8, 8, 16], + filter_nhwc=[8, 3, 3, 16], + output_nhwc=[1, 6, 6, 8], + padding_hw=[0, 0], + stride_hw=[1, 1], + dilation_hw=[1, 1], + ) + + assert buf_size == 576 diff --git a/examples/arm/setup.sh b/examples/arm/setup.sh index 323815b59a2..51a651d9bc9 100755 --- a/examples/arm/setup.sh +++ b/examples/arm/setup.sh @@ -22,6 +22,7 @@ enable_baremetal_toolchain=1 target_toolchain="" enable_fvps=1 enable_vela=1 +enable_cortex_m=1 enable_model_converter=0 # model-converter tool for VGF output enable_vgf_lib=0 # vgf reader - runtime backend dependency enable_emulation_layer=0 # Vulkan layer driver - emulates Vulkan ML extensions @@ -48,6 +49,7 @@ OPTION_LIST=( "--target-toolchain Select toolchain: gnu (default), zephyr, or linux-musl" "--enable-fvps Enable FVP setup" "--enable-vela Enable VELA setup" + "--disable-cortex-m-deps Do not setup what is needed for Cortex-M" "--enable-model-converter Enable MLSDK model converter setup" "--enable-vgf-lib Enable MLSDK vgf library setup" "--enable-emulation-layer Enable MLSDK Vulkan emulation layer" @@ -123,6 +125,10 @@ function check_options() { enable_vela=1 shift ;; + --disable-cortex-m-deps) + enable_cortex_m=0 + shift + ;; --enable-model-converter) enable_model_converter=1 shift @@ -211,6 +217,11 @@ function setup_ethos_u_tools() { CMAKE_POLICY_VERSION_MINIMUM=3.5 BUILD_PYBIND=1 pip install --no-dependencies -r $et_dir/backends/arm/requirements-arm-ethos-u.txt } +function setup_cortex_m_tools() { + log_step "cortex-m-tools" "Installing Cortex-M Python tooling" + pip install --no-dependencies -r $et_dir/backends/cortex_m/requirements-cortex-m.txt +} + function setup_mlsdk_dependencies() { log_step "mlsdk" "Installing MLSDK dependencies" pip install -r $et_dir/backends/arm/requirements-arm-vgf.txt @@ -299,6 +310,7 @@ if [[ $is_script_sourced -eq 0 ]]; then "root=${root_dir}, target-toolchain=${target_toolchain:-}" log_step "options" \ "ethos-u: fvps=${enable_fvps}, toolchain=${enable_baremetal_toolchain}, vela=${enable_vela} | " \ + "cortex-m: deps=${enable_cortex_m} | " \ "mlsdk: model-converter=${enable_model_converter}, vgf-lib=${enable_vgf_lib}, " \ "emu-layer=${enable_emulation_layer}, vulkan-sdk=${enable_vulkan_sdk}" @@ -349,6 +361,11 @@ if [[ $is_script_sourced -eq 0 ]]; then setup_ethos_u_tools fi + if [[ "${enable_cortex_m}" -eq 1 ]]; then + log_step "deps" "Installing Cortex-M CMSIS-NN tooling" + setup_cortex_m_tools + fi + log_step "main" "Setup complete" exit 0 fi diff --git a/install_executorch.py b/install_executorch.py index d305a06bd28..376a40aa308 100644 --- a/install_executorch.py +++ b/install_executorch.py @@ -195,7 +195,7 @@ def _parse_args() -> argparse.Namespace: help="Only installs necessary dependencies for core executorch and skips " " packages necessary for running example scripts.", ) - allowed_optional_dependencies = ["ethos_u", "vgf", "openvino"] + allowed_optional_dependencies = ["cortex_m", "ethos_u", "vgf", "openvino"] parser.add_argument( "--optional-dependency", action="append", @@ -230,7 +230,20 @@ def main(args): # Step 1: Install core dependencies first install_requirements(install_pinned_version) - # Step 2: Install core package + # Step 2: Install build dependencies for optional dependencies + # They need to be installed before optional dependencies due to --no-build-isolation + optional_build_dependencies: list[str] = [] + for optional_dep in args.optional_dependency: + match optional_dep: + case "cortex_m": + optional_build_dependencies.extend( + ["pybind11>=2.10", "scikit-build-core>=0.7"] + ) + if len(optional_build_dependencies) > 0: + cmd = [sys.executable, "-m", "pip", "install", *optional_build_dependencies] + subprocess.run(cmd, check=True) + + # Step 3: Install core package package_spec = "." if args.optional_dependency: extras = ",".join(dict.fromkeys(args.optional_dependency)) @@ -251,7 +264,7 @@ def main(args): ) subprocess.run(cmd, check=True) - # Step 3: Extra (optional) packages that is only useful for running examples. + # Step 4: Extra (optional) packages that is only useful for running examples. if not args.minimal: install_optional_example_requirements(install_pinned_version) diff --git a/pyproject.toml b/pyproject.toml index 8e1b2e874bb..bb3beda32b1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -77,6 +77,10 @@ dependencies=[ ] [project.optional-dependencies] +cortex_m = [ + # Keep this in sync with AoT deps from backends/cortex_m/requirements-cortex-m.txt + "cmsis_nn @ git+https://github.com/ARM-software/CMSIS-NN.git@d933672e7ca97eec70ef43230baee7b20c2a28ae", +] vgf = [ # AoT vgf dependencies # Keep this in sync with AoT deps from backends/arm/requirements-arm-vgf.txt and diff --git a/requirements-dev.txt b/requirements-dev.txt index 7704360275d..ef41c06ba5c 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -14,4 +14,4 @@ lintrunner-adapters==0.13.0 pytest<9.0 pytest-xdist pytest-rerunfailures==15.1 -pytest-json-report +pytest-json-report \ No newline at end of file