Sparsity Mask

A teaching visualisation for the sparsity patterns at the heart of structured pruning, sparse attention kernels, and GPU-friendly sparse matmul. The component renders a rows x cols grid where each cell is either kept (filled with cb-accent) or zeroed (filled with cb-bg-elevated and outlined with a dashed cb-border-strong stroke). A small badge above the grid declares the active pattern, and a kept-vs-total readout makes the ratio explicit.

2:4 sparsity mask, 8 by 8. 32 of 64 cells kept, 32 zeroed (50% sparse).
Sparsity mask32 / 64 kept· 50% sparse
Customize
Grid
8
8
Pattern
2:4
50%
42
Cursor reveal
32

Installation

npx shadcn@latest add https://craftbits.dev/r/sparsity-mask.json

Usage

import { SparsityMask } from "@craft-bits/core";
 
<SparsityMask rows={8} cols={8} pattern="2:4" />

Switch to unstructured random sparsity (an inline LabeledSlider exposes the rate):

<SparsityMask
  rows={8}
  cols={16}
  pattern="random"
  defaultSparsityRate={0.6}
/>

Drive a row-major cursor so the mask "fills in" as the lesson progresses:

<SparsityMask
  rows={8}
  cols={8}
  pattern="2:4"
  currentBlock={{ row: 3, col: 5 }}
/>

Pass a fully custom mask:

<SparsityMask
  rows={4}
  cols={4}
  pattern={{
    mask: [
      [true, false, true, false],
      [false, true, false, true],
      [true, false, true, false],
      [false, true, false, true],
    ],
  }}
/>

Anatomy

  1. Two cell states, two visual treatments. Kept cells are solid cb-accent rectangles; zeroed cells are cb-bg-elevated rectangles with a dashed cb-border-strong outline. The dashed border is the contract — it reads as "this cell exists but contributes nothing" without relying on colour alone.
  2. Three pattern families. "2:4" and "4:8" implement NVIDIA-style structured sparsity, picking M survivors out of every N-wide row block. "random" runs a deterministic LCG over each cell so the visible mask is stable across mounts but still varies with seed. { mask } accepts a fully bespoke boolean grid.
  3. Deterministic by default. All randomness flows through a seeded LCG keyed on seed, row, and column. Re-mounting the same component yields the identical mask, which is what you want inside an MDX explainer.
  4. Optional cursor reveal. When currentBlock is set, cells up to and including that row-col render at full opacity; cells beyond fade to about 18% so the mask appears to fill in progressively. Pair with a parent stepper for animated walkthroughs.
  5. Inline sparsity-rate slider (random only). When pattern === "random", a LabeledSlider renders below the grid for tuning the rate live. Set showSparsitySlider={false} for a static figure, or pass sparsityRate plus onSparsityRateChange for a controlled rate.
  6. SPRINGS.smooth for cell transitions. Opacity transitions animate via a smooth spring from @craft-bits/core/motion. prefers-reduced-motion: reduce collapses every transition to instant.

Props

PropTypeDefaultDescription
rowsnumber8Grid rows, clamped to 1..64.
colsnumber8Grid columns, clamped to 1..64.
pattern"2:4" | "4:8" | "random" | { mask }"2:4"Sparsity pattern applied across the grid.
sparsityRatenumberControlled rate in 0..1. Used by "random". Pair with onSparsityRateChange.
defaultSparsityRatenumber0.5Uncontrolled initial rate. Used by "random".
onSparsityRateChange(rate: number) => voidFires when the inline slider moves.
showSparsitySliderbooleantrueRender the inline LabeledSlider (only in "random" mode).
seednumber42Deterministic seed for the LCG that drives "random" and intra-block selection inside structured patterns.
currentBlockSparsityMaskBlockRow-major cursor — cells beyond it fade to about 18% opacity.
transitionTransitionSPRINGS.smoothSpring for cell-opacity transitions.
classNamestringMerged onto the root via cn().

Accessibility

  • The root is role="figure" with aria-labelledby pointing at the heading and aria-describedby at a visually-hidden aria-live="polite" summary.
  • The summary announces grid shape, pattern, kept-vs-total counts, and the resulting sparsity percentage whenever the mask changes.
  • Every cell carries a data-state="kept" or data-state="zeroed" attribute so styling never depends on colour alone — the dashed border on zeroed cells is the redundant visual signal.
  • The inline LabeledSlider exposes native aria-valuemin / aria-valuemax / aria-valuenow / aria-valuetext so keyboard arrows step the rate and screen readers narrate the value.
  • prefers-reduced-motion: reduce collapses opacity transitions to instant.

Credits

  • Extracted from: craftingattention (app/src/lessons/primitives/nn/SparsityMask.tsx). Re-architected from the source's stateful ReLU-demo (12 pre-activation bars, an "apply ReLU" button, staggered crush animation, project palette vars, SvgLabel and ChallengeBtn dependencies) into a pure declarative grid component. Generalised the fixed 1-D 12-bar layout to an arbitrary rows-by-cols grid, replaced the "negative pre-activations crushed by ReLU" framing with explicit pattern values so the component teaches the more general sparsity concept (structured pruning, sparse attention, custom masks). Added deterministic seeded mask generation, an optional row-major currentBlock cursor for animated reveal, and an inline LabeledSlider for tuning the random rate live.