Skip to content

One record, one line, one voice: TorchLens value displays

Every user-facing object answers a REPL in one voice (the names are experimental and may change in any release):

  • Value-record repr(x) is one nestable line, up to 100 columns: kind, stored short key, dtype, shape, device, and available value words.
  • Value-record str(x) is a bounded card with at most 10 rows below the first line. Its first line is the repr, and its more row names the exits.
  • Record HTML cards contain those same rows, with no automatic value grid.
  • A Trace str(x) is a bounded card with at most twelve rows below the first line, which is the repr; the module tree stays in .summary().
  • Containers point, records show: collection reprs are one-line composition cards (LayerAccessor: 553 layers (539 ops, 14 buffers)), with at most five members and .head(), .search() and .to_pandas() exits in str().

The explicit glance core line

<dtype><shape>@<device>  n=<count>  (<bytes>)  <distribution>  <health>  <evidence>

Generated by the shipped formatter (every specimen in this page is real formatter output, never hand-written):

A: f32[1,64,112,112]@cpu n=803k (3.06 MiB) [-6.000 %%#%###### 7.714] mean=0.8569 sd=4.000 zero=1.03%
U: f32[1,64,112,112]@cpu n=803k (3.06 MiB) [-6.000 <unicode ramp> 7.714] mean=0.8569 sd=4.000 zero=1.03%

ASCII is canonical; the unicode upgrade differs only through the declared nine-glyph ramp, and ascii == degrade(unicode) is a byte-checked CI contract. Extremes, the NaN/Inf census, and zero counts are EXACT at every size; only the sd and histogram may sample above declared gates, and a sampled family carries the ~ mark with disclosed precision.

Value records

A captured op renders:

Op relu_1: float32 [8] on cpu, min 0.000 max 0.5000 mean 0.1429 sd 0.1821

The card behind it (print(op)) adds position, module, output, values, graph, function, source, and code rows, then the more exits. The source row is a captured location. The code row reads fingerprint-checked source text, or names a changed or missing source file. Counts in card rows are exact and byte quantities use IEC units (60.0 KiB). Durations use compact units (4.02 ms); Flops and Macs keep their spelling.

The value words use four significant digits, including trailing zeros. If the line exceeds 100 columns, it drops mean and sd first, then min and max in favor of values in print(). Geometry and honesty stay visible. The explicit tl.stats.glance form retains its sparkline and its ASCII and Unicode equivalence contract.

Honesty vocabulary (never laundered)

(not saved) distinguishes an absent capture from saved-and-boring; (freed), (disk-backed: metadata only until read), (preview backend: shape/dtype only), and (edited) retain their meanings. Value words preserve nonfinite counts, magnitude-based complex statistics, and (~sampled) even when sampled moments no longer fit.

A saved CPU activation below the existing sampling gate is summarized once and cached by tensor version. Live parameters, device tensors, and larger activations show values not computed unless valid cached evidence already exists. Parameter cards disclose the live basis and version. Buffers disclose their capture-snapshot basis. Displays never load a lazy payload to produce a summary.

Multi-pass layers identify their pass count without a pooled statistic; the card shows up to three per-pass summaries. An individual operation uses its stored short key and pass suffix, such as tanh_1:2.

What no repr library can say (the forensics exhibit)

Real output from a poisoned capture (a 0/0 minted mid-network):

>>> log[bad]
Op truediv_1: float32 [1, 8] on cpu NaN 3, +Inf 5, no finite values
>>> log.first_nonfinite()
First non-finite saved out is in layer truediv_1_4 (op __truediv__, ...), shape=(1, 8),
dtype=torch.float32, parents=relu_1_2, sub_1_3, source=<file>:14.
>>> _ = log.health_facts
>>> log
Trace _poisoned_1: _Poisoned, 7 layers, 5 ops, NaN/Inf found (see .nonfinite_ops)

The flag says there was a crime; TorchLens hands you the crime scene -- in the same one-line language as the health report. Trace rendering reads cached health evidence and never scans tensors. Until health has been checked it says NaN/Inf not checked. A clean, fully checked capture carries the per-trace proof no NaN/Inf (exact).

The graph-scale table

trace.summary(view="stats") renders every retained site through the same kernel: graph-ordered rows, typed row states (ok / unsaved / disk_backed / unsupported / unscanned), = parent marks backed by a capture-path-stable distribution verdict (function semantics + geometry; payload-pointer comparison is banned), a max_scan_elements= element budget echoed in the header, and disclosed sampled-column sorts. "Sortable, exact where it says exact."

The function form

torchlens.stats.glance(t) renders the core line for ANY tensor -- the function, never a monkey-patch (lovely-tensors remains installable alongside TorchLens). A glanced line is a documented strict subset: no envelope, no deviation marks, no graph-proven role, and geometry-seeded sampling. torchlens.stats.narrate(site, sink=print) mounts the same line as a live per-firing stream on the capture hook surface; summary_cell / treescope_card_fields are the seam adapters the summary panel and HTML cards consume, so a number can never exist only in HTML.

The same stance holds for pictures: tl.viz.rgb(t) and tl.viz.chans(t) draw a bare tensor as an RGB image or a channel grid, as functions rather than tensor attributes (Look at a tensor as an image).

Selections, edits, options and reports

Selections report sites and values separately and name the selector that produced them. For a five-token GPT-2 small capture:

Selection ACT: 12 sites, 184.3k values, from tl.func('tanh')

print(selection) puts the first three site names and the number omitted on one sites row, followed by the attributes for further inspection. A dense mask says values not computed rather than scanning its elements while printing.

Selectors retain parentheses so their constructor spelling rebuilds the same boolean expression. Edits retain their public constructor spelling; tensor arguments use shape, dtype and device without printing elements:

tl.scale(2.0)
tl.project_out(<tensor float32 [4] on cpu>)
tl.when(tl.func('relu'), tl.zero_ablate())
Recipe: 2 rules

A recipe uses tl.when(...) only when that spelling rebuilds it exactly. Otherwise its repr reports the number of rules; print(recipe) lists at most five rules. Captured arguments and frozen intervention specs also show metadata and counts rather than tensor contents or callback dumps. Captured argument cards show parent display keys and scalar literals, including literals nested in tuples or lists.

Options show non-default fields in repr. Printing an options object lists its public fields with non-None values, including defaults. Boolean card values use words, rows align to the longest field name, and a closing more row points to details:

CaptureOptions()
CaptureOptions(save_grads=True, aten_ops=True)

Doctor, health and compute report reprs show their type, scope and row or finding counts. Summary tables retain their headers and totals, use plain numeric percentages, and explain every displayed marker in one legend. Lookup errors keep the problem first and offer at most five display keys, closest by edit distance first:

Layer 'tanhh_6' not found.
  candidates 'tanh_6', 'tanh_1', 'tanh_2', 'tanh_9', 'tanh_11'; and 5 more
  remedy     pass an in-range integer, a layer label, or a module address

Credits

The philosophy this surface mines and extends:

  • lovely-tensors (xl0) -- a tensor's default view should be a human summary, not a wall of digits: the inline sparkline, extremes bracketing the distribution, count and memory on the line, all_zeros/empty special cases, omitting the ordinary, dot-verb ergonomics, and repr-as-hot-path cost discipline.
  • torchsnooper (zasdfgbnm) -- the compact fixed-order form that stays scannable when repeated; dtype and device as the bug-catching fields.
  • penzai / treescope -- repr-is-the-product; NaN-as-pattern; collapsed-first rendering.
  • flax tabulate (totals conservation), Keras 3 (footer authority), pysnooper normalize=True (diffable summaries).

Trace and owned record collections

print(trace) shows at most twelve rows below its identity line. The card contains stored model, capture, graph and save metadata; trace.summary() provides the module hierarchy. Default pristine state is omitted.

ATen and event tuples keep tuple iteration, equality, hashing and slicing. Their reprs show only counts, for example AtenOps: 500 calls. Printing adds at most five identified members, the number omitted, and a closing more row. An ATen card puts each of its first three tensor inputs on a separate row, followed by the number omitted. Portable saves encode these sequences as ordinary tuples, so their display type does not change the artifact format.