diff --git a/.ci/scripts/wheel/test_cpp_sdk.py b/.ci/scripts/wheel/test_cpp_sdk.py index 565cc2d7cb6..1b7b7a38a7a 100644 --- a/.ci/scripts/wheel/test_cpp_sdk.py +++ b/.ci/scripts/wheel/test_cpp_sdk.py @@ -784,7 +784,7 @@ def test_no_absolute_runtime_paths() -> None: # Link every component this wheel offers, and report which ones those are so the test # can check the result. Guarded individually because the set depends on the wheel. -foreach(_component threadpool kernels_optimized) +foreach(_component threadpool kernels_optimized kernels_quantized) if(TARGET executorch::${_component}) target_link_libraries(component_consumer PRIVATE executorch::${_component}) # Report the library file, not just the target name: the two differ, and the test diff --git a/kernels/quantized/CMakeLists.txt b/kernels/quantized/CMakeLists.txt index 9269a8c00f4..fdb35eee197 100644 --- a/kernels/quantized/CMakeLists.txt +++ b/kernels/quantized/CMakeLists.txt @@ -170,10 +170,33 @@ target_compile_options(quantized_kernels PUBLIC ${_common_compile_options}) # Build a library for _quantized_kernels_srcs # # quantized_ops_lib: Register quantized ops kernels into Executorch runtime + +# Ship this as a shared library so an installed package has something a C++ +# application can link. A static library would need whole-archive linking on the +# consumer side for the kernel registration to survive. +if(EXECUTORCH_BUILD_SHARED) + set(_quantized_ops_shared SHARED) +else() + set(_quantized_ops_shared "") +endif() gen_operators_lib( - LIB_NAME "quantized_ops_lib" KERNEL_LIBS quantized_kernels DEPS + ${_quantized_ops_shared} + LIB_NAME + "quantized_ops_lib" + KERNEL_LIBS + quantized_kernels + DEPS executorch_core ) +if(EXECUTORCH_BUILD_SHARED) + # Name the file after the component that selects it, so the pair reads + # together: executorch::kernels_quantized resolves to + # libexecutorch_kernels_quantized.so. The helper would otherwise name it after + # its internal target. + set_target_properties( + quantized_ops_lib PROPERTIES OUTPUT_NAME executorch_kernels_quantized + ) +endif() install( TARGETS quantized_kernels quantized_ops_lib diff --git a/setup.py b/setup.py index edd46a69f56..53318f02cc0 100644 --- a/setup.py +++ b/setup.py @@ -1201,6 +1201,11 @@ def run(self): # noqa C901 cmake_build_args += ["--target", "aoti_cuda_backend"] cmake_build_args += ["--target", "aoti_common_shims_slim"] + if cmake_cache.is_enabled("EXECUTORCH_BUILD_KERNELS_QUANTIZED"): + # Nothing else in the wheel links this, so without naming it here the + # target is declared but never built and packaging finds no file. + cmake_build_args += ["--target", "quantized_ops_lib"] + if cmake_cache.is_enabled("EXECUTORCH_BUILD_EXTENSION_MODULE"): cmake_build_args += ["--target", "extension_module"] @@ -1325,6 +1330,29 @@ def run(self): # noqa C901 "EXECUTORCH_BUILD_KERNELS_OPTIMIZED", ], ), + # Install the quantized kernels the same way. A model exported + # with quantized operators needs these registered at run time, + # and they existed only inside the Python extension before, so a + # C++ application had nothing to link. + BuiltFile( + src_dir="%CMAKE_CACHE_DIR%/kernels/quantized/", + src_name=( + "libexecutorch_kernels_quantized.so." + f"{get_runtime_soname_major()}.*" + ), + dst=( + "executorch/lib/" + "libexecutorch_kernels_quantized.so." + f"{get_runtime_soname_major()}" + ), + # The target is only created when the quantized kernels are + # enabled, so packaging has to require that too rather than + # looking for a file a shared build may never have produced. + dependent_cmake_flags=[ + "EXECUTORCH_BUILD_SHARED", + "EXECUTORCH_BUILD_KERNELS_QUANTIZED", + ], + ), # Install the prebuilt pybindings extension wrapper for the runtime, # portable kernels, and a selection of backends. This lets users # load and execute .pte files from python. diff --git a/tools/cmake/executorch-wheel-config.cmake b/tools/cmake/executorch-wheel-config.cmake index 7415c789664..722ae0ae26f 100644 --- a/tools/cmake/executorch-wheel-config.cmake +++ b/tools/cmake/executorch-wheel-config.cmake @@ -379,6 +379,10 @@ executorch_define_component(threadpool executorch_threadpool) # checks, so it has to be defined here or a consumer following the documentation # gets a bare name that CMake hands to the linker as a literal flag. executorch_define_component(kernels_optimized executorch_kernels_optimized) +# The quantized kernel set, for a model exported with quantized operators that +# runs them on plain CPU. A model delegated to XNNPACK does not need this, +# because that delegate claims the quantize and dequantize operators itself. +executorch_define_component(kernels_quantized executorch_kernels_quantized) # A consumer that links the thread pool has to see the same switch a source # build sets, or the parallel helpers in the runtime headers compile their