Inference Atlas · documentation

Reading the charts

docs/tui/charts.md Last pushed 7 September 2026

Everything is drawn with text. No images, no sixel, no escape-code graphics protocol — so it works over ssh, inside tmux, in the VS Code terminal, and in a CI log.

How the plots are drawn

Line charts use braille characters. Each terminal cell is a 2 × 4 dot matrix, so a 40 × 10 character plot is really an 80 × 40 pixel canvas — enough resolution for a curve to have a shape rather than a staircase.

  550┤                                 ⣠⣶⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣶⣶│
     │                              ⣀⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿├20k
  500┤                           ⢀⣴⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿├15k
     └┬──┬────┬─────────┬────────────────────┬────────────────────────────┬
      1  2    4         8                   16                           32

The area under a curve is filled. A bare polyline across a wide panel reads as scattered dots; a filled body reads as a magnitude. The measured points themselves are always drawn, because they are the data — the line between them is a reading aid.

Two axes, one plot. Throughput (cobalt, left, filled) and latency (orange, right) share the plot area with independent scales, marked on the left and on the right. Stacking them as two short charts wasted the vertical space and made them hard to correlate.

The x axis labels what was actually run. A concurrency sweep of 1, 2, 4, 8, 16, 32 is labelled 1, 2, 4, 8, 16, 32 — not rounded to 0, 17, 34, and not padded out to −1. Inventing tick values between measured levels describes a measurement nobody made.

A y label is never repeated. If two ticks would both format to 20k, the second is dropped instead of drawing three identical rows.

Latency distribution

TTFT percentiles as bars from zero, with the colour deepening cobalt → orange from p50 to p99:

TTFT p50 ████████████████████████████████                       1,005 ms
TTFT p90 ████████████████████████████████████████               1,245 ms
TTFT p95 ██████████████████████████████████████████             1,295 ms
TTFT p99 ███████████████████████████████████████████            1,333 ms
         0                                            1,333 ms

Bars are drawn to eighth-of-a-character precision, so small differences are visible. When the tail is tight all four bars are nearly the same length — that is the honest picture, and the colour ramp is what keeps the rows distinguishable.

The line underneath is the scale: zero on the left, the maximum on the right.

Requests

Every request in the run as one column, tallest = slowest, three rows deep:

        ▃    ▃▄▆█▆▅█▅▇▅▆▆▄▄▄▃▅▄▄▅▅▄▄▅▄▆▅▄▃▄
   ▁▂▂▂▂▁▁ ▂▃▂▁▂▁▁▁  █▃▂▂▅██████████████████████████
▆▆▆▆▆▅▆▆▆▆▆▆▅▆▆▆▇▆▆▇▇▇▆▆▆▇▆▇█▇▇█▆▇▇███████████████████
first                                    last · peak 52,135 ms

Time runs left to right across the whole run. The series is resampled to the panel width — averaged down when there are more requests than columns, repeated up when there are fewer — so a 400-request sweep shows all of its arms and a 20-request run still fills the panel. Colour follows the latency ramp; a column containing a failed request is red.

This is where you see a run warm up, a queue build as concurrency rises, or one call that hung.

Sweep curves

For sweep and long-context runs: throughput and latency over the swept variable.

  • cobalt, filled, left axis — tokens per second
  • orange, right axis — TTFT
  • x is concurrency, or input tokens on a log₂ scale for context sweeps

The classic shape is throughput climbing then flattening while latency climbs steadily — the knee is where more concurrency stops buying you anything.

Pareto

A scatter of every serving run: x is TTFT p50 (log scale — the range runs from milliseconds to minutes), y is output tok/s. The frontier is drawn bright and connected; everything else is a dim dot. The selected point is .

A point far below the frontier is beaten by another configuration on both axes at once.

Coverage

Model × hardware, one coloured cell per square, brightness rising with the number of runs:

                        1  2
…seek-v4-flash-0731-spark   ███
        Qwen/Qwen3.6-35B-A3B   ███
            Qwen/Qwen3.8-27B   ███
       google/gemma-4-E2B-it ██████

Blank means nobody has measured it. In this atlas that is the interesting signal, not missing UI. The numbered legend underneath maps columns to hardware ids.

Colour

Three levels, detected automatically:

Level When What you get
truecolor COLORTERM=truecolor or 24bit, or a terminal known to support it (kitty, ghostty, iTerm, WezTerm, Alacritty) The full palette, exactly as designed
256 anything else Nearest match from the xterm-256 cube or greyscale ramp
mono NO_COLOR set, or TERM=dumb, or no TERM No escape codes at all — heatmaps fall back to block characters that encode presence by shape

Force one in the config:

[ui]
color = "mono"     # auto | truecolor | 256 | mono

NO_COLOR is honoured before everything else, including an explicit truecolor setting.

Palette

Colour Used for
cobalt #5b8cff throughput, selection, panel titles, the primary series
signal orange #f97316 latency — the counterpart series
green #22c55e ✓ recommended, the active target
amber #f59e0b ! tight, warning gotchas
red #ef4444 ✗ won't fit, failed requests, blocker gotchas
grey #8b93a7 secondary text, notes, absent values

Latency ramps run cobalt → violet → orange, so "slow" is always warmer than "fast".

If the charts look wrong

Boxes or question marks instead of mean your font lacks braille glyphs — rare, but switching to a programming font (JetBrains Mono, Fira Code, Menlo, DejaVu Sans Mono) fixes it. See Troubleshooting.

Every number here was measured. Open an issue if one looks wrong.

All documentation