Accumulation Simulator
A bar-chart visualisation for accumulator precision in mixed-precision training. The component walks the first currentStep entries of values, accumulates them at the chosen precision, and compares the running total to the exact fp64 sum of the same prefix. Two stacked bars share a scale; a mono drift readout prints the signed delta between them, switching to cb-warning once the relative drift crosses one part in a thousand.
The teaching moment: summing many small values into one large one is lossy in low precision. Once the running sum grows past the format's spacing at that magnitude, each subsequent small addend rounds to zero. fp16 drifts visibly off the truth long before the sum reaches 1.0; fp32 holds — which is why mixed-precision training keeps accumulators in fp32 even when activations and weights are fp16. Sibling to NumberLineZoomer and BitFieldExplorer in the Numerics group.
- fp16
- 0.9785
- exact
- 1
- drift
- -0.0215
Installation
npx shadcn@latest add https://craftbits.dev/r/accumulation-simulator.jsonUsage
import { AccumulationSimulator } from "@craft-bits/core";
<AccumulationSimulator defaultPrecision="fp16" defaultCurrentStep={1000} />Drive precision externally:
<AccumulationSimulator
precision={precision}
onPrecisionChange={setPrecision}
defaultCurrentStep={1000}
/>Auto-play the accumulation:
<AccumulationSimulator
defaultPrecision="fp16"
playing
playSpeed={60}
/>Custom value sequence:
const values = Array.from({ length: 2048 }, (_, i) => 0.5 / (i + 1));
<AccumulationSimulator values={values} defaultPrecision="fp16" />Anatomy
- Three precisions, one truth.
fp64uses the native JSNumberand matches the exact reference.fp32usesMath.fround— the canonical round-to-nearest-float32.fp16is hand-rolled IEEE 754 round-to-nearest-even at a 10-bit mantissa. The exact reference sum is always computed in fp64 regardless of the prop, so the drift readout is unambiguous. - Running sum, rounded at each step. Both the addend and the new running total are rounded to the chosen precision at every step. That mirrors the actual training loop — the gradient lands in the format first, then the add happens in the format — so the visualisation captures the failure mode (small delta below the spacing at the running total's magnitude) rather than a single end-of-loop rounding.
- Controlled or uncontrolled, twice.
precisionplusdefaultPrecisionandcurrentStepplusdefaultCurrentStepfollow the Radix idiom — pass the controlled prop alongside an on-change callback, or pass thedefault*prop alone. The internalplayingloop honours whichever mode the consumer picked. - Two bars, one shared scale. Both bars share a max of 1.1 times the larger of the two magnitudes so a small drift stays visible the instant it appears, but neither bar clips the readout. They animate via
transform: scaleXso the spring is GPU-composited — neverwidth, per the library's transform / opacity-only rule. SPRINGS.smoothfor the accumulator. The simulated bar springs between values viaSPRINGS.smooth;prefers-reduced-motion: reducecollapses it to an instant swap. The drift readout colour-codes ratios above 0.1% incb-warningso the "your sum doesn't match the truth" moment is visible at a glance.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
values | readonly number[] | 1 000 entries of 0.001 | Sequence of small values to accumulate. |
precision | "fp16" | "fp32" | "fp64" | — | Controlled accumulator precision. Pair with onPrecisionChange. |
defaultPrecision | "fp16" | "fp32" | "fp64" | "fp16" | Uncontrolled initial precision. |
onPrecisionChange | (precision) => void | — | Fires when the user picks a different precision. |
currentStep | number | — | Controlled prefix length, clamped to 0 to values.length. |
defaultCurrentStep | number | 0 | Uncontrolled initial prefix length. |
onCurrentStepChange | (step: number) => void | — | Fires whenever the prefix length changes. |
playing | boolean | false | Auto-advance currentStep toward values.length. |
playSpeed | number | 60 | Playback rate in steps per second when playing is true. |
transition | Transition | SPRINGS.smooth | Override the spring for the simulated bar. |
className | string | — | Merged onto the root via cn(). |
Accessibility
- The outer element is
role="figure"with anaria-labeldescribing the precision, the simulated sum, the exact sum, and the absolute drift. - An
aria-live="polite"summary mirrors the same readout so screen readers hear the new values whenever the precision or step changes. - The precision picker is a
role="radiogroup"ofrole="radio"pills witharia-checked. Tab focuses the group; Space and Enter commit a selection. - The step slider is a native
<input type="range">with Arrow / Home / End keyboard support and anaria-valuetextthat spells outstep N of M. - Colour is never the only signal — every readout has a textual label, and the drift cell only switches to
cb-warningas an emphasis on top of the always-visible numeric value. - The bar animation respects
prefers-reduced-motion: reduceand collapses to an instant swap when the user opts out.
Credits
- Extracted from:
craftingattention(app/src/lessons/primitives/nn/AccumulationSimulator.tsx). The source bundled a five-phase narration state machine (observe / fp32-working / switch-low / stagnation / insight), a per-format "starting parameter" picker (FP32 / FP16 / BF16), an "Add Gradient" click loop, success / stuck / step counters, ghost bars rendered with imperativeanimate(...)calls, and the project'sChallengeBtnplusSvgLabelchrome. The library version reframes the same teaching surface as a pure accumulator drift visualisation — two bars (simulated + exact), three precisions (fp16 / fp32 / fp64), Radix-style controlled and uncontrolled prop pairs forprecisionandcurrentStep, aplayingplusplaySpeedloop for automatic sweeps, andcb-accent/cb-warningsemantic tokens in place of the inline palette.