2021-06-14 12:20:01 +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.
|
2022-05-03 16:54:41 +00:00
|
|
|
import contextlib
|
2021-02-13 00:27:44 +00:00
|
|
|
import logging
|
2022-05-03 16:54:41 +00:00
|
|
|
from unittest import mock
|
|
|
|
from unittest.mock import Mock
|
2021-02-03 21:40:57 +00:00
|
|
|
|
|
|
|
import torch
|
|
|
|
|
2021-08-20 16:22:03 +00:00
|
|
|
from pytorch_lightning import Callback, LightningModule, Trainer
|
2021-02-13 00:27:44 +00:00
|
|
|
from pytorch_lightning.callbacks import (
|
|
|
|
EarlyStopping,
|
|
|
|
GradientAccumulationScheduler,
|
|
|
|
LearningRateMonitor,
|
|
|
|
ModelCheckpoint,
|
2021-09-10 12:42:42 +00:00
|
|
|
ModelSummary,
|
2021-11-15 21:04:48 +00:00
|
|
|
ProgressBarBase,
|
2021-11-01 11:42:21 +00:00
|
|
|
TQDMProgressBar,
|
2021-02-13 00:27:44 +00:00
|
|
|
)
|
|
|
|
from pytorch_lightning.trainer.connectors.callback_connector import CallbackConnector
|
2022-05-03 16:54:41 +00:00
|
|
|
from pytorch_lightning.utilities.imports import _PYTHON_GREATER_EQUAL_3_8_0
|
2021-02-09 10:10:52 +00:00
|
|
|
from tests.helpers import BoringModel
|
2021-02-03 21:40:57 +00:00
|
|
|
|
|
|
|
|
|
|
|
def test_checkpoint_callbacks_are_last(tmpdir):
|
2021-07-26 11:37:35 +00:00
|
|
|
"""Test that checkpoint callbacks always get moved to the end of the list, with preserved order."""
|
2021-02-03 21:40:57 +00:00
|
|
|
checkpoint1 = ModelCheckpoint(tmpdir)
|
|
|
|
checkpoint2 = ModelCheckpoint(tmpdir)
|
2021-09-10 12:42:42 +00:00
|
|
|
model_summary = ModelSummary()
|
2021-11-09 18:08:03 +00:00
|
|
|
early_stopping = EarlyStopping(monitor="foo")
|
2021-02-03 21:40:57 +00:00
|
|
|
lr_monitor = LearningRateMonitor()
|
2021-11-01 11:42:21 +00:00
|
|
|
progress_bar = TQDMProgressBar()
|
2021-02-03 21:40:57 +00:00
|
|
|
|
2021-08-20 16:22:03 +00:00
|
|
|
# no model reference
|
2021-09-10 12:42:42 +00:00
|
|
|
trainer = Trainer(callbacks=[checkpoint1, progress_bar, lr_monitor, model_summary, checkpoint2])
|
2021-09-24 18:51:54 +00:00
|
|
|
assert trainer.callbacks == [
|
|
|
|
progress_bar,
|
|
|
|
lr_monitor,
|
|
|
|
model_summary,
|
|
|
|
trainer.accumulation_scheduler,
|
|
|
|
checkpoint1,
|
|
|
|
checkpoint2,
|
|
|
|
]
|
2021-02-03 21:40:57 +00:00
|
|
|
|
2021-08-20 16:22:03 +00:00
|
|
|
# no model callbacks
|
|
|
|
model = LightningModule()
|
|
|
|
model.configure_callbacks = lambda: []
|
|
|
|
trainer.model = model
|
2021-12-04 21:39:55 +00:00
|
|
|
cb_connector = CallbackConnector(trainer)
|
2021-08-20 16:22:03 +00:00
|
|
|
cb_connector._attach_model_callbacks()
|
2021-09-24 18:51:54 +00:00
|
|
|
assert trainer.callbacks == [
|
|
|
|
progress_bar,
|
|
|
|
lr_monitor,
|
|
|
|
model_summary,
|
|
|
|
trainer.accumulation_scheduler,
|
|
|
|
checkpoint1,
|
|
|
|
checkpoint2,
|
|
|
|
]
|
2021-08-20 16:22:03 +00:00
|
|
|
|
2021-02-13 00:27:44 +00:00
|
|
|
# with model-specific callbacks that substitute ones in Trainer
|
2021-08-20 16:22:03 +00:00
|
|
|
model = LightningModule()
|
2021-09-10 12:42:42 +00:00
|
|
|
model.configure_callbacks = lambda: [checkpoint1, early_stopping, model_summary, checkpoint2]
|
2021-02-13 00:27:44 +00:00
|
|
|
trainer = Trainer(callbacks=[progress_bar, lr_monitor, ModelCheckpoint(tmpdir)])
|
2021-08-04 15:43:34 +00:00
|
|
|
trainer.model = model
|
2021-02-13 00:27:44 +00:00
|
|
|
cb_connector = CallbackConnector(trainer)
|
2021-08-04 15:43:34 +00:00
|
|
|
cb_connector._attach_model_callbacks()
|
2021-09-24 18:51:54 +00:00
|
|
|
assert trainer.callbacks == [
|
|
|
|
progress_bar,
|
|
|
|
lr_monitor,
|
|
|
|
trainer.accumulation_scheduler,
|
|
|
|
early_stopping,
|
|
|
|
model_summary,
|
|
|
|
checkpoint1,
|
|
|
|
checkpoint2,
|
|
|
|
]
|
2021-02-13 00:27:44 +00:00
|
|
|
|
2021-02-03 21:40:57 +00:00
|
|
|
|
|
|
|
class StatefulCallback0(Callback):
|
2022-03-25 00:06:10 +00:00
|
|
|
def state_dict(self):
|
2021-02-03 21:40:57 +00:00
|
|
|
return {"content0": 0}
|
|
|
|
|
|
|
|
|
|
|
|
class StatefulCallback1(Callback):
|
2021-08-24 17:35:19 +00:00
|
|
|
def __init__(self, unique=None, other=None):
|
|
|
|
self._unique = unique
|
|
|
|
self._other = other
|
|
|
|
|
|
|
|
@property
|
|
|
|
def state_key(self):
|
|
|
|
return self._generate_state_key(unique=self._unique)
|
|
|
|
|
2022-03-25 00:06:10 +00:00
|
|
|
def state_dict(self):
|
2021-08-24 17:35:19 +00:00
|
|
|
return {"content1": self._unique}
|
2021-02-03 21:40:57 +00:00
|
|
|
|
|
|
|
|
|
|
|
def test_all_callback_states_saved_before_checkpoint_callback(tmpdir):
|
2021-09-06 12:49:09 +00:00
|
|
|
"""Test that all callback states get saved even if the ModelCheckpoint is not given as last and when there are
|
|
|
|
multiple callbacks of the same type."""
|
2021-02-03 21:40:57 +00:00
|
|
|
|
|
|
|
callback0 = StatefulCallback0()
|
2021-08-24 17:35:19 +00:00
|
|
|
callback1 = StatefulCallback1(unique="one")
|
|
|
|
callback2 = StatefulCallback1(unique="two", other=2)
|
2021-02-03 21:40:57 +00:00
|
|
|
checkpoint_callback = ModelCheckpoint(dirpath=tmpdir, filename="all_states")
|
|
|
|
model = BoringModel()
|
|
|
|
trainer = Trainer(
|
2021-08-24 17:35:19 +00:00
|
|
|
default_root_dir=tmpdir,
|
|
|
|
max_steps=1,
|
|
|
|
limit_val_batches=1,
|
|
|
|
callbacks=[
|
|
|
|
callback0,
|
|
|
|
# checkpoint callback does not have to be at the end
|
|
|
|
checkpoint_callback,
|
|
|
|
# callback2 and callback3 have the same type
|
|
|
|
callback1,
|
|
|
|
callback2,
|
|
|
|
],
|
2021-02-03 21:40:57 +00:00
|
|
|
)
|
|
|
|
trainer.fit(model)
|
|
|
|
|
|
|
|
ckpt = torch.load(str(tmpdir / "all_states.ckpt"))
|
2021-07-28 22:12:32 +00:00
|
|
|
state0 = ckpt["callbacks"]["StatefulCallback0"]
|
2021-08-24 17:35:19 +00:00
|
|
|
state1 = ckpt["callbacks"]["StatefulCallback1{'unique': 'one'}"]
|
|
|
|
state2 = ckpt["callbacks"]["StatefulCallback1{'unique': 'two'}"]
|
2021-02-03 21:40:57 +00:00
|
|
|
assert "content0" in state0 and state0["content0"] == 0
|
2021-08-24 17:35:19 +00:00
|
|
|
assert "content1" in state1 and state1["content1"] == "one"
|
|
|
|
assert "content1" in state2 and state2["content1"] == "two"
|
|
|
|
assert (
|
|
|
|
"ModelCheckpoint{'monitor': None, 'mode': 'min', 'every_n_train_steps': 0, 'every_n_epochs': 1,"
|
|
|
|
" 'train_time_interval': None, 'save_on_train_epoch_end': True}" in ckpt["callbacks"]
|
|
|
|
)
|
2021-02-13 00:27:44 +00:00
|
|
|
|
|
|
|
|
|
|
|
def test_attach_model_callbacks():
|
2021-07-26 11:37:35 +00:00
|
|
|
"""Test that the callbacks defined in the model and through Trainer get merged correctly."""
|
2021-02-13 00:27:44 +00:00
|
|
|
|
2021-09-24 18:51:54 +00:00
|
|
|
def _attach_callbacks(trainer_callbacks, model_callbacks):
|
2021-08-20 16:22:03 +00:00
|
|
|
model = LightningModule()
|
|
|
|
model.configure_callbacks = lambda: model_callbacks
|
2021-11-15 21:04:48 +00:00
|
|
|
has_progress_bar = any(isinstance(cb, ProgressBarBase) for cb in trainer_callbacks + model_callbacks)
|
2021-09-10 12:42:42 +00:00
|
|
|
trainer = Trainer(
|
2021-10-13 11:50:54 +00:00
|
|
|
enable_checkpointing=False,
|
2021-11-15 21:04:48 +00:00
|
|
|
enable_progress_bar=has_progress_bar,
|
|
|
|
enable_model_summary=False,
|
2021-10-13 11:50:54 +00:00
|
|
|
callbacks=trainer_callbacks,
|
2021-09-10 12:42:42 +00:00
|
|
|
)
|
2021-08-04 15:43:34 +00:00
|
|
|
trainer.model = model
|
2021-02-13 00:27:44 +00:00
|
|
|
cb_connector = CallbackConnector(trainer)
|
2021-08-04 15:43:34 +00:00
|
|
|
cb_connector._attach_model_callbacks()
|
2021-09-24 18:51:54 +00:00
|
|
|
return trainer
|
2021-02-13 00:27:44 +00:00
|
|
|
|
2021-11-09 18:08:03 +00:00
|
|
|
early_stopping = EarlyStopping(monitor="foo")
|
2021-11-01 11:42:21 +00:00
|
|
|
progress_bar = TQDMProgressBar()
|
2021-02-13 00:27:44 +00:00
|
|
|
lr_monitor = LearningRateMonitor()
|
|
|
|
grad_accumulation = GradientAccumulationScheduler({1: 1})
|
|
|
|
|
|
|
|
# no callbacks
|
2021-09-24 18:51:54 +00:00
|
|
|
trainer = _attach_callbacks(trainer_callbacks=[], model_callbacks=[])
|
|
|
|
assert trainer.callbacks == [trainer.accumulation_scheduler]
|
2021-02-13 00:27:44 +00:00
|
|
|
|
|
|
|
# callbacks of different types
|
2021-09-24 18:51:54 +00:00
|
|
|
trainer = _attach_callbacks(trainer_callbacks=[early_stopping], model_callbacks=[progress_bar])
|
|
|
|
assert trainer.callbacks == [early_stopping, trainer.accumulation_scheduler, progress_bar]
|
2021-02-13 00:27:44 +00:00
|
|
|
|
|
|
|
# same callback type twice, different instance
|
2021-09-24 18:51:54 +00:00
|
|
|
trainer = _attach_callbacks(
|
2021-11-09 18:08:03 +00:00
|
|
|
trainer_callbacks=[progress_bar, EarlyStopping(monitor="foo")],
|
2021-02-13 00:27:44 +00:00
|
|
|
model_callbacks=[early_stopping],
|
|
|
|
)
|
2021-09-24 18:51:54 +00:00
|
|
|
assert trainer.callbacks == [progress_bar, trainer.accumulation_scheduler, early_stopping]
|
2021-02-13 00:27:44 +00:00
|
|
|
|
|
|
|
# multiple callbacks of the same type in trainer
|
2021-09-24 18:51:54 +00:00
|
|
|
trainer = _attach_callbacks(
|
2021-11-09 18:08:03 +00:00
|
|
|
trainer_callbacks=[
|
|
|
|
LearningRateMonitor(),
|
|
|
|
EarlyStopping(monitor="foo"),
|
|
|
|
LearningRateMonitor(),
|
|
|
|
EarlyStopping(monitor="foo"),
|
|
|
|
],
|
2021-02-13 00:27:44 +00:00
|
|
|
model_callbacks=[early_stopping, lr_monitor],
|
|
|
|
)
|
2021-09-24 18:51:54 +00:00
|
|
|
assert trainer.callbacks == [trainer.accumulation_scheduler, early_stopping, lr_monitor]
|
2021-02-13 00:27:44 +00:00
|
|
|
|
|
|
|
# multiple callbacks of the same type, in both trainer and model
|
2021-09-24 18:51:54 +00:00
|
|
|
trainer = _attach_callbacks(
|
2021-02-13 00:27:44 +00:00
|
|
|
trainer_callbacks=[
|
2021-07-26 11:37:35 +00:00
|
|
|
LearningRateMonitor(),
|
|
|
|
progress_bar,
|
2021-11-09 18:08:03 +00:00
|
|
|
EarlyStopping(monitor="foo"),
|
2021-02-13 00:27:44 +00:00
|
|
|
LearningRateMonitor(),
|
2021-11-09 18:08:03 +00:00
|
|
|
EarlyStopping(monitor="foo"),
|
2021-02-13 00:27:44 +00:00
|
|
|
],
|
|
|
|
model_callbacks=[early_stopping, lr_monitor, grad_accumulation, early_stopping],
|
|
|
|
)
|
2021-09-24 18:51:54 +00:00
|
|
|
assert trainer.callbacks == [progress_bar, early_stopping, lr_monitor, grad_accumulation, early_stopping]
|
2021-02-13 00:27:44 +00:00
|
|
|
|
|
|
|
|
|
|
|
def test_attach_model_callbacks_override_info(caplog):
|
2021-07-26 11:37:35 +00:00
|
|
|
"""Test that the logs contain the info about overriding callbacks returned by configure_callbacks."""
|
2021-08-20 16:22:03 +00:00
|
|
|
model = LightningModule()
|
2021-11-09 18:08:03 +00:00
|
|
|
model.configure_callbacks = lambda: [LearningRateMonitor(), EarlyStopping(monitor="foo")]
|
|
|
|
trainer = Trainer(
|
|
|
|
enable_checkpointing=False, callbacks=[EarlyStopping(monitor="foo"), LearningRateMonitor(), TQDMProgressBar()]
|
|
|
|
)
|
2021-08-04 15:43:34 +00:00
|
|
|
trainer.model = model
|
2021-02-13 00:27:44 +00:00
|
|
|
cb_connector = CallbackConnector(trainer)
|
|
|
|
with caplog.at_level(logging.INFO):
|
2021-08-04 15:43:34 +00:00
|
|
|
cb_connector._attach_model_callbacks()
|
2021-02-13 00:27:44 +00:00
|
|
|
|
|
|
|
assert "existing callbacks passed to Trainer: EarlyStopping, LearningRateMonitor" in caplog.text
|
2022-05-03 16:54:41 +00:00
|
|
|
|
|
|
|
|
|
|
|
class ExternalCallback(Callback):
|
|
|
|
"""A callback in another library that gets registered through entry points."""
|
|
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
def test_configure_external_callbacks():
|
|
|
|
"""Test that the connector collects Callback instances from factories registered through entry points."""
|
|
|
|
|
|
|
|
def factory_no_callback():
|
|
|
|
return []
|
|
|
|
|
|
|
|
def factory_one_callback():
|
|
|
|
return ExternalCallback()
|
|
|
|
|
|
|
|
def factory_one_callback_list():
|
|
|
|
return [ExternalCallback()]
|
|
|
|
|
|
|
|
def factory_multiple_callbacks_list():
|
|
|
|
return [ExternalCallback(), ExternalCallback()]
|
|
|
|
|
|
|
|
with _make_entry_point_query_mock(factory_no_callback):
|
|
|
|
trainer = Trainer(enable_checkpointing=False, enable_progress_bar=False, enable_model_summary=False)
|
|
|
|
assert trainer.callbacks == [trainer.accumulation_scheduler] # this scheduler callback gets added by default
|
|
|
|
|
|
|
|
with _make_entry_point_query_mock(factory_one_callback):
|
|
|
|
trainer = Trainer(enable_checkpointing=False, enable_progress_bar=False, enable_model_summary=False)
|
|
|
|
assert isinstance(trainer.callbacks[1], ExternalCallback)
|
|
|
|
|
|
|
|
with _make_entry_point_query_mock(factory_one_callback_list):
|
|
|
|
trainer = Trainer(enable_checkpointing=False, enable_progress_bar=False, enable_model_summary=False)
|
|
|
|
assert isinstance(trainer.callbacks[1], ExternalCallback)
|
|
|
|
|
|
|
|
with _make_entry_point_query_mock(factory_multiple_callbacks_list):
|
|
|
|
trainer = Trainer(enable_checkpointing=False, enable_progress_bar=False, enable_model_summary=False)
|
|
|
|
assert isinstance(trainer.callbacks[1], ExternalCallback)
|
|
|
|
assert isinstance(trainer.callbacks[2], ExternalCallback)
|
|
|
|
|
|
|
|
|
|
|
|
@contextlib.contextmanager
|
|
|
|
def _make_entry_point_query_mock(callback_factory):
|
|
|
|
query_mock = Mock()
|
|
|
|
entry_point = Mock()
|
|
|
|
entry_point.name = "mocked"
|
|
|
|
entry_point.load.return_value = callback_factory
|
|
|
|
if _PYTHON_GREATER_EQUAL_3_8_0:
|
|
|
|
query_mock().get.return_value = [entry_point]
|
|
|
|
import_path = "importlib.metadata.entry_points"
|
|
|
|
else:
|
|
|
|
query_mock.return_value = [entry_point]
|
|
|
|
import_path = "pkg_resources.iter_entry_points"
|
|
|
|
with mock.patch(import_path, query_mock):
|
|
|
|
yield
|