Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
21 changes: 19 additions & 2 deletions .ci/scripts/wheel/test_cpp_sdk.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@
"executorch::backends::xnnpack::XnnpackBackendOptions::workspace_manager",
)

# A representative symbol from the CUDA delegate's shim layer. The delegate's own
# methods are weak symbols, so this checks a strong one instead.
_CUDA_SYMBOLS = ("executorch::backends::cuda::clearCurrentCUDAStream",)

# `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 @@ -113,8 +117,12 @@ def _defines_symbol(library: Path, symbol: str) -> bool:
return False


def _assert_single_definer(symbols, what: str) -> None:
"""Exactly one shipped library may define each of `symbols`."""
def _assert_single_definer(symbols, what: str, optional: bool = False) -> None:
"""Exactly one shipped library may define each of `symbols`.

`optional` allows a component that is only present in some wheel flavors,
such as an accelerator delegate, to be absent without failing.
"""
assert shutil.which("nm") is not None, "nm is required to inspect the wheel"

package_dir = _installed_package_dir()
Expand All @@ -124,6 +132,9 @@ def _assert_single_definer(symbols, what: str) -> None:
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]
if optional and not definers:
print(f"- no {what} in this wheel, skipping")
return
assert len(definers) == 1, (
f"expected exactly one library to define {symbol}, found "
f"{len(definers)}: {pretty}. More than one definition means the "
Expand Down Expand Up @@ -152,6 +163,11 @@ def test_single_xnnpack_delegate() -> None:
_assert_single_definer(_XNNPACK_SYMBOLS, "XNNPACK delegate")


def test_single_cuda_delegate() -> None:
"""Exactly one shipped library may define the CUDA delegate, if present."""
_assert_single_definer(_CUDA_SYMBOLS, "CUDA delegate", optional=True)


def test_cpp_consumer(work_dir: Path) -> None:
"""A standalone C++ app builds and runs against the installed wheel."""
assert shutil.which("cmake") is not None, "cmake is required to build a consumer"
Expand Down Expand Up @@ -209,4 +225,5 @@ def run_tests(work_dir: Path) -> None:
test_single_threadpool()
test_single_kernel_registration()
test_single_xnnpack_delegate()
test_single_cuda_delegate()
test_cpp_consumer(work_dir)
52 changes: 41 additions & 11 deletions backends/cuda/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,17 @@ target_compile_options(
PUBLIC "$<$<COMPILE_LANGUAGE:CXX>:${_cuda_cxx_compile_options}>"
)

# Link against ExecuTorch core libraries
target_link_libraries(cuda_platform PRIVATE executorch_core ${CMAKE_DL_LIBS})
# Link against ExecuTorch core libraries. Resolve them from the shared runtime
# when there is one, so this does not carry a second copy of the backend
# registry.
if(EXECUTORCH_BUILD_SHARED)
target_link_libraries(
cuda_platform PRIVATE executorch_shared ${CMAKE_DL_LIBS}
)
executorch_target_link_shared_runtime(cuda_platform)
else()
target_link_libraries(cuda_platform PRIVATE executorch_core ${CMAKE_DL_LIBS})
endif()

install(
TARGETS cuda_platform
Expand Down Expand Up @@ -169,14 +178,9 @@ if(_cuda_is_msvc_toolchain)
else()
target_link_libraries(
aoti_cuda_shims
PRIVATE cuda_platform
PUBLIC -Wl,--whole-archive
aoti_common_shims_slim
-Wl,--no-whole-archive
CUDA::cudart
CUDA::curand
extension_cuda
${CMAKE_DL_LIBS}
PRIVATE cuda_platform -Wl,--whole-archive aoti_common_shims_slim
-Wl,--no-whole-archive
PUBLIC CUDA::cudart CUDA::curand extension_cuda ${CMAKE_DL_LIBS}
)
endif()

Expand All @@ -200,7 +204,33 @@ if(_cuda_is_msvc_toolchain)
list(APPEND _aoti_cuda_backend_sources runtime/cuda_allocator.cpp)
endif()

add_library(aoti_cuda_backend STATIC ${_aoti_cuda_backend_sources})
# Build the delegate as a shared library for the wheel so a process has one copy
# of it, and keep it static everywhere else so no other build changes.
if(EXECUTORCH_BUILD_SHARED)
set(_aoti_cuda_backend_library_type SHARED)
else()
set(_aoti_cuda_backend_library_type STATIC)
endif()
add_library(
aoti_cuda_backend ${_aoti_cuda_backend_library_type}
${_aoti_cuda_backend_sources}
)
if(EXECUTORCH_BUILD_SHARED)
set_target_properties(
aoti_cuda_backend
PROPERTIES OUTPUT_NAME executorch_cuda_backend
VERSION "${PROJECT_VERSION}"
SOVERSION "${PROJECT_VERSION_MAJOR}"
)
if(NOT APPLE)
# Ships beside the runtime in the wheel's lib/ directory. libcudart and
# friends come from the environment, so they are not bundled here.
set_target_properties(
aoti_cuda_backend PROPERTIES BUILD_RPATH "$ORIGIN" INSTALL_RPATH
"$ORIGIN"
)
endif()
endif()

target_include_directories(
aoti_cuda_backend
Expand Down
17 changes: 17 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -1160,6 +1160,23 @@ def run(self): # noqa C901
"EXECUTORCH_BUILD_XNNPACK",
],
),
# Install the CUDA delegate beside them when it is built. The CUDA
# runtime itself is not bundled; it comes from the environment.
BuiltFile(
src_dir="%CMAKE_CACHE_DIR%/backends/cuda/",
src_name=(
"libexecutorch_cuda_backend.so."
f"{get_runtime_soname_major()}.*"
),
dst=(
"executorch/lib/libexecutorch_cuda_backend.so."
f"{get_runtime_soname_major()}"
),
dependent_cmake_flags=[
"EXECUTORCH_BUILD_SHARED",
"EXECUTORCH_BUILD_CUDA",
],
),
# 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