Skip to content

The graph, drawn

g.figure() # a plotly Figure, to show or to compose
g # in a notebook: the same figure, straight in the cell

A graph drawn from its declaration, before it has ever run

A graph can be drawn having never run — that one is the quickstart’s, and nothing has executed. Printing a declaration is not observing it, and this is the first of the three things observability was split into — the other two being the record of what happened and the diagnosis.

Every suffix is in it. worker1 and worker2 are the two dashed frames, which is what leaves this machine; cuda:0 · cached · frozen sits on the one node that carries all three; wave is the frame around what runs at the same time. None of that is a legend somebody maintains — it is the plan, laid out.

Not the list of edges. The plancompile then distribute — because that is where the decisions show: a Wave is what runs at once, a Remote is what crosses to another machine, and a bare list of edges says neither.

in the planon the figure
Executea box, filled by the device it runs on
Sequenceits children stacked, top to bottom
Waveits children side by side, inside a frame
Remotea frame labelled with the host
Emptyan empty figure that says so

The plan is a tree, so placing it needs no layout engine and no crossing heuristic. That is the payoff of compile decomposing rather than flattening.

The boxes say when; the arrows say what feeds what

Section titled “The boxes say when; the arrows say what feeds what”

A graph that is not series-parallel falls back to a flat Sequence, and there the nesting stops saying who feeds whom — the truth lives entirely in each step’s from. So the two channels are separated on purpose, and the N (a→c, a→d, b→d) is the case that keeps the figure honest. It is in the tests.

The N: four nodes with no series-parallel tree

There is no nesting to read here and the figure does not invent any: a and b sit in the same box because they run at the same time, and the arrows — not the box — say that c waits for a while d waits for both.

An edge that would cross a box it does not belong to is routed around it, one lane each. An arrow drawn over a node reads as an arrow into it.

The fill says where a node runs and nothing else. Cached, frozen and mapped are badges in the label, because three facts cannot share one fill.

Health gets a channel of its own — the outline turns red — precisely so the fill can go on saying where. On a graph spread over three machines, where does this run is the answer somebody came for.

The colours live in one table, somatize._theme, and are looked up with [] and never with .get(…, default) — so a typo fails rather than quietly coming out as the alarm colour. In the original the same colours lived in four tables keyed by the same strings.

That table is also what this site is painted from. A library whose graph is light and whose curves are dark is two libraries.

A node is often a whole architecture, and this node is unhealthy is not an answer when it is twenty layers:

from somatize.torch import architecture
g.figure(inside=architecture(g, x))

architecture traces what each node is made of — with fx where it can, because fx sees the operations that are not modules and a residual connection is exactly one; with a real forward where it cannot, saying so, because a residual that is missing looks exactly like a residual that is not there.

The unit is the node, since a node holding two modules composes them in its own forward.

g.figure(inside=...) draws a node’s box as a frame — the shape a Wave and a Remote already are — and lays the inside out by what feeds what, so a skip runs down a gutter and enters from the side.

Three nodes, each opened up

Three nodes and three architectures. The shapes are doing the work: a convolution is a parallelogram, a normalisation a capsule, a non-linearity is pointed, and the head’s two Linears taper because they narrow — 64 → 16 → 1, which is the only thing that makes a bottleneck a picture rather than a number you have to go and look up.

The rules that make an architecture readable

Section titled “The rules that make an architecture readable”

A kind, not a class name, decides the silhouette. A convolution is a parallelogram, a recurrent cell has a tab, an attention block has its corners cut and says what is in it, a normalisation is a capsule, a non-linearity is pointed, and anything that changes the width is tapered the way it goes. The kind is guessed by role, so a class the table has never heard of whose name ends in Norm is a normalisation — a guess, and a good one, because the alternative is calling half of everybody’s models other.

A composite everybody recognises is one box, and depth= opens it. Above, TransformerEncoderLayer ×4 is a single box that says what is in it. Asked to open:

The same stack with depth=2

Blocks that are the same block collapse to ×N — and when the block is more than one layer, that ×N goes on a frame around them rather than on each of them. That is the dashed frame: the count is said once and the block is said once. Four encoder layers written out would be eight boxes each saying ×4, which is the count said eight times and the block said none.

And depth= has a floor. It never opens an nn.MultiheadAttention, because there is nothing inside one to open to: torch runs it out of F.multi_head_attention_forward, which is handed four tensors and never calls out_proj — its only child. Opening it did not refine it, it deleted it, and depth=2 used to draw the encoder block’s norms and its feed-forward and no attention at all. The floor is measured rather than believed: a test hooks out_proj and watches it never fire, so whoever makes torch call it can delete the rule.

Written out by hand rather than taken from torch, the same block draws:

An attention block with its heads behind it

Identical lanes running at once get plates behind them, never separate boxes — the four plates stacked behind MultiheadAttention · 4 heads above. The heads of an attention block and the groups of a convolution are one projection in torch; four of them wired side by side would be a graph nobody built.

The shape is written on the layer, because that is the only thing that makes a bottleneck a picture. And every number says what it is4 batch · 16 steps · 24 dim, not 4×16×24. The batch is checked rather than assumed, since the caller knows how many rows went in, and a layer that did not change the shape keeps the names of the one that did — so a BatchNorm1d in a convolutional trunk says ch and len.

from somatize.health import overlaid
overlaid(g, store, run="tuesday", inside=architecture(g, x))

The audit’s scope and the drawing’s are then the same scope, which is what makes what is measured has a box true rather than hopeful — those two stop being the same set once fx has had its say, and a finding on a layer with no box lands nowhere.

Findings are coloured by family — numeric, signal, activation, step, capacity, data — with a legend of the ones actually on the figure. Six alarms that all look the same are one alarm.

from somatize.study import coordinates, importance, influence, table

importance is Spearman’s ρ, which the original names as fANOVA-deferred and never wrote. coordinates is hand-drawn out of splines, because plotly’s Parcoords only draws straight segments — it trades brushing for a trial reading as one curve.

In all three, pruned and finished trials are never ranked together.