Continuous Batching Viz
A timeline of continuous batching in LLM serving. Every request occupies one horizontal track of token cells starting at its arrivalTime; as the cursor advances tick-by-tick, cells fill left-to-right. The moment a request's last cell fills, the row dims — the slot is free and the next arrival takes over without waiting for the rest of the batch.
Continuous batching at tick 0 of 13. 0 in-flight, 0 completed, 6 total.
Continuous batchingt=00 / 13
- r00/4
- r10/7
- r20/3
- r30/6
- r40/5
- r50/4
Customize
Playback
320ms / tick
0
Installation
npx shadcn@latest add https://craftbits.dev/r/continuous-batching-viz.jsonUsage
import { ContinuousBatchingViz } from "@craft-bits/core";
const requests = [
{ id: "r0", arrivalTime: 0, numTokens: 4 },
{ id: "r1", arrivalTime: 0, numTokens: 7 },
{ id: "r2", arrivalTime: 2, numTokens: 3 },
{ id: "r3", arrivalTime: 4, numTokens: 6 },
];
<ContinuousBatchingViz requests={requests} playing playSpeed={320} />Drive the cursor from outside (scrubbing, narration, MDX prose hover):
<ContinuousBatchingViz
requests={requests}
currentTime={t}
onCurrentTimeChange={setT}
/>Render a static snapshot at a specific tick:
<ContinuousBatchingViz
requests={requests}
defaultCurrentTime={6}
playing={false}
/>Understanding the component
- One row per request. Each row is a flex line with a label, a grid of
maxTimecells, and afilled / totalreadout. Rows preserve their position across the trace — the component never reorders them when a request finishes. - Cells fill as the cursor passes. A cell at column
ibelongs to a request ifarrivalTime <= i < arrivalTime + numTokens. It becomes filled oncecurrentTime > i. Fill is amotion.spanwhoseopacityandscaleYanimate viaSPRINGS.snap. - Completed rows dim. As soon as
filled === numTokens, the row's accent fill drops tobg-cb-accent/40and the label desaturates — visually freeing that slot. Static batching would keep the row at full opacity until the longest request in the batch finishes; continuous batching releases each row independently. - Time axis underneath. The component renders sparse tick labels every
ceil(maxTime / 10)cells in tabular-nums monospace, so the column positions of arrivals are readable at a glance. - Controlled, uncontrolled, or autoplay.
currentTimefollows the Radix value / defaultValue pattern. Whenplayingis true, an interval advances the cursor by one tick everyplaySpeedms and loops back to 0 at the end of the trace. - Reduced motion.
prefers-reduced-motion: reducecollapses every spring toduration: 0and clamps the cursor tomaxTimeso screen-reader users hit the final state immediately.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
requests | readonly ContinuousBatchingVizRequest[] | — | Required. Each item has { id, arrivalTime, numTokens }. |
currentTime | number | — | Controlled cursor tick. |
defaultCurrentTime | number | 0 | Uncontrolled initial tick. |
onCurrentTimeChange | (t: number) => void | — | Fires on autoplay tick or scrub. |
playing | boolean | false | When true, autoplay advances the cursor at playSpeed. |
playSpeed | number | 400 | Milliseconds per tick during autoplay. Minimum 40 ms. |
transition | Transition | SPRINGS.snap | Spring used for cell-fill transitions. |
className | string | — | Merged onto the root via cn(). |
Accessibility
- The figure is
role="figure"with a hiddenaria-live="polite"summary that announcest,maxTime, in-flight count, completed count, and total request count — screen readers hear the progression on every tick. - Each row is a
<li>with its ownaria-labelof the form"Request <id>: <filled> of <total> tokens generated."so individual request progress is accessible without the visual fills. - Color is never the only signal — every row carries a textual
filled / totalreadout, and completed rows desaturate both their label and the readout. - Motion respects
prefers-reduced-motion: reduce: every spring collapses to instant and the cursor jumps to the end of the trace.
Credits
- Extracted from:
craftingattention(app/src/lessons/primitives/viz/ContinuousBatchingViz.tsx). The source paired the timeline with a side-by-side static-vs-continuous comparison, a per-mode harness (Explore / Predict / Challenge) with bookmarks and undo / redo viauseWidgetHistory, queue chips for waiting requests, GPU-utilization badges, score dots, and reveal-style narration. The library extract is the pure timeline primitive — requests in, animated token cells out — driven entirely by props with controlled / uncontrolled and play / pause APIs, so callers compose the comparison, the modes, and the narration around it.