diff --git a/.ci/scripts/build-qnn-direct-sdk.sh b/.ci/scripts/build-qnn-direct-sdk.sh new file mode 100755 index 00000000000..4eccd0115f4 --- /dev/null +++ b/.ci/scripts/build-qnn-direct-sdk.sh @@ -0,0 +1,33 @@ +#!/bin/bash +# Copyright (c) Qualcomm Innovation Center, Inc. +# All rights reserved +# +# This source code is licensed under the BSD-style license found in the +# LICENSE file in the root directory of this source tree. + +set -eux + +source "$(dirname "${BASH_SOURCE[0]}")/../../backends/qualcomm/scripts/install_qnn_sdk.sh" + +setup_android_ndk +install_qnn +install_hexagon_sdk + +bash backends/qualcomm/scripts/build.sh \ + --build_direct_mode 3 --soc_model SM8750 \ + --skip_x86_64 --skip_linux_android \ + --release + +ARTIFACT="build-direct/backends/qualcomm/libqnn_executorch_backend.so" +if [ ! -f "${ARTIFACT}" ]; then + echo "ERROR: direct-mode build did not produce ${ARTIFACT}" >&2 + exit 1 +fi + +MAX_SIZE_BYTES=$((200 * 1024)) +ARTIFACT_SIZE=$(stat -c%s "${ARTIFACT}") +if [ "${ARTIFACT_SIZE}" -gt "${MAX_SIZE_BYTES}" ]; then + echo "ERROR: ${ARTIFACT} is ${ARTIFACT_SIZE} bytes, exceeds ${MAX_SIZE_BYTES}-byte (200 KiB) limit" >&2 + exit 1 +fi +echo "PASSED: direct-mode build produced ${ARTIFACT} (${ARTIFACT_SIZE} bytes, under ${MAX_SIZE_BYTES}-byte limit)" diff --git a/.github/workflows/pull.yml b/.github/workflows/pull.yml index 3ead9e6a49c..0ecab2c11b5 100644 --- a/.github/workflows/pull.yml +++ b/.github/workflows/pull.yml @@ -948,6 +948,25 @@ jobs: PYTHON_EXECUTABLE=python bash .ci/scripts/build-qnn-sdk.sh PYTHON_EXECUTABLE=python bash .ci/scripts/test_model.sh ${{ matrix.model }} "cmake" "qnn" + test-qnn-direct-build-linux: + name: test-qnn-direct-build-linux + uses: pytorch/test-infra/.github/workflows/linux_job_v2.yml@main + permissions: + id-token: write + contents: read + with: + runner: linux.2xlarge + docker-image: ci-image:executorch-ubuntu-22.04-qnn-sdk + submodules: 'recursive' + ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} + timeout: 30 + script: | + # The generic Linux job chooses to use base env, not the one setup by the image + CONDA_ENV=$(conda env list --json | jq -r ".envs | .[-1]") + conda activate "${CONDA_ENV}" + PYTHON_EXECUTABLE=python bash .ci/scripts/setup-linux.sh --build-tool cmake + PYTHON_EXECUTABLE=python bash .ci/scripts/build-qnn-direct-sdk.sh + test-qnn-testsuite-linux: name: test-qnn-testsuite-linux permissions: diff --git a/CMakeLists.txt b/CMakeLists.txt index bf6701123df..721880ea1d1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -49,17 +49,6 @@ cmake_minimum_required(VERSION 3.24) set(EXECUTORCH_ROOT ${CMAKE_CURRENT_SOURCE_DIR}) -# Hexagon toolchain with release build complains about code in third party -# libraries. -if("${CMAKE_SYSTEM_PROCESSOR}" MATCHES "Hexagon" AND "${CMAKE_BUILD_TYPE}" - STREQUAL "Release" -) - add_compile_options( - -Wno-error=format -Wno-error=implicit-int-conversion - -Wno-error=unused-variable -Wno-error=unused-function - ) -endif() - # --- ExecuTorch Version --- # Parse version from version.txt (single source of truth) file(READ "${EXECUTORCH_ROOT}/version.txt" ET_VERSION_STRING) @@ -90,6 +79,18 @@ project(executorch VERSION "${ET_VERSION_MAJOR}.${ET_VERSION_MINOR}.${ET_VERSION_PATCH}" ) +# Hexagon toolchain with release build complains about code in third party +# libraries. Must come after project(), which runs the toolchain file that sets +# CMAKE_SYSTEM_PROCESSOR, and before add_subdirectory(third-party). +if("${CMAKE_SYSTEM_PROCESSOR}" MATCHES "Hexagon" AND "${CMAKE_BUILD_TYPE}" + STREQUAL "Release" +) + add_compile_options( + -Wno-error=format -Wno-error=implicit-int-conversion + -Wno-error=unused-variable -Wno-error=unused-function + ) +endif() + message( STATUS "ExecuTorch version: ${ET_VERSION_MAJOR}.${ET_VERSION_MINOR}.${ET_VERSION_PATCH}" diff --git a/backends/qualcomm/scripts/build.sh b/backends/qualcomm/scripts/build.sh index 498bf924921..b0ef5a8ddbd 100755 --- a/backends/qualcomm/scripts/build.sh +++ b/backends/qualcomm/scripts/build.sh @@ -48,6 +48,7 @@ usage() { echo "e.g.: executorch$ ./backends/qualcomm/scripts/build.sh --skip_x86_64" echo "" echo "Direct mode: Use --build_direct_mode --soc_model to enable." + echo " id is mapped to Hexagon SDK dsp id. Refer to Hexagon SDK for more info." echo "You can choose either LPAI (ADSP) or CDSP (HTP) as the target DSP:" echo " LPAI (ADSP): dsp_type=0" echo " CDSP (HTP): dsp_type=3" diff --git a/backends/qualcomm/scripts/build_utils.sh b/backends/qualcomm/scripts/build_utils.sh index 81a7f2d9f2d..91651deb7ec 100644 --- a/backends/qualcomm/scripts/build_utils.sh +++ b/backends/qualcomm/scripts/build_utils.sh @@ -21,15 +21,15 @@ import sys, os devnull = open(os.devnull, 'w') old_stdout = sys.stdout sys.stdout = devnull -from executorch.backends.qualcomm.utils.utils import get_soc_to_htp_arch_map +from executorch.backends.qualcomm.serialization.qc_schema import _soc_info_table sys.stdout = old_stdout -m = get_soc_to_htp_arch_map() +m = {soc.name: info.htp_info.htp_arch for soc, info in _soc_info_table.items()} if '${soc_model}' not in m: sys.exit(1) print(m['${soc_model}'].value) " 2>/dev/null) || { echo "Error: SoC model '${soc_model}' not found in HTP arch map." - echo "Check supported models in executorch/backends/qualcomm/utils/utils.py get_soc_to_htp_arch_map()." + echo "Check supported models in executorch/backends/qualcomm/serialization/qc_schema.py _soc_info_table." exit 1 } @@ -39,15 +39,19 @@ import sys, os devnull = open(os.devnull, 'w') old_stdout = sys.stdout sys.stdout = devnull -from executorch.backends.qualcomm.utils.utils import get_soc_to_lpai_hw_ver_map +from executorch.backends.qualcomm.serialization.qc_schema import _soc_info_table sys.stdout = old_stdout -m = get_soc_to_lpai_hw_ver_map() +m = { + soc.name: info.lpai_info.lpai_hardware_version + for soc, info in _soc_info_table.items() + if info.lpai_info is not None +} if '${soc_model}' not in m: sys.exit(1) print(m['${soc_model}'].value) " 2>/dev/null) || { echo "Error: SoC model '${soc_model}' not found in LPAI hardware version map." - echo "Check supported models in executorch/backends/qualcomm/utils/utils.py get_soc_to_lpai_hw_ver_map()." + echo "Check supported models in executorch/backends/qualcomm/serialization/qc_schema.py _soc_info_table." exit 1 } fi diff --git a/backends/qualcomm/scripts/install_qnn_sdk.sh b/backends/qualcomm/scripts/install_qnn_sdk.sh index 7921b48da2f..f7e8ccab184 100644 --- a/backends/qualcomm/scripts/install_qnn_sdk.sh +++ b/backends/qualcomm/scripts/install_qnn_sdk.sh @@ -109,6 +109,83 @@ install_qnn() { echo "Set QNN_SDK_ROOT=${QNN_SDK_ROOT}" } +# Install the Hexagon SDK required for direct-mode CI builds. +install_hexagon_sdk() { + # Check if already configured externally and valid. + if [ -n "${HEXAGON_SDK_ROOT:-}" ] && [ -d "${HEXAGON_SDK_ROOT:-}" ] \ + && [ -n "${HEXAGON_TOOLS_ROOT:-}" ] && [ -d "${HEXAGON_TOOLS_ROOT:-}" ]; then + echo "Hexagon SDK already set to ${HEXAGON_SDK_ROOT} - skipping installation" + return + fi + + echo "Start installing Hexagon SDK v${HEXAGON_SDK_VERSION} (tools v${HEXAGON_TOOLS_VERSION})" + HEXAGON_INSTALLATION_DIR="/tmp/hexagon-sdk" + HEXAGON_SDK_DIR="${HEXAGON_INSTALLATION_DIR}/Hexagon_SDK/${HEXAGON_SDK_VERSION}" + HEXAGON_TOOLS_DIR="${HEXAGON_SDK_DIR}/tools/HEXAGON_Tools/${HEXAGON_TOOLS_VERSION}" + + # Return if already exist + if [ -d "${HEXAGON_SDK_DIR}" ] && [ -d "${HEXAGON_TOOLS_DIR}" ]; then + echo "Hexagon SDK already installed at ${HEXAGON_SDK_DIR}" + export HEXAGON_SDK_ROOT="${HEXAGON_SDK_DIR}" + export HEXAGON_TOOLS_ROOT="${HEXAGON_TOOLS_DIR}" + return + fi + + mkdir -p "${HEXAGON_INSTALLATION_DIR}" + + HEXAGON_ZIP_FILE="Hexagon_SDK_Linux.zip" + # Match install_qnn's retry shape: --fail rejects HTTP errors, + # --retry-all-errors retries transport failures, `unzip -t` validates the + # archive, and the SHA-256 check pins the exact bytes we tested against. All + # are inside the retry condition so a truncated or wrong-content download is + # re-fetched rather than killing the job. + HEXAGON_DOWNLOAD_MAX_ATTEMPTS=5 + for attempt in $(seq 1 ${HEXAGON_DOWNLOAD_MAX_ATTEMPTS}); do + rm -f "/tmp/${HEXAGON_ZIP_FILE}" + if curl --fail --retry 3 --retry-delay 5 --retry-connrefused --retry-all-errors \ + -Lo "/tmp/${HEXAGON_ZIP_FILE}" "${HEXAGON_SDK_ZIP_URL}" \ + && unzip -tq "/tmp/${HEXAGON_ZIP_FILE}" \ + && echo "${HEXAGON_SDK_ZIP_SHA256} /tmp/${HEXAGON_ZIP_FILE}" | sha256sum -c -; then + break + fi + ls -l "/tmp/${HEXAGON_ZIP_FILE}" 2>&1 || true + if [ "${attempt}" = "${HEXAGON_DOWNLOAD_MAX_ATTEMPTS}" ]; then + echo "ERROR: Hexagon SDK download failed after ${attempt} attempts" >&2 + exit 1 + fi + echo "Hexagon SDK download attempt ${attempt} failed; retrying in $((attempt * 10))s..." + sleep $((attempt * 10)) + done + echo "Finishing downloading Hexagon SDK." + + unzip -qo "/tmp/${HEXAGON_ZIP_FILE}" -d "${HEXAGON_INSTALLATION_DIR}" + echo "Finishing unzip Hexagon SDK." + + export HEXAGON_SDK_ROOT="${HEXAGON_SDK_DIR}" + export HEXAGON_TOOLS_ROOT="${HEXAGON_TOOLS_DIR}" + + # Verify the unzipped layout matches what build.sh and the QNN CMake + # files actually consume. If any of these are missing, a future SDK + # release likely changed the directory shape; updating + # HEXAGON_SDK_VERSION / HEXAGON_TOOLS_VERSION in qnn_config.sh (or the + # extraction layout below) is the fix. + for hexagon_required_path in \ + "${HEXAGON_SDK_ROOT}" \ + "${HEXAGON_SDK_ROOT}/build/cmake/hexagon_toolchain.cmake" \ + "${HEXAGON_TOOLS_ROOT}" \ + "${HEXAGON_TOOLS_ROOT}/Tools/target/hexagon"; do + if [ ! -e "${hexagon_required_path}" ]; then + echo "[Hexagon] ERROR: expected path not found: ${hexagon_required_path}" >&2 + echo "[Hexagon] Hexagon SDK ${HEXAGON_SDK_VERSION} or tools ${HEXAGON_TOOLS_VERSION} layout differs from what we pinned." >&2 + ls -la "$(dirname "${hexagon_required_path}")" >&2 || true + exit 1 + fi + done + + echo "Set HEXAGON_SDK_ROOT=${HEXAGON_SDK_ROOT}" + echo "Set HEXAGON_TOOLS_ROOT=${HEXAGON_TOOLS_ROOT}" +} + setup_libcpp() { clang_version=$1 LLVM_VERSION="14.0.0" diff --git a/backends/qualcomm/scripts/qnn_config.sh b/backends/qualcomm/scripts/qnn_config.sh index 938eb0d3007..cbdf2af7630 100644 --- a/backends/qualcomm/scripts/qnn_config.sh +++ b/backends/qualcomm/scripts/qnn_config.sh @@ -8,3 +8,11 @@ # QNN SDK Configuration QNN_VERSION="2.37.0.250724" QNN_ZIP_URL="https://softwarecenter.qualcomm.com/api/download/software/sdks/Qualcomm_AI_Runtime_Community/All/${QNN_VERSION}/v${QNN_VERSION}.zip" + +# Hexagon SDK Configuration (used only by direct-mode CI build). +# HEXAGON_TOOLS_VERSION must match the toolchain shipped inside HEXAGON_SDK_VERSION. +HEXAGON_SDK_VERSION="6.5.0.0" +HEXAGON_TOOLS_VERSION="19.0.07" +HEXAGON_SDK_ZIP_URL="https://apigwx-aws.qualcomm.com/qsc/public/v1/api/download/software/sdks/Hexagon_SDK/Linux/Debian/${HEXAGON_SDK_VERSION}/Hexagon_SDK_Linux.zip" +# SHA-256 of the downloaded zip. Recompute and update when HEXAGON_SDK_VERSION changes. Command to gen followin sha: sha256sum Hexagon_SDK_Linux.zip +HEXAGON_SDK_ZIP_SHA256="668626f75c38ce1ca993768953db9bf4b632753c3e32ed8363a8287e3aaffc9a" diff --git a/extension/data_loader/mman.h b/extension/data_loader/mman.h index a7a335961c8..9d3ee4be5aa 100644 --- a/extension/data_loader/mman.h +++ b/extension/data_loader/mman.h @@ -48,10 +48,17 @@ ET_INLINE off_t get_mmap_offset(size_t offset) { * Hint the kernel to prefetch pages eagerly and to optimize for sequential * reads. Intended to reduce page-fault stutter during model initialization * when the caller does not want to mlock the pages into RAM. + * + * MADV_WILLNEED / MADV_SEQUENTIAL are absent on some POSIX libcs (e.g. the + * Hexagon DSP toolchain). */ ET_INLINE void madvise_pages_willneed_sequential(void* addr, size_t len) { +#ifdef MADV_WILLNEED ::madvise(addr, len, MADV_WILLNEED); +#endif +#ifdef MADV_SEQUENTIAL ::madvise(addr, len, MADV_SEQUENTIAL); +#endif } /**