2026/07/15 36.2k words

Transformer System #numinens#transformer#flashattention-3#flashattention-4#hopper#blackwell#tma#wgmma#tmem#gpu#kernel#softmax#fp8#scheduling

M4.6 - FlashAttention-3/4 Hardware Pipelines: Hopper, Blackwell, and the Moving Bottleneck

M4.6 - FlashAttention-3/4 Hardware Pipelines: Hopper, Blackwell, and the Moving Bottleneck

0x00 Overview And Background

M4.3 derived how tiled online softmax removes the quadratic score and probability matrices from HBM. M4.4 then showed that an IO-aware algorithm can still leave the GPU underused because work partition, warp ownership, and non-matrix instructions matter. M4.5 moved to the asymmetric one-query decode shape.

This final article in M4’s execution/kernel branch asks a different question:

Once HBM traffic and coarse work partitioning have improved, how must attention be rescheduled around the asynchronous engines, on-chip memories, synchronization rules, and precision formats of a specific GPU generation?

The logical operation has not changed:

Historical Problem: Every Improvement Exposes The Next Bottleneck

The FlashAttention lineage is not a sequence of interchangeable library versions. Each paper starts from a different residual bottleneck left visible by the previous algorithm and hardware schedule.

Starting pointBottleneck that remains visibleNext design responseDetailed owner
Materialized attentionscore and probability tensors move repeatedly through HBMFA1 tiles exact attention and carries compact online-softmax state on chipM4.3
FA1IO improves, but non-matmul work, query-block parallelism, and warp ownership still limit utilizationFA2 reduces state-rescaling work and changes CTA/warp partitioningM4.4
FA2 on Hopperthe schedule does not fully exploit TMA, WGMMA, warp specialization, and asynchronous overlapFA3 overlaps movement, matrix work, and softmax and adds a hardware-aware FP8 paththis article, Hopper half
FA3-style Hopper schedule on Blackwelltensor throughput and tile capacity grow faster than shared-memory and exponential throughputFA4 moves accumulators into TMEM, redesigns forward/backward pipelines, and changes numerical schedulingthis article, Blackwell half

The FA3 paper motivates its Hopper redesign by reporting that FA2 reaches only about 35% utilization on H100 under the paper’s accounting convention. That is a source-reported coordinate, not a universal statement about every FA2 shape or implementation. The FA4 paper then identifies a different imbalance on Blackwell: tensor-core capability grows faster than shared-memory and special-function throughput, so a schedule tuned for Hopper exposes new bottlenecks.

The causal chain is:

remove quadratic HBM materialization
    -> expose work-partition and non-matmul overhead
    -> expose asynchronous-engine overlap and register pressure
    -> expose TMEM placement, shared-memory traffic, and exponential throughput

The execution machine has changed twice:

Hopper / FlashAttention-3:
    TMA moves tiles asynchronously
    WGMMA performs asynchronous warpgroup matrix operations
    accumulators mainly occupy registers
    producer and consumer warpgroups overlap movement, MMA, and softmax

Blackwell / FlashAttention-4:
    tcgen05 MMA writes accumulators into TMEM
    MMA tiles and tensor throughput grow
    exponential and shared-memory rates do not grow proportionally
    2-CTA MMA, TMEM allocation, software exponential work, and scheduling become central

The owner question is therefore not merely “which FlashAttention version is faster?” It is:

which hardware resource owns each intermediate?
which engine produces it?
which consumer is allowed to read it?
which dependency can overlap?
which resource becomes the next bottleneck?
what numerical contract changes in a low-precision path?
what evidence proves that the intended kernel ran?

Owner Contract: Owns, Consumes, And Defers

Owns

M4.6 derives generation-specific attention pipelines:

Hopper TMA, WGMMA, warpgroups, and register reallocation
FA3 producer/consumer and circular-buffer state
FA3 pingpong and two-stage GEMM-softmax overlap
FA3 FP8 layouts, block scales, and incoherent processing
Blackwell TMEM, tcgen05, and 2-CTA MMA
FA4 forward and backward resource-cycle derivations
partial exponential emulation and conditional online-softmax rescaling
persistent scheduling, deterministic reduction, and CuTe DSL boundaries
hardware/version-specific benchmark interpretation

Consumes

These are imported contracts, not invitations to repeat their full derivations:

Imported ownerContract consumed hereNew consequence derived in M4.6
M4.1FLOP, byte, state, phase, and evidence-coordinate accountingconvert tensor work and source-reported rates into generation-specific resource ledgers
M4.2CTA, warpgroup, occupancy, barrier, and profiler vocabularyapply those contracts to TMA/WGMMA and TMEM/tcgen05 pipelines
M4.3exact compact online-softmax state and merge algebraassign the state to hardware roles and study delayed reference changes
M4.4FA2 state coordinate, query-block ownership, warp partition, and backward baselineidentify the utilization pressure that FA3 and FA4 schedules address
M4.5split-state reduction, cache/mask boundary, backend-admission schema, and profiler proofrecord only architecture-specific artifact eligibility and selected-kernel evidence
M8.3exact equivalent-transform theorytrace the attention-local transform before FP8 quantization
M8.6live activation/KV scale lifecyclebound the local FP8 scale and accumulator contract

Defers

Deferred ownerMaterial not re-derived here
M4.5generic backend selection, one/few-query split reduction, paged address translation, and mask-program mechanics
M5request scheduling, paged K/V allocation, placement, batching, and serving state
M8.2model-wide calibration, sensitivity, and precision allocation
M8.3general outlier geometry and equivalent-transform design
M8.6live activation and K/V quantization, scale lifetimes, and state lifecycle
M8.9quantized artifact compatibility, loader validation, and runtime admission
M8.10deployment evaluation, promotion, release, and rollback of the exact quantized candidate
M4.7-M4.9empirical training scaling, extrapolation, data constraints, downstream metrics, and lifecycle allocation
semantic long-context evaluationwhether a model can use distant evidence rather than merely process a longer tensor

The boundary can be summarized as:

M4.6 owns the physical schedule inside one generation-specific kernel.
It consumes earlier mathematical and execution contracts.
It does not own request policy, general backend policy, or model-wide quantization.

Claim Status And Source Discipline

StatusMeaning
Source-localA19 material is preserved where it remains useful, especially the FA2-to-FA3 transition.
Externally verifiedMechanisms are checked against the FA3/FA4 papers and official NVIDIA Hopper, WGMMA, tcgen05, and CuTe documentation.
Paper algorithmA mechanism or formula is scoped to the cited paper revision and its stated hardware.
Pinned implementation behaviorA source-code claim is scoped to an exact repository tag, commit, and path.
Current release behaviorRuntime support is scoped to a named release rather than inferred from the paper.
Rewrite derivationCycle, pipeline, register, and numerical identities are derived from source-reported rates and tensor shapes.
Source-reportedThroughput, speedup, accuracy, and compile-time results remain attached to the exact paper hardware and benchmark setup.
Boundary correctionA broad statement in A19 is narrowed when the primary source supports a more precise claim.

Evidence Epochs: Paper, Hardware, Source, Release

The same name can refer to a paper algorithm, an instruction contract, a source tree, or an installed runtime. M4.6 keeps those evidence surfaces separate.

Evidence surfaceImmutable or dated coordinate used hereWhat it can establishWhat it cannot establish
A19 local sourcerepository Markdown snapshotthe historical handoff and explanation style to recoverexternal correctness by itself
FA3 paperarXiv 2407.08608v2Hopper algorithm, FP8 method, and source-reported H100 resultscurrent library support or a local speedup
FA4 paperarXiv 2603.05451v1Blackwell algorithm, resource model, and source-reported B200 resultsevery mode in the current artifact
NVIDIA documentationofficial online documentation checked 2026-07-28TMA, WGMMA, mbarrier, TMEM, tcgen05, CTA-pair, and DSMEM semanticswhich FlashAttention specialization ran
Official implementationfa4-v4.0.0.beta23, commit 2409214a03797b168f648ea30df1adbc09ce658aversion-scoped dispatch, role, pipeline, and feature behaviortimeless FA3/FA4 behavior
Local deploymentno GPU build, profile, or timing in this evidence passno performance claimkernel selection, numerical behavior, or speed on this machine

Three rules follow:

paper algorithm != every later source path
source path exists != runtime selected it
source-reported benchmark != local deployment result

Corrections To The A19 Handoff

The short FA3 section in A19 is a useful historical pointer, but three claims need tighter boundaries:

not:
    Hopper is "up to 3x A100," therefore an attention kernel is 3x faster

instead:
    compare the measured kernel on a named GPU, precision, shape, and baseline

not:
    FA3 "uses QuIP"

instead:
    FA3 uses an orthogonal incoherent transform inspired by quantization work,
    including QuIP's line of ideas; this is one component of its FP8 path

not:
    a faster attention kernel creates semantic long-context ability

instead:
    it changes the runtime ceiling; model quality beyond the training regime
    is a separate position, data, and evaluation question

0x01 One Attention Equation, A Moving Hardware Bottleneck

FlashAttention-1 attacked HBM traffic. FlashAttention-2 improved work partitioning and reduced non-matrix overhead. That changes the denominator of the optimization problem: costs hidden behind HBM traffic become visible.

For one tiled forward iteration, the kernel repeatedly performs:

load K_j and V_j
S_j = Q_i @ K_j^T
P_j = online_softmax_update(S_j)
O_i = O_i + P_j @ V_j

A coarse resource model is:

only when independent engines and dependencies permit full overlap. Without overlap, the same terms approach a sum:

This gap is the scheduling opportunity. It is also constrained by finite on-chip state. A deeper pipeline needs more shared-memory stages, more accumulator slots, more registers, more barriers, or more TMEM columns.

The Four Contracts Of A Hardware Pipeline

ContractQuestion
DependencyHas the producer completed the exact tile a consumer will read?
LifetimeCan a buffer be reused, or is an older iteration still live?
CapacityDo SMEM, registers, TMEM, and barrier slots fit at the chosen tile/stage count?
NumericalDoes reordered or lower-precision execution still implement the intended attention result within the stated tolerance?

An asynchronous instruction does not remove these contracts. It makes them explicit.

0x02 Hopper Prerequisites: TMA, WGMMA, And Warpgroups

Hopper provides two primitives that reshape the kernel schedule. This section does not repeat M4.2’s general GPU hierarchy. It specializes that hierarchy into the contracts an SM90 attention mainloop must actually satisfy.

The Hopper Resource Envelope

The official Hopper Tuning Guide gives the following compute-capability 9.0 limits for H100:

ResourceH100 limit used in this articleWhy FA3 consumes it
register file65,536 32-bit registers per SMWGMMA accumulators, score fragments, online-softmax state, output state, pointers
architectural maximum255 registers per threadlarge consumer fragments approach the per-thread ceiling
resident thread blocksat most 32 per SMan architectural ceiling before registers and SMEM reduce residency
shared memory228 KiB per SMQ, staged K/V, optional P, barriers, and epilogue aliases
addressable shared memoryat most 227 KiB per blockCUDA reserves 1 KiB; allocations above 48 KiB require explicit dynamic-SMEM opt-in

These are capacity ceilings, not a residency result. A kernel still needs its compiled register count, rounded dynamic-SMEM allocation, thread count, cluster shape, and launch attributes. In particular, a legal 227 KiB block can leave only one such block resident, while a lower-SMEM specialization can still be register-limited.

Tensor Memory Accelerator

TMA performs asynchronous tensor transfers between global memory and shared memory. Official Hopper documentation describes one-dimensional through five-dimensional tensor copies, with address generation and bounds handling offloaded from ordinary per-element load instructions.

The important execution consequence is:

a small producer role can issue a large tile transfer;
consumer warps can spend their register and instruction budget on compute;
completion is communicated through pipeline barriers;

TMA does not mean data is immediately available after issue. The consumer still waits on the corresponding stage-complete state.

A Descriptor Is Not A Completed Tile

The host-side tensor map and the kernel-side TMA atom encode a multidimensional coordinate-to-address mapping. For one attention tile, the source-level transaction payload is:

where b is bytes per element. The pinned beta23 SM90 source computes these values with cute.size_in_bytes before constructing Q, K, and V TMA atoms. For B_M=B_N=d=d_v=128 and FP16/BF16, each transfer carries:

The transaction byte count is part of correctness, not only accounting. The PTX ISA TMA contract specifies a non-blocking cp.async.bulk.tensor operation whose global-to-shared completion can decrement the transaction count of an mbarrier. A consumer may treat the tile as complete only after the barrier phase observes both:

pending arrivals == 0
transaction count == 0

Calling arrive before the transfer finishes does not satisfy the second condition.

Full And Empty Are Different Barrier Facts

The pinned CuTe source allocates two barrier objects for Q and 2s each for K and V: one full and one empty state per stage. For s K/V stages:

The bytes are small; the state machine is not. A full barrier proves that the producer’s transfer for (stage, phase) completed. The empty barrier proves that every required consumer released that same stage before it is reused. One cannot substitute for the other.

The circular pipeline coordinate is a stage index plus phase parity:

Parity flips only when the index wraps. If a producer waits on the wrong parity, it can consume an old empty signal; if a consumer waits on the wrong parity, it can read a previous generation of the same SMEM address.

Shared Memory Is A Liveness Equation

At the pinned beta23 coordinate, the SM90 source builds one-stage Q storage, s-stage K/V storage, and optional shared P storage. Ignoring alignment and non-payload state:

When Q is retained in registers, the source aliases the Q and staged-V arena after Q’s shared lifetime ends:

For the 128x128, d=d_v=128, FP16/BF16 example with s=2 and register-sourced P:

Q payload                 32 KiB
two K stages              64 KiB
two V stages              64 KiB
without Q/V alias        160 KiB
with Q/V alias           128 KiB
raw Q/K/V barriers         80 B

This is a source-level lower bound. Swizzled layout cosizes, 1024-byte alignment, optional shared P, scheduler state, and compiler/runtime allocation still have to be reconciled with the launched function.

Warpgroup Matrix Multiply-Accumulate

Hopper WGMMA is a warpgroup-wide asynchronous matrix operation. One warpgroup is four warps, or 128 threads. Official WGMMA documentation exposes asynchronous issue, commit groups, and wait groups. Operand B comes from shared memory; operand A can come from shared memory or registers; accumulators reside in registers.

thread:
    scalar execution agent

warp:
    32 threads executing together

warpgroup:
    4 warps / 128 threads cooperating on WGMMA

CTA:
    producer and consumer warpgroups plus shared pipeline state

The PTX WGMMA contract has four distinct steps:

1. establish operand visibility and register ordering
2. issue one or more wgmma.mma_async operations
3. collect prior uncommitted operations with wgmma.commit_group
4. use wgmma.wait_group N before consuming an accumulator whose group must finish

wait_group 0 drains all prior committed groups. wait_group 1 lets only the newest committed group remain pending and waits until every older one has completed, which creates overlap but also keeps another accumulator generation live. Reading a destination register before the group containing its WGMMA completes is undefined behavior.

Two fences solve different problems:

Ordering primitiveOrdersDoes not prove
wgmma.fenceprior warpgroup register accesses before later WGMMA register accessesthat a TMA transfer completed
async-proxy fencegeneric-proxy writes to a shared operand before the async proxy reads itthat WGMMA accumulator results are ready
wgmma.wait_groupcompletion of committed WGMMA groupsthat another warpgroup reached a named scheduling rendezvous

The pinned source’s fence_view_async_shared() before a shared-memory P operand is therefore not interchangeable with warpgroup.wait_group(0). The first makes P visible to the async proxy; the second makes the WGMMA result safe to consume.

Register Reallocation Is A CTA Budget

Hopper also supports setmaxnreg, allowing producer warps to release register ownership and consumer warpgroups to request a larger maximum. The PTX instruction uses a per-CTA register pool; .inc can block until enough registers are available, and every warp in the warpgroup must execute the same operation. The immediate is an absolute per-thread maximum in [24,256], in multiples of eight.

At beta23, one producer warpgroup is paired with one, two, or three MMA warpgroups. The source requests:

MMA warpgroupsproducer maximumeach consumer maximumCTA ownership-hint sum
156256128*56 + 128*256 = 39,936
224240128*24 + 256*240 = 64,512
332160128*32 + 384*160 = 65,536

The last row exactly fills H100’s 65,536-register SM budget under this ownership arithmetic. It does not prove the kernel allocates those values, reaches a particular occupancy, or avoids spills. They are source-requested maxima. Compiled metadata remains required.

There is also a useful apparent mismatch to keep explicit: the Hopper guide lists an architectural maximum of 255 registers per thread, PTX permits a setmaxnreg immediate of 256, and the one-consumer source path requests 256. The safe interpretation is “source-level register-ownership hint at the legal PTX coordinate,” not “256 compiler-addressable registers were measured per thread.”

The reason a full 128-thread producer warpgroup enters the role branch even when one thread represents the TMA producer group is now visible: setmaxnreg is a warpgroup-collective contract. The beta23 code decreases registers for warps 0..3; inside the load routine, only the first producer warp handles the TMA path, and the TMA cooperative group is declared with size one.

Synchronization Is Plural

DomainSource-level operationProtected transitionTypical failure if omitted
TMA full/empty pipelineproducer_acquire, TMA issue, consumer_wait, consumer_releaseGMEM-to-SMEM completion and stage reusepartial tile read or early overwrite
WGMMA groupasync GEMM plus wait_groupaccumulator completionconsume an in-flight score/output fragment
async-proxy visibilityfence_view_async_sharedgeneric SMEM P write to WGMMA shared-operand readstale P operand
compute-WG schedulernamed barrier arrive/syncpingpong turn between consumer warpgroupsboth groups enter an unsafe schedule phase
register redistributionsetmaxregister_decrease/increaseproducer release and consumer acquisition of CTA register poolundefined divergent execution or blocked increase

A generic sync in pseudocode hides too much. This section names the object and the dependency each synchronization protects.

Pinned Source-To-Instruction Trace

The current implementation coordinate is fa4-v4.0.0.beta23, commit 2409214a03797b168f648ea30df1adbc09ce658a. It is broader than the FA3 paper artifact, so the trace records branches rather than pretending there is one universal Hopper path.

Branches Before Instructions

The pinned flash_fwd_sm90.py establishes:

architecture family:
    SM90a family only

K/V movement:
    TMA by default
    non-TMA paged-K/V branch when requested

Q movement:
    TMA when the packed-GQA tile is representable
    cp.async-style fallback otherwise

P-V operand A:
    registers when mma_pv_is_rs
    shared memory otherwise

overlap:
    intra-warpgroup two-stage path or non-overlapped path

Therefore “the Hopper kernel uses TMA” is too broad. Q, K/V, and O each have their own admission decision, and paged or irregular layouts can change the movement path while the architecture remains SM90.

From Source Abstraction To Expected PTX Family

Layer in the pinned sourceSource coordinateExpected PTX familyEvidence limit
Q/K/V tensor-copy atoms and byte countsflash_fwd_sm90.py lines 260-314cp.async.bulk.tensor...mbarrier::complete_tx::bytes on admitted TMA pathsexact PTX/SASS variant requires compilation
full/empty pipelines with producer/consumer groupsflash_fwd_sm90.py lines 451-516mbarrier init, arrive/expect-tx, parity wait, and release operationsCuTe/CUTLASS can change the concrete instruction sequence
producer and consumer register rolesflash_fwd_sm90.py lines 577-635setmaxnreg.dec/inc.sync.aligned.u32requested maxima are not compiled allocation evidence
QK/PV tiled MMA atomsflash_fwd_sm90.py lines 96-118wgmma.mma_async with source-selected register/shared operand modesexact shape and instruction count depend on specialization
non-overlapped tile bodyflash_fwd_sm90.py lines 1349-1408QK WGMMA, wait, softmax, proxy fence when needed, PV WGMMAsource order is not final scheduled SASS order
intra-WG overlap bodyflash_fwd_sm90.py lines 1411-1477QK and previous PV groups in flight, then wait_group 1 and wait_group 0achieved overlap requires a SASS/profiler check
pingpong scheduler barriersflash_fwd_sm90.py lines 1480-1545named barrier arrive/syncdoes not itself prove balanced WG issue

The older pinned hopper/mainloop_fwd_sm90_tma_gmma_ws.hpp remains useful as the historical FA3 C++ mainloop: it names PipelineTmaAsync, transaction bytes, producer acquire/commit, consumer wait/release, and explicit warpgroup waits. The current CuTe Python path is a later implementation surface, not a line-for-line transcription of that file.

One K Stage, End To End

coordinate:
    producer state = (stage i, phase phi)

producer:
    wait until EMPTY(i, phi) is complete
    configure FULL(i, phi) to expect X_K bytes
    issue tensor copy gK_j -> sK[i]

TMA engine:
    transfer X_K bytes
    signal transaction completion on FULL(i, phi)

consumer:
    wait until FULL(i, phi) completes
    issue QK WGMMA using sK[i]
    commit WGMMA group
    wait until the QK group is safe to consume
    release EMPTY(i, phi) only after sK[i] has no remaining readers

advance:
    i = (i + 1) mod s
    flip phi when i wraps

The TMA barrier and WGMMA wait occur in the same tile trace but certify different events. This separation is the core hardware contract.

Hopper source-to-instruction trace separating CuTe TMA atom construction, full and empty mbarrier phases, WGMMA issue and wait groups, named scheduler barriers, setmaxnreg role redistribution, and the unmeasured PTX and SASS proof boundary

Proof Boundary

This trace proves that the pinned source contains architecture-specific abstractions and ordering points consistent with Hopper’s documented instruction families. It does not prove the exact PTX opcode variant, generated SASS order, compiled registers, spills, selected runtime specialization, or achieved overlap. Those require build artifacts and profiler evidence in later loops.

No local GPU artifact or timing in this evidence pass: every source-to-instruction edge above remains source-audited and unmeasured.

State Ownership Ledger

ObjectPinned SM90 source locationProducerConsumerReuse condition
Q_i tileone-stage SMEM, optionally retained in registers so its arena can alias VTMA or Q fallback pathQK consumer warpgroupsQ consumers release its phase; aliased storage starts only after Q capture
K_j stages-stage circular SMEMTMA or K/V fallback pathQK WGMMAQK group no longer reads the stage, then consumer release
V_j stageseparate s-stage circular SMEM, possibly in the Q/V aliased arenaTMA or K/V fallback pathPV WGMMAPV group no longer reads the stage, then consumer release
score tile S_jFP32 consumer-register fragmentQK WGMMAmasks, modifiers, online softmax, P conversionscore-dependent scalar/vector work complete
probability tile P_jregister fragment or shared P arenasoftmax/conversion pathPV WGMMAPV has consumed the operand; proxy visibility required for SMEM mode
online max/sumFP32 registerssoftmax updatelater iterations and epilogueoutput tile complete
output accumulator O_iFP32 consumer registersPV WGMMA plus rescalingnext iteration/epiloguefinal store preparation complete
full/empty barriers2+4s raw mbarrier objects for Q/K/Vproducer, TMA completion, consumersopposite pipeline rolematching (stage,phase) completes

The table is the real pipeline. The instruction names are only its implementation.

0x03 FA3 Producer-Consumer Pipeline And Circular SMEM

The owner question is now narrower than the Hopper prerequisite question:

Given asynchronous movement and matrix engines, what state machine lets one warpgroup fill a finite shared-memory ring while other warpgroups consume it without reading early, overwriting live data, or deadlocking at the tail?

FlashAttention-3’s paper answers with a producer/consumer CTA and an s-stage circular SMEM buffer. The pinned beta23 implementation preserves that contract but specializes it further: Q has a one-stage pipeline, K and V have independent s-stage pipelines, their logical coordinates can be staggered by intra-warpgroup overlap, and the pipeline state survives across tiles assigned by the persistent scheduler.

Algorithm 1 Is A Contract, Not Literal Current Source

The paper’s Algorithm 1 gives the minimum dependency graph:

producer warpgroup:
    decrease its register ownership maximum
    load Q_i once
    for each logical KV tile j:
        acquire stage j mod s
        issue TMA(K_j, V_j)
        make the stage visible as full

consumer warpgroups:
    increase their register ownership maximum
    wait for Q_i
    for each logical KV tile j:
        wait for K_j
        complete Q_i K_j^T
        update online softmax state
        wait for V_j
        complete P_j V_j
        release stage j mod s

The current source should not be paraphrased as if every line above remained one operation:

ContractFA3 Algorithm 1Pinned beta23 CuTe path
Q lifetimeload once per query tileindependent one-stage Q pipeline with its own toggled phase
K/V storageone conceptual paired stageseparate K-full/K-empty and V-full/V-empty pipelines at a shared stage coordinate
tile traversalwritten as increasing jdense path traverses admissible n_block values from right to left
load engineTMATMA when admitted; paged fallback can use cooperative cp.async
consumer bodyone QK-softmax-PV iterationnon-overlap body or split first-half/steady/last-half overlap body
work assignmentone CTA-level query tilepipeline state persists through a persistent tile-scheduler loop

The paper is the algorithmic contract. The beta23 source is one release-specific realization of it.

One Logical KV Tile Has Several Coordinates

Let one scheduler work tile admit J K/V blocks. Define traversal ordinal r, not source block index, as:

For the dense beta23 path:

so r=0 refers to the rightmost admissible block. Its circular-buffer coordinates are:

These coordinates answer different questions:

CoordinateQuestion answered
work tile (m_block, head, batch)which output tile owns this traversal?
n_rwhich logical K/V block is being loaded?
optional page_idx(n_r)which physical page supplies the block?
stage i_rwhich SMEM slot holds it now?
epoch e_rhow many times has the ring wrapped?
parity phi_rwhich generation of this stage is a barrier wait observing?

n_r and i_r are not interchangeable. A paged source can change the physical address while preserving the same stage coordinate; a later traversal can reuse the same stage while referring to a different logical block.

Source Encoding Of Index And Phase

The pinned PipelineStateSimple packs index and phase into one integer z:

For reasoning, the barrier generation is the parity e(z) mod 2. The source returns the quotient e(z) directly and comments that an explicit modulo is omitted in this path. The evidence oracle normalizes the value to a parity bit rather than relying on that implementation shortcut.

Producer and consumer states deliberately start one generation apart:

consumer: z_c = 0  -> stage 0, full phase 0
producer: z_p = s  -> stage 0, empty phase 1

The opposite producer phase encodes that every ring entry is initially empty. For s=1, the source special-cases advancement by XORing the phase. For s>1, incrementing z changes stage each iteration and changes epoch only on wrap.

For s=2, J=5, and dense blocks [4,3,2,1,0]:

rn_rstage i_rconsumer raw phasesemantic full parityproducer raw phasesemantic empty parity
0400011
1310011
2201120
3111120
4002031

Stage zero appears three times, but each appearance carries a different generation. Waiting only on stage=0 would admit an ABA error: a consumer could mistake an old completion for the new payload.

Why A Circular Buffer Is Needed

Let P_X be the number of payloads committed to pipeline X and R_X the number released by its consumers. For each of the independent K and V rings:

The left inequality forbids releasing data that was never produced. The right inequality is the ring-capacity bound. Attempting to produce item r when r-s has not been released must block at producer_acquire; allowing it to proceed would overwrite a live generation.

At clean completion:

where A, C, W, and R count producer acquire, producer commit, consumer wait, and consumer release events. Equality of counts is necessary but not sufficient: each event must also carry the same (stage, phase) and logical payload identity.

The producer can run ahead only while empty generations remain. The consumer can run only after the matching full generation completes. In the ideal steady state the two agents therefore exert backpressure on each other rather than using an unbounded queue.

Startup, Steady State, And Drain

For a two-resource model with per-tile load time T_L, per-tile dependent compute time T_C, at least two stages, and no synchronization overhead, the finite-J schedule is:

The derivation exposes the three phases:

fill:        first tile must load before its compute can begin
steady:      each later tile advances at max(T_L, T_C)
drain:       the final consumer work completes after the last load issue

The serial baseline is:

so finite-work speedup is:

Only as J grows does this approach:

If T_L=300, T_C=500, and J=5, serial execution takes 4,000 idealized cycles while the two-stage lower bound is:

or 1.43x, not the asymptotic 1.6x. With J=1, there is no overlap benefit. With one SMEM stage, the next load cannot start before the current consumer releases the only slot, so this two-resource overlap model also collapses to the serial schedule.

These are dependency lower bounds, not H100 timings. TMA issue cost, WGMMA latency, barriers, masks, uneven K/V timing, occupancy, and scheduler work remain outside this model.

The Pinned Producer Trace

The beta23 producer initializes its K/V state once before the persistent scheduler loop. For each work tile it derives sequence and page coordinates, computes the admissible block interval, and walks that interval from right to left.

kv_write = producer_state(stages=s)       # source phase-index starts at s
q_phase = 1

while scheduler.has_work():
    work = scheduler.current()
    n_min, n_max = admissible_kv_interval(work)

    acquire(K, kv_write)
    load_and_commit(K[n_max - 1], stage=kv_write.index)
    acquire_and_load_Q(work, phase=q_phase)
    q_phase ^= 1

    acquire(V, kv_write)
    load_and_commit(V[n_max - 1], stage=kv_write.index)
    kv_write.advance()

    for n in reversed(range(n_min, n_max - 1)):
        acquire(K, kv_write)
        load_and_commit(K[n], stage=kv_write.index)
        acquire(V, kv_write)
        load_and_commit(V[n], stage=kv_write.index)
        kv_write.advance()

    scheduler.advance()

# Synchronization tail, not a dummy data tile.
producer_tail(V, kv_write)

That sketch is the non-overlapped TMA shape. The actual function also has paged cp.async, block-sparse, and staggered intra-warpgroup branches. In every branch, load_KV performs the source copy and then calls the matching pipeline’s producer commit.

The first s acquires still execute in source, but they should not block because the ring starts empty. This reconciles the paper’s statement that no waits are issued during fill with the pipeline abstraction’s unconditional-looking producer_acquire call: the logical wait returns immediately for an already-empty phase.

K And V Are Separate Reuse Proofs

The paper releases a conceptual paired K/V stage after PV. The pinned source can release the K generation earlier because QK is its final reader:

PayloadFirst legal consumer actionLast dependent actionLegal empty release
K at (i,phi)full wait completesQK WGMMA group completesafter wait_group makes the score fragment readable
V at (i,phi)full wait completesPV WGMMA group completesafter output accumulator update is complete

This separation permits the producer to reacquire a K slot while the corresponding V slot is still live. It does not permit advancing the logical K/V identity inconsistently. Both pipeline objects must agree on which traversal ordinal a stage generation represents.

The non-overlapped consumer body is therefore:

wait(K, read_state)
S = issue_QK(stage=read_state.index)
wait_wgmma_group(0)
release(K, read_state)

P, online_state = online_softmax(S, online_state)

wait(V, read_state)
O = issue_PV(P, V[read_state.index], O)
wait_wgmma_group(0)
release(V, read_state)
read_state.advance()

release(K) before QK completion is a use-after-reuse bug. release(V) before PV completion is the analogous output corruption bug. Advancing read_state does not release either barrier; advancement changes coordinates, while release changes ownership.

Intra-Warpgroup Overlap Splits The Loop Body

When intra_wg_overlap is enabled, the current source does not execute one complete QK-softmax-PV body per loop call. It peels the first and last halves:

first half:
    wait K_0 -> QK_0 -> release K_0 -> softmax_0

steady call at state r:
    clone state r for V_r
    advance state to r+1 for K_(r+1)
    issue QK_(r+1) and PV_r
    wait_group 1 -> release K_(r+1)
    softmax_(r+1)
    wait_group 0 -> release V_r

last half:
    wait V_last -> PV_last -> release V_last -> advance

This is why one shared kv_consumer_state can look off by one if read without the peeled-loop context. During the steady call, the cloned state names the previous V payload while the advanced state names the next K payload. 0x05 owns the performance and register-liveness consequences of that WGMMA-softmax overlap; this section owns the stage identities and release legality.

Tail Is Four Different Problems

“Handle the tail” is too vague for an asynchronous pipeline. At least four tails exist:

  1. Ragged data tail. If sequence length is not divisible by tile_n, the rightmost logical block contains invalid score columns. The source isolates the first traversed block and applies sequence masking. TMA and non-TMA paged paths also have different address-safety behavior; a masked score does not by itself make an invalid page-table read safe.
  2. Pipeline drain. After the last load, consumers must finish every produced K and V generation. No unproduced stage should receive a synthetic full signal merely to make the loop shape regular.
  3. Producer exit tail. The pinned source calls pipeline_v.producer_tail(kv_producer_state) because V is the final produced dependency. CUTLASS defines producer tail as ensuring used buffers are synchronized before producer exit so dangling barrier signals do not survive. It is synchronization closure, not another V tile.
  4. Persistent-work boundary. Q phase and K/V state are not reset for each scheduler tile. A work tile that produces no sparse blocks still releases Q and resets its numerical output state, but it must not invent K/V state advances.

Producer tail is not a payload. It changes no logical tile count and creates no consumer-visible data generation.

In other words, pipeline state persists across scheduler work tiles; the tile scheduler advances logical work without reinitializing the shared barrier generations.

For the dense branch, block interval, producer traversal, and consumer traversal must agree exactly:

For block-sparse execution, J is the number of selected block coordinates, not the width of the dense interval. The conservation equality must use that selected count.

Barrier State Machine

EMPTY(i, producer_phase)
    -- producer_acquire returns --> OWNED_BY_PRODUCER
    -- issue async copy + commit --> IN_FLIGHT(i, consumer_phase)
    -- expected bytes complete --> FULL(i, consumer_phase)
    -- consumer_wait returns --> OWNED_BY_CONSUMER
    -- final dependent WGMMA completes --> RELEASABLE
    -- consumer_release --> EMPTY(i, next_producer_phase)

Full and empty are independent facts. WGMMA completion is a third fact. A full barrier proves that movement completed for one payload generation; it does not prove that a consumer has stopped reading it.

Deadlock Closure By A Decreasing Wait-For Graph

For one ring, producer item r can wait for release of r-s, while consumer item r can wait for producer commit of r:

produce(r) waits on release(r-s), only when r >= s
consume(r) waits on commit(r)
release(r) follows completion of consume(r)

Following a producer wait backward gives:

The ordinal decreases by s. The first s producer acquires have an initially empty base case, so this graph is acyclic if producer and consumer enumerate the same payloads and every completed consumer releases. This is the liveness proof behind the ring, not merely an implementation convention.

The proof fails under these bugs:

BugBroken invariantLikely symptom
reset K/V state at every persistent work tilephase continuitywait on an old generation or overwrite a live stage
producer skips a selected block but consumer expects itcommit(r) existspermanent consumer full wait
consumer exits a branch without V releaseR_V=Jproducer blocks when the ring wraps or in tail
release K before QK wait_grouplast-reader completionintermittent score corruption
release V before PV wait_grouplast-reader completionintermittent output corruption
advance without releasecoordinate and ownership conflatedring appears to progress but empty barrier never arrives
release a stage that was never producedR_X<=P_Xlater producer observes false availability
reuse stage index without generation parityABA protectionstale completion accepted as current data
omit producer tail in a clustered pathexit synchronizationdangling arrives or cluster-dependent hang

The dangerous failures are temporal rather than shape errors: every tensor can have the expected dimensions while containing the next tile, the previous tile, or a partially completed transfer.

FlashAttention-3 two-stage producer-consumer pipeline showing reverse logical block order, stage and phase generations, separate K and V release points, startup, steady overlap, drain, producer tail, and the decreasing wait-for dependency used for deadlock closure

The visual is a dependency schedule, not a measured timeline. The source-audited pipeline proves ordering and liveness obligations; compiled instruction overlap and barrier-stall cost still require a Hopper build and profile.

0x04 FA3 Pingpong: Overlap Softmax With Another Warpgroup’s GEMM

The producer-consumer analysis in 0x03 separated data readiness from data reuse. Pingpong adds another, independent order: which consumer warpgroup may issue its next tensor-core bundle. It does not move K/V payloads and it does not transfer online-softmax state between warpgroups.

Why A Small Operation Can Own The Schedule

Faster matrix units make softmax visible. The FA3 paper gives the following H100 SXM5 comparison for head dimension 128:

matmul work / exponential work:       about 512x
matmul throughput / special-function throughput: about 256x

Convert operation counts and rates into time:

So even though there are far fewer exponentials, they can consume time equal to roughly half the matrix work in this source example. FP8 doubles the relevant tensor-core throughput while special-function throughput stays unchanged, making the ratio less favorable.

That ratio can be turned into an ideal scheduling bound. Let:

M = time for the ordered tensor-core bundle assigned to one consumer quantum
E = time for its scalar/MUFU softmax work

Two consumers executed serially take:

If the second consumer’s tensor work overlaps the first consumer’s softmax perfectly, the two-item flow-shop lower bound is:

and:

For a long stream of independent consumer quanta:

so the asymptotic ceiling is:

With the paper’s FP16 sketch, . The two-item ideal speedup is therefore 1.2x, while the long-stream ceiling is 1.5x. If FP8 halves while stays fixed, the two resources become closer to balanced and the asymptotic overlap opportunity approaches 2x. Neither number is a kernel prediction: barriers, instruction issue, dependencies, register pressure, tails, and imperfect compiler scheduling all reduce it.

The paper reports movement from roughly 570 TFLOP/s to 620-640 TFLOP/s for one FP16 forward configuration (d_h=128, sequence length 8192), or about 1.09-1.12x. It also reports 661 TFLOP/s when its tested optimizations are combined in another fixed configuration. These are source-reported ablations, not a universal pingpong multiplier.

Pingpong Orders Issue; It Does Not Exchange Numerical State

The paper’s picture can look like two warpgroups alternately compute the same output. That is not the useful implementation model. Each consumer warpgroup owns its own partitioned fragments and preserves its own online-softmax recurrence:

State or eventOwnerWhat crosses warpgroup boundaries?
local Q/S/P fragmentsone consumer warpgroupnothing
running row maximum, row sum, and output accumulatorone consumer warpgroupnothing
K/V stage readiness and reuseQ/K/V mbarrier pipelines from 0x03full/empty generation signals
WGMMA completionissuing warpgroupWGMMA commit/wait ordering only
pingpong permissionone named-barrier tokenissue-order credit, not tensor data

The current source partitions the tiled QK and PV operations by a warp_group_thread_layout. A consumer group advances the attention state for its own query-row partition. The named scheduler only delays the next tensor bundle long enough to encourage this pattern:

consumer WG0:
    issue GEMM bundle -> perform local softmax

consumer WG1:
    wait for token -> issue GEMM bundle while WG0 uses scalar/MUFU resources

then pass the issue token back

The phrase “roles swap” means the hardware-resource overlap swaps. It does not mean WG0 hands WG1 its state.

Paper, Historical Kernel, And Current Beta Are Three Evidence Surfaces

SurfaceScheduler contractBoundary
FA3 paper Figure 1two consumer warpgroups; bar.sync encourages one group’s softmax to overlap the other’s GEMMsconceptual schedule and source-reported ablation
historical Hopper C++ mainloop at the pinned committwo- or three-group named-barrier ring; explicit first signal; non-overlap and intra-WG branchessource behavior, not compiled proof
beta23 CuTe flash_fwd_sm90.pyone to three MMA warpgroups; scheduler barrier enabled only for selected compile-time casescurrent pinned implementation behavior

The current beta computes:

use_scheduler_barrier = (
    num_wg_mma >= 2 and tile_hdim <= 128
    if intra_wg_overlap
    else num_wg_mma == 2
)

That gives the following admission table:

Consumer warpgroups Intra-WG overlaptile_hdim <= 128Named scheduler
1eithereitherdisabled
2falseeitherenabled
3falseeitherdisabled in this branch
2 or 3trueyesenabled
2 or 3truenodisabled

This is why “FA3 uses pingpong” is too broad. The source has a compile-time scheduler decision, and the selected branch depends on warpgroup count, head dimension, and whether intra-warpgroup overlap is enabled.

Physical Warpgroup, Local Group, And Barrier ID

The CTA uses one physical producer warpgroup before the consumer groups. Define:

The beta23 forward enum reserves:

barrier 0: reserved for CTA-wide synchronization
barrier 1: Epilogue
barrier 2: WarpSchedulerWG1
barrier 3: WarpSchedulerWG2
barrier 4: WarpSchedulerWG3
barrier 5: PFull
barrier 6: PEmpty

Let be the first scheduler barrier. Consumer waits on:

The source expression is the same mapping in physical coordinates:

Each consumer warpgroup has threads. A scheduler generation pairs the current group with one signaling group, so the named-barrier participant count is:

This count is not interchangeable with the Q/K/V mbarrier arrival count. NVIDIA’s CUTLASS documentation explicitly distinguishes them: all participants in a named-barrier synchronization count, while an mbarrier pipeline can count only its signaling agents.

A Named Barrier Is A Reusable Generation, Not A Boolean Flag

PTX exposes 16 CTA named barriers, IDs 0..15. barrier.sync contributes the executing threads and waits; barrier.arrive contributes the executing threads without waiting. When the expected count is reached, the barrier releases waiters and automatically reinitializes for its next generation.

For scheduler barrier , a normal generation combines:

128 threads from consumer g executing sync(B_g, 256)
128 threads from predecessor pred(g) executing arrive(B_g, 256)

where:

The first generation of has no predecessor yet. mma_init() therefore deposits one bootstrap half-credit on from the first consumer group. Its first sync(B_0,256) supplies the other 128 arrivals and starts the ring.

Conceptually, if is a scheduler handoff ordinal:

At a stable persistent-work boundary, one half-credit remains pending on . All other scheduler barriers are empty. The scheduler token is therefore conserved:

While one group owns the token between sync completion and its arrive(next), no pending barrier credit exists. After arrive(next), ownership is encoded as one pending half-credit on the next group’s barrier.

Pinned Source Trace: Bootstrap, Wait, Issue, Pass

The current source reduces to this scheduler skeleton:

def scheduler_init(physical_wg):
    if use_scheduler_barrier and physical_wg == 1:
        barrier_arrive(B0, number_of_threads=256)

def wait_for_turn(physical_wg):
    local_wg = physical_wg - 1
    barrier_sync(B0 + local_wg, number_of_threads=256)

def pass_turn(physical_wg, G):
    local_wg = physical_wg - 1
    next_wg = (local_wg + 1) % G
    barrier_arrive(B0 + next_wg, number_of_threads=256)

For two consumer groups, one circuit is:

OrderEventBarrier state after event
0WG0 bootstrap arrive(B0)half-credit pending on B0
1WG0 sync(B0) completesWG0 owns token
2WG0 issues its tensor bundleWG1 still blocked on B1
3WG0 arrive(B1)half-credit pending on B1
4WG1 sync(B1) completesWG1 owns token
5WG1 issues its tensor bundleWG0 can perform non-tensor work
6WG1 arrive(B0)token rests on B0 for the next circuit

For three consumer groups, the same source becomes B0 -> B1 -> B2 -> B0. Figure 1 remains a two-group explanation; it should not be used as proof that every current source specialization launches exactly two consumers.

The Non-Overlap Path Has Startup, Middle, And Drain Grants

Without intra-warpgroup overlap, the source places one scheduler sync before the first block, one arrive after QK, another sync before PV, and a final arrive after the last PV:

wait_for_turn()                       # startup grant for QK(0)

for j in logical_kv_blocks:
    issue_QK(j)
    pass_turn()                       # peer may issue its tensor bundle

    finish_QK_and_run_softmax(j)
    wait_for_turn()                   # returned grant
    issue_and_finish_PV(j)
    # The next loop immediately issues QK(j+1) before passing again.

pass_turn()                           # drain grant after PV(last)

For K/V blocks, each consumer executes:

named-barrier syncs and the same number of arrives. The grants cover:

grant 0:       QK(0)
grant 1..J-1: PV(j-1) followed by QK(j)
grant J:       PV(J-1), then pass the drain token

The final arrive is not optional cleanup. Without it, the peer can remain blocked in its final sync or the next persistent work tile can start with the token stranded in the wrong group.

The Intra-Warpgroup Path Moves The Barriers Into The Steady Pair

The peeled overlap path from 0x03 has a different scheduler count:

startup:
    QK(0) outside the scheduler ring

steady j = 1..J-1:
    wait_for_turn()
    issue QK(j) and PV(j-1)
    pass_turn()
    finish dependencies and update softmax

drain:
    PV(J-1) outside the scheduler ring

The scheduler handoffs per consumer are therefore:

For J=1, the branch performs no steady-state scheduler handoff. The bootstrap half-credit remains on B0, ready for a later work tile. This is legal precisely because initialization is CTA-scoped rather than work-tile-scoped.

This subsection only owns where the cross-warpgroup token is consumed and passed. The register-buffer dependency that makes QK(j) and PV(j-1) legal remains the 0x05 owner.

Persistent Work And Sparse Branches Must Preserve The Token

In the pinned source, mma_init() executes once before the persistent while work_tile.is_valid_tile loop. Scheduler barriers are not reinitialized for every output tile. A full token-ring circuit returns one pending half-credit to B0, which the next work tile consumes when it first enters a scheduled steady region.

Dense, causal, local, and block-sparse branches can execute different numbers of arithmetic operations, but every participating consumer warpgroup must make the same scheduler decision and execute the same number of scheduler calls for a work tile. The block-sparse helper makes this explicit:

non-empty sparse work, non-overlap:
    one initial sync
    matched per-block scheduler calls
    one final arrive

empty sparse work:
    every consumer skips the scheduler calls together
    pending token remains unchanged

intra-overlap sparse work:
    first/steady/last split preserves the steady handoff count

The historical Hopper mainloop contains an even sharper warning. An append-K/V detour that temporarily reuses the first scheduler barrier consumes the pre-seeded WG0 credit before the detour and re-signals it afterward. The bootstrap arrival is state, not a harmless annotation.

Cross-Warpgroup Liveness Proof

Let the forward scheduler rank be:

For , consumer can leave its sync only after passes the token. Consumer depends on . Every non-bootstrap wait therefore points to the immediately preceding rank:

The base case is the bootstrap half-credit for . Because each recursive dependency decreases by one, the wait-for graph is acyclic under these obligations:

all 128 threads in a participating warpgroup execute each barrier call convergently
every granted group eventually reaches exactly one pass_turn for that grant
all groups choose the same scheduler-enabled branch and work-count path
the barrier ID and participant count are identical for both halves of a generation
WGMMA and K/V dependencies needed before pass_turn eventually complete

The proof establishes protocol liveness, not scheduler fairness or performance. A compiler can still reduce overlap, a long softmax can still leave tensor units idle, and a resource stall can delay the group that owns the token.

Why One Named Barrier Per Consumer Group

DesignBenefitCost or failure pressure
no scheduler barrierno barrier instruction overheadtensor bundles can cluster and leave MUFU/tensor overlap to incidental scheduling
CTA-wide barriersimple global phaseincludes unrelated producer work and destroys fine-grained overlap
one named barrier reused for every groupfewer IDsgenerations are ambiguous when a fast group reaches the next turn early
per-group named-barrier ringexplicit predecessor/successor and automatic generation resetconsumes of 16 hardware barrier IDs and requires exact call balance
three-group ringmore independent consumer worklower per-group register budget and more scheduling/resource pressure; not automatically faster
intra-WG overlap onlyfewer cross-group handoffsloses deliberate GEMM/MUFU ordering between consumers
combined intra- and inter-WG schedulingexposes both dependency dimensionsmost complex startup, steady, drain, and proof surface

An mbarrier array could encode a more general scheduler, but it would introduce shared-memory state, explicit phase handling, and different participant accounting. The pinned kernel uses mbarriers for payload readiness and named barriers for issue order; preserving that separation makes the proof auditable.

Scheduler Failure Matrix

FailureImmediate effectLater symptom
omit bootstrap arrivalevery consumer waits on an empty ringCTA deadlock at first scheduled bundle
use 128 instead of 256 participantsa group can complete its own barrier generationordering collapses; peer arrival enters the wrong generation
use more than 256 participantsthe two consumer halves cannot satisfy the countpermanent named-barrier wait
map physical WG directly without subtracting producer WGwait and arrive target different IDsone barrier starves while another accumulates arrivals
one group skips a conditional barriergeneration counts divergeshape- or mask-dependent deadlock
omit non-overlap final arrivetoken remains owned after final PVpeer drain or next persistent tile hangs
re-prime every work tileduplicate half-credit enters an active generationphase drift, premature release, or deadlock
treat named barrier as K/V readinessissue order is mistaken for payload completionstale shared-memory read or early stage release
assume the paper timeline is compiled factperformance claim lacks artifact proofcorrect kernel runs slower than the clean diagram predicts

Runtime Proof Surface

The source predicts the protocol, but a Hopper artifact is needed to prove it survived compilation. A complete runtime record should include:

compile-time branch:
    num_wg_mma, intra_wg_overlap, tile_hdim, use_scheduler_barrier

artifact:
    BAR.SYNC / BAR.ARRIVE family, barrier IDs, participant count
    WGMMA issue/commit/wait placement around those barriers

dynamic balance:
    work tiles and logical K/V blocks per CTA
    expected scheduler handoffs for the selected branch
    barrier-stall and issue-stall observations

resource overlap:
    tensor-pipe activity
    MUFU/special-function activity
    timeline evidence that the activities overlap

correctness:
    dense, causal, local, sparse, empty, and ragged cases
    repeated persistent work tiles

For the non-overlap branch, the source-level expected count per consumer and non-empty work tile is J+1 syncs and J+1 arrives. For the intra-overlap branch it is max(J-1, 0) of each. These are static source expectations, not measured instruction counts; compiler specialization and disabled scheduler branches must be recorded before comparison.

FA3 pingpong scheduling overlaps one warpgroup's softmax with another warpgroup's GEMMs

Source: Shah et al., FlashAttention-3, Figure 1. The diagram is an intended schedule, not a guarantee that every issued instruction overlaps perfectly after compilation.

FlashAttention-3 cross-warpgroup scheduler showing the bootstrap credit, per-group named barriers, two-consumer token ring, non-overlap and intra-warpgroup handoff counts, persistent token state, and liveness invariants

The reconstruction makes the source protocol visible; it is not a measured instruction timeline. The original paper figure explains the intended resource overlap, while the reconstruction explains how the pinned source tries to enforce progress.

0x05 FA3 Two-Stage WGMMA-Softmax Pipeline

Pingpong in 0x04 orders two consumer warpgroups. This section asks a different question inside one consumer warpgroup:

How can the kernel overlap WGMMA with online softmax without reading an unfinished accumulator, overwriting an in-flight register operand, or spending so many registers that the schedule loses residency?

The answer is not merely “add another stage.” A legal two-stage schedule needs four contracts at once:

algorithm:
    QK(j) -> softmax(j) -> PV(j)

WGMMA queue:
    commit QK before PV; wait by group age

register liveness:
    S_next, P_current, and O coexist across the overlap window

compiler evidence:
    the intended source order must survive lowering without spills or serialization

Paper Algorithm, Current Source, And Compiled Artifact

FA3 Algorithm 2 is the paper contract. It peels the first QK/softmax and the final PV, then overlaps the middle pairs. The current fa4-v4.0.0.beta23 CuTe source implements the same dependency shape with different abstractions: mma_qk_fn, mma_pv_fn, PipelineAsync K/V states, and explicit warpgroup.wait_group calls.

Those are related but not interchangeable evidence surfaces:

SurfaceWhat it establishesWhat it does not establish
FA3 Algorithm 2intended two-stage numerical and wait orderbehavior of every later implementation
beta23 source at commit 2409214...one concrete source path and compile-time alternativesemitted PTX/SASS or selected runtime kernel
resolved CuTe/Quack/compiler environmentexact helper and lowering versionsruntime overlap by itself
PTX/SASS artifactactual instruction families, order, registers, and spillsdynamic overlap or end-to-end speedup
profiler traceexecution and stalls for one kernel coordinateuniversal behavior across shapes

This section is paper-verified and source-audited, but locally uncompiled and unmeasured.

Derive The Two-Stage Schedule From The Dependency DAG

For one query block i and K/V traversal tile j:

Writing the rescaled old output accumulator as makes the last dependency two explicit steps:

The three operations are not mutually reorderable:

QK(j) must finish before S_j is read by softmax.
softmax(j) must produce P_j before PV(j) reads P_j.
PV(j) must finish before O is rescaled, reused, or stored.

The legal overlap comes from adjacent tiles. After peeling tile 0, steady pair j performs:

older numerical state:    P_(j-1), V_(j-1), O
newer numerical state:    Q, K_j, S_j

1. issue and commit QK(j)
2. issue and commit PV(j-1)
3. wait until QK(j) is complete, while PV(j-1) may remain pending
4. compute softmax(j) from S_j
5. wait until PV(j-1) is complete
6. convert/store P_j and update the online-softmax output scale

The complete finite schedule is therefore:

PhaseMatrix workScalar/register workBoundary
StartupQK(0)softmax(0), form P_0no older PV exists
Steady j=1..J-1QK(j) then PV(j-1)softmax(j) overlaps the pending PVtwo WGMMA groups
DrainPV(J-1)final rescale/storeno newer QK exists

Let M_Q, M_V, and E denote normalized time for QK WGMMA, PV WGMMA, and the scalar softmax/update path. A serial traversal has the dependency lower bound

Because softmax cannot begin until QK completes, the steady pair is bounded by QK followed by the slower of softmax and PV:

Including startup and drain gives the idealized finite bound

For M_Q=M_V=E=1 and J=5, serial work is 15 normalized units while the dependency-only two-stage bound is 11, a 15/11 ~= 1.36x finite speedup. This is a scheduling bound, not a benchmark prediction: issue bandwidth, WGMMA latency, barriers, occupancy, and compiler placement are omitted.

FA3 two-stage WGMMA-softmax pipeline overlaps matrix work from one iteration with softmax from the next

Source: Shah et al., FlashAttention-3, Figure 2.

wait_group(1) Is An Age Proof, Not A QK Label

WGMMA groups are ordered commit groups; they are not named “QK” and “PV” by the hardware. The pinned helper performs:

warpgroup.fence()
cute.gemm(...)
warpgroup.commit_group()
if wg_wait >= 0:
    warpgroup.wait_group(wg_wait)

The overlap path calls both GEMMs with wg_wait=-1, so the helper commits each group but leaves waiting to the caller:

oldest                                              newest
group g:     QK(j)  committed first
group g+1:   PV(j-1) committed second

wait_group(1): at most one newest group may remain pending
               -> QK(j) is complete
               -> PV(j-1) may still be pending

wait_group(0): no prior groups may remain pending
               -> PV(j-1) is complete too

The official PTX contract makes two uses undefined before a wait that covers their WGMMA group: accessing the accumulator, and accessing or reusing a register fragment used as matrix A. Applied to this schedule:

Resource/actionEarliest legal pointReason
read or transform S_jafter wait_group(1)QK is the older completed group
release shared K_j stageafter wait_group(1)QK has finished reading K
read/rescale O after PV issueafter wait_group(0)PV accumulates into O asynchronously
overwrite register P_(j-1) on the RS PV pathafter wait_group(0)P is PV’s register-A fragment
release shared V_(j-1) stageafter wait_group(0)PV has finished reading V

wgmma.fence solves a different problem: it orders earlier register accesses against a later WGMMA access to the same accumulator or register-A fragment. Shared-memory producers additionally need the async-proxy visibility protocol derived in 0x02-0x03. A fence is not completion; a wait is not a proxy fence.

Pinned Beta23 Trace: Two Commits, Two Different Waits

The current source’s mma_one_n_block_intrawg_overlap path has this exact shape:

Source stepGroup stateLive-state consequence
wait for next K; scheduler syncno new WGMMA groupK stage becomes readable
mma_qk_fn(..., wg_wait=-1)commit QK as older groupacc_S exists but is pending
optional rescale_O_before_gemmonly QK pendingO is still legal to touch
wait for previous VQK pendingV stage becomes readable
mma_pv_fn(..., wg_wait=-1)commit PV as newer groupO and P become in-flight operands
warpgroup.wait_group(1)QK complete; PV may remainrelease K; consume acc_S
score modifiers, mask, online softmaxPV may remainscalar path overlaps the newer group
warpgroup.wait_group(0)all prior WGMMA completerelease V; reuse P; touch O
convert acc_S -> tOrPno prior PV uses tOrPprepare probability operand for next pair

This explains an otherwise surprising source detail on the register-shared (RS) PV path: conversion into the persistent tOrP register fragment occurs after wait_group(0), not immediately after softmax. Reusing that fragment while the previous PV group still reads it would violate the PTX register-input contract. On the shared-memory (SS) PV alternative, converted P is copied through a shared-memory path with its own proxy-fence and visibility obligations; the persistent register-A interval below applies specifically to RS PV.

The beta package does not completely pin this helper implementation. Its dependency is quack-kernels>=0.5.3, without an upper bound. The audited 0.5.3 wheel implements wg_wait=-1 as “commit with no helper wait,” and the same core wrapper is present in 0.6.1; a reproducible compiled artifact must still record the actually resolved Quack wheel and hash.

Register Liveness Is The Capacity Contract

The important question is not whether a tensor is conceptually temporary. It is whether its live interval intersects the overlap window.

resource             live interval in a steady pair
-------------------  -------------------------------------------------
O accumulator        whole query traversal; in-flight during PV
P_(j-1) register A   before PV issue through wait_group(0)
S_j accumulator      QK issue through softmax and P_j conversion
row m and l          whole query traversal
row_scale            softmax(j) through the selected O-rescale point
K_j shared stage     K wait through wait_group(1), then releasable
V_(j-1) shared stage V wait through wait_group(0), then releasable

Let G consumer warpgroups cooperate on a row-partitioned tile, so the average ownership denominator is 128G consumer threads. For the RS PV path, a logical 32-bit-register-word lower bound is:

where b_P is bytes per probability element. These are payload averages, not compiler register counts. Fragment layouts, padding, predicates, descriptors, row reductions, addresses, and compiler temporaries add state; uneven ownership can change the per-thread maximum.

For B_r=B_c=d_v=128, G=2, FP32 O/S, and FP16 P:

Live payloadCTA-wide bytesAverage 32-bit words per consumer thread
O64 KiB64
S_j64 KiB64
P_(j-1)32 KiB32
RS overlap subtotal160 KiB160

That 160 is a logical lower bound for the central RS live payload, not a prediction that ptxas will report exactly 160 registers. An SS PV path moves the persistent P operand into shared memory but adds store, visibility, and shared-memory-capacity costs. The pinned source’s two-consumer budget hint is 240 registers per consumer thread; for RS PV, the difference must cover online-softmax state, descriptors, masks, addresses, fragment-layout overhead, and compiler temporaries. A spill or one extra live fragment can erase the expected overlap benefit.

The paper describes the two-stage increment through the extra score accumulator:

For B_r=B_c=128 and FP32 score accumulators:

That is 16,384 logical 32-bit values across the CTA, or 64 values per consumer thread under the G=2 even-ownership coordinate. The actual maximum remains a compiled property.

FlashAttention-3 two-stage schedule showing QK and PV WGMMA group age, wait-group legality, score probability and output register live intervals, and the source-to-SASS compiler proof ladder

The reconstruction combines the paper algorithm, official PTX rules, and the pinned beta23 source. It is a dependency and liveness proof, not a captured GPU timeline.

Rescale Before Or After PV Is A Real Design Choice

The online update needs O expressed in the new row-max reference frame. The pinned source has two legal placements:

rescale_O_before_gemm = true:
    rescale O after QK issue but before PV issue
    keep scores_scale row state for the next update

rescale_O_before_gemm = false:
    issue PV first
    wait_group(0)
    rescale the now-complete O with row_scale

The first placement overlaps O rescaling with QK but must finish before O becomes PV’s in-flight accumulator. The second shortens pre-PV scalar work but puts rescaling after the full drain. They are algebraically coordinated with the source’s scores_scale state; moving the instruction without moving the reference-frame bookkeeping is a numerical bug, not a harmless schedule change.

Why The Paper Stops At Two Stages

A three-stage proposal overlaps:

QK(j+2), softmax(j+1), and PV(j)

It exposes more independent work, but FA3 Appendix B.3 reports two costs. First, relative to the two-stage design it needs another probability tile and row-scale state:

At B_r=B_c=128 with FP16 probabilities, this is 32.5 KiB, about 32.5 more logical 32-bit words per consumer thread for G=2, before layout overhead. Added to the 160-word two-stage subtotal, it leaves much less of a 240-register source budget for everything else.

Second, the paper reports that its compiler did not realize the intended three-way overlap: only the first WGMMA overlapped useful non-WGMMA work, while the second remained effectively unpaired. The paper therefore found the three-stage variant worse in practice. More graph-level parallelism is not useful when register pressure forces a smaller tile or lowering does not preserve the schedule.

VariantExtra live-state obligationCompiler obligationMain failure mode
One stageO plus current S/P transitionordinary WGMMA correctnessscalar path serializes matrix work
Two stageenforce S_next + P_current + O coexistenceQK/PV group age and scalar interleavingspills or premature waits
Three stageadd next P and row scale beyond two stagepreserve three-way overlapregister pressure plus failed lowering

The source-reported FA3 Table 2 ablation also shows why the techniques cannot be treated as additive toggles. For non-causal FP16 forward at {batch,seqlen,nheads,hdim}={4,8448,16,128} on H100:

Paper configurationTimeThroughput
full FA33.538 ms661 TFLOP/s
warp specialization, no GEMM-softmax pipeline4.021 ms582 TFLOP/s
GEMM-softmax pipeline, no warp specialization4.105 ms570 TFLOP/s

These are source-reported measurements for one coordinate. The last two rows remove different components; their difference is not a clean estimate of either component’s isolated contribution.

The Compiler Schedule Needs A Proof Ladder

Source order is only the first rung. A release artifact should preserve this identity before claiming overlap:

source:
    FlashAttention release/commit and exact file hash

dependency closure:
    resolved CuTe DSL, CUTLASS, Quack, CUDA, nvcc, ptxas, target, flags

compile coordinate:
    dtype, B_r, B_c, d_h, G, stages, RS/SS PV path,
    rescale placement, causal/mask branch, generated kernel symbol

artifacts:
    IR/PTX hash, cubin hash, SASS hash, ptxas registers, local memory, SMEM

The proof then proceeds in four rungs:

  1. Source DAG: QK commits before PV; wait_group(1) precedes score consumption; wait_group(0) precedes P reuse, V release, and post-PV O access.
  2. PTX contract: wgmma.fence, two wgmma.commit_group operations, then wgmma.wait_group 1 and 0 appear in the legal regions. PTX can still differ in exact instruction count after specialization.
  3. SASS static evidence: identify the corresponding WARPGROUP.ARRIVE, HGMMA, and dependency-barrier instructions; inspect instruction order, register count, and local-memory spill traffic. Static order supports a schedule argument but does not prove simultaneous engine activity.
  4. Dynamic evidence: profile the exact symbol and coordinate, verify tensor/scalar activity, dependency and barrier stalls, achieved occupancy, local-memory traffic, correctness, and timing distribution.

FA3 Appendix B.2 supplies a source-reported example of rung 3 for the paper artifact. Its simplified SASS places row-max/exponential work early, interleaves F2FP, MUFU, and scalar instructions among the first HGMMA bundle, emits the second WGMMA bundle without comparable non-WGMMA interleaving, and later drains with a dependency barrier. This supports the paper’s two-stage explanation. It does not prove that the current beta23 source compiled in this repository produces the same SASS.

Compiler And Liveness Failure Matrix

FaultWhy source review may miss itObservable evidence
helper turns wg_wait=-1 into a waitdependency source still looks stagedPTX/SASS serializes after each GEMM
wait_group(0) emitted where 1 was intendednumerically correctno scalar/PV overlap; higher dependency stalls
score read before the covering waithigh-level IR may obscure async useinvalid PTX contract or wrong results
P registers overwritten before PV completionvalues appear logically “consumed” at issuewrong output; register-input use before wait
O rescaled while PV is in flightscalar statement looks independentaccumulator race/undefined behavior
K/V stage released before its WGMMA completespipeline counters remain balancedproducer overwrites data still being read
compiler extends S, P, or mask temporariesequations count only payloadshigh register count, lower residency, spills
SASS contains desired order but runtime does not overlapstatic listing has no timing axisprofiler shows pipe or dependency stalls
profile captures another specialized kernelsymbol family looks similarcompile key/hash mismatch

The design rule is:

dependency graph exposes overlap
    -> wait placement makes it legal
    -> liveness determines whether it fits
    -> compiler artifacts show whether lowering preserved it
    -> profiler and correctness runs show whether it helped

0x06 FA3 FP8: Layout Is Part Of The Numerical Path

Changing Element from BF16 to FP8 is not a dtype substitution. It changes four contracts at once:

ContractChoice made in the FA3 FP8 pathWhat breaks if it is hidden
representationE4M3 operands and probability fragments, FP32 matrix accumulatorsinput width gets confused with accumulation or output width
layoutFP8 WGMMA shared-memory operands are k-major; P register ownership must match operand Amathematically valid tiles reach the instruction in an illegal physical arrangement
scalingQ/K factors enter scores; V factors enter output contributions; the axis determines legal factorizationa scale is applied after a nonlinear operation or broadcast over the wrong block
transformthe same orthogonal transform may precede Q/K quantizationpre-quantization equivalence is misreported as exact FP8 arithmetic

The owner question is therefore narrower than M8’s model-wide quantization question:

How does one FA3 Hopper forward path turn E4M3 Q/K/V and FP32 online-softmax state into legal FP8 WGMMAs without losing scale, reduction-axis, or layout semantics?

The answer needs two evidence coordinates. The FA3 paper specifies natural-block quantization and an incoherence transform. The pinned fa4-v4.0.0.beta23 C++ Hopper path at commit 2409214... exposes a different public scale granularity. They are related, not identical implementations.

E4M3 Names The Operand, Not The Whole Arithmetic Path

E4M3FN has one sign bit, four exponent bits, and three explicit fraction bits. Its largest finite magnitude is 448; its smallest positive subnormal is . The finite-only variant has a NaN encoding rather than infinity. A value outside the finite range must saturate, become non-finite under another conversion policy, or be prevented by scale selection.

The pinned public C++ admission path accepts PyTorch Float8_e4m3fn Q/K/V on SM90 or newer, requires the last dimension to be contiguous, requires head dimensions to be multiples of 16, and returns BF16 output for FP8 input. Internally, the Hopper template recognizes E4M3 and E5M2 as FP8 types, but that internal predicate does not widen this public API contract. Its forward path is:

Q, K, V storage:          E4M3FN
QK WGMMA accumulator S:  FP32
softmax row state:        FP32
probability operand P:   E4M3FN after FP32 exponentiation
PV WGMMA accumulator O:  FP32
public output:            BF16

This is mixed-precision attention. Calling it an “FP8 accumulator” would be wrong.

Derive Where Every Scale Must Enter

Let the real-valued blocks be approximated by E4M3 code values and positive descales:

For query block and K/V block , the unquantized score block is

Substitution gives the low-precision matrix product and its dequantization factor:

The product must affect the score before row maximum, exponentiation, and normalization. Applying it after softmax is not equivalent because softmax is nonlinear.

The V scale has a different factorization. Partitioning the output over K/V traversal blocks gives

If changes with j, each PV tile contribution must enter the online output accumulation in the right scale frame. One final scalar cannot be pulled outside the sum. If one is constant for the whole batch/KV-head traversal, then

and the factor can legally be folded into final row normalization.

This exposes a paper/source divergence that must remain visible:

SurfaceQ descale axisK descale axisV descale axisConsequence
FA3 Section 3.3query block [B_r,d]K block [B_c,d]V block [B_c,d_v]score and PV contribution factors can vary per traversal tile
pinned beta23 public C++ API[batch, kv_head][batch, kv_head][batch, kv_head]Q/K product is constant for that head; one V factor can be applied at finalize
missing descale pointerimplicit 1.0implicit 1.0implicit 1.0caller has asserted that E4M3 values already use the intended numerical units

The pinned API checks exact descale shape (batch_size, num_heads_k), including Q descale. It does not accept the paper’s query-block or K/V-block scale arrays through this interface. The bundled FP8 benchmark also passes unit descales; that benchmark scaffolding is not evidence that natural-block scaling is implemented by the public call.

Why The Source Raises Softmax Probabilities By 2^8

Ordinary unnormalized softmax values lie in [0,1], using little of E4M3’s positive range and making small probabilities more likely to underflow. The pinned Hopper Softmax<..., Max_offset=8> instead forms

The FP32 row sum receives the same factor:

Ignoring P conversion error for one moment,

so the offset cancels exactly. The source also divides the stored sum by 2^8 when reconstructing log-sum-exp. The numerical purpose is not to change attention; it is to present a wider [0,256] probability range to E4M3.

Conversion makes the actual path approximate:

The denominator is accumulated from FP32 u, while PV consumes rounded E4M3 probabilities. The offset can reduce underflow; it does not remove E4M3 rounding or saturation.

Renderer-safe scale path:

raw QK WGMMA:
    C_ij = q8_i @ k8_j.T                  # FP32 accumulator

score entering softmax:
    S_ij = alpha * dQ_i * dK_j * C_ij

probability operand:
    u_ij = 2^8 * exp(S_ij - row_max_i)    # FP32
    p8_ij = E4M3(u_ij)                    # approximate

pinned head-wide V scale:
    O_i = dV_bh * sum_j(p8_ij @ v8_j) / sum_j(u_ij)

paper block-varying V scale:
    O_i = sum_j[dV_j * (p8_ij @ v8_j)] / sum_j(u_ij)

FP8 WGMMA Turns V Layout Into A Producer Job

For FP16/BF16 Hopper WGMMA, a shared-memory operand can use the relevant mn-major or k-major mode. FA3 notes that FP8 WGMMA accepts only k-major shared-memory operands. Q and K commonly arrive contiguous in head dimension, which matches their reduction dimension in QK. V also commonly arrives contiguous in head dimension, but in P @ V the reduction dimension is sequence/block position. TMA can tile and swizzle; it does not exchange the contiguous matrix dimensions.

Three designs are possible:

DesignBenefitCost or constraint
store V sequence-contiguous upstreamdirect FP8 PV consumer layoutchanges the external V/cache layout and every producer/consumer agreement
standalone global transposesimple attention mainloopanother launch and HBM read/write unless fused elsewhere
in-kernel tile transposepreserves head-contiguous public input and avoids standalone HBM trafficextra SMEM buffer, producer instructions, barrier state, and shape constraints

FA3 and the pinned C++ path choose the third design for row-major V. The source sets Transpose_V = Is_FP8 && !V_colmajor, uses MN as V’s TMA major mode and K as its WGMMA major mode, and expands the producer from one warp to a full 128-thread warpgroup.

The source-level state machine is:

1. TMA writes head-contiguous V_j into Vt_smem[stage].
2. The producer warpgroup waits on pipeline_vt[stage].full.
3. LDSM.T loads packed 16-bit units from Vt_smem to registers.
4. Each 16-bit unit contains two FP8 values; byte_perm separates/reorders them.
5. STSM writes the k-major tile into V_smem[stage].
6. An async shared-memory fence publishes the transposed view.
7. pipeline_v[stage] is committed for the WGMMA consumer.
8. A 128-thread named barrier closes collective register/shared-memory use.
9. Only then is the original Vt stage released to TMA.

The audited templates require d_v % 32 == 0, B_c % 32 == 0, and either d_v % 64 == 0 or B_c % 64 == 0. They choose 64 x 32 or 32 x 64 transpose blocks accordingly. The separate Vt source and V destination stages are not free merely because FP8 uses one byte per value.

The Physical Transpose Includes A Paired Mathematical Permutation

LDSM.T and STSM are described around 16-bit entries. Packing two FP8 values into each entry leaves an 8-bit ordering problem that transpose instructions alone cannot solve. FA3 Appendix B.8 inserts byte permutations with selectors 0x6420 and 0x7531 between the collective load and store.

The faster pinned layout also permutes V’s eventual output-feature columns. The consumer must compensate. For a permutation of the common reduction coordinate,

This is an exact reindexing identity, not a quantization transform. Permuting only P or only V silently changes the result. The row-major-V path later calls permute_output_fp8 to undo its chosen output-column arrangement.

FP32 Score Ownership Is Not FP8 Operand-A Ownership

The first WGMMA writes S into FP32 accumulator registers. After exponentiation, those same logical entries must become the FP8 register-A operand of the second WGMMA. Downcasting values does not automatically move each byte to the lane and register expected by operand A.

FA3 Figures 3, 4, and 9 derive the four-lane exchange with __byte_perm and __shfl_sync. The pinned C++ source contains two specialized routes:

V routeP conversion/ownership repairOutput repair
head-contiguous V with in-kernel transposepermute the FP32 C-register view, reinterpret it as A-register layout, then convert to E4M3undo the chosen V-column permutation in O
already column-major Vreinterpret/convert, then use byte-permute and four-lane shuffle on E4M3 A registerscolumn-major-specific epilogue mapping

The hot path around the conversion is therefore tensor-level, not merely scalar:

// Pinned-source structure, abbreviated.
online_softmax(tSrS);                    // FP32 u, row_sum remains FP32
if (fp8 && row_major_v) permute_Cregs_fp8(tSrS);
tOrP_acc = reinterpret_as_pv_A_layout(tSrS);
convert_type_out(tOrP_acc, tOrP);        // FP32 -> E4M3
if (fp8 && v_colmajor) permute_Aregs_fp8(tOrP);
gemm_pv(tOrP, transposed_or_colmajor_V, tOrO); // FP32 O accumulator

Shape-compatible values in the wrong register ownership are still wrong. A test that checks only matrix dimensions cannot catch this class of bug.

FlashAttention-3 FP8 path separating E4M3 representation, exact Q and K transform, score and value scale axes, FP32 accumulation, probability range offset, V shared-memory transpose, and paired P/V register permutations

This is a source-derived contract reconstruction from FA3 Sections 3.3, B.7, and B.8 plus the pinned beta23 Hopper C++ path. It is not a captured GPU trace.

Incoherent Processing Is Exact Before Quantization Only

Let be orthogonal:

Apply the same right transform to Q and K:

Then

FA3 follows the incoherence-processing lineage of QuIP and QuIP#, using products of random sign diagonals and a normalized Hadamard transform so multiplication costs rather than . The paper says the transform can be fused with an upstream memory-bound RoPE operation. Fusion means one kernel can perform both operations; it does not mean an arbitrary rotation commutes with RoPE.

It should not be summarized as the whole kernel “using QuIP.” FA3 adapts one incoherence-processing idea to its Q/K FP8 path; it does not import QuIP’s weight-quantization objective, codebooks, or complete method.

V is not multiplied by this Q/K orthogonal transform. Do not conflate it with the paired P/V layout permutation above. The former changes the head-feature basis before quantization while preserving QK in exact arithmetic. The latter only reindexes the PV reduction axis.

After E4M3 conversion,

in general. The hoped-for benefit is geometric: a signed Hadamard mixes a concentrated coordinate into many coordinates, often making one shared scale less destructive. It is not universal.

Executable Oracle: Exact Identities And Geometry-Dependent Error

The FP8 evidence bundle contains a pure-Python E4M3FN codebook oracle. It is seed-fixed and accumulates in Python binary64; it is not a bit-exact CUDA conversion or a local H100 run.

For seed 3406, it verifies:

CheckMaximum absolute error
1.78e-14
paired 2.22e-16
exact 2^8 numerator/denominator cancellation before P rounding1.11e-16

Its paper-inspired input distribution is over 32 x 32 Q/K/V fixtures. That run contains one injected outlier in each tensor:

CPU teaching variantOutput RMSE versus binary64 reference
per-tensor E4M30.04762
per-8-row-block E4M30.04066
signed Hadamard plus per-block E4M30.05024

The transform is worse on that seed. This is useful evidence: the identity is exact, but the quantized-error outcome is distribution- and scale-dependent.

A separate controlled fixture places persistent +/-10 excursions in Q/K channel 0. Per-block E4M3 RMSE is 0.23677; adding the same signed Hadamard to Q/K reduces it to 0.08066. That experiment illustrates the intended geometry without pretending to reproduce FA3 Table 3.

What The Paper Actually Reports

FA3 Section 4.3 uses an FP64 reference and the same stated synthetic distribution. Its Table 3 reports:

Paper methodRMSE
baseline FP163.2e-4
FA2 FP161.9e-4
FA3 FP161.9e-4
baseline FP8, per-tensor scaling2.4e-2
FA3 FP8, block quantization + incoherent processing9.1e-3
FA3 FP8 without block quantization9.3e-3
FA3 FP8 without incoherent processing2.4e-2

The often-quoted 2.6x is 2.4e-2 / 9.1e-3 for this source-reported coordinate. The ablation says that incoherent processing dominates this particular synthetic result and block quantization provides a smaller additional change here. It does not establish the same ordering for every model, layer, sequence, or training run. The paper also lists large-scale FP8 training behavior as future work rather than evidence already supplied.

Paper Algorithm, Pinned Source, And Runtime Selection

The complete claim ladder for this section is:

FA3 paper:
    natural-block scales, orthogonal Q/K transform,
    FP8 layout algorithms, source-reported accuracy and throughput

pinned beta23 C++ source:
    E4M3 public admission, [batch, KV-head] descales,
    Max_offset=8, V transpose, register permutations, BF16 output

compiled artifact:
    actual FP8 instruction variants, register allocation, SMEM,
    conversion behavior, selected layout specialization

runtime evidence:
    selected symbol, correctness against a reference,
    saturation/underflow counters, latency and throughput

Only the first two rungs are present here, plus a CPU oracle. The pinned artifact was not built, the FP8 kernel was not run locally, and no H100 profile was captured.

FP8 Failure Matrix

FaultWhy it can look plausibleConsequence
broadcast Q scale by KV blockblock counts can accidentally matchwrong score scale for each query block
multiply after softmaxscale is still present somewhere in codedifferent distribution because softmax is nonlinear
apply one final V scale when variescorrect for a head-wide scalecross-tile contributions use inconsistent units
feed head-contiguous V directly to FP8 PV WGMMAlogical V shape is correctillegal or semantically wrong major-mode layout
release Vt before collective transpose closesdestination stage may look ready in one warpproducer overwrite or partial transposed tile
permute P without matching V, or V without matching Peach tensor remains in boundsexact PV result changes silently
convert FP32 P without register ownership repairelement count and dtype matchwrong lanes/bytes feed WGMMA operand A
add 2^8 to P but not its denominator/LSE bookkeepinglarger P range appears intentionaloutput or log-sum-exp is scaled incorrectly
zero, negative, NaN, or infinite descalepointer shape passes API checkscollapse, NaN propagation, or overflow
let one scale drive values beyond 448E4M3 conversion still returns a bytesaturation destroys magnitude information
say the orthogonal transform makes FP8 exactexact identity was provenquantization errors are omitted from the claim
report one favorable transform seed onlydeterministic result is reproduciblegeometry dependence is hidden by selection

FP8 Boundary And Release Gate

This section owns the FA3-specific low-precision kernel path: its E4M3 operands, FP32 state, scale application points, WGMMA layout repair, V transpose, and exact identities surrounding quantization.

M8.3 owns general outlier geometry, equivalent-transform topology, conditioning, and method comparison. M8.6 owns model-wide live activation and K/V scale lifetimes, append state, cache metadata, and serving integration. M4.6 does not turn this one attention path into a general quantization policy.

M8.2 owns model-wide calibration, sensitivity, and precision allocation; M8.9 owns quantized artifact compatibility and runtime admission; M8.10 owns deployment evaluation, promotion, release, and rollback of the exact candidate. The checklist below concerns this article’s local FP8 explanation; it does not authorize deployment of a quantized candidate.

Publish only if:

  • E4M3 input/P width is separated from FP32 accumulation and BF16 output.
  • every descale names its axis and application point.
  • paper block scales are separated from pinned [batch, kv_head] API scales.
  • the 2^8 probability offset is derived through numerator, denominator, and LSE.
  • V transpose includes source/destination stages, packed-FP8 byte movement, synchronization, and shape constraints.
  • accumulator-to-operand register ownership is traced after conversion.
  • Q/K orthogonal equivalence and paired P/V permutation are proved separately.
  • exact pre-quantization identities are not promoted to exact E4M3 arithmetic.
  • paper Table 3 remains source-reported and separate from the CPU oracle.
  • no local GPU accuracy, throughput, or compiled-layout claim is implied.

0x07 Blackwell Changes The Machine Again

The FlashAttention-4 paper targets B200/GB200 Blackwell. Its algorithm is not FA3 with larger constants. Blackwell changes where matrix accumulators live, who may issue the matrix operation, how a CTA pair shares work, and which synchronization event makes a result consumable. A schedule that preserves the algebra but violates one of those machine contracts is not a slower FA4 kernel. It is an invalid kernel.

This loop asks a deliberately narrower owner question:

What must be true of TMEM addresses, lifetimes, CTA-group ownership, and asynchronous completion before an FA4 schedule can be legal?

The next section derives the forward schedule and its rate model. This section first fixes the machine on which that schedule must execute.

ResourceHopper / FA3 modelBlackwell / FA4 modelKernel consequence
Tensor MMAWGMMAtcgen05.mma / UMMAnew instruction and synchronization contract
Accumulator locationregistersTMEMaccumulator lifetime moves out of general registers
Cooperative MMAone warpgroup-oriented path1-CTA and 2-CTA modespaired CTAs can share a larger MMA tile
Representative accumulator tile64 x 128 in FA3 comparison128 x 128row ownership and softmax mapping change
BF16 MMA throughput in FA4 model4096 ops/cycle/SM8192 ops/cycle/SMmatrix work doubles per cycle
MUFU exponential rate in FA4 model16 ops/cycle/SM16 ops/cycle/SMsoftmax becomes relatively more expensive
SMEM read rate in FA4 model128 B/cycle/SM128 B/cycle/SMshared-memory feeding can become limiting

The rate comparison is the FA4 paper’s simplified per-SM model. It identifies design pressure; it is not a complete GPU roofline and excludes resources such as register bandwidth, L2, instruction issue, and occupancy. The paper’s main text and performance figures say B200, while Appendix A.1 says B100 and contains a date inconsistent with the arXiv submission. This article preserves that metadata discrepancy instead of silently choosing one label.

TMEM Adds A New State Coordinate

The PTX ISA describes Tensor Memory, or TMEM, as a dedicated on-chip store distinct from registers and shared memory. tcgen05.mma can place its accumulator there. A later tensor operation can consume a TMEM fragment directly, while ordinary elementwise work first loads the required TMEM lanes into registers.

That gives one logical value at least four independent coordinates:

logical:       score S, probability P, or output accumulator O
address:       TMEM lane and column
time:          allocation epoch and producer/consumer lifetime
ownership:     CTA group, CTA rank, warp, and lane slice

S0 and P0 may refer to the same columns at different times. Two CTAs may use the same numeric TMEM address in different per-CTA views. One warp may issue an instruction whose result is not yet visible to another warp. A scalar pointer alone therefore does not identify the state.

Derive Capacity, Granularity, And Address

For sm_100, PTX exposes a per-CTA addressable view with 512 columns, 128 lanes per column, and one 32-bit cell at each lane-column coordinate. The FA4 paper describes the corresponding 256 KiB capacity in its per-SM machine model:

The minimum allocation granule is 32 columns:

The legal allocation amounts are powers of two between 32 and 512 columns:

32, 64, 128, 256, 512 columns

Successive allocation amounts in one kernel may not increase, and all allocated TMEM must be explicitly deallocated before kernel exit. Calling relinquish_alloc_permit closes the right to allocate again; it does not make live values disappear.

A 32-bit TMEM address packs lane and column:

For example, lane 37, column 384 encodes as 2,425,216, then decodes back to (37, 384). This is an address inside one CTA’s view. Multiplying it by the CTA count would invent a flat address space that PTX does not provide.

The warp index determines which lane band a TMEM load or store owns:

Warp inside warpgroupTMEM lanes
0[0, 32)
1[32, 64)
2[64, 96)
3[96, 128)

One warp covers 32 lanes. A warpgroup of four warps is needed to cover all 128 lanes. This is different from the issue rule of tcgen05.mma, which is a single-thread operation.

Allocation Is A Collective Lifetime

The pinned fa4-v4.0.0.beta23 Blackwell forward path allocates the maximum 512 columns. Its control path is structurally:

# Pinned-source structure, abbreviated.
if warp_idx == mma_warp_id:
    tmem.allocate(get_max_tmem_alloc_cols("sm_100"))  # 512 columns
    tmem.wait_for_alloc()
    tmem_ptr = tmem.retrieve_ptr(Float32)
    run_mma_pipeline(...)
    tmem.relinquish_alloc_permit()
    tmem_alloc_barrier.arrive_and_wait()
    tmem.free(tmem_ptr)

The allocation wrapper hides different hardware participation contracts:

Operation familyCTA group 1 issueCTA group 2 issueCompletion/visibility obligation
allocate, deallocate, relinquishone warp in the CTAone warp from each peer CTAcollective operation must complete before dependent use or exit
mma, cp, shift, commitone threadone thread from the CTA pairresult is asynchronous
ld, st, waitwarp operationwarp operation in each relevant CTAlane ownership and operation-specific wait apply

“Single-thread issue” does not mean “single-thread computation.” The one issuing thread launches a collective tensor operation whose operands and destination are distributed according to the MMA atom and CTA-group contract.

For CTA group 2, the peer CTA must already be active and remain active while a paired operation can touch its state. The peer rank differs in the final cluster-rank bit. Allocation, relinquish, and deallocation require one participating warp from each CTA. Deallocating after only the local CTA has stopped touching TMEM is insufficient; peer access must also have quiesced.

Derive The Pinned Beta23 TMEM Map

The current beta23 source does not leave the offsets implicit. For a K/V tile width , value head dimension , two score slots, and output slots, it constructs:

so the logical map requires

With , , and two Q stages:

ObjectColumnsWidthLive use
FP32 score S0[0, 128)128QK accumulator, masking, softmax input
BF16 probability P0[64, 128)64PV operand after S0 dies
FP32 score S1[128, 256)128second score/softmax slot
BF16 probability P1[192, 256)64PV operand after S1 dies
FP32 output O0[256, 384)128persistent PV accumulator
FP32 output O1[384, 512)128second persistent PV accumulator

The source represents offsets in FP32-column units. Since BF16 P has half the width of FP32 S, one probability slot needs

columns for this coordinate. The P slot is placed in the tail half of the dead S slot. This is not “P and S coexist in 64 columns.” It is a time-dependent alias.

The capacity equation also exposes why stage count and output width cannot be tuned independently:

N=128, q_stage=2, d_v=128 -> 2*128 + 2*128 = 512 columns: exact fit
N=128, q_stage=1, d_v=192 -> 2*128 + 1*192 = 448 columns: algebraically fits
N=128, q_stage=2, d_v=192 -> 2*128 + 2*192 = 640 columns: impossible

The last two rows are sensitivity probes, not claims that the pinned dispatcher selects both coordinates. The source requests all 512 columns even when its logical map uses fewer. Its fragment code can treat base address zero as known precisely because a full-width allocation starts at zero; a smaller allocation would need a retrieved-base-dependent layout.

Blackwell TMEM contract showing the 128-lane by 512-column address space, beta23 score probability and output aliases, allocation lifecycle, tcgen05 asynchronous completion, and one-CTA versus two-CTA output ownership

This is an editorial reconstruction from PTX ISA 9.3, the official CUTLASS tcgen05 guide, and beta23 commit 2409214.... It is not a captured B200 trace.

Alias Legality Requires Address And Time

For objects and , define their column intervals and live intervals . Reuse is legal only when at least one coordinate does not overlap:

The score-to-probability transition is therefore a protocol:

tcgen05.mma writes FP32 S_i to TMEM
-> completion is observed
-> softmax warps load their owned S_i lanes
-> all score consumers finish
-> BF16 P_i overwrites the tail of the same address interval
-> PV consumes P_i as a TMEM operand
-> P_i dies before the score slot is reused

Changing P0.time_begin from the score-release boundary to an earlier instant produces a real live-address conflict even though every address remains in bounds. Capacity checks alone cannot detect this bug.

Async Issue, Completion, And Visibility Are Different Events

tcgen05.mma is asynchronous. The issuing thread can continue before the TMEM result is safe for a consumer load. The positive dependency chain is:

single thread issues tcgen05.mma
-> single thread commits completion to an mbarrier
-> consumer observes the mbarrier completion
-> participating threads synchronize as required
-> tcgen05.fence::after_thread_sync publishes the TMEM result
-> four warps load their 32-lane slices
-> tcgen05.wait::ld closes the load before reuse

The exact PTX spellings depend on instruction form, but the state transitions do not. A barrier arrival is not itself a consumer fence, and a consumer fence cannot precede completion observation.

Shared-memory operands add a second memory-proxy boundary. Generic-proxy writes and asynchronous-proxy tensor operations that touch the same SMEM location require the appropriate fence.proxy.async ordering. TMEM completion does not retroactively publish an unfenced SMEM producer.

PTX permits pipelined same-thread MMA ordering only under its matching-accumulator, shape, and kind conditions. Changing the MMA kind or accumulator relationship removes that implicit dependency; the schedule must then add explicit ordering. “Issued by the same thread” is not a universal happens-before proof.

One CTA And Two CTAs Partition Different Coordinates

The official CUTLASS tcgen05 programming guide distinguishes CTA-group 1 and CTA-group 2 atoms. In the pinned source:

cta_group_size = 2 if use_2cta_instrs else 1
mma_tiler_qk = (cta_group_size * M, N, d)
mma_tiler_pv = (cta_group_size * M, d_v, N)
cluster_shape_mn = (cta_group_size, 1)

For :

ContractCTA-group 1CTA-group 2
logical MMA tile128 x 128 x 128256 x 128 x 128 across the pair
CTA 0 output rows[0, 128)[0, 128)
CTA 1 output rowsseparate tile[128, 256) in the paired tile
cluster shape(1, 1)(2, 1)

The pair expands the M coordinate. It does not automatically split the reduction K coordinate, and it does not merge the two per-CTA TMEM views into one flat 1024-column allocation.

The B operand is logically needed by both output-row owners. Two independent CTAs would each stage a full BF16 128 x 128 B tile:

In the pinned 2-CTA layout, the source divides K/V SMEM per stage by the CTA-group size and uses cluster-aware TMA/UMMA pipelines. Each CTA stages a 16 KiB share, so the pair stages 32 KiB in this teaching coordinate:

This is a source-derived shared-memory payload ledger, not a universal claim that 2-CTA mode halves HBM traffic or doubles end-to-end speed. TMA multicast, DSMEM access, pair synchronization, cluster residency, tails, and occupancy still decide the realized cost.

A CTA Pair Is Not Two Independent CTAs

The paired mode adds contracts that do not exist between ordinary independent blocks:

  1. Both CTAs must select CTA-group 2; all tcgen05 operations in one kernel must use the same CTA-group mode.
  2. The peer CTA must be active before a paired operation and may not exit while the pair can still access its TMEM state.
  3. Each CTA selects its own MMA slice with get_slice(mma_tile_coord_v) and writes its own output portion.
  4. Pipeline participant counts that bridge UMMA and non-MMA work scale by cta_group_size.
  5. Allocation, relinquish, and deallocation are pair-collective even though mma issue is single-thread.
  6. Cluster placement is a feasibility constraint. It must be checked with the architecture-appropriate cluster occupancy API, not inferred from thread count alone.

The Blackwell tuning guide reports 64K 32-bit registers per SM, a maximum of 255 registers per thread, 228 KiB of shared memory per SM, and 227 KiB per CTA. Those ceilings bound admission; they do not prove that a 16-warp specialization, its SMEM, and a two-CTA cluster achieve any particular occupancy. Only a compiled artifact and cluster occupancy query can close that claim.

Pinned Dispatch Makes Two-CTA Conditional

The current beta23 public interface does not select the general 2-CTA path for every Blackwell attention call. At commit 2409214..., the ordinary path requires all of the following:

SM100/SM110 family
2CTA not explicitly disabled
noncausal and nonlocal attention
no split-KV output
fixed-length Q (no cu_seqlens_q or seqused_q)
no block sparsity
page size absent or 128
padded d in {128, 192}
padded d_v == 128
packed query length > 2 * tile_m
GQA packing divisibility condition satisfied

A separate dedicated path admits (d,d_v)=(256,256). These predicates are pinned implementation behavior, not requirements imposed by the FA4 paper or the tcgen05 ISA. A later release may change them. The only runtime proof that 2-CTA executed is the selected compiled symbol/configuration plus a profile; source eligibility alone is not dispatch evidence.

Executable TMEM And CTA-Group Oracle

The Blackwell TMEM evidence bundle turns the contracts above into a deterministic CPU model. It regenerates the 512-column source map, address round trip, legal allocation sizes, live-alias audit, 1-CTA/2-CTA ownership, B-payload ledger, and positive completion trace.

Its negative fixtures fail with named reasons:

MutationRequired failure
allocate 96 columnsallocation is not a legal power of two
allocate 64 then 128 columnsallocation size increases
allocate after relinquishingallocation permit is closed
use two Q stages with algebraic logical map needs 640 columns
write P while aliased S is still livesimultaneous address/lifetime overlap
mix CTA-group 1 and 2 operationskernel-wide CTA-group mode mismatch
launch group 2 without active peerpeer-liveness violation
allocate group 2 from one warp onlycollective-participant violation
load TMEM before completion/fenceunfinished result consumption
deallocate before peer quiescencelive peer access
let warp 0 load lane 64lane-ownership violation

This oracle checks arithmetic and event preconditions. It does not lower PTX, prove ptxas instruction selection, run on B200, or measure cluster occupancy.

Blackwell Failure Matrix

FaultWhy it can look plausibleConsequence
treat TMEM as extra registersboth hold accumulator-like valueswrong allocation and visibility protocol
count a CTA pair as one 1024-column pooleach CTA exposes 512 columnsinvalid cross-CTA address arithmetic
overlap P with a still-live S tailboth intervals fit inside 512 columnssoftmax input is overwritten before all consumers finish
request 512 columns, then allocate again after relinquishfirst allocation remains liveillegal allocation-permit transition
let one CTA allocate/deallocate group 2MMA issue itself is single-threadcollective allocation protocol is incomplete
allow peer CTA to exit earlylocal CTA still has valid pointerspaired instruction accesses dead peer state
load after mbarrier wait but before consumer fencecompletion was observedTMEM visibility to the consumer is not established
use warp 0 for all 128 lanespointer arithmetic is in range96 lanes are outside that warp’s ownership
mix CTA-group modes in one kerneleach instruction form is individually validkernel violates a global tcgen05 constraint
assume same-thread MMA issue always orders operationssource order looks sequentialunsupported accumulator/shape/kind transition races
call 2-CTA universally fastersource halves one SMEM payload ledgercluster, synchronization, tail, or occupancy cost can dominate
treat source eligibility as runtime selectionpredicate evaluates true on papercompiled specialization or dispatcher may differ

Blackwell Hardware Boundary And Release Gate

This section owns the Blackwell machine contract needed before FA4 scheduling: TMEM shape and addressing, allocation lifetime, source-derived suballocation, time-safe aliasing, tcgen05 issue/completion rules, CTA-group ownership, and pinned 2-CTA dispatch admission.

It does not yet claim that the FA4 forward schedule hides exponential latency or reaches the paper’s throughput. Section 0x08 owns the forward pipeline and feeds-and-speeds derivation. Section 0x09 owns software exponential and conditional rescaling. Section 0x0A owns backward, DSMEM reduction, and determinism.

Publish this loop only if:

  • 256 KiB capacity is derived without inventing one flat group address space.
  • allocation size, permit, deallocation, lane, and CTA-group rules are separate.
  • the beta23 S/P/O map is derived from , , and .
  • address overlap is legal only when live intervals do not overlap.
  • single-thread issue is separated from asynchronous completion and visibility.
  • group-2 output ownership is separated from K-reduction ownership.
  • the 0.5 B-payload ratio is labeled source-derived, not a universal speedup.
  • current 2-CTA dispatch predicates are pinned behavior, not paper or ISA requirements.
  • the CPU oracle is not presented as a compiled Blackwell artifact or local benchmark.
  • FA4 forward scheduling, exponential implementation, and backward DSMEM remain deferred.

This changes the capacity problem:

Hopper:
    many live accumulator tiles consume registers directly

Blackwell:
    accumulator tiles consume finite TMEM columns
    registers still hold softmax rows and scalar/vector state
    SMEM still stages matrix operands

Moving an accumulator does not eliminate its lifetime. It gives the scheduler a different storage pool and a different access path.

2-CTA MMA

Blackwell can pair two CTAs for a cooperative MMA. Each CTA stages part of the operands and owns an accumulator slice, while the instruction operates as one larger tile. The CTAs form a fixed cooperating pair and must agree on the paired mode and instruction order. This can reduce duplicated shared-memory reads, but it adds cluster placement, distributed shared-memory exchange, and pairwise synchronization constraints.

0x08 FA4 Forward: Deriving The Feeds And Speeds

The owner question for this loop is narrower than “is attention compute-bound?”:

For one Blackwell forward work tile, what work feeds the tensor, shared-memory, and exponential paths; which dependencies permit that work to overlap; and which source-level schedule attempts to approach the resulting lower bound?

The answer requires three different objects. The FA4 paper supplies a deliberately simplified machine model and an intended two-query-tile schedule. The official machine contracts in 0x07 constrain what TMEM and tcgen05 are allowed to do. The pinned fa4-v4.0.0.beta23 source supplies concrete warp IDs, pipeline objects, split- handoffs, and startup/steady/drain order. None of those is a measured runtime trace.

This section owns:
    forward work and resource-time derivations
    instruction-ceiling sensitivity
    paper H/L query-tile schedule
    beta23 warp roles and pipeline handoffs
    startup, steady-state, and drain ordering
    q_stage and TMEM-partition alternatives

It consumes:
    0x07 TMEM allocation, alias, and CTA-group legality
    M4.3 online-softmax algebra
    M4.4 work-partition language

It defers:
    software exp2 accuracy and conditional rescaling -> 0x09
    backward, DSMEM, and deterministic reduction -> 0x0A
    measured throughput and dispatch proof -> 0x0D and profiler evidence

Asymmetric Scaling Changes What Deserves A Ledger

The paper’s B200/GB200 model uses three per-SM rates:

ResourcePaper-model rateEvidence and limit
BF16 tensor operations FLOPs/cycle/SMderived in the paper from its cited B200 peak, clock, and SM count
SMEM reads bytes/cycle/SMsource-reported microbenchmark rate, not an ISA guarantee
exponential operations operations/cycle/SMpaper-reported B200/GB200 MUFU rate

The paper explicitly calls this a simplified analysis. It omits ordinary FP32/FMA work, register and TMEM movement, barrier latency, issue limits, L2/HBM effects, occupancy, tails, and epilogue cost. Its purpose is to expose pressure, not predict a kernel timestamp.

That limitation is central to the history. If tensor throughput doubles while SMEM and exponential throughput do not, the old statement “the tensor cores dominate” can become false without the attention equation changing. A pipeline redesign is then an accounting response to asymmetric hardware scaling.

Derive The Two MMA Terms From Tensor Shapes

Let one logical forward tile cover:

Q: [M, d]
K: [N, d]
V: [N, d]
S = Q K^T: [M, N]
P = softmax(S): [M, N]
O = P V: [M, d]

For a conventional multiply-add count, performs multiplies and additions:

The second contraction has multiply-adds as well:

Therefore the tensor-work ledger is

and the paper-model resource time is

This is logical work divided by a peak per-SM rate. It does not yet include partial-tile efficiency, instruction latency, or inability to keep the tensor path continuously issued.

Derive SMEM Reads From MMA Instruction Coverage

The SMEM term is not “read Q, K, and V once.” The paper models 128-by-128 MMA output tiles and counts repeated operand reads when , , or requires multiple instructions.

For , both operands come from SMEM. Define

Each covered output tile reads one Q operand and one K operand. With bytes per element,

For , is a TMEM operand and V is the modeled SMEM operand. Its instruction grid is

so the V-read ledger is

The general paper-model time is therefore

When , , and are divisible by 128, the ceilings disappear. Substitution gives

and hence

For BF16, , and for the paper’s bytes/cycle/SM rate,

This is SMEM-to-tensor-operand traffic after global tiles have been staged. It is not the HBM traffic owned by M4.3, and it is not the full on-chip traffic of the compiled kernel.

Derive Exponential Pressure And The Crossover Head Dimension

There is one exponentiation per score entry in the simplified model:

The ratio to tensor work removes and :

The two resource times are equal at

With and , . This is why the canonical head-dimension-128 ledger ties MMA and exponentiation. It is not a universal property of attention: change the head dimension or either hardware rate and the balance moves.

For divisible BF16 tiles, the SMEM/MMA ratio is also dimensionless:

The last equality is specific to the paper’s rates and BF16 element width.

max Is A Lower Bound, Not A Runtime Formula

If the three modeled resources could be overlapped perfectly, a lower bound would be

If none overlapped, the modeled subtotal would be

Neither expression is the complete kernel runtime. The first ignores dependency bubbles and all omitted resources. The second includes no omitted resources either and therefore is not a rigorous upper bound. The useful question is how close a legal schedule can move the modeled subtotal toward the modeled lower bound.

For :

The lower bound is 1024 cycles and the no-overlap subtotal is 2816 cycles. Calling the kernel a “1024-cycle kernel” would be wrong; 1024 is only the largest term in this simplified resource ledger.

Ceiling-aware FA4 forward feeds-and-speeds derivation with MMA, shared-memory, and exponential cycle ledgers

Repository-authored analytic reconstruction from FA4 Section 3.1.1 and Table 1. The diagram is not a benchmark or profiler capture.

Instruction Ceilings Explain Ragged-Tile Surprises

The compact SMEM formula is legal only under the paper’s divisibility assumption. Consider . Logical MMA and exponential work scale with the requested shape, but the modeled instruction grids are

QK instruction grid: ceil(192/128) x ceil(96/128) = 2 x 1
PV instruction grid: ceil(192/128) x ceil(160/128) = 2 x 2

The oracle obtains:

Tile MMA cyclesSMEM cyclesexp cyclesLargest modeled term
10247681024MMA and exp tie
204815362048MMA and exp tie
144020481152SMEM after instruction ceilings

The ragged row is a teaching coordinate, not a claim that beta23 selects that exact tile. It shows why dropping ceilings can name the wrong resource before code generation begins.

Sensitivity Moves The Bottleneck Without Changing The Algorithm

Holding the canonical 128-cubed shape fixed:

Synthetic rate mutationMMASMEMexpLargest term
paper B200 model10247681024MMA and exp
double only exponential throughput1024768512MMA
double only tensor throughput5127681024exp
halve only SMEM bandwidth102415361024SMEM

This table is a sensitivity analysis, not a forecast for another GPU. It demonstrates the design principle: optimizing the currently largest term can expose the next one, and a tile choice should be re-evaluated against the actual target’s rates and instruction shapes.

The Paper Schedule Uses Two Query Tiles To Create Independent Work

The FA4 paper’s Figure 1 names two 128-row query/output tiles H and L. The intended pipeline performs tensor work for one tile while the other tile’s softmax warpgroup processes a score row. Blackwell changes the ownership geometry relative to FA3:

Hopper FA3:
    accumulator in registers
    multiple threads participate in one logical score row

Blackwell FA4 paper schedule:
    accumulator in TMEM
    one 128-thread softmax warpgroup
    each thread owns one complete 128-value score row

One-thread-per-row removes the inter-thread row-max shuffle required by the Hopper ownership pattern, but it does not make softmax cheap. A thread must hold the 128 score values and temporary state in registers. The paper cites 128 registers for the input row and potentially 64 for output-related state, before miscellaneous temporaries. Register redistribution is therefore part of the schedule’s feasibility.

The two softmax warpgroups are not two independent exponential engines. The paper explicitly synchronizes them so their exponential critical sections do not overlap. Their other work can overlap tensor operations, but the scarce MUFU interval remains serialized.

FA4 forward pipeline maps two query tiles, tensor operations, and softmax work onto Blackwell resources

Source: Zadouri et al., FlashAttention-4, Figure 1, CC BY-NC-SA 4.0. Superscripts H and L identify the two 128-row query/output tiles. This is the paper schedule, not a complete beta23 source trace or measured timeline.

Two S Slots Win The Startup Trade-Off

After reserving two output tiles in TMEM at head dimension 128, the paper describes two broad partitions for the remaining space:

TMEM alternativeBenefitCost
one S slot + two independent P slotssimpler non-aliased P placementonly one score tile can occupy the remaining score surface at startup
two S slots whose tails become Pcompute both startup score tiles immediatelyrequires address-plus-lifetime proof for the S/P aliases

FA4 chooses the second. Section 0x07 derived the corresponding beta23 map: S0/P0, S1/P1, O0, and O1. The purpose of the overlap is now visible: two complete score tiles can be issued before the main PV/QK alternation begins.

Beta23 Turns The Paper Roles Into Sixteen Warp IDs

The pinned flash_fwd_sm100.py partitions one CTA as follows for the audited two-stage path:

Warp IDsSource roleState emphasis
0..3softmax stage Hone 128-row score tile, large row-register budget
4..7softmax stage Lsecond score tile, same logical role
8..11correctionconsume scale statistics, conditionally rescale O, final normalization/epilogue path
12MMA issuerallocate TMEM; issue QK and PV operations; own tensor order
13epilogueseparate S-to-G path when correction warps do not own it
14loadQ/K/V producer path when one load warp is sufficient
15empty or CLC schedulerspecialization-dependent spare/scheduler role

These IDs are pinned implementation behavior, not requirements of the FA4 algorithm. The source can reassign roles: q_stage=1 removes the second softmax warpgroup, non-TMA K/V paths can consume two load warps, and the correction warps can absorb epilogue work.

For one audited noncausal BF16, , 1-CTA teaching specialization, the source fallback targets are

softmax setmaxnreg target:   192
correction setmaxnreg target: 80
other-role target:            48

budget equation:
    2 * 192 + 80 + 48 = 512

Those are setmaxnreg policy targets in source. They are not compiler-reported registers per thread, spill counts, or an occupancy result. A compiled cubin and toolchain-specific resource report are still required.

Pipeline Objects Encode The Actual Handoffs

The paper’s arrows become several distinct beta23 pipelines around lines 928-1026:

Source objectProducer -> consumerPayload/lifetime meaning
pipeline_qload -> MMAQ stage is full; MMA later releases it after the K/V traversal
pipeline_kvload -> MMAone circular K or V stage is full; release permits producer reuse
pipeline_s_p_oMMA <-> softmax + correctionMMA publishes S; softmax publishes P and correction publishes rescaled O before PV/reuse
pipeline_p_lastsplitsoftmax -> MMAfinal P fraction becomes available after the early split signal
pipeline_o_accMMA -> correctionfinal accumulated O is available when a separate guarantee is required
pipeline_sm_stats plus named barriersoftmax -> correctionrow-max/row-sum scale state is ready
pipeline_o_epicorrection -> epiloguecorrected output is ready for a separate output-store role

pipeline_s_p_o is intentionally not a normal one-way queue. The MMA warp first acts as producer of S. The softmax and correction roles then close the stage from the opposite side by producing P readiness and O-rescale readiness. Replacing that circuit with one vague “barrier” would hide which state is safe to reuse.

Startup, Steady State, And Drain Are Different Schedules

For two query stages and two K/V blocks, the pinned MMA loop’s logical tensor-issue order is

startup:
    QK_H_0
    QK_L_0

steady alternation:
    PV_H_0 -> QK_H_1
    PV_L_0 -> QK_L_1

drain:
    PV_H_1
    PV_L_1

The source startup waits for Q and the first K stage, issues both score operations, publishes both S slots, and only then enters the K/V loop. The steady loop waits for P plus corrected O, issues PV, then overwrites the now-dead S/P slot with the next QK result. The drain releases Q and issues the final PV operations without another QK behind them.

This is why a steady-state diagram is not a full algorithm. A short K/V traversal spends a larger fraction of time in startup and drain, and empty/sparse work must still close the synchronization protocol without fabricating payloads.

FA4 forward warp roles, dependency handoffs, and startup steady drain tensor issue order

Repository-authored reconstruction from FA4 Figure 1 and pinned beta23 source lines 263-337, 928-1026, 1193-1303, 1555-1847, and 2254-2553. It is not a captured Blackwell timeline.

Split P Creates An Issue Point, Not An Early Completion

Beta23 sets

For , this is 96 columns, or 75 percent of P. The source comment and constructor explain the intent: softmax stores the first fraction, signals the MMA warp so PV can begin, then stores and signals the remainder.

The dependency is therefore two-level:

P_75_ready + corrected_O_ready + V_ready
    -> PV may issue

P_100_ready
    -> PV may consume the final P fraction and complete

PV complete
    -> aliased S/P columns may become the next S destination

Treating the 75-percent signal as “P is complete” would permit an early overwrite or an incomplete contraction. The split is overlap within one logical PV operation, not permission to weaken its completion contract.

q_stage, Paper , And CTA-Group Are Distinct Coordinates

The paper’s Table 1 uses for two 128-row query tiles. In the pinned source, three different coordinates contribute to covered query rows:

With :

Source configurationQuery rows in one logical work groupTMEM columns for Softmax warpgroups
q_stage=1, g_CTA=11281
q_stage=2, g_CTA=12562
q_stage=2, g_CTA=2512 across the CTA pairtwo per-CTA 512-column views2 per CTA

The interface pin selects q_stage=2 when the packed query length exceeds one M tile and otherwise uses one stage, with dedicated paths adding further constraints. Paper must not be copied into every 2-CTA source ledger as though it named the same coordinate.

Design Alternatives Preserve Algebra But Change The Schedule

ChoiceWhat improvesWhat worsens or changes
one query stageless TMEM and one softmax warpgroupless independent H/L work; startup cannot precompute two S slots
two query stagesmore overlap opportunity and paper-like H/L pipelinefills 512 TMEM columns at ; two large register owners
one S + two P regionssimpler P address lifetimeweaker two-S startup
two S regions aliased with Pimmediate dual-QK startuprequires exact P/PV completion before reuse
unsplit P publicationsimpler readiness protocolPV waits for all P columns
75/25 split publicationoverlaps final P production with PV issueadds partial/full completion states
rescale O in softmax rolefewer role handoffslengthens softmax critical work and register lifetime
separate correction warpgroupremoves O rescale from the softmax critical pathadds statistics, O-ready, and epilogue coordination

These are schedule alternatives, not alternative attention equations. A valid implementation can choose differently if its resource rates, head dimensions, or feature path differ.

Source-Level Forward Skeleton

# Teaching skeleton. Pipeline objects and phases are omitted, not optional.
load_Q_stages()
load_first_K_stage()

for q_stage in (H, L):
    wait(Q[q_stage], K[0])
    issue_QK(Q[q_stage], K[0], S[q_stage])
    publish_S_full(q_stage)

for kv_block in remaining_blocks:
    for q_stage in (H, L):
        # These are separate arrivals in the real source circuit.
        wait(P_early[q_stage])
        wait(O_rescaled[q_stage])
        wait(V[kv_block - 1])
        issue_PV(P[q_stage], V[kv_block - 1], O[q_stage])
        wait(P_final[q_stage])
        complete_PV(q_stage)

        # S and P share columns; overwrite only after PV consumption closes.
        wait(K[kv_block])
        issue_QK(Q[q_stage], K[kv_block], S[q_stage])
        publish_S_full(q_stage)

for q_stage in (H, L):
    wait(P_early[q_stage], P_final[q_stage], O_rescaled[q_stage], V[last])
    issue_and_complete_final_PV(q_stage)
    publish_final_O(q_stage)

The actual source pipelines K and V through the same circular SMEM surface, carries phase state across persistent work tiles, handles masks and sparse/empty paths, and can fuse correction with the epilogue. The skeleton exists to expose dependencies, not replace the implementation.

Executable Forward Oracle

The forward-schedule evidence bundle contains:

loop8_forward_schedule_fixture.json
loop8_forward_schedule_oracle.py
loop8_forward_schedule_report.json
loop8_forward_schedule_source_trace.json

The CPU oracle mechanically:

  1. reproduces both FA4 Table-1 cycle rows;
  2. retains MMA instruction ceilings for a ragged teaching tile;
  3. derives the MMA/exp crossover;
  4. verifies that the 16 source roles partition warp IDs 0..15;
  5. checks the 96/128 split-P fraction and source register-target equation;
  6. validates startup/steady/drain tensor order and required handoff edges;
  7. rejects overlapping normalized H/L exponential critical sections;
  8. rejects nine resource, dependency, alias, and evidence-surface mutations.

It does not compile CuTe DSL, lower tcgen05, simulate B200 pipelines, establish occupancy, or measure overlap.

Forward Failure Matrix

Tempting shortcutWhy it appears plausibleBroken contract
drop instruction ceilingslogical shapes are smaller than covered tilesrepeated full-operand reads disappear from the SMEM ledger
add resource times and call that optimized runtimeall three terms are cyclesoverlap has been forbidden by assumption
take max and call it measured runtimeperfect overlap lower bound is compactdependencies and omitted resources disappear
overlap H and L exponential critical sectionsthere are two softmax warpgroupsthe paper deliberately serializes scarce MUFU work
issue PV before correction releases OP is already availableold O may still be in the wrong exponential frame
overwrite S after the early P signal75 percent of P is publishedPV still needs final P and the alias remains live
equate paper with every source work groupboth discuss larger query coverageq_stage and CTA-group M were collapsed
call setmaxnreg(192) the compiled register countsource contains the numbercompiler allocation and spills were never inspected
call the source issue order a profiler timelineorder is explicit in Python DSLlowering, issue gaps, stalls, and runtime selection are unknown

Renderer-Safe Formula Ledger

F_MMA = 4 * M * N * d
T_MMA = F_MMA / R_MMA

I_QK = ceil(M/128) * ceil(N/128)
B_QK = b * I_QK * (128*d + 128*d)

I_PV = ceil(M/128) * ceil(d/128)
B_PV = b * I_PV * (128*N)
T_SMEM = (B_QK + B_PV) / R_SMEM

T_exp = M * N / R_exp
d_crossover = R_MMA / (4 * R_exp)

T_modeled_lower_bound = max(T_MMA, T_SMEM, T_exp)

Forward-Schedule Boundary And Release Gate

Publish this loop only if:

  • the two MMA terms are derived from shapes rather than asserted;
  • SMEM reads retain 128-by-128 instruction ceilings before simplification;
  • exponential pressure is tied to and a hardware-rate coordinate;
  • max(resource times) is labeled an optimistic lower bound, not runtime;
  • paper H/L roles are separated from beta23 warp IDs;
  • H/L exponential critical sections do not overlap in the paper contract;
  • split-P issue, final-P completion, and S/P alias reuse are separate events;
  • startup, steady state, and drain are all represented;
  • paper , q_stage, and CTA-group M are not collapsed;
  • register targets are not presented as compiler artifacts;
  • 0x09 still owns exponential approximation and conditional-rescale numerics;
  • no local Blackwell build, profile, or timing is implied.

0x09 FA4 Softmax: Use More Than The Exponential Unit

The forward ledger in 0x08 ended with a simplified B200 model in which a 128 x 128 x 128 forward tile needs 1024 modeled tensor cycles and 1024 modeled exponential cycles. Faster tensor cores therefore do not make the scalar path disappear. They make the scalar path visible.

The owner question for this loop is:

How can one softmax warpgroup feed more exponentials than one saturated special-function path can provide, while preserving a coherent online-softmax reference and a finite-precision probability range?

The answer has two coupled parts. The FA4 paper moves a subset of 2^x evaluations onto FP32 FMA/ALU resources. It also delays some output-accumulator rescalings so the correction warpgroup performs fewer vector passes over O. The pinned fa4-v4.0.0.beta23 source makes both choices compile-time specialization parameters rather than universal constants.

This section owns:
    base-2 score, reference, and probability coordinates
    degree-3 exp2 range reduction and Horner evaluation
    hardware/software exp2 dispatch within one score row
    arbitrary-reference numerator/denominator recurrence
    threshold, offset, warp-vote, and correction-warpgroup contracts
    deterministic CPU error and recurrence oracles

It consumes:
    M4.3 exact online-softmax and merge algebra
    0x06 probability-offset and FP8 range coordinates
    0x07 TMEM/correction ownership
    0x08 H/L warp roles, split-P, and resource pressure

It defers:
    backward dS exponential and reduction schedule -> 0x0A
    measured specialization speedups -> 0x0D and profiler evidence
    model-wide calibration, sensitivity, and precision allocation -> M8.2
    live activation quantization and scale/state lifecycle -> M8.6

One Score Has Raw, Natural, And Base-2 Coordinates

Let denote the score value held after QK, masking, and score modification, before the ordinary softmax scale . The mathematical softmax exponent is

The source evaluates exp2, so define

If is the retained score reference in the same coordinate as , and is an optional probability offset, the actual exp2 argument is

These coordinates must not be collapsed:

CoordinateMeaningPinned source representation
score before softmax scaleFP32 score fragment loaded from TMEM
retained score referenceSoftmaxSm100.row_max[0]
base-2 logitproduced by packed FMA in scale_subtract_rowmax
base-2 accumulator referenceimplicit in row_max * scale_log2
exponent argumentconsumed by hardware or emulated exp2
unnormalized probability operandconverted to BF16/FP16/FP8 before PV

This is also where the conditional-rescale threshold lives. A source threshold means

not “the raw logits differ by at most eight” unless .

Derive The Software exp2 Path

For one base-2 exponent argument , Cody-Waite-style range reduction writes

and therefore

The pinned helper approximates only the fractional term. Its degree-3 coefficients, stored from lowest to highest power, are:

Thus

but the implementation does not materialize powers. Horner’s rule gives

which needs three dependent FP32 FMAs per scalar. evaluate_polynomial_2 evaluates two score elements with packed f32x2 FMAs.

The integer path is less obvious. The source:

  1. clamps from below at -127;
  2. adds with round-toward-minus-infinity;
  3. subtracts using the expected round-to-nearest mode, recovering ;
  4. computes ;
  5. evaluates ;
  6. shifts the encoded integer bits into the FP32 exponent position and combines them with the bits of .

The magic constant works because the FP32 unit in that magnitude range is one. The directed-rounding addition removes the fractional part while leaving the small integer encoded in the low bits used by the subsequent exponent-field construction. The source assumes ; the softmax reference and dtype envelope keep the positive side far below that bound.

# Teaching equivalent of utils.ex2_emulation_2; source uses packed FP32x2 ops.
def exp2_degree3(x):
    x = max(x, -127.0)
    n = floor_via_directed_rounding_magic(x)
    f = x - n

    fractional = fma(p3, f, p2)
    fractional = fma(fractional, f, p1)
    fractional = fma(fractional, f, p0)

    return combine_exponent_bits(n, fractional)

This is not a general replacement for a correctly rounded exponential library. The lower clamp deliberately makes every share the same effective input; the bit construction assumes a bounded positive side; and the coefficient/error contract is specialized to exp2 on the reduced interval.

FA4 exp2 path splits a score row between hardware MUFU and packed FP32 polynomial emulation with range reduction

Repository-authored reconstruction from FA4 Section 3.1.3 and pinned beta23 softmax.py/utils.py. It is an algorithm and source map, not captured SASS or a throughput trace.

Partial Emulation Is A Fragment Predicate, Not One Percentage

The paper reports emulating roughly 10-25% of entries in its tested configurations. The pinned source exposes the actual selection rule in SoftmaxSm100.apply_exp2_convert.

For a row fragment of 32 elements, the loop visits element pairs . A pair uses software emulation only when all three conditions hold:

where is ex2_emu_freq, is ex2_emu_res, is the fragment index, and is the number of 32-element fragments. The last fragment always uses hardware exp2; early fragments may also be forced to hardware.

For a 128-element teaching row:

Pinned specialization coordinatefreq/res/startEmulated entriesFraction
SM100 2-CTA, noncausal, 10/4/124 / 12818.75%
SM100 1-CTA, causal, 16/4/116 / 12812.50%
SM100 2-CTA, noncausal, 16/4/024 / 12818.75%
SM100 1-CTA, causal, 32/4/18 / 1286.25%
SM100 2-CTA, 14/6/036 / 12828.125%
SM103 tuning rows0/4/00 / 1280%

The last two rows are especially important. They show later pinned-source tuning outside the paper’s compact 10-25% description. They do not contradict the paper experiment; they belong to a different release/configuration coordinate. ex2_emu_freq=0 means all hardware exp2, and the source comment attributes that choice to faster native exp2 on SM103.

Full software emulation is not automatically faster. It adds coefficient and intermediate live ranges, packed FMA traffic, integer/bit operations, and a dependency chain longer than one hardware MUFU.EX2. The useful design point balances two queues without making registers or ordinary issue bandwidth the next bottleneck.

Raw FP32 Error And BF16-Consumed Error Are Different Claims

FA4 Table 2 reports four million random inputs on against an FP64 reference:

PathFP32 max rel. errorFP32 mean rel. errorBF16 max rel. errorBF16 mean rel. error
ideal FP64 rounded to BF16--3.89e-31.41e-3
hardware MUFU.EX21.41e-73.04e-83.89e-31.41e-3
degree 38.77e-55.43e-53.90e-31.41e-3
degree 43.05e-61.84e-63.89e-31.41e-3
degree 51.44e-75.48e-83.89e-31.41e-3

The softmax-numerics CPU oracle evaluates the pinned degree-3 coefficients with FP32 FMA rounding on a separate deterministic 65,536-point uniform grid. It obtains:

raw FP32 max relative error:       8.7683755e-5
raw FP32 mean relative error:      5.4271432e-5
post-BF16 max relative error:      3.8975323e-3
post-BF16 mean relative error:     1.4095395e-3
exact BF16-code match:             99.0051%
within one BF16 code step:         100%

The close agreement is a useful independent check of the coefficients and evaluation order. It is not a reproduction of the paper’s random sample, GPU instruction, or SASS path. The result supports the paper’s narrow argument: when probabilities are consumed at BF16 precision, BF16 rounding dominates this degree-3 approximation on the tested domain. It does not make the raw FP32 approximation equivalent to hardware, nor does it establish end-to-end model accuracy.

Conditional Rescaling Is An Arbitrary-Reference Recurrence

M4.3 derived online softmax with the running maximum as its reference. This section needs a more general state because a skipped update deliberately keeps an older reference.

Work in base-2 coordinates. After some blocks, let:

is the denominator represented in frame ; is the unnormalized output numerator in the same frame. The attention output is .

For the next score block , define the candidate

The threshold policy commits a new reference only when

Thus

Both branches are one recurrence:

If the reference is retained, and : old state is untouched and the new block is centered on the retained reference. If the reference changes, the same converts both old numerator and denominator into the new frame.

The proof is direct:

and the same identity holds termwise for . Therefore

so the retained reference and the offset cancel in exact arithmetic.

This corrects a tempting but inaccurate description: after a skipped update, the source’s row_max is not necessarily the literal true running maximum. It is a legal retained frame. After the first three blocks in the deterministic fixture, the true base-2 maximum is 2, while the retained reference is still 0; normalization still matches direct attention.

Final Normalization And LSE Use The Retained Frame

Because includes the probability offset , the natural-log sum-exp is

In the default source coordinate , this becomes

which is the form used by the pinned epilogue. No separate “true maximum” is required: the denominator is interpreted in the same retained frame that produced it.

The final output scale is simply

with the V descale applied where required. Again, numerator and denominator must refer to the same frame. Rescaling only one breaks the ratio.

Source Trace: Softmax Warpgroup To Correction Warpgroup

The pinned forward path encodes the recurrence across two specialized owners:

EventSoftmax ownerCorrection/MMA consequence
load/reduce Ssoftmax warpgroup loads one row and computes candidate maximumno O mutation yet
threshold decisionupdate_row_max* returns retained row_max and acc_scaleacc_scale=1 means no frame conversion
publish scalesoftmax row writes acc_scale to sScale and signals stats barriercorrection warpgroup can inspect the row
center and exponentiatepacked FMA forms ; hardware/software predicate computes PP is converted and stored to TMEM
split-P releasefirst configured columns can release early issue permissiondoes not complete P or close the alias
denominator updaterow_sum <- acc_scale * row_sum + sum(P)denominator remains in the retained frame
warp decisioncorrection warp executes vote_ballot_sync(scale < 1)any row requiring correction activates the warp path
O conversionrows multiply O by their own scale; scale-one rows remain unchangedMMA receives “O rescaled” permission
final epiloguereciprocal row sum normalizes O; optional LSE uses retained frameoutput and statistics close consistently

The ballot is not merely a performance detail. Per-lane control flow around a warp-cooperative TMEM copy/rescale path would be unsafe. The source uses one warp-level decision; rows that skipped the reference update carry scale one, so participating in the common path leaves them unchanged.

# Source-level teaching skeleton; barriers and TMEM layouts remain mandatory.
candidate = max(retained_reference, rowmax(score_fragment))
delta_log2 = (candidate - retained_reference) * softmax_scale_log2

if delta_log2 > rescale_threshold:
    next_reference = candidate
    old_state_scale = exp2(-delta_log2)
else:
    next_reference = retained_reference
    old_state_scale = 1.0

x = score_fragment * softmax_scale_log2 \
    - next_reference * softmax_scale_log2 \
    + max_offset
probability = mixed_hardware_and_degree3_exp2(x)

publish_scale_to_correction(old_state_scale)
row_sum = old_state_scale * row_sum + sum(probability)

# In the dedicated correction warpgroup:
if warp_any(old_state_scale < 1.0):
    output_accumulator *= old_state_scale
publish_output_rescaled()
Conditional online softmax retains or changes one base-2 reference and applies the same frame conversion to denominator and output numerator

Repository-authored algebra/source trace from FA4 Section 3.1.4 and pinned beta23 SoftmaxSm100, forward softmax, and correction loops. It is not a profiler timeline.

Threshold And Probability Offset Form One Range Contract

The source does not use one threshold for every dtype. It also adds a probability offset for FP8 so values are represented higher in the finite FP8 range before PV. If the retained reference is stale by at most , the largest unnormalized probability is bounded by

Pinned dtype coordinateDerived Format maximum used by source comment
BF16/FP16 input path80256at least FP16 65504
FP8 E4M3FN44256448
other 8-bit path / E5M2 coordinate48409657344

For E4M3FN, the headroom ratio is only 448/256=1.75. This explains why threshold and offset cannot be tuned independently. Raising either can saturate converted P even though the numerator/denominator algebra remains exact over real numbers. Section 0x06 owns the broader FP8 layout and scaling argument; this section owns the local online-softmax envelope.

Finite precision also creates a lower-side risk: entries far below the reference underflow in P. Retaining an old reference does not make that risk worse. The retained sits at most below the running maximum, so each new exponent is up to larger than in a frame that always follows the maximum. A very negative entry must therefore lie further below the maximum before it underflows; the price is upper headroom, which the bound above already accounts for. Moving the reference too frequently adds more accumulator multiplications and rounding. The threshold balances range, work, and error; it is not a universal softmax constant.

Design Alternatives And Their Costs

ChoiceBenefitCost / proof obligation
all hardware MUFU.EX2shortest implementation, strong raw FP32 approximationcan saturate the exponential path exposed in 0x08
all degree-3 software emulationremoves MUFU demandmaximizes FMA/ALU, register, and dependency-chain pressure
partial fragment stripingoverlaps MUFU and FMA resourcestuning is shape/architecture-specific; fragment exclusions matter
degree 4 or 5lower raw FP32 errorone or two extra dependent FMAs and longer live ranges
always update referencestrongest centering and simplest invariantmore O rescale passes and correction synchronization
delayed updatefewer O conversionslarger P dynamic range and threshold/offset proof
correction in softmax warpgroupfewer role handoffslengthens the softmax critical path and register liveness
dedicated correction warpgroupdecouples O rescale from exponential workadds TMEM ownership and barrier protocol
per-row divergent correctionavoids scale-one multiplies in an abstract scalar loopincompatible with warp-cooperative source path; use warp vote

Executable Softmax-Numerics Oracle

The evidence package includes:

loop9_softmax_numerics_fixture.json
    paper accuracy coordinates
    pinned coefficients, source hashes, and tuning predicates
    dtype threshold/offset envelopes
    exact-reference recurrence fixture
    negative mutation registry

loop9_softmax_numerics_oracle.py
    FP32 FMA Horner simulation
    BF16 round-to-nearest-even comparison
    source fragment-dispatch counter
    arbitrary-reference numerator/denominator recurrence
    LSE and dtype-envelope checks

loop9_softmax_numerics_report.json
    deterministic generated result

The oracle proves arithmetic and source-coordinate consistency. It does not reproduce MUFU.EX2, compile CuTe DSL, report register allocation, execute a Blackwell kernel, or measure a speedup.

Softmax-Numerics Failure Matrix

SymptomLikely violated contractFirst check
degree-3 path looks as accurate as hardware in raw FP32BF16-consumed and raw approximation claims were collapsedcompare before conversion
emulation fraction differs from a paper percentagesource fragment/frequency specialization changedinspect freq/res/start and forced hardware fragments
output drifts only when reference update is skippednew block and old accumulator use different referenceslog retained reference, candidate, and exponent bias
denominator is plausible but output is wrongonly D or only N was frame-convertedcompare identical on both states
LSE is biased by a constantmax_offset omitted from final log correctioncheck
FP8 path saturates Pthreshold-plus-offset exceeds dtype envelopecompute
correction path deadlocks/divergesper-lane branch replaced the warp voteinspect ballot and barrier participation
SM103 slows after copied SM100 tuningemulation treated as architecture-independentverify native-exp2 specialization
article claims measured gainCPU oracle/source inspection crossed its evidence ceilingrequire compiled kernel and profile

Renderer-Safe Formula Ledger

base-2 score:
    lambda = softmax_scale * log2(e)
    b = lambda * score

exp2 range reduction:
    n = floor(x)
    f = x - n in [0, 1)
    2^x = 2^n * 2^f

degree-3 Horner polynomial:
    p(f) = p0 + f * (p1 + f * (p2 + f * p3))

conditional retained reference:
    candidate = max(retained_reference, block_max)
    reference' = candidate if candidate-reference > tau else reference
    gamma = 2^(reference-reference')

same-frame recurrence:
    D' = gamma * D + sum(2^(b-reference'+offset))
    N' = gamma * N + sum(2^(b-reference'+offset) * value)

final normalization and LSE:
    output = N' / D'
    LSE = (reference' + log2(D') - offset) * ln(2)

probability range:
    P_max <= 2^(tau + offset)

Softmax-Numerics Boundary And Release Gate

Publish this loop only if:

  • raw scores, natural logits, base-2 logits, retained references, and offsets are distinct;
  • the degree-3 polynomial is tied to pinned coefficients and FP32 FMA order;
  • range reduction, directed rounding, clamp, and exponent reconstruction are explained;
  • paper random-sample errors and the deterministic CPU grid remain separate evidence;
  • raw FP32 and post-BF16 errors are not conflated;
  • partial emulation is derived from the beta23 fragment predicate;
  • source tuning drift beyond 10-25% is explicit;
  • delayed rescaling updates numerator and denominator in one retained frame;
  • retained row_max is not mislabeled as the true running maximum after a skip;
  • final output and LSE are derived from the retained reference and offset;
  • threshold and FP8 offset share one finite-range envelope;
  • correction uses a warp vote and a dedicated ownership handoff;
  • 0x0A still owns backward softmax-gradient scheduling;
  • no CPU oracle is presented as compiled Blackwell accuracy or speed.

0x0A FA4 Backward: Five MMAs, TMEM Lifetimes, And Ordered Reductions

Owner question. How does the attention-backward graph become a legal Blackwell kernel when five matrix contractions, softmax-gradient work, CTA-pair exchange, and global gradient accumulation all compete for different storage and synchronization domains?

This section owns the FA4 backward dataflow and its 1CTA and 2CTA realization. It consumes the exact softmax algebra from M4.3, the FA2 backward partition from M4.4, and the Blackwell machine contract from 0x07. It does not reopen FA1 IO complexity, teach tcgen05 from first principles, or own persistent work scheduling and benchmark reconciliation; those remain in 0x0B-0x0D.

The evidence has four levels and they are not interchangeable:

EvidenceWhat it establishesWhat it does not establish
FA4 Sections 3.2.1-3.2.4backward graph, analytic cycle ledger, 2CTA exchange, deterministic-order intentthe exact beta23 specialization or a measured B200 timeline
official NVIDIA documentationCTA-group MMA, TMEM ownership, DSMEM lifetime, remote-copy byte accountingwhich FlashAttention path was selected
pinned fa4-v4.0.0.beta23 sourceconcrete warp roles, TMEM offsets, pipeline objects, relay barriers, reduction callsgenerated SASS, achieved occupancy, or progress for every specialization
deterministic CPU oraclealgebra, repacking, interval, event-order, and failure invariantscompiled Blackwell correctness or performance

Deriving The Backward Graph Instead Of Memorizing It

For one attention head, let

where , for the paper’s equal-value-dimension model, and is the score scale. Given , the linear map gives two gradients immediately:

The softmax step is the part that is easy to compress too far. For one row ,

Multiplying that Jacobian by the upstream row gives

Define the row scalar

Then the matrix form is

Because and , the same scalar can be obtained without materializing all of :

Finally, differentiating yields

This derivation gives two useful invariants. First, every row of sums to zero:

Second, delta = row_sum(P * dP) and delta = row_dot(dO, O) must agree. The backward oracle checks both identities before it tests any physical schedule.

Why The Paper Counts Five MMAs

Backward does not retain the full score or probability matrix from forward. It reconstructs them tile by tile. The matrix work is therefore:

1. S  = alpha * Q @ K^T       # recompute scores
2. dP = dO @ V^T              # upstream probability gradient
3. dV += P^T @ dO             # value gradient partial
4. dK += alpha * dS^T @ Q     # key gradient partial
5. dQ += alpha * dS @ K       # query gradient partial

P  = exp(S - LSE)             # elementwise reconstruction
dS = P * (dP - delta)         # elementwise softmax backward

The five MMAs are S, dP, dV, dK, and dQ. Reconstructing P and forming dS are elementwise stages between MMA producers and consumers. A source implementation may transpose or permute these tensors to satisfy tensor-operation layouts; those physical orientations do not change the logical equations.

ObjectLogical tile shapeProduced byConsumed byPrecision boundary
SM x NQ @ K^T MMAexponential / P reconstructionFP32 accumulator in TMEM
PM x Nelementwise exp(S-LSE)dV MMA and dSsource-specific operand conversion
dPM x NdO @ V^T MMAdSFP32 accumulator before elementwise work
dSM x NP * (dP-delta)dK, dQ MMAsconverted/staged as an MMA operand
dVN x dP^T @ dO MMAglobal reduction/epilogueFP32 accumulator
dKN x ddS^T @ Q MMAglobal reduction/epilogueFP32 accumulator
dQM x ddS @ K MMAstaged global reduce-addFP32 accumulator

FA4 backward computation graph places five MMA operations, elementwise work, TMEM regions, and reductions across prologue, main loop, and tail

Source: Zadouri et al., FlashAttention-4, Figure 2. The figure is the paper schedule, not a beta23 instruction trace.

Reconstructing The Paper’s One-CTA Roofline

One tile performs five contractions, each with floating-point operations under the usual multiply-add-equals-two convention:

At the paper model’s normalized rate of 8192 operations per cycle,

The shared-memory operand ledger can also be derived rather than copied. Three MMAs use shared/shared-style operand traffic in the simplified model:

S:   Q (M*d)  + K  (N*d)
dP: dO (M*d)  + V  (N*d)
dQ: dS (M*N)  + K  (N*d)

The dV and dK contractions consume their TMEM-side accumulator operand and each read one M*d shared-memory operand. The total operand elements are therefore

and the paper’s 64-element-per-cycle model gives

Two additional transfers matter. dS is written as an MMA operand,

and FP32 dQ is written to and read from shared memory before the global reduction,

The elementwise exponential work is

For M=N=d=128, the complete normalized ledger is:

Resource componentDerivationCycles
five MMAs10*M*N*d / 81922560
shared MMA operands(4*M*d + 3*N*d + M*N) / 642048
dS writeM*N / 64256
dQ FP32 write plus readM*d / 161024
total shared-memory ledger2048 + 256 + 10243328
exponential unitM*N / 161024

The model takes the maximum resource time, not the sum:

T_tile_lower_bound ~= max(T_MMA, T_SMEM_total, T_exp)
                   = max(2560, 3328, 1024)
                   = 3328 cycles

Shared memory exceeds the MMA budget by

This is a peak-rate lower bound. It is not a profiler timeline and does not include launch, dependency bubbles, barriers, tails, atomics, or occupancy loss. Its value is causal: it explains why reducing shared-memory movement can matter even though backward contains more MMA work than forward.

From The Paper Graph To Beta23 Warp Ownership

The pinned flash_bwd_sm100.py specializes the graph into 16 warps, or 512 threads, per CTA:

Warp IDsSource rolePrincipal ownership
0-3reductiondQ TMEM to registers to SMEM to global reduce-add
4-11computereconstruct P, form dS, stage peer exchange, dK/dV epilogue
12MMAissue the five tcgen05 MMA families
13loadTMA producer for input and statistic tiles
14relayconvert peer DSMEM completion into a local MMA-consumable event
15empty / register donorno principal dataflow role

The source uses role-specific setmaxnreg targets. For the audited head-dimension-128 paths:

PathreductioncomputeloadMMAemptySource normalized budget
1CTA152136888824152 + 2*136 + max(88,88) = 512
2CTA13613610410424136 + 2*136 + max(104,104) = 512

These are source allocation targets, not compiler-reported registers per thread or an occupancy proof. setmaxnreg changes which roles can hold live fragments; the compiled resource record still decides residency.

FA4 backward ownership diagram deriving the five MMA dependency chain, beta23 warp roles, and the distinct one-CTA and two-CTA TMEM lifetime maps

Repository-authored reconstruction from FA4 Sections 3.2.1-3.2.3 and beta23 source coordinates. TMEM widths are source-derived specialization coordinates, not measured allocation occupancy.

TMEM Offsets Are A Lifetime Schedule

For the audited 1CTA d=128 specialization, the source assigns a 512-column TMEM allocation as follows:

ColumnsSerial occupantsCompletion edge required before reuse
[0,128)S -> Pscore MMA completes before probability work consumes/reuses it
[128,256)dVdV MMA and epilogue consumption complete
[256,384)dP -> dS -> dQdP retires, then dS feeds both gradient MMAs, then dQ may accumulate
[384,512)dKdK MMA and epilogue consumption complete

The equality of offsets is not itself proof that aliasing is safe. The proof requires disjoint live intervals and completion edges. In particular, the 1CTA path serializes three occupants in [256,384):

dP completion
    -> elementwise dS formation
    -> dS publication to dK and dQ consumers
    -> dQ MMA may reuse the TMEM columns

The audited 2CTA d=128 map differs:

Effective per-CTA columnsOccupant
[0,64)current S/P CTA-group accumulator partition
[64,128)previous-tile dQ accumulator partition
[128,256)dV
[256,384)dP -> dS
[384,512)dK

The source offsets alone do not prove the 64-column physical widths. That reconstruction also consumes the official tcgen05 CTA-group-2 rule: the CTA pair cooperates on one MMA and each CTA receives its accumulator partition. This tighter packing allows previous-tile dQ/dK work to overlap current-tile S/P/dP/dS/dV work in ways the 1CTA alias map cannot express.

There is also a d=192/128 source specialization with different layouts and stage choices. It is a distinct contract, not evidence that the d=128 map generalizes to every head dimension.

One-CTA Prologue, Main Loop, And Tail

At the dependency level, one key tile follows this sequence:

def backward_tile_1cta(q, do, k, v, lse, delta):
    S = mma_qk(q, k)                 # TMEM [0, 128)
    wait_mma(S)
    P = exp_from_lse(S, lse)         # elementwise consumer

    dP = mma_do_vt(do, v)            # TMEM [256, 384)
    dV = mma_pt_do(P, do)            # TMEM [128, 256)
    wait_mma(dP)
    dS = P * (dP - delta[:, None])

    publish_dS_for_gradient_mmas(dS)
    dK = mma_dst_q(dS, q)            # TMEM [384, 512)
    wait_until_dS_operand_retired()
    dQ = mma_ds_k(dS, k)             # reuses [256, 384)

    reduce_dq_tmem_to_global(dQ)
    epilogue_dk_dv(dK, dV)

The real source pipelines multiple tiles. Its prologue creates the first score/probability and gradient states, the main loop overlaps current and previous tiles under separate pipeline phases, and the tail drains outstanding dQ, dK, and dV consumers before storage can be recycled. The pseudocode above preserves ownership and reuse edges but is not a transcription of generated control flow.

Why Two CTAs Must Exchange Half Of dS

Suppose a CTA pair partitions the key dimension. CTA 0 owns columns 0:N; CTA 1 owns columns N:2N. Before dQ, each has

CTA 0: dS[:, 0:N]   and K[0:N, :]
CTA 1: dS[:, N:2N]  and K[N:2N, :]

But every output row requires the complete reduction dimension:

Leaving the data column-partitioned would give each CTA only a partial dQ, followed by two global atomic contributions for the same output rows. FA4 instead repartitions by output rows:

CTA 0 keeps dS[0:M/2, 0:N]
      receives dS[0:M/2, N:2N]
      computes dQ[0:M/2, :] from the full 2N reduction axis

CTA 1 receives dS[M/2:M, 0:N]
      keeps dS[M/2:M, N:2N]
      computes dQ[M/2:M, :] from the full 2N reduction axis

Each CTA sends one (M/2) x N half. Across the pair, the raw exchange payload is

This byte identity is not the paper’s 384-cycle DSMEM entry. The latter is a normalized throughput ledger that includes the paper’s machine-rate assumptions.

FA4 2-CTA dQ path exchanges half of dS through DSMEM before a paired MMA with doubled reduction width

Source: Zadouri et al., FlashAttention-4, Figure 3. The paper figure explains the algorithmic repartition; the next diagram reconstructs the pinned source transaction and reduction ownership.

Two-CTA dQ reduction showing peer half-dS exchange through distributed shared memory, output-row repacking, TMEM-to-register-to-shared staging, and ordered or unordered global reduce-add

Repository-authored algebra and source trace. DSMEM repairs a partition-axis conflict; the semaphore shown later repairs a floating-point ordering conflict.

The DSMEM Copy Is A Transaction Protocol

The CUDA Programming Guide’s DSMEM contract permits cluster CTAs to access one another’s shared-memory partitions only while the participating blocks exist. The PTX cp.async.bulk contract ties a remote cluster copy to an mbarrier transaction byte count.

The beta23 source implements a multi-role handoff:

compute warpgroup:
    write the exchange half into local sdS_xchg
    elected thread arrives-and-expects peer transaction bytes
    issue cp.async.bulk local SMEM -> remote cluster SMEM

relay warp:
    wait until the remote dS transaction completes
    arrive on a local leader barrier

MMA warp:
    wait for the leader event
    issue dQ MMA only after the peer half is visible

The event order is mechanically checked as

write_xchg
-> expect_tx
-> remote_copy
-> relay_wait
-> relay_arrive
-> mma_wait
-> mma_dQ
-> phase_toggle

Omitting expect_tx loses completion accounting. Omitting the relay edge creates a visibility race. Letting only one CTA advance the phase makes the pair disagree about which generation is live. The source also rejects sparse pairings that do not map both CTAs to the same sparse K/V column; a mismatched pair can wait forever on a peer that is working on another logical tile.

Reconstructing The Two-CTA Traffic Trade

The paper normalizes the 2CTA pair against the same 2560-cycle MMA budget:

Resource1CTA2CTAWhat changed
MMA compute25602560same normalized contraction work
SMEM MMA operands20481536paired MMA shares more operand traffic
dS write256256still required as an MMA operand
dS DSMEM exchange0384new peer repartition cost
dQ write plus read1024512each CTA owns half the output rows
total SMEM/DSMEM ledger33282688smaller remaining traffic bottleneck
exponential unit10241024unchanged normalized work

The total shared-memory-side estimate falls by

More importantly, the excess above the 2560-cycle MMA budget falls from 768 to 128 cycles:

The pair did not remove communication. It exchanged a specific dS half so that it could avoid duplicated operand movement and halve the FP32 dQ staging/global-reduction surface. Normalizing one pair over two N partitions, the oracle counts

1CTA plan: 65,536 dQ atomic elements
2CTA plan: 32,768 dQ atomic elements
ratio:      0.5

This is a count of reduction elements under the paper tile mapping, not a claim that atomic latency or end-to-end backward time is exactly halved.

dQ Reduction: TMEM To RMEM To SMEM To Global

The output of the dQ MMA is not directly a final global tensor. In beta23:

reduction warps 0-3
    load CTA-owned dQ rows from TMEM into registers
    repack and stage them into shared memory
    execute a shared-memory fence
    synchronize the 128 reduction threads
    issue bulk global FP32 reduce-add
    wait for completion before stage reuse or semaphore release

The shared fence and barrier are correctness edges. Performing the global add before all four reduction warps have published their slice can expose partially initialized shared memory. Reusing the stage before the bulk operation completes can overwrite bytes still in flight.

Determinism Requires A Writer Order

Global gradient accumulation combines contributions from multiple tiles or query heads. Floating-point addition is not associative. The oracle’s four FP32 contributions

[100000000.0, 1.0, -100000000.0, 3.0]

sum to 4.0 in one arrival order and 3.0 in the enforced writer order. A deterministic kernel therefore needs more than a deterministic mathematical expression; it needs a deterministic physical reduction sequence.

The pinned source follows this protocol:

wait_eq(semaphore, my_lock_value)
stage_dq_tmem_to_smem()
shared_fence_and_reduction_warp_barrier()
bulk_global_reduce_add_f32()
wait_for_bulk_reduce_completion()
arrive_inc(semaphore)

The order of those calls matters. Releasing the semaphore before the global reduce-add completes allows the next writer to overlap the same accumulator in an order that is no longer the declared sequence.

The source uses different lock coordinates for different ownership patterns:

CaseOrdered coordinateSource-derived lock idea
dense dQK/V tile ordinallock = n_block
causal SPT dQvalid K/V tiles in reverse writer orderlock = n_block_max_for_m_block - 1 - n_block
GQA dK/dVquery head within a shared K/V headlock = head_idx % qhead_per_kvhead

For the causal example with valid physical writers [1,2,3], reverse scheduling produces execution order [3,2,1] and lock values {3:0, 2:1, 1:2}. Sparse or truncated schedules create another hazard: if a physical writer slot is absent, later writers cannot wait for an increment that will never occur. Beta23 contains tail bridging that advances missing slots. The oracle intentionally removes that bridge and observes a deadlock after the first writer.

The deterministic path pays for waiting, fences, and serialized add order. The nondeterministic path lets arrival order select the FP32 add order and can be faster. They are different schedules and must be benchmarked as such.

Variants And Design Alternatives

Design axisAlternativeBenefitCost or constraint
CTA group1CTAsimpler local ownership and no DSMEM exchangeduplicated traffic and more dQ reductions
CTA group2CTAlower normalized SMEM ledger and half the dQ atomic elementscluster admission, peer liveness, DSMEM transaction, stricter phases
head dimensiond=128 mapaudited compact TMEM layoutnot universal
head dimensiond=192/128 source specializationsupports another shape familydifferent layouts/stages; separate proof required
reduction ordernondeterministic bulk addavoids semaphore serializationrun-to-run add order may vary
reduction orderdeterministic semaphorerepeatable declared writer orderwait/fence/tail-bridge overhead
head sharingMHAone query head per K/V headless cross-head reduction sharing
head sharingGQA/MQAK/V gradients combine query-head contributionsadditional deterministic head-order coordinate
sparsitydense/causalregular or monotone writer setscausal order still needs valid-slot accounting
sparsityblock sparse / variable lengthskips workpaired CTAs and semaphore slots must agree on the same physical work set

The decisive design choice is not “use 2CTA because it is newer.” It is whether the saved local traffic exceeds the cost of cluster admission, exchange, synchronization, and any occupancy or tail loss for the actual shape.

Executable Reconciliation

The backward oracle reconstructs both the algebra and the ownership schedule on deterministic CPU fixtures:

CheckResult
one-CTA paper roofline maximum absolute error0 cycles
tiled dK versus direct reference5.55e-17 max absolute error
tiled dQ versus direct reference1.11e-16 max absolute error
tiled dV versus direct reference1.11e-16 max absolute error
exact 2CTA row/column repack0 max absolute error
omit peer dS exchange0.6799 max absolute dQ error
exchange the wrong row half1.2337 max absolute dQ error
swap peer K column order1.4536 max absolute dQ error
valid relay event sequenceaccepted
omit expected bytes / relay arrival / MMA waitrejected
valid TMEM live intervalsno concurrent overlap
force concurrent 1CTA dP/dS/dQ aliasrejected
deterministic dense and causal writer orderscompleted
missing writer slot without tail bridgedeadlock detected

These results are stronger than prose examples and weaker than a compiled kernel run. The oracle does not lower CuTe DSL, model TMEM latency, prove compiler scheduling, or profile B200 hardware.

Paper, Source, And Runtime Boundaries

ClaimHighest evidence reached here
five-MMA and softmax-gradient algebraderivation plus deterministic oracle
paper cycle ledgerexact arithmetic reproduction
warp IDs, register targets, TMEM offsets, relay, and reduction callspinned beta23 source
CTA-group accumulator partition and DSMEM lifetimeofficial NVIDIA documentation plus source reconstruction
legal generated barriers for every specializationnot established
compiled register count, occupancy, selected kernel, overlap, and timingnot measured

Renderer-safe formula ledger:

S = alpha * Q @ K^T
P = row_softmax(S)
O = P @ V

dP = dO @ V^T
delta_i = sum_j P_ij * dP_ij = dot(dO_i, O_i)
dS = P * (dP - delta[:, None])
dV = P^T @ dO
dK = alpha * dS^T @ Q
dQ = alpha * dS @ K

T_MMA = 10 * M * N * d / 8192
T_SMEM_MMA = (4*M*d + 3*N*d + M*N) / 64
T_dS_write = M*N / 64
T_dQ_write_read = M*d / 16
T_exp = M*N / 16

B_xchg_pair = M * N * bytes_dS
T_tile_lower_bound ~= max(T_MMA, T_SMEM_total, T_exp)

Backward Failure Lab

FailureWhy a plausible implementation can still be wrongRequired proof
count only four MMAsforgets score recomputation because forward did not retain Slogical graph and FLOP derivation
materialize P globallypreserves algebra but abandons FlashAttention’s state contracttile-local reconstruction trace
reuse equal TMEM offsets concurrentlycoordinates match but lifetimes overlapinterval plus completion-edge proof
infer 2CTA widths from offsets aloneignores CTA-group accumulator partitionofficial CTA-group contract plus source layout
omit peer half of dSeach CTA computes only a partial reduction axisexact repack oracle
exchange the same half twicebyte counts and shapes still look validrow/column ownership check
issue dQ MMA before relay arrivalpeer data may not be visibleexpect-copy-relay-MMA event trace
pair different sparse K/V columnseach CTA waits on the wrong peer generationpair-descriptor admission
reduce before shared fence/barrierglobal bulk add sees incomplete stagingreduction-warp publication proof
release lock before bulk completionnext writer changes the declared FP32 orderwait-before-arrive protocol
leave a missing semaphore slota later writer waits forevertail bridge or compact writer numbering
call the CPU oracle B200 evidencearithmetic checks pass without a compiled kernelartifact plus profiler evidence

Backward Boundary And Release Gate

Publish this loop only if:

  • the softmax Jacobian produces the final dS formula and zero-row-sum invariant;
  • delta = row_sum(P*dP) = row_dot(dO,O) is derived and tested;
  • all five MMAs have tensor shapes, producers, consumers, and precision boundaries;
  • the one-CTA roofline is derived from operand traffic rather than copied as a table;
  • paper lower bounds remain distinct from profiler time;
  • beta23 warp roles and source register targets are not called compiled occupancy;
  • TMEM reuse is justified by lifetimes and completion edges;
  • 1CTA, 2CTA d=128, and the separate d=192/128 specialization are not conflated;
  • the half-dS exchange is derived from the reduction-axis conflict;
  • DSMEM transaction bytes, relay completion, and MMA consumption are ordered;
  • dQ staging includes TMEM, RMEM, SMEM, fence/barrier, and global reduce-add;
  • deterministic dense, causal SPT, sparse-tail, and GQA orders are distinguished;
  • all negative mutations fail mechanically;
  • the CPU oracle is not presented as a Blackwell artifact or timing result;
  • persistent scheduling, CuTe artifact integration, and benchmarks remain deferred to 0x0B-0x0D.

0x0B Persistent Scheduling, LPT, And Variable-Work Geometry

Owner question: once the inner kernel is pipelined, how should a finite set of unequal attention tiles be ordered and assigned so that the last SM does not inherit all of the long work?

This section owns global tile-cost estimation, LPT coordinate order, persistent work progression, cache-aware head/batch swizzles, CLC work assignment, and paper-to-release scheduler drift. It consumes the per-tile pipelines derived in 0x03-0x0A. It does not rederive 0x0A’s dQ semaphore or shortest-processing-time-first reduction protocol, and it does not reopen M4.5’s framework-level backend admission.

Four Terms That Sound Similar But Are Not

TermWhat it controlsState that changesWhat it does not prove
Persistent CTA schedulinghow one resident CTA obtains more than one logical tilecurrent linear tile/work descriptorthat the tile order is load-balanced
LPT orderingwhich estimated-cost tile is made available firstlogical coordinate linearizationthat CTAs remain resident or steal work
CLC dynamic schedulinghow hardware-assisted work descriptors are requested and returnedproducer/consumer work tokenthat the static L2 swizzle is preserved
Deterministic SPT reductionwhich backward writer reaches a shared dQ row firstsemaphore turn and reduction orderthat forward or backward work is globally longest-first

The distinction matters in the pinned source. StaticPersistentTileScheduler caps the physical grid by the SM count and advances a resident CTA by grid stride. SingleTileLPTScheduler maps a linear tile index through an L2 swizzle and optional block reversal; its static path performs one logical tile per CTA, while its CLC path consumes hardware-assigned work. One class name or one is_persistent flag therefore cannot describe the whole scheduling contract.

Deriving A Causal Tile-Cost Proxy

Let a query tile contain rows

and let each K/V tile contain key positions. For aligned causal self-attention without a local window, the largest key index visible to query tile is approximately the last query position in the tile. The number of K/V tiles that can contain a valid score is therefore

For and a full square sequence, this reduces to

The real cycle cost contains a fixed tile prologue/epilogue plus per-K/V-tile loads, two MMAs, mask work, softmax work, and possible sparse or tail corrections:

J_m is useful because it is cheap metadata and preserves the dominant monotone ordering for the causal dense case. It is not a universal cycle model. A sliding window clips ; block sparsity replaces it with selected-block count; variable lengths make it batch-dependent; SplitKV and two-CTA modes change the cost per selected block.

If identical worker slots receive tile set , the makespan objective is

with the unavoidable lower bound

This is why utilization at the beginning of a launch is not enough. The kernel completes when the most-loaded worker finishes.

Worked Makespan: Lexical Order Versus LPT

For six causal query blocks with cost proxy [1,2,3,4,5,6] and three worker slots, a first-free list scheduler gives:

OrderWorker loadsMakespanInterpretation
lexical m=0..55, 7, 99the late wave contains the longest tiles
LPT m=5..07, 7, 77long work starts early; short tiles fill the tail

The lower bound is max(21/3, 6) = 7, so LPT reaches it in this toy. That is an accounting example, not the source of the paper’s measured speedups. Its purpose is to expose the tail mechanism that a TFLOPS bar hides.

def list_schedule(tile_costs, logical_order, worker_count):
    loads = [0] * worker_count
    schedule = []
    for tile in logical_order:
        worker = min(range(worker_count), key=lambda p: (loads[p], p))
        start = loads[worker]
        finish = start + tile_costs[tile]
        loads[worker] = finish
        schedule.append((tile, worker, start, finish))
    return schedule, max(loads)

lexical = range(6)              # costs 1,2,3,4,5,6
lpt = reversed(range(6))        # costs 6,5,4,3,2,1
Causal tile cost, lexical versus LPT makespan, cache-aware head sections, persistent and CLC scheduling, and variable-length paper-to-source drift

Why Naive Global LPT Can Still Lose

The FA4 paper’s Section 3.3 does not merely reverse a flat list. A global longest-first sort can interleave batches whose K/V tensors are unrelated or bring too many K/V heads into the active set. It may reduce arithmetic tail length while increasing L2 misses.

For one batch and one K/V head, a rough resident K/V working-set estimate is

so a head section should satisfy

The paper uses this hierarchy:

for batch in batches:                       # unrelated K/V batches stay outside
    for head_section in L2_fitting_sections:
        for mblock in reverse(query_blocks): # longest causal work first
            for head in head_section:
                issue(batch, head, mblock)

The beta23 fixed-length source mechanically reconstructs the same shape of static coordinate map: it flattens batch/head sections, chooses a power-of-two head swizzle, reverses mblock when lpt=True, and treats the residual head section separately. The source sets its numerical L2 heuristic to 50 * 1024 * 1024 bytes even though one adjacent comment still says 40 MB; that constant is a release-specific heuristic, not a portable hardware capacity statement.

For MQA/GQA, the paper adds a reuse constraint: traverse all query heads sharing one K/V head before moving the K/V-head work coordinate. This reduces repeated K/V working-set turnover. It is a cache-locality choice layered on top of the cost order, not a change to attention semantics.

The paper reports, on H200 with BF16 and head dimension 128, 4-8% FLOPS gain for MHA and 7-14% for MQA8 from the described scheduling order. Those values are source-reported scheduling measurements on the paper’s configurations, not expected gains for beta23, B200, arbitrary sequence distributions, or end-to-end serving.

Static Persistence And CLC Work Stealing

The pinned source implements at least two different ways to move from one logical work tile to another.

Static persistent grid-stride path. StaticPersistentTileScheduler.get_grid_shape caps grid x at the number of available SM slots, rounded for cluster shape. Each resident CTA then advances:

next_linear_tile = current_linear_tile + physical_grid_x

This reduces the physical launch grid and lets each resident CTA consume several tiles. The assignment is deterministic from grid geometry, but it can still suffer if every stride column receives a bad mixture of long and short tiles.

CLC dynamic path. SingleTileLPTScheduler and SingleTileVarlenScheduler also accept SchedulingMode.CLC. Their protocol is:

work = clc.initial_work_tile_info()
while work.is_valid:
    consume(map_hardware_work_to_attention_coords(work))
    clc.prefetch_next_work()
    clc.consumer_wait()
    work = clc.get_current_work()
    clc.consumer_release()
clc.producer_tail()

The CLC response contains raw work-grid coordinates. The beta23 source explicitly says that this path does not apply the static L2 swizzle because hardware decides work order; it only applies cluster conversion, optional LPT block reversal, and SplitKV unpacking. Dynamic assignment can improve tail balance, but it can also sacrifice the cache order that made static LPT useful.

That tradeoff appears in the host selector. In beta23, a requested CLC scheduler is disabled for variable-length MHA and dense noncausal attention because source comments record regressions from additional K/V blocks in flight, worse L2 behavior, or pure work-stealing overhead. use_clc_scheduler is then included in the forward compile key, so changing this policy changes artifact identity rather than only runtime metadata.

Variable Lengths: Paper Algorithm And beta23 Source Diverge

For sequence lengths [128,768,256,512] with M=128, the query worktile counts are [1,6,2,4]. A paper-style LPT preprocessing pass produces a virtual-to-actual map:

virtual batch:  0  1  2  3
actual batch:   1  3  2  0
worktiles:      6  4  2  1

The paper proposes a preprocessing kernel that sorts batches by estimated maximum per-worktile execution time, writes that map, and allows the metadata to be cached. That design is especially relevant to mixed prefill/decode batches, where lexical request order has no reason to correlate with cost.

The pinned beta23 SingleTileVarlenScheduler should not be described as that exact implementation. Its main coordinate map reads cu_seqlens_q or seqused_q, computes per-batch query-block counts with warp prefix sums, scans groups of 31 batches to locate the current flat tile, and then applies per-batch head swizzle and optional block reversal. The main scheduler does not consume the paper’s virtual-to-actual sort map. A varlen_batch_idx appears in the separate forward-combine surface, but that is not evidence that the main attention scheduler implements the Section 3.3 preprocessing contract.

This is a useful form of source drift:

ContractBatch orderCost order inside batchEvidence
FA4 paper varlen designsorted virtual batcheslongest estimated tile firstpaper algorithm
beta23 main varlen scheduleroriginal metadata order located by prefix sumsoptional reverse block and head swizzlepinned source
deploymentwhichever compiled path actually runsprofiler-observednot measured here

Deterministic Reduction Is A Consumed Boundary

Section 0x0A already derives why deterministic dQ uses semaphores, fences, a declared writer order, causal SPT, sparse-tail bridging, and GQA-aware turns. This section consumes only the consequence:

LPT:
    tries to reduce global makespan by starting expensive work early

deterministic causal SPT:
    tries to make the first dQ writer arrive without waiting and fixes FP32 order

The two objectives can conflict. The paper’s deterministic schedule combines K/V-block order, query-block order, batch/head swizzle, and reduction turn order rather than applying one universal sort.

FlashAttention-4 source-reported deterministic backward scheduler ablations on B200

Source: Zadouri et al., FlashAttention-4, Figure 8, cropped from the CC BY-NC-SA 4.0 paper. Left: noncausal batch/head swizzle versus naive order. Right: causal SPT, reverse-mblock LPT, naive LPT, and naive order. Values are source-reported B200 FP16/BF16 deterministic-backward throughput for head dimension 128; they are not beta23 measurements.

For mobile-readable spot checks, the figure reports:

SequenceNoncausal swizzle / naiveCausal SPT / reverse-LPT / LPT / naive
1k630 / 500470 / 450 / 440 / 250 TFLOP/s
8k850 / 730920 / 790 / 720 / 350 TFLOP/s
32k880 / 880950 / 830 / 760 / 540 TFLOP/s

The convergence of noncausal swizzle and naive order at 32k is as important as the 1k gap: scheduler value depends on wave count, cache behavior, and tail geometry.

Scheduling Failure Closure

MutationWhat still looks plausibleWhat fails
launch causal mblock in increasing orderall tiles are correctlate workers inherit the longest mainloops
globally sort all heads and batches by costarithmetic tail may shrinkunrelated K/V working sets thrash L2
call grid-stride persistence LPTCTAs process many tileslogical order can remain poorly balanced
call CLC work stealing the static L2 swizzleboth return legal coordinatescache locality contract is different
call beta23 varlen prefix sums the paper sortbatch coordinates are recoveredno virtual-to-actual LPT ordering exists
apply forward LPT to deterministic dQ turnslong tiles start earlyfirst writer can stall and FP32 order changes

0x0C CuTe DSL, Dispatch, And The Artifact Boundary

Owner question: which facts are proven by Python source, which are fixed by a compile key, and which require the generated artifact and the runtime launch?

CuTe DSL is Python-hosted metaprogramming, not interpreted device execution. NVIDIA’s CuTe DSL code-generation documentation describes Python execution and tracing into compiler IR, progressive lowering, translation to PTX/SASS, device-binary construction, module load, and launch. The debugging contract exposes retained IR, PTX, CUBIN, SASS, and Python-to-device line information.

Source-To-Execution Proof Ladder

Python API call
    -> host validation and dispatch predicates
    -> compile key and specialization cache
    -> CuTe IR / MLIR lowering
    -> PTX
    -> ptxas -> CUBIN / SASS
    -> JIT executor and loaded kernel symbol
    -> profiler trace + numerical comparison + timing distribution

Each arrow can fail independently. An API may admit a shape that fails compilation. A compiled executable may not be the one selected after a cache-key bug. A selected kernel may run correctly but spill or occupy fewer CTAs than the source target. A fast kernel-only timing may still lose end-to-end after compilation, metadata preprocessing, or fallback.

CuTe source to IR PTX CUBIN SASS runtime proof ladder, beta23 compile-key groups, and beta24 release drift

Paper-Era Hardware Matrix

The FlashAttention-4 paper targets B200/GB200 Blackwell. FlashAttention-3’s paper contract targets Hopper, while FlashAttention-4’s algorithm and reported resource model target Blackwell. Those paper coordinates establish algorithmic intent and source-reported measurements; they do not establish the dispatch surface of a later package release.

Paper coordinateHardware coordinateContract used here
FlashAttention-3H100/H800-class HopperTMA/WGMMA pipeline, warp specialization, FP8 method, and paper measurements
FlashAttention-4B200/GB200-class BlackwellTMEM/tcgen05 pipeline, two-CTA schedule, software exponential path, and paper measurements

Pinned Artifact Scope

The pinned beta23 package exposes SM90, SM100, and SM120-family paths as well as a separate SM80 forward path. The broader beta23 source surface does not retroactively change the FA4 paper’s hardware contract. Conversely, the paper does not prove that every feature in beta23 uses the paper’s exact schedule: that requires source-path selection, generated-artifact inspection, and runtime evidence for the chosen specialization.

The Release Pin Moved During This Audit

The implementation anchor remains pre-release fa4-v4.0.0.beta23, published 2026-07-22 at commit 2409214a03797b168f648ea30df1adbc09ce658a. As of this audit on 2026-07-30, the official repository has already published fa4-v4.0.0.beta24, dated 2026-07-29 at commit 849f660f73b176e5ad5670e7f822c7fa9f3eaf8b.

LayerImmutable identityWhat this article may claim
FA4 paperarXiv 2603.05451v1, audited PDF SHA-256 7c053b...f10ebpaper algorithms and source-reported measurements
implementation anchorbeta23, commit 2409214a...source predicates and source structure extracted below
drift headbeta24, commit 849f660f...release-note drift only, not beta23 behavior
local executionnoneno compiled kernel, artifact, profile, or timing claim

beta24’s official release notes include variable-length backward score-mod support, combine-kernel scheduling changes for CUDA Graph capture, and FP8 numerical changes. The beta23 and beta24 tile_scheduler.py blob is unchanged, but interface.py changed. That is enough to keep the matrix pinned: release drift can alter validation, compile keys, numerical paths, or wrapper integration even when one scheduler file is identical.

beta23 Interface Admission Is A Predicate Set

The beta23 forward interface first validates tensor shapes, dtype equality, integer metadata layout, CUDA placement, architecture family, head ratio, and optional-state compatibility. It then derives tile sizes, SplitKV, two-CTA eligibility, sparse normalization, scheduler mode, and finally a compile key.

The source-admitted surface is broader than the FA4 paper:

Surfacebeta23 source predicateImportant exclusions
forward architecturearch // 10 in [8,9,10,11,12]each family selects a different class and feature set
backward architecturearch // 10 in [9,10,11,12]SM80 forward admission does not imply beta23 backward admission
input dtypeFP16, BF16, FP8 E4M3FN/E5M2 at API boundaryFP8 is forward-only and restricted to SM10x source path
SM90 head dimensionsaligned 8..256 for Q/K and Vprofitable specialization is not guaranteed
SM100/110 head dimensionsaligned 8..128, (192,128), selected 64/512, or (256,256)special paths add feature exclusions
SplitKV forwardSM10x/11x conditionalsource rejects it on SM80, SM90, and SM120; shape can force one split
paged K/V forwardSM90 and SM10x/11x conditionalrejected on SM80/SM120; page-size/path constraints remain
Blackwell 2-CTA forwardnoncausal, nonlocal, fixed-Q, nonsplit, dense, compatible shapeshost/env flags and dedicated d=256 rules can override
static persistent forwardSM10x/11x noncausal, nonlocal, fixed-Q, nonsplitpersistence does not imply CLC or LPT
deterministic backwardselected SM90/SM10x/SM11x pathsexplicitly rejected on SM120; sparse mode needs write-order metadata

This is a source-admission matrix. It does not mean every admitted combination was compiled in this audit, passes correctness tests, selects the paper schedule, or is faster than another backend.

Why The Compile Key Is Part Of Correctness

The beta23 forward key has dozens of fields. Listing them flat hides why they exist, so group them by the generated code they can change:

numeric and shape:
    dtype, d_qk, d_v, qheads_per_kvhead, architecture

semantics:
    causal, score_mod hash, mask_mod hash,
    block-sparse presence/broadcast pattern, auxiliary captures

optional tensor state:
    LSE presence, cu_seqlens/seqused presence, page table,
    window sides, learnable sink, FP8 descales, sparse offsets

tile and schedule:
    tile_m, tile_n, q_stage, thread count, SplitKV, pack-GQA,
    paged non-TMA mode, 2CTA, subtile factors,
    PV RS/SS placement, intra-WG overlap, CLC

specialized paths:
    Q/QV presence, top-k gather length, sparse-KV flags, log level

The operational pattern is:

compile_key = derive_constexpr_identity(validated_call, selected_policy)

if compile_key not in compile_cache:
    kernel_object = select_architecture_and_feature_class(compile_key)
    compile_cache[compile_key] = cute.compile(
        kernel_object,
        fake_or_specialization_tensors,
        options="--enable-tvm-ffi",
    )

executor = compile_cache[compile_key]
executor(runtime_tensors_and_scalars)

If a value changes control flow, instruction family, static tensor layout, optional pointer ABI, or synchronization topology, it belongs either in the key or in a validated runtime contract. Omitting a compile-time branch can return a stale executor whose Python call signature remains shape-compatible while its device assumptions are wrong.

The reverse error is also costly: adding values that do not affect generated code can explode cache cardinality and first-use compile latency. Artifact integration is therefore a partitioning problem between:

constexpr identity -> one generated executor
runtime metadata   -> many legal calls to that executor

The Compiler Artifact Must Be Retainable

The paper describes:

CuTe DSL Python -> PTX -> ptxas -> SASS

Current official CUTLASS documentation makes the proof boundary more explicit. A compiled JIT executor can expose generated MLIR/PTX/CUBIN, and debug options can retain IR, PTX, CUBIN, and SASS. A release-quality artifact manifest should therefore contain:

source:
    repository, tag, commit, source-file blob SHAs

compiler:
    nvidia-cutlass-dsl, CUDA/ptxas, target SM, options, environment

specialization:
    canonical compile key, input ABI, selected class and scheduler

artifact:
    IR/PTX/CUBIN hashes, loaded module and kernel symbol, SASS hash

execution:
    driver/GPU identity, resource attributes, profiler trace, correctness result

beta23’s package pin is itself multi-component: Python >=3.10, nvidia-cutlass-dsl==4.6.0.dev0, apache-tvm-ffi>=0.1.12,<0.2, and quack-kernels>=0.5.3. The unbounded upper range on Quack means the FA4 tag alone does not determine every helper implementation. A strict reproduction should lock the resolved environment, not just copy the tag.

Paper, Source, Artifact, And Runtime Claim Matrix

ClaimPaperbeta23 sourceCompiled artifactRuntime/profile
LPT design existsprovescontains related mappingsnot requiredmeasures effect
exact scheduler variant rannoeligible onlysymbol/code can proveprofiler confirms
shape is acceptedpaper cases onlyhost predicate provescompile may still failexecution proves
WGMMA/tcgen05 instruction emittedintent/algorithmsource intentPTX/SASS provescounters corroborate
register/SMEM/TMEM residencymodel/targetsannotations/heuristicsptxas metadata partly provesprofiler attributes prove
beta23 reaches paper TFLOPSnononorequires beta23 timing manifest

The evidence produced for this article reaches the paper and source columns. It deliberately does not claim local compilation or execution.

Release And Re-Audit Policy

A current-support statement expires when any of these changes:

attention release or source commit
compiler/CUDA/ptxas version
target architecture
compile-key schema or selected specialization
dependency resolution
benchmark numerator or timing boundary

The re-audit sequence is mechanical: extract the new interface predicates, regenerate the support matrix, diff source blob SHAs and compile-key groups, retain a representative artifact, rerun correctness, then benchmark the selected symbol. A release note is a drift signal, not a substitute for that sequence.

0x0D How To Read FA3 And FA4 Benchmarks

Benchmark Numerators, Timing Boundaries, And Reconciliation

Owner question: when a graph says TFLOP/s or 1.3x, which operations were counted, which wall-clock interval was timed, and which exact artifact and baseline own the number?

Derive The Counted FLOPs Before Reading The Bar

For batch , query length , key length , heads, Q/K head dimension , and V dimension , dense forward attention contains two dominant matmuls:

Therefore

When and , this becomes the paper’s familiar per-batch form with the restored batch multiplier:

For exact aligned causal self-attention, the valid score-pair count is triangular:

so an exact useful-work numerator would replace by . The FA4 paper uses the simpler convention of dividing dense FLOPs by two. At long sequence length the relative difference is ; at short length or with tile padding it can be visible.

The paper counts backward as 2.5 * forward because the backward algorithm performs five dominant matmuls while forward performs two:

This omits non-matmul instructions, preprocessing, reduction traffic, and recomputation details from the numerator even though they remain in elapsed time. TFLOP/s is therefore an accounting throughput, not a statement that every cycle issued tensor-core FLOPs.

Useful, Scheduled, And Dense-Equivalent Numerators

One number is not enough for local windows, sparsity, ragged batches, or padded tiles:

NumeratorCountsUseful forFailure if mislabeled
useful FLOPssemantically valid score/output pairsalgorithmic efficiencyhides executed padding or masked work
scheduled FLOPsworktiles/instructions actually issued under a declared modelkernel efficiencyrequires accurate path-specific accounting
dense-equivalent FLOPsdense reference work regardless of sparsityeffective application ratecan exceed hardware peak without being physical FLOPs

For a block-sparse kernel, dense-equivalent TFLOP/s can legitimately exceed a hardware dense peak because the numerator credits skipped work. Calling it measured tensor-core throughput would be wrong.

A Numeric Reconciliation

Take B=32, S_q=S_k=1024, H=16, d=128. The counted dense forward work is

4 * 32 * 1024 * 1024 * 16 * 128
= 274,877,906,944 FLOPs

Under the paper’s causal-half convention:

ModeCounted FLOPsExample timeDerived accounting throughput
dense forward274.878e90.20 ms1374.39 TFLOP/s
causal forward137.439e90.20 ms687.19 TFLOP/s
dense backward687.195e90.45 ms1527.10 TFLOP/s
causal backward343.597e90.45 ms763.55 TFLOP/s

These times are synthetic oracle inputs, not measurements. The point is that reusing dense forward FLOPs for the causal bar doubles the reported number, while forgetting the backward 2.5x factor understates it.

Kernel Time Is Not Compile Time Or End-To-End Time

At least four clocks can be relevant:

developer compile time:
    source specialization -> executable artifact

cold first-call latency:
    validation + cache lookup/miss + compile/load + launch

warm kernel time:
    already-compiled selected symbol only

end-to-end attention latency:
    metadata preprocessing + packing/paging + kernel(s) + combine + framework overhead

FA4 Table 4 reports one-kernel developer compile time:

MethodForward compileBackward compileReconciled speedup
FA3 C++ templates55 s45 sbaseline
FA4 CuTe DSL2.5 s1.4 s22.0x, 32.14x (32x rounded in paper)

This supports the paper’s 20-30x iteration claim. It does not imply a faster GPU kernel, a 32x first-request improvement, or a complete package build that is 32x shorter. The release’s benchmark.py helper also has its own timing scopes; its presence does not prove that beta23 recreated Table 4 or the paper’s B200 protocol.

FA3 Source Results Remain A Hopper Contract

The FA3 paper evaluates H100 80GB SXM5 with FP16 inputs across sequence lengths 512 through 16k, fixed total-token configurations, and several head dimensions. It reports:

up to 740 TFLOP/s, about 75% of the cited H100 theoretical rate
about 1.5-2.0x FA2 forward in tested configurations
about 1.5-1.75x FA2 backward in tested configurations
FP8 forward close to 1.2 PFLOP/s in reported cases

FlashAttention-3 forward throughput across sequence lengths, causal modes, and head dimensions on H100

Source: Shah et al., FlashAttention-3, Figure 5. These are forward-kernel measurements on H100 80GB SXM5 for the paper’s declared inputs, shapes, and baselines. They are not end-to-end model throughput and do not predict a Blackwell result.

The FA4 paper notes that its compared FA3 implementation does not run on B200. That is a paper-era artifact statement: WGMMA, TMEM, CTA-group instructions, compiler support, and synchronization contracts are architecture-specific. It does not mean that no later FA3-derived or CuTe implementation can target Blackwell.

FA4 Paper Benchmark Manifest

The main text’s benchmark contract is:

FieldPaper value
hardwareB200 in main text and Figures 4-8
inputBF16 in Section 5; captions say FP16/BF16
sequence lengths1k, 2k, 4k, 8k, 16k, 32k
batchchosen so total tokens equal 32k
hidden dimension2048
standard headsd=64, H=32; d=128, H=16
DeepSeek shape(d_qk,d_v)=(192,128), H=16
maskcausal and noncausal
forward numerator4 * S^2 * d * H, with batch implied by problem and causal divided by two
backward numerator2.5 * forward
compared systemsPyTorch, FA2, Triton, Gluon, cuDNN 9.13

There is an unresolved metadata inconsistency inside the paper. The paper’s main text and plotted labels identify B200, while Appendix A.1 records B100; the full conflict is preserved below before these values are interpreted as a reproduction contract.

It reports up to 1613 TFLOP/s, about 71% of the cited B200 theoretical rate, up to 1.3x cuDNN 9.13, and up to 2.7x Triton. These are best-case, source-reported paper claims.

FlashAttention-4 forward throughput for causal and non-causal head-dimension-128 attention on B200

Source: Zadouri et al., FlashAttention-4, Figure 4. The plots report B200 forward accounting throughput with head dimension 128. The paper states that newer cuDNN versions incorporated related techniques and can approach FA4, so this is a versioned comparison, not a permanent ranking.

Preserve The Paper’s Metadata Contradiction

Appendix A.1 says:

GPU: B100 180GB SXM6 (1000W)
warmup: 5 runs
timed repetitions: 10
statistic: arithmetic mean
CUDA: 13.1
FlashAttention: 2.8.3
Triton: 3.6
PyTorch: 2.10.0
CuTe-DSL: 4.4.1
cuDNN: 9.13 in main comparison; 9.19.1.2 as latest appendix entry

The main text and every relevant figure label say B200, while the appendix says B100 and dates the software snapshot to “March 2025” despite arXiv v1 being submitted in March 2026. This article does not guess which appendix labels are typographical errors. It does not silently replace B100 with B200, infer the intended date, or merge the two manifests. Until an author correction resolves the conflict, the appendix discrepancy remains part of the benchmark record and prevents the appendix from serving as a complete reproduction contract.

Baseline Reconciliation

Speedup is a ratio of two measured intervals:

It is only stable when numerator semantics and timing boundaries match. Before quoting a bar, verify:

same GPU, power mode, and clocks
same B, Sq, Sk, Hq, Hkv, dQK, dV
same dtype, scaling, mask, window, sparsity, and deterministic mode
same warmup, synchronization, repetitions, and statistic
same allocation/preprocessing/compile inclusion boundary
same correctness threshold
exact baseline and tested-kernel versions

The paper itself says newer cuDNN releases adopted related techniques. A comparison against cuDNN 9.13 does not establish the same ratio against 9.19.1.2, beta23, beta24, or a framework backend that selects another kernel.

Source-Reported, Release-Audited, And Locally Measured Must Stay Separate

StatementCorrect label
FA4 reaches up to 1613 TFLOP/ssource-reported paper peak
beta23 accepts this architecture/shape/feature predicatepinned-source audit
beta23 compiles to this symbol and SASSrequires retained local artifact
that symbol reaches X TFLOP/s hererequires local timing manifest
the deployment gains Y tokens/srequires end-to-end service measurement

No Blackwell or Hopper GPU artifact was compiled or timed for this loop. The local oracle checks schedule accounting, compile-time ratios, and FLOP numerators only.

Reproduction And Rollback Manifest

For one benchmark row, record:

identity:
    paper/release/commit, source blob SHAs, dependency lock
    IR/PTX/CUBIN/SASS hashes and loaded kernel symbol

environment:
    GPU product, SM count, memory, clocks, power mode
    driver, CUDA, ptxas, CuTe DSL, PyTorch, framework

dispatch:
    requested backend, admitted path, compile key
    scheduler, 1CTA/2CTA, SplitKV, persistent/CLC, fallback

problem:
    B, Sq, Sk, Hq, Hkv, dQK, dV
    dtype/scales, causal/window/sparse, varlen/paged state

measurement:
    FLOP convention, warmup, repetitions, synchronization
    statistic and distribution, compile/preprocess/allocation boundary

correctness:
    reference, tolerance, max/mean error, deterministic behavior

A release candidate should also carry a fallback and rollback result: force an unsupported shape, verify that the declared backend rejects or falls back visibly, and verify that reverting the release restores the previous artifact and performance distribution. Otherwise a deployment regression can masquerade as natural workload variance.

Scheduling, Artifact, And Benchmark Release Gate

  • causal tile cost and makespan lower bound are derived;
  • lexical, LPT, cache-aware LPT, persistence, CLC, and deterministic SPT are distinct;
  • fixed-length and variable-length scheduler coordinate traces reach pinned source;
  • the paper’s varlen sort is not attributed to beta23’s main prefix-sum scheduler;
  • paper scheduling gains remain source-reported and configuration-specific;
  • beta23 is the implementation anchor and beta24 is explicit drift;
  • architecture/feature rows are source-admission predicates, not execution proof;
  • compile-key fields are grouped by generated-code ownership;
  • source, IR, PTX, CUBIN/SASS, loaded symbol, profile, and timing form separate evidence levels;
  • FA4 compile time is reconciled without calling it kernel runtime;
  • dense, causal, forward, and backward FLOP numerators are derived;
  • useful, scheduled, and dense-equivalent throughput are distinguished;
  • the main-text B200 and appendix B100 manifests remain unresolved rather than silently merged;
  • no paper benchmark is attributed to beta23 or beta24;
  • no local GPU artifact, profile, or timing is claimed.

0x0E Failure Lab

Architecture And Dispatch Failures

FailureSymptomRoot causeCheck
Compile Hopper path for Blackwell and assume compatibilityunsupported instruction or fallbackWGMMA/TMEM contracts differrecord architecture and loaded kernel symbol
API says FlashAttention but generic backend runsexpected speedup absentshape/dtype/mask unsupportedframework diagnostics plus profiler trace
Compare different causal or deterministic modesmisleading benchmarksemantics and work differinclude complete dispatch key
Treat beta23 source admission as executed supportpaper/source table looks complete but deployment failsvalidation, compilation, symbol selection, and launch were collapsedretain artifact and profile selected symbol
Reuse an executor after a missing compile-key field changesshape-compatible call produces wrong path or stale ABIgenerated-code identity is incompletemutate every constexpr branch and assert a cache miss
Import beta24 behavior into the beta23 matrixdocumentation no longer matches pinned blobsrelease drift was treated as retroactive evidencepin release, commit, file blobs, and re-extract

Pipeline And Synchronization Failures

FailureSymptomRoot causeCheck
Reuse SMEM stage too earlyintermittent wrong outputconsumer still reads old stagebarrier phase assertions and stress tests
Reset K/V phase state at a persistent-work boundarystale wait or deadlock after several tilesring generation continuity was discardedmulti-work-tile stage/phase trace
Advance state without releasing K or Vproducer hangs on wrapcoordinate motion was confused with ownership transferacquire/commit/wait/release conservation counters
Producer and consumer enumerate different sparse blockspermanent full waita payload expected by the consumer was never committedcompare selected block-coordinate manifests
Treat producer tail as a dummy payloadextra consumer wait or false full generationsynchronization closure was confused with data productionassert no payload count changes in tail
Omit the scheduler bootstrap arrivalall consumer groups stop before first scheduled tensor bundleno named-barrier half-credit starts the ringsource trace plus first-generation barrier oracle
Use the wrong named-barrier participant countpremature release or permanent waitthe two 128-thread consumer halves do not sum to the declared 256inspect emitted barrier IDs/counts and stress repeated work tiles
Diverge scheduler calls across consumer groupsshape-dependent deadlocknamed-barrier generations no longer matchper-group static call ledger for dense/sparse/empty branches
Omit the non-overlap drain arrivalpeer or next work tile hangsfinal PV consumed the token without passing itverify J+1 sync/arrive balance per non-empty tile
Re-prime scheduler at every persistent work tilepremature barrier generation or later deadlockbootstrap token was duplicated instead of preservedmulti-tile token-conservation trace
Wait too early after async issuecorrect but slowoverlap collapses into serializationinstruction timeline and stall reasons
Wait too laterace or stale datadependency not satisfiedsanitizer/reference comparison
Producer gets excessive registersoccupancy or spillssetmaxnreg budget wrongregister count, spills, resident CTAs
Add a third stage blindlyslower kernellive score tiles exceed register budgetstage-count sweep and occupancy profile
2-CTA pair diverges in mode/orderdeadlock or undefined resultcooperative instruction contract violatedpaired-CTA control-flow audit

Memory And Layout Failures

FailureSymptomRoot causeCheck
FP8 V uses FP16 layout assumptionwrong values or unsupported MMAk-major operand constraint ignoredlayout/unit test before MMA
TMEM regions overlap while livesilent corruptionlifetime allocator reused columns earlydraw TMEM interval/lifetime chart
DSMEM exchange partitions reduction axis incorrectlywrong dQeach CTA lacks full reductioncompare 1-CTA and 2-CTA reference
Larger tile exceeds SMEM/TMEM budgetlaunch failure or low occupancytile choice ignores capacitystatic resource ledger plus profiler

Numerical Failures

FailureSymptomRoot causeCheck
Orthogonal transform differs between Q and Klogits change before quantizationR_Q R_K^T != IFP64 identity test
Per-tensor scale used where block scale expectedFP8 error spikes on outliersone scale covers heterogeneous blocksblockwise error histogram
Polynomial exp used in FP32-sensitive pathaccuracy regressionBF16-rounding argument no longer appliescompare before and after target dtype rounding
Conditional rescale threshold too largeoverflow or saturated Preference frame drifts too faradversarial monotone score rows
Approximate path compared with loose aggregate metric onlyrare catastrophic rows hiddenmean error masks tailsmax/percentile row and output error

Measurement Failures

report theoretical TFLOP/s without measured time
compare kernels with different tensor shapes
omit compile/warmup overhead inconsistently
include allocation in one path but not another
benchmark a fallback while naming the requested backend
turn a paper's best case into a deployment-wide multiplier
infer semantic long-context quality from kernel throughput
use dense FLOPs for a causal or sparse timing without labeling the convention
call the paper's one-kernel compile speedup a device-runtime speedup
silently normalize the FA4 appendix B100 label into the main-text B200 label
attribute the paper's B200 bars to beta23 or beta24 without a release timing run

0x0F Profiler And Evidence Checklist

For one selected kernel, record:

identity:
    architecture, symbol, source commit, compiler, library version

shape:
    B, S_q, S_k, H_q, H_kv, d_h, mask, causal mode, dtype

capacity:
    registers/thread, SMEM/CTA, TMEM allocation, stages, CTAs/SM

movement:
    HBM bytes, L2 behavior, TMA transactions, SMEM traffic

execution:
    tensor-pipe activity, MUFU activity, issue stalls, barrier stalls

correctness:
    reference output, max/mean error, deterministic-mode behavior

timing:
    warmup, repetitions, distribution, forward/backward boundary

Evidence Hierarchy

weak:
    source code requested a FlashAttention backend

better:
    dispatch diagnostics report eligibility and selected variant

strong:
    profiler identifies the kernel, resource usage, and bottleneck

strongest for a release:
    versioned benchmark + profiler + numerical comparison + fallback test

0x10 What The M4 Execution Branch Now Establishes

M4’s execution/kernel branch closes with a sequence of increasingly concrete resource questions:

M4.1:
    count model parameters, activations, FLOPs, bytes, and phase-specific state

M4.2:
    map tensors onto GPU execution agents, memories, and profiler evidence

M4.3:
    remove quadratic attention intermediates from HBM with tiled online softmax

M4.4:
    improve query-block and warp-level work partitioning

M4.5:
    recover decode parallelism and verify mask/cache/backend contracts

M4.6:
    schedule attention around generation-specific asynchronous engines,
    on-chip memories, low-precision layouts, and moving bottlenecks

M4 also contains a separate empirical branch from M4.1:

M4.7:
    empirical training scaling laws and compute-optimal allocation

M4.8:
    extrapolation, alternative functional forms, and data constraints

M4.9:
    overtraining, downstream metrics, and lifecycle allocation

M4.6 closes the kernel-execution argument; it does not claim that hardware accounting closes empirical model-scaling or lifecycle decisions.

The shared systems rule is:

A mathematical operation does not determine one optimal kernel. The winning schedule depends on tensor shape, numerical mode, persistent state, memory hierarchy, available engines, and the exact hardware generation.

M5.1 starts from the state that survives these kernels across requests: K/V pages, request queues, admission decisions, placement, and scheduler pressure.

The model-wide quantization handoff has five owners: M8.2 chooses calibration, sensitivity, and precision-allocation evidence; M8.3 designs equivalent transforms; M8.6 owns live activation and K/V quantization and scale/state lifetimes; M8.9 validates quantized artifact compatibility and runtime admission; M8.10 owns deployment evaluation, promotion, release, and rollback of the exact candidate. M4.6 retains the attention-local, generation-specific kernel schedule and the evidence it derives; a local FP8 kernel result is not model-wide calibration or release approval.

0x11 Publish Gate

GateStatus
Hopper resource ceilings are separated from compiled residency0x02
TMA transaction bytes, full/empty barriers, stage parity, and SMEM liveness are derived0x02
WGMMA group, proxy-fence, named-barrier, and register-pool domains are distinct0x02
Pinned CuTe source is traced to expected PTX families without claiming compiled proof0x02
Paper Algorithm 1 is separated from beta23 Q/K/V pipeline objects and traversal order0x03
Producer/consumer index, phase, parity, ownership, and conservation are derived0x03
Non-overlap and peeled intra-warpgroup source paths have legal K/V release points0x03
Ragged, drain, producer-exit, and persistent-work tails are distinct0x03
Circular-buffer liveness is closed by a decreasing wait-for proof and failure matrix0x03
Pingpong benefit is derived from operation/rate imbalance0x04
Pingpong preserves per-warpgroup numerical state and passes only issue-order permission0x04
Physical/local warpgroup coordinates, named-barrier IDs, and the 256-thread count are reconciled0x04
Bootstrap, non-overlap drain, intra-overlap steady state, and persistent token state are distinct0x04
Cross-warpgroup liveness is closed by token conservation and a decreasing-rank proof0x04
Paper Figure 1, historical Hopper source, current beta source, and compiled proof remain separate0x04
Two-stage startup, steady state, drain, and finite timing bound are derived0x05
QK/PV commit order gives wait_group(1) and wait_group(0) legal meanings0x05
RS/SS probability placement, register live intervals, and payload limits are separated0x05
One-, two-, and three-stage variants include compiler and capacity tradeoffs0x05
Source, PTX, SASS, and dynamic-profile proof claims remain distinct0x05
FP8 layout and numerical transforms are separated0x06
Blackwell TMEM and 2-CTA changes are source-grounded0x07
FA4 forward cycle ledger is derived0x08
Exponential approximation and rescaling have numerical boundaries0x09
FA4 backward cycle ledger and DSMEM trade are derived0x0A
Causal cost, LPT, cache-aware swizzles, static persistence, CLC, and deterministic SPT are separated0x0B
Paper varlen sorting and beta23 prefix-sum coordinate recovery remain distinct0x0B
beta23 source admission, compile key, generated artifact, loaded symbol, profile, and timing are separate evidence levels0x0C
beta24 is recorded as release drift rather than imported into the beta23 support matrix0x0C
Dense/causal and forward/backward benchmark numerators are derived and timing scopes are separated0x0D
Main-text B200 and appendix B100 benchmark metadata remain explicitly unresolved0x0D
Failure diagnosis reaches synchronization, placement, precision, and evidence0x0E-0x0F
Paper benchmarks remain hardware/version/shape-specific0x0D
Display equations are balanced and each numerical coordinate has a mechanically checked representative formulafinal publication audit
Every local source, article link, figure target, and responsive visual has a checked identity or provenance recordfinal publication audit
Desktop/mobile light/dark and reduced-motion rendering have no broken image, MathJax error, or article-level horizontal overflowfinal publication audit
The local evidence ceiling remains source-audited and unmeasured: no retained GPU artifact, profile, correctness run, or timing sample0x0C-0x0F

0xFF References