Skip to content

Commit dc79de3

Browse files
committed
[ExecuTorch][WebGPU] Add per-pass dispatch ordering + scratch buffer tests
Pull Request resolved: #20080 Native unit tests for two runtime enablers: per-pass compute-dispatch ordering (D107543258) and graph-owned scratch buffers (D107543259). `test/native/test_dispatch_order.cpp` exercises multi-dispatch read-after-write ordering through a single `execute()` using dependency chains -- a single-input `add` self-chain and a heterogeneous `rms_norm` -> `add` chain, both lowered via `VulkanPartitioner` -- comparing GPU output to a torch-computed golden per element. `test/native/test_scratch_buffer.cpp` is a white-box test of `WebGPUGraph::create_scratch_buffer` (no black-box consumer exists below the SDPA op): allocation + zero-size guard, copy round-trip, a compute Storage round-trip (its actual use), and a create/destroy lifecycle stress. Authored with assistance from Claude. ghstack-source-id: 390566134 @exported-using-ghexport Differential Revision: [D107576199](https://our.internmc.facebook.com/intern/diff/D107576199/)
1 parent 3b001db commit dc79de3

6 files changed

Lines changed: 615 additions & 0 deletions

File tree

backends/webgpu/CMakeLists.txt

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,4 +144,65 @@ if(EXECUTORCH_BUILD_WEBGPU_TEST)
144144

145145
target_compile_options(webgpu_rms_norm_test PRIVATE -fexceptions)
146146
set_property(TARGET webgpu_rms_norm_test PROPERTY CXX_STANDARD 17)
147+
add_executable(webgpu_dispatch_order_test test/native/test_dispatch_order.cpp)
148+
149+
target_include_directories(
150+
webgpu_dispatch_order_test PRIVATE $<BUILD_INTERFACE:${EXECUTORCH_ROOT}/..>
151+
)
152+
153+
target_link_libraries(
154+
webgpu_dispatch_order_test
155+
PRIVATE webgpu_backend
156+
${WEBGPU_GPU_LIB}
157+
executorch_core
158+
extension_module_static
159+
extension_data_loader
160+
extension_tensor
161+
portable_kernels
162+
portable_ops_lib
163+
)
164+
165+
if(APPLE)
166+
target_link_libraries(
167+
webgpu_dispatch_order_test
168+
PRIVATE "-framework Metal" "-framework QuartzCore"
169+
"-framework CoreGraphics"
170+
)
171+
else()
172+
target_link_libraries(webgpu_dispatch_order_test PRIVATE dl m pthread)
173+
endif()
174+
175+
target_compile_options(webgpu_dispatch_order_test PRIVATE -fexceptions)
176+
set_property(TARGET webgpu_dispatch_order_test PROPERTY CXX_STANDARD 17)
177+
178+
add_executable(webgpu_scratch_buffer_test test/native/test_scratch_buffer.cpp)
179+
180+
target_include_directories(
181+
webgpu_scratch_buffer_test PRIVATE $<BUILD_INTERFACE:${EXECUTORCH_ROOT}/..>
182+
)
183+
184+
target_link_libraries(
185+
webgpu_scratch_buffer_test
186+
PRIVATE webgpu_backend
187+
${WEBGPU_GPU_LIB}
188+
executorch_core
189+
extension_module_static
190+
extension_data_loader
191+
extension_tensor
192+
portable_kernels
193+
portable_ops_lib
194+
)
195+
196+
if(APPLE)
197+
target_link_libraries(
198+
webgpu_scratch_buffer_test
199+
PRIVATE "-framework Metal" "-framework QuartzCore"
200+
"-framework CoreGraphics"
201+
)
202+
else()
203+
target_link_libraries(webgpu_scratch_buffer_test PRIVATE dl m pthread)
204+
endif()
205+
206+
target_compile_options(webgpu_scratch_buffer_test PRIVATE -fexceptions)
207+
set_property(TARGET webgpu_scratch_buffer_test PROPERTY CXX_STANDARD 17)
147208
endif()
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
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+
#include <executorch/backends/webgpu/runtime/WebGPUDevice.h>
10+
#include <executorch/extension/module/module.h>
11+
#include <executorch/extension/tensor/tensor.h>
12+
13+
#include <algorithm>
14+
#include <cmath>
15+
#include <cstdio>
16+
#include <cstdlib>
17+
#include <fstream>
18+
#include <string>
19+
#include <vector>
20+
21+
using namespace executorch::backends::webgpu;
22+
using namespace executorch::extension;
23+
using namespace executorch::runtime;
24+
25+
namespace {
26+
27+
struct Case {
28+
const char* name;
29+
std::vector<int32_t> sizes;
30+
};
31+
32+
// Mirrors _CASES in test_dispatch_order.py (add-chain or rms_norm+add chain).
33+
const std::vector<Case> kCases = {
34+
{"single", {16, 16}},
35+
{"chain3", {64, 64}},
36+
{"chain5_tiny", {1, 1}},
37+
{"chain5_wide", {7, 896}},
38+
{"chain8", {256, 256}},
39+
{"deep32", {128, 128}},
40+
{"large_chain", {1024, 1024}},
41+
{"het_small", {1, 1, 7, 896}},
42+
{"het_deep", {1, 1, 5, 256}},
43+
};
44+
45+
std::vector<float> read_f32_bin(const std::string& path) {
46+
std::ifstream f(path, std::ios::binary | std::ios::ate);
47+
if (!f) {
48+
return {};
49+
}
50+
const size_t bytes =
51+
static_cast<size_t>(f.tellg()) / sizeof(float) * sizeof(float);
52+
f.seekg(0);
53+
std::vector<float> data(bytes / sizeof(float));
54+
f.read(
55+
reinterpret_cast<char*>(data.data()),
56+
static_cast<std::streamsize>(bytes));
57+
return data;
58+
}
59+
60+
bool run_case(const std::string& dir, const Case& tc) {
61+
printf("\n--- dispatch_order[%s] ---\n", tc.name);
62+
const std::string base = dir + "/" + tc.name;
63+
std::vector<float> input = read_f32_bin(base + ".input.bin");
64+
std::vector<float> golden = read_f32_bin(base + ".golden.bin");
65+
if (input.empty() || golden.empty()) {
66+
printf("FAIL: could not read input/golden for %s\n", tc.name);
67+
return false;
68+
}
69+
70+
Module module(base + ".pte");
71+
if (module.load_forward() != Error::Ok) {
72+
printf("FAIL: could not load %s.pte\n", tc.name);
73+
return false;
74+
}
75+
76+
size_t expected = 1;
77+
for (int32_t d : tc.sizes) {
78+
expected *= static_cast<size_t>(d);
79+
}
80+
if (input.size() != expected) {
81+
printf(
82+
"FAIL: input numel %zu != expected %zu for %s\n",
83+
input.size(),
84+
expected,
85+
tc.name);
86+
return false;
87+
}
88+
auto x = make_tensor_ptr(tc.sizes, std::vector<float>(input));
89+
auto result = module.forward({EValue(x)});
90+
if (!result.ok()) {
91+
printf("FAIL: forward failed (error %d)\n", (int)result.error());
92+
return false;
93+
}
94+
const auto& outputs = result.get();
95+
if (outputs.empty() || !outputs[0].isTensor()) {
96+
printf("FAIL: no tensor output\n");
97+
return false;
98+
}
99+
const auto& out_tensor = outputs[0].toTensor();
100+
if (static_cast<size_t>(out_tensor.numel()) != golden.size()) {
101+
printf(
102+
"FAIL: output numel %zu != golden %zu\n",
103+
(size_t)out_tensor.numel(),
104+
golden.size());
105+
return false;
106+
}
107+
const float* out_data = out_tensor.const_data_ptr<float>();
108+
109+
float max_abs_err = 0.0f;
110+
float max_rel_err = 0.0f;
111+
for (size_t i = 0; i < golden.size(); i++) {
112+
const float abs_err = std::abs(out_data[i] - golden[i]);
113+
max_abs_err = std::max(max_abs_err, abs_err);
114+
const float denom = std::max(std::abs(golden[i]), 1e-6f);
115+
max_rel_err = std::max(max_rel_err, abs_err / denom);
116+
}
117+
printf(
118+
"Max abs error: %e Max rel error: %e (%zu elements)\n",
119+
max_abs_err,
120+
max_rel_err,
121+
golden.size());
122+
// Lenient gate: pass iff abs<=tol OR rel<=tol (near-zero goldens).
123+
if (max_abs_err > 1e-3f && max_rel_err > 1e-3f) {
124+
printf("FAIL: dispatch_order[%s] exceeds tolerance 1e-3\n", tc.name);
125+
return false;
126+
}
127+
printf("PASS: dispatch_order[%s]\n", tc.name);
128+
return true;
129+
}
130+
131+
} // namespace
132+
133+
int main(int argc, char** argv) {
134+
std::string dir = "/tmp/dispatch_order";
135+
if (argc > 1) {
136+
dir = argv[1];
137+
}
138+
if (const char* env = std::getenv("WEBGPU_DISPATCH_ORDER_DIR")) {
139+
dir = env;
140+
}
141+
142+
WebGPUContext ctx;
143+
try {
144+
ctx = create_webgpu_context();
145+
} catch (const std::exception& e) {
146+
printf("SKIP: %s\n", e.what());
147+
return 0;
148+
}
149+
set_default_webgpu_context(&ctx);
150+
printf("WebGPU device acquired (native); case dir: %s\n", dir.c_str());
151+
152+
bool ok = true;
153+
for (const auto& tc : kCases) {
154+
ok = run_case(dir, tc) && ok;
155+
}
156+
157+
set_default_webgpu_context(nullptr);
158+
destroy_webgpu_context(ctx);
159+
160+
if (!ok) {
161+
return 1;
162+
}
163+
printf("\nAll dispatch_order tests passed\n");
164+
return 0;
165+
}

0 commit comments

Comments
 (0)