Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
201 changes: 201 additions & 0 deletions .ci/scripts/test_wheel_package_qnn.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
#!/bin/bash
# === CI Wheel Build & Test Script ===

# Exit immediately on error, print each command, and capture all output to build.log
set -e
set -x
exec > >(tee -i build.log) 2>&1

# Save repo root
REPO_ROOT=$(pwd)

# ----------------------------
# Dynamically create script_qnn_wheel_test.py
# ----------------------------
cat > "/tmp/script_qnn_wheel_test.py" << 'EOF'
# pyre-ignore-all-errors
import argparse

import torch
from executorch.backends.qualcomm.quantizer.quantizer import QnnQuantizer
from executorch.backends.qualcomm.utils.utils import (
generate_htp_compiler_spec,
generate_qnn_executorch_compiler_spec,
get_soc_to_chipset_map,
to_edge_transform_and_lower_to_qnn,
)
from executorch.exir.backend.utils import format_delegated_graph
from executorch.examples.models.model_factory import EagerModelFactory
from executorch.exir.capture._config import ExecutorchBackendConfig
from executorch.extension.export_util.utils import save_pte_program
from torchao.quantization.pt2e.quantize_pt2e import convert_pt2e, prepare_pt2e, prepare_qat_pt2e

def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("-f", "--output_folder", type=str, default="", help="The folder to store the exported program")
parser.add_argument("--soc", type=str, default="SM8650", help="Specify the SoC model.")
parser.add_argument("-q", "--quantization", choices=["ptq", "qat"], help="Run post-traininig quantization.")
args = parser.parse_args()

class LinearModule(torch.nn.Module):
def __init__(self):
super().__init__()
self.linear = torch.nn.Linear(3, 3)
def forward(self, arg):
return self.linear(arg)
def get_example_inputs(self):
return (torch.randn(3, 3),)

model = LinearModule()
example_inputs = model.get_example_inputs()

if args.quantization:
quantizer = QnnQuantizer()
m = torch.export.export(model.eval(), example_inputs, strict=True).module()
if args.quantization == "qat":
m = prepare_qat_pt2e(m, quantizer)
m(*example_inputs)
elif args.quantization == "ptq":
m = prepare_pt2e(m, quantizer)
m(*example_inputs)
m = convert_pt2e(m)
else:
m = model

use_fp16 = True if args.quantization is None else False
backend_options = generate_htp_compiler_spec(use_fp16=use_fp16)
compile_spec = generate_qnn_executorch_compiler_spec(
soc_model=get_soc_to_chipset_map()[args.soc],
backend_options=backend_options,
)
delegated_program = to_edge_transform_and_lower_to_qnn(m, example_inputs, compile_spec)
output_graph = format_delegated_graph(delegated_program.exported_program().graph_module)
# Ensure QnnBackend is in the output graph
assert "QnnBackend" in output_graph
executorch_program = delegated_program.to_executorch(
config=ExecutorchBackendConfig(extract_delegate_segments=False)
)
save_pte_program(executorch_program, "linear", args.output_folder)

if __name__ == "__main__":
main()
EOF

# ----------------------------
# Wheel build and .so checks
# ----------------------------
echo "=== Building Wheel Package ==="
source .ci/scripts/utils.sh
install_executorch
EXECUTORCH_BUILDING_WHEEL=1 python setup.py bdist_wheel
unset EXECUTORCH_BUILDING_WHEEL

WHEEL_FILE=$(ls dist/*.whl | head -n 1)
echo "Found wheel: $WHEEL_FILE"

PYTHON_VERSION=$1
# ----------------------------
# Check wheel does NOT contain qualcomm/sdk
# ----------------------------
echo "Checking wheel does not contain qualcomm/sdk..."
SDK_FILES=$(unzip -l "$WHEEL_FILE" | awk '{print $4}' | grep "executorch/backends/qualcomm/sdk" || true)
if [ -n "$SDK_FILES" ]; then
echo "ERROR: Wheel package contains unexpected qualcomm/sdk files:"
echo "$SDK_FILES"
exit 1
else
echo "OK: No qualcomm/sdk files found in wheel"
fi

# ----------------------------
# Check .so files in the wheel
# ----------------------------
echo "Checking for .so files inside the wheel..."
WHEEL_SO_FILES=$(unzip -l "$WHEEL_FILE" | awk '{print $4}' | grep "executorch/backends/qualcomm/python" || true)
if [ -z "$WHEEL_SO_FILES" ]; then
echo "ERROR: No .so files found in wheel under executorch/backends/qualcomm/python"
exit 1
else
echo "Wheel contains the following .so files:"
echo "$WHEEL_SO_FILES"
fi

# ----------------------------
# Helpers
# ----------------------------
get_site_packages_dir () {
local PYBIN="$1"
"$PYBIN" - <<'PY'
import sysconfig, sys
print(sysconfig.get_paths().get("purelib") or sysconfig.get_paths().get("platlib"))
PY
}

run_core_tests () {
local PYBIN="$1" # path to python
local PIPBIN="$2" # path to pip
local LABEL="$3" # label to print (conda/venv)

echo "=== [$LABEL] Installing wheel & deps ==="
"$PIPBIN" install --upgrade pip
"$PIPBIN" install "$WHEEL_FILE"
"$PIPBIN" install torch=="2.9.0.dev20250906" --index-url "https://download.pytorch.org/whl/nightly/cpu"
"$PIPBIN" install --pre torchao --index-url "https://download.pytorch.org/whl/nightly/cpu"

echo "=== [$LABEL] Import smoke tests ==="
"$PYBIN" -c "import executorch; print('executorch imported successfully')"
"$PYBIN" -c "import executorch.backends.qualcomm; print('executorch.backends.qualcomm imported successfully')"

echo "=== [$LABEL] List installed executorch/backends/qualcomm/python ==="
local SITE_DIR
SITE_DIR="$(get_site_packages_dir "$PYBIN")"
local SO_DIR="$SITE_DIR/executorch/backends/qualcomm/python"
ls -l "$SO_DIR" || echo "Folder does not exist!"

echo "=== [$LABEL] Run export script to generate linear.pte ==="
(cd "$REPO_ROOT" && "$PYBIN" "/tmp/script_qnn_wheel_test.py")

if [ -f "$REPO_ROOT/linear.pte" ]; then
echo "[$LABEL] Model file linear.pte successfully created"
else
echo "ERROR: [$LABEL] Model file linear.pte was not created"
exit 1
fi
}

# ----------------------------
# Conda environment setup & tests
# ----------------------------
echo "=== Testing in Conda env ==="
TEMP_ENV_DIR=$(mktemp -d)
echo "Using temporary directory for conda: $TEMP_ENV_DIR"
conda create -y -p "$TEMP_ENV_DIR/env" python=$PYTHON_VERSION
# derive python/pip paths inside the conda env
CONDA_PY="$TEMP_ENV_DIR/env/bin/python"
CONDA_PIP="$TEMP_ENV_DIR/env/bin/pip"
# Some images require conda run; keep pip/python direct to simplify path math
run_core_tests "$CONDA_PY" "$CONDA_PIP" "conda"

# Cleanup conda env
conda env remove -p "$TEMP_ENV_DIR/env" -y || true
rm -rf "$TEMP_ENV_DIR"

# ----------------------------
# Python venv setup & tests
# ----------------------------
echo "=== Testing in Python venv ==="
TEMP_VENV_DIR=$(mktemp -d)
echo "Using temporary directory for venv: $TEMP_VENV_DIR"
python3 -m venv "$TEMP_VENV_DIR/venv"
VENV_PY="$TEMP_VENV_DIR/venv/bin/python"
VENV_PIP="$TEMP_VENV_DIR/venv/bin/pip"

# Ensure venv has wheel/build basics if needed
"$VENV_PIP" install --upgrade pip

run_core_tests "$VENV_PY" "$VENV_PIP" "venv"

# Cleanup venv
rm -rf "$TEMP_VENV_DIR"

echo "=== All tests completed! ==="
27 changes: 27 additions & 0 deletions .github/workflows/pull.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,33 @@ concurrency:
cancel-in-progress: true

jobs:
test-qnn-wheel-packages-linux:
name: test-qnn-wheel-packages-linux
uses: pytorch/test-infra/.github/workflows/linux_job_v2.yml@main
permissions:
id-token: write
contents: read
strategy:
fail-fast: false
matrix:
python-version: [ "3.10", "3.11", "3.12" ]
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: 180
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}"

# Create a clean env for each python version
conda create -y -n test_env_${{ matrix.python-version }} python=${{ matrix.python-version }}
conda activate test_env_${{ matrix.python-version }}

PYTHON_EXECUTABLE=python bash .ci/scripts/test_wheel_package_qnn.sh "${{ matrix.python-version }}"

test-setup-linux-gcc:
name: test-setup-linux-gcc
uses: pytorch/test-infra/.github/workflows/linux_job_v2.yml@main
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ cmake-android-out/
cmake-ios-out/
cmake-out*
cmake-out-android/
build-android/
build-x86/
dist/
ethos-u-scratch/
executorch.egg-info
Expand Down
14 changes: 14 additions & 0 deletions backends/qualcomm/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import os

from .scripts.download_qnn_sdk import install_qnn_sdk, is_linux_x86


env_flag = os.getenv("EXECUTORCH_BUILDING_WHEEL", "0").lower()
# If users have preinstalled QNN_SDK_ROOT, we will use it.
qnn_sdk_root_flag = os.getenv("QNN_SDK_ROOT", None)

if env_flag not in ("1", "true", "yes") and not qnn_sdk_root_flag and is_linux_x86():
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@kimishpatel this line

ok = install_qnn_sdk()

if not ok:
raise RuntimeError("Failed to install QNN SDK. Please check the logs above.")
7 changes: 5 additions & 2 deletions backends/qualcomm/runtime/backends/QnnImplementation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
* LICENSE file in the root directory of this source tree.
*/
#include <executorch/backends/qualcomm/runtime/backends/QnnImplementation.h>

#include "QnnInterface.h"
namespace executorch {
namespace backends {
Expand Down Expand Up @@ -52,7 +51,11 @@ Error QnnImplementation::StartBackend(
const QnnSaver_Config_t** saver_config) {
Qnn_ErrorHandle_t error = QNN_SUCCESS;
void* lib_handle = nullptr;
lib_handle = dlopen(lib_path.c_str(), RTLD_NOW | RTLD_GLOBAL);
// If the library is already loaded, return the handle.
lib_handle = dlopen(lib_path.c_str(), RTLD_NOW | RTLD_NOLOAD);
if (!lib_handle) {
lib_handle = dlopen(lib_path.c_str(), RTLD_NOW | RTLD_GLOBAL);
}
if (lib_handle == nullptr) {
QNN_EXECUTORCH_LOG_ERROR(
"Cannot Open QNN library %s, with error: %s",
Expand Down
1 change: 1 addition & 0 deletions backends/qualcomm/scripts/build.sh
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#!/usr/bin/env bash
# Copyright (c) Qualcomm Innovation Center, Inc.
# All rights reserved
#
Expand Down
Loading
Loading