2026/07/15 10.1k words

Transformer System #numinens#transformer#gpu#cuda#kernel#tiling#occupancy#roofline#profiling#hbm#shared-memory#flashattention

M4.2 - GPU Execution Model for Transformer Kernels: Launch Geometry, Memory Hierarchy, Tiling, and Profiling

M4.2 - GPU Execution Model for Transformer Kernels: Launch Geometry, Memory Hierarchy, Tiling, and Profiling

0x00 One Tensor Equation Does Not Have One GPU Runtime

M4.1 converted a Transformer into several ledgers:

parameter elements
activation elements
FLOPs
logical HBM transfer bytes
persistent KV bytes

Those ledgers are necessary, but they do not execute themselves.

For a long time, operation count was the natural first approximation to machine cost. If one algorithm performed fewer operations than another, the first one looked cheaper. That approximation remains useful when both implementations use the same machine path and keep the same hardware busy. It breaks when an accelerator can execute arithmetic much faster than it can supply operands, or when a mathematically smaller problem exposes too little parallel work.

This is the historical pressure behind the Roofline model and, later, the IO-aware argument in FlashAttention. A18 correctly noticed that FLOP reductions alone do not predict wall-clock improvement. Its stronger claim that approximate attention methods cannot reduce memory traffic is too broad: changing an algorithm can change both arithmetic and storage. The durable point is narrower. Neither asymptotic FLOPs nor asymptotic memory alone specifies the physical schedule that ran.

Two programs can compute the same tensor equation, perform nearly the same floating-point operations, and have very different runtimes. The difference can come from where an intermediate is stored, how many thread blocks exist, how data is laid out for a warp, whether registers spill, whether a block consumes too much shared memory, whether edge tiles waste work, or whether one kernel writes an object that the next kernel immediately reads.

The local question of M4.2 is therefore:

How does a logical Transformer operation become work scheduled on a GPU, and which physical constraints decide whether the implementation is fast?

The original Transformer Series article A18 - FlashAttention correctly recognized that FlashAttention cannot be understood from the attention equation alone. It first introduced GPU memory, streaming multiprocessors, blocks, warps, tiling, arithmetic intensity, and fusion. That prerequisite move was valuable. Some details in that source are architecture-specific or too simplified, however, so this article rebuilds the execution model from current NVIDIA documentation before M4.3 opens the FlashAttention-1 algorithm.

The complete path studied here is:

tensor expression
    -> framework operator
    -> selected backend
    -> one or more kernel launches
    -> grid of thread blocks
    -> resident blocks and warps on SMs
    -> register/shared-memory/HBM traffic
    -> measured runtime and profiler trace

The critical distinction is between a logical object and a physical execution plan.

logical:
    Z = X @ W
    Y = activation(Z)

physical plan A:
    kernel 1 writes Z to HBM
    kernel 2 reads Z from HBM

physical plan B:
    one fused kernel keeps Z in registers or shared memory
    Z is never materialized in HBM

Both plans can return the same Y. Their launch count, byte traffic, register pressure, occupancy, and numerical order can differ.

They are not the only alternatives:

physical plan C - tiled library path:
    many CTAs each own one output tile
    A/B fragments are reused from shared memory and registers
    edge tiles and the last execution wave may be partly empty

physical plan D - persistent or split reduction:
    fewer long-lived CTAs revisit several tiles, or multiple CTAs split K
    launch/grid behavior changes
    partial results require an extra reduction or atomic protocol
One tensor equation branches into materialized, fused, tiled, and split or persistent GPU schedules with different traffic and parallelism

The physical explanation must therefore answer five questions in order:

1. Which semantic operation and exact tensors were requested?
2. Which backend and kernel implementation were selected?
3. How did that kernel map tensor axes to blocks, warps, and lanes?
4. Which state lived in registers, shared memory, caches, and HBM?
5. Which synchronized timing and profiler evidence observed the result?

Skipping a level creates familiar category errors: an API name is treated as a kernel name, logical bytes are treated as DRAM transactions, occupancy is treated as utilization, or a Roofline ceiling is reported as measured runtime.

Background knowledge: GPUs optimize throughput, not one thread

A CPU core is designed to make a relatively small number of instruction streams fast. A GPU exposes many streaming multiprocessors and schedules large populations of lightweight threads. When one warp is waiting for data or a dependency, another eligible warp may issue work.

That does not mean “more threads automatically make a program faster.” Threads need work, data, registers, and a legal synchronization structure. The GPU can only schedule the parallelism the kernel exposes, and each resident block consumes finite SM resources.

The current CUDA Programming Guide describes the device as a set of graphics processing clusters containing SMs. Each SM owns a register file, a unified L1/shared-memory resource, and functional units. Device memory is visible across the GPU, while registers and shared memory have narrower ownership and lifetime.

CUDA hardware model: CPU and system memory connect to a GPU composed of GPCs, SMs, local resources, and GPU memory

Source: NVIDIA, CUDA Programming Guide, Programming Model, Figure 2. The figure is a programming model, not a promise that every GPU generation has the same physical layout. Reproduced for technical commentary; rights remain with NVIDIA.

What this article owns

M4.2 owns:

host and device execution
kernel launch
grid, block, warp, and lane
SM residency and occupancy
register, shared-memory, cache, and HBM boundaries
global-memory coalescing
GEMM tiling
fusion and materialization
roofline reasoning
profiler evidence

It does not own:

online softmax and FlashAttention-1 exactness       -> M4.3
FlashAttention-2 work partitioning                  -> M4.4
split-KV decode, paged reads, and backend dispatch  -> M4.5
Hopper/Blackwell asynchronous attention pipelines  -> M4.6
KV allocation, reuse, scheduling, and placement     -> M5

The article is CUDA-facing because the local source and FlashAttention lineage are CUDA/GPU systems. The reasoning pattern still applies to other accelerators: identify the work hierarchy, local storage, global storage, movement path, synchronization model, and measured bottleneck.

0x01 Immutable Execution Contract And Source-To-Kernel Coordinates

The article keeps four claim classes separate.

ClassMeaning
Primary-source factDirectly documented by NVIDIA or a cited paper.
DerivedFollows from an equation and stated assumptions in this article.
IllustrativeUses hypothetical hardware numbers to expose the calculation.
MeasuredWould require a named GPU, software stack, shape, dtype, and profiler output.

No local GPU benchmark is claimed in this article. Numerical roofline and occupancy examples are analytical illustrations unless a source and device are named.

Freeze the case before calculating it

M4.1 froze architecture, workload, numerical state, ownership, and accounting before deriving logical cost. M4.2 extends that manifest with the coordinates that turn logical cost into a physical execution hypothesis:

operation:
    semantic equation
    input/output shapes, strides, layouts, and aliases
    storage dtype and accumulation dtype
    training, prefill, or decode phase
    mask, dropout, causal, and numerical-reference contract

dispatch:
    framework and compiler versions
    eager, compiled, graph-captured, or library route
    requested and selected backend
    kernel symbol and fallback reason
    launch count, stream, and synchronization edges

machine:
    GPU model and compute capability
    SM count and warp size
    resident thread, warp, block, register, and shared-memory limits
    dtype/instruction-specific compute roof
    L1, L2, and DRAM bandwidth boundaries
    clocks, power policy, and concurrency

schedule:
    grid and block dimensions
    tile sizes, stage count, and warp roles
    tensor-axis ownership and address map
    registers per thread and shared memory per block
    materialized intermediates and synchronization protocol

observation:
    warmup and raw timing samples
    event/synchronization method
    backend diagnostic and kernel trace
    compiler resource report and profiler version
    exact metric names and replay mode
    output tolerance/error and raw artifact locator
An immutable GPU execution manifest binds operation, dispatch, machine, schedule, and observation contracts

Changing any coordinate creates a new case. In particular, an autotuner that selects a different tile or a compiler that emits a different kernel has not merely changed a footnote: it has changed the physical schedule being compared.

The manifest also separates three evidence labels:

analytical:
    equations evaluated from the declared operation, machine, and schedule

source-reported:
    a paper or official document result retained with its original conditions

locally measured:
    synchronized timing and profiler artifacts from the declared environment

unmeasured is a valid state. It is better than filling an observation record with analytical numbers and calling them measurements.

Corrections inherited from A18

The source article says that a block “occupies one SM.” The precise contract is:

one thread block executes on one SM at a time
one thread block does not span multiple SMs
one SM may host multiple resident blocks concurrently
the resident count is limited by threads, warps, registers,
shared memory, architectural block limits, and allocation granularity

The CUDA Programming Guide explicitly illustrates multiple active thread blocks on each SM.

Several thread blocks from a grid can be resident on each SM at the same time

Source: NVIDIA, CUDA Programming Guide, Figure 4. A block remains on one SM, but a one-block-per-SM rule does not exist. Reproduced for technical commentary; rights remain with NVIDIA.

Other guardrails:

  • “SRAM” is too broad for implementation reasoning. Registers, shared memory, and cache are different resources with different ownership and compiler control.
  • High occupancy is not the same as high utilization or high performance.
  • Peak tensor-core FLOP/s is not the throughput ceiling for every dtype, shape, instruction mix, or kernel.
  • Theoretical HBM bandwidth is not the number of useful bytes delivered to a kernel.
  • A tensor that exists in the mathematical graph need not be written to HBM.
  • A fused kernel is not automatically faster if fusion causes spills, poor occupancy, redundant work, or an unfavorable schedule.

0x02 From A Framework Call To A Kernel Launch

Consider a PyTorch expression:

y = torch.nn.functional.silu(x @ weight)

The line is not a GPU execution specification. Depending on shape, dtype, device, compilation mode, library versions, and graph capture, it may produce:

one GEMM library kernel + one SiLU kernel
one GEMM kernel with an epilogue activation
a compiler-generated fused kernel
a fallback path
several setup, copy, or conversion kernels

The framework launches work from the host. A kernel launch supplies at least:

kernel function
grid dimensions
thread-block dimensions
dynamic shared-memory bytes
stream
kernel arguments

A simplified CUDA launch looks like:

dim3 grid(num_blocks_x, num_blocks_y, num_blocks_z);
dim3 block(threads_x, threads_y, threads_z);

kernel<<<grid, block, dynamic_smem_bytes, stream>>>(arguments...);

A source-to-kernel trace

The smallest publishable trace preserves every identity transition:

semantic source:
    y = silu(x @ W)

framework graph:
    aten::mm -> aten::silu

dispatch decision:
    eager library path, compiler fusion, or fallback

selected implementation:
    library algorithm / generated kernel / custom extension revision

kernel launch:
    symbol, grid, block, dynamic SMEM, stream, arguments

compiled resource record:
    registers/thread, static+dynamic SMEM, spill loads/stores, code target

runtime observation:
    ordered trace, synchronized duration, output error, profiler report

The middle of this chain is not stable across shapes. A large aligned GEMM may use a tensor-core library kernel; a small or irregular shape may select another algorithm; a compiler may fuse the activation into the epilogue; and an unsupported dtype or stride may fall back. The Python expression alone proves none of these outcomes.

A framework expression passes through graph lowering, backend selection, kernel launch, compiled resource metadata, timing, and profiler evidence

The launch is asynchronous with respect to the host in the common case. The CPU can enqueue work and continue. Therefore this timing pattern is wrong:

start = time.perf_counter()
y = gpu_function(x)
elapsed = time.perf_counter() - start  # often measures enqueue time

A basic synchronized timing pattern is:

for _ in range(20):
    gpu_function(x)  # warm up compilation, caches, and allocations

torch.cuda.synchronize()

start = torch.cuda.Event(enable_timing=True)
end = torch.cuda.Event(enable_timing=True)

start.record()
for _ in range(100):
    gpu_function(x)
end.record()

torch.cuda.synchronize()
milliseconds = start.elapsed_time(end) / 100

This is still not a complete benchmark. The harness must also freeze:

GPU model and clocks
driver and CUDA versions
framework and backend versions
shape and stride
dtype and accumulation dtype
training/eval mode
causal/dropout/mask configuration
warmup and repetition policy
concurrency and stream state

Raw samples matter because one aggregate hides warmup, clock transitions, autotuning, interference, and tails. A minimal report keeps the samples and derives median, dispersion, and a tail quantile from the same immutable case.

Streams define order, not just concurrency

Operations enqueued in one CUDA stream execute in stream order. Different streams may overlap when dependencies and device resources permit; an event can make one stream wait for work in another. A host call returning only proves that work was enqueued, not completed.

host thread:  enqueue K0 ---- enqueue K1 ---- continue
stream 0:               [ K0 device work ][ K1 device work ]
event time:              ^ start                    end ^
Host enqueue returns before two ordered kernels finish on a CUDA stream while device events bracket actual device work

CUDA Graph capture can reduce repeated host launch overhead by replaying a captured graph. It does not make an underfilled kernel compute-bound or repair bad memory access. It changes the launch path and therefore belongs in the dispatch manifest, not in a generic claim that graphs make kernels faster.

Launch count is a cost, but not the only cost

Launching many tiny kernels creates host/runtime overhead and intermediate materialization boundaries. Combining them can help. But one large kernel can become slower if it has too little parallel work or consumes too many registers and shared-memory bytes.

The optimization question is not:

Can these operators be fused?

It is:

Does the fused schedule reduce costly movement and launch overhead without creating a worse resource or parallelism bottleneck?

0x03 Grid, Block, Warp, And Lane Ownership

CUDA organizes a kernel launch into a grid of thread blocks. A block contains threads. Hardware groups the block’s threads into warps of 32 lanes.

ObjectScopeWhat it owns
gridone kernel launchall blocks for that launch
thread block / CTAscheduled on one SMa cooperating set of threads and block-local shared memory
warp32 lanes from one blockan instruction-issue group under SIMT
thread / laneone logical laneindices, registers, and per-thread control state

Blocks may be scheduled in any order. Ordinary blocks cannot assume another block has run or can participate in a global barrier inside the kernel. Threads within one block can coordinate through block-scoped synchronization and shared memory.

Mapping data to work

A kernel usually maps block and thread indices to tensor coordinates:

int row = blockIdx.y * blockDim.y + threadIdx.y;
int col = blockIdx.x * blockDim.x + threadIdx.x;

For a matrix operation, a block may own an output tile rather than one scalar:

block (i, j)
    owns C[i : i+B_M, j : j+B_N]

warps inside the block
    own subtiles or pipeline roles

lanes inside each warp
    own fragments, loads, reductions, or stores

This ownership map is the bridge from tensor shape to parallel work. A shape can be large while the useful launch grid is small. During one-token decode, for example, the query-row axis may have length one, removing an axis that exposed many independent blocks during prefill.

Minimal tile and address trace

For C[M,N] = A[M,K] @ B[K,N], let a block own C[m0:m0+B_M, n0:n0+B_N]. The launch grid is:

A flattened program id can be decoded as:

pid_m = pid // G_N
pid_n = pid % G_N
m0 = pid_m * B_M
n0 = pid_n * B_N

Inside one reduction stage k0, a lane does not load an abstract A tile. It computes an address from strides:

a_offset = (m0 + local_m) * stride_am + (k0 + local_k) * stride_ak
b_offset = (k0 + local_k) * stride_bk + (n0 + local_n) * stride_bn

a_valid = (m0 + local_m < M) and (k0 + local_k < K)
b_valid = (k0 + local_k < K) and (n0 + local_n < N)

The validity masks protect edge tiles. They do not remove all issued work: fixed-size tensor-core fragments or block-level schedules can still execute on partly empty tiles. The tiling analysis in 0x07 therefore accounts for useful versus issued tile work.

Matrix tensor axes map to a grid tile, warp subtile, lane fragment, stride-derived addresses, and edge validity masks

Triton expresses comparable ownership with program ids and block pointers; CUDA exposes blocks, threads, and explicit pointers; a vendor library hides most choices behind a selected algorithm. These are alternative programming surfaces over the same physical questions, not interchangeable source code.

Warps and divergence

All lanes in a warp execute the same instruction stream, with inactive lanes masked when control flow diverges. If half the lanes take one branch and half take another, the warp may execute both paths with different active masks.

Warp lanes not taking a branch are masked while active lanes execute that path

Source: NVIDIA, CUDA Programming Guide, Figure 7. Hardware details vary, but the SIMT contract explains why coherent control flow matters. Reproduced for technical commentary; rights remain with NVIDIA.

Divergence is not automatically the dominant bottleneck. A branch that skips a large amount of work may be valuable. The correct question is whether the branch creates enough inactive-lane execution to matter in the measured kernel.

0x04 SM Residency And Occupancy

An SM has finite resources. A block can become resident only if all required resources fit. A first-order resident-block model is:

where:

T_SM       maximum resident threads per SM
T_block    threads per block
W_SM       maximum resident warps per SM
W_size     hardware warp width
R_SM       32-bit registers available per SM
R_thread   registers allocated per thread
S_SM       shared-memory capacity available per SM
S_block    shared-memory bytes allocated per block
N_arch     architectural maximum resident blocks per SM

Real residency calculation rounds register and shared-memory allocations to architecture-specific granularities and can include named-barrier, cluster, or other generation-specific limits. Optional shared-memory/L1 partitioning can also change the available budget. The equation is a debugging model, not a replacement for the occupancy calculator and compiled-kernel report for the declared target.

The active-warp count is approximately:

and occupancy is:

Worked residency example

Use an illustrative SM budget:

maximum resident threads   T_SM   = 2048
maximum resident warps     W_SM   = 64
registers per SM           R_SM   = 65536
shared memory per SM       S_SM   = 164 KiB
architectural block limit          = 32

kernel block size          T_block  = 256 threads = 8 warps
registers per thread       R_thread = 64
shared memory per block    S_block  = 48 KiB

Each limit permits:

Shared memory is the first-order limiter, so three blocks may be resident:

If the block’s shared-memory allocation falls from 48 KiB to 32 KiB, the shared-memory ceiling rises to five blocks, but registers still limit the result to four:

This does not prove the second kernel is faster. The shared-memory reduction may cause extra HBM traffic or recomputation. Conversely, the first kernel may have enough instruction-level parallelism and active warps to hide its important stalls.

Resident blocks are the minimum of architectural, thread, warp, register, and shared-memory ceilings, with shared memory limiting the worked case

Residency is a sensitivity surface

The same block can move between limiters when one schedule coordinate changes:

Change from the worked caseThread ceilingRegister ceilingShared ceilingFirst-order resident blocks
256 threads, 64 registers, 48 KiB8433
shared memory falls to 32 KiB8454
registers rise to 96/thread8232
block falls to 128 threads, same per-block SMEM16833

The last row shows why reducing threads per block need not increase residency: another fixed per-block resource can remain limiting. Conversely, accepting lower occupancy can be rational when it buys more accumulator state, reuse, or instruction-level parallelism.

Spills add a second failure path. If the compiler cannot allocate all live thread-local values in registers, some values can be addressed through local memory. “Local” describes per-thread addressability, not an on-chip residence guarantee; spill traffic can travel through cache and device-memory paths.

Occupancy is not utilization

Occupancy answers:

How many warps can be resident relative to the architectural maximum?

It does not answer:

how many cycles issue useful instructions
how busy tensor cores are
how much HBM bandwidth is achieved
how many lanes are active
whether warps are stalled on dependencies
whether the launch grid has enough blocks for all SMs

NVIDIA’s best-practices guidance is explicit: higher occupancy does not always produce higher performance, though low occupancy reduces the ability to hide latency. The useful profiler chain is:

theoretical residency
    -> achieved occupancy
    -> eligible warps
    -> issued warps
    -> stall reasons
    -> compute and memory throughput

Nsight’s scheduler vocabulary makes the causal gap explicit:

theoretical warps:
    allowed by the launch and resource model

active warps:
    resident and not yet completed

eligible warps:
    active and ready to issue an instruction now

issued warps:
    selected by a scheduler this cycle

High theoretical occupancy with few eligible warps points toward dependency or latency-hiding problems. High issued activity with low useful-lane work may instead indicate divergence or tail masks. Neither symptom is captured by one occupancy percentage.

0x05 Memory Hierarchy Means Ownership And Lifetime

A memory hierarchy diagram is useful only if it answers four questions:

who can access this state?
how long does it live?
who controls placement?
what traffic occurs when it is missed or spilled?
ResourceTypical scopeTypical roleMain failure mode
registersthread/laneaccumulators, pointers, local statepressure reduces residency or spills to local memory
shared memorythread blockcooperative tiles, reductions, data exchangecapacity limit, bank conflicts, synchronization
L1/data cacheSMcache for local/global trafficcache behavior is not a programmer-owned tile contract
L2 cachedeviceshared cache across SM trafficworking-set and access-pattern misses
HBM/global memorydevicemodel weights, activations, K/V, outputsbandwidth, latency, transaction waste, materialization
host memoryCPU/systeminput, output, offload, staginginterconnect transfer and synchronization

The FlashAttention paper’s motivating memory figure made the capacity/bandwidth asymmetry visible using A100-era values:

A100-era capacity and bandwidth hierarchy used to motivate IO-aware attention

Source: FlashAttention, Figure 1, reused from A18. The numbers are hardware-specific; the durable claim is the large capacity/bandwidth difference between off-chip and on-chip storage. Reproduced for technical commentary; rights remain with the authors or publisher.

Registers are not a free private cache

Registers are low-latency thread state allocated by the compiler. A large accumulator or many live pointers can increase registers per thread. Since all resident threads share the finite register file, register use can reduce resident blocks.

If the compiler cannot keep required local state in registers, it may use local memory. Despite the name, local memory is off-chip addressable storage and can incur traffic through the cache/global-memory path. Compiler output and profiler evidence are needed to detect spills.

Shared memory is explicit but block-scoped

Shared memory is allocated per block. It is useful when threads cooperate on a tile, reuse data, transpose a layout, or perform a reduction. Its benefits come with constraints:

capacity limits resident blocks
loads and stores must avoid harmful bank conflicts
producer and consumer threads need correct synchronization
the lifetime ends with the block

Shared memory does not let ordinary independent blocks communicate. That would violate the block scheduling model.

Cache hits change measured traffic, not the logical ledger

M4.1 can calculate requested bytes. Actual DRAM traffic may be lower because data hits in L2, or higher because transactions fetch unused sectors, writebacks occur, or an implementation materializes additional workspaces.

Keep three byte ledgers separate:

logical bytes:
    elements required by the tensor algorithm

requested/effective bytes:
    useful bytes requested or produced by the kernel

actual traffic:
    sectors and bytes moved through L1/L2/DRAM interfaces

For profiling and Roofline reconciliation, use the full boundary chain:

logical tensor bytes
    -> requested useful bytes from active lanes
    -> sectors/transactions at the requesting cache
    -> L1-to-L2 traffic after L1 hits and evictions
    -> L2-to-DRAM traffic after L2 hits and evictions
    -> writebacks, atomics, and implementation workspaces
Logical tensor bytes become lane requests, cache sectors, L1 and L2 transfers, DRAM traffic, and writebacks through distinct accounting boundaries

0x06 Coalescing: Layout Becomes Transactions

A warp does not issue 32 unrelated ideal scalar memory transfers. Hardware combines lane requests into memory transactions. On current CUDA guidance for compute capability 6.0 or higher, concurrent accesses are serviced by the number of 32-byte transactions needed to cover the requested addresses.

For 32 lanes loading adjacent FP32 values:

With proper alignment, those addresses occupy four 32-byte sectors.

Adjacent FP32 accesses from a warp are covered by four aligned 32-byte transactions

Source: NVIDIA, CUDA C++ Best Practices Guide, Coalesced Access, Figure 3. The exact transaction rules depend on compute capability. Reproduced for technical commentary; rights remain with NVIDIA.

For adjacent BF16 values:

so an aligned access can fit in two 32-byte sectors.

Now suppose adjacent lanes load FP32 elements with stride two. The warp still requests 128 useful bytes, but the address span covers 256 bytes. A first-order sector count is eight, so only half of each fetched region is useful:

Caches and reuse can modify measured DRAM traffic, but they do not make the access pattern coalesced.

The first-order sector calculation can be made executable. For addresses a_0,...,a_31, element width b, and sector width s, count the unique sectors touched by every byte interval:

Then the requested-to-sector efficiency is:

For 32 adjacent FP32 values aligned to a 32-byte sector boundary, N_sector=4 and eta_sector=1. Moving the first address by four bytes spans five sectors, so the same 128 useful bytes have a first-order efficiency of 128/160=80%. A neighboring warp may reuse the over-fetched sector from cache, which is why this is a request-boundary result rather than a DRAM-byte claim.

Transformer layout example

Suppose K is stored as:

K: [B, H_kv, T, d_h]

with d_h contiguous. Mapping neighboring lanes to neighboring head dimensions gives unit-stride loads inside one token/head row.

lane 0  -> K[b,h,t,0]
lane 1  -> K[b,h,t,1]
...
lane 31 -> K[b,h,t,31]

Mapping neighboring lanes across tokens instead can create a stride of d_h elements:

lane 0  -> K[b,h,t+0,d]
lane 1  -> K[b,h,t+1,d]
...

Whether that is harmful depends on the full layout and vectorization strategy. The point is that a shape such as [B,H,T,d_h] is insufficient. A kernel contract needs:

strides
alignment
vector width
lane-to-index mapping
page/block layout if K/V is non-contiguous

This becomes central in M4.5 and M5 when decode kernels traverse paged KV state.

Shared-memory banks are a second address map

Coalescing global loads does not guarantee conflict-free shared-memory access. For a common 32-bank, 32-bit-word model, a word address maps approximately as:

Suppose a 32 x 32 FP32 tile is stored row-major. A warp reading one row touches successive banks. A warp reading one column at fixed column c touches word addresses 32r+c; every lane maps to bank c, creating a 32-way conflict in the simple model. Padding the row to 33 words changes the column addresses to 33r+c, distributing successive lanes across banks.

unpadded column: bank((32*r)+c) = c
padded column:   bank((33*r)+c) = (r+c) mod 32

The exact bank width, multicast behavior, and instruction mapping are architecture-specific. The invariant is that global layout, shared layout, and lane ownership must be audited separately.

0x07 Tiling: Reuse Data Before Eviction

Matrix multiplication is the canonical tiling example:

A block can own an output tile C_tile: [B_M,B_N], then iterate across the reduction dimension in chunks B_K:

for k0 in range(0, K, B_K):
    load A[M tile, K tile]
    load B[K tile, N tile]
    accumulate C tile
store C tile

A global array is partitioned into tiles; a block loads a tile, operates locally, and stores a tile

Source: NVIDIA, CUDA Programming Guide, Tile Space and Data Movement, Figure 9. A block is an execution unit; a tile is a data unit. They are related by the implementation, not identical by definition. Reproduced for technical commentary; rights remain with NVIDIA.

Per-stage arithmetic intensity

For one reduction stage, the block loads approximately:

elements and performs:

FLOPs under the multiply-add-equals-two convention.

If each input element occupies b bytes, the input-side stage intensity is:

For square 128 x 128 output tiles and BF16 inputs (b=2):

For 64 x 64 tiles:

Larger tiles can increase reuse, but they also increase accumulator state, shared-memory demand, register pressure, edge waste, and sometimes synchronization.

Tile capacity example

Assume:

B_M = 128
B_N = 128
B_K = 32
input bytes b = 2
two shared-memory stages for double buffering

Staging A and B tiles requires approximately:

The FP32 accumulator tile contains:

distributed across thread registers or other architecture-specific fragments. It is not one contiguous block-local array that can be assigned freely without resource consequences.

Tile-edge quantization

The grid rounds M and N up to whole output tiles:

A first-order output-tile utilization is:

For M=N=129 with 128 x 128 tiles, the logical output has 16,641 elements, but the four-tile rectangle covers 65,536 positions:

Masks prevent invalid memory accesses. They do not imply that all fixed tile instructions disappear. Library heuristics may choose a narrower tile to trade reuse for less edge waste.

Wave quantization

Let G be the number of launched blocks and let the first-order concurrent block slots be:

Then:

and, when the last wave is non-empty, its slot fill is:

For 1,024 blocks, 108 SMs, and three resident blocks per SM, one wave has 324 slots. Four waves are needed, and the final wave contains only 52 blocks:

This does not say the last wave lasts exactly as long as a full wave; block durations and overlap vary. It exposes a parallelism cliff that FLOP count does not contain.

Tile quantization wastes edge positions while wave quantization leaves only 52 of 324 concurrent block slots occupied in a tail wave

Double buffering trades capacity for overlap

With one stage:

load tile
wait
compute tile
load next tile
wait
compute next tile

With a legal asynchronous pipeline:

compute stage 0 while loading stage 1
compute stage 1 while loading stage 0 for the next iteration

This can hide movement latency, but the extra stage consumes storage. The CUDA Best Practices Guide notes that asynchronous global-to-shared copies can avoid an intermediate register and overlap copy with computation on supported hardware. M4.6 revisits this idea with Hopper TMA/WGMMA pipelines, Blackwell TMEM/2-CTA execution, and explicit register/shared-memory tradeoffs.

0x08 Fusion: Remove A Materialization Boundary

Suppose two operators are executed separately:

kernel A:
    read X
    compute Z
    write Z to HBM

kernel B:
    read Z from HBM
    compute Y
    write Y

If one fused kernel can consume Z on chip, it can remove at least one HBM write and one HBM read of the intermediate:

Worked fusion lower bound

Let:

Z shape = [4096, 4096]
dtype   = BF16 = 2 bytes

Then:

The eliminated write-read round trip is 64 MiB. On an illustrative 1.5 TB/s memory interface, even the ideal bandwidth-only lower bound is:

This is not a runtime prediction. It ignores cache behavior, launch overhead, overlap, transaction waste, the rest of both kernels, and the fused kernel’s resource changes. It only shows that a logical intermediate can represent a meaningful physical round trip.

Why fusion can lose

Fusion may increase:

register live ranges
shared-memory allocation
instruction-cache pressure
synchronization
recomputation
compile time
tail waste for irregular shapes

If extra register pressure reduces residency or spills to local memory, the fused plan can lose the traffic it intended to save. Fusion is an execution hypothesis that needs measurement.

Alternative schedules move different bottlenecks

One operation can admit several legal schedules:

ScheduleMain gainNew cost or riskEvidence required
larger output tilemore A/B reusefewer blocks, larger accumulator, edge wasteselected tile, registers, SMEM, waves
smaller output tilemore blocks and less edge wasteless reuse and repeated loadstraffic and achieved throughput
grouped tile orderbetter L2 reuse between neighboring CTAsshape-dependent ordering and tailsL2 traffic/hit evidence
split-Kparallelism when M/N are small and K is largepartial-output reduction, workspace, atomicsreduction kernel and extra bytes
persistent CTAsamortized setup and controlled reusefewer CTAs, load balance, long live statewaves, tile queue, registers, SMEM
extra pipeline stagemovement/compute overlapmore SMEM/registers, lower residencyasync-copy and scheduler evidence
operator fusionremoves launch and materializationlonger live ranges, spills, less parallelismkernel identity, spills, bytes, timing
A schedule comparison maps large and small tiles, split K, persistent blocks, pipelining, and fusion to their gains and resource costs

Vendor libraries and Triton autotuners explore parts of this schedule space. The winning configuration is conditional on shape, dtype, layout, hardware, compiler, and concurrent workload. Autotuning results are therefore part of the dispatch/schedule manifest, not timeless properties of an operator.

0x09 Roofline: A Bound, Not A Runtime Oracle

Let:

F       useful floating-point operations
Q       bytes moved through the chosen memory boundary
C_peak  peak compute throughput
B_peak  peak bandwidth through that boundary
I       arithmetic intensity = F / Q

The simple roofline bound is:

Start from two independent lower bounds for a chosen work convention and memory boundary:

If arithmetic and movement can overlap ideally, neither lower bound can be violated, so:

Since achieved performance is P=F/t and intensity is I=F/Q:

The ridge point is:

A related lower bound on time is:

The max is a lower-bound assumption, not a statement that every kernel perfectly overlaps all movement and computation. A strictly serialized schedule can approach a sum instead. Dependencies, barriers, issue limits, launch latency, and insufficient parallelism add constraints that the simple two-roof model does not represent.

Use an illustrative accelerator:

C_peak = 300 TFLOP/s
B_peak = 1.5 TB/s

Then:

A kernel with I=40 FLOP/byte has a bandwidth roof of:

even though the compute peak is 300 TFLOP/s.

A kernel with I=400 FLOP/byte has a nominal bandwidth roof of 600 TFLOP/s, so the compute roof of 300 TFLOP/s becomes lower.

Nsight Compute roofline: arithmetic intensity selects a memory-bound or compute-bound ceiling, and measured points show distance from that ceiling

Source: NVIDIA, Nsight Compute Profiling Guide, Roofline Analysis. Hierarchical rooflines can expose L1, L2, and device-memory ceilings rather than treating “memory” as one level. Reproduced for technical commentary; rights remain with NVIDIA.

Worked time bound

Suppose a kernel performs:

F = 120 GFLOP
Q = 3 GB through HBM

On the illustrative accelerator:

The simple lower bound is 2.0 ms. A measured runtime of 3.2 ms would not prove that HBM alone is responsible. The gap can include limited parallelism, instruction throughput, dependency stalls, synchronization, launch overhead, cache behavior, or lower achieved bandwidth.

Choose the memory boundary explicitly

Arithmetic intensity changes depending on whether Q counts:

HBM traffic
L2 traffic
L1/shared-memory traffic
requested useful bytes
actual sectors transferred

This is why hierarchical rooflines are valuable. A kernel can be below the HBM roof but limited by shared-memory throughput or instruction issue.

For levels j in a memory hierarchy, use a separate measured or modeled byte count and bandwidth:

One kernel therefore has several horizontal coordinates, not one context-free arithmetic intensity. The compulsory operand bytes from M4.1 may be a useful starting point, but an Nsight point needs counter-defined traffic at the same boundary as its bandwidth ceiling.

A hierarchical Roofline plots separate L1, L2, and DRAM intensities and shows an underfilled kernel below all bandwidth and compute ceilings

Peak, empirical, and achieved ceilings

C_peak must match the executed dtype, accumulation, and instruction path. Marketing tensor-core throughput is not the correct roof for an FP32 CUDA-core kernel or a kernel dominated by reductions and exponentials. Likewise, theoretical DRAM pin bandwidth and a sustained copy benchmark answer different questions.

Use three labels:

theoretical peak:
    architecture/vendor limit under stated clocks and instruction path

empirical machine ceiling:
    sustained result from a versioned characterization benchmark

achieved application point:
    work, traffic, and elapsed time measured for the target kernel

The distance from an achieved point to a roof is optimization headroom under that model. It is not causal proof. A high-intensity point far below the compute roof can be underfilled, instruction-latency limited, synchronization limited, or using a different instruction mix than the chosen roof.

0x0A Transformer Operations Under The Execution Model

M4.2 does not claim one bottleneck for every Transformer operation.

OperationTypical physical pressureShape change that matters
large prefill GEMMtensor-core work, tile reuse, staginglarge M/N/K expose many blocks and reuse
one-row decode projectionweight reads, launch overhead, small-M scheduleM collapses toward active-token count
RMSNorm / LayerNormreductions and low arithmetic intensityrow width and number of rows
activation / elementwisememory traffic and launch boundariestotal element count
prefill attentionscore/probability materialization, softmax state, query tilesboth query and key lengths are large
decode attentionpersistent K/V reads and limited query parallelismq_len is one or small; k_len grows
output headvocabulary-width GEMM and selection pathnumber of projected rows and vocabulary size

GEMM compulsory-byte contract

For C[M,N] = A[M,K] @ B[K,N], the useful arithmetic under the FMA-equals-two convention is:

If each operand is read once and the output is written once at a chosen boundary, a compulsory-byte lower bound is:

and:

This is not a transaction prediction. It assumes perfect reuse inside the chosen boundary and excludes epilogues, partial-output reductions, workspaces, write-allocate effects, and cache refetches.

For BF16 inputs/output and a square projection with K=N=4096:

Row count MUseful FLOPsCompulsory bytesFirst-order intensity
133,554,43233,570,8160.9995 FLOP/byte
1284,294,967,29635,651,584120.47 FLOP/byte
4096137,438,953,472100,663,2961,365.33 FLOP/byte

The weights dominate the one-row case and are amortized across many rows in prefill. The formulas do not guarantee those bytes come from DRAM: cache state, batching, and implementation decide the physical boundary.

Elementwise and reduction contracts

For a pointwise map y_i=f(x_i) with c_f useful operations per element and one input read plus one output write of b bytes:

Fusion can remove the input read or output materialization at one boundary, but may lengthen live ranges. A normalization also reduces across a row, usually requiring row statistics, synchronization, and more than one logical pass unless fused or reorganized. Quoting one universal FLOP/byte value for RMSNorm or LayerNorm would silently choose an implementation.

Example: prefill and decode can select different kernels

prefill Q: [B, H_q, S, d_h]
prefill K: [B, H_kv, S, d_h]

decode Q:  [B, H_q, 1, d_h]
cache K:   [B, H_kv, T, d_h]

The mathematical attention contract still contains QK^T, softmax, and PV. The work grid is not the same:

prefill:
    many independent query tiles
    substantial matrix-multiply work

decode:
    query-row axis collapses
    long cache must be read
    parallelism may move to heads, requests, or KV splits

M4.4 and M4.5 will use this execution model to distinguish FA2 query-block parallelism from split-KV decode.

Example: logical GQA savings require a physical grouped path

GQA reduces stored K/V heads:

Q heads:  H_q
KV heads: H_kv < H_q

The cache-byte ledger improves only if the runtime preserves the smaller K/V object. Materializing repeated K/V heads can reintroduce traffic. A grouped kernel can map multiple query-head groups to shared K/V loads without physically expanding the cache.

This is an execution boundary, not merely a shape annotation.

The operation atlas is a hypothesis table

Transformer operations are organized by tensor shape, reuse axis, likely pressure, and the evidence needed to confirm a bottleneck

The table and formulas justify candidate bottlenecks, not universal labels. “Transformer inference is memory-bound” is too coarse: a large prefill GEMM, a one-row projection, a fused normalization, and long-context decode attention can occupy different regions of the same GPU during one request.

0x0B Profiler Evidence: From Hypothesis To Trace

A performance explanation should move through an evidence ladder.

Level 1: tensor contract

Record:

shape
stride
dtype
device
mask/dropout/causal flags
training or inference

Level 2: analytical ledger

Estimate:

FLOPs
useful bytes
materialized intermediates
launch count
expected work-grid axes
first-order residency limits

Level 3: backend and kernel identity

Verify:

which framework backend was selected
which kernels launched
whether fallback occurred
whether a logical fusion actually became one kernel

Level 4: measured runtime

Use warmup, device events, synchronization, repeated shapes, and distribution statistics. Report median and a tail statistic rather than one timing.

Level 5: hardware counters

Inspect at least:

QuestionProfiler evidence
Is the launch grid large enough?grid dimensions, waves per SM, tail effects
What limits residency?registers, shared memory, blocks, warps
Are warps issuing?active, eligible, and issued warps; skipped issue slots
Is memory busy?DRAM/L2/L1 throughput and transaction efficiency
Is compute busy?tensor/core pipeline throughput and instruction mix
Are loads useful?requested versus actual bytes, sectors, cache hit rates
Did registers spill?local-memory loads/stores and compiler resource report
Is control flow coherent?branch efficiency and active-lane behavior

NVIDIA Nsight Compute defines occupancy as active warps divided by possible active warps and warns that higher occupancy does not always mean higher performance. Its scheduler statistics distinguish active, eligible, and issued warps. That is more informative than quoting occupancy alone.

A broad collection command is:

ncu --set full -o m4_2_report python benchmark.py

The exact sections and metric names vary by Nsight Compute release and GPU architecture. A publishable experiment records both.

A performance claim climbs from tensor contract and analytical ledger to dispatch identity, synchronized timing, hardware counters, and numerical validation

Profiler sections answer different questions. LaunchStats establishes launch shape and static resources; Occupancy compares theoretical and achieved residency; SchedulerStats distinguishes active, eligible, and issued warps; MemoryWorkloadAnalysis exposes cache and DRAM traffic; and ComputeWorkloadAnalysis reports pipeline activity. A high percentage in one section is not a complete bottleneck diagnosis.

Metric collection can require replay and can perturb cache state or elapsed time. The report must record profiler version, section set, replay mode, kernel filter, and whether timing came from the unprofiled benchmark or the profiled run. Timing and counters are related evidence, not automatically the same run.

Minimal result schema

hardware:
    GPU model
    compute capability
    clock policy
    memory capacity/bandwidth reference

software:
    driver
    CUDA
    framework
    compiler/backend

input:
    shapes
    strides
    dtype
    flags

launch:
    kernel name
    grid
    block
    registers/thread
    shared memory/block

result:
    median runtime
    achieved compute throughput
    achieved memory throughput
    achieved occupancy
    limiting residency resource
    dominant stall evidence
    numerical error against reference

Without this schema, “the kernel is memory-bound” is often an untested interpretation.

Executable Evidence Bundle: Empty Is Different From Measured

The repository now contains a machine-checked version of this schema:

docs/numinens-evidence/m4-m5-p1/kernel_bundle_template.json
docs/numinens-evidence/m4-m5-p1/validate_kernel_bundle.py

The checked-in bundle is deliberately labeled unmeasured. The validator allows an empty template, but a locally_measured result is rejected unless it contains at least 20 raw timing samples, the exact dispatch key, selected backend, kernel symbol, reference tolerances and errors, profiler command, profiler report, and hardware counters. This prevents two common evidence failures:

timing without kernel identity
    -> a fast fallback can be mistaken for the intended implementation

profiler counters without a numerical reference
    -> a fast but incorrect kernel can be presented as an optimization

Run the schema check with:

python3 docs/numinens-evidence/m4-m5-p1/validate_kernel_bundle.py \
  docs/numinens-evidence/m4-m5-p1/kernel_bundle_template.json

The current artifact validates as an empty evidence record, not as a GPU result. No CUDA measurement was produced during this content pass. NVIDIA’s Nsight Compute CLI supports saved reports, kernel filters, selected metric sets, and CSV output; the exact command and metric names must therefore remain inside the result bundle rather than being implied by prose.

0x0C Executable Estimator And Predicted-Observed Reconciliation

The article’s analytical path is executable in:

docs/numinens-evidence/m4-2-gpu-execution-model/
    execution_manifest.json
    gpu_execution_estimator.py
    observation_unmeasured.json
    test_gpu_execution_estimator.py

The manifest freezes the operation, illustrative machine, schedule, warp request, fusion boundary, and hierarchical Roofline case. It deliberately does not name a real GPU. The estimator derives the same quantities as the prose:

grid and issued tile extent
resource-limited resident blocks and occupancy
concurrent block slots, wave count, and tail fill
requested bytes, sector count, and sector efficiency
stage FLOPs, bytes, and arithmetic intensity
compulsory GEMM bytes and fusion round-trip bytes
L1, L2, and DRAM Roofline lower bounds

Run it with:

python3 docs/numinens-evidence/m4-2-gpu-execution-model/gpu_execution_estimator.py \
  docs/numinens-evidence/m4-2-gpu-execution-model/execution_manifest.json \
  --observation docs/numinens-evidence/m4-2-gpu-execution-model/observation_unmeasured.json \
  --fail-on-mismatch

The exact checked fixture closes at:

Derived objectExact result
32 x 32 output-tile grid1,024 blocks
resident blocks per SM3, limited by shared memory
active warps and occupancy24, 37.5%
concurrent block slots324
waves and final-wave blocks4, 52
final-wave slot fill16.049382716049383%
aligned FP32 warp request128 useful bytes, 4 sectors, 100% efficiency
128 x 128 x 32 tile stage1,048,576 FLOPs, 16,384 bytes, 64 FLOP/byte
fused 4096 x 4096 BF16 round trip67,108,864 bytes removed
hierarchical Roofline lower bound0.002 seconds

The observation file returns:

status: unmeasured
observed median: absent
predicted lower bound: 0.002 seconds
residual: absent

This distinction is part of the result, not missing paperwork. When a valid measurement exists, reconciliation is:

A nonnegative residual is expected because the analytical number is a lower bound. It must be explained with observed launch, issue, traffic, occupancy, spill, synchronization, or dispatch evidence. The code rejects an incomplete locally_measured label, fewer than 20 raw samples, a case-id mismatch, or a kernel whose numerical error exceeds its declared tolerance.

An analytical execution manifest produces a lower bound while an independent timing and profiler bundle produces an observation and signed residual explanation

Run the rejection and sensitivity suite with:

python3 docs/numinens-evidence/m4-2-gpu-execution-model/test_gpu_execution_estimator.py

0x0D Failure Lab

Execution-model failures

  1. Treating one framework call as one kernel. Compilation and dispatch may fuse, split, or fall back.

  2. Treating a block as an SM. A block stays on one SM, but an SM can host multiple blocks and execute many blocks over time.

  3. Assuming blocks execute in grid order. Ordinary blocks cannot depend on that schedule.

  4. Confusing block and tile. A block is an execution unit; a tile is a data unit chosen by the implementation.

  5. Assuming every thread maps to one CUDA core permanently. Threads are scheduled in warps over functional pipelines; the mapping is not a dedicated-core ownership contract.

Resource failures

  1. Using too many registers. Residency falls or state spills to local memory.

  2. Calling local memory on-chip. CUDA local memory is thread-scoped in address semantics but physically off-chip/cache-backed.

  3. Increasing shared memory without recalculating residency. Better reuse can reduce the number of resident blocks.

  4. Equating occupancy with utilization. Resident warps can still be stalled or inactive.

  5. Maximizing occupancy at any cost. Forcing fewer registers can create spills or destroy useful instruction-level parallelism.

  6. Ignoring allocation granularity. The simple floor equations can overestimate real residency.

Memory and layout failures

  1. Counting useful elements as actual transactions. Misalignment, stride, and sector fetches can move extra bytes.

  2. Assuming cache hits from one benchmark generalize. Batch, sequence, concurrency, and cache state change reuse.

  3. Using a coalesced global layout that creates shared-memory bank conflicts. Optimization at one memory level can expose another bottleneck.

  4. Materializing a logical broadcast. expand-style semantics can become a physical copy through reshape, fallback, or kernel choice.

  5. Ignoring page/block layout in decode. Logical K/V continuity does not imply contiguous physical addresses.

Tiling and fusion failures

  1. Choosing the largest tile that fits. It may leave too few blocks, waste edges, or create excessive accumulator pressure.

  2. Double buffering without including both stages in the shared-memory ledger.

  3. Missing synchronization between shared-memory producers and consumers. Results can be nondeterministically wrong.

  4. Assuming fusion always reduces runtime. Fusion can spill, reduce parallelism, or duplicate work.

  5. Confusing no HBM materialization with no intermediate state. The state still exists logically and may occupy registers/shared memory.

Roofline and measurement failures

  1. Using peak FLOP/s for the wrong dtype or instruction path.

  2. Using theoretical bandwidth as achieved useful bandwidth.

  3. Computing arithmetic intensity without naming the memory boundary.

  4. Timing asynchronous enqueue work with a CPU timer.

  5. Benchmarking the first invocation. Compilation, autotuning, allocation, and cold caches contaminate the result.

  6. Profiling one shape and generalizing to all Transformer phases.

  7. Reading one high-level utilization number without work-grid and stall evidence.

  8. Ignoring profiler replay overhead or clock variability.

  9. Claiming a backend from the API name. Dispatch must be observed.

  10. Treating masked edge positions as zero issued work. Predication protects correctness but fixed tile instructions and tail CTAs can remain.

  11. Ignoring wave quantization. A small tail wave can leave most concurrent block slots empty.

  12. Using one arithmetic intensity for every cache level. L1, L2, and DRAM traffic are different denominators.

  13. Treating a Roofline distance as causal proof. The point can be below a roof because of issue, parallelism, synchronization, or a mismatched ceiling.

  14. Calling an analytical fixture measured. A measured label requires raw timing, dispatch, profiler, and numerical artifacts.

0x0E Publish-Gate Checks

The article-level gate records where each requirement is established. It does not claim that a local GPU benchmark was run; measured claims remain explicitly out of scope unless a versioned result artifact is attached.

GateStatus
the physical runtime problem appears before CUDA taxonomy0x00
operation, dispatch, machine, schedule, and observation are immutable coordinates0x01
logical tensor objects are separated from physical allocations0x02, 0x05
host launch and asynchronous timing semantics are explicit0x02
API, backend, and kernel identities remain separate0x02, 0x0B
grid, block, warp, and lane ownership are correct0x03
one tile and lane-address trace is executable from strides0x03
one-block-per-SM language has been removed0x04
registers, shared memory, L1, L2, and HBM are not collapsed into “SRAM”0x05
occupancy includes its limiting resource and is not called utilization0x04
active, eligible, and issued warps are distinct0x04, 0x0B
tile sizes include register/shared-memory consequences0x07
tile-edge and wave quantization are derived0x07
coalescing claims name dtype, alignment, stride, and hardware scope0x06
shared-memory banks have a separate address audit0x06
fusion claims include removed bytes and new resource cost0x08
split-K, persistent, buffering, and tile alternatives are compared0x08
roofline equations name the counted memory boundary0x09
theoretical, empirical, and achieved ceilings are distinguished0x09
Transformer bottlenecks are operation- and phase-specific0x0A
measured-claim schema names hardware, software, shape, dtype, and flags0x0B
kernel/backend identity has an evidence procedure0x0B
numerical parity against a reference is required0x0B
exact synthetic estimator fixtures close without becoming measurements0x0C
predicted lower bound, observation, and residual are separate0x0C

0x0F Handoff To M4.3

M4.2 has established the physical vocabulary needed for IO-aware attention:

HBM materialization is an execution choice
tiles create local reuse
blocks own work tiles
warps execute and cooperate inside blocks
register/shared-memory demand limits residency
layout determines transactions
fusion trades movement for resource pressure
roofline gives a bound
profilers decide which hypothesis survived execution

The next article can now ask a narrower algorithmic question:

Softmax couples every key in a query row through one denominator. How can an attention kernel stream K/V tiles, keep only compact row state on chip, and still return exactly the same normalized result?

M4.3 - IO-Aware Attention and FlashAttention-1 owns that proof and algorithm.

The boundary is strict:

M4.1 owns:
    logical work, state, and compulsory-byte schedules

M4.2 owns:
    generic GPU schedule realization, machine ceilings, and profiler evidence

M4.3 owns:
    online-softmax state, exact FA1 recurrence, tile loop, and IO proof

M4.4-M4.6 own:
    attention-version work partition, decode reduction, and new hardware pipelines

M5 owns:
    request-level K/V lifecycle, paging, scheduling, reuse, and placement

M8 owns:
    numerical formats, quantizer metadata, packing, and quantized release

0xFE Claim Status And Scope

Claim familyStatusScope
CUDA hierarchy, streams, coalescing, shared-memory banksexternally anchoredcurrent official CUDA documentation; architecture qualifications retained
occupancy and Nsight evidence ladderexternally anchoredcurrent Nsight Compute concepts; metric names remain version-specific
tile/wave quantization alternativesexternally anchored and derivedNVIDIA GEMM guide plus article formulas
Roofline and hierarchical boundariesexternally anchored and derivedoriginal and hierarchical Roofline sources
exact fixture numbersrewrite interpretation, synthetic-contract testedillustrative manifest; no named hardware result
A18 motivation and A100 hierarchysource-local, corrected and scopedhistorical context, not a current hardware specification
kernel timing or profiler conclusionunmeasuredno local CUDA artifact was produced

0xFF References

Local series

Primary and official sources