← TinyGPT · docs · devlog · roadmap · speedup
source: docs/learn/webgpu-execution-model.md · view on GitHub ↗

The WebGPU execution model

Just enough of the model to read webgpu/*.wgsl and explain it cleanly. The canonical references are WebGPU Fundamentals (compute articles) and the WebGPU / WGSL specs — this page is the project-specific connective tissue, not a re-explanation of the spec.

The six nouns

  1. Adapter / Device / Queue. The adapter is the physical GPU; the device is your logical handle to it; the queue is where you submit work. You record commands, then queue.submit().
  2. Compute pipeline. A compiled WGSL @compute entry point plus its layout. It says “run this shader function over a grid.”
  3. Dispatch. dispatchWorkgroups(gx, gy, gz) launches a 3-D grid of workgroups. This is how you say “do this kernel that many times.” The grid size is your tiling decision.
  4. Workgroup. One cell of the dispatch grid. It is a group of invocations that run together, can share workgroup memory, and can synchronize with workgroupBarrier(). Declared in the shader: @workgroup_size(x, y, z). The workgroup is the only unit with fast shared scratch and cheap synchronization.
  5. Invocation. One thread running the shader once. It knows its position via built-ins: @builtin(global_invocation_id) (where am I in the whole grid), @builtin(local_invocation_id) (where am I in my workgroup). Map these to the output element(s) this thread is responsible for.
  6. Bind group. The shader’s argument list: the buffers (and textures/samplers) it reads and writes, declared as @group(g) @binding(b) var<...>. You bind concrete GPU buffers to those slots before dispatch.

The one rule + the memory hierarchy

The rule: invocations can cooperate (share memory, barrier) only within a single workgroup. There is no cross-workgroup synchronization inside a dispatch — if workgroup A needs workgroup B’s output, that’s a second dispatch. This single constraint shapes every kernel: you tile the problem so each workgroup owns an independent chunk.

The memory tiers, fastest to slowest:

Access mode is load-bearing: var<storage, read> must be bound as read-only-storage. A mismatch between the WGSL declaration and the bind-group layout silently returns garbage on Apple/Chromium (no validation error) — the exact bug in study_guide §9 that drove loss to 88.67.

How our shaders map onto it

webgpu/matmul_blocked.wgsl (the §8 walkthrough):

webgpu/attention_fa2.wgsl (the §7 / online-softmax walkthrough):

Explaining it to another engineer (the 30-second version)

“You compile a shader into a pipeline, then dispatch a grid of workgroups. Each workgroup is a pack of invocations (threads) that share fast workgroup memory and can barrier — but workgroups can’t talk to each other within one dispatch, so you tile the problem into independent per-workgroup chunks. Inputs/outputs are storage buffers (slow global memory) bound via bind groups; the trick in every kernel is to stage a tile into workgroup memory or registers and reuse it many times before going back to global. Get the bind-group access mode wrong and Apple’s driver hands you garbage with no error — so you trust end-to-end parity, not the validation layer.”

References