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
33 changes: 33 additions & 0 deletions .ci/scripts/build-qnn-direct-sdk.sh
Original file line number Diff line number Diff line change
@@ -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 \

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add --skip_x86_64 --skip_linux_android to this? iiuc, build.sh still builds the x86 and full Android stacks (those default on), so a job meant to verify just the direct build does a lot of extra work. Not sure..

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @psiddh,
I think both are fine. I was doing a full build just to double check that direct_runner for build-android can also be properly build. Since the goal here is to verify .so is built successfully, I have skipped x86 and android in newest change.
Thanks

--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)"
19 changes: 19 additions & 0 deletions .github/workflows/pull.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should we gate this job on QNN-related paths (e.g. backends/qualcomm/**, CMakeLists.txt, the build scripts) instead of running it on every PR?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is the intial idea. But the general layer can also introduce changes that does not work with Hexagon toolchain. For this reason, I have set it to run on every PR.

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:
Expand Down
23 changes: 12 additions & 11 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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}"
Expand Down
1 change: 1 addition & 0 deletions backends/qualcomm/scripts/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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 <dsp_type> --soc_model <model> to enable."
echo "<dsp_type> 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"
Expand Down
16 changes: 10 additions & 6 deletions backends/qualcomm/scripts/build_utils.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand All @@ -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
Expand Down
77 changes: 77 additions & 0 deletions backends/qualcomm/scripts/install_qnn_sdk.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
8 changes: 8 additions & 0 deletions backends/qualcomm/scripts/qnn_config.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"

@psiddh psiddh Jun 10, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nits: Should we add a SHA-256 check on the downloaded Hexagon SDK zip? as a follow up PR maybe ?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have added this check in newest commit. Thanks for the suggestion on safety check.

# 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"
7 changes: 7 additions & 0 deletions extension/data_loader/mman.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

/**
Expand Down
Loading