Thread Trace Panel

Collapsible diagnostic table for parallel-tile algorithms. One row per iteration, one column per traced scalar (m_i, l_i, α, row_max, row_sum, …). Pick a Q-block / thread pair from two row-of-pills selectors and the panel renders that cell's trace against a reference, colouring each value green or red by whether it falls within tolerance of the expected value.

The visualisation comes from Flash Attention debugging: when an online softmax recurrence diverges from its closed-form reference, knowing which iteration, which thread, and which scalar broke is the whole game.

Thread trace — per-iteration scalar table for one Q-block / thread pair.
Q Block
Thread
iterm_il_iαrow_maxrow_sum
0-∞0.00001.00002.341218.4291
12.341218.42910.63212.810733.0518
22.810733.05180.89122.926156.2243
32.926156.22430.97042.956374.8830

All values match reference within 1e-4 tolerance.

Customize
Trace
1e-4
40 ms

Installation

npx shadcn@latest add https://craftbits.dev/r/thread-trace-panel.json

Usage

import { ThreadTracePanel } from "@craft-bits/viz/thread-trace-panel";
 
<ThreadTracePanel
  getRowsFor={(qBlock, thread) =>
    qBlock === 0 && thread === 0
      ? { rows: myActualTrace, reference: myReferenceTrace }
      : null
  }
/>;

Drive open / selection from outside (parent scrubber, lesson narration):

const [open, setOpen] = useState(false);
const [qBlock, setQBlock] = useState(0);
const [thread, setThread] = useState(0);
 
<ThreadTracePanel
  open={open}
  onOpenChange={setOpen}
  qBlock={qBlock}
  onQBlockChange={setQBlock}
  thread={thread}
  onThreadChange={setThread}
  rows={myRowsForCurrentCell}
  referenceRows={myReferenceForCurrentCell}
/>;

Custom columns for a different recurrence:

<ThreadTracePanel
  columns={[
    { key: "step", label: "step", decimals: 0, isLabel: true },
    { key: "loss", label: "loss", decimals: 6 },
    { key: "grad_norm", label: "‖∇‖", decimals: 4 },
  ]}
  rows={steps}
  referenceRows={referenceSteps}
  tolerance={1e-3}
/>

Understanding the component

  1. Two selectors, one cell. The Q-block and thread row-of-pills selectors pick a (qBlock, thread) pair. Either pass rows + referenceRows directly, or provide a getRowsFor(qBlock, thread) callback the component invokes on every selector change.
  2. Cell-level match check. For every non-label column, the actual value is compared against the reference using |actual − reference| < tolerance. Both +∞ and −∞ are treated as exact-match. Cells render their value coloured by the result — var(--cb-success) when matching, var(--cb-error) when diverging, var(--cb-fg-subtle) for label columns.
  3. Phase machine. The root carries a data-phase attribute — collapsed, missing, matching, or diverging. Useful for styling hooks in surrounding UI.
  4. Row entrance stagger. Rows enter with a 4 ms / row stagger (clamped to [0, 50 ms] via rowStaggerSec) so the trace reads as a coordinated reveal. Reduced-motion users get the table flat and instant.
  5. Reduced motion. Under prefers-reduced-motion: reduce, the collapse animation, the selector pill colour transition, and the row entrance stagger all collapse to instant.
  6. Controlled + uncontrolled, three ways. open, qBlock, and thread each accept a controlled prop paired with on*Change, or matching default* uncontrolled variants.
  7. Narration overrides. Pass matchNarration / divergeNarration / emptyNarration to rewrite the three states' callouts.

Props

PropTypeDefaultDescription
rowsreadonly ThreadTracePanelRow[] | nullRows for the current cell. null triggers the empty callout.
referenceRowsreadonly ThreadTracePanelRow[] | nullrowsReference rows. Defaults to rows.
getRowsFor(qBlock, thread) => { rows, reference? } | nullLookup called on every selector change.
columnsreadonly ThreadTracePanelColumn[]iter / m_i / l_i / α / row_max / row_sumColumn definitions in render order.
qBlocksreadonly number[][0, 1, 2, 3]Q-block selector options.
threadsreadonly number[][0, 1, 2, 3]Thread selector options.
qBlocknumberControlled selected Q-block.
defaultQBlocknumber0Uncontrolled initial Q-block.
onQBlockChange(qBlock) => voidFires when the Q-block changes.
threadnumberControlled selected thread.
defaultThreadnumber0Uncontrolled initial thread.
onThreadChange(thread) => voidFires when the thread changes.
openbooleanControlled open state.
defaultOpenbooleanfalseUncontrolled initial open state.
onOpenChange(open) => voidFires when the panel opens / closes.
tolerancenumber1e-4Absolute tolerance for the cell-level match check.
transitionTransitionSPRINGS.snapOverride the collapse + row entrance spring.
rowStaggerSecnumber0.04Per-row entrance stagger, clamped to [0, 0.05].
formatValue(value, column) => stringtoFixed(decimals)Override the cell formatter.
toggleLabel(open) => ReactNode"Show / Hide thread trace"Override the toggle button label.
matchNarrationReactNodetolerance copyOverride the success narration.
divergeNarrationReactNoderescale-hint copyOverride the failure narration.
emptyNarrationReactNode"No reference data …"Override the missing-cell callout.
classNamestringMerged onto the root via cn().

Accessibility

  • The root carries a hidden title via aria-labelledby summarising the panel's scope.
  • The toggle button is aria-expanded + aria-controls-linked to the collapsible region.
  • Each selector row is a role="radiogroup" with aria-label; each pill is role="radio" with aria-checked. Colour is paired with data-state, focus rings, and an integer label.
  • The three narration callouts are aria-live="polite" regions — the missing / matching / diverging copy is announced as the trace updates.
  • Cells are tabular-nums and consistently padded.
  • Motion respects prefers-reduced-motion: reduce — the collapse, the pill colour transition, and the row stagger all collapse to instant.

Credits

  • Extracted from: craftingattention (app/src/lessons/primitives/viz/ThreadTracePanel.tsx). The source paired a hard-coded Q-block 0 / thread 0 reference trace with a lesson-coupled visible prop driven from a parent lessonId scrubber, a TogglePill import for the selectors, an inline var(--color-accent-400) / var(--color-success-400) / var(--color-fail-500) palette, and a SPRINGS.snappy reference that doesn't exist on the library's motion module. The viz extract strips every lesson-only import, remaps every inline colour to the --cb-accent / --cb-success / --cb-error / --cb-warning token vocabulary, swaps SPRINGS.snappy for the canonical SPRINGS.snap, generalises the four hard-coded columns into a columns prop and the single reference dictionary into a getRowsFor callback, and exposes controlled+uncontrolled open / qBlock / thread props so the panel can be driven by an outer scrubber or by its own selectors.