Exercise 02 — Layout algebra drill (written, no compiler)¶
Goal. Be able to go from a (Shape, Stride) pair to a linear offset, and back, on a
whiteboard. This is the arithmetic CuTe automates; you cannot debug a CuTe kernel — or read
sgemm_2.cu — without being fluent in it.
Do it on paper first. You can check parts 1–4 by running Lab 2
(make run-cute CUTLASS_DIR=...), which prints the same tables via cute::print_layout.
Reference answers with reasoning: ../solutions/02_layout_algebra_solution.md.
The one rule you need. A Layout is a (Shape, Stride) pair. The index is the inner
product of the (natural) coordinate with the stride:
For a hierarchical shape, the rule applies recursively: Shape (3,(2,3)) with
Stride (3,(12,1)) and natural coordinate (i,(j,k)) gives i*3 + j*12 + k*1.
Part 1 — Column-major offsets¶
Layout L1 = (4,2):(1,4).
Write the index formula, then compute the offsets:
| coordinate | offset |
|---|---|
(0,0) |
|
(1,0) |
|
(3,0) |
|
(0,1) |
|
(2,1) |
|
(3,1) |
Then draw the full 4×2 table of offsets.
Part 2 — Row-major offsets¶
Layout L2 = (4,2):(2,1) — same shape as L1, different stride.
| coordinate | offset |
|---|---|
(0,0) |
|
(1,0) |
|
(3,0) |
|
(0,1) |
|
(2,1) |
|
(3,1) |
Then answer in one sentence: what, exactly, distinguishes "row-major" from "column-major" in this formalism?
Part 3 — A hierarchical layout¶
Layout L3 = (2,(2,2)):(4,(2,1)).
- Write the index formula for a natural coordinate
(i,(j,k)). - Compute the offsets for
(0,(0,0)),(1,(0,0)),(0,(1,0)),(0,(0,1)),(1,(1,1)). L3is logically a 2×4 matrix, so it also accepts a 2-D coordinate(m,n)wherenis a 1-D coordinate into the(2,2)multi-mode. CuTe enumerates those in colexicographical order (right-to-left, i.e. the left-most sub-mode varies fastest). Fill in the mapping and then the full 2×4 offset table:
1-D n |
natural (j,k) |
|---|---|
| 0 | |
| 1 | |
| 2 | |
| 3 |
- Is
L3row-major, column-major, or neither? Justify from the strides.
Part 4 — One element, three coordinates¶
Layout L4 = (3,(2,3)):(3,(12,1)) (size 18).
Compute the index for each of these, and explain why they agree:
- the 1-D coordinate
16 - the 2-D coordinate
(1,5) - the natural coordinate
(1,(1,2))
(Show the 1-D → natural conversion explicitly; that step is where people go wrong.)
Part 5 — Design a transposed view¶
You have a 4×8 row-major float matrix in memory, i.e. layout (4,8):(8,1).
- Write the
(Shape, Stride)of an 8×4 transposed view of the same memory — no data moves, no copy. - Prove it: show that element
(1,5)of the original and element(5,1)of your view compute the same offset. - What does this tell you about the cost of "transposing" a tensor in this formalism, and why does a real GEMM still care about which operand is transposed?
Part 6 — Design a tiled view¶
You have an 8×8 row-major matrix, layout (8,8):(8,1). You want to view it as a
2×2 grid of 4×4 tiles, so that a kernel can index it as (intra_tile_coord, tile_coord).
- Derive the offset formula for tile
(ti,tj), intra-tile element(i,j). Start fromrow = 4*ti + i,col = 4*tj + jand substitute into the row-major formula. - Read the four strides straight off your formula and write the rank-2 hierarchical layout
in the form
((4,4),(2,2)) : ((?,?),(?,?)), where mode 0 is the intra-tile coordinate and mode 1 is the tile coordinate. - Verify with two spot checks: tile
(1,1)element(0,0), and tile(0,1)element(3,3). - This is what CuTe's
logical_divide/zipped_dividecompute for you. Say in one sentence why having the compiler do it matters more for a tensor-core GEMM than for a simple copy. (Reference: CuTe layout algebra docs.)
Part 7 — Banks, padding, and swizzling¶
A shared-memory tile of float (4 bytes), 32 banks, bank = word_offset mod 32.
- Unpadded. Layout
(32,32):(32,1). A warp reads column 5 (one element per lane, rows 0..31). Write the offsets and the banks. How many distinct banks? How many-way is the conflict? - Padded (A02's fix). Change the layout to
(32,32):(33,1). Recompute the bank for rows 0,1,2,3 reading column 5, and state the general formula. Is it conflict-free? - Swizzled. CuTe's
Swizzle<BBits, MBase, SShift>computesoffset ^ ((offset & yyy_msk) >> SShift), where the mask selectsBBitsbits startingMBase + SShiftbits up, and the XOR lands on theBBitsbits starting atMBase. For a row-major tile with row stride 32 words, chooseBBits,MBase,SShiftso that a column read hits 32 distinct banks. Show the resulting bank for rows 0..3, column 5. - Repeat part 3 for a row stride of 128 words.
- Padding and swizzling both remove the conflict. Give the concrete reason a tensor-core
kernel must use the swizzle. (Hint: what is special about a power-of-two stride when you
issue 128-bit loads or
ldmatrix?)
What "done" looks like¶
You can, without notes:
- turn any (Shape, Stride) into an index formula and evaluate it;
- convert a 1-D coordinate to a natural coordinate through a hierarchical shape;
- write the layout of a transposed or tiled view of existing memory;
- compute shared-memory banks and say whether an access pattern conflicts.
If part 6 or part 7 took you more than ten minutes, redo them — this is the arithmetic you will be asked to do live in a kernel-engineering interview.