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.jsonUsage
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
- Two ways to feed the grid. Pass
accesses: number[][]for a real measured count matrix, or omit it and let the component synthesise frompattern. The synthesised mode exists so a lesson can A/B between three canonical layouts without hand-rolling matrices. - Three preset patterns, three visual signatures.
coalescedlights every cell once — a uniformly warm grid; one transaction services the whole warp.stridedlights only every Nth column — vertical hot stripes; each read triggers a separate transaction.randomscatters hot spots with no geometry — a speckled grid; worst case for the cache. - 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-fgpast ~0.55 intensity so contrast stays AA on both themes. - 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. - Counts auto-hide on dense grids.
showCountsdefaults totrue, but the digit is auto-hidden whencellSize < 22so it never overflows the tile. SPRINGS.snapfor tint transitions. Cell fills animate via the snap spring from@craft-bits/core/motion.prefers-reduced-motion: reducecollapses every transition to instant.- Determinism. The
randompreset uses a seeded LCG keyed on(seed, row, col). Re-mounting with the sameseedyields the identical scatter pattern.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
accesses | readonly (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. |
rows | number | 8 | Grid rows for the synthesised pattern. Ignored when accesses is set. Clamped to 1..64. |
cols | number | 8 | Grid columns for the synthesised pattern. Ignored when accesses is set. Clamped to 1..64. |
cellSize | number | 28 | Pixel size of each cell. |
showCounts | boolean | true | Render the per-cell access count inside the tile. Auto-hidden when cellSize < 22. |
seed | number | 42 | Deterministic seed for the "random" preset. |
transition | Transition | SPRINGS.snap | Spring for cell-tint transitions. |
className | string | — | Merged onto the root via cn(). |
Accessibility
- The figure is
role="figure"witharia-labelledbypointing at the "Memory access" heading andaria-describedbyat a visually-hiddenaria-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 adata-state="hot" | "cold"attribute so styling never depends on colour alone. - Numeric readouts use
font-variant-numeric: tabular-numsso values do not reflow when they update. prefers-reduced-motion: reducecollapses 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 —ModeStriptwo-mode toggle,Naive / TiledTogglePillpair,(row, col)thread selector, side-by-sideMatrix A+Matrix B8×8 grids with hard-codedN = 8/TILE = 4/TILES_PER_DIM = 2,getAccess(mode, ti, tj)row+column projection,NAIVE_READS = 1024/TILED_READS = 512running counter,usePredictRounds-driven four-question quiz pool covering tile passes / shared-memory barriers / naive read counts / why tiling wins,exploreNarrnarration heuristics,FeedbackBadge/ScoreDots/DoneCard/ChallengeBtnchrome, plus a siblingTileLoadAnimationfour-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 generalaccesses: number[][]count grid; added thecoalesced/strided/randompreset family so the same component teaches the broader cache-friendly-access concept (not just the matmul instance). Cell tint pulled ontooklch(from var(--cb-accent))so it tracks the active theme.