GPU Timeline Profiler

A Gantt-chart view of a GPU trace. Each row is a stream — the compute queue, the copy engine, a synchronisation lane — and each rectangular block is one executable unit (a kernel launch, a memory transfer, an event record). An optional vertical cursor at currentTime threads the chart through external playback, scrubbing, or narration state.

GPU timeline. 3 streams, 8 events, trace duration 22.00 ms. Cursor at t=8.50 ms. compute: 4 events; copy: 2 events; sync: 2 events.
GPU timelinet=8.50 / 22.00 ms
computecopysyncqkvattnprojffn
Customize
Timeline
8.5ms
22px

Installation

npx shadcn@latest add https://craftbits.dev/r/gpu-timeline-profiler.json

Usage

import { GPUTimelineProfiler } from "@craft-bits/core";
 
const streams = [
  {
    id: "compute",
    label: "compute",
    events: [
      { id: "qkv",  start: 1.0,  duration: 3.4, label: "qkv" },
      { id: "attn", start: 4.6,  duration: 5.8, label: "attn", tone: "success" },
      { id: "proj", start: 10.6, duration: 2.6, label: "proj" },
      { id: "ffn",  start: 13.4, duration: 6.2, label: "ffn" },
    ],
  },
  {
    id: "copy",
    label: "copy",
    events: [
      { id: "h2d", start: 0.0,  duration: 1.4, label: "h2d" },
      { id: "d2h", start: 19.8, duration: 1.6, label: "d2h" },
    ],
  },
  {
    id: "sync",
    label: "sync",
    events: [
      { id: "evt", start: 10.4, duration: 0.4, label: "evt",  tone: "warning" },
      { id: "syn", start: 21.4, duration: 0.6, label: "sync", tone: "warning" },
    ],
  },
];
 
<GPUTimelineProfiler streams={streams} currentTime={8.5} />

Drive the cursor from outside (scrubbing, narration, MDX prose hover):

<GPUTimelineProfiler streams={streams} currentTime={t} />

Stretch the horizontal axis for sub-millisecond traces:

<GPUTimelineProfiler streams={streams} unitsPerMs={120} />

Understanding the component

  1. One row per stream. Streams render in the order they appear in streams — the component never re-sorts. The row label sits in the left gutter; the lane background is a single rounded rectangle so empty time is still visually anchored.
  2. Blocks placed by start + duration. Each event becomes a rounded rectangle whose x is start * effectivePxPerMs and whose width is duration * effectivePxPerMs. Tiny blocks (under 2 px) widen to a 2 px floor so single-tick events stay visible.
  3. Tone via semantic tokens. accent (default), warning, success, error map to --cb-accent, --cb-warning, --cb-success, --cb-error — recolour your theme and the timeline repaints. Colour is not the only signal: every block can carry a label and the row has its own gutter label.
  4. Effective pxPerMs floors at a legible chart. unitsPerMs is the requested horizontal scale, but the chart floors at 320 px wide so a short trace at the default unitsPerMs: 1 still reads. The effective scale is chartWidth / traceDuration so blocks fill the lane instead of clustering on the left.
  5. Blocks enter on a stagger. Each block fades and scaleYs in from 0.6 on SPRINGS.snap with a per-index delay capped at 0.25 s — the trace "lights up" left to right when it mounts, then sits still.
  6. Optional vertical cursor. When currentTime is supplied and falls inside [0, traceDuration], a dashed accent-toned line draws at that x. The cursor animates via transform: translateX, so scrubbing is GPU-composited.
  7. Reduced motion. prefers-reduced-motion: reduce collapses every spring to { duration: 0 }, skips the enter stagger entirely, and renders the final frame on first paint.

Props

PropTypeDefaultDescription
streamsreadonly GPUTimelineStream[]Required. One row per item, rendered in the supplied order.
currentTimenumberOptional cursor position on the shared time axis.
unitsPerMsnumber1Requested horizontal scale, in pixels per millisecond.
transitionTransitionSPRINGS.snapSpring used for block enter and cursor transitions.
classNamestringMerged onto the root <div> via cn().

GPUTimelineStream

FieldTypeDescription
idstringUnique key — used as the React key and the a11y token.
labelstringRow label drawn in the left gutter.
eventsreadonly GPUTimelineEvent[]Events to draw on this stream's row.

GPUTimelineEvent

FieldTypeDescription
idstringUnique within the parent stream — used as the React key.
startnumberStart time on the shared axis, in caller units.
durationnumberDuration in the same units. Floored to 1 px at render.
labelstring?Drawn inside the block when it fits (>= 36 px wide).
tone"accent" | "warning" | "success" | "error"?Semantic tone. Defaults to "accent".

Accessibility

  • The root is role="figure" with aria-labelledby pointing at the "GPU timeline" heading and aria-describedby at a visually-hidden aria-live="polite" summary that narrates stream count, event count, trace duration, and cursor position.
  • Stream labels live as SVG <text> inside the chart and as part of the live summary, so screen-reader users get the row structure even though the chart itself is role="presentation".
  • Tone is reinforced by the optional in-block label and the gutter row label, so colour is never the only signal — error and warning blocks read as distinct without the contrast.
  • Motion respects prefers-reduced-motion: reduce: blocks paint without the enter stagger and the cursor moves without a spring.

Credits

  • Extracted from: craftingattention (app/src/lessons/primitives/viz/GPUTimelineProfiler.tsx). The original was a three-preset (tiny / compute-bound / fused) teaching widget with an internal stage machine, an Auto-play / Next-stage controller, a fixed CPU/GPU/Transfer lane model, a side-by-side "5-dispatch vs fused" comparison row, ad-hoc summary metrics (Total / Compute % / Preset), and narration panels reading from a stage's narration field. The library extract is the pure Gantt primitive — streams in, blocks out, optional cursor through — so callers compose the presets, the comparison, the summary, and the narration around it. Sibling to RooflinePlotter, KVCacheViz, MemoryBudgetAllocator under ML Viz → Performance.