Memory Access Heatmap

A rows × cols grid where each cell's colour intensity encodes how many times that memory location was touched. The intended teaching beat is contrasting coalesced vs strided vs random memory access on a GPU — same matrix, very different cache behaviour, very different shapes on the heatmap. Pair it with HBMTrafficViz to teach why coalescing matters: it minimises this exact picture.

coalesced memory access heatmap, 8 by 8. 64 of 64 cells touched at least once. Peak 1 access per cell, 64 total.
Memory accesspeak 1· 64 touches
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
Customize
Grid
8×8
28px
Pattern
coalesced
42
Display

Installation

npx shadcn@latest add https://craftbits.dev/r/memory-access-heatmap.json

Usage

import { MemoryAccessHeatmap } from "@craft-bits/core";
 
// Synthesised preset — flip between the three canonical patterns.
<MemoryAccessHeatmap pattern="coalesced" />
<MemoryAccessHeatmap pattern="strided" />
<MemoryAccessHeatmap pattern="random" />

Pass a measured count matrix when you have one (e.g. from a profiler trace):

<MemoryAccessHeatmap
  accesses={[
    [4, 0, 0, 0],
    [4, 0, 0, 0],
    [4, 0, 0, 0],
    [4, 0, 0, 0],
  ]}
/>

Hide the per-cell digit for dense grids and let colour do the work:

<MemoryAccessHeatmap rows={16} cols={16} pattern="random" showCounts={false} />

Understanding the component

  1. Two ways to feed the grid. Pass accesses: number[][] for a real measured count matrix, or omit it and let the component synthesise from pattern. The synthesised mode exists so a lesson can A/B between three canonical layouts without hand-rolling matrices.
  2. Three preset patterns, three visual signatures. coalesced lights every cell once — a uniformly warm grid; one transaction services the whole warp. strided lights only every Nth column — vertical hot stripes; each read triggers a separate transaction. random scatters hot spots with no geometry — a speckled grid; worst case for the cache.
  3. Colour encodes intensity. Each cell's fill is oklch(from var(--cb-accent) l c h / α) with α = 0.1 + intensity * 0.85, so even tiny counts show a hint of warmth and hot cells render close to solid --cb-accent. The label colour flips to --cb-accent-fg past ~0.55 intensity so contrast stays AA on both themes.
  4. Normalisation is per-grid. Intensity is count / max(grid). Two grids rendered side-by-side aren't on the same absolute scale — they're each normalised against their own peak. This is on purpose: it keeps the shape of each access pattern legible even when one grid touches every cell ten times and the other touches one cell once.
  5. Counts auto-hide on dense grids. showCounts defaults to true, but the digit is auto-hidden when cellSize < 22 so it never overflows the tile.
  6. SPRINGS.snap for tint transitions. Cell fills animate via the snap spring from @craft-bits/core/motion. prefers-reduced-motion: reduce collapses every transition to instant.
  7. Determinism. The random preset uses a seeded LCG keyed on (seed, row, col). Re-mounting with the same seed yields the identical scatter pattern.

Props

PropTypeDefaultDescription
accessesreadonly (readonly number[])[]Measured count matrix. When omitted, the component synthesises from pattern. Negative and non-finite values clamp to 0.
pattern"coalesced" | "strided" | "random""coalesced"Preset access pattern used when accesses is omitted.
rowsnumber8Grid rows for the synthesised pattern. Ignored when accesses is set. Clamped to 1..64.
colsnumber8Grid columns for the synthesised pattern. Ignored when accesses is set. Clamped to 1..64.
cellSizenumber28Pixel size of each cell.
showCountsbooleantrueRender the per-cell access count inside the tile. Auto-hidden when cellSize < 22.
seednumber42Deterministic seed for the "random" preset.
transitionTransitionSPRINGS.snapSpring for cell-tint transitions.
classNamestringMerged onto the root via cn().

Accessibility

  • The figure is role="figure" with aria-labelledby pointing at the "Memory access" heading and aria-describedby at a visually-hidden aria-live="polite" summary.
  • The summary announces the active pattern, grid shape, the number of touched cells, the peak count per cell, and the total touches — so the chart is readable without colour.
  • Every cell carries an aria-label ("Row 3, column 5: 4 accesses") and a data-state="hot" | "cold" attribute so styling never depends on colour alone.
  • Numeric readouts use font-variant-numeric: tabular-nums so values do not reflow when they update.
  • prefers-reduced-motion: reduce collapses cell-tint transitions to instant.

Credits

  • Extracted from: craftingattention (app/src/lessons/primitives/viz/MemoryAccessHeatmap.tsx). The source was a stateful Explore / Predict CUDA-matmul lesson primitive — ModeStrip two-mode toggle, Naive / Tiled TogglePill pair, (row, col) thread selector, side-by-side Matrix A + Matrix B 8×8 grids with hard-coded N = 8 / TILE = 4 / TILES_PER_DIM = 2, getAccess(mode, ti, tj) row+column projection, NAIVE_READS = 1024 / TILED_READS = 512 running counter, usePredictRounds-driven four-question quiz pool covering tile passes / shared-memory barriers / naive read counts / why tiling wins, exploreNarr narration heuristics, FeedbackBadge / ScoreDots / DoneCard / ChallengeBtn chrome, plus a sibling TileLoadAnimation four-phase shared-memory race-condition simulator with auto-play and barrier-removal toggle. Reframed here as a pure heatmap primitive: stripped the modes, predict quiz, narration, dual-grid matmul framing, thread selector, read counters, and the sibling tile-load animation; replaced the matmul-specific row+column projection with a general accesses: number[][] count grid; added the coalesced / strided / random preset family so the same component teaches the broader cache-friendly-access concept (not just the matmul instance). Cell tint pulled onto oklch(from var(--cb-accent)) so it tracks the active theme.