[WebGPU] Fix wait logic for inflight jobs (#20096)

* Enable tmate debugging for investigating thread safety issue

* Refactor wait and submit to operate on vector<wgpu::FutureWaitInfo>, and fix wait to delete only the future that is completed.

* Cleanup

* Remove clear change and run clang-format

* Cleanup
This commit is contained in:
Nikhil Jain
2026-03-04 11:54:55 -08:00
committed by GitHub
parent 541bf37622
commit 24d2ee0527
+51 -21
View File
@@ -123,11 +123,6 @@ struct webgpu_pool_bufs {
wgpu::Buffer dev_buf; wgpu::Buffer dev_buf;
}; };
// The futures to wait on for a single queue submission
struct webgpu_submission_futures {
std::vector<wgpu::FutureWaitInfo> futures;
};
// Holds a pool of parameter buffers for WebGPU operations // Holds a pool of parameter buffers for WebGPU operations
struct webgpu_buf_pool { struct webgpu_buf_pool {
std::vector<webgpu_pool_bufs> free; std::vector<webgpu_pool_bufs> free;
@@ -463,26 +458,60 @@ static void ggml_webgpu_create_buffer(wgpu::Device & device,
/** End WebGPU object initializations */ /** End WebGPU object initializations */
/** WebGPU Actions */ /** WebGPU Actions */
static void erase_completed(std::vector<wgpu::FutureWaitInfo> & futures) {
futures.erase(std::remove_if(futures.begin(), futures.end(),
[](const wgpu::FutureWaitInfo & info) { return info.completed; }),
futures.end());
}
// Wait for the queue to finish processing all submitted work // Wait for the queue to finish processing all submitted work
static void ggml_backend_webgpu_wait(webgpu_global_context & ctx, static void ggml_backend_webgpu_wait(webgpu_global_context & ctx,
std::vector<webgpu_submission_futures> & futures, std::vector<wgpu::FutureWaitInfo> & futures,
bool block = true) { bool block = true) {
// If we have too many in-flight submissions, wait on the oldest one first. // If we have too many in-flight submissions, wait on the oldest one first.
if (futures.empty()) {
return;
}
uint64_t timeout_ms = block ? UINT64_MAX : 0; uint64_t timeout_ms = block ? UINT64_MAX : 0;
while (futures.size() >= WEBGPU_MAX_INFLIGHT_SUBS_PER_THREAD) { while (futures.size() >= WEBGPU_MAX_INFLIGHT_SUBS_PER_THREAD) {
ctx->instance.WaitAny(futures[0].futures.size(), futures[0].futures.data(), UINT64_MAX); auto waitStatus = ctx->instance.WaitAny(1, &futures[0], UINT64_MAX);
if (waitStatus == wgpu::WaitStatus::Error) {
GGML_LOG_ERROR("ggml_webgpu: WaitAny returned an error\n");
}
if (futures[0].completed) {
futures.erase(futures.begin()); futures.erase(futures.begin());
} }
size_t i = 0; }
while (i < futures.size()) {
auto waitStatus = ctx->instance.WaitAny(futures[i].futures.size(), futures[i].futures.data(), timeout_ms); if (futures.empty()) {
return;
}
if (block) {
while (!futures.empty()) {
auto waitStatus = ctx->instance.WaitAny(futures.size(), futures.data(), timeout_ms);
switch (waitStatus) { switch (waitStatus) {
case wgpu::WaitStatus::Success: case wgpu::WaitStatus::Success:
futures.erase(futures.begin() + i); // WaitAny doesn't tell us which future completed, so we must check all futures to see which finished.
erase_completed(futures);
break;
case wgpu::WaitStatus::Error:
GGML_LOG_ERROR("ggml_webgpu: WaitAny returned an error\n");
break;
default:
GGML_LOG_ERROR("ggml_webgpu: WaitAny returned an unknown status\n");
break;
}
}
} else {
// Poll once and return
auto waitStatus = ctx->instance.WaitAny(futures.size(), futures.data(), timeout_ms);
switch (waitStatus) {
case wgpu::WaitStatus::Success:
// WaitAny doesn't tell us which future completed, so we must check all futures to see which finished.
erase_completed(futures);
break; break;
case wgpu::WaitStatus::TimedOut: case wgpu::WaitStatus::TimedOut:
i++;
break; break;
case wgpu::WaitStatus::Error: case wgpu::WaitStatus::Error:
GGML_LOG_ERROR("ggml_webgpu: WaitAny returned an error\n"); GGML_LOG_ERROR("ggml_webgpu: WaitAny returned an error\n");
@@ -525,7 +554,8 @@ static void ggml_backend_webgpu_debug(webgpu_global_context & ctx) {
} }
#endif #endif
static webgpu_submission_futures ggml_backend_webgpu_submit(webgpu_global_context ctx, static std::vector<wgpu::FutureWaitInfo> ggml_backend_webgpu_submit(
webgpu_global_context ctx,
std::vector<webgpu_command> commands, std::vector<webgpu_command> commands,
webgpu_buf_pool & param_buf_pool, webgpu_buf_pool & param_buf_pool,
webgpu_buf_pool * set_rows_error_buf_pool = nullptr) { webgpu_buf_pool * set_rows_error_buf_pool = nullptr) {
@@ -600,7 +630,7 @@ static webgpu_submission_futures ggml_backend_webgpu_submit(webgpu_global_contex
futures.push_back({ f }); futures.push_back({ f });
} }
#endif #endif
return { futures }; return futures;
} }
static webgpu_command ggml_backend_webgpu_build_multi( static webgpu_command ggml_backend_webgpu_build_multi(
@@ -727,8 +757,7 @@ static void ggml_backend_webgpu_buffer_memset(webgpu_global_context & ctx,
webgpu_command command = webgpu_command command =
ggml_backend_webgpu_build(ctx, ctx->memset_buf_pool, ctx->memset_pipelines[0], params, entries, wg_x); ggml_backend_webgpu_build(ctx, ctx->memset_buf_pool, ctx->memset_pipelines[0], params, entries, wg_x);
std::vector<webgpu_submission_futures> futures = { ggml_backend_webgpu_submit(ctx, { command }, auto futures = ggml_backend_webgpu_submit(ctx, { command }, ctx->memset_buf_pool);
ctx->memset_buf_pool) };
ggml_backend_webgpu_wait(ctx, futures); ggml_backend_webgpu_wait(ctx, futures);
} }
@@ -2186,7 +2215,7 @@ static ggml_status ggml_backend_webgpu_graph_compute(ggml_backend_t backend, str
WEBGPU_CPU_PROFILE_TOTAL_START(graph_compute); WEBGPU_CPU_PROFILE_TOTAL_START(graph_compute);
std::vector<webgpu_command> commands; std::vector<webgpu_command> commands;
std::vector<webgpu_submission_futures> futures; std::vector<wgpu::FutureWaitInfo> futures;
uint32_t num_batched_kernels = 0; uint32_t num_batched_kernels = 0;
for (int i = 0; i < cgraph->n_nodes; i++) { for (int i = 0; i < cgraph->n_nodes; i++) {
if (auto cmd = ggml_webgpu_encode_node(ctx, cgraph->nodes[i])) { if (auto cmd = ggml_webgpu_encode_node(ctx, cgraph->nodes[i])) {
@@ -2196,8 +2225,9 @@ static ggml_status ggml_backend_webgpu_graph_compute(ggml_backend_t backend, str
if (num_batched_kernels >= WEBGPU_COMMAND_SUBMIT_BATCH_SIZE) { if (num_batched_kernels >= WEBGPU_COMMAND_SUBMIT_BATCH_SIZE) {
num_batched_kernels = 0; num_batched_kernels = 0;
futures.push_back(ggml_backend_webgpu_submit(ctx->global_ctx, commands, ctx->param_buf_pool, std::vector<wgpu::FutureWaitInfo> compute_futures = ggml_backend_webgpu_submit(
&ctx->set_rows_error_buf_pool)); ctx->global_ctx, commands, ctx->param_buf_pool, &ctx->set_rows_error_buf_pool);
futures.insert(futures.end(), compute_futures.begin(), compute_futures.end());
// Process events and check for completed submissions // Process events and check for completed submissions
ctx->global_ctx->instance.ProcessEvents(); ctx->global_ctx->instance.ProcessEvents();
ggml_backend_webgpu_wait(ctx->global_ctx, futures, false); ggml_backend_webgpu_wait(ctx->global_ctx, futures, false);
@@ -2205,9 +2235,9 @@ static ggml_status ggml_backend_webgpu_graph_compute(ggml_backend_t backend, str
} }
} }
if (!commands.empty()) { if (!commands.empty()) {
webgpu_submission_futures new_futures = auto new_futures =
ggml_backend_webgpu_submit(ctx->global_ctx, commands, ctx->param_buf_pool, &ctx->set_rows_error_buf_pool); ggml_backend_webgpu_submit(ctx->global_ctx, commands, ctx->param_buf_pool, &ctx->set_rows_error_buf_pool);
futures.push_back(new_futures); futures.insert(futures.end(), new_futures.begin(), new_futures.end());
} }
ggml_backend_webgpu_wait(ctx->global_ctx, futures); ggml_backend_webgpu_wait(ctx->global_ctx, futures);