Skip to content

hetero_neighbor_sample is non-deterministic#527

Description

@pbielak

馃悰 Describe the bug

Hi, I encountered the problem that the NeighborLoader produces different outputs (different subgraphs) for hetergeneous graphs despite setting a fixed seed. In particular:

  • Each time, I launch a script, the generated batches (subgraphs) are different, which makes it hard to get consistent results (i.e., different graphs -> different model outputs -> different metrics)
  • I encountered the problem while dealing with model inference on heterogeneous graphs, but the same problem will also apply to training scenarios
  • I tried using only torch.manual_seed(...) as well as the seed_everything(...) function from PyG - in both cases the NeighborLoader outputs are non-deterministic

A minimal reproducer is given below:

import torch_geometric.transforms as T
from torch_geometric.datasets import OGB_MAG
from torch_geometric.loader import NeighborLoader
from torch_geometric import seed_everything


seed_everything(42)

data = OGB_MAG(
    root='./data',
    preprocess='metapath2vec',
    transform=T.ToUndirected(merge=True),
)[0]

val_loader = NeighborLoader(
    data,
    num_neighbors=[2, 2],
    shuffle=False,
    input_nodes=('paper', data['paper'].val_mask),
    batch_size=2,
    num_workers=0,
)

bt = next(iter(val_loader))
print(bt["paper"].n_id, bt["paper"].n_id.shape)

Expected behaviour: The exact same neighbor nodes are sampled for the given anchor nodes (here: 332, 756).

Actual runs of the script:

$ python bug.py
tensor([   332,    756, 228723,  93868, 450423,  36291, 716566, 651899, 494053,
        137372, 437078, 441757, 282790, 394355,  89966, 590215, 588425, 629091,
        560313, 571727, 629037, 577729, 707931,  15342, 634495]) torch.Size([25])

$ python bug.py
tensor([   332,    756, 420915,  93868,  15342, 734005, 670966, 726226, 733772,
        732766, 733204, 735112,    181, 736230, 707931, 401042, 715045]) torch.Size([17])

$ python bug.py
tensor([   332,    756, 228723,  93868, 450423,  36291, 729748, 726226, 441757,
        734491,     30, 736234, 736170,      0, 736388,    121, 736027, 707931,
        401042, 715045]) torch.Size([20])

The NeighborLoader calls internally the hetero_neighbor_sample kernel from pyg-lib. While looking into the implementation I found that the problem lies in the at::parallel_for call, i.e., https://github.com/pyg-team/pyg-lib/blob/master/pyg_lib/csrc/sampler/cpu/neighbor_kernel.cpp#L714

  • The random number generator is created in L606: https://github.com/pyg-team/pyg-lib/blob/master/pyg_lib/csrc/sampler/cpu/neighbor_kernel.cpp#L606
  • The kernel parallelizes computations for each edge type https://github.com/pyg-team/pyg-lib/blob/master/pyg_lib/csrc/sampler/cpu/neighbor_kernel.cpp#L715
  • All threads are using the same number generator, but we have no control over the thread launch order, so the first time we call the whole script, threads might be launched as follows: T1, T2, T3, T4, ... but the next time we run the script it may be as follows: T2, T1, T4, T3. This causes the RNG calls to be in different order each time and eventually this will result in different subgraphs.
  • When disabling this parallelization (e.g., by using torch.set_num_threads(1)) the NeighborLoader outputs are deterministic, but the kernel computation is much slower (on custom graphs the slowdown was about 100x)
  • I also tried to create the RNGs in each thread separately, but the performance was still degraded

I'm looking for a solution that could provide both deterministic outputs (when using a seed in the main script) and a decent performance.

Environment

  • pyg-lib version: master
  • PyTorch version: 2.8.0
  • OS: Linux
  • Python version: 3.10
  • CUDA/cuDNN version: N/A
  • How you installed PyTorch and pyg-lib (conda, pip, source): PyTorch - pip, pyg-lib - built from master
  • Any other relevant information: N/A

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions