Skip to content
Open
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
46 changes: 46 additions & 0 deletions .ci/scripts/test_qnn_imports.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#!/usr/bin/env python3
# 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.

"""Validate that all QNN backend Python modules can be imported.

Walks executorch.backends.qualcomm.* via pkgutil and attempts to import
each module. Reports failures with module name and error, then exits
non-zero if any imports failed. Run after editable install + QNN SDK
setup to catch missing dependencies early in CI.
"""

import importlib
import pkgutil
import sys


def main():
failures = []
count = 0
for _importer, name, _ispkg in pkgutil.walk_packages(
["backends/qualcomm"], prefix="executorch.backends.qualcomm."
):
# Skip fb-internal and test modules
if ".fb." in name or ".tests." in name:
continue
count += 1
try:
importlib.import_module(name)
except Exception as e:
failures.append((name, type(e).__name__, str(e)))

if failures:
print(f"{len(failures)}/{count} import failure(s):")
for name, etype, msg in failures:
print(f" FAIL: {name} -- {etype}: {msg}")
sys.exit(1)

print(f"All {count} QNN modules imported successfully")


if __name__ == "__main__":
main()
68 changes: 68 additions & 0 deletions .github/workflows/pull.yml
Original file line number Diff line number Diff line change
Expand Up @@ -923,6 +923,74 @@ jobs:
# Run QNN pass unit tests
pytest -xvs backends/qualcomm/tests/test_passes.py

test-qnn-python-imports-linux:
name: test-qnn-python-imports-linux
uses: pytorch/test-infra/.github/workflows/linux_job_v2.yml@main
permissions:
id-token: write
contents: read
strategy:
fail-fast: false
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: 15
script: |
CONDA_ENV=$(conda env list --json | jq -r ".envs | .[-1]")
conda activate "${CONDA_ENV}"

PYTHON_EXECUTABLE=python bash .ci/scripts/setup-qnn-deps.sh
PYTHON_EXECUTABLE=python source .ci/scripts/build-qnn-sdk.sh

CMAKE_ARGS="-DEXECUTORCH_BUILD_QNN=ON -DQNN_SDK_ROOT=$QNN_SDK_ROOT -DEXECUTORCH_BUILD_EXTENSION_TENSOR=ON" \
PYTHON_EXECUTABLE=python bash .ci/scripts/setup-linux.sh --build-tool cmake --editable true

pip install -r requirements-examples.txt

# Validate all QNN Python imports resolve.
# Catches missing deps (lm_eval, openpyxl, etc.) early with a clear error.
python .ci/scripts/test_qnn_imports.py

test-qnn-delegate-linux:
name: test-qnn-delegate-linux
uses: pytorch/test-infra/.github/workflows/linux_job_v2.yml@main
permissions:
id-token: write
contents: read
strategy:
fail-fast: false
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: 90
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-qnn-deps.sh
# Source (not bash) so QNN_SDK_ROOT stays in the environment
PYTHON_EXECUTABLE=python source .ci/scripts/build-qnn-sdk.sh

# Editable install so the PyQnnManagerAdaptor .so built by build-qnn-sdk.sh
# is visible in the source tree
CMAKE_ARGS="-DEXECUTORCH_BUILD_QNN=ON -DQNN_SDK_ROOT=$QNN_SDK_ROOT -DEXECUTORCH_BUILD_EXTENSION_TENSOR=ON" \
PYTHON_EXECUTABLE=python bash .ci/scripts/setup-linux.sh --build-tool cmake --editable true

pip install -r requirements-examples.txt

# Enable conftest.py to call setup_environment() for pytest runs
export QNN_DELEGATE_TEST=1
export LD_LIBRARY_PATH="$QNN_SDK_ROOT/lib/x86_64-linux-clang/:$(realpath build-x86/lib/):${LD_LIBRARY_PATH:-}"

# Run operator test classes only.
pytest -vs backends/qualcomm/tests/test_qnn_delegate.py \
-k "TestQNNFloatingPointOperator or TestQNNQuantizedOperator"

test-phi-3-mini-runner-linux:
name: test-phi-3-mini-runner-linux
uses: pytorch/test-infra/.github/workflows/linux_job_v2.yml@main
Expand Down
61 changes: 61 additions & 0 deletions backends/qualcomm/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# 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.

"""Pytest conftest for test_qnn_delegate.py.

When test_qnn_delegate.py is invoked via pytest (instead of as __main__),
setup_environment() is never called, so TestQNN class attributes remain at
their defaults (None / empty string / False). This conftest bridges the gap
by calling setup_environment() with a synthetic argv built from environment
variables, keeping a single source of truth for attribute assignment.

Required env vars for x86 simulator execution:
QNN_SOC_MODEL - e.g. "SM8650"
QNN_SDK_ROOT - path to QNN SDK (also used by verify_output)

Optional env vars (with defaults shown):
QNN_BACKEND - "htp"
QNN_BUILD_FOLDER - "build-x86"
QNN_ENABLE_X86_64 - "1" to enable x86 simulator mode
EXECUTORCH_ROOT - cwd
QNN_ARTIFACT_DIR - "/tmp/qnn_test_artifacts"
"""

import os
import sys


def pytest_configure(config):
if os.environ.get("QNN_DELEGATE_TEST") != "1":
return

argv = [
"test_qnn_delegate.py",
"--soc_model",
os.environ.get("QNN_SOC_MODEL", "SM8650"),
"--backend",
os.environ.get("QNN_BACKEND", "htp"),
"--build_folder",
os.environ.get("QNN_BUILD_FOLDER", "build-x86"),
"--executorch_root",
os.environ.get("EXECUTORCH_ROOT", os.getcwd()),
"--artifact_dir",
os.environ.get("QNN_ARTIFACT_DIR", "/tmp/qnn_test_artifacts"),
]

if os.environ.get("QNN_ENABLE_X86_64", "1") == "1":
argv.append("--enable_x86_64")

original_argv = sys.argv
try:
sys.argv = argv
from executorch.backends.qualcomm.tests.test_qnn_delegate import (
setup_environment,
)

setup_environment()
finally:
sys.argv = original_argv
10 changes: 8 additions & 2 deletions backends/qualcomm/tests/test_qnn_delegate.py
Original file line number Diff line number Diff line change
Expand Up @@ -1308,6 +1308,8 @@ def test_qnn_backend_is_inf(self):
self.lower_module_and_test_output(module, sample_input)

def test_qnn_backend_is_nan(self):
if self.enable_x86_64:
self.skipTest("isnan produces incorrect results on x86 simulator")
module = IsNan() # noqa: F405
sample_inputs = [
(
Expand Down Expand Up @@ -2182,7 +2184,9 @@ def test_qnn_backend_causal_mask(self):
module = CausalMask() # noqa: F405
torch.manual_seed(8)
sample_input = (torch.rand((1, 1, 1, 128)) < 0.5,)
self.lower_module_and_test_output(module, sample_input)
self.lower_module_and_test_output(
module, sample_input, skip_mutable_buffer=self.enable_x86_64
)

def test_qnn_backend_chunk_add(self):
module = ChunkAdd() # noqa: F405
Expand Down Expand Up @@ -4766,7 +4770,9 @@ def test_qnn_backend_causal_mask(self):
module = CausalMask() # noqa: F405
sample_input = (torch.rand((1, 1, 1, 128)) < 0.5,)
module = self.get_qdq_module(module, sample_input)
self.lower_module_and_test_output(module, sample_input)
self.lower_module_and_test_output(
module, sample_input, skip_mutable_buffer=self.enable_x86_64
)

def test_qnn_backend_chunk_add(self):
module = ChunkAdd() # noqa: F405
Expand Down
Loading