TGraphX Insights Building a Shape-Checked GNN Experiment Skeleton with TGraphX
← Back to Insights

Building a Shape-Checked GNN Experiment Skeleton with TGraphX

Target keyword: shape checked GNN experiment

Building a Shape-Checked GNN Experiment Skeleton with TGraphX

A good experiment skeleton does three things before the first forward pass
runs: it records the environment, seeds randomness, validates the data
structure, and puts the model on the right device. This tutorial builds a
concrete, working skeleton using TGraphX's utilities, layer stack, and logging
infrastructure. Every step is grounded in the library's actual API.

Why a Skeleton Matters

Ad-hoc experiment scripts accumulate technical debt quickly. They lack
reproducibility (no seed), lack introspection (no env_report), and fail
mysteriously when moved to a different machine or batch size. A skeleton is a
starting template that enforces good practices from the first commit.

Step 1: Environment Report

Before anything else, record what you're running on:

python
from tgraphx.performance import env_report
        
        info = env_report(include_hardware=True)
        print(info)
        # Returns: python version, PyTorch version, TGraphX version,
        #          CUDA availability, GPU name if present
        

This call is cheap and should be the first line of any experiment script.
Store the output in your experiment log so you can reproduce conditions later.

Step 2: Reproducibility

python
from tgraphx.reproducibility import set_seed
        
        set_seed(42, deterministic=True)
        # Sets Python random, NumPy random, and PyTorch random seeds.
        # deterministic=True enables PyTorch's deterministic algorithm mode.
        # Note: deterministic mode may reduce throughput.
        

set_seed is available from Beta v0.4.1. Call it before constructing data,
before building the model, and before the training loop.

Step 3: Device Selection

python
from tgraphx.performance import recommended_device
        import torch
        
        device = recommended_device()
        # Returns 'cuda' if torch.cuda.is_available(), else 'cpu'
        print(f"Using device: {device}")
        

Step 4: Graph Construction with Validation

Construct your Graph object with explicit shape assertions. The constructor
validates shapes and devices, but adding your own assertions documents your
intent:

python
import torch
        from tgraphx import Graph
        from tgraphx.graph_builders import build_knn_graph
        
        # Example: 64 nodes, each with a [32]-dim feature vector
        N = 64
        node_features = torch.randn(N, 32)        # [N, d]
        node_labels   = torch.randint(0, 4, (N,)) # [N]
        
        # Build edges
        edge_index = build_knn_graph(node_features, k=5)  # [2, E]
        E = edge_index.shape[1]
        edge_features = torch.randn(E, 16)                 # [E, d_e]
        
        # Document expected shapes
        assert node_features.shape == (N, 32), f"Unexpected: {node_features.shape}"
        assert edge_index.shape[0] == 2, f"edge_index must be [2, E]"
        assert edge_features.shape[0] == E, "edge_features/edge_index mismatch"
        
        g = Graph(
            node_features=node_features,
            edge_index=edge_index,
            edge_features=edge_features,
            node_labels=node_labels,
        ).to(device)
        
        print(f"Graph: {N} nodes, {E} edges, on {device}")
        

Step 5: Build the Model

TGraphX provides several layers. For node classification, a two-layer GNN with
TensorGraphSAGELayer is a common starting point:

python
import torch.nn as nn
        from tgraphx.layers import TensorGraphSAGELayer
        
        class SimpleGNN(nn.Module):
            def __init__(self, in_dim, hidden_dim, out_dim):
                super().__init__()
                self.layer1 = TensorGraphSAGELayer(in_dim, hidden_dim)
                self.layer2 = TensorGraphSAGELayer(hidden_dim, out_dim)
                self.relu = nn.ReLU()
        
            def forward(self, g):
                x = self.layer1(g)
                x = self.relu(x)
                # For layer2, update g.node_features with x
                # (exact API depends on layer's forward signature)
                return x
        
        model = SimpleGNN(in_dim=32, hidden_dim=64, out_dim=4).to(device)
        

Step 6: Shape Check the Model Output

After building the model, run a single forward pass before the training loop
to verify shapes end-to-end:

python
model.eval()
        with torch.no_grad():
            out = model(g)
        
        # Verify output shape matches expectation
        expected_shape = (N, 4)  # [num_nodes, num_classes]
        assert out.shape == expected_shape, (
            f"Model output shape {out.shape} does not match expected {expected_shape}"
        )
        print(f"Shape check passed: {out.shape}")
        

This smoke test catches layer configuration errors before you waste time
training.

Step 7: Logging Setup

TGraphX exports three loggers:

python
from tgraphx import CSVLogger, TensorBoardLogger, MLflowLogger
        
        # CSVLogger: lightweight, no external dependencies
        logger = CSVLogger(log_dir="./logs", experiment_name="skeleton_run")
        
        # TensorBoardLogger: if tensorboard is installed
        # logger = TensorBoardLogger(log_dir="./logs/tb")
        
        # MLflowLogger: if mlflow is installed
        # logger = MLflowLogger(tracking_uri="./mlruns")
        

For a first skeleton, CSVLogger is the lowest-friction choice.

Step 8: Write Experiment Config

Before training starts, write out your configuration:

python
from tgraphx import write_experiment_config
        
        config = {
            "seed": 42,
            "device": device,
            "num_nodes": N,
            "num_edges": E,
            "hidden_dim": 64,
            "num_classes": 4,
            "epochs": 50,
            "lr": 0.01,
            "model": "SimpleGNN",
        }
        
        write_experiment_config(config, path="./logs/experiment_config.json")
        

Step 9: Training Loop with Logging

python
import torch.optim as optim
        
        optimizer = optim.Adam(model.parameters(), lr=config["lr"])
        criterion = nn.CrossEntropyLoss()
        
        model.train()
        for epoch in range(config["epochs"]):
            optimizer.zero_grad()
            out = model(g)
            loss = criterion(out, g.node_labels)
            loss.backward()
            optimizer.step()
        
            logger.log({"epoch": epoch, "loss": loss.item()})
        
            if epoch % 10 == 0:
                print(f"Epoch {epoch:3d} | Loss: {loss.item():.4f}")
        

Step 10: Hardware Report

After training, write a hardware report alongside your results:

python
from tgraphx import write_hardware_report
        
        write_hardware_report(path="./logs/hardware_report.json")
        

The Complete Skeleton Structure

experiment/
        ├── skeleton.py          ← your script
        └── logs/
            ├── experiment_config.json   ← write_experiment_config
            ├── hardware_report.json     ← write_hardware_report
            ├── metrics.csv              ← CSVLogger output
            └── graph_stats.json         ← write_graph_stats (optional)
        

Common Mistakes This Skeleton Prevents

Mistake How the Skeleton Prevents It
Forgetting to seed randomness set_seed() called first
Wrong device for model vs data recommended_device() + g.to(device)
Silent shape mismatch Graph constructor + explicit assertions
Model output shape wrong Shape check before training loop
No record of Python/PyTorch ver env_report() at startup
No experiment config saved write_experiment_config() before training
No hardware context for results write_hardware_report() after training

Notes on Beta Status

TGraphX is Beta software. The skeleton above may need small adjustments if API
details change between versions. Pin your version in requirements.txt:

tgraphx==1.4.2
        

Check the changelog when upgrading. The experiment infrastructure — loggers,
write_* functions, set_seed, env_report — is the most likely to remain stable,
as it has fewer dependencies on the core graph computation machinery.