Triton #11401 — Why an Internal Cluster Barrier Had to Clear the Scratch Write but Preserve the Read
Multi-CTA scratch reuse, atomic-result broadcasts, and the difference between a later read and a later overwrite
The barrier was already inside the operation.
The compiler still had to remember something after it.
Several Triton operations use shared-memory scratch as a temporary broadcast path across multiple CTAs, or thread blocks.
Their internal memory sequence is:
write scratch
↓
internal cluster barrier
↓
read scratchThe barrier resolves the first half of that sequence.
By the time the operation reaches its read phase, the earlier scratch write has been synchronized.
But the read itself has just happened.
That read remains relevant if another operation wants to reuse the same scratch allocation.
The correct outgoing state is therefore:
scratch write
→ no longer pendingscratch read
→ still pendingBefore Triton PR #11401, the NVIDIA cluster-barrier analysis represented that distinction for some operations, including cross-CTA reductions and layout conversions.
It did not recognize every operation that used the same cross-CTA scratch protocol.
The missing set included paths such as:
atomic compare-and-swap
atomic read-modify-write
timed atomic polling
local atomic scatter RMWThat incomplete classification could produce two opposite barrier mistakes.
| Next reuse of the same scratch | Correct result |
|---|---|
| Another operation only reads it | No additional barrier |
| Another operation overwrites it | Cluster barrier required |
If the earlier operation’s internal write remained visible to the analysis, a later read could receive a redundant barrier.
If its outgoing cross-CTA read was not classified correctly, a later local write could receive no barrier at all.
PR #11401 repaired both outcomes by giving the analysis one accurate timeline:
W(scratch)
→ internal cluster barrier
→ R(scratch)An internal barrier can retire the accesses before it. It cannot retire a read that occurs after it.
The minimum two-operation example
Consider two CTAs sharing a scratch allocation.
The first operation computes an atomic result and broadcasts it through scratch.
Operation A:
CTA-local scratch write
↓
cluster-wide synchronization
↓
cross-CTA scratch readNow consider two possible operations after it.
Case 1 — the next operation only reads
Operation A:
W → barrier → R
Operation B:
RThe relevant unresolved relationship is:
R → RTwo reads do not overwrite one another.
No additional cluster barrier is needed between them.
Case 2 — the next operation writes
Operation A:
W → barrier → R
Operation C:
WThe unresolved relationship is now:
R → WOperation C must not overwrite the scratch allocation until every CTA participating in Operation A has completed its read.
This is a write-after-read hazard.
A cluster barrier is required before Operation C.
The compiler does not need to remember the complete history of Operation A.
It needs the part of that history that remains unresolved at its exit.
Retired:
the write before the internal barrierStill live:
the read after the internal barrierWhy cross-CTA atomic results use scratch
An atomic operation may be performed by only part of the logical Triton tensor.
Its result may still need to appear consistently to threads that did not execute the atomic instruction directly.
For a scalar result, the lowering can use a small shared-memory scratch region.
Conceptually:
participating thread performs atomic
↓
result written to scratch
↓
CTAs rendezvous
↓
other threads or CTAs read the resultThe same broad mechanism can appear for a tensor result with replicated or broadcast dimensions.
The scratch storage is not the user’s explicit destination.
It is compiler-managed temporary memory used to preserve Triton’s block-level value semantics.
That distinction matters for barrier analysis.
The atomic may access:
the user-visible atomic destination
and
an internal scratch allocationPR #11401 concerns the synchronization state of the second one.
How Triton tracks shared-memory hazards
Triton’s membar analysis stores accesses in two collections:
syncReadSlices
syncWriteSlicesEach entry identifies:
a shared-memory allocation slice,
the operations that accessed it,
and whether the access was a read or write.
MembarInfo.pending represents the accesses that have occurred since the most recent relevant synchronization.
When a new operation arrives, the analysis checks for:
RAW
write followed by readWAR
read followed by writeWAW
write followed by writeIf two overlapping scratch accesses create one of those relationships and the access is distributed across CTAs, the pass may insert:
ttng.cluster_barrierThe challenge in #11401 was not detecting that the scratch intervals overlapped.
The allocation analysis already knew which temporary region each operation used.
The challenge was describing where the operation’s own internal synchronization divided its accesses.
The required state transition
For a scratch-backed cross-CTA operation, the merged analysis performs the following sequence.
1. Record the scratch write
current operation state:
W(scratch)2. Resolve an incoming conflict if necessary
Suppose a previous operation still has a pending read of the same allocation.
pending:
R(scratch)
current:
W(scratch)The pass inserts a cluster barrier before the current operation.
That barrier clears the incoming state.
It does not delete the current operation’s scratch write because that write occurs after the inserted barrier.
before inserted barrier:
pending = R
current = Wafter inserted barrier:
pending = empty
current = W3. Model the operation’s internal barrier
When the current operation uses cross-CTA scratch, its own lowering contains a synchronization between the scratch write and scratch read.
The analysis commits the write, processes the internal synchronization, and clears the current write state.
W(scratch)
↓
internal barrier
↓
write retired4. Record the outgoing read
After that internal barrier, the operation reads the shared result.
outgoing state:
R(scratch)This final read is added to the pending state used when analyzing the next operation.
That sequence is the core of the patch.
What the old classifier recognized
The previous isDistributedMultiCTAOp() contained direct logic for cross-CTA scratch use by operations such as:
convert_layout
reduceIt also handled several other explicitly named multi-CTA operations, including selected TMA, tensor-memory, and barrier paths.
But atomic-result scratch use was defined elsewhere.
Allocation analysis already had a broader helper:
hasCrossCTAScratch(op)That helper knows whether an operation’s temporary storage crosses CTA boundaries.
Its covered cases include:
cross-CTA layout conversion
reduction split across CTAs
timed atomic poll with a consumed result
atomic operations whose result requires broadcast
local atomic scatter RMW with cross-CTA result useThe cluster-barrier classifier had duplicated only part of that knowledge.
The result was two separate definitions of:
Which operations use cross-CTA scratch?They no longer agreed.
The patch reused the existing allocation contract
The repaired classifier begins with:
if (hasCrossCTAScratch(op))
return isRead;That line carries two decisions.
Decision 1 — use the shared source of truth
The barrier pass no longer maintains its own incomplete list of scratch-producing operations.
It asks the same helper used by shared-memory allocation analysis.
Allocation analysis:
Does this operation need cross-CTA scratch?Barrier analysis:
Use the same answer.Decision 2 — only the read is distributed
The helper may report that the operation uses scratch across CTAs.
That does not mean both phases are remote.
The merged source states:
scratch write
→ CTA-localscratch read
→ may access another CTA’s shared memoryTherefore:
isDistributedMultiCTAOp(
op,
/*isRead=*/false
)returns false for this scratch protocol.
But:
isDistributedMultiCTAOp(
op,
/*isRead=*/true
)returns true.
That directional answer is what allows the analysis to distinguish:
later read
→ no barrierfrom:
later write
→ barrierThe same hasCrossCTAScratch() contract remains in the current Triton source.
Why a later read must not receive another barrier
One regression begins with a cross-CTA atomic CAS.
The operation broadcasts its scalar result through scratch.
It is followed by:
ttng.tmem_copyThe two-CTA tensor-memory copy reads a shared-memory allocation that reuses the same physical scratch interval.
After the atomic’s internal barrier, its outgoing scratch state is already:
RThe copy contributes another:
RSo the test requires:
atomic CAS
no extra cluster barrier
tmem_copyThe same negative-barrier pattern is checked for:
cross-CTA reduction
atomic RMW
timed atomic poll
local atomic scatter RMW“No extra barrier” does not mean these operations contain no internal synchronization.
It means the pass must not insert another cluster barrier between two already ordered read phases.
Why a later local reduction does need one
The companion regressions replace the read-only copy with a CTA-local reduction.
That local reduction reuses the same scratch interval for a new write.
The previous cross-CTA operation still has an outgoing read pending.
previous:
R(scratch)next:
W(scratch)The test therefore requires:
tt.atomic_cas
ttng.cluster_barrier
"tt.reduce" ... axis = 0The barrier appears immediately before the local reduction.
Equivalent positive-barrier checks are included for:
atomic RMW
timed atomic poll
local atomic scatter RMWThe local reduction itself does not need to be distributed across CTAs for this barrier to matter.
It is writing storage that a prior distributed read may still be using.
The cross-CTA property belongs to the unresolved earlier access.
The tests check both over-synchronization and under-synchronization
Barrier patches are often evaluated only by asking:
Did the compiler add a missing barrier?PR #11401 protects both directions.
Too many barriers
cross-CTA scratch read
followed by
another readExpected:
no additional cluster barrierAn extra barrier can serialize CTAs and add unnecessary synchronization cost.
The PR does not publish a benchmark quantifying that cost.
Too few barriers
cross-CTA scratch read
followed by
scratch overwriteExpected:
cluster barrier before the writeWithout it, one CTA could begin reusing the allocation while another CTA still depends on its previous contents.
No runtime wrong-result reproducer was published with this PR, so the article should not claim a measured corruption.
The static ordering defect and repaired barrier placement are directly covered.
The operation matrix
The added MLIR coverage includes these scratch-producing operations:
| First operation | Following reuse | Expected extra barrier |
|---|---|---|
| Cross-CTA reduction | tmem_copy read | No |
| Atomic CAS | tmem_copy read | No |
| Atomic CAS | Local reduction write | Yes |
| Atomic RMW | tmem_copy read | No |
| Atomic RMW | Local reduction write | Yes |
| Timed atomic poll | tmem_copy read | No |
| Timed atomic poll | Local reduction write | Yes |
| Local atomic scatter RMW | Read-only reuse | No |
| Local atomic scatter RMW | Local scratch write | Yes |
This pairing makes the intended state visible.
For each operation class, the tests ask two different questions:
Did the internal barrier retire its earlier write?and:
Did the later read remain pending?A model that clears everything would fail the write-reuse case.
A model that clears nothing would fail the read-only case.
Why the atomic poll test includes a timeout
The allocation helper does not assign broadcast scratch to every atomic poll.
A poll without a timeout can only complete through the successful condition represented by that form and may not require a returned result to be broadcast through shared scratch.
The covered timed-poll path requires:
a timeout
and
a result that is actually consumedThat is the case for which hasCrossCTAScratch() reports the distributed scratch protocol.
The test is therefore attached to the operation form that reaches the repaired analysis path.
This avoids turning a specific scratch contract into a claim about every atomic poll.
Local atomic scatter has two memory stories
local_atomic_scatter_rmw is slightly more complicated than the scalar atomic cases.
It accesses:
an explicit shared-memory destination
and
compiler-managed scratch used to broadcast its resultMost scratch operations are expected not to carry unrelated explicit shared-memory dependencies in the same analysis step.
The source contains a specific exception for local atomic scatter RMW because it legitimately has both.
The patch does not merge those two memory regions into one abstract access.
It keeps the explicit destination effects and models the temporary scratch’s internal barrier separately.
This is why its regression is valuable.
It protects a case where one operation participates in two distinct shared-memory contracts.
The pass is limited to NVIDIA multi-CTA targets
runClusterBarrierInsertion() returns without doing this analysis when:
compute capability < 90or:
number of CTAs = 1The new regressions use modules configured for two CTAs, including cuda:100 cases.
The direct patch scope is therefore:
NVIDIA cluster-capable multi-CTA loweringIt does not establish the same issue in:
single-CTA kernels,
AMD lowering,
CPU execution,
or older NVIDIA targets that do not enter this cluster-barrier pass.
The source is part of Triton’s NVIDIA GPU transform pipeline, not its generic frontend semantics.
What the merged evidence confirms
The source and tests confirm that:
cross-CTA scratch operations contain logically separate write and read phases;
an internal cluster barrier separates those phases;
the previous barrier classifier had narrower operation coverage than allocation analysis;
atomic CAS, atomic RMW, timed atomic poll, and local atomic scatter RMW can use the same cross-CTA scratch protocol;
the merged classifier delegates that classification to
hasCrossCTAScratch();scratch writes are treated as CTA-local;
scratch reads are treated as distributed when the helper reports cross-CTA scratch;
read-only reuse receives no additional barrier;
a later local scratch write receives a cluster barrier;
PR #11401 merged into Triton
main;and the repaired classification remains in current
main.
What the evidence does not establish
The public record does not establish that:
a production model returned an incorrect result before the patch;
one specific GPU race was captured at runtime;
every atomic operation allocates scratch;
every read-after-read barrier had measurable performance cost;
every write-after-read omission manifests deterministically;
the fix affects single-CTA kernels;
AMD requires the same barrier model;
or the pass is now complete for every possible cross-CTA operation.
The supported conclusion is narrower:
Triton’s NVIDIA membar analysis did not consistently recognize every operation whose result passed through cross-CTA scratch. The merged patch models those operations as a local scratch write, an internal cluster barrier, and an outgoing distributed read, then inserts later barriers according to the access that actually remains unresolved.
The contract that remains
A synchronization point does not erase an operation as a whole.
It partitions the operation’s memory history.
accesses before the barrier
→ synchronizedaccesses after the barrier
→ still relevantTreating the complete operation as either:
fully unsynchronizedor:
fully synchronizedloses the ordering information needed for safe scratch reuse.
Barrier analysis must preserve which side of the barrier each memory effect belongs to.
That is the compiler contract repaired by Triton #11401.
Related material
Patch status: Merged into Triton main on August 24, 2026, as 5c955485.