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
120 changes: 120 additions & 0 deletions .ci/scripts/build-qnn-windows-msvc.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# 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.

# Build-validation for the Qualcomm (QNN) backend under MSVC on Windows. Mirrors
# setup-windows-msvc.ps1 but provisions the QNN SDK and delegates the actual
# build to backends/qualcomm/scripts/build.ps1, which builds both the x86-64
# Windows host and the ARM64 Windows cross-compiled targets. This is a build-only
# bring-up job: it exists to surface MSVC portability issues in the QNN backend
# sources. The QNN backend loads the SDK's runtime libraries via the OS loader at
# execution time, so only the SDK headers (shipped cross-platform in the SDK zip)
# are needed to build.
#
# backends/qualcomm/scripts/download_qnn_sdk.py only auto-downloads on Linux
# x86, so this script fetches and extracts the SDK itself. The pinned version
# and URL are read from that same module (see "Provision the QNN SDK" below),
# which resolves them from backends/qualcomm/scripts/qnn_config.sh -- the single
# source of truth shared with the Linux build scripts.

$ErrorActionPreference = "Stop"

conda create --yes --quiet -n et python=3.12
conda activate et

# Install cmake
conda install -y cmake

# Install CI requirements
pip install -r .ci/docker/requirements-ci.txt

# --- Provision the QNN SDK -------------------------------------------------
# Reuse the QNN_VERSION / QNN_ZIP_URL that download_qnn_sdk.py already resolves
# from qnn_config.sh (the single source of truth) instead of re-parsing it here.
$qnnInfo = python -c "import sys; sys.path.insert(0, r'backends\qualcomm\scripts'); import download_qnn_sdk as d; print(d.QNN_VERSION); print(d.QNN_ZIP_URL)"
if ($LASTEXITCODE -ne 0 -or $qnnInfo.Count -lt 2) {
Write-Error "Failed to read QNN_VERSION / QNN_ZIP_URL from download_qnn_sdk.py."
exit 1
}
$qnnVersion = $qnnInfo[0].Trim()
$qnnZipUrl = $qnnInfo[1].Trim()

$qnnInstallDir = Join-Path $env:TEMP "qnn"
$qnnSdkRoot = Join-Path $qnnInstallDir "qairt\$qnnVersion"

if (Test-Path -Path $qnnSdkRoot) {
Write-Host "QNN SDK already present at $qnnSdkRoot"
} else {
New-Item -Path $qnnInstallDir -ItemType Directory -Force | Out-Null
$qnnZip = Join-Path $env:TEMP "qnn_sdk.zip"
Write-Host "Downloading QNN SDK v$qnnVersion ..."
$ProgressPreference = "SilentlyContinue"
Invoke-WebRequest -Uri $qnnZipUrl -OutFile $qnnZip
Write-Host "Extracting QNN SDK ..."
Expand-Archive -Path $qnnZip -DestinationPath $qnnInstallDir -Force
Remove-Item -Path $qnnZip -Force
}

if (-not (Test-Path -Path (Join-Path $qnnSdkRoot "include\QNN"))) {
Write-Error "QNN SDK layout unexpected: missing include\QNN under $qnnSdkRoot"
exit 1
}

$env:QNN_SDK_ROOT = $qnnSdkRoot
Write-Host "Set QNN_SDK_ROOT=$env:QNN_SDK_ROOT"

# --- Build ------------------------------------------------------------------
# Delegate to backends/qualcomm/scripts/build.ps1, the canonical Windows build
# entry point, rather than duplicating cmake invocations here. It reads
# $env:QNN_SDK_ROOT (set above), activates the matching VS dev shell for each
# pass itself, and has its own $ErrorActionPreference = 'Stop' plus per-step
# $LASTEXITCODE checks that throw on failure -- so a failed configure or compile
# aborts this script (and the CI job) without extra handling here.
#
# Two passes, one per target architecture:
#
# Pass 1 -- x86-64 Windows host build. -SkipArm64Windows limits build.ps1 to
# its x86-64 block, which also builds the AOT pybind module and example
# runners. This validates the native host build.
.\backends\qualcomm\scripts\build.ps1 -SkipArm64Windows -Release

# Verify the x86-64 pass produced its key artifacts. build.ps1 throws on a
# failed configure/compile, but a silently-empty or relocated output would still
# pass; assert the backend DLL and the QNN example runner exist so a packaging or
# output-path regression fails the job here rather than downstream.
$x86Artifacts = @(
"build-x86_64-windows\backends\qualcomm\Release\qnn_executorch_backend.dll",
"build-x86_64-windows\examples\qualcomm\executor_runner\Release\qnn_executor_runner.exe"
)
foreach ($artifact in $x86Artifacts) {
if (-not (Test-Path -Path $artifact)) {
Write-Error "Expected x86-64 build artifact not found: $artifact"
exit 1
}
}

# Pass 2 -- ARM64 Windows cross-compile (for Windows-on-Snapdragon devices).
# Still runs on the same x86 CI runner: build.ps1 activates the arm64 dev shell
# itself and sets CMAKE_SYSTEM_PROCESSOR=ARM64 to cross-compile, so no arm64
# hardware is required. -SkipX86Windows limits build.ps1 to its ARM64 block.
# This validates the cross-build, which is the configuration that ships to
# devices.
.\backends\qualcomm\scripts\build.ps1 -SkipX86Windows -Release

# Verify the ARM64 cross-compile produced its key artifacts, mirroring the
# x86-64 check above. This is the configuration that ships to devices, so a
# missing backend DLL or example runner here should fail the job.
$arm64Artifacts = @(
"build-arm64-windows\backends\qualcomm\Release\qnn_executorch_backend.dll",
"build-arm64-windows\examples\qualcomm\executor_runner\Release\qnn_executor_runner.exe"
)
foreach ($artifact in $arm64Artifacts) {
if (-not (Test-Path -Path $artifact)) {
Write-Error "Expected ARM64 build artifact not found: $artifact"
exit 1
}
}

Write-Host "QNN backend MSVC build completed successfully!"
80 changes: 80 additions & 0 deletions .github/workflows/qnn-windows-msvc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
name: Test QNN Backend (Windows MSVC build)

# Windows MSVC build-validation for the Qualcomm (QNN) backend. Separate from
# test-backend-qnn.yml (which runs the Linux delegate tests) so this scarce
# Windows runner is only used when files that can affect the build change.
# The path filter is intentionally broader than backends/qualcomm/**: core
# build inputs (top-level CMake, tools/cmake, extension/, runtime/) can also
# break the QNN Windows build even when no QNN file is touched.
#
# Kept standalone rather than added to pull.yml: pull.yml runs every job on
# every PR, and Windows runners are expensive. Path-filtering here avoids
# burning one on unrelated PRs, matching cuda.yml / vulkan.yml / windows-msvc.yml.

on:
push:
branches:
- main
- release/*
tags:
- ciflow/trunk/*
pull_request:
paths:
- .github/workflows/qnn-windows-msvc.yml
- .ci/scripts/build-qnn-windows-msvc.ps1
- backends/qualcomm/**
- CMakeLists.txt
- tools/cmake/**
- extension/**
- runtime/**
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}-${{ github.event_name == 'workflow_dispatch' }}
cancel-in-progress: true

permissions:
contents: read

jobs:
changed-files:
name: Get changed files
uses: ./.github/workflows/_get-changed-files.yml
with:
include-push-diff: true

run-decision:
name: CI run decision
uses: ./.github/workflows/_ci-run-decision.yml

build-qnn-windows-msvc:
name: build-qnn-windows-msvc
needs: [changed-files, run-decision]
# Path-filtered: mirrors the workflow-level pull_request `paths:` filter, so
# push commits that don't touch these paths skip this job on non-sampled
# commits. The paths are broader than backends/qualcomm/** because core
# build inputs can also break the QNN Windows build. See
# _ci-run-decision.yml for the sampling policy.
if: |
contains(needs.changed-files.outputs.changed-files, 'backends/qualcomm/') ||
contains(needs.changed-files.outputs.changed-files, '.ci/scripts/build-qnn-windows-msvc.ps1') ||
contains(needs.changed-files.outputs.changed-files, '.github/workflows/qnn-windows-msvc.yml') ||
contains(needs.changed-files.outputs.changed-files, 'CMakeLists.txt') ||
contains(needs.changed-files.outputs.changed-files, 'tools/cmake/') ||
contains(needs.changed-files.outputs.changed-files, 'extension/') ||
contains(needs.changed-files.outputs.changed-files, 'runtime/') ||
needs.run-decision.outputs.is-full-run == 'true'
uses: pytorch/test-infra/.github/workflows/windows_job.yml@main
with:
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
timeout: 90
script: |
git config --global http.sslBackend openssl
git submodule update --init --recursive
conda init powershell
powershell -Command "& {
Set-PSDebug -Trace 1
\$ErrorActionPreference = 'Stop'
\$PSNativeCommandUseErrorActionPreference = \$true
.ci/scripts/build-qnn-windows-msvc.ps1
}"
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
/*
* Copyright (c) Qualcomm Innovation Center, Inc.
* All rights reserved.
Expand All @@ -16,6 +16,8 @@
#include <executorch/backends/qualcomm/runtime/backends/lpai/LpaiBackend.h>
#include <executorch/backends/qualcomm/runtime/backends/lpai/LpaiDevice.h>

#include <pal/Path.h>

#include <string>

namespace executorch {
Expand Down Expand Up @@ -154,7 +156,7 @@
}
// 5. Create QnnSystemImplementation and load qnn library
std::unique_ptr<QnnSystemImplementation> system_implementation =
std::make_unique<QnnSystemImplementation>("libQnnSystem.so");
std::make_unique<QnnSystemImplementation>(pal::path::GetLibraryName("QnnSystem"));
ret = system_implementation->Load();
ET_CHECK_OR_RETURN_ERROR(
ret == Error::Ok, Internal, "Fail to load Qnn system library");
Expand Down
Loading
Loading