Lightning-AI / Lightning-AI/pytorch-lightning

FSDP Fails with floating nn.Parameter

Open
#20,138 6 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug duplicate strategy: fsdp ver: 2.2.x
Dominant language
Python
Stars
31.4k
Forks
3.8k
Avg merge
6d 7h
Merged PRs (30d)
6

Description

Bug description

I'm training an adversarial model with PyTorch Lightning, similar to a GAN.:

  • The generator and discriminator each have their own optimizer.
  • We train the generator for G steps consecutively with a frozen discriminator and then train the discriminator for D steps consecutively with a frozen generator.

When I try training the model with FSDP strategy -- I receive errors during backprop:

  • The first step of training is successful in optimizing model 1 according to step 1.
  • This error emerges on the second step of training, when I toggle optimizers and take a step to optimize model 2 according to loss function 2. This leads me to believe that my error is tied to incorrect handling of the unused parameters.
What version are you seeing the problem on?

master

How to reproduce the bug
"""
File: test_fsdp.py
Description: Minimal example of FSDP failure with unused parameters
"""

import os

import torch
from torch import nn
from pytorch_lightning import LightningModule, Trainer
from torch.utils.data import DataLoader, Dataset


class AdversarialModel(LightningModule):

    def __init__(self):
        super().__init__()
        self.generator = GeneratorNetwork()
        self.discriminator = DiscriminatorNetwork()
        self.automatic_optimization = False

    def forward(self, x):
        return self.layer(x)

    def training_step(self, batch, batch_idx):
        opts = self.optimizers()
        current_cycle = batch_idx % len(opts)

        if current_cycle == 0:
            #  compute loss from generator
            self.computed_loss = self.generator(batch).mean()
        else:
            # compute loss from discriminator
            self.computed_loss = self.discriminator(batch).mean()

    def on_train_batch_end(self, outputs, batch, batch_idx):
        opts = self.optimizers()
        current_cycle = batch_idx % len(opts)
        opt = opts[current_cycle]

        with opt.toggle_model():
            self.manual_backward(self.computed_loss)
            opt.step()
            opt.zero_grad()

    def validation_step(self, batch, batch_idx):
        generator_loss = self.generator(batch).mean()
        discriminator_loss = self.discriminator(batch).mean()
        self.log("valid_generator_loss", generator_loss)
        self.log("valid_discriminator_loss", discriminator_loss)

    def configure_optimizers(self):
        return [
            torch.optim.SGD(self.generator.parameters(), lr=0.1),
            torch.optim.SGD(self.discriminator.parameters(), lr=0.1)
        ]


class GeneratorNetwork(nn.Module):

    def __init__(self):
        super().__init__()
        self.layer = torch.nn.Linear(32, 2)

    def forward(self, x):
        return self.layer(x)

    def parameters(self, recurse: bool = True):
        return self.layer.parameters(recurse=recurse)


class DiscriminatorNetwork(nn.Module):

    def __init__(self):
        super().__init__()
        self.layer = torch.nn.Linear(32, 2)

    def forward(self, x):
        return self.layer(x)

    def parameters(self, recurse: bool = True):
        return self.layer.parameters(recurse=recurse)


class RandomDataset(Dataset):

    def __init__(self, size, length):
        self.len = length
        self.data = torch.randn(length, size)

    def __getitem__(self, index):
        return self.data[index]

    def __len__(self):
        return self.len


def run():
    train_data = DataLoader(RandomDataset(32, 64), batch_size=2)
    val_data = DataLoader(RandomDataset(32, 64), batch_size=2)

    model = AdversarialModel()
    trainer = Trainer(default_root_dir=os.getcwd(),
                      limit_train_batches=10,
                      limit_val_batches=10,
                      num_sanity_val_steps=0,
                      max_epochs=1,
                      enable_model_summary=False,
                      num_nodes=1,
                      devices=8,
                      strategy='fsdp',
                      enable_progress_bar=True)
    trainer.fit(model, train_dataloaders=train_data, val_dataloaders=val_data)


if __name__ == "__main__":
    run()
Error messages and logs
ERROR: expected to be in states [<TrainingState.FORWARD_BACKWARD: 2>] but current state is TrainingState.IDLE
  File "/home/sahil/.cache/pypoetry/virtualenvs/project-auw7Hy33-py3.10/lib/python3.10/site-packages/torch/utils/_contextlib.py", line 115, in decorate_context
    return func(*args, **kwargs)
  File "/home/sahil/.cache/pypoetry/virtualenvs/project-auw7Hy33-py3.10/lib/python3.10/site-packages/torch/distributed/fsdp/_runtime_utils.py", line 708, in _post_backward_hook
    _assert_in_training_states(state, [TrainingState.FORWARD_BACKWARD])
  File "/home/sahil/.cache/pypoetry/virtualenvs/project-auw7Hy33-py3.10/lib/python3.10/site-packages/torch/distributed/fsdp/_common_utils.py", line 471, in _assert_in_training_states
    traceback.print_stack()
[rank0]: Traceback (most recent call last):
[rank0]:   File "/home/sahil/project/tests/test_fsdp.py", line 119, in <module>
[rank0]:     run()
[rank0]:   File "/home/sahil/project/tests/test_fsdp.py", line 115, in run
[rank0]:     trainer.fit(model, train_dataloaders=train_data, val_dataloaders=val_data)
[rank0]:   File "/home/sahil/.cache/pypoetry/virtualenvs/project-auw7Hy33-py3.10/lib/python3.10/site-packages/pytorch_lightning/trainer/trainer.py", line 544, in fit
[rank0]:     call._call_and_handle_interrupt(
[rank0]:   File "/home/sahil/.cache/pypoetry/virtualenvs/project-auw7Hy33-py3.10/lib/python3.10/site-packages/pytorch_lightning/trainer/call.py", line 43, in _call_and_handle_interrupt
[rank0]:     return trainer.strategy.launcher.launch(trainer_fn, *args, trainer=trainer, **kwargs)
[rank0]:   File "/home/sahil/.cache/pypoetry/virtualenvs/project-auw7Hy33-py3.10/lib/python3.10/site-packages/pytorch_lightning/strategies/launchers/subprocess_script.py", line 105, in launch
[rank0]:     return function(*args, **kwargs)
[rank0]:   File "/home/sahil/.cache/pypoetry/virtualenvs/project-auw7Hy33-py3.10/lib/python3.10/site-packages/pytorch_lightning/trainer/trainer.py", line 580, in _fit_impl
[rank0]:     self._run(model, ckpt_path=ckpt_path)
[rank0]:   File "/home/sahil/.cache/pypoetry/virtualenvs/project-auw7Hy33-py3.10/lib/python3.10/site-packages/pytorch_lightning/trainer/trainer.py", line 990, in _run
[rank0]:     results = self._run_stage()
[rank0]:   File "/home/sahil/.cache/pypoetry/virtualenvs/project-auw7Hy33-py3.10/lib/python3.10/site-packages/pytorch_lightning/trainer/trainer.py", line 1034, in _run_stage
[rank0]:     self.fit_loop.run()
[rank0]:   File "/home/sahil/.cache/pypoetry/virtualenvs/project-auw7Hy33-py3.10/lib/python3.10/site-packages/pytorch_lightning/loops/fit_loop.py", line 205, in run
[rank0]:     self.advance()
[rank0]:   File "/home/sahil/.cache/pypoetry/virtualenvs/project-auw7Hy33-py3.10/lib/python3.10/site-packages/pytorch_lightning/loops/fit_loop.py", line 363, in advance
[rank0]:     self.epoch_loop.run(self._data_fetcher)
[rank0]:   File "/home/sahil/.cache/pypoetry/virtualenvs/project-auw7Hy33-py3.10/lib/python3.10/site-packages/pytorch_lightning/loops/training_epoch_loop.py", line 140, in run
[rank0]:     self.advance(data_fetcher)
[rank0]:   File "/home/sahil/.cache/pypoetry/virtualenvs/project-auw7Hy33-py3.10/lib/python3.10/site-packages/pytorch_lightning/loops/training_epoch_loop.py", line 270, in advance
[rank0]:     call._call_lightning_module_hook(trainer, "on_train_batch_end", batch_output, batch, batch_idx)
[rank0]:   File "/home/sahil/.cache/pypoetry/virtualenvs/project-auw7Hy33-py3.10/lib/python3.10/site-packages/pytorch_lightning/trainer/call.py", line 159, in _call_lightning_module_hook
[rank0]:     output = fn(*args, **kwargs)
[rank0]:   File "/home/sahil/project/tests/test_fsdp.py", line 44, in on_train_batch_end
[rank0]:     self.manual_backward(self.computed_loss)
[rank0]:   File "/home/sahil/.cache/pypoetry/virtualenvs/project-auw7Hy33-py3.10/lib/python3.10/site-packages/pytorch_lightning/core/module.py", line 1084, in manual_backward
[rank0]:     self.trainer.strategy.backward(loss, None, *args, **kwargs)
[rank0]:   File "/home/sahil/.cache/pypoetry/virtualenvs/project-auw7Hy33-py3.10/lib/python3.10/site-packages/pytorch_lightning/strategies/strategy.py", line 212, in backward
[rank0]:     self.precision_plugin.backward(closure_loss, self.lightning_module, optimizer, *args, **kwargs)
[rank0]:   File "/home/sahil/.cache/pypoetry/virtualenvs/project-auw7Hy33-py3.10/lib/python3.10/site-packages/pytorch_lightning/plugins/precision/precision.py", line 72, in backward
[rank0]:     model.backward(tensor, *args, **kwargs)
[rank0]:   File "/home/sahil/.cache/pypoetry/virtualenvs/project-auw7Hy33-py3.10/lib/python3.10/site-packages/pytorch_lightning/core/module.py", line 1103, in backward
[rank0]:     loss.backward(*args, **kwargs)
[rank0]:   File "/home/sahil/.cache/pypoetry/virtualenvs/project-auw7Hy33-py3.10/lib/python3.10/site-packages/torch/_tensor.py", line 525, in backward
[rank0]:     torch.autograd.backward(
[rank0]:   File "/home/sahil/.cache/pypoetry/virtualenvs/project-auw7Hy33-py3.10/lib/python3.10/site-packages/torch/autograd/__init__.py", line 267, in backward
[rank0]:     _engine_run_backward(
[rank0]:   File "/home/sahil/.cache/pypoetry/virtualenvs/project-auw7Hy33-py3.10/lib/python3.10/site-packages/torch/autograd/graph.py", line 744, in _engine_run_backward
[rank0]:     return Variable._execution_engine.run_backward(  # Calls into the C++ engine to run the backward pass
[rank0]:   File "/home/sahil/.cache/pypoetry/virtualenvs/project-auw7Hy33-py3.10/lib/python3.10/site-packages/torch/utils/_contextlib.py", line 115, in decorate_context
[rank0]:     return func(*args, **kwargs)
[rank0]:   File "/home/sahil/.cache/pypoetry/virtualenvs/project-auw7Hy33-py3.10/lib/python3.10/site-packages/torch/distributed/fsdp/_runtime_utils.py", line 708, in _post_backward_hook
[rank0]:     _assert_in_training_states(state, [TrainingState.FORWARD_BACKWARD])
[rank0]:   File "/home/sahil/.cache/pypoetry/virtualenvs/project-auw7Hy33-py3.10/lib/python3.10/site-packages/torch/distributed/fsdp/_common_utils.py", line 472, in _assert_in_training_states
[rank0]:     raise ValueError(msg)
[rank0]: ValueError: expected to be in states [<TrainingState.FORWARD_BACKWARD: 2>] but current state is TrainingState.IDLE
Environment
Current environment
  • CUDA:
    - GPU:
    - NVIDIA H100 80GB HBM3
    - NVIDIA H100 80GB HBM3
    - NVIDIA H100 80GB HBM3
    - NVIDIA H100 80GB HBM3
    - NVIDIA H100 80GB HBM3
    - NVIDIA H100 80GB HBM3
    - NVIDIA H100 80GB HBM3
    - NVIDIA H100 80GB HBM3
    - available: True
    - version: 12.1
  • Lightning:
    - lightning-utilities: 0.11.5
    - pytorch-lightning: 2.3.3
    - torch: 2.3.1
    - torchmetrics: 1.4.0.post0
    - torchvision: 0.18.1
  • Packages:
    - aiohttp: 3.9.5
    - aiosignal: 1.3.1
    - annotated-types: 0.7.0
    - antlr4-python3-runtime: 4.9.3
    - anyio: 4.4.0
    - argon2-cffi: 23.1.0
    - argon2-cffi-bindings: 21.2.0
    - arrow: 1.3.0
    - asttokens: 2.4.1
    - async-lru: 2.0.4
    - async-timeout: 4.0.3
    - attrs: 23.2.0
    - autocommand: 2.2.2
    - babel: 2.15.0
    - backports.tarfile: 1.2.0
    - beautifulsoup4: 4.12
    - notebook-shim: 0.2.4
    - numpy: 1.26.4
    - nvidia-cublas-cu12: 12.1.3.1
    - nvidia-cuda-cupti-cu12: 12.1.105
    - nvidia-cuda-nvrtc-cu12: 12.1.105
    - nvidia-cuda-runtime-cu12: 12.1.105
    - nvidia-cudnn-cu12: 8.9.2.26
    - nvidia-cufft-cu12: 11.0.2.54
    - nvidia-curand-cu12: 10.3.2.106
    - nvidia-cusolver-cu12: 11.4.5.107
    - nvidia-cusparse-cu12: 12.1.0.106
    - nvidia-nccl-cu12: 2.20.5
    - nvidia-nvjitlink-cu12: 12.5.82
    - nvidia-nvtx-cu12: 12.1.105
    - omegaconf: 2.3.0
    - opencv-python: 4.10.0.84
    - ordered-set: 4.1.0
    - overrides: 7.7.0
    - packaging: 24.1
    - pandocfilters: 1.5.1
    - parso: 0.8.4
    - pexpect: 4.9.0
    - pillow: 10.4.0
    - pip: 24.1
    - platformdirs: 4.2.2
    - pre-commit: 3.7.1
    - proglog: 0.1.10
    - prometheus-client: 0.20.0
    - prompt-toolkit: 3.0.47
    - protobuf: 5.27.2
    - psutil: 6.0.0
    - ptyprocess: 0.7.0
    - pure-eval: 0.2.2
    - pybind11: 2.13.1
    - pycparser: 2.22
    - pydantic: 2.8.2
    - pydantic-core: 2.20.1
    - pydantic-settings: 2.3.4
    - pygments: 2.18.0
    - python-dateutil: 2.9.0.post0
    - python-dotenv: 1.0.1
    - python-json-logger: 2.0.7
    - pytorch-lightning: 2.3.3
    - pyyaml: 6.0.1
    - pyzmq: 26.0.3
    - referencing: 0.35.1
    - regex: 2024.5.15
    - requests: 2.32.3
    - rfc3339-validator: 0.1.4
    - rfc3986-validator: 0.1.1
    - rpds-py: 0.19.0
    - s3transfer: 0.10.2
    - safetensors: 0.4.3
    - send2trash: 1.8.3
    - sentry-sdk: 2.10.0
    - setproctitle: 1.3.3
    - setuptools: 71.0.2
    - six: 1.16.0
    - smmap: 5.0.1
    - sniffio: 1.3.1
    - soupsieve: 2.5
    - stack-data: 0.6.3
    - sympy: 1.13.0
    - terminado: 0.18.1
    - tinycss2: 1.3.0
    - tokenizers: 0.19.1
    - tomli: 2.0.1
    - torch: 2.3.1
    - torchmetrics: 1.4.0.post0
    - torchvision: 0.18.1
    - tornado: 6.4.1
    - tqdm: 4.66.4
    - traitlets: 5.14.3
    - transformers: 4.43.1
    - triton: 2.3.1
    - typeguard: 4.3.0
    - types-python-dateutil: 2.9.0.20240316
    - typing-extensions: 4.12.2
    - uri-template: 1.3.0
    - urllib3: 2.2.2
    - virtualenv: 20.26.3
    - wandb: 0.17.4
    - wcwidth: 0.2.13
    - webcolors: 24.6.0
    - webdataset: 0.2.86
    - webencodings: 0.5.1
    - websocket-client: 1.8.0
    - wheel: 0.43.0
    - yarl: 1.9.4
    - zipp: 3.19.2
  • System:
    - OS: Linux
    - architecture:
    - 64bit
    - ELF
    - processor: x86_64
    - python: 3.10.12
    - release: 5.15.0-1048-oracle
    - version: 54-Ubuntu SMP Wed Nov 8 15:12:17 UTC 2023
More info

Originally, I thought it was a PyTorch issue -- https://github.com/pytorch/pytorch/issues/132068

But after converting my reproduction script to raw torch, the error went away -- so my hunch is that there is an edge case in the PyTorch Lightning FSDP wrapper.

cc @awaelchli @carmocca

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.

Research direction

Start with the reproducer in test_fsdp.py, especially training_step, on_train_batch_end, and the manual_backward call, then trace the FSDP failure at torch/distributed/fsdp/_runtime_utils.py:708. Run the two-optimizer example with the stated FSDP setup and compare the first and second optimization steps. Done means the discriminator step completes without the TrainingState.IDLE error.

Written by the indexing model from the issue text.

Assessment

Tech stack
python, pytorch
Domain
distributed-systems, machine-learning
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.