From 83692dbfd7fddaa8f759cd3d7c6bb3c2655f67fe Mon Sep 17 00:00:00 2001 From: arkadip-maitra Date: Sun, 10 May 2026 18:10:16 +0530 Subject: [PATCH 1/2] fix selective build validation and dtype build plumbing --- .../selective_build/test_selective_build.sh | 55 +++++++++++++- tools/cmake/Codegen.cmake | 6 ++ tools/cmake/common/preset.cmake | 73 ++++++++++++++++++- tools/cmake/preset/default.cmake | 12 +-- 4 files changed, 132 insertions(+), 14 deletions(-) diff --git a/examples/selective_build/test_selective_build.sh b/examples/selective_build/test_selective_build.sh index c1b5c627c42..6be555420db 100755 --- a/examples/selective_build/test_selective_build.sh +++ b/examples/selective_build/test_selective_build.sh @@ -150,7 +150,7 @@ test_cmake_select_ops_in_model() { # the .pte via gen_selected_max_kernel_num(). retry cmake -DCMAKE_BUILD_TYPE="$CMAKE_BUILD_TYPE" \ -DEXECUTORCH_SELECT_OPS_MODEL="./${model_export_name}" \ - -DEXECUTORCH_DTYPE_SELECTIVE_BUILD=ON \ + -DEXECUTORCH_ENABLE_DTYPE_SELECTIVE_BUILD=ON \ -DEXECUTORCH_OPTIMIZE_SIZE=ON \ -DCMAKE_INSTALL_PREFIX=cmake-out \ -DPYTHON_EXECUTABLE="$PYTHON_EXECUTABLE" \ @@ -171,6 +171,58 @@ test_cmake_select_ops_in_model() { || { echo "ERROR: header missing expected define"; exit 1; } echo "Generated: $(cat ${generated_header} | tail -1)" + echo "Verifying dtype-selective variant header was generated" + local generated_variant_header + generated_variant_header=$(find "${build_dir}" -name selected_op_variants.h -print -quit) + if [[ -z "${generated_variant_header}" ]]; then + echo "ERROR: selected_op_variants.h not generated" + exit 1 + fi + + echo 'Running selective build test' + ${build_dir}/selective_build_test --model_path="./${model_export_name}" + + echo "Removing ${model_export_name}" + rm "./${model_export_name}" +} + +test_cmake_select_ops_in_model_and_list() { + local model_name="add_mul" + local model_export_name="${model_name}.pte" + echo "Exporting ${model_name}" + ${PYTHON_EXECUTABLE} -m examples.portable.scripts.export --model_name="${model_name}" + local example_dir=examples/selective_build/basic + local build_dir=cmake-out/${example_dir}_combined + rm -rf ${build_dir} + retry cmake -DCMAKE_BUILD_TYPE="$CMAKE_BUILD_TYPE" \ + -DEXECUTORCH_SELECT_OPS_MODEL="./${model_export_name}" \ + -DEXECUTORCH_SELECT_OPS_LIST="aten::relu.out" \ + -DCMAKE_INSTALL_PREFIX=cmake-out \ + -DPYTHON_EXECUTABLE="$PYTHON_EXECUTABLE" \ + -B${build_dir} \ + ${example_dir} + + echo "Building ${example_dir} with model and list selectors" + cmake --build ${build_dir} -j9 --config $CMAKE_BUILD_TYPE + + echo "Verifying merged selected_operators.yaml contains both selector inputs" + local selected_operators_yaml=${build_dir}/executorch/executorch_selected_kernels/selected_operators.yaml + if [[ ! -f "${selected_operators_yaml}" ]]; then + echo "ERROR: selected_operators.yaml not generated" + exit 1 + fi + ${PYTHON_EXECUTABLE} - < Date: Wed, 13 May 2026 15:42:08 +0530 Subject: [PATCH 2/2] addressing reviews fixing bugs --- examples/selective_build/README.md | 30 +++++--- .../selective_build/advanced/CMakeLists.txt | 8 +-- examples/selective_build/basic/CMakeLists.txt | 2 +- tools/cmake/Codegen.cmake | 2 + tools/cmake/common/preset.cmake | 55 +++++++++------ tools/cmake/common/preset_test.py | 70 +++++++++++++++++++ tools/cmake/preset/default.cmake | 4 -- 7 files changed, 133 insertions(+), 38 deletions(-) diff --git a/examples/selective_build/README.md b/examples/selective_build/README.md index c6c8dc1ba57..2f13acd0d22 100644 --- a/examples/selective_build/README.md +++ b/examples/selective_build/README.md @@ -24,9 +24,15 @@ cmake --build . -j8 ### CMake Options -The example commands above show use of the EXECUTORCH_SELECT_OPS_MODEL option to select operators used in a PTE file, but there are -several ways to provide the operator list. The options can be passed to CMake in the same way (during configuration) and are mutually -exclusive, meaning that only one of these options should be chosen. +The example commands above show use of the `EXECUTORCH_SELECT_OPS_MODEL` +option to select operators used in a PTE file, but there are several ways to +provide the operator list. These options can be passed to CMake in the same +way during configuration, either individually or in combination. When multiple +selectors are provided, ExecuTorch merges them into a single +`selected_operators.yaml` file. A common pattern is to use +`EXECUTORCH_SELECT_OPS_MODEL` for model-derived metadata and +`EXECUTORCH_SELECT_OPS_LIST` for additional operators that must remain +available. * `EXECUTORCH_SELECT_OPS_MODEL`: Select operators used in a .PTE file. Takes a path to the file. * `EXECUTORCH_SELECT_OPS_YAML`: Provide a list of operators from a .yml file, typically generated with the `codegen/tools/gen_oplist.py` script. See this script for usage information. @@ -42,9 +48,15 @@ aten,aten::clone.out" #### DType-Selective Build -To further reduce binary size, ExecuTorch can specialize the individual operators for only the dtypes (data types) used. For example, if -the model only calls add with 32-bit floating point tensors, it can drop parts of the code that handle integer tensors or other floating point types. This option is controlled by passing `-DEXECUTORCH_DTYPE_SELECTIVE_BUILD=ON` to CMake. It is only supported in conjunction -with the `EXECUTORCH_SELECT_OPS_MODEL` option and is not yet supported for other modes. It is recommended to enable this option when using `EXECUTORCH_SELECT_OPS_MODEL` as it provides significant size savings on top of the kernel selective build. +To further reduce binary size, ExecuTorch can specialize the individual +operators for only the dtypes (data types) used. For example, if the model only +calls add with 32-bit floating point tensors, it can drop parts of the code +that handle integer tensors or other floating point types. This option is +controlled by passing `-DEXECUTORCH_ENABLE_DTYPE_SELECTIVE_BUILD=ON` to CMake. +It is only supported when model-derived metadata is available, typically via +`EXECUTORCH_SELECT_OPS_MODEL`. It is recommended to enable this option when +using `EXECUTORCH_SELECT_OPS_MODEL` as it provides significant size savings on +top of the kernel selective build. ### How it Works @@ -106,7 +118,7 @@ gen_selected_ops( OPS_FROM_MODEL "${EXECUTORCH_SELECT_OPS_MODEL}" DTYPE_SELECTIVE_BUILD - "${EXECUTORCH_DTYPE_SELECTIVE_BUILD}" + "${EXECUTORCH_ENABLE_DTYPE_SELECTIVE_BUILD}" ) generate_bindings_for_kernels( @@ -117,7 +129,7 @@ generate_bindings_for_kernels( CUSTOM_OPS_YAML "${_custom_ops_yaml}" DTYPE_SELECTIVE_BUILD - "${EXECUTORCH_DTYPE_SELECTIVE_BUILD}" + "${EXECUTORCH_ENABLE_DTYPE_SELECTIVE_BUILD}" ) gen_operators_lib( @@ -128,7 +140,7 @@ gen_operators_lib( DEPS executorch_core DTYPE_SELECTIVE_BUILD - "${EXECUTORCH_DTYPE_SELECTIVE_BUILD}" + "${EXECUTORCH_ENABLE_DTYPE_SELECTIVE_BUILD}" ) ``` diff --git a/examples/selective_build/advanced/CMakeLists.txt b/examples/selective_build/advanced/CMakeLists.txt index fdef5e6555d..47f450e9f55 100644 --- a/examples/selective_build/advanced/CMakeLists.txt +++ b/examples/selective_build/advanced/CMakeLists.txt @@ -54,7 +54,7 @@ option(EXECUTORCH_EXAMPLE_USE_CUSTOM_OPS # used by this example when defining a custom operator target: # # EXECUTORCH_SELECT_OPS_YAML EXECUTORCH_SELECT_OPS_LIST -# EXECUTORCH_SELECT_OPS_MODEL EXECUTORCH_DTYPE_SELECTIVE_BUILD +# EXECUTORCH_SELECT_OPS_MODEL EXECUTORCH_ENABLE_DTYPE_SELECTIVE_BUILD # ------------------------------- OPTIONS END -------------------------------- @@ -99,7 +99,7 @@ gen_selected_ops( OPS_FROM_MODEL "${EXECUTORCH_SELECT_OPS_MODEL}" DTYPE_SELECTIVE_BUILD - "${EXECUTORCH_DTYPE_SELECTIVE_BUILD}" + "${EXECUTORCH_ENABLE_DTYPE_SELECTIVE_BUILD}" ) generate_bindings_for_kernels( @@ -110,7 +110,7 @@ generate_bindings_for_kernels( CUSTOM_OPS_YAML "${_custom_ops_yaml}" DTYPE_SELECTIVE_BUILD - "${EXECUTORCH_DTYPE_SELECTIVE_BUILD}" + "${EXECUTORCH_ENABLE_DTYPE_SELECTIVE_BUILD}" ) gen_operators_lib( @@ -121,7 +121,7 @@ gen_operators_lib( DEPS executorch_core DTYPE_SELECTIVE_BUILD - "${EXECUTORCH_DTYPE_SELECTIVE_BUILD}" + "${EXECUTORCH_ENABLE_DTYPE_SELECTIVE_BUILD}" ) executorch_target_link_options_shared_lib(select_build_lib) diff --git a/examples/selective_build/basic/CMakeLists.txt b/examples/selective_build/basic/CMakeLists.txt index d74f94d7b3a..b0985fd3ea8 100644 --- a/examples/selective_build/basic/CMakeLists.txt +++ b/examples/selective_build/basic/CMakeLists.txt @@ -45,7 +45,7 @@ add_subdirectory(${EXECUTORCH_ROOT} ${CMAKE_CURRENT_BINARY_DIR}/executorch) # the generated kernel target. # # EXECUTORCH_SELECT_OPS_YAML EXECUTORCH_SELECT_OPS_LIST -# EXECUTORCH_SELECT_OPS_MODEL EXECUTORCH_DTYPE_SELECTIVE_BUILD +# EXECUTORCH_SELECT_OPS_MODEL EXECUTORCH_ENABLE_DTYPE_SELECTIVE_BUILD # ------------------------------- OPTIONS END -------------------------------- diff --git a/tools/cmake/Codegen.cmake b/tools/cmake/Codegen.cmake index f2e12ae5c8f..d3689f0fb63 100644 --- a/tools/cmake/Codegen.cmake +++ b/tools/cmake/Codegen.cmake @@ -355,6 +355,8 @@ function(gen_operators_lib) selected_portable_kernels PRIVATE EXECUTORCH_SELECTIVE_BUILD_DTYPE=1 ) + # Export these helper targets too because executorch_selected_kernels + # publicly links selected_portable_kernels in the dtype-selective path. install( TARGETS selected_kernels_util_all_deps selected_portable_kernels EXPORT ExecuTorchTargets diff --git a/tools/cmake/common/preset.cmake b/tools/cmake/common/preset.cmake index 27cd3b91ee3..1db83051e23 100644 --- a/tools/cmake/common/preset.cmake +++ b/tools/cmake/common/preset.cmake @@ -116,6 +116,33 @@ macro(load_build_preset) # try to determine a preset file. endmacro() +# Presets may cache boolean literals like OFF as STRING values before the final +# cache type is known. Treat CMake false literals as disabled while keeping real +# selectors and paths enabled. +function(is_nonfalse_cmake_string VALUE OUT_VAR) + string(TOUPPER "${VALUE}" _value_upper) + if("${VALUE}" STREQUAL "" + OR "${_value_upper}" STREQUAL "0" + OR "${_value_upper}" STREQUAL "FALSE" + OR "${_value_upper}" STREQUAL "OFF" + OR "${_value_upper}" STREQUAL "NO" + OR "${_value_upper}" STREQUAL "N" + OR "${_value_upper}" STREQUAL "IGNORE" + OR "${_value_upper}" STREQUAL "NOTFOUND" + OR "${_value_upper}" MATCHES ".*-NOTFOUND$" + ) + set(${OUT_VAR} + FALSE + PARENT_SCOPE + ) + else() + set(${OUT_VAR} + TRUE + PARENT_SCOPE + ) + endif() +endfunction() + # Check if the required options are set. function(is_option_enabled NAME OUT_VAR) if(NOT DEFINED ${NAME} AND NOT DEFINED CACHE{${NAME}}) @@ -151,30 +178,18 @@ function(is_option_enabled NAME OUT_VAR) ) endif() else() - if(NOT "${${NAME}}" STREQUAL "") - set(${OUT_VAR} - TRUE - PARENT_SCOPE - ) - else() - set(${OUT_VAR} - FALSE - PARENT_SCOPE - ) - endif() - endif() - else() - if("${${NAME}}") - set(${OUT_VAR} - TRUE - PARENT_SCOPE - ) - else() + is_nonfalse_cmake_string("${${NAME}}" _string_option_enabled) set(${OUT_VAR} - FALSE + ${_string_option_enabled} PARENT_SCOPE ) endif() + else() + is_nonfalse_cmake_string("${${NAME}}" _string_option_enabled) + set(${OUT_VAR} + ${_string_option_enabled} + PARENT_SCOPE + ) endif() endfunction() diff --git a/tools/cmake/common/preset_test.py b/tools/cmake/common/preset_test.py index 99c1bd1de85..8e1c9296d3a 100644 --- a/tools/cmake/common/preset_test.py +++ b/tools/cmake/common/preset_test.py @@ -320,6 +320,34 @@ def test_set_overridable_option_with_cli_override(self): self.run_cmake(cmake_args=["-DEXECUTORCH_TEST_MESSAGE='from the cli'"]) self.assert_cmake_cache("EXECUTORCH_TEST_MESSAGE", "from the cli", "STRING") + def test_is_option_enabled_cached_string_false_literal_is_disabled(self): + _cmake_lists_txt = """ + cmake_minimum_required(VERSION 3.24) + project(test_preset) + include(${PROJECT_SOURCE_DIR}/preset.cmake) + set(EXECUTORCH_TEST_OPTION OFF CACHE STRING "") + is_option_enabled(EXECUTORCH_TEST_OPTION EXECUTORCH_TEST_OPTION_ENABLED) + if(EXECUTORCH_TEST_OPTION_ENABLED) + message(FATAL_ERROR "Expected EXECUTORCH_TEST_OPTION to be disabled") + endif() + """ + self.create_workspace({"CMakeLists.txt": _cmake_lists_txt}) + self.run_cmake() + + def test_is_option_enabled_uncached_string_value_is_enabled(self): + _cmake_lists_txt = """ + cmake_minimum_required(VERSION 3.24) + project(test_preset) + include(${PROJECT_SOURCE_DIR}/preset.cmake) + set(EXECUTORCH_TEST_OPTION "aten::add.out,aten::mm.out") + is_option_enabled(EXECUTORCH_TEST_OPTION EXECUTORCH_TEST_OPTION_ENABLED) + if(NOT EXECUTORCH_TEST_OPTION_ENABLED) + message(FATAL_ERROR "Expected EXECUTORCH_TEST_OPTION to be enabled") + endif() + """ + self.create_workspace({"CMakeLists.txt": _cmake_lists_txt}) + self.run_cmake() + def test_check_required_options_on_if_on_off(self): """Test that when IF_ON is OFF, no checks are performed.""" @@ -343,6 +371,27 @@ def test_check_required_options_on_if_on_off(self): self.create_workspace({"CMakeLists.txt": _cmake_lists_txt}) self.run_cmake() # Should succeed + def test_check_required_options_on_cached_string_false_literal(self): + """Test that cached string OFF does not trigger required-option checks.""" + + _cmake_lists_txt = """ + cmake_minimum_required(VERSION 3.24) + project(test_preset) + include(${PROJECT_SOURCE_DIR}/preset.cmake) + + set(FEATURE_FLAG OFF CACHE STRING "") + set(REQUIRED_OPTION OFF) + + check_required_options_on( + IF_ON + FEATURE_FLAG + REQUIRES + REQUIRED_OPTION + ) + """ + self.create_workspace({"CMakeLists.txt": _cmake_lists_txt}) + self.run_cmake() + def test_check_required_options_on_all_required_on(self): """Test that when IF_ON is ON and all required options are ON, no error occurs.""" @@ -458,6 +507,27 @@ def test_check_conflicting_options_on_no_conflicts(self): self.create_workspace({"CMakeLists.txt": _cmake_lists_txt}) self.run_cmake() + def test_check_conflicting_options_on_cached_string_false_literal(self): + """Test that cached string OFF does not trigger conflict checks.""" + + _cmake_lists_txt = """ + cmake_minimum_required(VERSION 3.24) + project(test_preset) + include(${PROJECT_SOURCE_DIR}/preset.cmake) + + set(FEATURE_FLAG ON) + set(CONFLICTING_OPTION OFF CACHE STRING "") + + check_conflicting_options_on( + IF_ON + FEATURE_FLAG + CONFLICTS_WITH + CONFLICTING_OPTION + ) + """ + self.create_workspace({"CMakeLists.txt": _cmake_lists_txt}) + self.run_cmake() + def test_check_conflicting_options_on_one_conflict(self): """Test that when IF_ON is ON and one conflicting option is also ON, a fatal error occurs.""" _cmake_lists_txt = """ diff --git a/tools/cmake/preset/default.cmake b/tools/cmake/preset/default.cmake index f4cefe6ec11..321485ecc41 100644 --- a/tools/cmake/preset/default.cmake +++ b/tools/cmake/preset/default.cmake @@ -423,10 +423,6 @@ check_conflicting_options_on( EXECUTORCH_BUILD_PTHREADPOOL EXECUTORCH_BUILD_CPUINFO ) -# Legacy selective build selectors are intentionally allowed to compose. -# This matches gen_oplist.py, which merges list, YAML, and model inputs into -# one selected_operators.yaml file. - check_required_options_on( IF_ON EXECUTORCH_BUILD_WASM REQUIRES EXECUTORCH_BUILD_EXTENSION_MODULE EXECUTORCH_BUILD_EXTENSION_TENSOR