Gradient Pipeline

A visceral, click-through demo of the vanishing-gradient problem. A gradient signal starts at the loss on the right of the pipeline and travels backward through each layer. At every layer the pulse is multiplied by the local derivative ∂a/∂z — the radius shrinks, the opacity dims, and the colour shifts from the accent (healthy) hue through warning to error (vanishing). By the time the pulse reaches the leftmost layer, the eye sees the gradient is barely there.

Generic over the layer set. Pass any array of { label, fn, derivative } and the component renders the boxes, arrows, and running-gradient labels itself.

gradient shrank 88% — from 1.0 to 0.12 across 3 layers
Idle. Gradient at the loss is 1.0.

3 layers, each with its own local derivative. The loss has been computed — now the gradient must travel backward through every layer to reach the weights. Click to send the signal.

Customize
Pipeline
vanishing (default)
1.0

Installation

npx shadcn@latest add https://craftbits.dev/r/gradient-pipeline.json

Usage

import { GradientPipeline } from "@craft-bits/viz/gradient-pipeline";
 
<GradientPipeline />;

Custom layer set (a 5-layer pipeline that vanishes harder):

<GradientPipeline
  layers={[
    { label: "Layer 1", fn: "a₁ = σ(w₁x)",  derivative: 0.6 },
    { label: "Layer 2", fn: "a₂ = σ(w₂a₁)", derivative: 0.5 },
    { label: "Layer 3", fn: "a₃ = σ(w₃a₂)", derivative: 0.5 },
    { label: "Layer 4", fn: "a₄ = σ(w₄a₃)", derivative: 0.4 },
    { label: "Layer 5", fn: "a₅ = σ(w₅a₄)", derivative: 0.3 },
  ]}
/>

Subscribe to stage transitions to drive a sibling chart or transcript:

<GradientPipeline
  onStageChange={(stage) => {
    /* drive a sibling chart, transcript, etc. */
  }}
  onReset={() => {
    /* clear sibling state */
  }}
/>

Understanding the component

  1. Pipeline layout. Each layer is rendered as a rounded box with its label, function, and local derivative . The loss sits to the right as a small badge holding the initial gradient.
  2. Stage machine. Stage 0 is idle (pulse at the loss). Each click advances by one — the pulse crosses one layer and the running gradient is updated. The terminal stage shows the summary annotation; one more click resets.
  3. Pulse geometry. The pulse position is the left edge of the layer the pulse has just exited. Its radius, opacity, and colour all track the running gradient divided by the initial gradient. Colour passes through three semantic tokens — var(--cb-accent) while the ratio is above 0.5, var(--cb-warning) between 0.2 and 0.5, and var(--cb-error) below.
  4. Per-layer labels. Each layer renders two imperatively-driven labels — a multiplication readout below the box and a running-gradient readout above. Both fade in on SPRINGS.snap, and the running-gradient readout uses SPRINGS.bouncy for the entry slide.
  5. Box glow + backward arrow. When the pulse crosses a layer, that layer's outer rect flashes via SPRINGS.damped and the backward arrow on that span lights up. The first transition also reveals a dashed ghost ring at the pulse's original position so the eye can compare the now-shrunken pulse against where it started.
  6. Reduced motion. Under prefers-reduced-motion: reduce, all imperative tweens collapse to instant attribute sets, the idle breathing pulse on the loss is suppressed, and stage transitions are immediate.

Props

PropTypeDefaultDescription
layersreadonly { label; fn; derivative }[]3-layer canonical demoLayers ordered left-to-right (Layer 1 leftmost).
initialGradientnumber1Gradient value at the loss (right-hand side).
transitionTransitionSPRINGS.smoothOverride the pulse-move transition.
onStageChange(stage) => voidFires whenever the stage advances.
onReset() => voidFires when the user resets.
classNamestringMerged onto the root via cn().

Accessibility

  • The SVG is role="img" with an aria-label summarising the layer count, current stage, and the initial-to-final gradient.
  • A polite live region announces each stage in plain prose so screen-reader users get the same outcome as sighted users.
  • The narration paragraph also has aria-live="polite" and reads as plain prose; it is the canonical explanation for each phase.
  • Colour is never the only signal — the running gradient is also encoded numerically in the per-layer labels and the narration.
  • The action button has a context-aware label so the next action is always obvious.
  • Motion respects prefers-reduced-motion: reduce — all imperative tweens collapse to instant attribute sets, and the idle breathing pulse on the loss is suppressed.

Credits

  • Extracted from: craftingattention (app/src/lessons/primitives/math/GradientPipeline.tsx). The source was a tightly bundled lesson component — it consumed SvgLabel from the SVG primitives and ChallengeBtn from the lesson chrome, hardcoded the 3-layer set with derivatives 0.8 / 0.5 / 0.3 and an initial gradient of 1, baked in a five-branch narration generator with per-stage button labels, and depended on per-track lesson palette tokens. The viz extract drops the lesson chrome (raw <text> plus a token-styled button), parameterises layers and initialGradient so the component drives any pipeline depth, remaps the colour palette to var(--cb-*) semantic tokens so consumer themes repaint freely, and re-keys the per-stage springs to SPRINGS.smooth / SPRINGS.snap / SPRINGS.bouncy / SPRINGS.damped so all motion comes from the same place as every other craft-bits component.