Lightning-AI / Lightning-AI/pytorch-lightning

Validation loader is seeded the same during epoch in distributed mode

Open
#17,843 5 comments 0 reactions 1 assignee View on GitHub

@justusschock is already working on this.

Since Jun 16, 2023.

data handling question ver: 2.0.x
Dominant language
Python
Stars
31.4k
Forks
3.8k
Avg merge
6d 7h
Merged PRs (30d)
6

Description

Bug description

The validation dataloader is seeded identically for each validation step during each epoch in distributed mode. While this may be fine when the whole validation set is consumed entirely in the validation step, this leads to unexpected results when using the limit_val_batches option.

It seems to me that this is not the result that is expected when setting limit_val_batches ; it seems that that should give a different subset of batches every time the validation loop is executed. The behavior for devices=1 confirms this: in this case the dataloader provides a new set of batches on every validation loop, even in the case of multiple validation loops per epoch.

The issue seems to arise from the distributed sampler in pytorch (torch.utils.data.distributed.DistributedSampler, see https://pytorch.org/docs/stable/_modules/torch/utils/data/distributed.html#DistributedSampler), which seeds based on a seed, and epoch number. The effect is that the exact same shuffling is used as long as the epoch number has not been updated. And then, as mentioned before, when limiting the validation to a small subset of the total validation set, over the course of one epoch, exactly the same batches are used for evaluation at every step, and then a new fixed set on the second epoch, etcetera.

Below is a reproducing example that displays the batch indices at every validation step. It is easy to see that the output is identical all throughout the first epoch. Setting the number of devices to 1, gives a random set of batches every validation step (the expected behavior).

Summary:
The combination devices > 1, limit_val_batches < (len(dataset) // batch_size) and shuffle=True gives the same set of validation batches for each validation step.

What version are you seeing the problem on?

v2.0

How to reproduce the bug
from torch.utils.data import DataLoader
import torch
import torch.nn as nn
import torch.optim as optim
import pytorch_lightning as pl
import time

# Define the LightningModule
class MyModel(pl.LightningModule):
    def __init__(self):
        super(MyModel, self).__init__()
        self.model = nn.parameter.Parameter(torch.ones(1,), requires_grad=True)

    # Nothing important here
    def training_step(self, batch, batch_idx):
        loss = (self.model - batch).square().mean()
        self.log('train_loss', loss)
        return loss

    def train_dataloader(self):
        dataset = torch.range(0, 100000).float() / 10000
        train_dataloader = DataLoader(
            dataset, batch_size=2)
        return train_dataloader

    def val_dataloader(self):
        val_dataset = torch.range(0, 10000).float()
        val_batch_size=8

        val_dataloader = DataLoader(
            val_dataset, batch_size=val_batch_size, drop_last=False, num_workers=0,
            pin_memory=False, collate_fn=collate_fn, shuffle=True
        )
        return val_dataloader

    def configure_optimizers(self):
        optimizer = optim.Adam(self.parameters(), lr=1e-4)
        return optimizer

    def validation_step(self, batch, batch_idx):
        print(f'Reporting from local rank {self.trainer.local_rank}, index {batch_idx}, batch {batch}')

collate_fn = torch.stack

if __name__ == '__main__':
    devices= 2
    accelerator = 'cpu'
    # Create a PyTorch Lightning trainer
    model = MyModel()

    trainer = pl.Trainer(
        max_epochs=100,
        val_check_interval=0.2,
        limit_val_batches=2,
        devices=devices,
        accelerator=accelerator,
    )


    # Train the model using the Lightning trainer
    trainer.fit(model)
Error messages and logs
[ ... ] 
Epoch 0:  20%|███████████████████████▌                                                                                              | 5000/25001 [00:06<00:24, 811.98it/s, v_num=0]
Reporting from local rank 1, index 0, batch tensor([8240., 9125., 2912., 3170., 9948., 9427., 6728., 1879.])
Reporting from local rank 1, index 1, batch tensor([6557., 5502., 8580., 1543., 6386., 1750., 4385., 3793.])
Validation: 0it [00:00, ?it/s]                                                                                                                                                    
Reporting from local rank 0, index 0, batch tensor([ 354., 2137., 5853., 6143., 8100., 3042., 2407., 1548.])                                                  | 0/2 [00:00<?, ?it/s]
                                                                                                                                                                                  
Reporting from local rank 0, index 1, batch tensor([7737., 3251., 6256., 6769., 7153.,  720., 3561., 5881.])                                        | 1/2 [00:00<00:00, 2004.93it/s]
Epoch 0:  40%|██████████████████████████████████████████████▊                                                                      | 10000/25001 [00:12<00:18, 806.40it/s, v_num=0]
Reporting from local rank 1, index 0, batch tensor([8240., 9125., 2912., 3170., 9948., 9427., 6728., 1879.])                                                                       
Reporting from local rank 1, index 1, batch tensor([6557., 5502., 8580., 1543., 6386., 1750., 4385., 3793.])
                                                                                                                                                                                  
Reporting from local rank 0, index 0, batch tensor([ 354., 2137., 5853., 6143., 8100., 3042., 2407., 1548.])                                                  | 0/2 [00:00<?, ?it/s]
                                                                                                                                                                                  
Reporting from local rank 0, index 1, batch tensor([7737., 3251., 6256., 6769., 7153.,  720., 3561., 5881.])                                        | 1/2 [00:00<00:00, 1972.86it/s]
Epoch 0:  60%|██████████████████████████████████████████████████████████████████████▏                                              | 15000/25001 [00:18<00:12, 799.65it/s, v_num=0]
Reporting from local rank 1, index 0, batch tensor([8240., 9125., 2912., 3170., 9948., 9427., 6728., 1879.])                                                                       
Reporting from local rank 1, index 1, batch tensor([6557., 5502., 8580., 1543., 6386., 1750., 4385., 3793.])
                                                                                                                                                                                  
Reporting from local rank 0, index 0, batch tensor([ 354., 2137., 5853., 6143., 8100., 3042., 2407., 1548.])                                                  | 0/2 [00:00<?, ?it/s]
                                                                                                                                                                                  
Reporting from local rank 0, index 1, batch tensor([7737., 3251., 6256., 6769., 7153.,  720., 3561., 5881.])                                        | 1/2 [00:00<00:00, 1897.88it/s]
[ ... ] 
Environment
Current environment
  • CUDA:
    • GPU:
      • NVIDIA GeForce RTX 3080 Laptop GPU
    • available: True
    • version: 11.7
  • Lightning:
    • lightning-utilities: 0.8.0
    • pytorch-lightning: 2.0.3
    • torch: 2.0.1
    • torchaudio: 2.0.2
    • torchmetrics: 0.11.4
    • torchvision: 0.15.2
  • Packages:
    • aiohttp: 3.8.4
    • aiosignal: 1.3.1
    • appdirs: 1.4.4
    • async-timeout: 4.0.2
    • attrs: 23.1.0
    • certifi: 2023.5.7
    • charset-normalizer: 3.1.0
    • click: 8.1.3
    • cmake: 3.26.4
    • docker-pycreds: 0.4.0
    • filelock: 3.12.2
    • frozenlist: 1.3.3
    • fsspec: 2023.6.0
    • gitdb: 4.0.10
    • gitpython: 3.1.31
    • idna: 3.4
    • jinja2: 3.1.2
    • lightning-utilities: 0.8.0
    • lit: 16.0.6
    • markupsafe: 2.1.3
    • mpmath: 1.3.0
    • multidict: 6.0.4
    • networkx: 3.1
    • numpy: 1.24.3
    • nvidia-cublas-cu11: 11.10.3.66
    • nvidia-cuda-cupti-cu11: 11.7.101
    • nvidia-cuda-nvrtc-cu11: 11.7.99
    • nvidia-cuda-runtime-cu11: 11.7.99
    • nvidia-cudnn-cu11: 8.5.0.96
    • nvidia-cufft-cu11: 10.9.0.58
    • nvidia-curand-cu11: 10.2.10.91
    • nvidia-cusolver-cu11: 11.4.0.1
    • nvidia-cusparse-cu11: 11.7.4.91
    • nvidia-nccl-cu11: 2.14.3
    • nvidia-nvtx-cu11: 11.7.91
    • packaging: 23.1
    • pathtools: 0.1.2
    • pillow: 9.5.0
    • pip: 22.0.2
    • protobuf: 4.23.3
    • psutil: 5.9.5
    • pytorch-lightning: 2.0.3
    • pyyaml: 6.0
    • requests: 2.31.0
    • sentry-sdk: 1.25.1
    • setproctitle: 1.3.2
    • setuptools: 59.6.0
    • six: 1.16.0
    • smmap: 5.0.0
    • sympy: 1.12
    • torch: 2.0.1
    • torchaudio: 2.0.2
    • torchmetrics: 0.11.4
    • torchvision: 0.15.2
    • tqdm: 4.65.0
    • triton: 2.0.0
    • typing-extensions: 4.6.3
    • urllib3: 2.0.3
    • wandb: 0.15.4
    • wheel: 0.40.0
    • yarl: 1.9.2
  • System:
    • OS: Linux
    • architecture:
      • 64bit
      • ELF
    • processor: x86_64
    • python: 3.10.4
    • release: 5.17.15-76051715-generic
    • version: #202206141358165591911622.04~1db9e34 SMP PREEMPT Wed Jun 22 19
More info

No response

cc @justusschock @awaelchli

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.