Sampling Playground

The decoder pipeline every LLM runs at every step, laid out as a single bar chart. Raw next-token logits flow through temperature-scaled softmax, then top-k truncation, then top-p (nucleus) truncation. The candidates that survive light up in the accent colour; the cut tail goes faint with a strikethrough so the trade-off between filter aggressiveness and tail diversity is legible at a glance.

Sampling · T = 1.00 · k = off · p = 1.00H = 1.612 nats · 10/10 eligible
0%25%50%75%100%40.2%the27.0%a13.4%cat8.1%dog4.5%sat2.7%onmatbigredhappyprobability
1.00
off
1.00
Customize
Distribution
longtail
1.00
Truncation
off
1.00
Display

Installation

npx shadcn@latest add https://craftbits.dev/r/sampling-playground.json

Usage

import { SamplingPlayground } from "@craft-bits/core";
 
<SamplingPlayground
  logits={[3.2, 2.8, 2.1, 1.6, 1.0, 0.5, 0.1, -0.3, -0.8, -1.5]}
  labels={["the", "a", "cat", "dog", "sat", "on", "mat", "big", "red", "happy"]}
  defaultTemperature={1}
/>

Drive every knob from outside (parent slider, scrubber, recorded trace, …):

const [t, setT] = useState(1);
const [k, setK] = useState(5);
const [p, setP] = useState(0.9);
 
<SamplingPlayground
  logits={logits}
  temperature={t}
  onTemperatureChange={setT}
  topK={k}
  onTopKChange={setK}
  topP={p}
  onTopPChange={setP}
/>

Hide the controls panel for a read-only embed:

<SamplingPlayground
  logits={logits}
  temperature={0.7}
  topK={5}
  topP={0.9}
  showControls={false}
/>

Understanding the component

  1. Temperature first. Each raw logit z_i is divided by T, then run through softmax: p_i = exp(z_i / T) / Σ exp(z_j / T). T → 0 collapses to the one-hot argmax; T → ∞ flattens to uniform 1 / N. The max-subtraction trick (subtract max(z_i / T) before exp) keeps the computation numerically stable even for tiny T.
  2. Top-k second. After the softmax, top-k zeros every token outside the top k and renormalises what remains. topK = 0 (the default) disables the filter — every token stays eligible. Setting topK = 1 collapses sampling to a deterministic argmax.
  3. Top-p third. The remaining distribution is then walked from most- to least-probable token, accumulating mass until it reaches p. Everything above the threshold is dropped. topP = 1 (the default) disables the filter; topP = 0.9 keeps the smallest nucleus that covers 90% of the mass.
  4. Eligibility highlight. Bars that survive both filters render in the accent colour. Bars that get cut fade to a muted tone and gain a strikethrough underline on the label axis so the eliminated tail is legible without a colour-only cue.
  5. Entropy readout. Shannon entropy of the final (renormalised) distribution is shown in nats next to the eligible-count. Drops to 0 when only one token is eligible, climbs toward log(eligible) as the nucleus widens.
  6. Spring on bar heights. y and height animate with SPRINGS.smooth from @craft-bits/core/motion so dragging any of the three sliders feels continuous instead of chattering. Reduced-motion users snap.
  7. Controlled + uncontrolled. Every knob has the matching * / default* / on*Change triple. Drive all three from a parent for a recorded explainer, or omit them entirely and let the component own its state.

Props

PropTypeDefaultDescription
logitsreadonly number[]Raw next-token logits. 2..12 entries reads best.
labelsreadonly string[]indicesBar labels — falls back to the numeric index when missing.
temperaturenumberControlled T > 0. Pair with onTemperatureChange.
defaultTemperaturenumber1Uncontrolled initial T.
onTemperatureChange(t: number) => voidFires whenever the slider commits a new T.
topKnumber0Keep only the k highest-probability tokens. 0 disables.
onTopKChange(k: number) => voidFires whenever top-k changes.
topPnumber1Keep the smallest top set whose cumulative mass reaches p. 1 disables.
onTopPChange(p: number) => voidFires whenever top-p changes.
tempRangereadonly [number, number][0.1, 3]Slider extents for temperature. Both > 0, min < max.
showControlsbooleantrueRender the three sliders.
showEntropybooleantrueRender the entropy + eligible-count readout.
transitionTransitionSPRINGS.smoothSpring for bar-height transitions.
classNamestringMerged onto the root <div> via cn().

Accessibility

  • The chart is wrapped in role="figure" with a dynamic aria-label ("Next-token distribution. Temperature 0.50, top-k 5, top-p 0.90. 5 of 10 tokens eligible. Maximum probability 62% on the.") so screen-reader users get the same headline as sighted users.
  • Every control is a native <input type="range"> — full keyboard support out of the box (Arrow keys step, Page Up / Page Down step by 10%, Home / End jump to the extents).
  • The entropy + eligible-count readout uses aria-live="polite" so changes are announced as the user drags.
  • Eligibility uses both colour (accent vs muted) and a strikethrough underline on the cut tokens — colour-blind users still see the truncation.
  • Bar heights animate with SPRINGS.smooth; reduced-motion users snap to the new values instantly.

Credits

  • Extracted from: craftingattention (app/src/lessons/primitives/viz/SamplingPlayground.tsx). Stripped the lesson-specific Explore / Predict mode strip, six-round question bank, narration banner, click-to-predict / Sample button, history strip, score dots, DoneCard, and the project-specific Widget chrome; generalised to a single visualisation primitive with controlled / uncontrolled state for all three knobs. Added the eligible-count readout in the entropy line and the strikethrough indicator on cut tokens for non-colour eligibility. Replaced the inline SPRINGS.snappy (typo in source — missing from canonical token set) with SPRINGS.smooth for bar transitions.