Tools for peak-performance GEMM and attention on NVIDIA hardware: what the SM executes (SASS), how tensor cores are fed, how CUTLASS and CuTe describe tiles, threads and memory as composable layouts, and how FA3 and ThunderKittens apply them. The lectures are not in upstream order; they go bottom-up: microarchitecture โ tensor cores โ hand-built GEMM โ CUTLASS โ CuTe โ layout algebra โ CuTeDSL / PyCuTe โ FA3 and ThunderKittens.
Notation: a layout is Shape:Stride, e.g. (4,6):(1,4). Coordinates are column-major (colexicographic): the leftmost mode varies fastest.
Contents
- Lecture 37 โ SASS and GPU Microarchitecture
- Lecture 23 โ Tensor Cores
- Lecture 43 โ INT8 Matmul on Turing Tensor Cores
- Lecture 15 โ CUTLASS
- Lecture 57 โ CuTe
- Lecture 103 โ CuTe Layout Algebra and Category Theory
- Lecture 86 โ CuTeDSL
- Lecture 114 โ PyCuTe
- Lecture 36 โ CUTLASS and FlashAttention-3
- Lecture 75 โ GPU Programming Fundamentals and ThunderKittens
- Track summary
Lecture 37 โ SASS and GPU Microarchitecture
Slides: PDF
- Steer the compiler toward good SASS; you don't write SASS (no public H100 assembler). PTX is a virtual ISA that
ptxaslowers non-trivially, so always inspect the SASS (cuobjdump -sass, Godbolt, Nsight Compute Source view). - Issue rate is the hidden ceiling: each SMSP issues 1 instruction/clock (4 per SM). Index math, predicates and address arithmetic steal slots from FMAs.
- "Barrier" is overloaded: scoreboard dependency barriers (hidden),
BAR/__syncthreads(), and mbarriers for TMA/async copy/tensor cores. - Little's Law: hiding ~200 (L2) to ~1000 (HBM) cycles of latency needs that much work in flight, from ILP + TLP.
- Element-wise isn't automatically DRAM-bound: the GELU case study uses vector loads,
MUFU.TANHvia inline PTX, and FMA reordering, and still falls short of full bandwidth. - Isolate code for analysis with
noinline,__launch_bounds__,nanosleep. Optimize the algorithm first, then the SASS.
Lecture 23 โ Tensor Cores
Slides: external
- One instruction = small
D = AยทB + Cacross a thread group. Each thread holds a fixed fragment; describing that mapping is what CuTe TV-layouts do. - Generations: Volta
mma.sync.m8n8k4(quad-pairs, regs) โ Amperemma.sync.m16n8k16(1 warp, regs vialdmatrix) โ Hopperwgmma.mma_async(warpgroup of 128 threads, B from smem, 64รNร16, N โค 256) โ Blackwelltcgen05.mma(single thread issues, accumulator in TMEM). - The trend: async and operands closer to the tensor core, freeing registers and issue slots for softmax, epilogue and address math.
- Feeding it: operands staged in swizzled smem (bank-conflict-free), loaded async (
cp.async, then TMA) and multi-buffered. - Major-ness: operands are K-major or MN-major; FP8 wgmma accepts only K-major smem operands, which forces FA3's V transpose.
- Accumulate wider: fp16/bf16/fp8 โ fp32; int8 โ int32.
Lecture 43 โ INT8 Matmul on Turing Tensor Cores
Slides: PDF
- int8 is exact, so no float tolerances. Verify matmuls cheaply with Freivalds' check:
C x == A (B x)is O(nยฒ). - Naive WMMA: one warp per 16ร16 fragment, ~4 TOPS against a โ114 TOPS speed-of-light.
- Uncoalesced B โ LG Throttle stalls. Transposing B once (O(nยฒ)) took 1482 โ 922 ms at n=15040.
- Stall percentages are per selected cycle, so they may not drop even when runtime does.
- Register tiling (3ร3 fragments per warp) raises MMAs per fragment loaded from 1/2 to 3/2 (513 ms); shared memory tiling reaches 332 ms; register pressure then limits occupancy.
- Avoid per-load layout shuffles: copy fragment registers directly (194 ms), then pre-swizzle A/B in global memory into fragment order (178 ms, ~40% of SOL).
Lecture 15 โ CUTLASS
Slides: video only
- Header-only C++ templates for GEMM-like kernels, re-assemblable into fused epilogues, GETT, conv and attention.
- CUTLASS 3.x layers: Device (
GemmUniversalAdapter) โ Kernel (tile scheduler, warp roles) โ Collective (CTA mainloop / epilogue) โ CuTe (TiledCopy,TiledMMA, atoms). - Tiling: CTA tile (e.g. 128ร128ร64) โ warp/warpgroup tile โ MMA atom; pipelined K mainloop; epilogue
D = ฮฑยทacc + ฮฒยทCplus fused ops. - 2.x โ 3.x: dozens of named layout/iterator types replaced by one vocabulary type, CuTe's
Layout<Shape,Stride>. - Hopper schedules: cooperative (2 consumer warpgroups split a tile) vs ping-pong (they alternate tiles so one's epilogue overlaps the other's MMA); both use a TMA producer warp.
- Configuration is compile-time (tile, cluster, stages, schedule, types); performance comes from picking the right instance.
Lecture 57 โ CuTe
Slides: PDF
- A layout is the canonical form of a loop nest:
for (i=2; i<=50; i+=3) A[7*i+5]becomes baseA+19, size 17, stride 21. - Layout = function coord โ index via
inner_product(coord, stride). Static extents (_22) let loops unroll and offset math fold away. - Hierarchy makes any fold representable:
(2,2,2):(4,1,2)has no flat 4ร2 view, but((2,2),2):((4,2),1)works. One layout accepts 1-D, 2-D or nested coords: for(8,(2,2)):(2,(1,16)),A(17) = A(1,2) = A(1,(0,1)) = 18. - Tensor = Engine โ Layout, a view, not a container. Copy, gather, broadcast (
8:0), transpose, and NT/TN GEMM are just choices of layouts. - All GETTs are GEMMs: group modes into M, N, K and batch modes, then fold to batched GEMM.
- Partitioning = composition then slicing:
composition(data, TV)(tid, _)with a(thread, value) โ coordlayout. - MMA atoms describe hardware as layouts:
ALayout/BLayout/CLayout : (thr, val) โ coord. Stack: MMAOp โ MMATraits โ MMA_Atom โ TiledMMA โ ThrMMA (partition_A/B/C,make_fragment_*).
TiledMMA mma = make_tiled_mma(SM80_16x8x8_F32F16F16F32_TN{}, Layout<Shape<_2,_2>>{});
ThrMMA thr = mma.get_slice(threadIdx.x);
Tensor tCrA = thr.make_fragment_A(thr.partition_A(sA)); // likewise B, C
gemm(mma, tCrA, tCrB, tCrC);
Lecture 103 โ CuTe Layout Algebra and Category Theory
Slides: PDF
- Vocabulary: for
S = (4,(5,5),(2,(3,3))), rank 3 (modes), length 6 (entries), size 1800.SrefinesS'if entries ofS'are replaced by nested tuples of equal size. - Layout function:
ฮฆ_L = (Dยท) โ colexโปยน. For(5,3):(3,1),ฮฆ(11): coord(1,2)โ3ยท1 + 1ยท2 = 5. - Same grid, different strides:
(4,6):(6,1)row-major,(4,6):(1,4)column-major,(4,6):(1,0)broadcast. The 2ร2 labelling0 1 / 3 2is not a layout; it needs a swizzle. - Core operations:
| Operation | Definition | Example |
|---|---|---|
| Coalesce | merge sโ,sโ : dโ,dโ โ sโsโ : dโ when sโdโ = dโ | (2,3,2,3):(12,6,1,2) โ (2,3,6):(12,6,1) |
Composition BโA | ฮฆ_{BโA} = ฮฆ_B โ ฮฆ_A | (12,4):(4,1) โ (4,6):(6,1) โ ((2,2),6):((24,1),4) |
Complement comp(L,N) | fills the holes so (L,L') is a bijection onto [0,N) | comp((3,7):(2,30), 210) = (2,5):(1,6) |
Logical divide A โ B | (AโB, Aโcomp(B, size A)) | (6,8):(1,6) โ (2,4):(1,6) = ((2,4),(3,2)):((1,6),(2,24)) |
Logical product A โ B | (A, comp(A, size(A)ยทcosize(B)) โ B) | (3,3):(3,1) โ (2,2):(1,4) = ((3,3),(2,2)):((3,1),(9,36)) |
- Worked composition: A's first mode
4:6visits 0, 6, 12, 18, which in B's(12,4)coords cross B's mode boundary at 12, so it splits into(2,2)with strides(6ยท4, 1) = (24,1); A's second mode6:1stays in B's first mode, giving stride 4. Check:ฮฆ_B(ฮฆ_A(3)) = ฮฆ_B(18) = 25. - Worked complement:
(3,7):(2,30)hits{0,2,4} + {0,30,โฆ,180}; holes are filled by stride 1 (ร2) then stride 6 (ร5). - Worked divide (1-D):
24:1 โ 4:2 = (4,(2,3)):(2,(1,8)). Mode 0 is the tile{0,2,4,6}, mode 1 indexes 6 tiles at bases{0,1,8,9,16,17}. This is howzipped_divide/local_tilefind "tile i". - Tractable layouts: composition doesn't always exist; restrict to layouts derived from column-major by permuting, removing, inserting stride-0 modes and re-parenthesizing.
(4,6):(1,5)is not tractable. - Category view: tractable layouts โ morphisms in the category Nest; composition, complement, divide and product become diagram pasting, and the composition algorithm (via mutual refinement) is proven correct whenever
BโAexists.
Lecture 86 โ CuTeDSL
Slides: PDF
- Why a DSL: C++ templates mean high mental load, huge error messages and slow compiles that block JIT and autotuning. CUTLASS 4.0 / CuTeDSL exposes the same CuTe model from Python with full tensor-core control (
pip install nvidia-cutlass-dsl). - Two decorators:
@cute.jitis host-side (prepares params, builds TMA descriptors);@cute.kernelis the device kernel. - Tracing + AST rewriting: runtime-dependent control flow becomes IR; compile-time Python is evaluated at trace time.
cutlass.Constexprargs are baked in and key the kernel cache. TensorvsTensorSSA: a view of memory vs an immutable thread-local value used forload โ compute โ store.- Blackwell block-scaled GEMM: TMA load A/B/scales โ S2T copy scales to TMEM โ UMMA (
tcgen05.mma, accumulator in TMEM) โ T2R copy to registers (SIMT, sliced per thread) โ store. tXaYnaming: partitioningtXof tensoraYin memory space r/g/s/c/p/t; e.g.tCsA= MMA-C partitioning of A in smem.- Pipelines:
PipelineTmaUmmaandPipelineUmmaAsyncwrap mbarrier producer/consumer handshakes. Debug withcute.printfandCUTE_DSL_KEEP_PTX=1.
Lecture 114 โ PyCuTe
Slides: PDF
- PyCuTe: a pure-Python reference of CuTe's layout algebra that works unchanged inside CuTeDSL, running at trace time and emitting no IR.
- Mappings to reason about: memory layout
L, tilerT, access distributionTV;LโTVgives the memory access pattern. - Design tension: scope vs closure. Ladder: flat strides โ hierarchical โ coordinate strides โ Fโ strides โ ComposedLayouts, each step trading away algebra.
- COPY by algebra: permute the iteration by
right_inverseof one side;nullspacerejects write-after-write races; agreement of the other side gives aV:1vector width. It degrades to the reference loop in the worst case. - GEMM as layout: say B is N-major or K-major, not "transposed". Im2col is a layout transform; implicit GEMM is a change of view.
- Coordinate strides (
1@0,2@1) make layouts emit coordinates: predication, TMA, better tilers. - Swizzles as Fโ strides: XOR as addition, so coalesce and inverses work; power-of-two Fโ layouts are isomorphic to Triton's Linear Layouts.
Lecture 36 โ CUTLASS and FlashAttention-3
Slides: PDF
- Why FA2 stalls on H100: ~35โ40% utilization;
mma.syncreaches only ~2/3 of peak, so wgmma and TMA are required. - Three ingredients: new instructions (WGMMA, TMA), asynchrony (producer/consumer warp specialization with
setmaxnregregister reallocation), and FP8. - Exp is a co-bottleneck: for head dim 128 and a 128ร192 tile, FP16 WGMMA takes 3072 cycles and
exp1536 (50%); with FP8 both take 1536. Softmax must overlap the matmuls. - Pingpong: two consumer warpgroups alternate softmax and GEMM via
bar.sync(580 โ 640 TFLOPS). Intra-warpgroup pipelining overlaps iteration j's softmax with j+1'sQKแต(โ 670 TFLOPS). - FP8 layout: wgmma needs K-major operands, so V is transposed in-kernel (TMA can't change the contiguous mode) and P's accumulator layout is reshaped in registers.
- Persistent kernels: #SMs CTAs + tile scheduler overlap epilogues with next-tile loads and load-balance causal masking.
- Results: 1.6โ2.0ร over FA2, up to ~850 TFLOPS BF16 and ~1.3 PFLOPS FP8.
Lecture 75 โ GPU Programming Fundamentals and ThunderKittens
- 4 warp schedulers per SM, each like an in-order SIMD core. Minimum to keep H100 busy: 132 ร 4 ร 32 = 16,896 threads; more threads only add latency hiding.
- Wave quantization:
<<<133,1024>>>can be ~2ร slower than<<<128,1024>>>on 132 SMs, since one block runs a second wave. Same reason for persistent tile schedulers. - The golden rule: H100 tensor cores ~989 TFLOPS BF16 vs ~60 FMA and ~7.5 SFU
exp, so ~16ร everything else. Minimize the latency of non-tensor-core ops: fuse and tile them. - ThunderKittens: a tile-based C++ library (<1 MB) with three abstractions: 16ร16 tiles with managed global/shared/register layouts, side-effect-free tile functions
func(dest, src...), and schedules (pipelining, producer/consumer specialization). - Bet: AI kernels use few patterns, so small transparent primitives can match CUTLASS-class performance with far less code.
Track summary
| Concept | Lecture | Takeaway |
|---|---|---|
| SASS & issue rate | 37 | Inspect SASS; 1 instr/clk per SMSP, overhead steals MMA slots |
| Little's Law | 37, 75 | ~200โ1000 cycles of latency need ILP + TLP in flight |
| Tensor-core generations | 23 | mma.sync (warp, regs) โ wgmma (warpgroup, smem, async) โ tcgen05 (1 thread, TMEM) |
| Hand-built TC GEMM | 43 | Transpose โ register tile โ smem โ pre-swizzle: 1482 โ 178 ms |
| CUTLASS layers | 15 | Device โ Kernel โ Collective โ CuTe atoms |
| Layout / Tensor | 57 | Shape:Stride coordโindex; Tensor = Engine โ Layout |
| Partitioning & MMA atoms | 57 | Compose with a TV-layout, slice by thread |
| Layout algebra | 103 | Composition, complement, AโB=(AโB, AโBแถ), AโB=(A, AแถโB) |
| CuTeDSL | 86 | CuTe in Python with fast JIT and full TC control |
| COPY & swizzles via algebra | 114 | right_inverse vectorizes, nullspace finds WAW; swizzles = Fโ strides |
| FA3 | 36 | WGMMA + TMA + warp specialization + pingpong + FP8 โ ~850 TFLOPS BF16 |
| Waves & persistent kernels | 36, 75 | #SM CTAs + tile scheduler beat wave quantization |
| Tensor cores dominate | 75 | ~16ร other compute on H100: keep them busy |