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
- Call the closure first in your hook: `optimizer_closure(); optimizer.step()`
- Or pass it through: `optimizer.step(closure=optimizer_closure)`
- 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
- Always call optimizer_closure() (or optimizer.step(closure=...)) inside custom optimizer_step
- Prefer built-in mechanisms (gradient_clip_val, configure_optimizers) over optimizer_step overrides
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
- In automatic_optimization, when `training_step` returns a di
- In automatic optimization, `training_step` must return a Ten
- AMP and the LBFGS optimizer are not compatible.
- Skipping backward by returning `None` from your `training_st
- Device should be CPU, got {device} instead.
AI-assisted analysis of Lightning-AI/pytorch-lightning@9fed5c27d2 (2026-08-28).
Data as JSON: /api/errors/b44542cd1511e14c.
Report an issue: GitHub.