Skip to content
Merged
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
17 changes: 11 additions & 6 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -801,6 +801,9 @@ if(EXECUTORCH_BUILD_PYBIND)
torch
)

# RPATH for _portable_lib.so
set(_portable_lib_rpath "$ORIGIN/../../../torch/lib")

if(EXECUTORCH_BUILD_EXTENSION_MODULE)
# Always use static linking for pybindings to avoid runtime symbol
# resolution issues
Expand Down Expand Up @@ -835,6 +838,7 @@ if(EXECUTORCH_BUILD_PYBIND)

if(EXECUTORCH_BUILD_QNN)
list(APPEND _dep_libs qnn_executorch_backend)
string(APPEND _portable_lib_rpath ":$ORIGIN/../../backends/qualcomm")
endif()

if(EXECUTORCH_BUILD_ENN)
Expand Down Expand Up @@ -886,19 +890,20 @@ if(EXECUTORCH_BUILD_PYBIND)
target_compile_options(portable_lib PUBLIC ${_pybind_compile_options})
target_link_libraries(portable_lib PRIVATE ${_dep_libs})

# Set RPATH to find PyTorch libraries relative to the installation location
# This goes from executorch/extension/pybindings up to site-packages, then to
# torch/lib. Don't do this to APPLE, as it will error out on the following
# error:
# Set RPATH to find PyTorch and backend libraries relative to the installation
# location. This goes from executorch/extension/pybindings up to
# site-packages, then to torch/lib. If QNN is enabled, also add
# backends/qualcomm/. Don't do this to APPLE, as it will error out on the
# following error:
#
if(APPLE)
# Skip setting @loader_path for APPLE, since it causes error like ld:
# duplicate LC_RPATH '@loader_path' in '<site-packages>/torch/lib/
# libtorch_cpu.dylib'
else()
set_target_properties(
portable_lib PROPERTIES BUILD_RPATH "$ORIGIN/../../../torch/lib"
INSTALL_RPATH "$ORIGIN/../../../torch/lib"
portable_lib PROPERTIES BUILD_RPATH "${_portable_lib_rpath}"
INSTALL_RPATH "${_portable_lib_rpath}"
)
endif()

Expand Down
53 changes: 52 additions & 1 deletion backends/qualcomm/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,47 @@ get_filename_component(
_common_include_directories "${EXECUTORCH_SOURCE_DIR}/.." ABSOLUTE
)

# We only download QNN SDK when we build pip wheel for ExecuTorch. Please don't
# change this code unless you know what you are doing.
if(EXECUTORCH_BUILD_WHEEL_DO_NOT_USE)
set(_qnn_default_sdk_dir "${CMAKE_CURRENT_BINARY_DIR}/sdk/qnn")

if(EXISTS "${_qnn_default_sdk_dir}" AND EXISTS "${_qnn_default_sdk_dir}/lib")
message(STATUS "Found cached Qualcomm SDK at ${_qnn_default_sdk_dir}")
set(QNN_SDK_ROOT
${_qnn_default_sdk_dir}
CACHE PATH "Qualcomm SDK root directory" FORCE
)
else()
message(STATUS "Downloading Qualcomm SDK")
execute_process(
COMMAND
${PYTHON_EXECUTABLE}
${EXECUTORCH_SOURCE_DIR}/backends/qualcomm/scripts/download_qnn_sdk.py
--dst-folder ${_qnn_default_sdk_dir} --print-sdk-path
WORKING_DIRECTORY ${EXECUTORCH_SOURCE_DIR}
RESULT_VARIABLE _qnn_sdk_download_result
OUTPUT_VARIABLE _qnn_sdk_download_output
ERROR_VARIABLE _qnn_sdk_download_error
OUTPUT_STRIP_TRAILING_WHITESPACE
)
if(NOT _qnn_sdk_download_result EQUAL 0 OR _qnn_sdk_download_output
STREQUAL ""
)
message(
FATAL_ERROR
"Failed to download Qualcomm SDK. stdout: ${_qnn_sdk_download_output}\n"
"stderr: ${_qnn_sdk_download_error}"
)
endif()
set(QNN_SDK_ROOT
${_qnn_sdk_download_output}
CACHE PATH "Qualcomm SDK root directory" FORCE
)
endif()
set(ENV{QNN_SDK_ROOT} ${QNN_SDK_ROOT})
endif()

if(NOT DEFINED QNN_SDK_ROOT)
message(
FATAL_ERROR
Expand Down Expand Up @@ -214,7 +255,9 @@ add_subdirectory(
install(
TARGETS qnn_executorch_backend
EXPORT ExecuTorchTargets
DESTINATION ${CMAKE_INSTALL_LIBDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/executorch/backends/qualcomm
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}/executorch/backends/qualcomm
RUNTIME DESTINATION ${CMAKE_INSTALL_LIBDIR}/executorch/backends/qualcomm
)

# QNN pybind
Expand Down Expand Up @@ -275,4 +318,12 @@ if(${CMAKE_SYSTEM_PROCESSOR} MATCHES "x86_64")
${QNN_EXECUTORCH_ROOT_DIR}/aot/python
${CMAKE_CURRENT_BINARY_DIR}/qnn_executorch/python
)

install(
TARGETS PyQnnManagerAdaptor PyQnnWrapperAdaptor
LIBRARY
DESTINATION ${CMAKE_INSTALL_LIBDIR}/executorch/backends/qualcomm/python
RUNTIME
DESTINATION ${CMAKE_INSTALL_LIBDIR}/executorch/backends/qualcomm/python
)
endif()
45 changes: 44 additions & 1 deletion backends/qualcomm/scripts/download_qnn_sdk.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Add these imports for additional logging
import argparse
import ctypes
import logging
import os
Expand Down Expand Up @@ -592,3 +592,46 @@ def install_qnn_sdk() -> bool:

# libc++ and QNN SDK setup
return _ensure_libcxx_stack() and _ensure_qnn_sdk_lib()


def main(argv: Optional[List[str]] = None) -> int:
parser = argparse.ArgumentParser(
description="Helper utility for Qualcomm SDK staging."
)
parser.add_argument(
"--dst-folder",
type=pathlib.Path,
default=SDK_DIR,
help="Destination directory for the Qualcomm SDK.",
)
parser.add_argument(
"--print-sdk-path",
action="store_true",
help="Print the resolved Qualcomm SDK path to stdout.",
)
parser.add_argument(
"--install-sdk",
action="store_true",
help="Ensure the SDK and runtime libraries are staged and loaded.",
)
args = parser.parse_args(argv)

logging.basicConfig(level=logging.INFO)

sdk_path: Optional[pathlib.Path]
if args.install_sdk:
if not install_qnn_sdk():
return 1
sdk_path = pathlib.Path(os.environ.get("QNN_SDK_ROOT", args.dst_folder))
else:
sdk_path = _download_qnn_sdk(dst_folder=args.dst_folder)
if sdk_path is None:
return 1

if args.print_sdk_path and sdk_path is not None:
print(sdk_path)
return 0


if __name__ == "__main__":
raise SystemExit(main())
104 changes: 24 additions & 80 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,6 @@
import site
import subprocess
import sys
import sysconfig
import tempfile

from distutils import log # type: ignore[import-not-found]
from distutils.sysconfig import get_python_lib # type: ignore[import-not-found]
Expand Down Expand Up @@ -463,84 +461,6 @@ def run(self):
if self._ran_build:
return

try:
# Following code is for building the Qualcomm backend.
from backends.qualcomm.scripts.download_qnn_sdk import (
_download_qnn_sdk,
is_linux_x86,
)

if is_linux_x86():
os.environ["EXECUTORCH_BUILDING_WHEEL"] = "1"

with tempfile.TemporaryDirectory() as tmpdir:
tmp_path = Path(tmpdir)
sdk_path = _download_qnn_sdk(dst_folder=tmp_path)

if not sdk_path:
raise RuntimeError(
"Qualcomm SDK not found, cannot build backend"
)

# Determine paths
prj_root = Path(__file__).parent.resolve()
build_sh = prj_root / "backends/qualcomm/scripts/build.sh"
build_root = prj_root / "build-x86"

if not build_sh.exists():
raise FileNotFoundError(f"{build_sh} not found")

# Run build.sh with SDK path exported
env = dict(**os.environ)
env["QNN_SDK_ROOT"] = str(sdk_path)
subprocess.check_call(
[
str(build_sh),
"--skip_linux_android",
"--skip_linux_embedded",
],
env=env,
)

# Copy the main .so into the wheel package
so_src = (
build_root / "backends/qualcomm/libqnn_executorch_backend.so"
)
so_dst = Path(
self.get_ext_fullpath(
"executorch.backends.qualcomm.qnn_backend"
)
)
self.mkpath(str(so_dst.parent)) # ensure destination exists
self.copy_file(str(so_src), str(so_dst))
logging.info(f"Copied Qualcomm backend: {so_src} -> {so_dst}")

# Copy Python adaptor .so files
ext_suffix = sysconfig.get_config_var("EXT_SUFFIX")

so_files = [
(
"executorch.backends.qualcomm.python.PyQnnManagerAdaptor",
prj_root
/ f"backends/qualcomm/python/PyQnnManagerAdaptor{ext_suffix}",
),
(
"executorch.backends.qualcomm.python.PyQnnWrapperAdaptor",
prj_root
/ f"backends/qualcomm/python/PyQnnWrapperAdaptor{ext_suffix}",
),
]

for module_name, so_src in so_files:
so_dst = Path(self.get_ext_fullpath(module_name))
self.mkpath(str(so_dst.parent))
self.copy_file(str(so_src), str(so_dst))
logging.info(f"Copied Qualcomm backend: {so_src} -> {so_dst}")

except ImportError:
logging.error("Fail to build Qualcomm backend")
logging.exception("Import error")

if self.editable_mode:
self._ran_build = True
self.run_command("build")
Expand Down Expand Up @@ -837,6 +757,11 @@ def run(self): # noqa C901
cmake_build_args += ["--target", "custom_ops_aot_lib"]
cmake_build_args += ["--target", "quantized_ops_aot_lib"]

if cmake_cache.is_enabled("EXECUTORCH_BUILD_QNN"):
cmake_build_args += ["--target", "qnn_executorch_backend"]
cmake_build_args += ["--target", "PyQnnManagerAdaptor"]
cmake_build_args += ["--target", "PyQnnWrapperAdaptor"]

# Set PYTHONPATH to the location of the pip package.
os.environ["PYTHONPATH"] = (
site.getsitepackages()[0] + ";" + os.environ.get("PYTHONPATH", "")
Expand Down Expand Up @@ -924,5 +849,24 @@ def run(self): # noqa C901
dst="executorch/data/lib/",
dependent_cmake_flags=[],
),
BuiltFile(
src_dir="%CMAKE_CACHE_DIR%/backends/qualcomm/%BUILD_TYPE%/",
src_name="qnn_executorch_backend",
dst="executorch/backends/qualcomm/",
is_dynamic_lib=True,
dependent_cmake_flags=["EXECUTORCH_BUILD_QNN"],
),
BuiltExtension(
src_dir="%CMAKE_CACHE_DIR%/backends/qualcomm/%BUILD_TYPE%/",
src="PyQnnManagerAdaptor.*",
modpath="executorch.backends.qualcomm.python.PyQnnManagerAdaptor",
dependent_cmake_flags=["EXECUTORCH_BUILD_QNN"],
),
BuiltExtension(
src_dir="%CMAKE_CACHE_DIR%/backends/qualcomm/%BUILD_TYPE%/",
src="PyQnnWrapperAdaptor.*",
modpath="executorch.backends.qualcomm.python.PyQnnWrapperAdaptor",
dependent_cmake_flags=["EXECUTORCH_BUILD_QNN"],
),
],
)
6 changes: 6 additions & 0 deletions tools/cmake/preset/default.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,12 @@ define_overridable_option(
BOOL
FALSE
)
define_overridable_option(
EXECUTORCH_BUILD_WHEEL_DO_NOT_USE
"On if in the wheel building process. Should only be used to guard code that is only needed for building the wheel."
BOOL
FALSE
)

# ------------------------------------------------------------------------------
# Validations
Expand Down
4 changes: 4 additions & 0 deletions tools/cmake/preset/pybind.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ set_overridable_option(EXECUTORCH_BUILD_EXTENSION_FLAT_TENSOR ON)
set_overridable_option(EXECUTORCH_BUILD_EXTENSION_DATA_LOADER ON)
set_overridable_option(EXECUTORCH_BUILD_EXTENSION_MODULE ON)
set_overridable_option(EXECUTORCH_BUILD_EXTENSION_NAMED_DATA_MAP ON)
set_overridable_option(EXECUTORCH_BUILD_WHEEL_DO_NOT_USE ON)

# TODO(larryliu0820): Temporarily disable building llm_runner for Windows wheel
# due to the issue of tokenizer file path length limitation.
Expand All @@ -35,6 +36,9 @@ elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
set_overridable_option(EXECUTORCH_BUILD_EXTENSION_TRAINING ON)
set_overridable_option(EXECUTORCH_BUILD_EXTENSION_LLM_RUNNER ON)
set_overridable_option(EXECUTORCH_BUILD_EXTENSION_LLM ON)
if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|amd64|i.86)$")
set_overridable_option(EXECUTORCH_BUILD_QNN ON)
endif()
elseif(CMAKE_SYSTEM_NAME STREQUAL "Windows" OR CMAKE_SYSTEM_NAME STREQUAL
"WIN32"
)
Expand Down
Loading