You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
WebGPU: GPUBuffer.mapAsync() and GPUQueue.onSubmittedWorkDone() always take >= ~10 ms, even with an idle GPU #37042
GPUBuffer.mapAsync() never resolves in less than about 11 ms on Windows, even when nothing is queued on the GPU, and GPUQueue.onSubmittedWorkDone() takes about 15 ms. For readback-heavy workloads (e.g. running a small neural net once per step and reading the result back) this fixed delay dominates: in our case it was most of the wall time of a single inference loop.
Repro (no dependencies; deno run --unstable-webgpu repro.ts):
constadapter=awaitnavigator.gpu.requestAdapter();constdevice=awaitadapter!.requestDevice();constdst=device.createBuffer({size: 256,usage: GPUBufferUsage.MAP_READ|GPUBufferUsage.COPY_DST});constt: number[]=[];for(leti=0;i<200;i++){consts=performance.now();awaitdst.mapAsync(GPUMapMode.READ);// nothing is queued on the GPUt.push(performance.now()-s);dst.unmap();}t.sort((a,b)=>a-b);console.log(`mapAsync p50 ${t[100].toFixed(2)} ms`);
Deno 2.9.0, Windows 11, RTX 4090 (p50 of 200 calls, two runs each):
stock 2.9.0
with the fix below
mapAsync, nothing queued
11.39 / 11.46 ms
0.01 / 0.01 ms
tiny copy + submit + mapAsync
15.53 / 15.52 ms
0.06 / 0.06 ms
empty submit + onSubmittedWorkDone
15.44 / 15.42 ms
0.04 / 0.04 ms
Cause. In ext/webgpu/buffer.rs (map_async) and ext/webgpu/queue.rs (on_submitted_work_done) the polling future is:
while !*done.borrow(){self.instance.device_poll(self.device, wgpu_types::PollType::wait_indefinitely()).unwrap();
tokio::time::sleep(Duration::from_millis(10)).await;}
device_poll(wait_indefinitely()) blocks until the queue is idle, and the map / work-done callback fires inside that poll, which sends on the oneshot. But the loop then always sleeps 10 ms before it re-checks done (set by the receiver future on the same task), so every call pays at least one full sleep after the GPU is already finished; on Windows the timer rounds that up to about 11-15 ms.
Suggested fix: yield instead of sleeping (PR to follow). wait_indefinitely already blocks until the work is done, so the sleep is not needed for pacing.
Separately, and not addressed by that fix: the blocking wait_indefinitely poll runs on the JS thread inside the op, so the JS thread cannot do other work while the GPU computes. Moving that poll onto a blocking-pool thread let JS keep running in our local tests; that is a larger change, so it is only mentioned here.
This issue was investigated and written with the help of an AI assistant (Claude); the measurements were run on my machine.
Activity