Skip to content

Commit d66a2a3

Browse files
committed
[ExecuTorch][WebGPU] Fix CI: link shared wgpu-native to avoid LTO bitcode clash
Pull Request resolved: pytorch#20036 The "Test WebGPU Backend" CI job fails on every PR while building the editable executorch wheel. The pybind _portable_lib thin-LTO link aborts with `LLVM gold plugin has failed to create LTO module: Invalid value (Producer: 'LLVM21.1.3-rust-1.92.0-stable' Reader: 'LLVM 12.0.1')`. Root cause: with `-DEXECUTORCH_BUILD_WEBGPU=ON` the backend static-links the prebuilt Rust `libwgpu_native.a`, whose objects embed LLVM-21 `.llvmbc` bitcode (377 of 487 archive members), into the thin-LTO'd pybind extension that pybind11 builds with the CI image's clang-12. LLVM-12's gold plugin cannot parse LLVM-21 bitcode, so the link fails before any test runs. Fix: link the prebuilt shared `libwgpu_native.so` (already downloaded by `setup-wgpu-native.sh`) instead of the static `.a`. The `.so` carries no `.llvmbc`, so no foreign bitcode enters the LTO link. This mirrors how the QNN/CoreML/OpenVINO backends consume prebuilt native deps (shared, runtime-loaded) rather than static-linking foreign objects. Also flips the setup-script idempotency check to the `.so`. Authored with Claude Code. ghstack-source-id: 390126987 @exported-using-ghexport Differential Revision: [D107539304](https://our.internmc.facebook.com/intern/diff/D107539304/)
1 parent 913ada6 commit d66a2a3

5 files changed

Lines changed: 77 additions & 20 deletions

File tree

‎backends/test/suite/flows/webgpu.py‎

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,23 @@ def _create_webgpu_flow() -> TestFlow:
1313
"webgpu",
1414
backend="webgpu",
1515
tester_factory=WebGPUTester,
16-
skip_patterns=["float16", "float64"], # Not supported in swiftshader
16+
skip_patterns=[
17+
"float16",
18+
"float64", # Not supported in swiftshader
19+
# WebGPU add is elementwise-only; broadcasting add.Tensor unsupported.
20+
"bcast_first",
21+
"bcast_second",
22+
"hardswish",
23+
"lstm_batch_sizes",
24+
"upsample_nearest2d",
25+
# torchvision models with broadcasting adds; resnet50 covers wide.
26+
"mobilenet_v3_small",
27+
"shufflenet_v2_x1_0",
28+
"resnet50",
29+
"vit_b_16",
30+
"swin_v2_t",
31+
"convnext_small",
32+
],
1733
)
1834

1935

‎backends/webgpu/CMakeLists.txt‎

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,17 +49,18 @@ set(WGPU_NATIVE_DIR
4949
CACHE PATH "Path to wgpu-native installation"
5050
)
5151

52-
if(NOT EXISTS "${WGPU_NATIVE_DIR}/lib/libwgpu_native.a")
52+
# Link the shared lib; the static .a carries LLVM bitcode that breaks LTO.
53+
# Suffix resolves per platform: .so on Linux, .dylib on macOS.
54+
set(WGPU_LIB_NAME "libwgpu_native${CMAKE_SHARED_LIBRARY_SUFFIX}")
55+
set(WGPU_LIB "${WGPU_NATIVE_DIR}/lib/${WGPU_LIB_NAME}")
56+
if(NOT EXISTS "${WGPU_LIB}")
5357
message(FATAL_ERROR "wgpu-native not found at ${WGPU_NATIVE_DIR}. "
5458
"Run: bash backends/webgpu/scripts/setup-wgpu-native.sh"
5559
)
5660
endif()
5761

58-
add_library(wgpu_native STATIC IMPORTED)
59-
set_target_properties(
60-
wgpu_native PROPERTIES IMPORTED_LOCATION
61-
"${WGPU_NATIVE_DIR}/lib/libwgpu_native.a"
62-
)
62+
add_library(wgpu_native SHARED IMPORTED)
63+
set_target_properties(wgpu_native PROPERTIES IMPORTED_LOCATION "${WGPU_LIB}")
6364

6465
target_include_directories(
6566
webgpu_backend PUBLIC $<BUILD_INTERFACE:${WGPU_NATIVE_DIR}/include>

‎backends/webgpu/runtime/WebGPUDevice.cpp‎

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
#include <cstdio>
1212
#include <cstdlib>
13+
#include <memory>
1314
#include <stdexcept>
1415

1516
namespace executorch {
@@ -157,7 +158,30 @@ void set_default_webgpu_context(WebGPUContext* ctx) {
157158
}
158159

159160
WebGPUContext* get_default_webgpu_context() {
160-
return g_default_context;
161+
if (g_default_context) {
162+
return g_default_context;
163+
}
164+
#if !defined(__EMSCRIPTEN__)
165+
// Native-only lazy process-wide context, mirroring Vulkan api::context().
166+
static const std::unique_ptr<WebGPUContext, void (*)(WebGPUContext*)>
167+
lazy_context(
168+
[]() -> WebGPUContext* {
169+
try {
170+
return new WebGPUContext(create_webgpu_context());
171+
} catch (...) {
172+
return nullptr;
173+
}
174+
}(),
175+
[](WebGPUContext* c) {
176+
if (c) {
177+
destroy_webgpu_context(*c);
178+
delete c;
179+
}
180+
});
181+
return lazy_context.get();
182+
#else
183+
return nullptr;
184+
#endif
161185
}
162186

163187
void destroy_webgpu_context(WebGPUContext& ctx) {

‎backends/webgpu/scripts/setup-wgpu-native.sh‎

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,23 +16,23 @@ WGPU_DIR="${SCRIPT_DIR}/../third-party/wgpu-native"
1616
WGPU_VERSION="v27.0.4.0"
1717
WGPU_BASE_URL="https://github.com/gfx-rs/wgpu-native/releases/download/${WGPU_VERSION}"
1818

19-
if [[ -f "${WGPU_DIR}/lib/libwgpu_native.a" ]]; then
20-
echo "wgpu-native already installed at ${WGPU_DIR}"
21-
exit 0
22-
fi
23-
2419
OS="$(uname -s)"
25-
ARCH="$(uname -m)"
26-
2720
case "${OS}" in
28-
Darwin) PLATFORM="macos" ;;
29-
Linux) PLATFORM="linux" ;;
21+
Darwin) PLATFORM="macos"; LIB_EXT="dylib" ;;
22+
Linux) PLATFORM="linux"; LIB_EXT="so" ;;
3023
*)
3124
echo "Unsupported OS: ${OS}"
3225
exit 1
3326
;;
3427
esac
3528

29+
if [[ -f "${WGPU_DIR}/lib/libwgpu_native.${LIB_EXT}" ]]; then
30+
echo "wgpu-native already installed at ${WGPU_DIR}"
31+
exit 0
32+
fi
33+
34+
ARCH="$(uname -m)"
35+
3636
case "${ARCH}" in
3737
x86_64) WGPU_ARCH="x86_64" ;;
3838
aarch64|arm64) WGPU_ARCH="aarch64" ;;

‎backends/webgpu/test/tester.py‎

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,24 @@
1515
from executorch.backends.vulkan.partitioner.vulkan_partitioner import VulkanPartitioner
1616
from executorch.exir import EdgeCompileConfig
1717
from executorch.exir.backend.partitioner import Partitioner
18+
from executorch.exir.dialects._ops import ops as exir_ops
1819

20+
# Edge ops the WebGPU runtime implements; restricts the Vulkan partitioner.
21+
WEBGPU_SUPPORTED_OPS = [
22+
exir_ops.edge.aten.add.Tensor,
23+
]
1924

20-
# Lowers via VulkanPartitioner (WebGPU consumes the Vulkan VK00 serialization).
25+
26+
# Lowers via VulkanPartitioner (WebGPU consumes the Vulkan VK00 serialization),
27+
# restricted to the ops the WebGPU runtime implements.
2128
class Partition(BaseStages.Partition):
2229
def __init__(self, partitioner: Optional[Partitioner] = None):
2330
super().__init__(
24-
partitioner=partitioner or VulkanPartitioner({"skip_bool_tensors": True}),
31+
partitioner=partitioner
32+
or VulkanPartitioner(
33+
{"skip_bool_tensors": True},
34+
operator_allowlist=WEBGPU_SUPPORTED_OPS,
35+
),
2536
)
2637

2738

@@ -32,7 +43,12 @@ def __init__(
3243
edge_compile_config: Optional[EdgeCompileConfig] = None,
3344
):
3445
if partitioners is None:
35-
partitioners = [VulkanPartitioner({"skip_bool_tensors": True})]
46+
partitioners = [
47+
VulkanPartitioner(
48+
{"skip_bool_tensors": True},
49+
operator_allowlist=WEBGPU_SUPPORTED_OPS,
50+
)
51+
]
3652

3753
super().__init__(
3854
default_partitioner_cls=VulkanPartitioner,

0 commit comments

Comments
 (0)