Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 28 additions & 8 deletions backends/webgpu/runtime/WebGPUQueryPool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <executorch/backends/webgpu/runtime/WebGPUCompat.h>
#include <executorch/backends/webgpu/runtime/WebGPUQueryPool.h>

#include <algorithm>
#include <cstdio>
#include <map>
#include <stdexcept>
Expand Down Expand Up @@ -125,6 +126,32 @@ void WebGPUQueryPool::resolve(WGPUCommandEncoder encoder) {
static_cast<uint64_t>(count) * kTimestampBytes);
}

// Mali pins begin-of-pass timestamps; use consecutive-end delta per op.
void fill_shader_durations(
std::vector<ShaderDuration>& 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<uint64_t>(base * ns_per_tick);
d.end_time_ns = static_cast<uint64_t>(t1 * ns_per_tick);
d.execution_duration_ns =
(t1 > base) ? static_cast<uint64_t>((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;
Expand All @@ -149,14 +176,7 @@ void WebGPUQueryPool::extract_results(WGPUInstance instance) {
const uint64_t* ticks = static_cast<const uint64_t*>(
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<uint64_t>(t0 * ns_per_tick_);
d.end_time_ns = static_cast<uint64_t>(t1 * ns_per_tick_);
d.execution_duration_ns =
(t1 >= t0) ? static_cast<uint64_t>((t1 - t0) * ns_per_tick_) : 0;
}
fill_shader_durations(durations_, ticks, ns_per_tick_);
}
wgpuBufferUnmap(readback_buf_);
}
Expand Down
6 changes: 6 additions & 0 deletions backends/webgpu/runtime/WebGPUQueryPool.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@ class WebGPUQueryPool {
std::vector<ShaderDuration> durations_;
};

// Per-op durations from begin/end tick pairs (consecutive-end delta).
void fill_shader_durations(
std::vector<ShaderDuration>& durations,
const uint64_t* ticks,
double ns_per_tick);

#endif // WGPU_BACKEND_ENABLE_PROFILING

} // namespace executorch::backends::webgpu
57 changes: 57 additions & 0 deletions backends/webgpu/test/test_webgpu_native.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<uint32_t> idxs) {
std::vector<ShaderDuration> 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) {
Expand Down Expand Up @@ -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
Expand Down
Loading