Serializing Tensor Graphs: save_tgraphx, load_tgraphx, and GraphML Round-Trips
A reproducible experiment needs to save its inputs exactly. For a tensor-valued graph that is harder than it sounds: most graph file formats were designed for nodes with a handful of scalar attributes, not nodes that are [3, 64, 64] feature maps. TGraphX takes a two-format stance in tgraphx/ux/serialization.py and tgraphx/io/graphml.py: a lossless native bundle for exact persistence, and a GraphML round-trip for interoperability where you accept some loss. Knowing which to use — and the safety note attached to the native one — keeps your artifacts trustworthy.
This is the persistence half of the reproducibility story told in GNN Research Reproducibility and the artifact discussion in Schema-Stable Experiment Packaging.
The native format: .tgx
save_tgraphx(obj, path) writes a Graph or KnowledgeGraph to a .tgx file, which the source describes as a single torch.save bundle. Because it uses torch.save, tensor features of any rank are preserved exactly — shapes, dtypes, and all — along with edges, labels, and the user metadata dict (including the masks that live there). Round-tripping is symmetric:
import tgraphx as tgx
tgx.save_tgraphx(g, "experiment_graph.tgx")
g2 = tgx.load_tgraphx("experiment_graph.tgx") # map_location="cpu" by default
| Property | .tgx (native) |
GraphML |
|---|---|---|
| Tensor shape preserved | yes, exactly | no — see caveat |
| Carries metadata/masks | yes | limited |
| Cross-tool interop | TGraphX only | broad (NetworkX, Gephi, …) |
| Backing | torch.save bundle |
XML text |
load_tgraphx defaults map_location="cpu" so a graph saved on a GPU machine loads on a laptop without surprises.
The safety note you must not skip
.tgx bundles are pickled by torch.save, and because they include arbitrary user metadata the loader uses torch.load(..., weights_only=False) — weights_only=True would reject the metadata. The practical consequence is the standard PyTorch one, stated plainly in the source: only load .tgx files from a source you trust. A maliciously crafted pickle can execute code on load. The loader exposes a trust_source parameter to make this an explicit, conscious decision. For your own experiment outputs this is a non-issue; for files downloaded from strangers it is exactly the warning the torch.load docs give, and TGraphX surfaces it rather than hiding it.
GraphML: interop with honest limits
For exchanging graph structure with the wider ecosystem, tgraphx/io/graphml.py provides write_graphml and read_graphml. GraphML is plain XML that tools like NetworkX and Gephi understand, which makes it ideal for sharing topology and small scalar attributes. The honest limitation is in the module's own documentation: GraphML cannot express arbitrary tensor shapes safely. The package therefore treats GraphML as a structure-and-scalars interchange format, and a dedicated example notebook in the public gallery walks through the tensor-semantics warning so you do not silently lose feature maps. The rule of thumb: GraphML for structure you want other tools to read; .tgx for the full tensor-valued graph you want to reload faithfully.
A reproducibility workflow
A clean pattern for a research run:
- Build and validate the graph (
tgx.validate_graph(g, strict=True)). tgx.save_tgraphx(g, run_dir / "input_graph.tgx")so the exact input is recoverable.- Train, writing run metadata alongside (covered in the reproducibility-context article).
- To reload and reproduce,
tgx.load_tgraphx(...)returns the identical tensor graph — same shapes, same masks.
Because the bundle preserves the metadata['masks'] split, you do not risk re-deriving a different train/test partition on reload, which is a subtle but real source of irreproducible results.
Honest framing
Three caveats keep this accurate. The .tgx format is TGraphX-specific; it is not a portable interchange standard, and you should keep the package version that wrote it noted in your run metadata. GraphML interop is intentionally lossy for tensor features — do not expect a [C, H, W] node to survive a GraphML round-trip intact. And the weights_only=False requirement is a genuine security consideration, not a formality: treat untrusted .tgx files exactly as you would any pickle.
.tgx versus a model checkpoint
It is worth separating two things that both "save state." save_tgraphx persists a graph — the node and edge tensors, the labels, and the metadata dict. A model checkpoint persists weights, and TGraphX has separate utilities for that: save_checkpoint and load_checkpoint in the training module. A reproducible run typically writes both — the .tgx input graph and a checkpoint of the trained model — plus the run metadata that ties them together. Conflating them is a common mistake: reloading a checkpoint does not restore the graph it was trained on, and reloading a .tgx does not restore the model. Keep them as distinct artifacts with names that say which is which.
One versioning note follows from the format choice. Because .tgx is a torch.save bundle, record the TGraphX and PyTorch versions that wrote it in your run metadata. The loader is backward-compatible with older payloads, but knowing the writing version makes any future migration straightforward and lets you reproduce a result against the exact code that produced it.
Related guides
- Schema-Stable Experiment Packaging with .tgx Artifacts
- Research Reproducibility in GNN Projects
- The TGraphX Graph and GraphBatch: Storage and Batching
Conclusion
TGraphX serialization is two tools for two jobs: save_tgraphx/load_tgraphx write a lossless .tgx bundle that preserves any-rank tensors and metadata (mind the trusted-source rule), while write_graphml/read_graphml exchange structure with the broader ecosystem at the cost of tensor fidelity. Pick the format by intent — faithful reload versus cross-tool interop — and your saved graphs will mean the same thing tomorrow as they do today.