diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e72a4a37d81..cce865857d80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). ### Added +- Added instructions for ROCm build wheels ([#7143](https://github.com/pyg-team/pytorch_geometric/pull/7143)) - Added a `ComposeFilters` class to compose `pre_filter` functions in `Dataset` ([#7097](https://github.com/pyg-team/pytorch_geometric/pull/7097)) - Added a time-step aware variant of the `EllipticBitcoinDataset` called `EllipticBitcoinTemporalDataset` ([#7011](https://github.com/pyg-team/pytorch_geometric/pull/7011)) - Added `to_dgl` and `from_dgl` conversion functions ([#7053](https://github.com/pyg-team/pytorch_geometric/pull/7053)) @@ -17,6 +18,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). ### Changed - Added an optional `batch_size` argument to `LayerNorm`, `GraphNorm`, `InstanceNorm`, `GraphSizeNorm` and `PairNorm` ([#7135](https://github.com/pyg-team/pytorch_geometric/pull/7135)) +- Improved code coverage ([#7093](https://github.com/pyg-team/pytorch_geometric/pull/7093)) +- Fix `numpy` incompatiblity when reading files for `Planetoid` datasets ([#7141](https://github.com/pyg-team/pytorch_geometric/pull/7141)) - Added support for `Data.num_edges` for native `torch.sparse.Tensor` adjacency matrices ([#7104](https://github.com/pyg-team/pytorch_geometric/pull/7104)) - Fixed crash of heterogeneous data loaders if node or edge types are missing ([#7060](https://github.com/pyg-team/pytorch_geometric/pull/7060), [#7087](https://github.com/pyg-team/pytorch_geometric/pull/7087)) - Accelerated attention-based `MultiAggregation` ([#7077](https://github.com/pyg-team/pytorch_geometric/pull/7077)) diff --git a/README.md b/README.md index 14f7a3ed6831..e67c1572ccfe 100644 --- a/README.md +++ b/README.md @@ -390,7 +390,7 @@ If you want to utilize the full set of features from PyG, there exists several a * **[`torch-cluster`](https://github.com/rusty1s/pytorch_cluster)**: Graph clustering routines * **[`torch-spline-conv`](https://github.com/rusty1s/pytorch_spline_conv)**: [`SplineConv`](https://pytorch-geometric.readthedocs.io/en/latest/generated/torch_geometric.nn.conv.SplineConv.html) support -These packages come with their own CPU and GPU kernel implementations based on the [PyTorch C++/CUDA extension interface](https://github.com/pytorch/extension-cpp). +These packages come with their own CPU and GPU kernel implementations based on the [PyTorch C++/CUDA/hip(ROCm) extension interface](https://github.com/pytorch/extension-cpp). For a basic usage of PyG, these dependencies are **fully optional**. We recommend to start with a minimal installation, and install additional dependencies once you start to actually need them. @@ -446,6 +446,11 @@ or install PyG **from master** via pip install git+https://github.com/pyg-team/pytorch_geometric.git ``` +### ROCm Wheels + +The external [`pyg-rocm-build` repository](https://github.com/Looong01/pyg-rocm-build) provides wheels and detailed instructions on how to install PyG for ROCm. +If you have any questions about it, please open an issue [here](https://github.com/Looong01/pyg-rocm-build/issues). + ## Cite Please cite [our paper](https://arxiv.org/abs/1903.02428) (and the respective papers of the methods used) if you use this code in your own work: diff --git a/docs/source/install/installation.rst b/docs/source/install/installation.rst index 02c84000edbe..9c696c70b31c 100644 --- a/docs/source/install/installation.rst +++ b/docs/source/install/installation.rst @@ -50,7 +50,7 @@ If you want to utilize the full set of features from :pyg:`PyG`, there exists se * `torch-cluster `__: Graph clustering routines * `torch-spline-conv `__: :class:`~torch_geometric.nn.conv.SplineConv` support -These packages come with their own CPU and GPU kernel implementations based on the :pytorch:`null` `PyTorch C++/CUDA extension interface `_. +These packages come with their own CPU and GPU kernel implementations based on the :pytorch:`null` `PyTorch C++/CUDA/hip(ROCm) extension interface `_. For a basic usage of :pyg:`PyG`, these dependencies are **fully optional**. We recommend to start with a minimal installation, and install additional dependencies once you start to actually need them. @@ -104,6 +104,9 @@ For ease of installation of these extensions, we provide :obj:`pip` wheels for t **For older versions, you need to explicitly specify the latest supported version number** or install via :obj:`pip install --no-index` in order to prevent a manual installation from source. You can look up the latest supported version number `here `__. +**ROCm:** The external `pyg-rocm-build repository `__ provides wheels and detailed instructions on how to install :pyg:`PyG` for ROCm. +If you have any questions about it, please open an issue `here `__. + Installation from Source ~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/test/data/test_view.py b/test/data/test_view.py new file mode 100644 index 000000000000..bf9054934f6b --- /dev/null +++ b/test/data/test_view.py @@ -0,0 +1,31 @@ +from torch_geometric.data.storage import BaseStorage + + +def test_views(): + storage = BaseStorage(x=1, y=2, z=3) + + assert str(storage.keys()) == "KeysView({'x': 1, 'y': 2, 'z': 3})" + assert len(storage.keys()) == 3 + assert list(storage.keys()) == ['x', 'y', 'z'] + + assert str(storage.values()) == "ValuesView({'x': 1, 'y': 2, 'z': 3})" + assert len(storage.values()) == 3 + assert list(storage.values()) == [1, 2, 3] + + assert str(storage.items()) == "ItemsView({'x': 1, 'y': 2, 'z': 3})" + assert len(storage.items()) == 3 + assert list(storage.items()) == [('x', 1), ('y', 2), ('z', 3)] + + args = ['x', 'z', 'foo'] + + assert str(storage.keys(*args)) == "KeysView({'x': 1, 'z': 3})" + assert len(storage.keys(*args)) == 2 + assert list(storage.keys(*args)) == ['x', 'z'] + + assert str(storage.values(*args)) == "ValuesView({'x': 1, 'z': 3})" + assert len(storage.values(*args)) == 2 + assert list(storage.values(*args)) == [1, 3] + + assert str(storage.items(*args)) == "ItemsView({'x': 1, 'z': 3})" + assert len(storage.items(*args)) == 2 + assert list(storage.items(*args)) == [('x', 1), ('z', 3)] diff --git a/test/loader/test_dataloader.py b/test/loader/test_dataloader.py index 8bf424f18a12..7dad99c0ffd1 100644 --- a/test/loader/test_dataloader.py +++ b/test/loader/test_dataloader.py @@ -7,7 +7,7 @@ from torch_geometric.data import Data, HeteroData from torch_geometric.loader import DataLoader -from torch_geometric.testing import get_random_edge_index +from torch_geometric.testing import get_random_edge_index, withCUDA with_mp = sys.platform not in ['win32'] num_workers_list = [0, 2] if with_mp else [0] @@ -16,8 +16,12 @@ multiprocessing.set_start_method('spawn') +@withCUDA @pytest.mark.parametrize('num_workers', num_workers_list) -def test_dataloader(num_workers): +def test_dataloader(num_workers, device): + if num_workers > 0 and device != torch.device('cpu'): + return + x = torch.Tensor([[1], [1], [1]]) edge_index = torch.tensor([[0, 1, 1, 2], [1, 0, 2, 1]]) face = torch.tensor([[0], [1], [2]]) @@ -25,9 +29,9 @@ def test_dataloader(num_workers): z = torch.tensor(0.) name = 'data' - data = Data(x=x, edge_index=edge_index, y=y, z=z, name=name) - assert str(data) == ( - "Data(x=[3, 1], edge_index=[2, 4], y=2.0, z=0.0, name='data')") + data = Data(x=x, edge_index=edge_index, y=y, z=z, name=name).to(device) + assert str(data) == ("Data(x=[3, 1], edge_index=[2, 4], y=2.0, z=0.0, " + "name='data')") data.face = face loader = DataLoader([data, data, data, data], batch_size=2, shuffle=False, @@ -35,6 +39,9 @@ def test_dataloader(num_workers): assert len(loader) == 2 for batch in loader: + assert batch.x.device == device + assert batch.edge_index.device == device + assert batch.z.device == device assert batch.num_graphs == len(batch) == 2 assert batch.batch.tolist() == [0, 0, 0, 1, 1, 1] assert batch.ptr.tolist() == [0, 3, 6] diff --git a/test/nn/conv/test_nn_conv.py b/test/nn/conv/test_nn_conv.py index e0db2f9575d1..397788e3926a 100644 --- a/test/nn/conv/test_nn_conv.py +++ b/test/nn/conv/test_nn_conv.py @@ -5,20 +5,21 @@ import torch_geometric.typing from torch_geometric.nn import NNConv -from torch_geometric.testing import is_full_test +from torch_geometric.testing import is_full_test, withCUDA from torch_geometric.typing import SparseTensor from torch_geometric.utils import to_torch_coo_tensor -def test_nn_conv(): - x1 = torch.randn(4, 8) - x2 = torch.randn(2, 16) - edge_index = torch.tensor([[0, 1, 2, 3], [0, 0, 1, 1]]) - value = torch.rand(edge_index.size(1), 3) +@withCUDA +def test_nn_conv(device): + x1 = torch.randn(4, 8, device=device) + x2 = torch.randn(2, 16, device=device) + edge_index = torch.tensor([[0, 1, 2, 3], [0, 0, 1, 1]], device=device) + value = torch.rand(edge_index.size(1), 3, device=device) adj1 = to_torch_coo_tensor(edge_index, value, size=(4, 4)) nn = Seq(Lin(3, 32), ReLU(), Lin(32, 8 * 32)) - conv = NNConv(8, 32, nn=nn) + conv = NNConv(8, 32, nn=nn).to(device) assert str(conv) == ( 'NNConv(8, 32, aggr=add, nn=Sequential(\n' ' (0): Linear(in_features=3, out_features=32, bias=True)\n' @@ -49,7 +50,7 @@ def test_nn_conv(): # Test bipartite message passing: adj1 = to_torch_coo_tensor(edge_index, value, size=(4, 2)) - conv = NNConv((8, 16), 32, nn=nn) + conv = NNConv((8, 16), 32, nn=nn).to(device) assert str(conv) == ( 'NNConv((8, 16), 32, aggr=add, nn=Sequential(\n' ' (0): Linear(in_features=3, out_features=32, bias=True)\n' diff --git a/test/nn/conv/test_rgcn_conv.py b/test/nn/conv/test_rgcn_conv.py index 80f9e9d6b5f3..ea27a9bdae63 100644 --- a/test/nn/conv/test_rgcn_conv.py +++ b/test/nn/conv/test_rgcn_conv.py @@ -3,32 +3,38 @@ import torch_geometric.typing from torch_geometric.nn import FastRGCNConv, RGCNConv -from torch_geometric.testing import is_full_test +from torch_geometric.testing import is_full_test, withCUDA from torch_geometric.typing import SparseTensor classes = [RGCNConv, FastRGCNConv] confs = [(None, None), (2, None), (None, 2)] +@withCUDA @pytest.mark.parametrize('conf', confs) -def test_rgcn_conv_equality(conf): +def test_rgcn_conv_equality(conf, device): num_bases, num_blocks = conf - x1 = torch.randn(4, 4) - edge_index = torch.tensor([[0, 1, 1, 2, 2, 3], [0, 0, 1, 0, 1, 1]]) - edge_type = torch.tensor([0, 1, 1, 0, 0, 1]) + x1 = torch.randn(4, 4, device=device) + edge_index = torch.tensor([ + [0, 1, 1, 2, 2, 3], + [0, 0, 1, 0, 1, 1], + ], device=device) + edge_type = torch.tensor([0, 1, 1, 0, 0, 1], device=device) edge_index = torch.tensor([ [0, 1, 1, 2, 2, 3, 0, 1, 1, 2, 2, 3], [0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1], - ]) - edge_type = torch.tensor([0, 1, 1, 0, 0, 1, 2, 3, 3, 2, 2, 3]) + ], device=device) + edge_type = torch.tensor([0, 1, 1, 0, 0, 1, 2, 3, 3, 2, 2, 3], + device=device) torch.manual_seed(12345) - conv1 = RGCNConv(4, 32, 4, num_bases, num_blocks, aggr='sum') + conv1 = RGCNConv(4, 32, 4, num_bases, num_blocks, aggr='sum').to(device) torch.manual_seed(12345) - conv2 = FastRGCNConv(4, 32, 4, num_bases, num_blocks, aggr='sum') + conv2 = FastRGCNConv(4, 32, 4, num_bases, num_blocks, + aggr='sum').to(device) out1 = conv1(x1, edge_index, edge_type) out2 = conv2(x1, edge_index, edge_type) @@ -40,19 +46,23 @@ def test_rgcn_conv_equality(conf): assert torch.allclose(out1, out2, atol=1e-6) +@withCUDA @pytest.mark.parametrize('cls', classes) @pytest.mark.parametrize('conf', confs) -def test_rgcn_conv(cls, conf): +def test_rgcn_conv(cls, conf, device): num_bases, num_blocks = conf - x1 = torch.randn(4, 4) - x2 = torch.randn(2, 16) - idx1 = torch.arange(4) - idx2 = torch.arange(2) - edge_index = torch.tensor([[0, 1, 1, 2, 2, 3], [0, 0, 1, 0, 1, 1]]) - edge_type = torch.tensor([0, 1, 1, 0, 0, 1]) + x1 = torch.randn(4, 4, device=device) + x2 = torch.randn(2, 16, device=device) + idx1 = torch.arange(4, device=device) + idx2 = torch.arange(2, device=device) + edge_index = torch.tensor([ + [0, 1, 1, 2, 2, 3], + [0, 0, 1, 0, 1, 1], + ], device=device) + edge_type = torch.tensor([0, 1, 1, 0, 0, 1], device=device) - conv = cls(4, 32, 2, num_bases, num_blocks, aggr='sum') + conv = cls(4, 32, 2, num_bases, num_blocks, aggr='sum').to(device) assert str(conv) == f'{cls.__name__}(4, 32, num_relations=2)' out1 = conv(x1, edge_index, edge_type) @@ -87,7 +97,7 @@ def test_rgcn_conv(cls, conf): assert torch.allclose(jit(None, adj.t()), out2, atol=1e-6) # Test bipartite message passing: - conv = cls((4, 16), 32, 2, num_bases, num_blocks, aggr='sum') + conv = cls((4, 16), 32, 2, num_bases, num_blocks, aggr='sum').to(device) assert str(conv) == f'{cls.__name__}((4, 16), 32, num_relations=2)' out1 = conv((x1, x2), edge_index, edge_type) diff --git a/test/nn/dense/test_linear.py b/test/nn/dense/test_linear.py index 6450d198e499..e258d77b2a6d 100644 --- a/test/nn/dense/test_linear.py +++ b/test/nn/dense/test_linear.py @@ -7,36 +7,41 @@ import torch_geometric.typing from torch_geometric.nn import HeteroDictLinear, HeteroLinear, Linear -from torch_geometric.testing import withPackage +from torch_geometric.testing import withCUDA, withPackage weight_inits = ['glorot', 'kaiming_uniform', None] bias_inits = ['zeros', None] +@withCUDA @pytest.mark.parametrize('weight', weight_inits) @pytest.mark.parametrize('bias', bias_inits) -def test_linear(weight, bias): - x = torch.randn(3, 4, 16) +def test_linear(weight, bias, device): + x = torch.randn(3, 4, 16, device=device) lin = Linear(16, 32, weight_initializer=weight, bias_initializer=bias) + lin = lin.to(device) assert str(lin) == 'Linear(16, 32, bias=True)' assert lin(x).size() == (3, 4, 32) +@withCUDA @pytest.mark.parametrize('weight', weight_inits) @pytest.mark.parametrize('bias', bias_inits) -def test_lazy_linear(weight, bias): - x = torch.randn(3, 4, 16) +def test_lazy_linear(weight, bias, device): + x = torch.randn(3, 4, 16, device=device) lin = Linear(-1, 32, weight_initializer=weight, bias_initializer=bias) + lin = lin.to(device) assert str(lin) == 'Linear(-1, 32, bias=True)' assert lin(x).size() == (3, 4, 32) assert str(lin) == 'Linear(16, 32, bias=True)' +@withCUDA @pytest.mark.parametrize('dim1', [-1, 16]) @pytest.mark.parametrize('dim2', [-1, 16]) -def test_load_lazy_linear(dim1, dim2): - lin1 = Linear(dim1, 32) - lin2 = Linear(dim1, 32) +def test_load_lazy_linear(dim1, dim2, device): + lin1 = Linear(dim1, 32).to(device) + lin2 = Linear(dim1, 32).to(device) lin2.load_state_dict(lin1.state_dict()) if dim1 != -1: @@ -78,11 +83,12 @@ def test_copy_unintialized_parameter(): copy.deepcopy(weight) +@withCUDA @pytest.mark.parametrize('lazy', [True, False]) -def test_copy_linear(lazy): - lin = Linear(-1 if lazy else 16, 32) +def test_copy_linear(lazy, device): + lin = Linear(-1 if lazy else 16, 32).to(device) - copied_lin = copy.copy(lin) + copied_lin = copy.copy(lin).to(device) assert id(copied_lin) != id(lin) assert id(copied_lin.weight) == id(lin.weight) if not isinstance(copied_lin.weight, UninitializedParameter): @@ -90,7 +96,7 @@ def test_copy_linear(lazy): assert id(copied_lin.bias) == id(lin.bias) assert copied_lin.bias.data_ptr() == lin.bias.data_ptr() - copied_lin = copy.deepcopy(lin) + copied_lin = copy.deepcopy(lin).to(device) assert id(copied_lin) != id(lin) assert id(copied_lin.weight) != id(lin.weight) if not isinstance(copied_lin.weight, UninitializedParameter): @@ -102,11 +108,12 @@ def test_copy_linear(lazy): assert torch.allclose(copied_lin.bias, lin.bias) -def test_hetero_linear(): - x = torch.randn(3, 16) - type_vec = torch.tensor([0, 1, 2]) +@withCUDA +def test_hetero_linear(device): + x = torch.randn(3, 16, device=device) + type_vec = torch.tensor([0, 1, 2], device=device) - lin = HeteroLinear(16, 32, num_types=3) + lin = HeteroLinear(16, 32, num_types=3).to(device) assert str(lin) == 'HeteroLinear(16, 32, num_types=3, bias=True)' out = lin(x, type_vec) @@ -116,22 +123,27 @@ def test_hetero_linear(): assert torch.allclose(jit(x, type_vec), out) -def test_lazy_hetero_linear(): - x = torch.randn(3, 16) - type_vec = torch.tensor([0, 1, 2]) +@withCUDA +def test_lazy_hetero_linear(device): + x = torch.randn(3, 16, device=device) + type_vec = torch.tensor([0, 1, 2], device=device) - lin = HeteroLinear(-1, 32, num_types=3) + lin = HeteroLinear(-1, 32, num_types=3).to(device) assert str(lin) == 'HeteroLinear(-1, 32, num_types=3, bias=True)' out = lin(x, type_vec) assert out.size() == (3, 32) +@withCUDA @pytest.mark.parametrize('bias', [True, False]) -def test_hetero_dict_linear(bias): - x_dict = {'v': torch.randn(3, 16), 'w': torch.randn(2, 8)} +def test_hetero_dict_linear(bias, device): + x_dict = { + 'v': torch.randn(3, 16, device=device), + 'w': torch.randn(2, 8, device=device), + } - lin = HeteroDictLinear({'v': 16, 'w': 8}, 32, bias=bias) + lin = HeteroDictLinear({'v': 16, 'w': 8}, 32, bias=bias).to(device) assert str(lin) == (f"HeteroDictLinear({{'v': 16, 'w': 8}}, 32, " f"bias={bias})") @@ -140,9 +152,12 @@ def test_hetero_dict_linear(bias): assert out_dict['v'].size() == (3, 32) assert out_dict['w'].size() == (2, 32) - x_dict = {'v': torch.randn(3, 16), 'w': torch.randn(2, 16)} + x_dict = { + 'v': torch.randn(3, 16, device=device), + 'w': torch.randn(2, 16, device=device), + } - lin = HeteroDictLinear(16, 32, types=['v', 'w'], bias=bias) + lin = HeteroDictLinear(16, 32, types=['v', 'w'], bias=bias).to(device) assert str(lin) == (f"HeteroDictLinear({{'v': 16, 'w': 16}}, 32, " f"bias={bias})") @@ -151,6 +166,15 @@ def test_hetero_dict_linear(bias): assert out_dict['v'].size() == (3, 32) assert out_dict['w'].size() == (2, 32) + +def test_hetero_dict_linear_jit(): + x_dict = { + 'v': torch.randn(3, 16), + 'w': torch.randn(2, 8), + } + + lin = HeteroDictLinear({'v': 16, 'w': 8}, 32) + if torch_geometric.typing.WITH_GMM: # See: https://github.com/pytorch/pytorch/pull/97960 with pytest.raises(RuntimeError, match="Unknown builtin op"): @@ -160,10 +184,14 @@ def test_hetero_dict_linear(bias): assert len(jit(x_dict)) == 2 -def test_lazy_hetero_dict_linear(): - x_dict = {'v': torch.randn(3, 16), 'w': torch.randn(2, 8)} +@withCUDA +def test_lazy_hetero_dict_linear(device): + x_dict = { + 'v': torch.randn(3, 16, device=device), + 'w': torch.randn(2, 8, device=device), + } - lin = HeteroDictLinear(-1, 32, types=['v', 'w']) + lin = HeteroDictLinear(-1, 32, types=['v', 'w']).to(device) assert str(lin) == "HeteroDictLinear({'v': -1, 'w': -1}, 32, bias=True)" out_dict = lin(x_dict) @@ -172,15 +200,16 @@ def test_lazy_hetero_dict_linear(): assert out_dict['w'].size() == (2, 32) +@withCUDA @withPackage('pyg_lib') @pytest.mark.parametrize('type_vec', [ torch.tensor([0, 0, 1, 1, 2, 2]), torch.tensor([0, 1, 2, 0, 1, 2]), ]) -def test_hetero_linear_sort(type_vec): - x = torch.randn(type_vec.numel(), 16) +def test_hetero_linear_sort(type_vec, device): + x = torch.randn(type_vec.numel(), 16, device=device) - lin = HeteroLinear(16, 32, num_types=3) + lin = HeteroLinear(16, 32, num_types=3).to(device) out = lin(x, type_vec) for i in range(type_vec.numel()): diff --git a/torch_geometric/io/planetoid.py b/torch_geometric/io/planetoid.py index 2e7338afaa12..ba767668f21d 100644 --- a/torch_geometric/io/planetoid.py +++ b/torch_geometric/io/planetoid.py @@ -103,7 +103,7 @@ def read_file(folder, prefix, name): return out out = out.todense() if hasattr(out, 'todense') else out - out = torch.Tensor(out) + out = torch.from_numpy(out).to(torch.float) return out