From dc7e59e6349142ffc57c242db8206f6e66d9fa6d Mon Sep 17 00:00:00 2001 From: Julian Ng-Thow-Hing Date: Fri, 17 Jul 2026 17:22:20 -0700 Subject: [PATCH] Update [ghstack-poisoned] --- backends/webgpu/runtime/WebGPUQueryPool.cpp | 36 ++++++++++--- backends/webgpu/runtime/WebGPUQueryPool.h | 6 +++ backends/webgpu/test/test_webgpu_native.cpp | 57 +++++++++++++++++++++ 3 files changed, 91 insertions(+), 8 deletions(-) diff --git a/backends/webgpu/runtime/WebGPUQueryPool.cpp b/backends/webgpu/runtime/WebGPUQueryPool.cpp index 89e08a2afce..8e92ed2ad93 100644 --- a/backends/webgpu/runtime/WebGPUQueryPool.cpp +++ b/backends/webgpu/runtime/WebGPUQueryPool.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -125,6 +126,32 @@ void WebGPUQueryPool::resolve(WGPUCommandEncoder encoder) { static_cast(count) * kTimestampBytes); } +// Mali pins begin-of-pass timestamps; use consecutive-end delta per op. +void fill_shader_durations( + std::vector& durations, + const uint64_t* ticks, + double ns_per_tick) { + std::sort( + durations.begin(), + durations.end(), + [](const ShaderDuration& a, const ShaderDuration& b) { + return a.idx < b.idx; + }); + uint64_t prev_end = 0; + bool have_prev = false; + for (auto& d : durations) { + const uint64_t t0 = ticks[2 * d.idx]; + const uint64_t t1 = ticks[2 * d.idx + 1]; + const uint64_t base = have_prev ? std::max(t0, prev_end) : t0; + d.start_time_ns = static_cast(base * ns_per_tick); + d.end_time_ns = static_cast(t1 * ns_per_tick); + d.execution_duration_ns = + (t1 > base) ? static_cast((t1 - base) * ns_per_tick) : 0; + prev_end = have_prev ? std::max(prev_end, t1) : t1; + have_prev = true; + } +} + void WebGPUQueryPool::extract_results(WGPUInstance instance) { if (num_pairs_ == 0) { return; @@ -149,14 +176,7 @@ void WebGPUQueryPool::extract_results(WGPUInstance instance) { const uint64_t* ticks = static_cast( wgpuBufferGetConstMappedRange(readback_buf_, 0, bytes)); if (ticks != nullptr) { - for (auto& d : durations_) { - const uint64_t t0 = ticks[2 * d.idx]; - const uint64_t t1 = ticks[2 * d.idx + 1]; - d.start_time_ns = static_cast(t0 * ns_per_tick_); - d.end_time_ns = static_cast(t1 * ns_per_tick_); - d.execution_duration_ns = - (t1 >= t0) ? static_cast((t1 - t0) * ns_per_tick_) : 0; - } + fill_shader_durations(durations_, ticks, ns_per_tick_); } wgpuBufferUnmap(readback_buf_); } diff --git a/backends/webgpu/runtime/WebGPUQueryPool.h b/backends/webgpu/runtime/WebGPUQueryPool.h index 9e5d6cb788c..3f5089d5e9a 100644 --- a/backends/webgpu/runtime/WebGPUQueryPool.h +++ b/backends/webgpu/runtime/WebGPUQueryPool.h @@ -83,6 +83,12 @@ class WebGPUQueryPool { std::vector durations_; }; +// Per-op durations from begin/end tick pairs (consecutive-end delta). +void fill_shader_durations( + std::vector& durations, + const uint64_t* ticks, + double ns_per_tick); + #endif // WGPU_BACKEND_ENABLE_PROFILING } // namespace executorch::backends::webgpu diff --git a/backends/webgpu/test/test_webgpu_native.cpp b/backends/webgpu/test/test_webgpu_native.cpp index 5ba4a95f976..170967f69dc 100644 --- a/backends/webgpu/test/test_webgpu_native.cpp +++ b/backends/webgpu/test/test_webgpu_native.cpp @@ -153,6 +153,59 @@ void test_query_pool_roundtrip(const WebGPUContext& ctx) { printf(" probe duration: %llu ns\n", (unsigned long long)dur); EXPECT_NE(dur, 0u) << "probe duration is zero (expected monotonic non-zero)"; } + +// Device-free: tick->duration delta math (the Mali begin-pinning fix). +void test_query_pool_delta_math() { + auto durs = [](std::vector idxs) { + std::vector v; + for (uint32_t i : idxs) { + ShaderDuration d; + d.idx = i; + v.push_back(d); + } + return v; + }; + // Well-behaved backend (begin >= prev end): per-op == end - begin, unchanged. + { + const uint64_t ticks[] = {10, 20, 20, 35, 40, 50}; + auto d = durs({0, 1, 2}); + fill_shader_durations(d, ticks, 1.0); + EXPECT_EQ(d[0].execution_duration_ns, 10u); + EXPECT_EQ(d[1].execution_duration_ns, 15u); + EXPECT_EQ(d[2].execution_duration_ns, 10u); + } + // Tile GPU: begin pinned, ends cumulative -> recover per-op; sum == wall. + { + const uint64_t ticks[] = {0, 20, 0, 50, 0, 90}; + auto d = durs({0, 1, 2}); + fill_shader_durations(d, ticks, 1.0); + EXPECT_EQ(d[0].execution_duration_ns, 20u); + EXPECT_EQ(d[1].execution_duration_ns, 30u); + EXPECT_EQ(d[2].execution_duration_ns, 40u); + const uint64_t sum = d[0].execution_duration_ns + + d[1].execution_duration_ns + d[2].execution_duration_ns; + EXPECT_EQ(sum, 90u); + } + // Recorded out of order: the idx-sort keeps the delta correct. + { + const uint64_t ticks[] = {0, 20, 0, 50, 0, 90}; + auto d = durs({2, 0, 1}); + fill_shader_durations(d, ticks, 1.0); + for (const auto& x : d) { + const uint64_t exp = x.idx == 0 ? 20u : (x.idx == 1 ? 30u : 40u); + EXPECT_EQ(x.execution_duration_ns, exp) << "idx " << x.idx; + } + } + // Non-monotone end (op1 end < prev end): running-max base + zero-clamp. + { + const uint64_t ticks[] = {0, 100, 0, 40, 0, 120}; + auto d = durs({0, 1, 2}); + fill_shader_durations(d, ticks, 1.0); + EXPECT_EQ(d[0].execution_duration_ns, 100u); + EXPECT_EQ(d[1].execution_duration_ns, 0u); + EXPECT_EQ(d[2].execution_duration_ns, 20u); + } +} #endif // WGPU_BACKEND_ENABLE_PROFILING void test_update_cache(const std::string& model_path) { @@ -1594,6 +1647,10 @@ TEST(WebGPUNative, QueryPoolOverrunThrows) { TEST(WebGPUNative, QueryPoolRoundtrip) { test_query_pool_roundtrip(*get_default_webgpu_context()); } + +TEST(WebGPUNative, QueryPoolDeltaMath) { + test_query_pool_delta_math(); +} #endif // WGPU_BACKEND_ENABLE_PROFILING // The override wg_size must not change results: run the rms_norm scalar kernel