PyTorch Geometric (PyG)
PyTorch Geometric (PyG) is a Python library built on top of PyTorch for deep learning on graphs. It provides tools for working with graph-structured data, and implementations of many Graph Neural Networks (GNNs).
PyG on Aurora
PyTorch Geometric includes a base library, called torch_geometric
, and a number of optional dependencies:
- torch_scatter
- torch_sparse
- torch_cluster
- torch_spline_conv
- pyg_lib
The base library, torch_geometric
, relies solely on PyTorch and can utilize Intel GPUs through the xpu
device specification.
The optional dependencies include optimized operations that are relevant to GNNs and are missing in PyTorch. They have only CPU and CUDA implementations and can therefore only run on Aurora CPUs.
PyG base library
Here we explain how to install the base library, torch_geometric
, on Aurora and show a simple example of training a PyG model on Intel GPUs.
Installing torch_geometric
on Aurora
We are going to install torch_geometric
in a virtual environment that inherits PyTorch and the other libraries from the base frameworks module:
module load frameworks
python3 -m venv --clear venv --system-site-packages
source venv/bin/activate
pip install torch_geometric
Example
The following example is inspired by the gcn.py
example on the PyG repository.
- Copy the following Python script into a file called
gcn.py
.gcn.py# gcn.py import argparse import time import torch import intel_extension_for_pytorch as ipex import torch_geometric parser = argparse.ArgumentParser() parser.add_argument('--dataset', type=str, default='Cora') parser.add_argument('--hidden_channels', type=int, default=256) parser.add_argument('--lr', type=float, default=0.01) parser.add_argument('--epochs', type=int, default=20) parser.add_argument('--device', choices=['auto', 'cpu'], default='auto', help='device to use') parser.add_argument('--num_nodes', type=int, default=10000) parser.add_argument('--num_edges', type=int, default=5000000) parser.add_argument('--num_features', type=int, default=32) parser.add_argument('--num_classes', type=int, default=100) args = parser.parse_args() device = torch_geometric.device(args.device) print(f"device: {device}") edge_index = torch.randint(args.num_nodes, size=(args.num_edges, 2), dtype=torch.long) x = torch.randn(size=(args.num_nodes, args.num_features)) y = torch.randint(args.num_classes, size=(args.num_nodes,), dtype=torch.long) data = torch_geometric.data.Data(x=x, edge_index=edge_index.t().contiguous(), y=y, num_classes=(y.max()+1).item()) data = data.to(device) class GCN(torch.nn.Module): def __init__(self, in_channels, hidden_channels, out_channels): super().__init__() self.conv1 = torch_geometric.nn.GCNConv(in_channels, hidden_channels) self.conv2 = torch_geometric.nn.GCNConv(hidden_channels, out_channels) def forward(self, x, edge_index, edge_weight=None): x = torch.nn.functional.dropout(x, p=0.5, training=self.training) x = self.conv1(x, edge_index, edge_weight).relu() x = torch.nn.functional.dropout(x, p=0.5, training=self.training) x = self.conv2(x, edge_index, edge_weight) return x model = GCN( in_channels=data.num_features, hidden_channels=args.hidden_channels, out_channels=data.num_classes, ).to(device) optimizer = torch.optim.Adam([ dict(params=model.conv1.parameters(), weight_decay=5e-4), dict(params=model.conv2.parameters(), weight_decay=0) ], lr=args.lr) # Only perform weight-decay on first convolution. model, optimizer = ipex.optimize(model, optimizer=optimizer) def train(): model.train() optimizer.zero_grad() out = model(data.x, data.edge_index, data.edge_attr) loss = torch.nn.functional.cross_entropy(out, data.y) loss.backward() optimizer.step() return float(loss) times = [] for epoch in range(1, args.epochs + 1): start = time.time() loss = train() times.append(time.time() - start) print(f'Median time per epoch: {torch.tensor(times).median():.4f}s')
- Start an interactive job on one node.
- Load the frameworks module, activate the virtual environment and run the script: The output should look like:
- To run on the CPU use
python gcn.py --device cpu
.
Optional dependencies
Use the following script to create a virtual environment and install the base library and the CPU versions of all the optional dependencies:
#!/bin/bash
module load frameworks
TORCH_LIB=$(python -c "import torch; print(torch.__file__)" | sed 's/__init__.py/lib/')
TORCH_VERSION=`python -c "import torch; print(torch.__version__)" | sed 's/^\([0-9.]*\).*/\1/'`
python3 -m venv --clear venv --system-site-packages
source venv/bin/activate
export LD_LIBRARY_PATH=${TORCH_LIB}:$LD_LIBRARY_PATH
# PyTorch Geometric and utils
pip install torch_geometric
libs=(\
"https://github.com/rusty1s/pytorch_scatter.git" \
"https://github.com/rusty1s/pytorch_sparse.git" \
"https://github.com/rusty1s/pytorch_cluster.git" \
"https://github.com/rusty1s/pytorch_spline_conv.git")
for lib in "${libs[@]}"; do
LIB_NAME="$(basename "$lib" .git)"
git clone ${lib} && cd "$LIB_NAME"
git submodule update --init --recursive
# get library version compatible with pytorch
LIB_VERSION=`wget -O - https://data.pyg.org/whl/torch-${TORCH_VERSION}%2Bcpu.html 2>/dev/null | \
grep -m1 $(echo ${LIB_NAME} | sed 's/pytorch_\(.*\)/\1/') | \
sed 's/.*torch_[a-z_]*-\([^+-]*\)[%+-].*/\1/'`
git checkout ${LIB_VERSION}
# Force to install without OpenMP backend
sed "s|if ('backend: OpenMP' in info and 'OpenMP not found' not in info|if (False|g" setup.py > tmp && \
mv tmp setup.py
pip install .
cd ..
rm -rf "$LIB_NAME"
done