Skip to content

Subgroups & Register Shuffling

While all threads in a workgroup are logically grouped, the GPU physical hardware actually groups threads into smaller clusters called subgroups (also known as warps on NVIDIA GPUs, or wavefronts on AMD GPUs).

Subgroups typically consist of 32 or 64 threads executing in physical lockstep.

Direct Register-to-Register Shuffling

Normally, to share data between threads, we must write to shared workgroup memory (var<workgroup>) and call workgroupBarrier(). This involves roundtrips to SRAM caches, which adds latency.

Subgroup collective operations allow threads in the same subgroup to share and shuffle values directly between their hardware registers, completely bypassing memory! This is the fastest possible communication on modern GPU architectures.

WGSL Subgroup Operations

To use subgroup features, the WebGPU context must enable the subgroups extension. WGSL then exposes several subgroup built-ins:

  1. subgroupBroadcast(value, id): Shares the value from thread id with every other thread in the subgroup.
  2. subgroupAdd(value) / subgroupMul(value): Performs collective reductions (e.g., sum or product) across all active threads.
  3. subgroupShuffle(value, index): Reads value directly from the register of the thread at index.
  4. subgroupShuffleXor(value, mask): Shuffles values based on a bitwise XOR of the thread coordinates—ideal for fast tree-reductions.

Uniformity Constraints

Because subgroup operations are collective (meaning all active threads in the subgroup must execute them simultaneously), they are subject to strict Uniformity Analysis rules:

  • They must only be called in uniform control flow (i.e., outside of divergent branches like if (thread_id % 2 == 0)).
  • Calling them inside divergent blocks will cause compilation errors or undefined values!

Subgroup State

In the shader code on the right, we show how subgroup-level tree reductions and shuffles are performed. The visualizer captures the outputs.