Skip to content

Commit 07d559f

Browse files
committed
[ExecuTorch][WebGPU] Switch native backend from wgpu-native to Dawn (Tint) + SwiftShader
Pull Request resolved: #20079 Make Dawn (Chrome's WebGPU implementation, whose WGSL compiler Tint is the spec reference) running on SwiftShader the sole native WebGPU backend, replacing wgpu-native (naga), so the op tests run on a spec-faithful, headless, deterministic CLI backend. The `WEBGPU_IMPL` cache variable, the wgpu-native CMake branch, and the `WEBGPU_IMPL_DAWN` compile define are removed -- CMake now unconditionally `find_package(Dawn REQUIRED)` and links `dawn::webgpu_dawn`. `WebGPUCompat.h` drives pending callbacks via Dawn's `wgpuInstanceProcessEvents` on native and yields to the JS event loop under Emscripten. Dawn is vendored with NO new S3 artifact: `oss/.ci/scripts/setup-webgpu-linux-deps.sh` downloads Google's official `ubuntu-latest-Release` prebuilt directly from github.com/google/dawn/releases (pinned tag + sha256, the same pattern as `setup-wgpu-native.sh`), and reuses the SwiftShader prebuilt already on the ossci bucket. The release exports `dawn::webgpu_dawn` (a static lib) which drops into the existing `find_package(Dawn)`. It has no bundled SwiftShader, so `WebGPUDevice.cpp` requests a normal Vulkan adapter (`forceFallbackAdapter=false`) and `VK_ICD_FILENAMES` makes SwiftShader the only device. The release is built with a recent GCC, so the deps script also pulls a current libstdc++ from the `ubuntu-toolchain-r` PPA (its lib references `_M_replace_cold`, a GCC 13+ symbol) plus `libvulkan1` (Dawn dlopens the Vulkan loader) -- all scoped to the WebGPU CI job, backward-compatible, no repo-wide impact. Authored with assistance from Claude. ghstack-source-id: 390671995 @exported-using-ghexport Differential Revision: [D107589774](https://our.internmc.facebook.com/intern/diff/D107589774/)
1 parent b21a64d commit 07d559f

10 files changed

Lines changed: 333 additions & 161 deletions

File tree

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

Lines changed: 69 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,26 +5,79 @@
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+
# Vendor Dawn (Tint) + SwiftShader for the WebGPU backend CI WITHOUT hosting a
9+
# private prebuilt:
10+
# * Dawn : Google's official nightly prebuilt, downloaded directly from
11+
# github.com/google/dawn/releases (pinned tag+rev+sha256) -- the same
12+
# "fetch a pinned upstream prebuilt" pattern used for other CI deps.
13+
# * SwiftShader : reuse the prebuilt ALREADY on the ossci-android bucket (the one
14+
# setup-vulkan-linux-deps.sh uses). No new S3 uploads anywhere.
15+
# Dawn (Chrome's WebGPU impl; its WGSL compiler Tint is the spec reference) on
16+
# SwiftShader gives a headless, deterministic, spec-faithful CLI backend.
17+
#
18+
# Exports Dawn_DIR / VK_ICD_FILENAMES / LD_LIBRARY_PATH for the cmake build+run.
19+
# Local/rig override: set DAWN_PREBUILT_DIR=<dir containing lib64/cmake/Dawn> to
20+
# skip the Dawn download.
821
set -ex
922

10-
# SwiftShader: software Vulkan adapter for GPU-less CI (LunarG SDK not needed).
11-
install_swiftshader() {
12-
_https_amazon_aws=https://ossci-android.s3.amazonaws.com
13-
_swiftshader_archive=swiftshader-abe07b943-prebuilt.tar.gz
14-
_swiftshader_dir=/tmp/swiftshader
15-
mkdir -p $_swiftshader_dir
23+
# --- pinned versions (bump rev+sha together when upgrading Dawn) --------------
24+
DAWN_TAG="${DAWN_TAG:-v20260423.175430}"
25+
DAWN_REV="${DAWN_REV:-31e25af254ab572c77054edec4946d2244e184dd}"
26+
DAWN_SHA256="${DAWN_SHA256:-ac76fac090162dc1ecea5ed0f28a557bb8f49efc47faab01886105ace82b7b64}"
27+
SWIFTSHADER_ARCHIVE="${SWIFTSHADER_ARCHIVE:-swiftshader-abe07b943-prebuilt.tar.gz}"
1628

17-
_tmp_archive="/tmp/${_swiftshader_archive}"
29+
_dawn_dir="${DAWN_PREBUILT_DIR:-/tmp/dawn-ci}"
30+
_ss_dir=/tmp/swiftshader
1831

19-
curl --silent --show-error --location --fail --retry 3 --retry-all-errors \
20-
--output "${_tmp_archive}" "$_https_amazon_aws/${_swiftshader_archive}"
32+
# --- toolchain prereqs --------------------------------------------------------
33+
# Dawn dlopens the system Vulkan loader at runtime (libvulkan1). And the
34+
# ubuntu-latest prebuilt is built with a bleeding-edge GCC: it references
35+
# libstdc++ symbols newer than ubuntu-22.04's default (e.g. _M_replace_cold,
36+
# GCC 13+), so the static .a won't link against the stock runtime. Pull a current
37+
# libstdc++ from the ubuntu-toolchain-r PPA when the symbol floor isn't met. All
38+
# of this is scoped to the WebGPU CI job; newer libstdc++ is backward-compatible.
39+
if command -v apt-get >/dev/null 2>&1; then
40+
_SUDO=""; command -v sudo >/dev/null 2>&1 && _SUDO="sudo"
41+
${_SUDO} apt-get update -y || true
42+
${_SUDO} apt-get install -y libvulkan1 software-properties-common || true
43+
if ! strings /usr/lib/x86_64-linux-gnu/libstdc++.so.6 2>/dev/null \
44+
| grep -q "GLIBCXX_3.4.32"; then
45+
${_SUDO} add-apt-repository -y ppa:ubuntu-toolchain-r/test || true
46+
${_SUDO} apt-get update -y || true
47+
${_SUDO} apt-get install -y libstdc++6 || true # newest GCC runtime
48+
fi
49+
fi
2150

22-
tar -C "${_swiftshader_dir}" -xzf "${_tmp_archive}"
51+
# The native binaries / pybind lib run INSIDE the CI conda env, whose libstdc++
52+
# predates GLIBCXX_3.4.32 (the Dawn prebuilt's floor) -- the same wall ssjia hit
53+
# for the vulkan op tests. Upgrade the conda runtime libstdc++ so the loaded
54+
# libstdc++.so.6 (conda's, not the system one) satisfies Dawn at run time.
55+
if command -v conda >/dev/null 2>&1; then
56+
conda install -y -c conda-forge "libstdcxx-ng>=14" || true
57+
fi
2358

24-
export VK_ICD_FILENAMES="${_swiftshader_dir}/swiftshader/build/Linux/vk_swiftshader_icd.json"
25-
export LD_LIBRARY_PATH="${_swiftshader_dir}/swiftshader/build/Linux/:${LD_LIBRARY_PATH}"
26-
export ETVK_USING_SWIFTSHADER=1
27-
}
59+
# --- Dawn: official prebuilt from GitHub (no S3) ------------------------------
60+
mkdir -p "${_dawn_dir}"
61+
if [[ ! -d "${_dawn_dir}/lib64/cmake/Dawn" ]]; then
62+
_dawn_tar="/tmp/Dawn-${DAWN_REV}-ubuntu-latest-Release.tar.gz"
63+
curl --silent --show-error --location --fail --retry 3 --retry-all-errors \
64+
--output "${_dawn_tar}" \
65+
"https://github.com/google/dawn/releases/download/${DAWN_TAG}/Dawn-${DAWN_REV}-ubuntu-latest-Release.tar.gz"
66+
echo "${DAWN_SHA256} ${_dawn_tar}" | sha256sum -c -
67+
# archive top dir is Dawn-<rev>-ubuntu-latest-Release/{lib64,include,bin}
68+
tar -C "${_dawn_dir}" --strip-components=1 -xzf "${_dawn_tar}"
69+
fi
70+
71+
# --- SwiftShader: reuse the existing ossci prebuilt (no new upload) -----------
72+
if [[ ! -f "${_ss_dir}/swiftshader/build/Linux/vk_swiftshader_icd.json" ]]; then
73+
_ss_aws=https://ossci-android.s3.amazonaws.com
74+
mkdir -p "${_ss_dir}"
75+
curl --silent --show-error --location --fail --retry 3 --retry-all-errors \
76+
--output "/tmp/${SWIFTSHADER_ARCHIVE}" "${_ss_aws}/${SWIFTSHADER_ARCHIVE}"
77+
tar -C "${_ss_dir}" -xzf "/tmp/${SWIFTSHADER_ARCHIVE}"
78+
fi
2879

29-
install_swiftshader
30-
bash backends/webgpu/scripts/setup-wgpu-native.sh
80+
export Dawn_DIR="${_dawn_dir}/lib64/cmake/Dawn"
81+
export VK_ICD_FILENAMES="${_ss_dir}/swiftshader/build/Linux/vk_swiftshader_icd.json"
82+
export LD_LIBRARY_PATH="${_ss_dir}/swiftshader/build/Linux/:${LD_LIBRARY_PATH:-}"
83+
export WEBGPU_USING_SWIFTSHADER=1

.ci/scripts/test_backend.sh

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,10 @@ if [[ "$FLOW" == *vulkan* ]]; then
5858
fi
5959

6060
if [[ "$FLOW" == *webgpu* ]]; then
61-
# Setup swiftshader (software Vulkan adapter for GPU-less runners) and wgpu-native,
62-
# which are required to build and run the WebGPU delegate.
61+
# Dawn (Tint) + SwiftShader, the spec-faithful headless WebGPU backend.
6362
source .ci/scripts/setup-webgpu-linux-deps.sh
6463

65-
EXTRA_BUILD_ARGS+=" -DEXECUTORCH_BUILD_WEBGPU=ON"
64+
EXTRA_BUILD_ARGS+=" -DEXECUTORCH_BUILD_WEBGPU=ON -DDawn_DIR=$Dawn_DIR"
6665
fi
6766

6867
if [[ "$FLOW" == *arm* ]]; then
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
name: Test WebGPU Native (Dawn)
2+
3+
# The substantive WebGPU op-coverage gate. The shared operators suite only
4+
# delegates add.Tensor to WebGPU (everything else is CPU fallback), so the real
5+
# Dawn coverage comes from the native test executables (rms_norm, multi-dispatch
6+
# ordering, scratch). This runs them on Dawn (Tint) + SwiftShader, headless, on a
7+
# CPU runner -- separate from _test_backend.yml so that reusable template stays
8+
# untouched.
9+
10+
on:
11+
schedule:
12+
- cron: 0 2 * * *
13+
push:
14+
branches:
15+
- main
16+
- release/*
17+
tags:
18+
- ciflow/nightly/*
19+
pull_request:
20+
workflow_dispatch:
21+
22+
concurrency:
23+
group: ${{ github.workflow }}--${{ github.event.pull_request.number || github.sha }}-${{ github.event_name == 'workflow_dispatch' }}
24+
cancel-in-progress: true
25+
26+
jobs:
27+
test-webgpu-native:
28+
uses: pytorch/test-infra/.github/workflows/linux_job_v2.yml@main
29+
with:
30+
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
31+
runner: linux.4xlarge.memory
32+
docker-image: ci-image:executorch-ubuntu-22.04-clang12
33+
submodules: recursive
34+
timeout: 120
35+
script: |
36+
set -eux
37+
38+
# The generic Linux job uses the base conda env, not the image's; activate
39+
# the image env (it has the pinned from-source torch). Mirrors
40+
# test-vulkan-operators-linux in pull.yml.
41+
CONDA_ENV=$(conda env list --json | jq -r ".envs | .[-1]")
42+
conda activate "${CONDA_ENV}"
43+
44+
# Install the python package + runtime deps (the .pte exporters).
45+
PYTHON_EXECUTABLE=python bash .ci/scripts/setup-linux.sh --build-tool cmake
46+
47+
# Vendor Dawn (Tint) + SwiftShader, then build + run the native executables.
48+
source .ci/scripts/setup-webgpu-linux-deps.sh
49+
bash backends/webgpu/scripts/test_webgpu_native_ci.sh

backends/webgpu/CMakeLists.txt

Lines changed: 24 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -54,29 +54,14 @@ target_include_directories(
5454

5555
target_link_libraries(webgpu_backend PRIVATE vulkan_schema executorch_core)
5656

57-
# Native build: link against wgpu-native
58-
set(WGPU_NATIVE_DIR
59-
"${CMAKE_CURRENT_SOURCE_DIR}/third-party/wgpu-native"
60-
CACHE PATH "Path to wgpu-native installation"
61-
)
62-
63-
# Link the shared lib; the static .a carries LLVM bitcode that breaks LTO.
64-
# Suffix resolves per platform: .so on Linux, .dylib on macOS.
65-
set(WGPU_LIB_NAME "libwgpu_native${CMAKE_SHARED_LIBRARY_SUFFIX}")
66-
set(WGPU_LIB "${WGPU_NATIVE_DIR}/lib/${WGPU_LIB_NAME}")
67-
if(NOT EXISTS "${WGPU_LIB}")
68-
message(FATAL_ERROR "wgpu-native not found at ${WGPU_NATIVE_DIR}. "
69-
"Run: bash backends/webgpu/scripts/setup-wgpu-native.sh"
70-
)
71-
endif()
72-
73-
add_library(wgpu_native SHARED IMPORTED)
74-
set_target_properties(wgpu_native PROPERTIES IMPORTED_LOCATION "${WGPU_LIB}")
75-
76-
target_include_directories(
77-
webgpu_backend PUBLIC $<BUILD_INTERFACE:${WGPU_NATIVE_DIR}/include>
78-
)
79-
target_link_libraries(webgpu_backend PRIVATE wgpu_native)
57+
# Native WebGPU backend: Dawn (Tint) + SwiftShader; deps script sets Dawn_DIR.
58+
# Native-only: browser/Emscripten builds use the system webgpu.h and never reach
59+
# this find_package (root CMake gates it via EXECUTORCH_BUILD_WEBGPU).
60+
# dawn::webgpu_dawn's link interface references Threads::Threads.
61+
find_package(Threads REQUIRED)
62+
find_package(Dawn REQUIRED)
63+
set(WEBGPU_GPU_LIB dawn::webgpu_dawn)
64+
target_link_libraries(webgpu_backend PUBLIC ${WEBGPU_GPU_LIB})
8065

8166
if(APPLE)
8267
target_link_libraries(
@@ -100,67 +85,37 @@ install(
10085
DESTINATION ${CMAKE_INSTALL_LIBDIR}
10186
)
10287

103-
# Native test target
104-
if(EXECUTORCH_BUILD_WEBGPU_TEST)
105-
add_executable(webgpu_native_test test/test_webgpu_native.cpp)
106-
107-
target_include_directories(
108-
webgpu_native_test PRIVATE $<BUILD_INTERFACE:${EXECUTORCH_ROOT}/..>
109-
"${WGPU_NATIVE_DIR}/include"
110-
)
111-
112-
target_link_libraries(
113-
webgpu_native_test
114-
PRIVATE webgpu_backend
115-
wgpu_native
116-
executorch_core
117-
extension_module_static
118-
extension_data_loader
119-
extension_tensor
120-
portable_kernels
121-
portable_ops_lib
122-
)
123-
124-
if(APPLE)
125-
target_link_libraries(
126-
webgpu_native_test PRIVATE "-framework Metal" "-framework QuartzCore"
127-
"-framework CoreGraphics"
128-
)
129-
else()
130-
target_link_libraries(webgpu_native_test PRIVATE dl m pthread)
131-
endif()
132-
133-
target_compile_options(webgpu_native_test PRIVATE -fexceptions)
134-
set_property(TARGET webgpu_native_test PROPERTY CXX_STANDARD 17)
135-
136-
add_executable(webgpu_rms_norm_test test/native/test_rms_norm.cpp)
137-
88+
# Native test targets. Helper mirrors backends/vulkan's vulkan_op_test: every
89+
# test executable links the same backend + runtime libs.
90+
function(add_webgpu_native_test test_name test_src)
91+
add_executable(${test_name} ${test_src})
13892
target_include_directories(
139-
webgpu_rms_norm_test PRIVATE $<BUILD_INTERFACE:${EXECUTORCH_ROOT}/..>
140-
"${WGPU_NATIVE_DIR}/include"
93+
${test_name} PRIVATE $<BUILD_INTERFACE:${EXECUTORCH_ROOT}/..>
14194
)
142-
14395
target_link_libraries(
144-
webgpu_rms_norm_test
96+
${test_name}
14597
PRIVATE webgpu_backend
146-
wgpu_native
98+
${WEBGPU_GPU_LIB}
14799
executorch_core
148100
extension_module_static
149101
extension_data_loader
150102
extension_tensor
151103
portable_kernels
152104
portable_ops_lib
153105
)
154-
155106
if(APPLE)
156107
target_link_libraries(
157-
webgpu_rms_norm_test PRIVATE "-framework Metal" "-framework QuartzCore"
158-
"-framework CoreGraphics"
108+
${test_name} PRIVATE "-framework Metal" "-framework QuartzCore"
109+
"-framework CoreGraphics"
159110
)
160111
else()
161-
target_link_libraries(webgpu_rms_norm_test PRIVATE dl m pthread)
112+
target_link_libraries(${test_name} PRIVATE dl m pthread)
162113
endif()
114+
target_compile_options(${test_name} PRIVATE -fexceptions)
115+
set_property(TARGET ${test_name} PROPERTY CXX_STANDARD 17)
116+
endfunction()
163117

164-
target_compile_options(webgpu_rms_norm_test PRIVATE -fexceptions)
165-
set_property(TARGET webgpu_rms_norm_test PROPERTY CXX_STANDARD 17)
118+
if(EXECUTORCH_BUILD_WEBGPU_TEST)
119+
add_webgpu_native_test(webgpu_native_test test/test_webgpu_native.cpp)
120+
add_webgpu_native_test(webgpu_rms_norm_test test/native/test_rms_norm.cpp)
166121
endif()
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
#pragma once
10+
11+
#include <webgpu/webgpu.h>
12+
13+
#if defined(__EMSCRIPTEN__)
14+
#include <emscripten/emscripten.h>
15+
#else
16+
#include <chrono>
17+
#include <thread>
18+
#endif
19+
20+
namespace executorch::backends::webgpu {
21+
22+
// Make progress on pending WebGPU callbacks; callers loop until their done flag
23+
// is set. Native (Dawn): pump the event queue + brief yield (no busy-spin).
24+
// Browser: yield to the JS event loop.
25+
inline void webgpu_poll(WGPUInstance instance) {
26+
#if defined(__EMSCRIPTEN__)
27+
(void)instance;
28+
emscripten_sleep(0);
29+
#else
30+
wgpuInstanceProcessEvents(instance);
31+
std::this_thread::sleep_for(std::chrono::microseconds(50));
32+
#endif
33+
}
34+
35+
} // namespace executorch::backends::webgpu

backends/webgpu/runtime/WebGPUDevice.cpp

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
* LICENSE file in the root directory of this source tree.
77
*/
88

9+
#include <executorch/backends/webgpu/runtime/WebGPUCompat.h>
910
#include <executorch/backends/webgpu/runtime/WebGPUDevice.h>
1011

1112
#include <cstdio>
@@ -101,9 +102,18 @@ WebGPUContext create_webgpu_context() {
101102
adapter_cb.callback = on_adapter_request;
102103
adapter_cb.userdata1 = &adapter_result;
103104

104-
wgpuInstanceRequestAdapter(ctx.instance, nullptr, adapter_cb);
105+
// Release Dawn has no bundled fallback adapter; pick the platform backend
106+
// (Metal on Apple, Vulkan elsewhere -- SwiftShader via VK_ICD_FILENAMES).
107+
WGPURequestAdapterOptions adapter_opts = {};
108+
#if defined(__APPLE__)
109+
adapter_opts.backendType = WGPUBackendType_Metal;
110+
#else
111+
adapter_opts.backendType = WGPUBackendType_Vulkan;
112+
#endif
113+
adapter_opts.forceFallbackAdapter = false;
114+
wgpuInstanceRequestAdapter(ctx.instance, &adapter_opts, adapter_cb);
105115
while (!adapter_result.done) {
106-
wgpuInstanceProcessEvents(ctx.instance);
116+
webgpu_poll(ctx.instance);
107117
}
108118

109119
if (!adapter_result.adapter) {
@@ -133,7 +143,7 @@ WebGPUContext create_webgpu_context() {
133143

134144
wgpuAdapterRequestDevice(ctx.adapter, &device_desc, device_cb);
135145
while (!device_result.done) {
136-
wgpuInstanceProcessEvents(ctx.instance);
146+
webgpu_poll(ctx.instance);
137147
}
138148

139149
if (!device_result.device) {

backends/webgpu/runtime/WebGPUGraph.cpp

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
#include <executorch/backends/vulkan/serialization/schema_generated.h>
1313
#include <executorch/runtime/core/named_data_map.h>
1414

15+
#include <executorch/backends/webgpu/runtime/WebGPUCompat.h>
1516
#include <executorch/backends/webgpu/runtime/WebGPUDevice.h>
16-
#include <webgpu/wgpu.h>
1717

1818
#include <cstring>
1919
#include <stdexcept>
@@ -510,7 +510,17 @@ void WebGPUGraph::copy_outputs(std::vector<std::pair<void*, size_t>>& outputs) {
510510
cb_info);
511511
}
512512

513-
wgpuDevicePoll(device_, true, nullptr);
513+
bool all_mapped = false;
514+
while (!all_mapped) {
515+
webgpu_poll(instance_);
516+
all_mapped = true;
517+
for (size_t i = 0; i < count; i++) {
518+
if (outputs[i].second != 0 && !cb_data[i].done) {
519+
all_mapped = false;
520+
break;
521+
}
522+
}
523+
}
514524

515525
for (size_t i = 0; i < count; i++) {
516526
if (outputs[i].second == 0) {

0 commit comments

Comments
 (0)