LR Schedule Viz

A line plot of learning rate over training. Drop in one of the five built-in schedules — constant, linear, cosine, step, warmup-cosine — or pass a custom (step, total) -> number. Use it to teach why warmup matters, the cosine annealing pattern, and the tradeoff between aggressive decay and stable convergence.

Learning rate schedule: cosine.Learning rate schedule: cosine, current step 50 of 100, LR 0.500
cosinepeak 1.00 step 50/100 lr 0.500
01.000102030405060708090100training steplearning rate
Customize
Schedule
cosine
Curve
1.00
10
Cursor
50

Installation

npx shadcn@latest add https://craftbits.dev/r/lr-schedule-viz.json

Usage

import { LRScheduleViz } from "@craft-bits/core";
 
<LRScheduleViz schedule="cosine" peakLR={1.0} totalSteps={100} />

Highlight a specific step on the curve:

<LRScheduleViz
  schedule="warmup-cosine"
  peakLR={1.0}
  totalSteps={100}
  warmupSteps={10}
  currentStep={42}
/>

Pass a custom schedule:

<LRScheduleViz
  schedule={{ fn: (step, total) => 1 / (1 + step / total) }}
  peakLR={1.0}
  totalSteps={200}
/>

Understanding the component

  1. Five canonical schedules, each with a teaching point. constant keeps LR flat. linear ramps LR linearly down toward zero (floored at decayFloor). cosine is the canonical smooth anneal — half a cosine wave from peak down to the floor. step halves LR three times across training. warmup-cosine ramps from zero up to peak over warmupSteps, then cosine-decays the rest of the way.
  2. Custom schedules via { fn }. Any pure (step, total) -> number plugs in. The y-axis is normalised against peakLR, so make sure your function stays within [0, peakLR].
  3. Cursor is fully opt-in. Pass currentStep (controlled) or defaultCurrentStep (uncontrolled) to surface a vertical guide and a dot at that step on the curve. Without either, the chart just shows the curve and the peak/total readout.
  4. Two springs, two roles. The curve animates between schedule changes with SPRINGS.smooth (slow enough to read the shape changing). The cursor dot uses SPRINGS.snap (crisp enough to track a slider).
  5. Decay floor matters. Learning rates below decayFloor are clamped — keeps the curve readable and matches the safety rail you would want in real training code.
  6. Reduced-motion fallback. prefers-reduced-motion: reduce snaps both the curve and the cursor with no spring.

Props

PropTypeDefaultDescription
schedule'constant' | 'linear' | 'cosine' | 'step' | 'warmup-cosine' | { fn }'cosine'Built-in schedule or custom function.
peakLRnumber1.0Peak learning rate — the curve's maximum value.
totalStepsnumber100Total training steps over which to plot.
currentStepnumberControlled cursor step.
defaultCurrentStepnumberUncontrolled initial cursor step.
onCurrentStepChange(step: number) => voidFires when the cursor step changes.
warmupStepsnumber10Warmup duration (used by warmup-cosine).
decayFloornumber0.01Minimum LR for decaying schedules.
transitionTransitionSPRINGS.snapSpring for the cursor dot.
classNamestringMerged onto the root via cn().

Accessibility

  • The figure is role="figure" with a labelled title that names the active schedule.
  • An aria-live="polite" summary announces the schedule, current step, and current LR — readable without color.
  • The textual readout above the chart spells out peak LR, current step, and current LR, so color is never the only signal.
  • prefers-reduced-motion: reduce snaps both the curve and the cursor with no spring.

Credits

  • Extracted from: craftingattention (app/src/lessons/primitives/viz/LRScheduleViz.tsx). Stripped the lesson-specific Explore/Predict mode strip, prediction quiz rounds, three-schedule toggle pills, slider controls, drag-to-scrub interaction, and the lesson chrome. Generalised to a pure schedule-plot primitive: five built-in schedules, a custom { fn } escape hatch, controlled and uncontrolled cursor, and a single-curve render with a springed dot.