6.2.7 · D2GPU Architecture

Visual walkthrough — Memory hierarchy (global, shared, registers)

2,026 words9 min readBack to topic

The parent note told you the punchline: keep hot data close, and matrix multiply gets ~15× less global traffic. This page derives that number from zero. We will not assume you know what a "thread", a "warp", or a "cycle" is. We build every symbol on a picture, step by step, and count the memory trips ourselves.


Step 0 — The words we are allowed to use

Before any formula, three plain-word ideas, each pinned to the picture below.

And three storage places, ordered by how far the worker must reach:

A cycle is just one tick of the GPU's clock — our unit of "how long". Saying global is "400 cycles" means one walk to the pantry costs as much as ~400 ticks of arithmetic.

Figure — Memory hierarchy (global, shared, registers)

What / Why / Picture. What: we named workers and shelves. Why: every later symbol (, , tiles, transactions) is built only from these. Picture: the three shelves stacked by distance — the higher you climb, the longer the arrow (the walk). This distance-ladder is the whole reason a hierarchy exists.


Step 1 — The problem, drawn as arrows to the pantry

Our worked example: multiply two matrices, , with .

So one thread reads numbers from the pantry — one and one per loop trip. See Matrix Multiplication Optimization for the algorithm itself; here we only count the walks.

Figure — Memory hierarchy (global, shared, registers)

What / Why / Picture. What: we counted 2048 global reads per thread. Why: this is our baseline "walk count" — everything after is measured against it. Picture: one thread firing 2048 long arrows into the far pantry. The wall of arrows is the problem.


Step 2 — The naïve total, counted honestly

There is one thread per output cell, so threads (call it "1M").

Figure — Memory hierarchy (global, shared, registers)

What / Why / Picture. What: multiplied per-thread walks by number of threads. Why: to see the true traffic, and to expose the waste. Picture: the same row of (highlighted coral) is fetched by every thread in that row — the exact same 1024 numbers, walked to the pantry over and over. That repetition is the leak we plug next.


Step 3 — The idea: fetch once, reuse many (tiling)

Split each matrix into small square tiles of size . We use .

Figure — Memory hierarchy (global, shared, registers)

What / Why / Picture. What: introduced a tile parked in shared memory. Why: shared memory is ~10× closer than global, and cooperative loading removes the duplicate walks of Step 2. Picture: 256 threads (lavender box) each grab one element from the pantry (long arrows), drop it on the shared counter, then draw short arrows from the counter during the compute — many short arrows per one long arrow.


Step 4 — Count the tiled walks, term by term

Slide the tile across the -dimension. Number of tiles along a row/column:

With :

Figure — Memory hierarchy (global, shared, registers)

What / Why / Picture. What: recounted total walks with sharing. Why: to get the honest new number. Picture: one loaded -tile row (mint) feeds separate output cells — one walk, sixteen uses.


Step 5 — The speedup, and where the comes from

The parent's "~15×" is this same number, softened by loading edge/boundary tiles in practice. The clean law is: reuse each fetched value times → traffic drops by . This is the arithmetic-intensity lever behind the Roofline Model and Memory Bandwidth Optimization.

Figure — Memory hierarchy (global, shared, registers)

What / Why / Picture. What: divided the two counts. Why: to isolate what causes the speedup — it is , nothing else. Picture: a bar chart, 2.15B shrinking to 134M, with the factor labelling the gap.


Step 6 — Edge case: the shared counter has 32 lanes (bank conflicts)

We assumed shared memory is uniformly fast. It has a catch. Shared memory is split into banks: 32 lanes, each serving one request per cycle.

The transpose case shows the trap and the fix: a shared tile makes column access tile[i][i] hit lane for every thread → , a 32× slowdown. Padding to shifts each row by one lane, so a column spreads across all 32 lanes → .

Figure — Memory hierarchy (global, shared, registers)

What / Why / Picture. What: modelled shared memory as 32 lanes. Why: the counter is only fast if the team doesn't crowd one lane. Picture: left, a column jammed into lane 0 (coral, serialized); right, the same column fanned across all lanes after [32][33] padding (mint, parallel).


Step 7 — Degenerate cases: when tiling doesn't help

Figure — Memory hierarchy (global, shared, registers)

What / Why / Picture. What: swept the boundary values of and the no-reuse case. Why: the contract says every case must be shown — including when the trick fails. Picture: the -vs-speedup curve, flat at 1 for , rising, then a red "won't fit" wall past the shared-memory limit.


The one-picture summary

Figure — Memory hierarchy (global, shared, registers)

One thread's 2048 far-pantry walks (Step 1) × 1M threads = 2.15 B walks (Step 2, full of duplicates). Park a tile on the shared counter (Step 3), reuse each fetched value times (Step 4), and total walks fall to — a clean × win (Step 5) — provided the counter's 32 lanes aren't jammed (Step 6) and the tile fits with real reuse to exploit (Step 7).

Recall Feynman retelling — say it back in plain words

Picture a chef fetching from a far pantry. Naïvely, every dish sends the chef to the pantry for the same onion again and again — two billion trips, mostly repeats. The fix: send one runner to grab a tray of ingredients and set it on the shared counter; now everyone cooks from the counter, reaching the pantry once per tray instead of once per pinch. A tray of width 16 means each ingredient gets used 16 times per fetch, so trips drop 16-fold. Two cautions: the counter has 32 narrow slots, and if the whole team grabs from one slot they queue up (fix: nudge the layout so they spread out); and if a recipe uses each ingredient only once, there's nothing to reuse — the tray helps no one.

Reveal-check:

Total naïve reads for
billion
Total tiled reads for
million
The traffic-reduction factor equals
the tile width
Bank-conflict serialization for a [32][32] diagonal-column access
32
Why pad shared tile to [32][33]
to shift each row by one bank so a column spreads over all 32 lanes ()

Related: Memory Latency Hiding · Cache Architecture · GPU Thread Hierarchy · CUDA Memory Types