{"record":{"id":"f83dd0db5eb392b1","repo":"Lightning-AI/pytorch-lightning","slug":"training-step-returned-none-if-this-was-on-pu","errorCode":null,"errorMessage":"`training_step` returned `None`. If this was on purpose, ignore this warning...","messagePattern":"`training_step` returned `None`\\. If this was on purpose, ignore this warning\\.\\.\\.","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"src/lightning/pytorch/loops/optimization/automatic.py","lineNumber":134,"sourceCode":"\n    def __init__(\n        self,\n        step_fn: Callable[[], ClosureResult],\n        backward_fn: Optional[Callable[[Tensor], None]] = None,\n        zero_grad_fn: Optional[Callable[[], None]] = None,\n    ):\n        super().__init__()\n        self._step_fn = step_fn\n        self._backward_fn = backward_fn\n        self._zero_grad_fn = zero_grad_fn\n\n    @override\n    @torch.enable_grad()\n    def closure(self, *args: Any, **kwargs: Any) -> ClosureResult:\n        step_output = self._step_fn()\n\n        if step_output.closure_loss is None:\n            self.warning_cache.warn(\"`training_step` returned `None`. If this was on purpose, ignore this warning...\")\n\n        if self._zero_grad_fn is not None:\n            self._zero_grad_fn()\n\n        if self._backward_fn is not None and step_output.closure_loss is not None:\n            self._backward_fn(step_output.closure_loss)\n\n        return step_output\n\n    @override\n    def __call__(self, *args: Any, **kwargs: Any) -> Optional[Tensor]:\n        self._result = self.closure(*args, **kwargs)\n        return self._result.loss\n\n\n_OUTPUTS_TYPE = dict[str, Any]\n\n","sourceCodeStart":116,"sourceCodeEnd":152,"githubUrl":"https://github.com/Lightning-AI/pytorch-lightning/blob/9fed5c27d2a62ff0efd6c3573599921d6ff67c14/src/lightning/pytorch/loops/optimization/automatic.py#L116-L152","documentation":"AutomaticOptimization's closure warns when training_step returns None (closure_loss is None), meaning no loss was produced so no backward/step occurs for that batch. It is a warning only if intentional (e.g. skipping batches).","triggerScenarios":"training_step with conditional return (returning None on skip), a bug like forgetting return loss, or returning a dict/tensor shape the loss extraction can't read ('loss' key missing yields None closure loss path).","commonSituations":"Gradient-accumulation or dynamic batch skipping logic; refactoring training_step and dropping the return statement.","solutions":["Ensure training_step returns the loss (or dict with 'loss' key) on the training path","If skipping is intentional, keep the warning or return a zero-attached loss pattern","Inspect step_output.closure_loss in on_train_batch_end when debugging"],"exampleFix":"# before\ndef training_step(self, batch, i):\n    loss = self.loss(batch)\n    # forgot return\n# after\ndef training_step(self, batch, i):\n    loss = self.loss(batch)\n    return loss","handlingStrategy":"validation","validationCode":"out = model.training_step(batch, 0)\nassert out is not None and (torch.is_tensor(out) or 'loss' in out), 'training_step must return loss'","typeGuard":"def returns_loss(step_out) -> bool:\n    import torch\n    return torch.is_tensor(step_out) or (isinstance(step_out, dict) and step_out.get('loss') is not None)","tryCatchPattern":null,"preventionTips":["Always return loss from training_step","Unit-test training_step's return value in CI"],"tags":["training-step","loss","automatic-optimization","lightning"],"backgroundTag":"training-step-returned-none","analyzedSha":"9fed5c27d2a62ff0efd6c3573599921d6ff67c14","analyzedAt":"2026-08-28T11:52:41.083Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}