Skip to content

Commit 1904c5c

Browse files
committed
Vulkan: support Linux/Windows desktop GPUs and opt-in wheel builds
The Vulkan backend was developed with a focus on Android GPUs. This change makes it build and run correctly on Linux and Windows desktop discrete GPUs (NVIDIA/AMD/Intel) and adds an opt-in path to produce pre-built Vulkan binaries, without changing behavior on Android. The architecture was already largely portable (volk loader, headless compute, optional extensions gated by availability, staging-buffer transfers), so the work here is concentrated in build portability, a few discrete-GPU correctness fixes, and CI/packaging plumbing. Every change is Android-safe by construction: build divergence sits behind compile-time compiler/OS guards, AOT/policy changes are opt-in and default to current behavior, and runtime device-behavior changes key off queried capabilities so they resolve identically on a single-GPU Adreno/Mali device. Suggested review order: 1. Build portability -- backends/vulkan/CMakeLists.txt (per-compiler exception flag, submodule check), cmake/ShaderLibrary.cmake (glslc discovery, graceful skip in wheel builds), and runtime/vk_api/memory/vma_api.h (GCC/MSVC warning suppression alongside the existing clang block). 2. Shader compilation -- runtime/graph/ops/glsl/coopmat_mm.yaml targets Vulkan 1.3 so GL_KHR_cooperative_matrix compiles, plus a NameError-safety fix in runtime/gen_vulkan_spv.py. 3. Runtime correctness on discrete GPUs -- vk_api/Adapter.cpp enables the shaderInt16/Int64/Float64 features the shaders already use; vk_api/Runtime.cpp prefers a real GPU over software/integrated devices (with an ETVK_DEVICE_INDEX override); api/Context.cpp guards blit against compute-only queues; and api/containers/Tensor.cpp compares the storage-buffer size in bytes. 4. Ahead-of-time -- vulkan_preprocess.py, partitioner/vulkan_partitioner.py, and utils.py wire the previously-dropped small_texture_limits option through the compile-spec round-trip; test/test_vulkan_compile_options.py covers it. 5. Distribution and CI, all gated behind EXECUTORCH_BUILD_VULKAN so default wheels and other backends are untouched -- tools/cmake/preset/pybind.cmake, setup.py, .ci/scripts/wheel/*, .ci/scripts/setup-vulkan-*.{sh,ps1}, .github/workflows/test-backend-vulkan.yml (adds a real-GPU job), and .github/workflows/vulkan-windows.yml (MSVC build validation). Tested by building the backend with the Vulkan SDK and running fp32 and int8 models on an NVIDIA A100: outputs match the reference and all shaders compile (cooperative-matrix as SPIR-V 1.6). The existing SwiftShader CI path is unchanged. This change was authored with Claude.
1 parent e285edf commit 1904c5c

24 files changed

Lines changed: 546 additions & 45 deletions

.ci/scripts/setup-vulkan-linux-deps.sh

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
#!/bin/bash
32
# Copyright (c) Meta Platforms, Inc. and affiliates.
43
# All rights reserved.
@@ -43,7 +42,35 @@ install_vulkan_sdk() {
4342
export PATH="${PATH}:${_vulkan_sdk_dir}/${VULKAN_SDK_VERSION}/x86_64/bin/"
4443
}
4544

45+
setup_real_gpu_icd() {
46+
# On a real-GPU runner the system Vulkan ICD is installed by the GPU driver.
47+
# The loader searches both /etc/vulkan/icd.d and /usr/share/vulkan/icd.d, so
48+
# check both. If a system ICD is present, do NOT use SwiftShader so the real
49+
# device (and its fp16/int16/dot-product shader variants) is exercised. Fall
50+
# back to SwiftShader if no system ICD is found so the job stays green either
51+
# way.
52+
if ls /etc/vulkan/icd.d/*.json /usr/share/vulkan/icd.d/*.json \
53+
>/dev/null 2>&1; then
54+
echo "System Vulkan ICD(s) detected:"
55+
ls /etc/vulkan/icd.d/*.json /usr/share/vulkan/icd.d/*.json 2>/dev/null
56+
unset ETVK_USING_SWIFTSHADER || true
57+
else
58+
echo "WARNING: no system Vulkan ICD found; using SwiftShader."
59+
install_swiftshader
60+
fi
61+
}
62+
4663
VULKAN_SDK_VERSION="1.4.321.1"
4764

48-
install_swiftshader
49-
install_vulkan_sdk "${VULKAN_SDK_VERSION}"
65+
# The no-argument default installs SwiftShader so the existing CPU-runner CI is
66+
# unchanged. Pass "real-gpu" to prefer a real system ICD when one is present.
67+
case "${1:-swiftshader}" in
68+
real-gpu)
69+
install_vulkan_sdk "${VULKAN_SDK_VERSION}"
70+
setup_real_gpu_icd
71+
;;
72+
swiftshader | *)
73+
install_swiftshader
74+
install_vulkan_sdk "${VULKAN_SDK_VERSION}"
75+
;;
76+
esac
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Copyright (c) Meta Platforms, Inc. and affiliates.
2+
# All rights reserved.
3+
#
4+
# This source code is licensed under the BSD-style license found in the
5+
# LICENSE file in the root directory of this source tree.
6+
7+
# Install glslc (the Vulkan shader compiler) on Windows via conda-forge's
8+
# shaderc package, and make sure it is on PATH. glslc is the only build-time
9+
# Vulkan dependency -- the Vulkan headers and the volk loader come from the
10+
# in-tree submodules -- so this avoids depending on the heavyweight LunarG SDK
11+
# installer. Requires conda to be available (the callers create/activate an env).
12+
13+
$ErrorActionPreference = "Stop"
14+
15+
Write-Host "Installing shaderc (provides glslc) from conda-forge..."
16+
conda install -y -c conda-forge shaderc
17+
if ($LASTEXITCODE -ne 0) {
18+
Write-Error "Failed to install shaderc from conda-forge (exit ${LASTEXITCODE})"
19+
exit 1
20+
}
21+
22+
$glslc = Get-Command glslc -ErrorAction SilentlyContinue
23+
if (-not $glslc) {
24+
Write-Error "glslc not found on PATH after installing shaderc"
25+
exit 1
26+
}
27+
28+
# Expose glslc to the current process and, when running as a GitHub Actions step,
29+
# to subsequent steps.
30+
$glslcDir = Split-Path -Parent $glslc.Source
31+
$env:PATH = "${glslcDir};${env:PATH}"
32+
if ($env:GITHUB_PATH) {
33+
Add-Content -Path $env:GITHUB_PATH -Value $glslcDir
34+
}
35+
36+
Write-Host "glslc available at $($glslc.Source)"
37+
& glslc --version
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Copyright (c) Meta Platforms, Inc. and affiliates.
2+
# All rights reserved.
3+
#
4+
# This source code is licensed under the BSD-style license found in the
5+
# LICENSE file in the root directory of this source tree.
6+
7+
# Build-validation for the Vulkan backend under MSVC on Windows. Mirrors
8+
# setup-windows-msvc.ps1 but installs glslc (the Vulkan shader compiler) and
9+
# configures/builds the vulkan_backend target. This is a bring-up job: it exists
10+
# to surface MSVC portability issues in the Vulkan/volk/VMA code, so it may need
11+
# iteration.
12+
13+
conda create --yes --quiet -n et python=3.12
14+
conda activate et
15+
16+
# Install cmake
17+
conda install -y cmake
18+
19+
# Activate the VS environment - this is required for MSVC to work.
20+
& "C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\Common7\Tools\Launch-VsDevShell.ps1" -Arch amd64
21+
22+
# Install glslc (via conda-forge shaderc) and put it on PATH in this process.
23+
.ci/scripts/setup-vulkan-windows-deps.ps1
24+
25+
# Install CI requirements
26+
pip install -r .ci/docker/requirements-ci.txt
27+
28+
$buildDir = "cmake-out-vulkan"
29+
if (Test-Path -Path $buildDir) {
30+
Remove-Item -Path $buildDir -Recurse -Force
31+
}
32+
New-Item -Path $buildDir -ItemType Directory
33+
34+
cmake -S . -B $buildDir `
35+
-DCMAKE_BUILD_TYPE=Release `
36+
-DEXECUTORCH_BUILD_VULKAN=ON `
37+
-DPYTHON_EXECUTABLE=python
38+
39+
if ($LASTEXITCODE -ne 0) {
40+
Write-Host "CMake configuration failed. Exit code: $LASTEXITCODE."
41+
exit $LASTEXITCODE
42+
}
43+
44+
cmake --build $buildDir --config Release --target vulkan_backend -j16
45+
46+
if ($LASTEXITCODE -ne 0) {
47+
Write-Host "Vulkan backend MSVC build failed. Exit code: $LASTEXITCODE."
48+
exit $LASTEXITCODE
49+
}
50+
51+
Write-Host "Vulkan backend MSVC build completed successfully!"

.ci/scripts/test_backend.sh

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,15 @@ if [[ "$FLOW" == *qnn* ]]; then
5151
fi
5252

5353
if [[ "$FLOW" == *vulkan* ]]; then
54-
# Setup swiftshader and Vulkan SDK which are required to build the Vulkan delegate.
55-
source .ci/scripts/setup-vulkan-linux-deps.sh
54+
# Setup the Vulkan SDK and select an ICD: use the real system GPU ICD when one
55+
# is present (real-GPU runner), otherwise fall back to SwiftShader (CPU
56+
# runner). The Vulkan loader searches both standard ICD directories.
57+
if ls /etc/vulkan/icd.d/*.json /usr/share/vulkan/icd.d/*.json \
58+
>/dev/null 2>&1; then
59+
source .ci/scripts/setup-vulkan-linux-deps.sh "real-gpu"
60+
else
61+
source .ci/scripts/setup-vulkan-linux-deps.sh "swiftshader"
62+
fi
5663

5764
EXTRA_BUILD_ARGS+=" -DEXECUTORCH_BUILD_VULKAN=ON"
5865
fi

.ci/scripts/wheel/pre_build_script.sh

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,3 +69,37 @@ if [[ "$(uname -s)" == "Linux" && "$(uname -m)" == "x86_64" ]]; then
6969
echo "QNN_SDK_ROOT=${QNN_SDK_ROOT}" >> "${GITHUB_ENV}"
7070
echo "QNN SDK downloaded to ${QNN_SDK_ROOT}"
7171
fi
72+
73+
# Provision the Vulkan SDK (glslc) and submodules ONLY when explicitly requested
74+
# via EXECUTORCH_BUILD_VULKAN. The default wheel build leaves this unset, so it
75+
# does no extra work (no submodule fetch, no SDK download) and is unaffected.
76+
if [[ "${EXECUTORCH_BUILD_VULKAN:-0}" != "0" \
77+
&& "${EXECUTORCH_BUILD_VULKAN:-OFF}" != "OFF" ]]; then
78+
echo "Initializing Vulkan backend third-party submodules..."
79+
VULKAN_SUBMODULES=(
80+
backends/vulkan/third-party/Vulkan-Headers
81+
backends/vulkan/third-party/volk
82+
backends/vulkan/third-party/VulkanMemoryAllocator
83+
)
84+
if [[ $UNAME_S == *"MINGW"* || $UNAME_S == *"MSYS"* ]]; then
85+
git -c http.sslBackend=openssl submodule update --init "${VULKAN_SUBMODULES[@]}"
86+
echo "Installing Vulkan SDK for Windows wheel build..."
87+
powershell -ExecutionPolicy Bypass -File .ci/scripts/setup-vulkan-windows-deps.ps1
88+
else
89+
git submodule update --init "${VULKAN_SUBMODULES[@]}"
90+
echo "Installing Vulkan SDK for Linux wheel build..."
91+
VULKAN_SDK_VERSION="1.4.341.1"
92+
_vulkan_sdk_url="https://sdk.lunarg.com/sdk/download/${VULKAN_SDK_VERSION}/linux/vulkansdk-linux-x86_64-${VULKAN_SDK_VERSION}.tar.xz"
93+
_vulkan_sdk_dir="${HOME}/.vulkan-sdk/${VULKAN_SDK_VERSION}"
94+
mkdir -p "${_vulkan_sdk_dir}"
95+
curl --silent --show-error --location --fail --retry 3 --retry-all-errors \
96+
--output /tmp/vulkansdk.tar.xz "${_vulkan_sdk_url}"
97+
tar -C "${_vulkan_sdk_dir}" -xJf /tmp/vulkansdk.tar.xz
98+
VULKAN_SDK="${_vulkan_sdk_dir}/${VULKAN_SDK_VERSION}/x86_64"
99+
export VULKAN_SDK
100+
export PATH="${VULKAN_SDK}/bin:${PATH}"
101+
echo "VULKAN_SDK=${VULKAN_SDK}" >> "${GITHUB_ENV}"
102+
echo "${VULKAN_SDK}/bin" >> "${GITHUB_PATH}"
103+
echo "Vulkan SDK installed to ${VULKAN_SDK}"
104+
fi
105+
fi

.ci/scripts/wheel/test_linux.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,13 @@
3131
), f"OpenvinoBackend not found in registered backends: {registered}"
3232
print("✓ OpenvinoBackend is registered")
3333

34+
# Vulkan backend is optional: only present when the wheel was built with
35+
# EXECUTORCH_BUILD_VULKAN=1 and the Vulkan SDK (glslc) was available.
36+
if "VulkanBackend" in registered:
37+
print("✓ VulkanBackend is registered")
38+
else:
39+
print("⚠ VulkanBackend not registered (expected for the default wheel)")
40+
3441
test_base.run_tests(
3542
model_tests=[
3643
test_base.ModelTest(

.ci/scripts/wheel/test_windows.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
# This source code is licensed under the BSD-style license found in the
66
# LICENSE file in the root directory of this source tree.
77

8+
import platform
89
from typing import List
910

1011
import torch
@@ -15,6 +16,7 @@
1516
from executorch.examples.xnnpack.quantization.utils import quantize as quantize_xnn
1617
from executorch.exir import EdgeCompileConfig, to_edge_transform_and_lower
1718
from executorch.extension.pybindings.portable_lib import (
19+
_get_registered_backend_names,
1820
_load_for_executorch_from_buffer,
1921
)
2022
from test_base import ModelTest
@@ -63,6 +65,15 @@ def run_tests(model_tests: List[ModelTest]) -> None:
6365

6466

6567
if __name__ == "__main__":
68+
if platform.system() == "Windows":
69+
registered = _get_registered_backend_names()
70+
# Vulkan backend is optional: only present when the wheel was built with
71+
# EXECUTORCH_BUILD_VULKAN=1 and the Vulkan SDK (glslc) was available.
72+
if "VulkanBackend" in registered:
73+
print("✓ VulkanBackend is registered")
74+
else:
75+
print("⚠ VulkanBackend not registered (expected for the default wheel)")
76+
6677
run_tests(
6778
model_tests=[
6879
ModelTest(

.github/workflows/test-backend-vulkan.yml

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ concurrency:
1717
cancel-in-progress: true
1818

1919
jobs:
20+
# Default coverage: builds + runs on SwiftShader (software Vulkan) on CPU
21+
# runners. Runs on every PR and nightly.
2022
test-vulkan:
2123
uses: ./.github/workflows/_test_backend.yml
2224
with:
@@ -28,3 +30,47 @@ jobs:
2830
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
2931
timeout: 120
3032
run-linux: true
33+
34+
# Real-GPU coverage on an NVIDIA runner (exercises the fp16/int16/dot-product
35+
# shader variants that SwiftShader cannot). Standalone job (does not go through
36+
# _test_backend.yml) so the GPU is attached via gpu-arch-type, mirroring
37+
# cuda.yml. Not run on pull_request to avoid GPU cost and to keep it from
38+
# blocking PR merges; runs on nightly/push/dispatch.
39+
test-vulkan-real-gpu:
40+
if: ${{ github.event_name != 'pull_request' }}
41+
uses: pytorch/test-infra/.github/workflows/linux_job_v2.yml@main
42+
permissions:
43+
id-token: write
44+
contents: read
45+
with:
46+
timeout: 90
47+
runner: linux.g5.4xlarge.nvidia.gpu
48+
gpu-arch-type: cuda
49+
gpu-arch-version: "12.6"
50+
use-custom-docker-registry: false
51+
submodules: recursive
52+
ref: ${{ github.sha }}
53+
script: |
54+
set -eux
55+
56+
# Install the Vulkan SDK (glslc) and select a real system ICD. The NVIDIA
57+
# driver on this runner provides the ICD; install the loader as well.
58+
# NOTE: first-run check - inspect the vulkaninfo output below to confirm a
59+
# real NVIDIA device is selected (not llvmpipe/SwiftShader). If no system
60+
# ICD is present, setup-vulkan-linux-deps.sh falls back to SwiftShader.
61+
sudo apt-get update && sudo apt-get install -y libvulkan1 vulkan-tools || true
62+
source .ci/scripts/setup-vulkan-linux-deps.sh real-gpu
63+
vulkaninfo --summary || true
64+
65+
PYTHON_EXECUTABLE=python ./install_executorch.sh
66+
67+
cmake -DCMAKE_BUILD_TYPE=Release \
68+
-DEXECUTORCH_BUILD_VULKAN=ON \
69+
-DEXECUTORCH_BUILD_EXTENSION_TENSOR=ON \
70+
-DPYTHON_EXECUTABLE=python \
71+
-Bcmake-out .
72+
cmake --build cmake-out -j4 --target executor_runner
73+
74+
# Export a model to the Vulkan backend and run it on the GPU.
75+
python -m examples.vulkan.export -m mv2 -o .
76+
./cmake-out/executor_runner --model_path mv2.pte
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
name: Test Vulkan Backend Windows Build
2+
3+
# Build-validation for the Vulkan backend under MSVC on Windows. This is a
4+
# bring-up job (no GPU): it confirms the backend configures and compiles with
5+
# MSVC. Real-GPU Windows E2E is a follow-up once a Windows Vulkan GPU runner is
6+
# available. Path-filtered and not part of the required PR checks so it can be
7+
# iterated on without blocking unrelated work.
8+
9+
on:
10+
push:
11+
branches:
12+
- main
13+
- release/*
14+
tags:
15+
- ciflow/nightly/*
16+
pull_request:
17+
paths:
18+
- backends/vulkan/**
19+
- .ci/scripts/setup-vulkan-windows-deps.ps1
20+
- .ci/scripts/setup-windows-msvc-vulkan.ps1
21+
- .github/workflows/vulkan-windows.yml
22+
workflow_dispatch:
23+
24+
concurrency:
25+
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}-${{ github.event_name == 'workflow_dispatch' }}
26+
cancel-in-progress: true
27+
28+
permissions:
29+
contents: read
30+
31+
jobs:
32+
build-vulkan-windows-msvc:
33+
name: build-vulkan-windows-msvc
34+
uses: pytorch/test-infra/.github/workflows/windows_job.yml@main
35+
with:
36+
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
37+
timeout: 90
38+
script: |
39+
git config --global http.sslBackend openssl
40+
git submodule update --init backends/vulkan/third-party/Vulkan-Headers backends/vulkan/third-party/volk backends/vulkan/third-party/VulkanMemoryAllocator
41+
git submodule update --init
42+
conda init powershell
43+
powershell -Command "& {
44+
Set-PSDebug -Trace 1
45+
\$ErrorActionPreference = 'Stop'
46+
\$PSNativeCommandUseErrorActionPreference = \$true
47+
.ci/scripts/setup-windows-msvc-vulkan.ps1
48+
}"

backends/vulkan/CMakeLists.txt

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,24 @@ set(VULKAN_HEADERS_PATH ${VULKAN_THIRD_PARTY_PATH}/Vulkan-Headers)
4141
set(VOLK_PATH ${VULKAN_THIRD_PARTY_PATH}/volk)
4242
set(VMA_PATH ${VULKAN_THIRD_PARTY_PATH}/VulkanMemoryAllocator)
4343

44+
# These third-party dependencies are git submodules. They are not part of the
45+
# default submodule set checked out by install_executorch.py, so fail early with
46+
# an actionable message rather than a confusing missing-header error.
47+
if(NOT EXISTS "${VOLK_PATH}/volk.c"
48+
OR NOT EXISTS "${VULKAN_HEADERS_PATH}/include/vulkan/vulkan.h"
49+
OR NOT EXISTS "${VMA_PATH}/include/vk_mem_alloc.h"
50+
)
51+
message(
52+
FATAL_ERROR
53+
"The Vulkan backend third-party submodules are missing. "
54+
"Run the following from the repository root:\n"
55+
" git submodule update --init "
56+
"backends/vulkan/third-party/Vulkan-Headers "
57+
"backends/vulkan/third-party/volk "
58+
"backends/vulkan/third-party/VulkanMemoryAllocator"
59+
)
60+
endif()
61+
4462
set(COMMON_INCLUDES
4563
$<BUILD_INTERFACE:${EXECUTORCH_ROOT}/..>
4664
$<BUILD_INTERFACE:${VULKAN_HEADERS_PATH}/include>
@@ -49,7 +67,11 @@ set(COMMON_INCLUDES
4967

5068
# Compile settings
5169

52-
set(VULKAN_CXX_FLAGS "-fexceptions")
70+
# Exceptions are required: the vk_api layer throws on Vulkan errors (see
71+
# vk_api/Exception.h). MSVC does not understand -fexceptions and enables C++
72+
# exceptions via /EHsc instead, so select the flag per compiler.
73+
set(VULKAN_CXX_FLAGS "$<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-fexceptions>")
74+
list(APPEND VULKAN_CXX_FLAGS "$<$<CXX_COMPILER_ID:MSVC>:/EHsc>")
5375
list(APPEND VULKAN_CXX_FLAGS "-DUSE_VULKAN_WRAPPER")
5476
list(APPEND VULKAN_CXX_FLAGS "-DUSE_VULKAN_VOLK")
5577

0 commit comments

Comments
 (0)