Unified graph learning framework with PyTorch
Project description
Unified graph learning framework with a stable core abstraction for homogeneous, heterogeneous, and temporal graphs.
VGL (Versatile Graph Learning) is a PyTorch-first graph learning library that provides one canonical Graph abstraction for homogeneous, heterogeneous, and temporal graphs — plus a batteries-included training pipeline from data loading to evaluation.
Highlights
- Unified
Graphobject — a single data structure for homogeneous, heterogeneous, and temporal graphs with schema validation, lightweight views, and batching. - Dataset-style link prediction splits —
RandomLinkSplitcreates train/val/testLinkPredictionRecorddatasets that plug directly into the existing loader, sampler, and trainer stack. - Mini-batch neighbor sampling —
NodeNeighborSampler,LinkNeighborSampler, andTemporalNeighborSamplerprovide PyG/DGL-style local subgraph training for homogeneous, heterogeneous, and temporal node/link workloads, including relation-aware temporal event sampling for typed heterogeneous graphs, with opt-in plan-backed node/edge feature materialization for sampled node, link, and temporal batches. VGL also now exposes relation-localto_block(...)plus a lightweightBlockcontainer, and multi-relationto_hetero_block(...)plusHeteroBlock, for message-flow rewrites when one batch should be viewed as source/destination frontiers instead of one sampled subgraph.NodeNeighborSampler(..., output_blocks=True)materializesNodeBatch.blocksin outer-to-inner order while keepingbatch.graphandbatch.seed_indexunchanged; homogeneous paths still emitBlock, heterogeneous node workloads with exactly one inbound relation keep the relation-localBlockpath, and heterogeneous node workloads with zero or multiple inbound relations now emit per-hopHeteroBlocklayers for both local full-graph sampling and stitched shard-local sampling through a coordinator-backed feature source. For link workloads,LinkNeighborSampler(..., output_blocks=True)now does the same without changingbatch.graph,src_index,dst_index, orlabels; homogeneous paths still emitBlock, single-relation heterogeneous supervision keeps relation-localBlock, and mixed-edge-type heterogeneous supervision can now emit fullHeteroBlocklayers through the same coordinator-backed feature source. When seed-edge exclusion is active those excluded supervision edges are removed from the block message-passing view as well. On shard-local graphs, coordinator-backed node and link sampling can now stitch cross-partition frontier nodes and edges into one sampled subgraph for homogeneous workloads and non-temporal heterogeneous node/link workloads, whileTemporalNeighborSamplercan stitch earlier cross-partition history for both homogeneous and typed heterogeneous temporal workloads. - Foundation layers for scale —
vgl.sparse,vgl.storage,vgl.ops,vgl.data, andvgl.distributedprovide sparse adjacency views, Laplacian sparse views, graph sparse-format state APIs, weighted DGL-style adjacency exports, external sparse adjacency export, raw adjacency tensor exports, storage-backed graphs, graph transforms including line graphs, inbound/outbound frontier subgraphs, relation-localto_block(...)plus multi-relationto_hetero_block(...)message-flow rewrites, metapath reachability, relation-local hetero subgraphs/compaction, dataset catalogs / lazy on-disk formats, and local partition primitives plus typed node / edge routing, relation-scoped edge feature fetches, and partition-scoped local, boundary, and incident graph queries while keepingGraph,Loader, andTraineras the public entry points. - 50+ GNN convolution layers — all built on a clean
MessagePassinginterface:GCNConv,GATConv,SAGEConv,GINConv,TransformerConv, and many more. - Graph transformer encoders — reusable encoder blocks such as
GraphTransformerEncoder,GraphormerEncoder,GPSLayer,NAGphormerEncoder, andSGFormerEncoder. - Temporal encoders & memory — temporal modules such as
TimeEncoder,TGATLayer,TGATEncoder,IdentityTemporalMessage, andTGNMemoryplug into event prediction without changing the training loop. - Edge-aware operators — homogeneous graphs can now carry
edge_data, enabling operators such asNNConv,ECConv,GINEConv,GMMConv,CGConv,SplineConv,GatedGCNConv, andPDNConv. - Point / geometric operators — homogeneous graphs can also carry node positions via
pos, enabling operators such asPointNetConvandPointTransformerConv. - Two-layer training logs — default console progress plus pluggable structured loggers such as
JSONLinesLoggerandTensorBoardLoggermake experiments easier to monitor and replay. - End-to-end training —
Trainerhandles the full loop includingfit(),evaluate(),test(), early stopping, best-checkpoint saving, full training-state checkpoint/resume, epoch history tracking, gradient accumulation, scheduled gradient accumulation, gradient clipping, gradient value clipping, adaptive gradient clipping, gradient centralization, gradient noise injection, layer-wise learning-rate strategies, classification label smoothing, label smoothing scheduling, focal loss, focal-gamma scheduling, generalized cross entropy, generalized-cross-entropy scheduling, symmetric cross entropy, symmetric-cross-entropy beta scheduling, Poly-1 cross entropy, Poly-1 epsilon scheduling, soft/hard bootstrap loss correction, bootstrap-beta scheduling, confidence-penalty scheduling, flooding-level scheduling,LDAM, LDAM-margin scheduling, logit adjustment, logit-adjustment tau scheduling, balanced softmax, class weighting /pos_weight,pos_weightscheduling, weight-decay scheduling, loss flooding, confidence-penalty regularization,R-Dropregularization, sharpness-aware optimization withSAM,ASAM, andGSAM, link prediction uniform / hard-negative / candidate-set sampling, random edge splitting, and neighbor-subgraph sampling with optional seed-edge exclusion, raw and filtered ranking evaluation metrics such asMRRandHits@K, epoch-wise or step-wise LR scheduling including built-in warmup/cosine support and schedulers such asOneCycleLR, mixed precision, and training callbacks such as model checkpointing (including optional exception checkpointing), gradual unfreezing, deferred reweighting (DRW), EMA, SWA, and Lookahead. - Multiple graph tasks — node classification, graph classification, link prediction, and temporal event prediction out of the box.
- PyG, DGL, NetworkX, edge-list, and CSV-table interoperability — bidirectional adapters for PyG data, homogeneous NetworkX graphs, in-memory homogeneous edge lists, CSV edge-list files, paired node/edge CSV tables, plus homogeneous, heterogeneous, and temporal DGL graph round-trips that preserve canonical edge types and temporal
time_attrmetadata, along with dedicated single-relationBlockand multi-relationHeteroBlock<-> DGL block conversion for message-flow frontiers. - Clean, modular design — domain-oriented package layout that separates concerns and stays easy to extend.
Architecture
| Package | Description |
|---|---|
vgl.graph |
Graph, Block, GraphBatch, GraphSchema, GraphView, node / edge stores |
vgl.sparse |
SparseTensor, COO/CSR/CSC conversion helpers, torch sparse interop, multi-value payload support, transpose/reduction utilities, multi-value SPMM/SDDMM, edge softmax, sparse graph ops |
vgl.storage |
tensor stores including mmap-backed tensors, FeatureStore, GraphStore, storage-backed graph assembly with retained feature-source context |
vgl.ops |
structure transforms, adjacency/Laplacian sparse views, frontier subgraphs, relation-local to_block, multi-relation to_hetero_block, line graphs, random walks, metapath random walks, metapath reachability, homo/hetero relation-local subgraph extraction, k-hop expansion, compaction |
vgl.data |
dataset catalog models, cache helpers, built-in datasets, lazy homo/hetero/temporal on-disk datasets |
vgl.dataloading |
DataLoader, SamplingPlan, plan executor, samplers, sample records, explicit-or-graph-retained feature-source routing and sampled feature materialization |
vgl.distributed |
partition metadata, local shard loading, store adapters, typed node/edge routing, local/boundary/incident partition queries, sampling coordination, routed feature sources |
vgl.nn |
MessagePassing, 50+ convolution layers, graph/temporal encoders, HeteroConv, readout, GroupRevRes |
vgl.tasks |
NodeClassificationTask, GraphClassificationTask, LinkPredictionTask, TemporalEventPredictionTask |
vgl.engine |
Trainer, callbacks, checkpoints, TrainingHistory, evaluator, training strategies |
vgl.metrics |
Accuracy, Metric base, build_metric |
vgl.transforms |
Graph transforms (identity, extensible) |
vgl.compat |
PyG, DGL, NetworkX, edge-list, and CSV-table interoperability helpers |
Legacy imports (
vgl.core,vgl.data,vgl.train) remain as compatibility layers but new code should use the layout above.
Foundation Layers
vgl.sparseis where adjacency layouts and sparse execution helpers live. It now exposes COO/CSR/CSC conversion, bidirectional interop with nativetorch.sparse_*COO/CSR/CSC tensors, transpose, row/column structural selection, sparse edge payloads shaped(nnz, ...), additive reductions that preserve trailing payload dimensions, sampled dense-dense matmul throughsddmm(...), edge-wise normalization throughedge_softmax(...), andspmm(...)that preserves sparse payload dimensions while appending dense feature channels at the end of the output shape, plus cached adjacency views throughGraph.adjacency(...)and incidence-matrix views throughGraph.inc(...).vgl.storageturns in-memory or mmap-backed tensor stores plus graph stores into lazily feature-backedGraphobjects throughGraph.from_storage(...), which is the main path for large-graph and feature-store-backed workflows. Storage-backed graphs retain their originating feature source so later plan execution can reuse it without extra wiring.vgl.opscentralizes reusable graph transforms plus graph queries. Alongsidefind_edges(...),edge_ids(...), andhas_edges_between(...), it now exposesnum_nodes(...)/num_edges(...)withnumber_of_*aliases for graph cardinality,all_edges(...)for DGL-style full-edge enumeration, graph format-state helpers throughformats(...)andcreate_formats_(...),adj(...)for DGL-style weighted adjacency sparse views,laplacian(...)for square-relation Laplacian sparse views withNone,"rw", or"sym"normalization,adj_external(...)for torch / SciPy sparse adjacency export,adj_tensors(...)for raw COO/CSR/CSC adjacency tensor export,inc(...)for DGL-style incidence sparse views,in_edges(...),out_edges(...),predecessors(...), andsuccessors(...)for ordered one-hop adjacency, plusin_degrees(...)/out_degrees(...)for degree inspection. Count helpers return Pythonint, degree queries returnintfor one node and tensors for many or all nodes, and both continue to respect storage-backed declared node counts so isolated nodes surface as zero instead of disappearing.formats()reports graph-level sparse-format status,create_formats_()eagerly materializes all allowed formats, andall_edges(order="eid"),adj(...),laplacian(...),adj_external(...),adj_tensors(...), andinc(...)all follow the publice_idordering model when present.adj(..., eweight_name="weight")uses edge features as sparse values,laplacian(..., normalization="rw" | "sym")builds row-degree-based normalized Laplacians on square relations while preserving declared storage-backed node space,adj_external(...)returnstorch.sparse_coo_tensorby default, can emit native torch COO / CSR / CSC layouts throughtorch_fmt=, and still exports SciPycoo/csrmatrices throughscipy_fmt=, all with unit values.adj_tensors("coo")returns(src, dst)whileadj_tensors("csr")/adj_tensors("csc")return compressed pointers, compressed coordinates, and aligned public edge ids.inc(typestr="both")drops self-loop nonzeros just like DGL.in_subgraph(...)/out_subgraph(...)keep node space intact,to_simple(...)collapses parallel edges in stable first-occurrence order and can attach multiplicity counts throughcount_attr=,reverse(...)swaps endpoints while preservinge_idand storage-backed node-count context,to_block(...)keeps relation-local message-flow rewrites andsrc_n_id/dst_n_id/e_idmetadata aligned, andto_hetero_block(...)builds one multi-relation heterogeneous message-flow layer from per-type destination frontiers.vgl.datanow includes dataset manifests, local cache helpers, fixture-backed datasets, and an on-disk graph dataset format that writes one payload per graph undergraphs/, loads items lazily, exposes manifest-backed split views, keeps legacygraphs.ptartifacts readable, and round-trips homogeneous, heterogeneous, and temporal graphs for reproducible pipelines.vgl.distributedstarts the shard-aware surface with partition manifests, deterministic local partition writing, local shard loading, shard/global id remapping, owned-local plus boundary/incident partition edge queries, and single-process coordination contracts. The current local partition path now handles homogeneous, temporal homogeneous, single-node-type multi-relation, and true multi-node-type heterogeneous graphs without changing the overall manifest/payload workflow. Typed partition ownership flows throughPartitionManifest,LocalGraphShard, andLocalSamplingCoordinator, so node routing can be scoped bynode_type, relation-scoped edge ids can be routed byedge_type, boundary frontier structure can be queried explicitly through boundary and incident edge ids/indexes, edge features can be fetched through keys such as('edge', edge_type, 'weight'), and plan-backed feature fetch stages can route through the same coordinator whenLoaderorPlanExecutorreceives it as the feature source or falls back to a graph-retained source. When those stages run during node, link, or temporal sampling, the fetched slices are now materialized back into the sampled subgraph instead of staying stranded in executor state. For shard-local homogeneous graphs,NodeNeighborSamplerandLinkNeighborSamplercan now stitch cross-partition frontier structure through coordinator incident-edge queries, and homogeneousTemporalNeighborSamplercan stitch earlier cross-partition history. For shard-local non-temporal heterogeneous graphs,NodeNeighborSamplerandLinkNeighborSamplercan now stitch cross-partition typed frontier structure through the same coordinator while keeping sampled node and edge tensors aligned by per-type globaln_id/e_id. For shard-local typed heterogeneous temporal graphs,TemporalNeighborSamplercan now stitch earlier cross-partition relation-local history through the same coordinator while keeping per-type node tensors aligned by globaln_idand sampled relation edges aligned by globale_id.
These layers are intentionally underneath the user-facing API: models still consume Graph / batch objects, loaders still start at Loader, and training still starts at Trainer.
Quick Tour
Node Classification
import torch
from vgl.dataloading import DataLoader, ListDataset, NodeNeighborSampler
from vgl.graph import Graph
from vgl.tasks import NodeClassificationTask
from vgl.engine import JSONLinesLogger, Trainer
graph = Graph.homo(edge_index=edge_index, x=x, y=y,
train_mask=train_mask, val_mask=val_mask, test_mask=test_mask)
train_ds = ListDataset([(graph, {"seed": int(i)}) for i in graph.train_mask.nonzero().view(-1)])
val_ds = ListDataset([(graph, {"seed": int(i)}) for i in graph.val_mask.nonzero().view(-1)])
test_ds = ListDataset([(graph, {"seed": int(i)}) for i in graph.test_mask.nonzero().view(-1)])
train_loader = DataLoader(train_ds, sampler=NodeNeighborSampler([15, 10]), batch_size=1024)
val_loader = DataLoader(val_ds, sampler=NodeNeighborSampler([15, 10]), batch_size=2048)
test_loader = DataLoader(test_ds, sampler=NodeNeighborSampler([15, 10]), batch_size=2048)
task = NodeClassificationTask(target="y",
split=("train_mask", "val_mask", "test_mask"),
metrics=["accuracy"])
trainer = Trainer(model=model, task=task,
optimizer=torch.optim.Adam, lr=1e-3, max_epochs=200,
monitor="val_accuracy", save_best_path="best.pt",
loggers=[JSONLinesLogger("artifacts/train.jsonl", flush=True)],
log_every_n_steps=10)
history = trainer.fit(train_loader, val_data=val_loader)
result = trainer.test(test_loader)
print(f"Test accuracy: {result['accuracy']:.4f}")
Each node-sampling dataset item can carry one seed or a rank-1 seed collection in metadata["seed"]. NodeNeighborSampler will sample one union subgraph for that item and materialize one flat NodeBatch.seed_index entry per requested seed. For heterogeneous graphs, keep passing metadata["node_type"] for the supervised node type. NodeNeighborSampler(..., output_blocks=True) also exposes NodeBatch.blocks in outer-to-inner order without changing batch.graph or batch.seed_index; homogeneous paths emit Block, heterogeneous node paths with one inbound relation keep relation-local Block, and heterogeneous node paths with zero or multiple inbound relations now emit HeteroBlock.
Trainer writes console progress by default. Add loggers=[JSONLinesLogger(...)] for structured event logs, loggers=[CSVLogger(...)] for epoch-by-epoch spreadsheets, loggers=[TensorBoardLogger(...)] for TensorBoard scalars, tune step frequency with log_every_n_steps, or disable terminal logging with enable_console_logging=False.
For experiment hygiene and quick debugging, Trainer also supports a small set of trainer-level engineering controls:
trainer = Trainer(
...,
default_root_dir="artifacts/node-demo",
run_name="debug-neighbors",
fast_dev_run=True,
num_sanity_val_steps=2,
val_check_interval=0.5,
profiler="simple",
)
default_root_dir resolves relative artifact paths such as save_best_path="best.pt" or JSONLinesLogger("train.jsonl") under one run directory, run_name is recorded in structured logs and TrainingHistory, fast_dev_run caps every stage to a tiny sample, forces a single epoch, and suppresses automatic checkpoint writes, num_sanity_val_steps validates a few batches before training starts, val_check_interval can trigger extra validation passes inside an epoch, and profiler="simple" adds coarse timing totals to fit/epoch records without extra dependencies. For finer control, limit_train_batches, limit_val_batches, and limit_test_batches accept either a max batch count or a (0, 1] fraction.
The default console logger is also configurable through Trainer:
trainer = Trainer(
...,
enable_progress_bar=False,
console_mode="compact",
console_theme="cat",
console_metric_names={"loss", "train_loss", "val_loss"},
console_show_learning_rate=False,
console_show_events=False,
)
Console logs include HH:MM:SS timestamps by default. Detailed mode now starts each run with a compact summary card showing the model, task, optimizer, monitor, precision, and parameter counts, prints explicit stage-start lines for training / validation / testing, shows tqdm-style training-step context such as batch progress, percentage, throughput, and ETA, adds whole-fit progress fields such as fit=3/10 (30.0%) plus fit_eta=... in epoch summaries, and ends with fit-wide averages such as avg_epoch_time=... and avg_steps_per_second=.... Use console_mode="compact" for terse summaries, console_theme="cat" for an ASCII status mascot with stage labels such as starting, waiting, training, validating, testing, tracking, saving, and done plus distinct cat faces per phase and a small ASCII progress bar during training steps, console_metric_names={...} to whitelist printed metrics, and the console_show_* flags to hide learning-rate fields or lifecycle events from terminal output while keeping them in structured logs. Set console_show_timestamp=False if you want to remove the time prefix.
JSONLinesLogger also supports event filtering when you only want coarse summaries:
from vgl.engine import JSONLinesLogger
logger = JSONLinesLogger("artifacts/epochs.jsonl", events={"epoch_end", "fit_end"}, flush=True)
Emitted training-step and epoch-end records now include optimizer learning-rate fields such as lr (or lr/group_0, lr/group_1, ... for multiple parameter groups).
Structured logs now also include richer lifecycle events such as monitor_improved and checkpoint_saved; monitor_improved records carry previous_best, current_value, and improvement_delta so terminal and file logs can show how much the monitor moved, while checkpoint_saved records now carry size_bytes and save_seconds for artifact visibility. Run metadata on fit_start includes model/task/optimizer names and parameter counts.
You can also trim structured logs by metric and context:
from vgl.engine import CSVLogger, JSONLinesLogger
json_logger = JSONLinesLogger(
"artifacts/minimal.jsonl",
events={"epoch_end", "fit_end"},
metric_names={"train_loss", "val_loss"},
include_context=False,
show_learning_rate=False,
flush=True,
)
csv_logger = CSVLogger(
"artifacts/minimal.csv",
metric_names={"train_loss", "val_loss"},
include_context=False,
show_learning_rate=False,
flush=True,
)
metric_names={...} whitelists metrics written to file, show_learning_rate=False hides lr / lr/group_* fields, and include_context=False keeps only the core event coordinates plus the filtered metrics.
For TensorBoard integration:
from vgl.engine import TensorBoardLogger
tb_logger = TensorBoardLogger(
"artifacts/tensorboard",
events={"train_step", "epoch_end", "fit_end"},
show_learning_rate=False,
flush=True,
)
TensorBoardLogger writes step / epoch / fit scalars plus run metadata to an event directory. Launch TensorBoard with tensorboard --logdir artifacts/tensorboard. This logger requires the optional tensorboard package.
Graph Classification
from vgl.dataloading import DataLoader, ListDataset, FullGraphSampler, SampleRecord
from vgl.tasks import GraphClassificationTask
samples = [SampleRecord(graph=g, metadata={}, sample_id=str(i)) for i, g in enumerate(graphs)]
loader = DataLoader(dataset=ListDataset(samples), sampler=FullGraphSampler(),
batch_size=32, label_source="graph", label_key="y")
task = GraphClassificationTask(target="y", label_source="graph")
trainer = Trainer(model=model, task=task, optimizer=torch.optim.Adam, lr=1e-3, max_epochs=50)
trainer.fit(loader)
Link Prediction
from vgl.dataloading import CandidateLinkSampler, DataLoader, LinkNeighborSampler, UniformNegativeLinkSampler
from vgl.tasks import LinkPredictionTask
from vgl.transforms import RandomLinkSplit
train_ds, val_ds, test_ds = RandomLinkSplit(
num_val=0.1,
num_test=0.1,
disjoint_train_ratio=0.3,
neg_sampling_ratio=1.0,
add_negative_train_samples=False,
seed=0,
)(graph)
train_loader = DataLoader(
train_ds,
sampler=LinkNeighborSampler(
num_neighbors=[15, 10],
base_sampler=UniformNegativeLinkSampler(num_negatives=2),
),
batch_size=32,
)
val_loader = DataLoader(
val_ds,
sampler=LinkNeighborSampler(
num_neighbors=[15, 10],
base_sampler=CandidateLinkSampler(),
),
batch_size=64,
)
test_loader = DataLoader(
test_ds,
sampler=LinkNeighborSampler(
num_neighbors=[15, 10],
base_sampler=CandidateLinkSampler(),
),
batch_size=64,
)
task = LinkPredictionTask(target="label", metrics=["mrr", "filtered_mrr", "filtered_hits@10"])
trainer = Trainer(model=model, task=task, optimizer=torch.optim.Adam, lr=1e-3, max_epochs=50)
trainer.fit(train_loader, val_data=val_loader)
trainer.test(test_loader)
Link Prediction Ranking Evaluation
from vgl.dataloading import CandidateLinkSampler, DataLoader, ListDataset, LinkPredictionRecord
from vgl.tasks import LinkPredictionTask
eval_samples = [
LinkPredictionRecord(graph=graph, src_index=0, dst_index=1, label=1),
LinkPredictionRecord(graph=graph, src_index=3, dst_index=4, label=1, candidate_dst=[4, 8, 9, 10]),
]
eval_loader = DataLoader(
dataset=ListDataset(eval_samples),
sampler=CandidateLinkSampler(filter_known_positive_edges=True),
batch_size=2,
)
task = LinkPredictionTask(target="label", metrics=["mrr", "filtered_mrr", "filtered_hits@10"])
history = trainer.evaluate(eval_loader)
Use UniformNegativeLinkSampler or HardNegativeLinkSampler for training-time sampled negatives, CandidateLinkSampler for validation/test-time ranking over all destinations or an explicit candidate_dst set, and wrap either one with LinkNeighborSampler when you want mini-batch message passing on local subgraphs instead of full-graph propagation.
Pass node_feature_names=... and edge_feature_names=... to LinkNeighborSampler when those sampled link subgraphs should rehydrate node/edge tensors from an external feature store. For heterogeneous graphs, provide dictionaries keyed by node type and edge type.
LinkNeighborSampler(..., output_blocks=True) also exposes LinkPredictionBatch.blocks in outer-to-inner order while keeping batch.graph, batch.src_index, batch.dst_index, and batch.labels unchanged. This now covers local and stitched homogeneous link sampling plus heterogeneous link sampling through a coordinator-backed feature source. Those blocks are derived from the sampled message-passing graph rather than a raw supervision view, so positive edges hidden by exclude_seed_edge / exclude_seed_edges stay excluded from block message passing as well. On heterogeneous graphs, single-relation supervision keeps relation-local Block, while mixed-edge-type supervision can now emit HeteroBlock.
For heterogeneous link prediction, pass edge types explicitly:
train_ds, val_ds, test_ds = RandomLinkSplit(
num_val=0.1,
num_test=0.1,
edge_type=("author", "writes", "paper"),
rev_edge_type=("paper", "written_by", "author"),
)(hetero_graph)
Each resulting LinkPredictionRecord keeps edge_type (and optional reverse_edge_type) so negative sampling, neighbor sampling, and seed-edge exclusion stay relation-aware.
disjoint_train_ratio holds out a fraction of train positives as supervision-only edges (removed from train/val/test message-passing graphs), while neg_sampling_ratio can add pre-sampled negatives directly into split datasets when you want split-time labels instead of sampler-time negatives.
LinkPredictionBatch now also supports mixing multiple heterogeneous supervision relations in the same mini-batch, exposing batch.edge_types and batch.edge_type_index so models can route each record to the correct source/destination node-type encoders.
author_x = author_encoder(batch.graph.nodes["author"].x)
paper_x = paper_encoder(batch.graph.nodes["paper"].x)
logits = []
for i, rel_id in enumerate(batch.edge_type_index.tolist()):
src_type, _, dst_type = batch.edge_types[rel_id]
src_bank = author_x if src_type == "author" else paper_x
dst_bank = author_x if dst_type == "author" else paper_x
logits.append(score(torch.cat([src_bank[batch.src_index[i]], dst_bank[batch.dst_index[i]]], dim=-1)))
Temporal Event Prediction
from vgl.dataloading import DataLoader, FullGraphSampler, ListDataset, TemporalEventRecord, TemporalNeighborSampler
from vgl.tasks import TemporalEventPredictionTask
graph = Graph.temporal(nodes=nodes, edges=edges, time_attr="timestamp")
samples = [
TemporalEventRecord(graph=graph, src_index=0, dst_index=1, timestamp=3, label=1),
TemporalEventRecord(graph=graph, src_index=2, dst_index=0, timestamp=5, label=0),
]
full_loader = DataLoader(ListDataset(samples), sampler=FullGraphSampler(), batch_size=2)
sampled_loader = DataLoader(
ListDataset(samples),
sampler=TemporalNeighborSampler(num_neighbors=[20, 10], max_events=1024),
batch_size=2,
)
task = TemporalEventPredictionTask(target="label")
Use FullGraphSampler when the model should see the full temporal graph for each event, or switch to TemporalNeighborSampler to build strict-history local subgraphs with optional hop fanout, rolling time windows, and max_events caps.
For typed heterogeneous temporal graphs, pass edge_type= on each TemporalEventRecord. The resulting TemporalEventBatch mirrors the typed link-prediction contract through edge_type / edge_types, edge_type_index, and src_node_type / dst_node_type, while TemporalNeighborSampler keeps strict-history extraction and feature prefetch scoped to that relation instead of mixing all event types. When the source graph is shard-local and sampled through LocalSamplingCoordinator, that relation-local history can also stitch earlier cross-partition events into one typed temporal subgraph.
TemporalNeighborSampler(node_feature_names=..., edge_feature_names=...) can append plan-backed fetch stages as well, so strict-history event batches can overlay sampled x / edge features from a retained graph feature store or an explicit feature_store= source.
Installation
Requirements
- Python ≥ 3.10
- PyTorch ≥ 2.4
From PyPI
pip install sky-vgl
Optional Extras
pip install "sky-vgl[scipy]"
pip install "sky-vgl[networkx]"
pip install "sky-vgl[tensorboard]"
pip install "sky-vgl[dgl]"
pip install "sky-vgl[pyg]"
pip install "sky-vgl[full]"
Use extras when you need optional sparse export, interoperability, or logging integrations.
From Source
git clone https://github.com/skygazer42/sky-vgl.git
cd sky-vgl
pip install -e .
With Development Dependencies
pip install -e ".[dev,networkx,scipy,tensorboard]" # adds test/lint/type tools plus common test extras
Simple homogeneous graphs stay on DGL's lightweight dgl.graph(...) path. Typed or temporal VGL graphs export through dgl.heterograph(...) so canonical edge types survive, and temporal round-trips preserve Graph.schema.time_attr through the adapter-owned vgl_time_attr graph attribute. Relation-local VGL Block objects now also round-trip through dedicated single-relation DGL block helpers.
Supported Convolution Layers
All layers are built on the MessagePassing base class and share a consistent forward(x, edge_index, ...) interface.
For edge-aware operators on homogeneous graphs, Graph.homo(...) also accepts edge_data={...} and exposes it through graph.edata.
Full list of 50+ convolution operators
| Category | Layers |
|---|---|
| Spectral | GCNConv, ChebConv, SGConv, TAGConv, ARMAConv, APPNPConv, BernConv, SSGConv |
| Attention | GATConv, GATv2Conv, TransformerConv, SuperGATConv, AGNNConv, FAGCNConv, FAConv, FeaStConv, DNAConv |
| Relation-aware | RGCNConv, RGATConv, HGTConv, HEATConv, HeteroConv |
| Edge-aware | NNConv, ECConv, GINEConv, GMMConv, CGConv, SplineConv, GatedGCNConv, PDNConv |
| Point / Geometric | PointNetConv, PointTransformerConv |
| Semantic Hetero | HANConv |
| Aggregation | SAGEConv, GINConv, PNAConv, MixHopConv, GraphConv, EdgeConv, GENConv, FiLMConv, MFConv, GeneralConv, SimpleConv, EGConv, LEConv, LightGCNConv, LGConv, ClusterGCNConv |
| Deep / Residual | GCN2Conv, DAGNNConv, GPRGNNConv, H2GCNConv, DirGNNConv, TWIRLSConv, AntiSymmetricConv, GatedGraphConv, ResGatedGraphConv |
| Transformer Encoders | GraphTransformerEncoder, GraphormerEncoder, GPSLayer, NAGphormerEncoder, SGFormerEncoder |
| Temporal | TimeEncoder, TGATLayer, TGATEncoder, IdentityTemporalMessage, LastMessageAggregator, MeanMessageAggregator, TGNMemory |
| Other | WLConvContinuous, GroupRevRes (grouped reversible residual wrapper) |
Additionally, HeteroConv provides a wrapper for applying different convolution operators per edge type in heterogeneous graphs.
Readout / Pooling
| Function | Description |
|---|---|
global_mean_pool |
Mean readout over all nodes |
global_sum_pool |
Sum readout over all nodes |
global_max_pool |
Max readout over all nodes |
Framework Compatibility
VGL provides bidirectional conversion with the two most popular graph learning libraries:
from vgl.compat.pyg import from_pyg, to_pyg
from vgl.compat.dgl import (
block_from_dgl,
block_to_dgl,
from_dgl,
hetero_block_from_dgl,
hetero_block_to_dgl,
to_dgl,
)
from vgl.graph import Block, HeteroBlock
vgl_graph = from_pyg(pyg_data) # PyG Data → VGL Graph
pyg_data = to_pyg(vgl_graph) # VGL Graph → PyG Data
vgl_graph = from_dgl(dgl_graph) # DGL graph / heterograph → VGL Graph
dgl_graph = to_dgl(vgl_graph) # VGL Graph → DGL graph / heterograph
vgl_block = block_from_dgl(dgl_block) # single-relation DGL block → VGL Block
dgl_block = block_to_dgl(vgl_block) # VGL Block → single-relation DGL block
dgl_block = vgl_block.to_dgl() # convenience API
vgl_block = Block.from_dgl(dgl_block) # convenience API
vgl_hetero_block = hetero_block_from_dgl(dgl_block) # multi-relation DGL block → VGL HeteroBlock
dgl_block = hetero_block_to_dgl(vgl_hetero_block) # VGL HeteroBlock → multi-relation DGL block
dgl_block = vgl_hetero_block.to_dgl() # convenience API
vgl_hetero_block = HeteroBlock.from_dgl(dgl_block) # convenience API
Simple homogeneous graphs stay on dgl.graph(...). Typed or temporal VGL graphs export through dgl.heterograph(...) so canonical edge types survive, and temporal round-trips preserve Graph.schema.time_attr through the adapter-owned vgl_time_attr graph attribute. Graph adapters remain graph-only; message-flow frontiers use the relation-local block_from_dgl(...), block_to_dgl(...), Block.from_dgl(...), and Block.to_dgl() path for single-relation DGL blocks, or the multi-relation hetero_block_from_dgl(...), hetero_block_to_dgl(...), HeteroBlock.from_dgl(...), and HeteroBlock.to_dgl() path for heterogeneous DGL blocks. This means sampler-produced HeteroBlock layers can now round-trip through DGL without collapsing them back to one relation-local block.
Examples
| Task | Script | Graph Type |
|---|---|---|
| Node Classification | examples/homo/node_classification.py |
Homogeneous |
| Graph Classification | examples/homo/graph_classification.py |
Homogeneous |
| Link Prediction | examples/homo/link_prediction.py |
Homogeneous |
| Conv Zoo (50+ layers) | examples/homo/conv_zoo.py |
Homogeneous |
| Node Classification | examples/hetero/node_classification.py |
Heterogeneous |
| Link Prediction | examples/hetero/link_prediction.py |
Heterogeneous |
| Graph Classification | examples/hetero/graph_classification.py |
Heterogeneous |
| Event Prediction (TGAT) | examples/temporal/event_prediction.py |
Temporal |
| Event Prediction (TGN Memory) | examples/temporal/memory_event_prediction.py |
Temporal |
Heterogeneous graph-classification batches keep batch.graphs intact and expose per-node-type membership through graph_index_by_type / graph_ptr_by_type, so models can pool paper, author, or other typed node representations without flattening the graph schema away.
Run any example with:
python examples/homo/node_classification.py
Testing & Quality
# Run the full test suite
python -m pytest -v
# Lint check
python -m ruff check .
# Type check
python -m mypy vgl
The test suite covers:
- Core: Graph, batch, schema, views, heterogeneous/temporal constructors
- Data: Loaders, samplers, graph/link/temporal record pipelines
- Training: Trainer, tasks, metrics, callbacks, checkpoints, history
- NN: MessagePassing, all convolution operators, readout, GroupRevRes
- Compat: PyG and DGL adapter round-trips
- Integration: End-to-end workflows for all graph types and tasks
Project Structure
VGL/
├── vgl/
│ ├── graph/ # Graph, GraphBatch, Schema, View, Stores
│ ├── nn/
│ │ ├── conv/ # 50+ convolution operators
│ │ ├── message_passing.py
│ │ ├── hetero.py # HeteroConv
│ │ ├── readout.py # global_mean/sum/max_pool
│ │ └── grouprevres.py
│ ├── tasks/ # Node/Graph/Link/Temporal task definitions
│ ├── engine/ # Trainer, callbacks, checkpoints, history
│ ├── metrics/ # Accuracy, Metric base
│ ├── dataloading/ # DataLoader, datasets, samplers, records
│ ├── transforms/ # Graph transforms
│ └── compat/ # PyG & DGL converters
├── examples/
│ ├── homo/ # Homogeneous graph examples
│ ├── hetero/ # Heterogeneous graph examples
│ └── temporal/ # Temporal graph examples
├── tests/ # Comprehensive test suite
├── docs/ # Documentation & design plans
└── pyproject.toml
Documentation
Contributing
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch (
git checkout -b feature/my-feature) - Ensure all tests pass (
python -m pytest -v) - Ensure code quality (
python -m ruff check .andpython -m mypy vgl) - Submit a pull request
License
See LICENSE for details.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file sky_vgl-0.1.3.tar.gz.
File metadata
- Download URL: sky_vgl-0.1.3.tar.gz
- Upload date:
- Size: 191.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2b245c0e9d2cad6b126f366f4400f890e6ec8cb0af938bbc2b71fa5c70eb0118
|
|
| MD5 |
bff274a04bf4dc4f663dd26e42e8308d
|
|
| BLAKE2b-256 |
ce7a87349d08949c279c83c76074ffe448dda61dd7913de0a442b7c73fd94643
|
File details
Details for the file sky_vgl-0.1.3-py3-none-any.whl.
File metadata
- Download URL: sky_vgl-0.1.3-py3-none-any.whl
- Upload date:
- Size: 231.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d5ce155459181a3d60fceee2c2731a5c96e8bf0a63c31d7a3b15232da666e04b
|
|
| MD5 |
6118fa7c01ba545565a4f2cdc6a08b2e
|
|
| BLAKE2b-256 |
1bdf3e1889f5db1962f36bb8ca001bb842aa8fa3b8860347244c4b50cf82840c
|