The Netron export¶
tl.export.netron turns a finished Trace into a single JSON file the
Netron viewer opens fully lit:
typed shapes on every edge (Netron's oldest open ask, #71), the module
hierarchy as one-click drill-down boxes, a curated properties panel, and a
metrics companion (written by default) rendered in Netron's own sidebar
sections. Every
keyword name below is experimental and may change in any release.
import torchlens as tl
log = tl.trace(model, x)
tl.export.netron(log, "model.json") # module view (default)
tl.export.netron(log, "model.json", granularity="op") # every leaf op
tl.export.netron(log, "model.json", attachment=False) # no metrics companion
tl.export.netron(log, "model.json", baseline=ref_log) # + per-node diff facts
tl.export.netron(log, serve=True) # serve + browser tab
The artifact is valid ONNX protobuf JSON (irVersion 10): it strict-parses
into onnx.ModelProto and passes onnx.checker.check_model(full_check=True).
It is deliberately not a runnable model — ops keep their captured
TorchLens names under the custom ai.torchlens.lossy domain, module calls
become ai.torchlens.module FunctionProtos, and the disclaimer plus a full
honesty block (torchlens.capture_outcome, counts, buffer policy,
intervention marks) ride metadataProps. Node names stay verbatim trace
labels: log["conv2d_1_1"] is the way back from any box to the live API.
Granularities¶
"module"(default): per-call FunctionProtos grouped at module depth 1 — the architecture-diagram view; deeper structure nests recursively behind the drill-down icon; single-op modules are inlined rather than drawn as one-box rooms.depth=overrides the grouping level and is recorded in the metadata."op": every leaf op flat. Real transformers render as a rope at this granularity (an enriched flat GPT-2 measures 26 screens tall); the export warns past the ~27-rank legibility budget and names the remedy."rolled": repeated passes merged by rolled identity. An edge is typed only when every pass agrees on shape and dtype; otherwiseshape_variants/dtype_variantsdisclose the drift and the edge stays untyped. Recurrent feedback is disclosed via therecurrenceattribute — a drawn feedback edge would be a dataflow cycle the ONNX checker rejects.
Buffer density¶
show_buffers="never"|"meaningful"|"always" is TorchLens's existing
tri-state policy. The default meaningful also drops counter-update chains
(the zero-dim add ops feeding num_batches_tracked in train mode) and
disclosures ride the owning module's docString plus the model metadata
(torchlens.hidden_buffer_count / torchlens.hidden_op_count); always
is the forensic escape hatch. Trace under model.eval() for inspection
artifacts — it drops the twenty train-mode counter ops natively.
The metrics companion¶
Every file export also writes <stem>.attachment.json beside the artifact
(attachment=None, the default): Netron's own netron:attachment sidecar,
with per-node observed durations, estimated FLOPs, and captured-tensor
bytes that render in dedicated "Metadata" and "Metrics" sidebar sections
after you drop the companion onto the open model. The main artifact stays
self-contained; the companion is additive depth. attachment=False opts
out; attachment=True demands it and refuses the memory-only serve
(path=None), which has no file for a companion to sit beside (the default
skips it there). The writer refuses (code netron_attachment_invalid) any
row Netron would silently drop.
Diffing two captures¶
baseline= takes the reference Trace and fills the torchlens.baseline
slot from TorchLens' own guarded differential
(torchlens.report.diff(log, baseline), log - baseline).
Netron has no diff view, so the facts ride the channels it renders: each
exported node whose ops settled into a diff row carries torchlens.diff.*
rows in its node metadata and in the companion (status, max_abs_delta,
reference_label, verdict, detail; a module-call or rolled node carries
row_counts over its ops and the largest delta among them). A node with no
settled row carries no diff facts, never a zero. The model-level
torchlens.baseline row holds the JSON summary: direction, row counts over
the closed added|removed|unresolved|excluded_machinery|unreachable|zero|changed
vocabulary, and the reference-only (removed) labels the subject graph
cannot draw. A string baseline= is recorded verbatim as an identifier,
with no per-node facts.
Opening it¶
- File: open in the Netron desktop app or at
netron.app— no extra needed. serve=True: serves through the netron package (loopback, ephemeral port) and opens a browser tab or notebook widget; needspip install 'torchlens[netron]'(refusal codenetron_serve_unavailableteaches this). Withpath=Nonethe artifact serves from memory.
Reading the render¶
Nodes draw in Netron's default dark node style (charcoal with white text) —
the same deliberate style Netron uses for its own torch.export graphs;
TorchLens ops are custom-domain by design (honesty beats fake category
colours), so structure, typed edges, and density carry the visual load.
Shape labels cost about half again as much height as an unlabeled graph and
are worth it; labels longer than 16 characters (5-D shapes and beyond)
truthfully fall back to the sidebar. The module drill-down affordance is
the small "ƒ" glyph at the right edge of a module box's header — it is only
6x12 px, so know to look for it; drill-down is push navigation with a
breadcrumb, not inline expansion.
Wording contract¶
The properties panel is part of the accuracy contract: durations are
observed, FLOPs are estimates, activation bytes are captured-tensor
bytes, module totals say inclusive, and a fact that does not apply is
absent, never zero. The module-path attribute is deliberately not named
module — Netron already labels the ONNX domain "module" in the sidebar.
Credit¶
The rendering, the ONNX JSON readers, function-definition navigation, the
input_names port hook, the serve/widget grammar, and the attachment
Metadata/Metrics design are Lutz Roeder's Netron. TorchLens contributes
observed execution facts Netron cannot infer; it does not present Netron's
visual grammar as its own. The demand record this design answers lives in
Netron issues #65, #71, #275, #1234, #1240, #1241, #1346, #1358, #1369,
1370, #1480, #1544, and #1596.¶
Upstream asks (filed, expected to land never)¶
- A sidecar/
?attachment=convention innetron.serve()so the metrics channel needs no manual drop. - A larger or labelled function-drill-down affordance.
ai.torchlens.*category rows inonnx-metadata.json(lowest priority; colour re-domaining is rejected on measured evidence — it buys a dark-on-dark tint shift and forfeits the checker gate).