Skip to content

Commit 007fc2b

Browse files
pytorchbotJCNTH
andauthored
[ExecuTorch][WebGPU] Shared pipeline + dispatch-grid + vec4-align helpers (op-suite foundation) (#21243)
This PR was created by the merge bot to help merge the original PR into the main branch. ghstack PR number: #20842 by @JCNTH ^ Please use this as the source of truth for the PR details, comments, and reviews ghstack PR base: https://github.com/pytorch/executorch/tree/gh/JCNTH/17/base ghstack PR head: https://github.com/pytorch/executorch/tree/gh/JCNTH/17/head Merge bot PR base: https://github.com/pytorch/executorch/tree/main Merge bot PR head: https://github.com/pytorch/executorch/tree/gh/JCNTH/17/orig @diff-train-skip-merge --------- Co-authored-by: Julian Ng-Thow-Hing <juliannth@meta.com>
1 parent 9df506e commit 007fc2b

102 files changed

Lines changed: 8566 additions & 1162 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

backends/webgpu/CMakeLists.txt

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ function(add_webgpu_native_test test_name test_src)
109109
target_link_libraries(
110110
${test_name}
111111
PRIVATE webgpu_backend
112+
vulkan_schema
112113
${WEBGPU_GPU_LIB}
113114
executorch_core
114115
extension_module_static
@@ -153,10 +154,11 @@ if(EXECUTORCH_BUILD_WEBGPU_TEST)
153154
)
154155

155156
# Device-free util unit test: no backend/Dawn link (pure manifest/tolerance
156-
# helpers), so it does NOT use the native-test helper.
157+
# + dispatch-grid-math helpers), so it does NOT use the native-test helper.
157158
add_executable(
158-
webgpu_op_test_util_test test/op_tests/test_driver_util.cpp
159-
test/op_tests/driver_util.cpp
159+
webgpu_op_test_util_test
160+
test/op_tests/test_driver_util.cpp test/op_tests/driver_util.cpp
161+
test/native/test_webgpu_utils.cpp
160162
)
161163
target_include_directories(
162164
webgpu_op_test_util_test
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
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+
// Pure dispatch-grid math with zero WebGPU/Dawn dependency, so it is
12+
// unit-testable without a WGPUDevice (split out of WebGPUUtils.h, which
13+
// requires <webgpu/webgpu.h> for its device-facing functions).
14+
15+
#include <cmath>
16+
#include <cstdint>
17+
#include <stdexcept>
18+
#include <string>
19+
#include <vector>
20+
21+
namespace executorch::backends::webgpu::utils {
22+
23+
// Ceiling division for non-negative integers (mirrors Vulkan's utils::div_up).
24+
template <typename T>
25+
inline T div_up(T a, T b) {
26+
return (a + b - 1) / b;
27+
}
28+
29+
// Product of a tensor's dims; the same accumulation was duplicated per-op.
30+
inline uint64_t numel(const std::vector<int64_t>& dims) {
31+
uint64_t n = 1;
32+
for (int64_t d : dims) {
33+
if (d < 0) {
34+
throw std::runtime_error("numel: negative dimension");
35+
}
36+
uint64_t ud = static_cast<uint64_t>(d);
37+
if (ud != 0 && n > UINT64_MAX / ud) {
38+
throw std::runtime_error("numel: element count overflow");
39+
}
40+
n *= ud;
41+
}
42+
return n;
43+
}
44+
45+
// Broadcasts a 1- or 2-element int list to (h, w); PyTorch's convention for
46+
// kernel_size/stride/padding/dilation args. Was duplicated as a local `hw`
47+
// lambda in conv2d/conv_transpose2d/max_pool2d.
48+
inline void parse_hw(
49+
const std::vector<int64_t>& v,
50+
uint32_t& h,
51+
uint32_t& w,
52+
const char* op_name,
53+
const char* arg_name) {
54+
if (v.size() == 1) {
55+
h = w = static_cast<uint32_t>(v[0]);
56+
} else if (v.size() == 2) {
57+
h = static_cast<uint32_t>(v[0]);
58+
w = static_cast<uint32_t>(v[1]);
59+
} else {
60+
throw std::runtime_error(
61+
std::string("WebGPU ") + op_name + ": " + arg_name +
62+
" must be 1 or 2 elements");
63+
}
64+
}
65+
66+
// Adaptive 1D->2D dispatch grid. `count_x`/`count_y` are the dispatch dims;
67+
// `stride_x` (= count_x * wg_size) lets the shader decode a flat index as
68+
// `gid.y * stride_x + gid.x`. Used by ops whose element count can exceed the
69+
// 65535 per-dimension ceiling that compute_1d_workgroup_count throws on.
70+
struct DispatchGrid {
71+
uint32_t wg_size;
72+
uint32_t count_x;
73+
uint32_t count_y;
74+
uint32_t stride_x;
75+
};
76+
77+
// Given the workgroup count needed (1D) and the device's per-dimension
78+
// dispatch-count ceiling, compute a near-square 2D grid rather than
79+
// {max_dim, div_up(total, max_dim)} — maxing one dim pads the other with
80+
// mostly-idle workgroups (up to ~2x the needed launch) when total isn't a
81+
// clean multiple of max_dim.
82+
inline DispatchGrid compute_dispatch_grid_from_limits(
83+
uint32_t total, // workgroups needed (1D)
84+
uint32_t wg_size,
85+
uint32_t max_dim,
86+
const char* op_name) {
87+
DispatchGrid g;
88+
g.wg_size = wg_size;
89+
if (total <= max_dim) {
90+
g.count_x = total;
91+
g.count_y = 1;
92+
} else {
93+
uint32_t sq =
94+
static_cast<uint32_t>(std::ceil(std::sqrt(static_cast<double>(total))));
95+
g.count_x = sq < max_dim ? sq : max_dim;
96+
g.count_y = div_up(total, g.count_x);
97+
if (g.count_y >
98+
max_dim) { // > max_dim^2 * wg threads — astronomically large
99+
throw std::runtime_error(
100+
std::string("WebGPU ") + op_name +
101+
": dispatch exceeds 2D grid capacity");
102+
}
103+
}
104+
g.stride_x = g.count_x * g.wg_size;
105+
return g;
106+
}
107+
108+
} // namespace executorch::backends::webgpu::utils

backends/webgpu/runtime/WebGPUGraph.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,7 @@ void WebGPUGraph::build(
418418
value_lists_.resize(num_vals);
419419
doubles_.resize(num_vals, 0.0);
420420
bools_.resize(num_vals, false);
421+
strings_.resize(num_vals);
421422

422423
// Pre-scan the op chain: a constant may be DEFERRED (no eager GPU buffer; the
423424
// prepack node materializes it once) only if it is a prepack source AND never
@@ -625,6 +626,14 @@ void WebGPUGraph::build(
625626
bools_[i] = val->value_as_Bool()->bool_val();
626627
break;
627628
}
629+
case vkgraph::GraphTypes::String: {
630+
value_types_[i] = ValueType::String;
631+
const auto* sv = val->value_as_String()->string_val();
632+
if (sv) {
633+
strings_[i] = sv->str();
634+
}
635+
break;
636+
}
628637
case vkgraph::GraphTypes::SymInt: {
629638
// Live scalar: small Uniform buffer the CPU rewrites per execute.
630639
value_types_[i] = ValueType::SymInt;

backends/webgpu/runtime/WebGPUGraph.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,10 @@ class WebGPUGraph {
152152
bool get_bool(int id) const {
153153
return bools_[id];
154154
}
155+
// String value (e.g. gelu's `approximate` kwarg).
156+
const std::string& get_string(int id) const {
157+
return strings_[id];
158+
}
155159

156160
// Live-scalar (SymInt) API; mirrors the Vulkan SymInt/ParamsBuffer UBO.
157161
// set_symint writes the buffer + marks dirty only if the value changed.
@@ -240,6 +244,20 @@ class WebGPUGraph {
240244
return dispatches_.size() - 1;
241245
}
242246

247+
// 2D sibling of add_dispatch (sets workgroup_count_y); returns the index.
248+
size_t add_dispatch_2d(
249+
WGPUComputePipeline pipeline,
250+
WGPUBindGroup bind_group,
251+
uint32_t count_x,
252+
uint32_t count_y) {
253+
WebGPUDispatch d;
254+
d.pipeline = pipeline;
255+
d.bind_group = bind_group;
256+
d.workgroup_count_x = count_x;
257+
d.workgroup_count_y = count_y;
258+
return add_dispatch(d);
259+
}
260+
243261
// In-graph buffer-to-buffer DMA (e.g. flat copy); returns the dispatch index.
244262
size_t add_buffer_copy(WGPUBuffer src, WGPUBuffer dst, size_t nbytes) {
245263
WebGPUDispatch d;
@@ -376,6 +394,7 @@ class WebGPUGraph {
376394
std::vector<std::vector<int>> value_lists_;
377395
std::vector<double> doubles_;
378396
std::vector<bool> bools_;
397+
std::vector<std::string> strings_;
379398

380399
// SymInt (live scalar): id -> {live Uniform buffer, current value}, sparse.
381400
struct SymIntSlot {

0 commit comments

Comments
 (0)