Lightning-AI/pytorch-lightning · error · MisconfigurationException

When `optimizer.step(closure)` is called, the closure should

Error message

When `optimizer.step(closure)` is called, the closure should be callable

What it means

LightningOptimizer.step(closure) requires the closure to be callable (it is re-executed by the strategy during optimization). Passing a non-callable (e.g. a tensor, result of calling the closure, or None-like object) raises MisconfigurationException.

Source

Thrown at src/lightning/pytorch/core/optimizer.py:151

                with opt_gen.toggle_model(sync_grad=accumulated_grad_batches):
                    opt_gen.step(closure=closure_gen)

                def closure_dis():
                    loss_dis = self.compute_discriminator_loss(...)
                    self.manual_backward(loss_dis)
                    if accumulated_grad_batches:
                        opt_dis.zero_grad()

                with opt_dis.toggle_model(sync_grad=accumulated_grad_batches):
                    opt_dis.step(closure=closure_dis)

        """
        self._on_before_step()

        if closure is None:
            closure = do_nothing_closure
        elif not callable(closure):
            raise MisconfigurationException("When `optimizer.step(closure)` is called, the closure should be callable")

        assert self._strategy is not None
        step_output = self._strategy.optimizer_step(self._optimizer, closure, **kwargs)

        self._on_after_step()

        return step_output

    @classmethod
    def _to_lightning_optimizer(
        cls, optimizer: Union[Optimizer, "LightningOptimizer"], strategy: "pl.strategies.Strategy"
    ) -> "LightningOptimizer":
        # the user could return a `LightningOptimizer` from `configure_optimizers`, see test:
        # tests/core/test_lightning_optimizer.py::test_lightning_optimizer[False]
        lightning_optimizer = optimizer if isinstance(optimizer, LightningOptimizer) else cls(optimizer)
        lightning_optimizer._strategy = proxy(strategy)
        return lightning_optimizer

View on GitHub (pinned to 9fed5c27d2)

Solutions

  1. Pass the closure itself, not its result: optimizer.step(closure) where closure is a zero-arg callable
  2. Wrap logic in a def or lambda: optimizer.step(lambda: self.training_step(batch, batch_idx))

Example fix

# before
optimizer.step(self.training_step(batch, batch_idx))
# after
optimizer.step(lambda: self.training_step(batch, batch_idx))
Defensive patterns

Strategy: type-guard

Validate before calling

assert callable(closure), "closure must be callable"
optimizer.step(closure)

Type guard

def is_closure(c) -> bool:
    return callable(c)

Prevention

When it happens

Trigger: Calling optimizer.step(training_step(...)) — i.e. passing the closure's return value instead of the function — or passing a non-function object in manual optimization.

Common situations: Manual optimization code that accidentally invokes the closure: optimizer.step(self.training_step(batch, batch_idx)) instead of passing the method reference.

Related errors


AI-assisted analysis of Lightning-AI/pytorch-lightning@9fed5c27d2 (2026-08-28). Data as JSON: /api/errors/749a215bdb3a9cc1. Report an issue: GitHub.