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

  1. Return only the loss tensor, a dict, or nothing at all
  2. 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

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


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