Files
TheRock/rocm-libraries/shared/stinkytofu/docs/developer/cluster-barrier.md
T
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

11 KiB

Insert Cluster Barrier Pass

createInsertClusterBarrierPass builds a pass that inserts cluster-barrier instructions at five well-defined rules covering the main and tail loops. This document describes each rule, the emitted code shapes, and the pass parameters.

The pass is created via:

STINKYTOFU_EXPORT std::unique_ptr<Pass> createInsertClusterBarrierPass(
    bool isKernelScope = true,
    int  pgrValue      = 1,
    int  plrValue      = 1);

Overview

Rules are numbered in kernel-execution order -- the rule with the lowest number is the first to fire when the kernel runs.

The cluster handshake uses signal/wait pairs at two scopes:

  • Workgroup scope: s_barrier_signal -1 / s_barrier_wait -1
  • Cluster scope: s_barrier_signal -3 / s_barrier_wait -3

<HASH> in the emitted labels is a fresh 16-character alphanumeric identifier generated per insertion. Only the first wave (WaveIdx == 0) executes the signal; the other waves fall through to the label.

Idempotency: each rule has its own skip check, so re-running the pass is a no-op when the handshake is already present.


Rule 1 -- Post-GSU==1 signal-only

Signal-only (no leading cluster wait), emitted immediately after each label_GSU_1: label (Tensile's post-GSU==1-guard label), wrapped in an outer LoopCounterL != 0 gate so the cluster-barrier signal only fires on non-zero iterations.

A workgroup-scope s_barrier_signal -1 / s_barrier_wait -1 pair sits inside the outer LCL skip region (and before the inner WaveIdx gate) so every wave in the workgroup has reached the post-GSU==1 join before any wave issues the cluster signal:

    s_cmp_eq_u32 s[sgprLoopCounterL], 0
    s_cbranch_scc1 label_skipCBPreSignal_LCL_<HASH_OUTER>
    s_barrier_signal -1                                          // workgroup signal
    s_barrier_wait -1                                            // workgroup wait
    s_cmp_eq_u32 s[sgprWaveIdx], 0
    s_cbranch_scc0 label_skipCBPreSignal_<HASH_INNER>
    s_barrier_signal -3
  label_skipCBPreSignal_<HASH_INNER>:
  label_skipCBPreSignal_LCL_<HASH_OUTER>:

Rule 2 -- First kernel load wait (kernel scope only)

A single s_barrier_wait -3 immediately before the first tensor_load_to_lds of the whole kernel.


Rule 3 -- LDS-publication handshake (currently disabled)

Rule 3 is currently disabled via the kRule3Enabled master switch in the .cpp. The description below is retained for when it is re-enabled.

A LoopCounterL-gated signal-only handshake at the LDS publication point that precedes label_openLoopL:. It has the same shape as Rule 1 but with the outer gate set to s_cmp_le_u32 s[sgprLoopCounterL], pgrValue (the cbranch skips the signal when there are too few iterations left, so the producer is not needed).

The gate mirrors Tensile's own s_cmp_le_u32 LCL, pgrValue / s_cbranch_scc1 LoopEndL loop-entry guard, so the cluster signal is suppressed on the exact same control-flow paths where the corresponding s_barrier_wait -3 inside the unrolled loop body is skipped -- keeping signal -3 / wait -3 paired everywhere.

Two anchor modes (backward scan from label_openLoopL:)

(a) Publication point already exists (typical for PrefetchLocalRead > 0 schedules)

An s_barrier_wait -1 is already present. Anchor at the successor of that wait; no new workgroup sync is synthesized. The scan stops as soon as a tensor_load_to_lds is reached: that instruction marks the prefetch section, before any workgroup sync could sit, so an earlier workgroup wait would be unrelated. Defers to Rule 4 if the same wait would also be a Rule-4 trigger.

(b) No publication point (typical for PrefetchLocalRead == 0 schedules)

No s_barrier_wait -1 between the prefetch tail and label_openLoopL: (the prologue has no local-read preamble barrier). Only active when plrValue == 0; anchor at the label and synthesize an s_barrier_signal -1 / s_barrier_wait -1 pair inside the LCL skip region (between the outer LCL skip-branch and the inner WaveIdx gate) so the workgroup sync sits on the same control-flow path as the cluster signal -- both are bypassed together on the LCL <= pgrValue skip path (matching Tensile's loop-entry guard: when the unrolled loop body is skipped, the LDS reads inside it are skipped too, so no LDS publication is needed). Emitted shape immediately before the label_openLoopL: label:

    s_cmp_le_u32 s[sgprLoopCounterL], <pgrValue>          // outer LCL gate
    s_cbranch_scc1 label_skipCBPreSignal_LCL_<HASH_OUTER> // skip when LCL <= pgr
    s_barrier_signal -1                                  // workgroup signal
    s_barrier_wait -1                                    // workgroup sync
    s_cmp_eq_u32 s[sgprWaveIdx], 0                        // inner wave gate
    s_cbranch_scc0 label_skipCBPreSignal_<HASH_INNER>
    s_barrier_signal -3
  label_skipCBPreSignal_<HASH_INNER>:
  label_skipCBPreSignal_LCL_<HASH_OUTER>:

Internal control-flow labels inside the prefetch prologue (e.g. label_skipPGR2_*) do not match label_openLoopL by exact-name comparison and are walked through.

Section-level idempotency: the backward scan also flags whether a cluster-scope signal/wait already sits in the section; if so (e.g. a prior pass run already emitted Rule 3), Rule 3 self-disables. Unlike a TEXTBLOCK anchor, the label/instruction-based scan survives ScopeAdaptor::moveIRToBlock, so Rule 3 keeps working whenever Gfx1250Backend::buildGfx1250Pipeline runs this pass at kernel scope when moduleOptions.ClusterBarrier == true.


Rule 4 -- Cluster handshake before loop loads

A cluster handshake after each workgroup-scope wait that precedes a tensor_load_to_lds. For every load in a label-/branch-delimited segment, the pass walks backward to the nearest preceding s_barrier_wait -1; triggers are deduplicated by identity so multiple loads sharing the same anchor wait yield exactly one handshake.

The emission is selected by the kRule4ForceUngatedSignalMode master switch in the .cpp (default on = mode (c)):

(c) always-ungated mode (active) -- for every trigger emit a WaveIdx-gated s_barrier_signal -3 then a bare s_barrier_wait -3; the cluster signal is never wrapped in an LCL skip branch. findLiveLoopCounterLCmpUpstream is still consulted: if SIA hoisted a live loop-exit s_cmp_eq LCL, imm whose SCC a downstream s_cbranch_scc0 LoopBeginL consumes, a clone of it is re-emitted after the bare wait (which has no SCC side effect) to restore that SCC.

When the switch is off, two fallback modes are selected by findLiveLoopCounterLCmpUpstream:

(a) inherited-SCC mode -- when SIA (typically ScheduleIterAlg=4) hoists the loop-exit s_cmp_eq_{u32,i32} LCL, imm above the anchor, a downstream cbranch still consumes its SCC. Emit an ungated leading s_barrier_wait -3 first, then the inherited-SCC signal block (the signal is single-iter skipped via the inherited SCC; a clone of the upstream cmp is re-emitted between the inner and outer skip labels to rebuild SCC for that downstream cbranch).

(b) drain-gated mode -- when no such upstream cmp is live, gate the handshake with ASYMMETRIC LCL thresholds: skip the WAIT at LCL <= pgrValue and the SIGNAL one stage earlier at LCL <= pgrValue+1 (both lowered by any hoisted LCL pre-decrement). The drain iterations -- where the paired tensor_load_to_lds is disabled -- drop the handshake while keeping signal -3 / wait -3 balanced.

Shape (mode (c); the <clone of upstream LCL cmp> line is emitted only when a live upstream cmp exists):

    s_cmp_eq_u32 s[sgprWaveIdx], 0                               // inner wave gate
    s_cbranch_scc0 label_skipCBPreSignal_<HASH_INNER>
    s_barrier_signal -3
  label_skipCBPreSignal_<HASH_INNER>:
    s_barrier_wait -3                                            // bare cluster wait
    <clone of upstream LCL cmp>                                  // restore SCC (if any)

Rule 5 -- Tail-loop cluster handshake (kernel scope only, paired)

Anchors on the first tensor_load_to_lds that follows the /* Tail Loop */ TEXTBLOCK marker. The wait and signal are emitted at two distinct sites because the load and the preceding workgroup wait sit in different label/branch-delimited segments (the tail TDM-reset block between them is not synchronization-critical, so collapsing both into a single site would unnecessarily serialize the cluster).

  • 5a -- signal-only handshake (no LoopCounterL gate) immediately after the workgroup-scope wait (s_barrier_wait -1) that precedes the tail load (searched backward from the load, bounded by the /* Tail Loop */ marker). Defers to Rule 4 if that wait is already a Rule-4 trigger.
  • 5b -- a single s_barrier_wait -3 immediately before the tail tensor_load itself.

Each half has its own idempotency check so re-runs are no-ops.


Parameters

isKernelScope (default true)

Must be true for the GFX1250 backend pipeline (whole-kernel insertion). When false, the pass is intended for region-scoped invocation via createKernelToRegionsPassAdaptor (not used by the backend today). Rule 2 only fires when this is true because the "first tensor_load of the whole kernel" anchor is meaningful only at kernel scope.

pgrValue (default 1, i.e. PrefetchGlobalRead=1)

Tensile's PrefetchGlobalRead setting. It is consulted only by Rule 4's drain-gated fallback mode (b) (the LCL <= pgrValue / LCL <= pgrValue+1 thresholds), i.e. when kRule4ForceUngatedSignalMode is off. The active mode (c) does not use it, and Rule 3 (whose LoopCounterL <= pgrValue gate would also use it) is disabled via kRule3Enabled. It is retained in the signature both for mode (b)'s drain gate and so the Rule 3 gate can be reinstated without an API change.

plrValue (default 1)

Tensile's PrefetchLocalRead setting. It only takes effect while Rule 3 is enabled (see kRule3Enabled), where it selects Rule 3's anchor mode (b): when plrValue == 0 and the backward scan from label_openLoopL: finds no s_barrier_wait -1 before reaching the prefetch boundary (tensor_load_to_lds), the rule synthesizes the missing publication point (workgroup s_barrier_signal -1 / s_barrier_wait -1) followed by the same LCL <= pgrValue gated cluster signal as anchor mode (a). Any non-zero value disables mode (b) (default).


Analysis invalidation

This pass mutates the CFG (new branches and a new label), so dependent CFG / dominance analyses are invalidated.


See Also

  • Architecture Overview -- system architecture and pass pipeline
  • src/transforms/asm/InsertClusterBarrierPass.cpp -- implementation
  • include/stinkytofu/transforms/asm/InsertClusterBarrierPass.hpp -- public API