Repository: Lightning-AI/pytorch-lightning
Pull request: Fix erroneous UserWarning for unset max_epochs
## What does this PR do?

Fixes #13257. @akihironitta

Now the warning is raised only during `trainer.fit` call.

### Does your PR introduce any breaking changes? If yes, please list them.

None

## Before submitting

- [x] Was this **discussed/approved** via a GitHub issue? (not for typos and docs)
- [x] Did you read the [contributor guideline](https://github.com/PyTorchLightning/pytorch-lightning/blob/master/.github/CONTRIBUTING.md), **Pull Request** section?
- [x] Did you make sure your **PR does only one thing**, instead of bundling different changes together?
- [x] Did you make sure to **update the documentation** with your changes? (if necessary)
- [x] Did you write any **new necessary tests**? (not for typos and docs)
- [x] Did you verify new and **existing tests pass** locally with your changes?
- [x] Did you list all the **breaking changes** introduced by this pull request?
- [ ] Did you **update the [CHANGELOG](https://github.com/PyTorchLightning/pytorch-lightning/blob/master/CHANGELOG.md)**? (not for typos, docs, test updates, or minor internal changes/refactors)

<!-- In the CHANGELOG, separate each item in the unreleased section by a blank line to reduce collisions -->

## PR review

Anyone in the community is welcome to review the PR.
Before you start reviewing, make sure you have read the [review guidelines](https://github.com/PyTorchLightning/pytorch-lightning/wiki/Review-guidelines). In short, see the following bullet-list:

- [x] Is this pull request ready for review? (if not, please submit in draft mode)
- [x] Check that all items from **Before submitting** are resolved
- [x] Make sure the title is self-explanatory and the description concisely explains the PR
- [x] Add labels and milestones (and optionally projects) to the PR so it can be classified

## Did you have fun?

Make sure you had fun coding 🙃


Problem statement:
Erroneous UserWarning for unset max_epochs 
## 🐛 Bug

When I use:
```python
Trainer(fast_dev_run=True)
```
I see the following warning:
```
pytorch_lightning.utilities.warnings.PossibleUserWarning: `max_epochs` was not set. Setting it to 1000 epochs. To train without an epoch limit, set `max_epochs=-1`.
```
However, `fast_dev_run=True` implies `max_epochs=1`, so shouldn't this warning be skipped?

### To Reproduce

Use code like:
```python
Trainer(fast_dev_run=True)
```
in your code with warnings enabled or captured by pytest.

### Expected behavior

No warning message.

### Environment

- PyTorch Lightning Version (e.g., 1.5.0): 1.6.4
- PyTorch Version (e.g., 1.10): 1.11.0
- Python version (e.g., 3.9): 3.10.4, etc.
- OS (e.g., Linux): Linux, etc.
- CUDA/cuDNN version: N/A
- GPU models and configuration: N/A
- How you installed PyTorch (`conda`, `pip`, source): pip
- If compiling from source, the output of `torch.__config__.show()`: N/A
- Any other relevant information: N/A

cc @justusschock @kaushikb11 @awaelchli @borda @rohitgr7

Hints from issue discussion:
@adamjstewart Thank you for submitting the issue!

I don't have a strong preference, but I find it reasonable to raise the warning even when `fast_dev_run` is specified because one will have to deal with the warning anyway once `fast_dev_run` is disabled. :eyes:
In my case, this is inside a unit test, so `fast_dev_run` will never be disabled. If `fast_dev_run==True`, we only run a single batch, so warning about setting `max_epochs` to 1000 seems odd.

Commit message: Merge branch 'master' into fixes/fast-dev-run

Patch to review:
diff --git a/src/pytorch_lightning/CHANGELOG.md b/src/pytorch_lightning/CHANGELOG.md
index aa66df9b54a8b..bc8fa846c3e9e 100644
--- a/src/pytorch_lightning/CHANGELOG.md
+++ b/src/pytorch_lightning/CHANGELOG.md
@@ -360,7 +360,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
 - Fixed issue where the CLI fails with certain torch objects ([#13153](https://github.com/Lightning-AI/lightning/pull/13153))
 
 
-- Fixed ``LightningCLI`` signature parameter resolving for some lightning classes ([#13283](https://github.com/Lightning-AI/lightning/pull/13283))
+- Fixed an issue where users would be warned about unset `max_epochs` even when `fast_dev_run` was set ([#13262](https://github.com/Lightning-AI/lightning/pull/13262))
+
+
+- Fixed `LightningCLI` signature parameter resolving for some lightning classes ([#13283](https://github.com/Lightning-AI/lightning/pull/13283))
 
 
 - Fixed Model Summary when using DeepSpeed Stage 3 ([#13427](https://github.com/Lightning-AI/lightning/pull/13427))
diff --git a/src/pytorch_lightning/callbacks/stochastic_weight_avg.py b/src/pytorch_lightning/callbacks/stochastic_weight_avg.py
index 8c141b28bc93d..20a3dcc3f0f26 100644
--- a/src/pytorch_lightning/callbacks/stochastic_weight_avg.py
+++ b/src/pytorch_lightning/callbacks/stochastic_weight_avg.py
@@ -155,6 +155,7 @@ def on_fit_start(self, trainer: "pl.Trainer", pl_module: "pl.LightningModule") -
         self._max_epochs = trainer.max_epochs
         if self._model_contains_batch_norm:
             # virtually increase max_epochs to perform batch norm update on latest epoch.
+            assert trainer.fit_loop.max_epochs is not None
             trainer.fit_loop.max_epochs += 1
 
     def on_train_epoch_start(self, trainer: "pl.Trainer", pl_module: "pl.LightningModule") -> None:
@@ -227,6 +228,7 @@ def on_train_end(self, trainer: "pl.Trainer", pl_module: "pl.LightningModule") -
             # BatchNorm epoch update. Reset state
             trainer.accumulate_grad_batches = self._accumulate_grad_batches
             trainer.num_training_batches -= 1
+            assert trainer.fit_loop.max_epochs is not None
             trainer.fit_loop.max_epochs -= 1
             self.reset_momenta()
         elif trainer.current_epoch - 1 == self.swa_end:
diff --git a/src/pytorch_lightning/loops/fit_loop.py b/src/pytorch_lightning/loops/fit_loop.py
index f4f7735f4b66e..9e0d53c66b4e3 100644
--- a/src/pytorch_lightning/loops/fit_loop.py
+++ b/src/pytorch_lightning/loops/fit_loop.py
@@ -50,10 +50,10 @@ class FitLoop(Loop[None]):
     def __init__(
         self,
         min_epochs: int = 0,
-        max_epochs: int = 1000,
+        max_epochs: Optional[int] = None,
     ) -> None:
         super().__init__()
-        if max_epochs < -1:
+        if isinstance(max_epochs, int) and max_epochs < -1:
             # Allow max_epochs to be zero, since this will be handled by fit_loop.done
             raise MisconfigurationException(
                 f"`max_epochs` must be a non-negative integer or -1. You passed in {max_epochs}."
@@ -162,6 +162,7 @@ def done(self) -> bool:
 
         # `processed` is increased before `on_train_epoch_end`, the hook where checkpoints are typically saved.
         # we use it here because the checkpoint data won't have `completed` increased yet
+        assert isinstance(self.max_epochs, int)
         stop_epochs = _is_max_limit_reached(self.epoch_progress.current.processed, self.max_epochs)
         if stop_epochs:
             # in case they are not equal, override so `trainer.current_epoch` has the expected value
diff --git a/src/pytorch_lightning/loops/utilities.py b/src/pytorch_lightning/loops/utilities.py
index d0631f5953e2f..491af6605c135 100644
--- a/src/pytorch_lightning/loops/utilities.py
+++ b/src/pytorch_lightning/loops/utilities.py
@@ -14,9 +14,8 @@
 import inspect
 from collections import OrderedDict
 from contextlib import contextmanager
-from datetime import timedelta
 from functools import lru_cache
-from typing import Any, Callable, Dict, Generator, List, Optional, Sequence, Tuple, Union
+from typing import Any, Callable, Generator, List, Optional, Sequence, Tuple
 
 import numpy as np
 import torch
@@ -25,6 +24,7 @@
 from torch.utils.data import DataLoader
 
 import pytorch_lightning as pl
+from pytorch_lightning.callbacks.timer import Timer
 from pytorch_lightning.loops import Loop
 from pytorch_lightning.strategies import ParallelStrategy, Strategy
 from pytorch_lightning.trainer.progress import BaseProgress
@@ -69,12 +69,8 @@ def _extract_hiddens(training_step_output: STEP_OUTPUT, truncated_bptt_steps: in
 
 
 def _parse_loop_limits(
-    min_steps: Optional[int],
-    max_steps: int,
-    min_epochs: Optional[int],
-    max_epochs: int,
-    max_time: Optional[Union[str, timedelta, Dict[str, int]]],
-) -> Tuple[Optional[int], int, int, int, Optional[Union[str, timedelta, Dict[str, int]]]]:
+    min_steps: Optional[int], max_steps: int, min_epochs: Optional[int], max_epochs: int, trainer: "pl.Trainer"
+) -> Tuple[int, int]:
     """This utility computes the default values for the minimum and maximum number of steps and epochs given the
     values the user has selected.
 
@@ -83,13 +79,13 @@ def _parse_loop_limits(
         max_steps: Maximum number of steps.
         min_epochs: Minimum number of epochs.
         max_epochs: Maximum number of epochs.
-        max_time: Maximum time for the training.
+        trainer: Trainer instance.
 
     Returns:
         The parsed limits, with default values being set for the ones that the user did not specify.
     """
     if max_epochs is None:
-        if max_steps == -1 and max_time is None:
+        if max_steps == -1 and not any(isinstance(cb, Timer) for cb in trainer.callbacks):
             rank_zero_warn(
                 "`max_epochs` was not set. Setting it to 1000 epochs. To train without an epoch limit,"
                 " set `max_epochs=-1`.",
@@ -98,13 +94,16 @@ def _parse_loop_limits(
             max_epochs = 1000
         else:
             max_epochs = -1
+
     if min_epochs is None and min_steps is not None:
         # setting this allows FitLoop.done to re-evaluate should_stop when it gets triggered `on_fit_start`
         min_epochs = 1
+
     if min_epochs is None:
         # the default value is 0 so no training will be done when should_stop is triggered `on_fit_start`
         min_epochs = 0
-    return min_steps, max_steps, min_epochs, max_epochs, max_time
+
+    return min_epochs, max_epochs
 
 
 def _build_training_step_kwargs(
diff --git a/src/pytorch_lightning/trainer/trainer.py b/src/pytorch_lightning/trainer/trainer.py
index 561fe799f1010..e69ec8891a8d3 100644
--- a/src/pytorch_lightning/trainer/trainer.py
+++ b/src/pytorch_lightning/trainer/trainer.py
@@ -455,9 +455,22 @@ def __init__(
         self._signal_connector = SignalConnector(self)
         self.tuner = Tuner(self)
 
-        min_steps, max_steps, min_epochs, max_epochs, max_time = _parse_loop_limits(
-            min_steps, max_steps, min_epochs, max_epochs, max_time
+        # Declare attributes to be set in _callback_connector on_trainer_init
+        self._callback_connector.on_trainer_init(
+            callbacks,
+            enable_checkpointing,
+            enable_progress_bar,
+            default_root_dir,
+            weights_save_path,
+            enable_model_summary,
+            max_time,
+            accumulate_grad_batches,
         )
+
+        # init logger flags
+        self._loggers: List[Logger]
+        self._logger_connector.on_trainer_init(logger, log_every_n_steps, move_metrics_to_cpu)
+
         fit_loop = FitLoop(min_epochs=min_epochs, max_epochs=max_epochs)
         training_epoch_loop = TrainingEpochLoop(min_steps=min_steps, max_steps=max_steps)
         fit_loop.connect(epoch_loop=training_epoch_loop)
@@ -474,6 +487,18 @@ def __init__(
         # default .predict() loop
         self.predict_loop = PredictionLoop()
 
+        self.val_check_interval: Union[int, float]
+        self._init_debugging_flags(
+            limit_train_batches,
+            limit_val_batches,
+            limit_test_batches,
+            limit_predict_batches,
+            fast_dev_run,
+            overfit_batches,
+            val_check_interval,
+            num_sanity_val_steps,
+        )
+
         # set when a checkpoint is loaded via `Trainer.{fit,validate,test,predict}`.
         self._ckpt_path: Optional[str] = None
 
@@ -483,26 +508,13 @@ def __init__(
         self._tested_ckpt_path: Optional[str] = None  # TODO: remove in v1.8
         self._predicted_ckpt_path: Optional[str] = None  # TODO: remove in v1.8
 
-        # init callbacks
-        # Declare attributes to be set in _callback_connector on_trainer_init
-        self._callback_connector.on_trainer_init(
-            callbacks,
-            enable_checkpointing,
-            enable_progress_bar,
-            default_root_dir,
-            weights_save_path,
-            enable_model_summary,
-            max_time,
-            accumulate_grad_batches,
-        )
-
         # hook
         self._call_callback_hooks("on_init_start")
 
         # init data flags
         self.check_val_every_n_epoch: int
         self._data_connector.on_trainer_init(
-            val_check_interval,
+            self.val_check_interval,
             reload_dataloaders_every_n_epochs,
             check_val_every_n_epoch,
         )
@@ -534,7 +546,7 @@ def __init__(
         self.track_grad_norm: float = float(track_grad_norm)
 
         self._detect_anomaly: bool = detect_anomaly
-        self._setup_on_init(num_sanity_val_steps)
+        self._setup_on_init()
 
         # configure tuner
         self.tuner.on_trainer_init(auto_lr_find, auto_scale_batch_size)
@@ -542,22 +554,6 @@ def __init__(
         # configure profiler
         self.__init_profiler(profiler)
 
-        # init logger flags
-        self._loggers: List[Logger]
-        self._logger_connector.on_trainer_init(logger, log_every_n_steps, move_metrics_to_cpu)
-
-        # init debugging flags
-        self.val_check_interval: Union[int, float]
-        self._init_debugging_flags(
-            limit_train_batches,
-            limit_val_batches,
-            limit_test_batches,
-            limit_predict_batches,
-            val_check_interval,
-            overfit_batches,
-            fast_dev_run,
-        )
-
         # Callback system
         self._call_callback_hooks("on_init_end")
 
@@ -567,54 +563,57 @@ def _init_debugging_flags(
         limit_val_batches: Optional[Union[int, float]],
         limit_test_batches: Optional[Union[int, float]],
         limit_predict_batches: Optional[Union[int, float]],
-        val_check_interval: Optional[Union[int, float]],
-        overfit_batches: Union[int, float],
         fast_dev_run: Union[int, bool],
-    ) -> None:
+        overfit_batches: Union[int, float],
+        val_check_interval: Optional[Union[int, float]],
+        num_sanity_val_steps: int,
+    ):
+        # init debugging flags
         if isinstance(fast_dev_run, int) and (fast_dev_run < 0):
             raise MisconfigurationException(
-                f"fast_dev_run={fast_dev_run} is not a valid configuration. It should be >= 0."
+                f"fast_dev_run={fast_dev_run!r} is not a valid configuration. It should be >= 0."
             )
-
         self.fast_dev_run = fast_dev_run
 
         # set fast_dev_run=True when it is 1, used while logging
         if fast_dev_run == 1:
             self.fast_dev_run = True
 
+        self.overfit_batches = _determine_batch_limits(overfit_batches, "overfit_batches")
+        overfit_batches_enabled = overfit_batches > 0
+
         if fast_dev_run:
             num_batches = int(fast_dev_run)
-            limit_train_batches = num_batches
-            limit_val_batches = num_batches
-            limit_test_batches = num_batches
-            limit_predict_batches = num_batches
+            if not overfit_batches_enabled:
+                self.limit_train_batches = num_batches
+                self.limit_val_batches = num_batches
+
+            self.limit_test_batches = num_batches
+            self.limit_predict_batches = num_batches
             self.fit_loop.max_steps = num_batches
             self.num_sanity_val_steps = 0
             self.fit_loop.max_epochs = 1
-            val_check_interval = 1.0
+            self.val_check_interval = 1.0
             self.check_val_every_n_epoch = 1
             self.loggers = [DummyLogger()] if self.loggers else []
-
             rank_zero_info(
                 f"Running in `fast_dev_run` mode: will run the requested loop using {num_batches} batch(es). "
                 "Logging and checkpointing is suppressed."
             )
-
-        self.limit_train_batches = _determine_batch_limits(limit_train_batches, "limit_train_batches")
-        self.limit_val_batches = _determine_batch_limits(limit_val_batches, "limit_val_batches")
-        self.limit_test_batches = _determine_batch_limits(limit_test_batches, "limit_test_batches")
-        self.limit_predict_batches = _determine_batch_limits(limit_predict_batches, "limit_predict_batches")
-        self.val_check_interval = _determine_batch_limits(val_check_interval, "val_check_interval")
-        self.overfit_batches = _determine_batch_limits(overfit_batches, "overfit_batches")
-        self._configure_overfit_batches(self.overfit_batches)
-
-    def _configure_overfit_batches(self, overfit_batches: Union[int, float]) -> None:
-        """Configure batch limits using `overfit_batches`."""
-        if overfit_batches > 0:
+        else:
+            if not overfit_batches_enabled:
+                self.limit_train_batches = _determine_batch_limits(limit_train_batches, "limit_train_batches")
+                self.limit_val_batches = _determine_batch_limits(limit_val_batches, "limit_val_batches")
+            self.limit_test_batches = _determine_batch_limits(limit_test_batches, "limit_test_batches")
+            self.limit_predict_batches = _determine_batch_limits(limit_predict_batches, "limit_predict_batches")
+            self.num_sanity_val_steps = float("inf") if num_sanity_val_steps == -1 else num_sanity_val_steps
+            self.val_check_interval = _determine_batch_limits(val_check_interval, "val_check_interval")
+
+        if overfit_batches_enabled:
             self.limit_train_batches = overfit_batches
             self.limit_val_batches = overfit_batches
 
-    def _setup_on_init(self, num_sanity_val_steps: int) -> None:
+    def _setup_on_init(self) -> None:
         self._log_device_info()
 
         self.should_stop = False
@@ -622,11 +621,6 @@ def _setup_on_init(self, num_sanity_val_steps: int) -> None:
         self.num_training_batches = float("inf")
         self.train_dataloader = None
 
-        if num_sanity_val_steps == -1:
-            self.num_sanity_val_steps = float("inf")
-        else:
-            self.num_sanity_val_steps = num_sanity_val_steps
-
         self.num_sanity_val_batches = []
         self.num_test_batches = []
         self.num_val_batches = []
@@ -1138,6 +1132,12 @@ def _run(
         # ----------------------------
         # TRAIN
         # ----------------------------
+        if self.state.fn in (TrainerFn.FITTING, TrainerFn.TUNING):
+            min_epochs, max_epochs = _parse_loop_limits(
+                self.min_steps, self.max_steps, self.min_epochs, self.max_epochs, self
+            )
+            self.fit_loop.min_epochs = min_epochs
+            self.fit_loop.max_epochs = max_epochs
 
         # reset logger connector
         self._logger_connector.reset_results()
@@ -1278,6 +1278,7 @@ def _run_train(self) -> None:
         torch.set_grad_enabled(True)
 
         self.fit_loop.trainer = self
+
         with torch.autograd.set_detect_anomaly(self._detect_anomaly):
             self.fit_loop.run()
 
diff --git a/tests/tests_pytorch/loops/test_training_loop.py b/tests/tests_pytorch/loops/test_training_loop.py
index a9da6dcf2be6d..bba0b9b7c5428 100644
--- a/tests/tests_pytorch/loops/test_training_loop.py
+++ b/tests/tests_pytorch/loops/test_training_loop.py
@@ -141,7 +141,7 @@ def validation_step(self, *args):
 
 
 def test_fit_loop_done_log_messages(caplog):
-    fit_loop = FitLoop()
+    fit_loop = FitLoop(max_epochs=1)
     trainer = Mock(spec=Trainer)
     fit_loop.trainer = trainer
 
diff --git a/tests/tests_pytorch/test_cli.py b/tests/tests_pytorch/test_cli.py
index 965f53a86d4b7..e37f799888f7e 100644
--- a/tests/tests_pytorch/test_cli.py
+++ b/tests/tests_pytorch/test_cli.py
@@ -1271,7 +1271,8 @@ def __init__(self, foo: int, *args, **kwargs):
 def test_lightning_cli_reinstantiate_trainer():
     with mock.patch("sys.argv", ["any.py"]):
         cli = LightningCLI(BoringModel, run=False)
-    assert cli.trainer.max_epochs == 1000
+
+    assert cli.trainer.max_epochs is None
 
     class TestCallback(Callback):
         ...
diff --git a/tests/tests_pytorch/trainer/flags/test_env_vars.py b/tests/tests_pytorch/trainer/flags/test_env_vars.py
index 9e7bd70468482..cfac06c8d7711 100644
--- a/tests/tests_pytorch/trainer/flags/test_env_vars.py
+++ b/tests/tests_pytorch/trainer/flags/test_env_vars.py
@@ -15,17 +15,20 @@
 from unittest import mock
 
 from pytorch_lightning import Trainer
+from pytorch_lightning.demos.boring_classes import BoringModel
 
 
 def test_passing_no_env_variables():
     """Testing overwriting trainer arguments."""
     trainer = Trainer()
+    model = BoringModel()
     assert trainer.logger is not None
     assert trainer.max_steps == -1
-    assert trainer.max_epochs == 1000
-    trainer = Trainer(False, max_steps=42)
+    assert trainer.max_epochs is None
+    trainer = Trainer(logger=False, max_steps=1)
+    trainer.fit(model)
     assert trainer.logger is None
-    assert trainer.max_steps == 42
+    assert trainer.max_steps == 1
     assert trainer.max_epochs == -1
 
 
diff --git a/tests/tests_pytorch/trainer/flags/test_min_max_epochs.py b/tests/tests_pytorch/trainer/flags/test_min_max_epochs.py
index 93a7c915ebb7b..25f2dfdab279f 100644
--- a/tests/tests_pytorch/trainer/flags/test_min_max_epochs.py
+++ b/tests/tests_pytorch/trainer/flags/test_min_max_epochs.py
@@ -3,6 +3,7 @@
 from pytorch_lightning import Trainer
 from pytorch_lightning.demos.boring_classes import BoringModel
 from pytorch_lightning.utilities.warnings import PossibleUserWarning
+from tests_pytorch.helpers.utils import no_warning_call
 
 
 @pytest.mark.parametrize(
@@ -37,7 +38,23 @@ def test_min_max_steps_epochs(tmpdir, min_epochs, max_epochs, min_steps, max_ste
 
 
 def test_max_epochs_not_set_warning():
-    """Test that a warning is emitted when `max_epochs` was not set by the user."""
-    with pytest.warns(PossibleUserWarning, match="`max_epochs` was not set. Setting it to 1000 epochs."):
-        trainer = Trainer(max_epochs=None)
-        assert trainer.max_epochs == 1000
+    """Test that a warning is only emitted when `max_epochs` was not set by the user."""
+
+    class CustomModel(BoringModel):
+        def training_step(self, *args, **kwargs):
+            self.trainer.should_stop = True
+
+    match = "`max_epochs` was not set. Setting it to 1000 epochs."
+
+    model = CustomModel()
+    model.training_epoch_end = None
+    trainer = Trainer(max_epochs=None, limit_train_batches=1)
+    with pytest.warns(PossibleUserWarning, match=match):
+        trainer.fit(model)
+
+    assert trainer.max_epochs == 1000
+    assert trainer.current_epoch == 1
+
+    with no_warning_call(expected_warning=PossibleUserWarning, match=match):
+        Trainer(fast_dev_run=True)
+        Trainer(fast_dev_run=1)
diff --git a/tests/tests_pytorch/trainer/properties/test_estimated_stepping_batches.py b/tests/tests_pytorch/trainer/properties/test_estimated_stepping_batches.py
index 8d8fb7f3a8c21..92a1126294dfc 100644
--- a/tests/tests_pytorch/trainer/properties/test_estimated_stepping_batches.py
+++ b/tests/tests_pytorch/trainer/properties/test_estimated_stepping_batches.py
@@ -97,11 +97,10 @@ def test_num_stepping_batches_infinite_training():
 
 def test_num_stepping_batches_with_max_steps():
     """Test stepping batches with `max_steps`."""
-    max_steps = 7
+    max_steps = 2
     trainer = Trainer(max_steps=max_steps)
     model = BoringModel()
-    trainer._data_connector.attach_data(model)
-    trainer.strategy.connect(model)
+    trainer.fit(model)
     assert trainer.estimated_stepping_batches == max_steps
 
 
diff --git a/tests/tests_pytorch/trainer/test_trainer.py b/tests/tests_pytorch/trainer/test_trainer.py
index ecc0ad724e879..136312ed1db61 100644
--- a/tests/tests_pytorch/trainer/test_trainer.py
+++ b/tests/tests_pytorch/trainer/test_trainer.py
@@ -523,11 +523,11 @@ def test_trainer_max_steps_and_epochs_validation(max_epochs, max_steps, incorrec
 @pytest.mark.parametrize(
     "max_epochs,max_steps,is_done,correct_trainer_epochs",
     [
-        (None, -1, False, 1000),
+        (None, -1, False, None),
         (-1, -1, False, -1),
         (5, -1, False, 5),
         (-1, 10, False, -1),
-        (None, 0, True, -1),
+        (None, 0, True, None),
         (0, -1, True, 0),
         (-1, 0, True, -1),
         (0, -1, True, 0),
@@ -538,7 +538,9 @@ def test_trainer_max_steps_and_epochs_fit_loop_done(max_epochs, max_steps, is_do
 
     assert trainer.max_epochs == correct_trainer_epochs
     assert trainer.max_steps == max_steps
-    assert trainer.fit_loop.done is is_done
+
+    if isinstance(correct_trainer_epochs, int):
+        assert trainer.fit_loop.done is is_done
 
     # Make sure there is no timer
     timer_callbacks = [c for c in trainer.callbacks if isinstance(c, Timer)]


Code review comments:
diff_hunk: @@ -1138,6 +1132,12 @@ def _run(
         # ----------------------------
         # TRAIN
         # ----------------------------
+        if self.state.fn in (TrainerFn.FITTING, TrainerFn.TUNING):
+            min_epochs, max_epochs = _parse_loop_limits(
+                self.min_steps, self.max_steps, self.min_epochs, self.max_epochs, self
+            )
+            self.fit_loop.min_epochs = min_epochs
+            self.fit_loop.max_epochs = max_epochs
path: src/pytorch_lightning/trainer/trainer.py
text: @user1:
why aren't we doing it earlier? At this point we've already run model hooks that could have accessed the trainer and loops.
Note, a few lines above we  `verify_loop_configurations(self)`, so I think this should at least move up there

@user2:
done!

diff_hunk: @@ -455,9 +455,22 @@ def __init__(
         self._signal_connector = SignalConnector(self)
         self.tuner = Tuner(self)
 
-        min_steps, max_steps, min_epochs, max_epochs, max_time = _parse_loop_limits(
-            min_steps, max_steps, min_epochs, max_epochs, max_time
+        # Declare attributes to be set in _callback_connector on_trainer_init
+        self._callback_connector.on_trainer_init(
path: src/pytorch_lightning/trainer/trainer.py
text: @user1:
I'm not convinced that changing the order here is a good idea. We still have the issue that some of these connectors write back attributes using their internal trainer reference, and the order in which things get initialized here can matter. Have we carefully checked this? 

It looks like all you wanted to do is move `_parse_loop_limit`. Would we be able to move the callback connector and init_debugging_flags and logger connector, back to where they were? For sanity? Or have we done this to resolve another bug?

@user2:
Tests should be failing if they aren't moved (I was who initially moved it!). It's very tricky to see these dependencies between connector, the easiest way to double-check would be to move it back and run CI

@user3:
tried moving back and it failed

@user1:
I reverted the re-ordering in [64e4506](https://github.com/Lightning-AI/lightning/pull/13262/commits/64e450663f4e891f2b7f6b3971ad105eef51efe4) and all tests pass. I am still convinced not touching the ordering here is the best way for sanity.

If you still think we should be doing the re-ordering, please feel free revert my last commit, then merge the PR.