Parameter Budget Viz

A direct-manipulation visualiser for the parameter cost of a convolutional layer, and how that cost compounds across a stack. The learner drags three discrete sliders — kernel size, filter count, channels_in — and a mini architecture column on the left renders input → conv → output tensors. A horizontal budget bar fills toward a reference ceiling (1M parameters by default).

Pressing Add layer commits the current configuration as a coloured segment of the bar and pins the next layer's channels_in to the previous layer's filter count — the coupling that makes parameter cost compound across depth. Phase-keyed narration moves through observesizingstackinginsight as the learner explores.

32 × 3 × 3 × 3 + 32 = 896filters × k × k × channels_in + biasarchitecture[H,W,3]Conv 3×3, 32f896[H',W',32]parameter budget (1.00M)10%25%50%75%1.00M896 total0.1% of budget
1 layers. Current layer: 3×3 kernel, 32 filters, 3 channels. 896 params. Total: 896.

Drag the sliders to configure a conv layer. Watch how the parameter budget fills.

Customize
Budget
1000K
6
Defaults
3×3

Installation

npx shadcn@latest add https://craftbits.dev/r/parameter-budget-viz.json

Usage

import { ParameterBudgetViz } from "@craft-bits/viz/parameter-budget-viz";
 
<ParameterBudgetViz />

Custom budget ceiling and layer cap:

<ParameterBudgetViz budgetMax={500_000} maxLayers={4} />

Subscribe to the committed layer stack from outside:

<ParameterBudgetViz
  onLayersChange={(layers) => {
    /* read layers[i].kernelSize, filters, channelsIn, params */
  }}
/>

Pin to 3×3 kernels (the modern convention) for a "filter count vs depth" lesson:

<ParameterBudgetViz kernelSizes={[3]} defaultKernelSize={3} />

Understanding the component

  1. Coordinate system. The plot is a 560 × 280 SVG with origin top-left. The left third holds the mini architecture column; the right two thirds host the budget bar, percentage ticks, formula readout, and totals.
  2. Parameter formula. Each layer's cost is filters · k² · channels_in + filters — the trailing + filters is the bias vector. The formula is rendered above the bar with current values substituted, so the relationship between sliders and count is always one glance away.
  3. Channel coupling. While the layer stack is empty, the channels_in slider is interactive. Once any layer is committed, it is replaced with a read-only chip that reads (from prev layer's filters) — the coupling that makes a 3-layer net easily exceed 1M.
  4. Budget bar segments. Each committed layer is a coloured segment in the bar, cycling through six semantic accents. The current (uncommitted) configuration appears as a pulsing translucent overlay so the learner can see the cost before committing.
  5. Imperative budget fill. A thin underbar tracks total usage. It animates between width values via motion's animate() so the SVG never re-renders per frame. New layer segments grow from zero width with a bouncy spring on commit. Under prefers-reduced-motion: reduce, both collapse to instant attribute sets.
  6. Tick marks and ceiling. The bar carries 10% / 25% / 50% / 75% / 1M ticks so the learner can read the budget fraction without a calculator. A soft glow at the fill edge marks where the next layer would land.
  7. Architecture column window. Only the last two layers' tensor boxes are drawn; a … N more layers above hint replaces the rest. The conv box of the currently active (uncommitted) layer renders in the accent colour so the live edit is visually distinct from committed history.

Props

PropTypeDefaultDescription
kernelSizesreadonly number[][1, 3, 5, 7]Discrete kernel sizes the kernel slider snaps to.
filterCountsreadonly number[][8, 16, 32, 64, 128, 256]Discrete filter counts the filter slider snaps to.
channelCountsreadonly number[][1, 3, 32, 64, 128, 256]Discrete channel counts for the first layer only.
defaultKernelSizenumber3Initial kernel size.
defaultFiltersnumber32Initial filter count.
defaultChannelsInnumber3Initial input channel count.
budgetMaxnumber1_000_000Reference budget ceiling (bar's full extent), in parameters.
maxLayersnumber6Maximum number of layers the learner can commit.
onLayersChange(layers) => voidFires whenever the committed layer stack changes.
transitionTransitionSPRINGS.snapOverride the spring used for the budget-fill animation.
classNamestringMerged onto the root via cn().

Accessibility

  • The viz SVG is role="img" with an aria-label summarising layer count, total parameters, and budget fraction.
  • Each parameter slider is a native <input type="range"> with an aria-label carrying the parameter name — full keyboard support (arrow keys advance one discrete step) comes for free.
  • A visually hidden sr-only status region with aria-live="polite" and aria-atomic announces the current configuration on every change.
  • The narration paragraph below the SVG is aria-live="polite" and announces phase transitions in prose.
  • Colour is never the only signal — the budget bar carries a numeric N% of budget underline, and per-layer breakdown text lists the kernel/filter/channel triple alongside the colour swatch.
  • Motion respects prefers-reduced-motion: reduce — the budget-fill spring, new-segment growth, and the breathing pulse all collapse to instant attribute sets.

Credits

  • Extracted from: craftingattention (app/src/lessons/primitives/nn/ParameterBudgetViz.tsx). The source was a CNN-lesson primitive wrapped in the lesson's SvgLabel text helper and ChallengeBtn chrome, consumed lesson-track palette tokens (--color-accent-400, --color-warn-400, --color-success-400, --color-fail-400, --color-ink-*), and used the project's ca-narration styled paragraph. The viz extract drops the lesson chrome, swaps the palette to var(--cb-*) semantic tokens so consumer themes repaint freely, replaces the lesson text helper with bare <text> carrying the canonical cb-* font, exposes kernelSizes / filterCounts / channelCounts / defaultKernelSize / defaultFilters / defaultChannelsIn / budgetMax / maxLayers / onLayersChange for reuse outside the lesson, and pins the imperative bar-fill animation to SPRINGS.snap with a transition override prop.