Lightning-AI/pytorch-lightning · error · MisconfigurationException
In manual optimization, `training_step` must either return a
Error message
In manual optimization, `training_step` must either return a Tensor or have no return.
What it means
Raised by ClosureResult.from_training_step_output in the manual optimization path when training_step returns something that is neither a Tensor, a Mapping, nor None. Manual optimization tolerates dict returns (treated as extra logging values with an optional 'loss') but any other type (tuple, list, float) is rejected.
Source
Thrown at src/lightning/pytorch/loops/optimization/manual.py:54
It is created from the output of :meth:`~lightning.pytorch.core.LightningModule.training_step`.
Attributes:
extra: Anything returned by the ``training_step``.
"""
extra: dict[str, Any] = field(default_factory=dict)
@classmethod
def from_training_step_output(cls, training_step_output: STEP_OUTPUT) -> "ManualResult":
extra = {}
if isinstance(training_step_output, Mapping):
extra = training_step_output.copy()
elif isinstance(training_step_output, Tensor):
extra = {"loss": training_step_output}
elif training_step_output is not None:
raise MisconfigurationException(
"In manual optimization, `training_step` must either return a Tensor or have no return."
)
if "loss" in extra:
# we detach manually as it's expected that it will have a `grad_fn`
extra["loss"] = extra["loss"].detach()
return cls(extra=extra)
@override
def asdict(self) -> dict[str, Any]:
return self.extra
_OUTPUTS_TYPE = dict[str, Any]
class _ManualOptimization(_Loop):View on GitHub (pinned to 9fed5c27d2)
Solutions
- Return only the loss tensor, a dict, or nothing at all
- For multiple losses, return a dict: `return {'loss_g': loss_g, 'loss_d': loss_d}` and step optimizers manually inside training_step
Example fix
# before
self.automatic_optimization = False
def training_step(self, batch, batch_idx):
...
return loss_d, loss_g # tuple -> raises
# after
def training_step(self, batch, batch_idx):
...
self.opt_d.step(); self.opt_g.step()
return {'loss_d': loss_d, 'loss_g': loss_g} Defensive patterns
Strategy: type-guard
Validate before calling
out = self.training_step(batch, batch_idx) assert out is None or isinstance(out, (torch.Tensor, Mapping))
Type guard
def valid_manual_step_output(out) -> bool:
return out is None or isinstance(out, (torch.Tensor, Mapping)) Prevention
- In manual optimization return only Tensor, dict, or None; step optimizers inside training_step
- Return a dict of named losses for GAN-style multi-optimizer loops
When it happens
Trigger: `self.automatic_optimization = False` plus `training_step` returning `loss, logits` or `loss.item()`; GAN training loops returning tuples of discriminator/generator losses.
Common situations: Writing manual optimization for GANs or RL where multiple optimizers step manually; returning unpacked tuples by habit from vanilla PyTorch code.
Related errors
- In automatic optimization, `training_step` must return a Ten
- With `def training_step(self, dataloader_iter)`, `self.log(.
- to use {fn_name}, please disable automatic optimization: set
- In automatic_optimization, when `training_step` returns a di
- Skipping the `training_step` by returning None in distribute
AI-assisted analysis of Lightning-AI/pytorch-lightning@9fed5c27d2 (2026-08-28).
Data as JSON: /api/errors/139d87f5588abc8a.
Report an issue: GitHub.