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 observe → sizing → stacking → insight as the learner explores.
Drag the sliders to configure a conv layer. Watch how the parameter budget fills.
Installation
npx shadcn@latest add https://craftbits.dev/r/parameter-budget-viz.jsonUsage
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
- Coordinate system. The plot is a
560 × 280SVG 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. - Parameter formula. Each layer's cost is
filters · k² · channels_in + filters— the trailing+ filtersis 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. - Channel coupling. While the layer stack is empty, the
channels_inslider 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 exceed1M. - 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.
- 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. Underprefers-reduced-motion: reduce, both collapse to instant attribute sets. - Tick marks and ceiling. The bar carries
10% / 25% / 50% / 75% / 1Mticks 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. - Architecture column window. Only the last two layers' tensor boxes are drawn; a
… N more layers abovehint 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
| Prop | Type | Default | Description |
|---|---|---|---|
kernelSizes | readonly number[] | [1, 3, 5, 7] | Discrete kernel sizes the kernel slider snaps to. |
filterCounts | readonly number[] | [8, 16, 32, 64, 128, 256] | Discrete filter counts the filter slider snaps to. |
channelCounts | readonly number[] | [1, 3, 32, 64, 128, 256] | Discrete channel counts for the first layer only. |
defaultKernelSize | number | 3 | Initial kernel size. |
defaultFilters | number | 32 | Initial filter count. |
defaultChannelsIn | number | 3 | Initial input channel count. |
budgetMax | number | 1_000_000 | Reference budget ceiling (bar's full extent), in parameters. |
maxLayers | number | 6 | Maximum number of layers the learner can commit. |
onLayersChange | (layers) => void | — | Fires whenever the committed layer stack changes. |
transition | Transition | SPRINGS.snap | Override the spring used for the budget-fill animation. |
className | string | — | Merged onto the root via cn(). |
Accessibility
- The viz SVG is
role="img"with anaria-labelsummarising layer count, total parameters, and budget fraction. - Each parameter slider is a native
<input type="range">with anaria-labelcarrying the parameter name — full keyboard support (arrow keys advance one discrete step) comes for free. - A visually hidden
sr-onlystatus region witharia-live="polite"andaria-atomicannounces 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 budgetunderline, 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'sSvgLabeltext helper andChallengeBtnchrome, consumed lesson-track palette tokens (--color-accent-400,--color-warn-400,--color-success-400,--color-fail-400,--color-ink-*), and used the project'sca-narrationstyled paragraph. The viz extract drops the lesson chrome, swaps the palette tovar(--cb-*)semantic tokens so consumer themes repaint freely, replaces the lesson text helper with bare<text>carrying the canonicalcb-*font, exposeskernelSizes/filterCounts/channelCounts/defaultKernelSize/defaultFilters/defaultChannelsIn/budgetMax/maxLayers/onLayersChangefor reuse outside the lesson, and pins the imperative bar-fill animation toSPRINGS.snapwith atransitionoverride prop.