2021-06-15 12:55:06 +00:00
|
|
|
# Copyright The PyTorch Lightning team.
|
|
|
|
#
|
|
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
# you may not use this file except in compliance with the License.
|
|
|
|
# You may obtain a copy of the License at
|
|
|
|
#
|
|
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
#
|
|
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
# See the License for the specific language governing permissions and
|
|
|
|
# limitations under the License.
|
2021-11-30 20:28:55 +00:00
|
|
|
from typing import Any, List, Optional, Tuple, Union
|
2021-06-15 12:55:06 +00:00
|
|
|
|
|
|
|
from deprecate import void
|
|
|
|
from torch import Tensor
|
|
|
|
|
|
|
|
from pytorch_lightning.loops.base import Loop
|
2021-09-28 13:22:22 +00:00
|
|
|
from pytorch_lightning.loops.optimization.manual_loop import _OUTPUTS_TYPE as _MANUAL_LOOP_OUTPUTS_TYPE
|
2021-09-10 13:18:24 +00:00
|
|
|
from pytorch_lightning.loops.optimization.manual_loop import ManualOptimization
|
2021-09-28 13:22:22 +00:00
|
|
|
from pytorch_lightning.loops.optimization.optimizer_loop import _OUTPUTS_TYPE as _OPTIMIZER_LOOP_OUTPUTS_TYPE
|
2021-09-10 13:18:24 +00:00
|
|
|
from pytorch_lightning.loops.optimization.optimizer_loop import OptimizerLoop
|
2021-09-25 11:17:47 +00:00
|
|
|
from pytorch_lightning.loops.utilities import _get_active_optimizers
|
2021-06-15 12:55:06 +00:00
|
|
|
from pytorch_lightning.trainer.supporters import TensorRunningAccum
|
|
|
|
|
2021-09-28 13:22:22 +00:00
|
|
|
_OUTPUTS_TYPE = List[Union[_OPTIMIZER_LOOP_OUTPUTS_TYPE, _MANUAL_LOOP_OUTPUTS_TYPE]]
|
2021-06-15 12:55:06 +00:00
|
|
|
|
2021-09-28 13:22:22 +00:00
|
|
|
|
|
|
|
class TrainingBatchLoop(Loop[_OUTPUTS_TYPE]):
|
2021-07-26 11:37:35 +00:00
|
|
|
"""Runs over a single batch of data."""
|
2021-06-15 12:55:06 +00:00
|
|
|
|
|
|
|
def __init__(self) -> None:
|
|
|
|
super().__init__()
|
2021-11-30 20:28:55 +00:00
|
|
|
self.accumulated_loss = TensorRunningAccum(window_length=20)
|
|
|
|
self.running_loss = TensorRunningAccum(window_length=20)
|
2021-07-29 08:00:04 +00:00
|
|
|
# the current split index when the batch gets split into chunks in truncated backprop through time
|
2022-01-12 01:07:30 +00:00
|
|
|
self.split_idx: int = 0
|
2021-09-02 11:40:05 +00:00
|
|
|
self.optimizer_loop = OptimizerLoop()
|
2021-09-08 00:26:39 +00:00
|
|
|
self.manual_loop = ManualOptimization()
|
2021-06-15 12:55:06 +00:00
|
|
|
|
2021-09-28 13:22:22 +00:00
|
|
|
self._outputs: _OUTPUTS_TYPE = []
|
2021-11-30 20:28:55 +00:00
|
|
|
self._remaining_splits: List[Tuple[int, Any]] = []
|
2021-06-15 12:55:06 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def done(self) -> bool:
|
2021-09-06 12:49:09 +00:00
|
|
|
"""Returns if all batch splits have been processed already."""
|
2021-06-15 12:55:06 +00:00
|
|
|
return len(self._remaining_splits) == 0
|
|
|
|
|
2021-11-30 20:28:55 +00:00
|
|
|
def connect( # type: ignore[override]
|
2021-11-26 18:00:18 +00:00
|
|
|
self, optimizer_loop: Optional[OptimizerLoop] = None, manual_loop: Optional[ManualOptimization] = None
|
2021-09-08 00:26:39 +00:00
|
|
|
) -> None:
|
|
|
|
if optimizer_loop is not None:
|
|
|
|
self.optimizer_loop = optimizer_loop
|
|
|
|
if manual_loop is not None:
|
|
|
|
self.manual_loop = manual_loop
|
2021-07-19 13:08:53 +00:00
|
|
|
|
2021-06-18 12:54:59 +00:00
|
|
|
def reset(self) -> None:
|
2021-09-06 12:49:09 +00:00
|
|
|
"""Resets the loop state."""
|
2021-09-28 13:22:22 +00:00
|
|
|
self._outputs = []
|
2021-06-18 12:54:59 +00:00
|
|
|
|
2021-11-30 20:28:55 +00:00
|
|
|
def on_run_start(self, batch: Any, batch_idx: int) -> None: # type: ignore[override]
|
2021-09-06 12:49:09 +00:00
|
|
|
"""Splits the data into tbptt splits.
|
2021-06-15 12:55:06 +00:00
|
|
|
|
|
|
|
Args:
|
|
|
|
batch: the current batch to run the trainstep on
|
|
|
|
batch_idx: the index of the current batch
|
|
|
|
"""
|
2021-08-16 11:34:42 +00:00
|
|
|
void(batch_idx)
|
2021-06-23 10:25:29 +00:00
|
|
|
self._remaining_splits = list(enumerate(self._tbptt_split_batch(batch)))
|
2021-06-15 12:55:06 +00:00
|
|
|
|
2021-11-30 20:28:55 +00:00
|
|
|
def advance(self, batch: Any, batch_idx: int) -> None: # type: ignore[override]
|
2021-09-06 12:49:09 +00:00
|
|
|
"""Runs the train step together with optimization (if necessary) on the current batch split.
|
2021-06-15 12:55:06 +00:00
|
|
|
|
|
|
|
Args:
|
|
|
|
batch: the current batch to run the training on (this is not the split!)
|
|
|
|
batch_idx: the index of the current batch
|
|
|
|
"""
|
2021-08-16 11:34:42 +00:00
|
|
|
void(batch)
|
2021-10-18 12:02:16 +00:00
|
|
|
self.split_idx, split_batch = self._remaining_splits.pop(0)
|
2021-06-15 12:55:06 +00:00
|
|
|
|
2022-03-05 02:33:42 +00:00
|
|
|
self.trainer._logger_connector.on_train_split_start(self.split_idx)
|
2021-06-15 12:55:06 +00:00
|
|
|
|
2021-11-30 20:28:55 +00:00
|
|
|
outputs: Optional[Union[_OPTIMIZER_LOOP_OUTPUTS_TYPE, _MANUAL_LOOP_OUTPUTS_TYPE]] = None # for mypy
|
2021-09-28 13:22:22 +00:00
|
|
|
# choose which loop will run the optimization
|
2021-06-15 12:55:06 +00:00
|
|
|
if self.trainer.lightning_module.automatic_optimization:
|
2021-09-25 11:17:47 +00:00
|
|
|
optimizers = _get_active_optimizers(self.trainer.optimizers, self.trainer.optimizer_frequencies, batch_idx)
|
2021-09-28 13:22:22 +00:00
|
|
|
outputs = self.optimizer_loop.run(split_batch, optimizers, batch_idx)
|
2021-06-15 12:55:06 +00:00
|
|
|
else:
|
2021-09-28 13:22:22 +00:00
|
|
|
outputs = self.manual_loop.run(split_batch, batch_idx)
|
|
|
|
if outputs:
|
|
|
|
# automatic: can be empty if all optimizers skip their batches
|
|
|
|
# manual: #9052 added support for raising `StopIteration` in the `training_step`. If that happens,
|
|
|
|
# then `advance` doesn't finish and an empty dict is returned
|
|
|
|
self._outputs.append(outputs)
|
2021-06-15 12:55:06 +00:00
|
|
|
|
2021-10-18 12:02:16 +00:00
|
|
|
def on_run_end(self) -> _OUTPUTS_TYPE:
|
2021-09-08 13:43:40 +00:00
|
|
|
self.optimizer_loop._hiddens = None
|
|
|
|
# this is not necessary as the manual loop runs for only 1 iteration, but just in case
|
|
|
|
self.manual_loop._hiddens = None
|
2021-11-30 20:28:55 +00:00
|
|
|
output, self._outputs = self._outputs, [] # free memory
|
|
|
|
self._remaining_splits = []
|
2021-10-18 12:02:16 +00:00
|
|
|
return output
|
2021-09-08 13:43:40 +00:00
|
|
|
|
2021-07-05 08:31:39 +00:00
|
|
|
def teardown(self) -> None:
|
2021-11-30 20:28:55 +00:00
|
|
|
self.optimizer_loop.teardown()
|
|
|
|
self.manual_loop.teardown()
|
2022-03-25 11:46:17 +00:00
|
|
|
# release memory
|
|
|
|
if self.accumulated_loss.memory is not None:
|
|
|
|
self.accumulated_loss.memory = self.accumulated_loss.memory.cpu()
|
|
|
|
if self.running_loss.memory is not None:
|
|
|
|
self.running_loss.memory = self.running_loss.memory.cpu()
|
2021-07-05 08:31:39 +00:00
|
|
|
|
2021-06-23 10:25:29 +00:00
|
|
|
def _tbptt_split_batch(self, batch: Any) -> List[Any]:
|
2021-06-15 12:55:06 +00:00
|
|
|
"""Splits a single batch into a list of sequence steps for tbptt.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
batch: the current batch to split
|
|
|
|
"""
|
2021-08-11 03:26:01 +00:00
|
|
|
tbptt_steps = self.trainer.lightning_module.truncated_bptt_steps
|
2021-08-10 04:23:05 +00:00
|
|
|
if tbptt_steps == 0:
|
|
|
|
return [batch]
|
|
|
|
|
2021-12-14 19:49:19 +00:00
|
|
|
splits = self.trainer._call_lightning_module_hook("tbptt_split_batch", batch, tbptt_steps)
|
2021-06-15 12:55:06 +00:00
|
|
|
return splits
|
|
|
|
|
2021-06-23 10:25:29 +00:00
|
|
|
def _update_running_loss(self, current_loss: Tensor) -> None:
|
2021-09-06 12:49:09 +00:00
|
|
|
"""Updates the running loss value with the current value."""
|
2021-06-15 12:55:06 +00:00
|
|
|
if self.trainer.lightning_module.automatic_optimization:
|
|
|
|
# track total loss for logging (avoid mem leaks)
|
|
|
|
self.accumulated_loss.append(current_loss)
|
|
|
|
|
|
|
|
accumulated_loss = self.accumulated_loss.mean()
|
|
|
|
|
|
|
|
if accumulated_loss is not None:
|
|
|
|
# calculate running loss for display
|
|
|
|
self.running_loss.append(self.accumulated_loss.mean() * self.trainer.accumulate_grad_batches)
|
|
|
|
|
|
|
|
# reset for next set of accumulated grads
|
|
|
|
self.accumulated_loss.reset()
|