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
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.jsonUsage
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
- Temperature first. Each raw logit
z_iis divided byT, then run through softmax:p_i = exp(z_i / T) / Σ exp(z_j / T).T → 0collapses to the one-hot argmax;T → ∞flattens to uniform1 / N. The max-subtraction trick (subtractmax(z_i / T)beforeexp) keeps the computation numerically stable even for tinyT. - Top-k second. After the softmax, top-k zeros every token outside the top
kand renormalises what remains.topK = 0(the default) disables the filter — every token stays eligible. SettingtopK = 1collapses sampling to a deterministic argmax. - 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.9keeps the smallest nucleus that covers 90% of the mass. - 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.
- Entropy readout. Shannon entropy of the final (renormalised) distribution is shown in nats next to the eligible-count. Drops to
0when only one token is eligible, climbs towardlog(eligible)as the nucleus widens. - Spring on bar heights.
yandheightanimate withSPRINGS.smoothfrom@craft-bits/core/motionso dragging any of the three sliders feels continuous instead of chattering. Reduced-motion users snap. - Controlled + uncontrolled. Every knob has the matching
*/default*/on*Changetriple. Drive all three from a parent for a recorded explainer, or omit them entirely and let the component own its state.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
logits | readonly number[] | — | Raw next-token logits. 2..12 entries reads best. |
labels | readonly string[] | indices | Bar labels — falls back to the numeric index when missing. |
temperature | number | — | Controlled T > 0. Pair with onTemperatureChange. |
defaultTemperature | number | 1 | Uncontrolled initial T. |
onTemperatureChange | (t: number) => void | — | Fires whenever the slider commits a new T. |
topK | number | 0 | Keep only the k highest-probability tokens. 0 disables. |
onTopKChange | (k: number) => void | — | Fires whenever top-k changes. |
topP | number | 1 | Keep the smallest top set whose cumulative mass reaches p. 1 disables. |
onTopPChange | (p: number) => void | — | Fires whenever top-p changes. |
tempRange | readonly [number, number] | [0.1, 3] | Slider extents for temperature. Both > 0, min < max. |
showControls | boolean | true | Render the three sliders. |
showEntropy | boolean | true | Render the entropy + eligible-count readout. |
transition | Transition | SPRINGS.smooth | Spring for bar-height transitions. |
className | string | — | Merged onto the root <div> via cn(). |
Accessibility
- The chart is wrapped in
role="figure"with a dynamicaria-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 (Arrowkeys step,Page Up/Page Downstep by 10%,Home/Endjump 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-specificWidgetchrome; 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 inlineSPRINGS.snappy(typo in source — missing from canonical token set) withSPRINGS.smoothfor bar transitions.