Lightning-AI/pytorch-lightning · error · MisconfigurationException

The closure hasn't been executed. HINT: did you call `optimi

Error message

The closure hasn't been executed. HINT: did you call `optimizer_closure()` in your `optimizer_step` hook? It could also happen because the `optimizer.step(optimizer_closure)` call did not execute it internally.

What it means

Raised by Closure.consume_result when the optimizer closure was never executed before its result was consumed. In automatic optimization Lightning wraps forward+backward in a closure passed to optimizer.step(); if a custom optimizer_step hook neither calls closure() itself nor passes it to optimizer.step(closure) so it runs internally, there is no result to read.

Source

Thrown at src/lightning/pytorch/loops/optimization/closure.py:53

    This class provides a simple abstraction making the instance of this class callable like a function while capturing
    the closure result and caching it.

    """

    def __init__(self) -> None:
        super().__init__()
        self._result: Optional[T] = None

    def consume_result(self) -> T:
        """The cached result from the last time the closure was called.

        Once accessed, the internal reference gets reset and the consumer will have to hold on to the reference as long
        as necessary.

        """
        if self._result is None:
            raise MisconfigurationException(
                "The closure hasn't been executed."
                " HINT: did you call `optimizer_closure()` in your `optimizer_step` hook? It could also happen because"
                " the `optimizer.step(optimizer_closure)` call did not execute it internally."
            )
        result, self._result = self._result, None  # free memory
        return result

    @abstractmethod
    def closure(self, *args: Any, **kwargs: Any) -> T:
        """Implements the behavior of the closure once it is getting called."""
        pass

    def __call__(self, *args: Any, **kwargs: Any) -> Any:
        self._result = self.closure(*args, **kwargs)
        return self

View on GitHub (pinned to 9fed5c27d2)

Solutions

  1. Call the closure first in your hook: `optimizer_closure(); optimizer.step()`
  2. Or pass it through: `optimizer.step(closure=optimizer_closure)`
  3. Prefer `Trainer(gradient_clip_val=...)` / configure_optimizers instead of overriding optimizer_step

Example fix

# before
def optimizer_step(self, epoch, batch_idx, optimizer, optimizer_closure):
    optimizer.step()  # closure never runs

# after
def optimizer_step(self, epoch, batch_idx, optimizer, optimizer_closure):
    optimizer_closure()
    optimizer.step()
Defensive patterns

Strategy: validation

Validate before calling

# lint your overrides: any optimizer_step must reference optimizer_closure
import inspect
src = inspect.getsource(MyModel.optimizer_step)
assert 'optimizer_closure' in src

Prevention

When it happens

Trigger: Overriding `def optimizer_step(self, epoch, batch_idx, optimizer, optimizer_closure): optimizer.step()` without invoking optimizer_closure(); using an optimizer wrapper/plugin that drops the closure argument; certain LBFGS-style optimizers requiring special handling.

Common situations: Custom optimizer_step for gradient clipping (though clip_grad is preferred), LR-decay-per-step hacks, or integrating Apex/fairscale optimizers while forgetting the closure.

Related errors


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