Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
944ea67
Update
shoumikhin Jul 31, 2026
9792394
Update
shoumikhin Jul 31, 2026
9fde789
Update
shoumikhin Aug 1, 2026
e058bf3
Update
shoumikhin Aug 1, 2026
942f741
Update
shoumikhin Aug 1, 2026
c00dc8b
Update
shoumikhin Aug 1, 2026
ad3528b
Update
shoumikhin Aug 1, 2026
c8f333f
Update
shoumikhin Aug 1, 2026
97e4007
Update
shoumikhin Aug 1, 2026
be4d74b
Update
shoumikhin Aug 1, 2026
203a66f
Update
shoumikhin Aug 1, 2026
b324f6e
Update
shoumikhin Aug 1, 2026
18e4030
Update
shoumikhin Aug 1, 2026
51d37b7
Update
shoumikhin Aug 2, 2026
468207a
Update
shoumikhin Aug 2, 2026
2894de8
Update
shoumikhin Aug 2, 2026
4631f84
Update
shoumikhin Aug 2, 2026
c25cc5e
Update
shoumikhin Aug 2, 2026
da77a31
Update
shoumikhin Aug 2, 2026
80aebd5
Update
shoumikhin Aug 2, 2026
2fdaa68
Update
shoumikhin Aug 2, 2026
f6d9099
Update
shoumikhin Aug 2, 2026
4095f12
Update
shoumikhin Aug 2, 2026
36de7d5
Update
shoumikhin Aug 2, 2026
749a8d4
Update
shoumikhin Aug 2, 2026
a4d8a53
Update
shoumikhin Aug 2, 2026
2d20e05
Update
shoumikhin Aug 2, 2026
61ef01e
Update
shoumikhin Aug 2, 2026
ec8588e
Update
shoumikhin Aug 2, 2026
e21affc
Update
shoumikhin Aug 2, 2026
57cf805
Update
shoumikhin Aug 2, 2026
4699868
Update
shoumikhin Aug 3, 2026
0b270a9
Update
shoumikhin Aug 3, 2026
8197622
Update
shoumikhin Aug 3, 2026
d151a60
Update
shoumikhin Aug 3, 2026
3f3b50a
Update
shoumikhin Aug 3, 2026
7ca3d8b
Update
shoumikhin Aug 3, 2026
75d40ac
Update
shoumikhin Aug 3, 2026
b9a1443
Update
shoumikhin Aug 3, 2026
0078a62
Update
shoumikhin Aug 3, 2026
8bcbe7a
Update
shoumikhin Aug 3, 2026
9ae15f5
Update
shoumikhin Aug 3, 2026
3276434
Update
shoumikhin Aug 3, 2026
02656a8
Update
shoumikhin Aug 3, 2026
993f168
Update
shoumikhin Aug 3, 2026
637798e
Update
shoumikhin Aug 3, 2026
ea9981e
Update
shoumikhin Aug 3, 2026
b86002a
Update
shoumikhin Aug 3, 2026
517622d
Update
shoumikhin Aug 3, 2026
80c591d
Update
shoumikhin Aug 3, 2026
5a3e33a
Update
shoumikhin Aug 3, 2026
24514f8
Update
shoumikhin Aug 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 20 additions & 5 deletions .ci/scripts/wheel/test_cpp_sdk.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@
"executorch::runtime::get_backend_class",
)

# The thread pool accessor. A second definer means a second pool, which
# oversubscribes the CPU because each pool sizes itself to all cores.
_THREADPOOL_SYMBOLS = ("executorch::extension::threadpool::get_threadpool",)

# `nm -DC` prints "<hexaddr> <kind> <name>" for a definition and
# " U <name>" for an undefined reference.
_DEFINED = re.compile(r"^[0-9a-fA-F]+\s+(?P<kind>[A-Za-z])\s+(?P<name>.+)$")
Expand Down Expand Up @@ -106,23 +110,33 @@ def _defines_symbol(library: Path, symbol: str) -> bool:
return False


def test_single_backend_registry() -> None:
"""Exactly one shipped library may define the backend registry."""
def _assert_single_definer(symbols, what: str) -> None:
"""Exactly one shipped library may define each of `symbols`."""
assert shutil.which("nm") is not None, "nm is required to inspect the wheel"

package_dir = _installed_package_dir()
libraries = _shipped_shared_objects(package_dir)
assert libraries, f"no shared libraries found under {package_dir}"

for symbol in _REGISTRY_SYMBOLS:
for symbol in symbols:
definers = [lib for lib in libraries if _defines_symbol(lib, symbol)]
pretty = [str(lib.relative_to(package_dir)) for lib in definers]
assert len(definers) == 1, (
f"expected exactly one library to define {symbol}, found "
f"{len(definers)}: {pretty}. More than one definition means the "
f"process has more than one backend registry."
f"process has more than one {what}."
)
print(f"✓ single backend registry across {len(libraries)} shipped libraries")
print(f"✓ single {what} across {len(libraries)} shipped libraries")


def test_single_backend_registry() -> None:
"""Exactly one shipped library may define the backend registry."""
_assert_single_definer(_REGISTRY_SYMBOLS, "backend registry")


def test_single_threadpool() -> None:
"""Exactly one shipped library may define the thread pool accessor."""
_assert_single_definer(_THREADPOOL_SYMBOLS, "thread pool")


def test_cpp_consumer(work_dir: Path) -> None:
Expand Down Expand Up @@ -224,4 +238,5 @@ def _assert_runs_relocated(consumer, package_dir, work_dir, environment) -> None

def run_tests(work_dir: Path) -> None:
test_single_backend_registry()
test_single_threadpool()
test_cpp_consumer(work_dir)
43 changes: 38 additions & 5 deletions extension/threadpool/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,46 @@ else()
set(_threadpool_size_flag "EXECUTORCH_THREADPOOL_USE_PERFORMANCE_CORES")
endif()

# The thread pool is a process-wide singleton held in a function-local static,
# so every library that links it statically gets its own copy. Build it shared
# for the wheel, where several extensions are loaded into one interpreter, and
# keep it static everywhere else so no other build changes.
if(EXECUTORCH_BUILD_SHARED)
set(_threadpool_library_type SHARED)
else()
set(_threadpool_library_type STATIC)
endif()

add_library(
extension_threadpool threadpool.cpp threadpool_guard.cpp thread_parallel.cpp
cpuinfo_utils.cpp
)
target_link_libraries(
extension_threadpool PUBLIC executorch_core cpuinfo pthreadpool
extension_threadpool
${_threadpool_library_type} threadpool.cpp threadpool_guard.cpp
thread_parallel.cpp cpuinfo_utils.cpp
)
if(EXECUTORCH_BUILD_SHARED)
set_target_properties(
extension_threadpool
PROPERTIES OUTPUT_NAME executorch_threadpool
VERSION "${PROJECT_VERSION}"
SOVERSION "${PROJECT_VERSION_MAJOR}"
)
if(NOT APPLE)
# Ships beside libexecutorch.so in the wheel's lib/ directory.
set_target_properties(
extension_threadpool PROPERTIES BUILD_RPATH "$ORIGIN" INSTALL_RPATH
"$ORIGIN"
)
Comment thread
shoumikhin marked this conversation as resolved.
endif()
# cpuinfo and pthreadpool are forced static, so bundle them inside this
# library instead of making every consumer supply them.
executorch_target_whole_archive(extension_threadpool cpuinfo)
executorch_target_whole_archive(extension_threadpool pthreadpool)
target_link_libraries(extension_threadpool PRIVATE cpuinfo pthreadpool)
target_link_libraries(extension_threadpool PUBLIC executorch_shared)
Comment thread
shoumikhin marked this conversation as resolved.
else()
target_link_libraries(
extension_threadpool PUBLIC executorch_core cpuinfo pthreadpool
)
endif()
target_include_directories(
extension_threadpool PUBLIC ${_common_include_directories}
)
Expand Down
14 changes: 14 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -1114,6 +1114,20 @@ def run(self): # noqa C901
dst=f"executorch/lib/libexecutorch.so.{get_runtime_soname_major()}",
dependent_cmake_flags=["EXECUTORCH_BUILD_SHARED"],
),
# Install the shared thread pool next to it. It is a separate
# library so that a process has one pool rather than one per
# component that uses it.
BuiltFile(
src_dir="%CMAKE_CACHE_DIR%/extension/threadpool/",
src_name=(
"libexecutorch_threadpool.so." f"{get_runtime_soname_major()}.*"
),
dst=(
"executorch/lib/libexecutorch_threadpool.so."
f"{get_runtime_soname_major()}"
),
dependent_cmake_flags=["EXECUTORCH_BUILD_SHARED"],
),
# 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.
Expand Down
Loading