From 14da94057fe72b51729b46bff156f9cab6637b2c Mon Sep 17 00:00:00 2001 From: Erik Lundell Date: Mon, 13 Apr 2026 15:06:07 +0200 Subject: [PATCH 1/2] Build and install cmsis_nn pybinds with executorch The package is not available on pypi, and is needed aot. Therefore, add it as a BuiltExtension in setup.py, and wire the cmake up to build it by default with the pybind.cmake preset, that is used when building the executorch wheel. A pip installation of cmsis_nn can still be used in backends.cortex_m.library.cmsis_nn as a backup. Tested in all wheel smoke tests. Additionally: - Move cmsis_nn build into the cortex_m build folder - Remove git+pip cmsis_nn install path, deprecating the --disable-cortex-m-deps flag in setup.sh - Remove pyproject.toml additional dependencies [cortex_m], since cmsis_nn is now included with executorch. - CMSIS-NN requires pybind11 as a python dependency. From a local test, the new dependency adds - 5 sec to a configure time of ~15s - 3 sec to a build time of ~10 min Signed-off-by: Erik Lundell Change-Id: Idb79e37dd1cf28f351062e0181140a50a8e7cae4 --- .ci/scripts/wheel/test_base.py | 18 +++ .ci/scripts/wheel/test_linux.py | 3 + .ci/scripts/wheel/test_macos.py | 3 + .ci/scripts/wheel/test_windows.py | 5 + CMakeLists.txt | 2 +- backends/cortex_m/CMakeLists.txt | 133 ++++++++++-------- backends/cortex_m/library/cmsis_nn.py | 44 +++--- backends/cortex_m/requirements-cortex-m.txt | 8 -- .../cortex_m/test/misc/test_cmsis_pybind.py | 106 +++++++++++++- examples/arm/setup.sh | 12 -- install_executorch.py | 19 +-- pyproject.toml | 4 - setup.py | 14 +- tools/cmake/preset/default.cmake | 4 + tools/cmake/preset/pybind.cmake | 1 + 15 files changed, 252 insertions(+), 124 deletions(-) delete mode 100644 backends/cortex_m/requirements-cortex-m.txt diff --git a/.ci/scripts/wheel/test_base.py b/.ci/scripts/wheel/test_base.py index 278e46fe75a..2b0d3c0dcb4 100644 --- a/.ci/scripts/wheel/test_base.py +++ b/.ci/scripts/wheel/test_base.py @@ -1,5 +1,6 @@ # Copyright (c) Meta Platforms, Inc. and affiliates. # All rights reserved. +# 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. @@ -40,6 +41,23 @@ class ModelTest: backend: Backend +def test_cmsis_nn_install(): + import executorch.backends.cortex_m.library.cmsis_nn as 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 + + def run_tests(model_tests: List[ModelTest]) -> None: # Test that we can import the portable_lib module - verifies RPATH is correct print("Testing portable_lib import...") diff --git a/.ci/scripts/wheel/test_linux.py b/.ci/scripts/wheel/test_linux.py index 7545b4c6f20..812eec89215 100644 --- a/.ci/scripts/wheel/test_linux.py +++ b/.ci/scripts/wheel/test_linux.py @@ -1,6 +1,7 @@ #!/usr/bin/env python # Copyright (c) Meta Platforms, Inc. and affiliates. # All rights reserved. +# 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. @@ -38,6 +39,8 @@ else: print("⚠ VulkanBackend not registered (expected for the default wheel)") + test_base.test_cmsis_nn_install() + test_base.run_tests( model_tests=[ test_base.ModelTest( diff --git a/.ci/scripts/wheel/test_macos.py b/.ci/scripts/wheel/test_macos.py index a2660016098..c139f828d63 100644 --- a/.ci/scripts/wheel/test_macos.py +++ b/.ci/scripts/wheel/test_macos.py @@ -1,6 +1,7 @@ #!/usr/bin/env python # Copyright (c) Meta Platforms, Inc. and affiliates. # All rights reserved. +# 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. @@ -9,6 +10,8 @@ from examples.models import Backend, Model if __name__ == "__main__": + test_base.test_cmsis_nn_install() + test_base.run_tests( model_tests=[ test_base.ModelTest( diff --git a/.ci/scripts/wheel/test_windows.py b/.ci/scripts/wheel/test_windows.py index ba141d4498c..bdf4e601d50 100644 --- a/.ci/scripts/wheel/test_windows.py +++ b/.ci/scripts/wheel/test_windows.py @@ -1,6 +1,7 @@ #!/usr/bin/env python # Copyright (c) Meta Platforms, Inc. and affiliates. # All rights reserved. +# 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. @@ -8,6 +9,8 @@ import platform from typing import List +import test_base + import torch from executorch.backends.xnnpack.partition.xnnpack_partitioner import XnnpackPartitioner from executorch.examples.models import Backend, Model, MODEL_NAME_TO_MODEL @@ -74,6 +77,8 @@ def run_tests(model_tests: List[ModelTest]) -> None: else: print("⚠ VulkanBackend not registered (expected for the default wheel)") + test_base.test_cmsis_nn_install() + run_tests( model_tests=[ ModelTest( diff --git a/CMakeLists.txt b/CMakeLists.txt index abd032e3e30..a4b56a55dc3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -751,7 +751,7 @@ if(EXECUTORCH_BUILD_XNNPACK) list(APPEND _executorch_backends xnnpack_backend) endif() -if(EXECUTORCH_BUILD_CORTEX_M) +if(EXECUTORCH_BUILD_CORTEX_M OR EXECUTORCH_BUILD_CMSIS_NN_PYBINDS) add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/backends/cortex_m) endif() diff --git a/backends/cortex_m/CMakeLists.txt b/backends/cortex_m/CMakeLists.txt index f88a6306fed..85e52b5782a 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 - "d933672e7ca97eec70ef43230baee7b20c2a28ae" + "dbf45dbfcc515421dd6099037d3e2637b90748c8" CACHE STRING "CMSIS-NN version to download" ) set(CMSIS_NN_LOCAL_PATH @@ -35,12 +35,19 @@ option(CORTEX_M_ENABLE_RUNTIME_CHECKS OFF ) +set(CMSISNN_BUILD_PYBIND + ${EXECUTORCH_BUILD_CMSIS_NN_PYBINDS} + CACHE BOOL "Build CMSIS-NN Python bindings" FORCE +) + # Try to find existing / local CMSIS-NN installation. This is useful for # debugging and testing with local changes. This is not common, as the CMSIS-NN # library is downloaded via FetchContent in the default/regular case. if(CMSIS_NN_LOCAL_PATH AND EXISTS "${CMSIS_NN_LOCAL_PATH}") message(STATUS "Using CMSIS-NN from specified path: ${CMSIS_NN_LOCAL_PATH}") - add_subdirectory(${CMSIS_NN_LOCAL_PATH} _deps/cmsis_nn-build) + add_subdirectory( + ${CMSIS_NN_LOCAL_PATH} ${CMAKE_CURRENT_BINARY_DIR}/cmsis_nn-build + ) else() # Use FetchContent with automatic fallback message(STATUS "Using CMSIS-NN via FetchContent") @@ -49,6 +56,9 @@ else() cmsis_nn GIT_REPOSITORY https://github.com/ARM-software/CMSIS-NN.git GIT_TAG ${CMSIS_NN_VERSION} + SOURCE_DIR ${CMAKE_CURRENT_BINARY_DIR}/cmsis_nn-src BINARY_DIR + ${CMAKE_CURRENT_BINARY_DIR}/cmsis_nn-build SUBBUILD_DIR + ${CMAKE_CURRENT_BINARY_DIR}/cmsis_nn-subbuild ) FetchContent_MakeAvailable(cmsis_nn) @@ -74,65 +84,72 @@ if(TARGET cmsis-nn) endif() endif() -# Cortex-M ops kernel sources -set(_cortex_m_kernels__srcs - ${CMAKE_CURRENT_SOURCE_DIR}/ops/op_dequantize_per_tensor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/ops/op_maximum.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/ops/op_minimum.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/ops/op_pad.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/ops/op_quantize_per_tensor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/ops/op_quantized_activation.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/ops/op_quantized_add.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/ops/op_quantized_avg_pool2d.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/ops/op_quantized_batch_matmul.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/ops/op_quantized_conv2d.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/ops/op_quantized_depthwise_conv2d.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/ops/op_quantized_linear.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/ops/op_quantized_max_pool2d.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/ops/op_quantized_mul.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/ops/op_quantized_transpose_conv2d.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/ops/op_softmax.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/ops/op_transpose.cpp -) +if(EXECUTORCH_BUILD_CMSIS_NN_PYBINDS) + if(NOT TARGET cmsis_nn) + message( + FATAL_ERROR + "EXECUTORCH_BUILD_CMSIS_NN_PYBINDS is ON, but CMSIS-NN did not define the cmsis_nn pybind target." + ) + endif() +endif() +if(EXECUTORCH_BUILD_CORTEX_M) + # Cortex-M ops kernel sources + set(_cortex_m_kernels__srcs + ${CMAKE_CURRENT_SOURCE_DIR}/ops/op_dequantize_per_tensor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/ops/op_maximum.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/ops/op_minimum.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/ops/op_pad.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/ops/op_quantize_per_tensor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/ops/op_quantized_activation.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/ops/op_quantized_add.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/ops/op_quantized_avg_pool2d.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/ops/op_quantized_batch_matmul.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/ops/op_quantized_conv2d.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/ops/op_quantized_depthwise_conv2d.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/ops/op_quantized_linear.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/ops/op_quantized_max_pool2d.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/ops/op_quantized_mul.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/ops/op_quantized_transpose_conv2d.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/ops/op_softmax.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/ops/op_transpose.cpp + ) -# Generate C++ bindings to register kernels into Executorch -set(_yaml_file ${CMAKE_CURRENT_LIST_DIR}/ops/operators.yaml) -gen_selected_ops(LIB_NAME "cortex_m_ops_lib" OPS_SCHEMA_YAML "${_yaml_file}") -generate_bindings_for_kernels( - LIB_NAME "cortex_m_ops_lib" CUSTOM_OPS_YAML "${_yaml_file}" -) + # Generate C++ bindings to register kernels into Executorch + set(_yaml_file ${CMAKE_CURRENT_LIST_DIR}/ops/operators.yaml) + gen_selected_ops(LIB_NAME "cortex_m_ops_lib" OPS_SCHEMA_YAML "${_yaml_file}") + generate_bindings_for_kernels( + LIB_NAME "cortex_m_ops_lib" CUSTOM_OPS_YAML "${_yaml_file}" + ) -# Build library for cortex_m_kernels -add_library(cortex_m_kernels ${_cortex_m_kernels__srcs}) + # Build library for cortex_m_kernels + add_library(cortex_m_kernels ${_cortex_m_kernels__srcs}) -# Use PRIVATE for implementation dependencies to avoid INTERFACE pollution -target_link_libraries( - cortex_m_kernels - PRIVATE cmsis-nn - PRIVATE executorch - PRIVATE kernels_util_all_deps -) -target_compile_definitions( - cortex_m_kernels - PRIVATE - $<$:CORTEX_M_ENABLE_RUNTIME_CHECKS> -) + target_link_libraries( + cortex_m_kernels + PRIVATE cmsis-nn + PRIVATE executorch + PRIVATE kernels_util_all_deps + ) + target_compile_definitions( + cortex_m_kernels + PRIVATE + $<$:CORTEX_M_ENABLE_RUNTIME_CHECKS> + ) -# Include directories for cortex_m_kernels -target_include_directories( - cortex_m_kernels PRIVATE ${EXECUTORCH_ROOT}/.. - ${EXECUTORCH_ROOT}/runtime/core/portable_type/c10 -) + target_include_directories( + cortex_m_kernels PRIVATE ${EXECUTORCH_ROOT}/.. + ${EXECUTORCH_ROOT}/runtime/core/portable_type/c10 + ) -# cortex_m_ops_lib: Register Cortex-M ops kernels into Executorch runtime -gen_operators_lib( - LIB_NAME "cortex_m_ops_lib" KERNEL_LIBS cortex_m_kernels DEPS executorch -) + gen_operators_lib( + LIB_NAME "cortex_m_ops_lib" KERNEL_LIBS cortex_m_kernels DEPS executorch + ) -install( - TARGETS cortex_m_kernels cortex_m_ops_lib cmsis-nn - EXPORT ExecuTorchTargets - DESTINATION ${CMAKE_INSTALL_LIBDIR} - PUBLIC_HEADER - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/executorch/backends/cortex_m/ops/ -) + install( + TARGETS cortex_m_kernels cortex_m_ops_lib cmsis-nn + EXPORT ExecuTorchTargets + DESTINATION ${CMAKE_INSTALL_LIBDIR} + PUBLIC_HEADER + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/executorch/backends/cortex_m/ops/ + ) +endif() diff --git a/backends/cortex_m/library/cmsis_nn.py b/backends/cortex_m/library/cmsis_nn.py index 791ffdf754b..89f0e523484 100644 --- a/backends/cortex_m/library/cmsis_nn.py +++ b/backends/cortex_m/library/cmsis_nn.py @@ -6,11 +6,15 @@ from __future__ import annotations +import importlib from types import ModuleType from typing import Any, cast, ClassVar, Sequence, TYPE_CHECKING _cmsis_nn: ModuleType | None = None -_cmsis_nn_import_error: ModuleNotFoundError | None = None +_cmsis_nn_module_candidates = ( + "executorch.backends.cortex_m.library._cmsis_nn.cmsis_nn", + "cmsis_nn", +) class _EnumValue: @@ -83,30 +87,28 @@ class DataType: if not TYPE_CHECKING: - try: - import cmsis_nn as _real_cmsis_nn # type: ignore[import-not-found, import-untyped] - except ModuleNotFoundError as exc: - if exc.name != "cmsis_nn": - raise - _cmsis_nn_import_error = exc - else: - _cmsis_nn = _real_cmsis_nn - Backend = _real_cmsis_nn.Backend - CortexM = _real_cmsis_nn.CortexM - DataType = _real_cmsis_nn.DataType - - -def _missing_dependencies_error() -> ModuleNotFoundError: - return ModuleNotFoundError( - "Cortex-M backend dependencies are not installed. " - "Install by running `examples/arm/setup.sh --i-agree-to-the-contained-eula`, " - "or pip install from the CMSIS-NN repo." - ) + # First try to load cmsis_nn from executorch build, then external. + # Load in such a way that we crash only if a cmsis_nn function is required. + for module_name in _cmsis_nn_module_candidates: + try: + _real_cmsis_nn = importlib.import_module(module_name) + except ModuleNotFoundError as exc: + if exc.name not in module_name: + raise exc + else: + _cmsis_nn = _real_cmsis_nn + Backend = _real_cmsis_nn.Backend + CortexM = _real_cmsis_nn.CortexM + DataType = _real_cmsis_nn.DataType + break def _require_cmsis_nn() -> ModuleType: if _cmsis_nn is None: - raise _missing_dependencies_error() from _cmsis_nn_import_error + raise ModuleNotFoundError( + f"Cortex-M backend dependencies are not installed (tried {','.join(_cmsis_nn_module_candidates)}). " + "Build using install_executorch.sh, or pip install from the CMSIS-NN repo." + ) return _cmsis_nn diff --git a/backends/cortex_m/requirements-cortex-m.txt b/backends/cortex_m/requirements-cortex-m.txt deleted file mode 100644 index bc3d28c7b23..00000000000 --- a/backends/cortex_m/requirements-cortex-m.txt +++ /dev/null @@ -1,8 +0,0 @@ -# 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 index 08a1d973234..353e961a503 100644 --- a/backends/cortex_m/test/misc/test_cmsis_pybind.py +++ b/backends/cortex_m/test/misc/test_cmsis_pybind.py @@ -4,19 +4,77 @@ # LICENSE file in the root directory of this source tree. import importlib +from types import ModuleType, SimpleNamespace import pytest +_WRAPPER_MODULE = "executorch.backends.cortex_m.library.cmsis_nn" +_BUNDLED_MODULE = "executorch.backends.cortex_m.library._cmsis_nn.cmsis_nn" +_BUNDLED_PACKAGE = "executorch.backends.cortex_m.library._cmsis_nn" +_EXTERNAL_MODULE = "cmsis_nn" -def _import_cmsis_nn(): + +class _ExternalCmsisNNModule(ModuleType): + Backend: SimpleNamespace + CortexM: SimpleNamespace + DataType: SimpleNamespace + + def __init__(self) -> None: + super().__init__(_EXTERNAL_MODULE) + self.Backend = SimpleNamespace(MVE="mve") + self.CortexM = SimpleNamespace(M55="m55") + self.DataType = SimpleNamespace(A8W8="a8w8") + + def resolve_backend(self, cpu: object) -> tuple[str, object]: + return ("external", cpu) + + +def _import_cmsis_nn_wrapper(): try: - return importlib.import_module("executorch.backends.cortex_m.library.cmsis_nn") + return importlib.import_module(_WRAPPER_MODULE) except Exception as exc: pytest.fail(f"Failed to resolve cmsis_nn: {exc}") +@pytest.fixture +def modify_available_cmsis_nn_modules(monkeypatch): + """Returns a cmsis_nn wrapper with specified backing cmsis_nn packages. + + missing_name: + """ + module = _import_cmsis_nn_wrapper() + original_import_module = importlib.import_module + + def _reload(*, available_modules=()): + def fake_import_module(name, package=None): + if name == _WRAPPER_MODULE: + return original_import_module(name, package) + if name == _EXTERNAL_MODULE: + if name in available_modules: + return _ExternalCmsisNNModule() + raise ModuleNotFoundError(name=name) + if name == _BUNDLED_MODULE: + if name in available_modules: + return original_import_module(name, package) + missing_name = ( + _BUNDLED_MODULE + if _BUNDLED_PACKAGE in available_modules + else _BUNDLED_PACKAGE + ) + raise ModuleNotFoundError(name=missing_name) + return original_import_module(name, package) + + monkeypatch.setattr(importlib, "import_module", fake_import_module) + return importlib.reload(module) + + yield _reload + + monkeypatch.undo() + importlib.reload(module) + + def test_cmsis_nn_convolve_wrapper_buffer_size() -> None: - cmsis_nn = _import_cmsis_nn() + cmsis_nn = _import_cmsis_nn_wrapper() buf_size = cmsis_nn.convolve_wrapper_buffer_size( cmsis_nn.Backend.MVE, @@ -30,3 +88,45 @@ def test_cmsis_nn_convolve_wrapper_buffer_size() -> None: ) assert buf_size == 576 + + +def test_fallback_with_missing_module( + modify_available_cmsis_nn_modules, +) -> None: + cmsis_nn = modify_available_cmsis_nn_modules( + available_modules=(_BUNDLED_PACKAGE, _EXTERNAL_MODULE), + ) + + assert cmsis_nn.resolve_backend(cmsis_nn.CortexM.M55) == ("external", "m55") + + +def test_fallback_with_missing_package( + modify_available_cmsis_nn_modules, +) -> None: + cmsis_nn = modify_available_cmsis_nn_modules( + available_modules=(_EXTERNAL_MODULE), + ) + + assert cmsis_nn.resolve_backend(cmsis_nn.CortexM.M55) == ("external", "m55") + + +def test_bundled_module_preferred_over_external( + modify_available_cmsis_nn_modules, +) -> None: + cmsis_nn = modify_available_cmsis_nn_modules( + available_modules=(_BUNDLED_MODULE, _BUNDLED_PACKAGE, _EXTERNAL_MODULE), + ) + + assert cmsis_nn.resolve_backend(cmsis_nn.CortexM.M55) != ("external", "m55") + + +def test_raise_without_dependency(modify_available_cmsis_nn_modules) -> None: + cmsis_nn = modify_available_cmsis_nn_modules( + available_modules=(), + ) + + with pytest.raises( + ModuleNotFoundError, + match="Cortex-M backend dependencies are not installed", + ): + cmsis_nn.resolve_backend(cmsis_nn.CortexM.M55) diff --git a/examples/arm/setup.sh b/examples/arm/setup.sh index 266698cd490..0ef0138f3a2 100755 --- a/examples/arm/setup.sh +++ b/examples/arm/setup.sh @@ -22,7 +22,6 @@ 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 @@ -49,7 +48,6 @@ 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" @@ -125,10 +123,6 @@ function check_options() { enable_vela=1 shift ;; - --disable-cortex-m-deps) - enable_cortex_m=0 - shift - ;; --enable-model-converter) enable_model_converter=1 shift @@ -311,7 +305,6 @@ 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}" @@ -362,11 +355,6 @@ 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 a1358418a14..140a1163020 100644 --- a/install_executorch.py +++ b/install_executorch.py @@ -191,7 +191,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 = ["cortex_m", "ethos_u", "vgf", "openvino"] + allowed_optional_dependencies = ["ethos_u", "vgf", "openvino"] parser.add_argument( "--optional-dependency", action="append", @@ -225,20 +225,7 @@ def main(args): # Step 1: Install core dependencies first install_requirements(use_pytorch_nightly) - # 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 + # Step 2: Install core package package_spec = "." if args.optional_dependency: extras = ",".join(dict.fromkeys(args.optional_dependency)) @@ -259,7 +246,7 @@ def main(args): ) subprocess.run(cmd, check=True) - # Step 4: Extra (optional) packages that is only useful for running examples. + # Step 3: Extra (optional) packages that is only useful for running examples. if not args.minimal: install_optional_example_requirements(use_pytorch_nightly) diff --git a/pyproject.toml b/pyproject.toml index ddcb0b7bdc3..7a3176105ec 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,10 +61,6 @@ requires-python = ">=3.10,<3.14" # in setup.py. [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/setup.py b/setup.py index 838e204cafc..f5f68b815bd 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,6 @@ # Copyright (c) Meta Platforms, Inc. and affiliates. -# Copyright 2024 Arm Limited and/or its affiliates. # All rights reserved. +# Copyright 2024, 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. @@ -136,6 +136,7 @@ def _minimal_cmake_flags() -> List[str]: "-DEXECUTORCH_BUILD_TESTS=OFF", "-DEXECUTORCH_BUILD_VULKAN=OFF", "-DEXECUTORCH_BUILD_XNNPACK=OFF", + "-DEXECUTORCH_BUILD_CMSIS_NN_PYBINDS=OFF", ] @@ -982,6 +983,9 @@ def run(self): # noqa C901 if cmake_cache.is_enabled("EXECUTORCH_BUILD_VULKAN"): cmake_build_args += ["--target", "vulkan_backend"] + if cmake_cache.is_enabled("EXECUTORCH_BUILD_CMSIS_NN_PYBINDS"): + cmake_build_args += ["--target", "cmsis_nn"] + if cmake_cache.is_enabled("EXECUTORCH_BUILD_CUDA"): cmake_build_args += ["--target", "aoti_cuda_backend"] cmake_build_args += ["--target", "aoti_common_shims_slim"] @@ -1094,6 +1098,14 @@ def run(self): # noqa C901 modpath="executorch.codegen.tools.selective_build", dependent_cmake_flags=["EXECUTORCH_BUILD_PYBIND"], ), + BuiltExtension( + src="cmsis_nn.*", + src_dir="backends/cortex_m/cmsis_nn-build", + modpath="executorch.backends.cortex_m.library._cmsis_nn.cmsis_nn", + dependent_cmake_flags=[ + "EXECUTORCH_BUILD_CMSIS_NN_PYBINDS", + ], + ), BuiltExtension( src="extension/llm/runner/_llm_runner.*", # @lint-ignore https://github.com/pytorch/executorch/blob/cb3eba0d7f630bc8cec0a9cc1df8ae2f17af3f7a/scripts/lint_xrefs.sh modpath="executorch.extension.llm.runner._llm_runner", diff --git a/tools/cmake/preset/default.cmake b/tools/cmake/preset/default.cmake index 65d96062518..ae5437ea443 100644 --- a/tools/cmake/preset/default.cmake +++ b/tools/cmake/preset/default.cmake @@ -184,6 +184,10 @@ define_overridable_option( define_overridable_option( EXECUTORCH_BUILD_CORTEX_M "Build the Cortex-M backend" BOOL OFF ) +define_overridable_option( + EXECUTORCH_BUILD_CMSIS_NN_PYBINDS "Build the CMSIS-NN Python bindings" BOOL + OFF +) define_overridable_option( EXECUTORCH_BUILD_CUDA "Build the CUDA backend" BOOL OFF ) diff --git a/tools/cmake/preset/pybind.cmake b/tools/cmake/preset/pybind.cmake index a4777700cee..d292c9ed240 100644 --- a/tools/cmake/preset/pybind.cmake +++ b/tools/cmake/preset/pybind.cmake @@ -6,6 +6,7 @@ # LICENSE file in the root directory of this source tree. set_overridable_option(EXECUTORCH_BUILD_PYBIND ON) +set_overridable_option(EXECUTORCH_BUILD_CMSIS_NN_PYBINDS ON) set_overridable_option(EXECUTORCH_BUILD_KERNELS_QUANTIZED ON) set_overridable_option(EXECUTORCH_BUILD_KERNELS_QUANTIZED_AOT ON) # Enable logging and program verification even when in release mode. We are From cd8a1e110a9dfe8a69c5e98999adebde3af28786 Mon Sep 17 00:00:00 2001 From: Erik Lundell Date: Fri, 26 Jun 2026 11:40:34 +0200 Subject: [PATCH 2/2] Fix cmsis_nn BuiltExtension for windows. Signed-off-by: Erik Lundell Change-Id: Ia9f9239e0a19b47a4a8480b86693a5f9236beff5 --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index f5f68b815bd..9aa4b128526 100644 --- a/setup.py +++ b/setup.py @@ -1099,8 +1099,8 @@ def run(self): # noqa C901 dependent_cmake_flags=["EXECUTORCH_BUILD_PYBIND"], ), BuiltExtension( - src="cmsis_nn.*", - src_dir="backends/cortex_m/cmsis_nn-build", + src="cmsis_nn.cp*" if _is_windows() else "cmsis_nn.*", + src_dir="backends/cortex_m/cmsis_nn-build/%BUILD_TYPE%/", modpath="executorch.backends.cortex_m.library._cmsis_nn.cmsis_nn", dependent_cmake_flags=[ "EXECUTORCH_BUILD_CMSIS_NN_PYBINDS",