.tgx Artifacts: Schema-Stable Experiment Packaging for GNN Research
Reproducibility in graph learning research depends on artifacts. A paper's headline number is just text; what makes it reproducible is the bundle of files that lets someone else load your graph, your model, your configuration, and your random seed, and rerun the experiment. The format of those artifacts matters more than people give it credit for.
Standard graph interchange formats — GraphML, GEXF, edge lists, adjacency matrices — were designed for classical graph analytics. They handle node IDs, edge weights, and simple attribute dictionaries. They do not handle tensors. A rank-4 node feature (an image patch, say) cannot be expressed in GraphML without inventing a custom serialization. TGraphX uses a native format, .tgx, that handles tensor features and the surrounding experiment metadata.
This note covers what .tgx is, when it matters, and how it fits into reproducible research workflows.
What .tgx is
A .tgx file is a torch.save() bundle containing a graph object and its metadata. Internally:
import tgraphx as tgx
g = tgx.Graph(x=node_features, edge_index=edge_index, labels=labels)
g.save("my_graph.tgx")
# Inspect (for documentation only — do not write code against the internal format)
import torch
bundle = torch.load("my_graph.tgx", weights_only=False)
print(bundle.keys())
# dict_keys(['node_features', 'edge_index', 'edge_features', 'node_labels',
# 'graph_label', 'metadata', 'format_version'])
The format is just a versioned dictionary of PyTorch tensors and Python primitives. It preserves dtype, shape, and tensor metadata that standard graph formats cannot.
Loading is symmetric:
g = tgx.Graph.load("my_graph.tgx")
print(g.node_features.shape) # restored with original rank and dtype
What it preserves that GraphML cannot
- Rank-4+ tensor features. GraphML can store node attributes as strings; serializing a
[3, 8, 8]patch as a string-encoded float list is technically possible but unwieldy and lossy. - Tensor dtype. GraphML attributes are typed (int, float, string, boolean). Complex dtypes (float16, complex64) require workarounds.
- Edge tensor features. Edges with rank-2+ features (e.g., spatial relationships as matrices) cannot be expressed naturally in standard formats.
- Heterogeneous node types. With node-type-specific feature shapes, multi-modal graphs need a richer container than GraphML provides.
- Native PyTorch device/grad metadata. Tensors saved with
.tgxround-trip through PyTorch correctly.
Knowledge graphs
For KGs:
import tgraphx as tgx
kg = tgx.KnowledgeGraph.from_triples(triples)
kg.save("my_kg.tgx")
kg2 = tgx.KnowledgeGraph.load("my_kg.tgx")
This preserves entity features (including multi-modal tensor features), relation embeddings, triple data, and any KG-specific metadata.
What it does NOT preserve
.tgx is graph data only. It does not bundle:
- The trained model. Save the model separately with
torch.save(model.state_dict(), ...). - The training hyperparameters. Use
experiment_config.json. - The training history. Use
metrics.csv. - The system fingerprint. Use
reproducibility_report.json.
A complete reproducible experiment is typically a directory containing:
runs/exp_001/
├── graph.tgx ← the graph data
├── model.pt ← the trained model state dict
├── experiment_config.json ← hyperparameters
├── metrics.csv ← training metrics per epoch
├── reproducibility_report.json ← system + seed fingerprint
└── benchmark_results.json ← aggregate results (optional)
The framework's experiment runner produces this layout automatically. If you write your own training loop, replicate the pattern manually.
Why this matters for reproducibility
The pattern in most published GNN research:
- Graph data is loaded from a custom CSV or per-paper format with non-standard preprocessing.
- Model code is in a repo branch that may have moved since publication.
- Hyperparameters are in a YAML file or comments in a script.
- Random seeds may or may not be documented.
- Reproducing the paper requires extracting these from multiple sources, often with missing pieces.
The improvement:
- Graph data is in a single
.tgxfile that round-trips throughtgx.Graph.load(). - Trained model checkpoint is alongside it.
- Hyperparameters are in
experiment_config.jsonin the same directory. - Seeds and system info are in
reproducibility_report.json.
Anyone can download the run directory and inspect or rerun. The format is self-describing and the layout is conventional.
Practical workflow
For each experiment:
- Construct your graph:
g = tgx.Graph(...). - Save it:
g.save(run_dir / "graph.tgx"). - Train and save the model:
torch.save(model.state_dict(), run_dir / "model.pt"). - Save config and metrics via the framework's tracking writers.
For sharing:
- Audit the run:
tgx.audit_run_dir(run_dir). - If complete, the directory is a self-contained artifact bundle.
For publication, this directory is the supplementary material.
A real example
import torch
import tgraphx as tgx
from pathlib import Path
run_dir = Path("runs/cifar10_patch_v3")
run_dir.mkdir(parents=True, exist_ok=True)
with tgx.reproducible(seed=42, deterministic=True):
# Load and construct graph
dataset = tgx.load_dataset("cifar10_patch", download=True, patch_size=8)
g = ... # build combined graph from dataset
tgx.validate_graph(g, strict=True)
g.save(run_dir / "graph.tgx")
# Train
result = tgx.classify_nodes(
x=g.node_features, edge_index=g.edge_index, labels=g.node_labels,
model="tensor_gcn", seed=42,
)
torch.save(result.model.state_dict(), run_dir / "model.pt")
# Audit
print(tgx.audit_run_dir(run_dir))
The run directory is now a complete experiment bundle. Someone with the same TGraphX version and the same dataset can reload everything and rerun.
Honest limitations
.tgxis TGraphX-specific. To share a graph with someone using PyG, you need to either install TGraphX (read-only is fine) or convert to a PyG-compatible format manually.- Backward compatibility across major versions is not guaranteed. The format version field is in every bundle to help with migrations.
- Very large graphs (>1GB tensor data) load slowly. Consider sharding for graphs of that size.
When .tgx is overkill
For graphs with flat-vector node features and simple structure, GraphML or an edge list is fine. .tgx matters specifically when:
- Features have rank ≥ 3.
- Features have non-standard dtype.
- You want a single file that round-trips through TGraphX exactly.
For simple graphs, GraphML is the more interoperable choice.
Bottom line
.tgx is the right format for TGraphX experiments that use tensor-valued features. It is not a competing standard to GraphML — they serve different needs. The framework's combination of .tgx plus the conventional run-directory layout is what makes reproducibility easier than it usually is in GNN research.
FAQ
Q: Is the format documented for third-party readers?
A: The format version is in every bundle; format details are in docs/io.md. Practically, you should read via tgx.Graph.load() rather than parsing the bundle directly.
Q: How does .tgx compare to PyG's Data.save()?
A: Both are PyTorch-based serialization. PyG's Data is rank-2 node features by default; .tgx is rank-any. For tensor-valued features, .tgx is the right choice.
Q: Can I compress .tgx files?
A: Yes. Gzip works fine: gzip my_graph.tgx. The TGraphX loader does not handle compression automatically; decompress before loading.
Q: What about cross-version compatibility?
A: The format_version field allows reads of older formats. Major changes are documented in the changelog.
Q: How do I convert from GraphML to .tgx?
A: Load with NetworkX, convert via tgx.make_graph(networkx_graph=G), then save with g.save().