Skip to content

Commit d595e01

Browse files
authored
[ExecuTorch][WebGPU] GPU timestamp query profiling (general implementation)
Differential Revision: D108188287 Pull Request resolved: #20201
1 parent cdcb59a commit d595e01

8 files changed

Lines changed: 562 additions & 2 deletions

File tree

backends/webgpu/CMakeLists.txt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ set(WEBGPU_SRCS
3030
runtime/WebGPUGraph.cpp
3131
runtime/WebGPUDelegateHeader.cpp
3232
runtime/WebGPUDevice.cpp
33+
runtime/WebGPUQueryPool.cpp
3334
runtime/ops/OperatorRegistry.cpp
3435
runtime/ops/add/BinaryOp.cpp
3536
runtime/ops/rms_norm/RmsNorm.cpp
@@ -76,6 +77,17 @@ endif()
7677

7778
target_compile_options(webgpu_backend PRIVATE -fexceptions)
7879

80+
# Opt-in GPU timestamp profiling (WebGPUQueryPool); OFF so production builds
81+
# request no TimestampQuery device feature. Mirrors Vulkan's compile-flag gate.
82+
option(EXECUTORCH_BUILD_WEBGPU_PROFILING
83+
"Enable WebGPU GPU timestamp-query profiling" OFF
84+
)
85+
if(EXECUTORCH_BUILD_WEBGPU_PROFILING)
86+
target_compile_definitions(
87+
webgpu_backend PRIVATE WGPU_BACKEND_ENABLE_PROFILING
88+
)
89+
endif()
90+
7991
# Link with --whole-archive for static registration of backend + ops
8092
executorch_target_link_options_shared_lib(webgpu_backend)
8193

@@ -114,6 +126,11 @@ function(add_webgpu_native_test test_name test_src)
114126
target_link_libraries(${test_name} PRIVATE dl m pthread)
115127
endif()
116128
target_compile_options(${test_name} PRIVATE -fexceptions)
129+
if(EXECUTORCH_BUILD_WEBGPU_PROFILING)
130+
target_compile_definitions(
131+
${test_name} PRIVATE WGPU_BACKEND_ENABLE_PROFILING
132+
)
133+
endif()
117134
set_property(TARGET ${test_name} PROPERTY CXX_STANDARD 17)
118135
endfunction()
119136

backends/webgpu/runtime/WebGPUDevice.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
#include <cstdlib>
1414
#include <memory>
1515
#include <stdexcept>
16+
#ifdef WGPU_BACKEND_ENABLE_PROFILING
17+
#include <vector>
18+
#endif // WGPU_BACKEND_ENABLE_PROFILING
1619

1720
namespace executorch {
1821
namespace backends {
@@ -137,6 +140,18 @@ WebGPUContext create_webgpu_context() {
137140
WGPUStatus_Success) {
138141
device_desc.requiredLimits = &supported_limits;
139142
}
143+
144+
#ifdef WGPU_BACKEND_ENABLE_PROFILING
145+
// Bench: enable TimestampQuery if available; fail-open (skip timing if not).
146+
std::vector<WGPUFeatureName> required_features;
147+
if (wgpuAdapterHasFeature(ctx.adapter, WGPUFeatureName_TimestampQuery)) {
148+
required_features.push_back(WGPUFeatureName_TimestampQuery);
149+
device_desc.requiredFeatureCount = required_features.size();
150+
device_desc.requiredFeatures = required_features.data();
151+
ctx.timestamp_supported = true;
152+
}
153+
#endif // WGPU_BACKEND_ENABLE_PROFILING
154+
140155
device_desc.uncapturedErrorCallbackInfo.callback = on_device_error;
141156

142157
WGPUWaitStatus device_wait = webgpu_wait(
@@ -192,6 +207,10 @@ WebGPUContext* get_default_webgpu_context() {
192207
}
193208

194209
void destroy_webgpu_context(WebGPUContext& ctx) {
210+
#ifdef WGPU_BACKEND_ENABLE_PROFILING
211+
// Release device-child GPU resources before the device handle.
212+
ctx.querypool.reset();
213+
#endif // WGPU_BACKEND_ENABLE_PROFILING
195214
if (ctx.queue) {
196215
wgpuQueueRelease(ctx.queue);
197216
ctx.queue = nullptr;

backends/webgpu/runtime/WebGPUDevice.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@
1010

1111
#include <webgpu/webgpu.h>
1212

13+
#ifdef WGPU_BACKEND_ENABLE_PROFILING
14+
#include <executorch/backends/webgpu/runtime/WebGPUQueryPool.h>
15+
16+
#include <memory>
17+
#endif // WGPU_BACKEND_ENABLE_PROFILING
18+
1319
namespace executorch {
1420
namespace backends {
1521
namespace webgpu {
@@ -19,6 +25,12 @@ struct WebGPUContext {
1925
WGPUAdapter adapter = nullptr;
2026
WGPUDevice device = nullptr;
2127
WGPUQueue queue = nullptr;
28+
#ifdef WGPU_BACKEND_ENABLE_PROFILING
29+
// True if the device was created with the TimestampQuery feature (bench).
30+
bool timestamp_supported = false;
31+
// Bench-only: timestamp-query pool, lazily created in execute() (env-gated).
32+
std::unique_ptr<WebGPUQueryPool> querypool;
33+
#endif // WGPU_BACKEND_ENABLE_PROFILING
2234
};
2335

2436
WebGPUContext create_webgpu_context();

backends/webgpu/runtime/WebGPUGraph.cpp

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include <executorch/backends/webgpu/runtime/WebGPUCompat.h>
1616
#include <executorch/backends/webgpu/runtime/WebGPUDevice.h>
1717

18+
#include <cstdlib>
1819
#include <cstring>
1920
#include <stdexcept>
2021

@@ -496,18 +497,57 @@ void WebGPUGraph::copy_inputs(
496497
}
497498
}
498499

500+
namespace {
501+
// Bench gate: compiled out unless WGPU_BACKEND_ENABLE_PROFILING; then the
502+
// WEBGPU_TIMESTAMP_QUERY env var enables per-pass GPU timestamp queries.
503+
bool should_timestamp_query() {
504+
#ifdef WGPU_BACKEND_ENABLE_PROFILING
505+
static const bool enabled = std::getenv("WEBGPU_TIMESTAMP_QUERY") != nullptr;
506+
return enabled;
507+
#else
508+
return false;
509+
#endif
510+
}
511+
} // namespace
512+
499513
void WebGPUGraph::execute() {
500514
const size_t n = dispatches_.size();
501515
const size_t chunk = execute_config_.chunk_size;
502516

503517
if (chunk == 0 || n <= chunk) {
518+
#ifdef WGPU_BACKEND_ENABLE_PROFILING
519+
// Bench: timestamp-query pool, null unless env-gated + feature present.
520+
WebGPUQueryPool* qp = nullptr;
521+
if (should_timestamp_query() && n > 0) {
522+
if (auto* ctx = get_default_webgpu_context()) {
523+
if (ctx->timestamp_supported) {
524+
if (!ctx->querypool || ctx->querypool->capacity() < n) {
525+
ctx->querypool = std::make_unique<WebGPUQueryPool>();
526+
ctx->querypool->initialize(device_, static_cast<uint32_t>(n));
527+
}
528+
qp = ctx->querypool.get();
529+
qp->reset(static_cast<uint32_t>(n));
530+
}
531+
}
532+
}
533+
#endif // WGPU_BACKEND_ENABLE_PROFILING
534+
504535
WGPUCommandEncoderDescriptor enc_desc = {};
505536
WGPUCommandEncoder encoder =
506537
wgpuDeviceCreateCommandEncoder(device_, &enc_desc);
507538

508539
// One pass per dispatch: enforces storage RAW ordering across deps.
509-
for (const auto& dispatch : dispatches_) {
540+
for (size_t i = 0; i < n; i++) {
541+
const auto& dispatch = dispatches_[i];
510542
WGPUComputePassDescriptor pass_desc = {};
543+
#ifdef WGPU_BACKEND_ENABLE_PROFILING
544+
// tw must outlive BeginComputePass (the descriptor points at it).
545+
WGPUPassTimestampWrites tw = {};
546+
if (qp) {
547+
tw = qp->writes_for(static_cast<uint32_t>(i));
548+
pass_desc.timestampWrites = &tw;
549+
}
550+
#endif // WGPU_BACKEND_ENABLE_PROFILING
511551
WGPUComputePassEncoder pass =
512552
wgpuCommandEncoderBeginComputePass(encoder, &pass_desc);
513553
wgpuComputePassEncoderSetPipeline(pass, dispatch.pipeline);
@@ -517,22 +557,51 @@ void WebGPUGraph::execute() {
517557
pass, dispatch.workgroup_count_x, 1, 1);
518558
wgpuComputePassEncoderEnd(pass);
519559
wgpuComputePassEncoderRelease(pass);
560+
#ifdef WGPU_BACKEND_ENABLE_PROFILING
561+
if (qp) {
562+
qp->record(
563+
static_cast<uint32_t>(i),
564+
dispatch.kernel_name,
565+
{dispatch.workgroup_count_x, 1, 1},
566+
{1, 1, 1});
567+
}
568+
#endif // WGPU_BACKEND_ENABLE_PROFILING
520569
}
521570

522571
for (const auto& copy : output_copies_) {
523572
wgpuCommandEncoderCopyBufferToBuffer(
524573
encoder, copy.src_buffer, 0, copy.staging_buffer, 0, copy.nbytes);
525574
}
526575

576+
#ifdef WGPU_BACKEND_ENABLE_PROFILING
577+
if (qp) {
578+
qp->resolve(encoder);
579+
}
580+
#endif // WGPU_BACKEND_ENABLE_PROFILING
581+
527582
WGPUCommandBufferDescriptor cmd_desc = {};
528583
WGPUCommandBuffer cmd = wgpuCommandEncoderFinish(encoder, &cmd_desc);
529584
wgpuQueueSubmit(queue_, 1, &cmd);
530585

531586
wgpuCommandBufferRelease(cmd);
532587
wgpuCommandEncoderRelease(encoder);
588+
589+
#ifdef WGPU_BACKEND_ENABLE_PROFILING
590+
if (qp) {
591+
qp->extract_results(instance_);
592+
qp->print_results();
593+
}
594+
#endif // WGPU_BACKEND_ENABLE_PROFILING
533595
return;
534596
}
535597

598+
// GPU timestamp queries assume one submit; chunked execute is multi-submit.
599+
if (should_timestamp_query()) {
600+
throw std::runtime_error(
601+
"WebGPU: WEBGPU_TIMESTAMP_QUERY is incompatible with chunked execute "
602+
"(multi-submit); disable chunking to use GPU timestamp queries");
603+
}
604+
536605
const size_t first_chunk = execute_config_.initial_chunk_size > 0
537606
? execute_config_.initial_chunk_size
538607
: chunk;

backends/webgpu/runtime/WebGPUGraph.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ struct WebGPUDispatch {
3131
WGPUComputePipeline pipeline = nullptr;
3232
WGPUBindGroup bind_group = nullptr;
3333
uint32_t workgroup_count_x = 1;
34+
std::string kernel_name; // bench label
3435
};
3536

3637
struct OutputCopy {

0 commit comments

Comments
 (0)