From addd578e14db10f571024a4936d4ff4fc2e06f2d Mon Sep 17 00:00:00 2001 From: Fabio Bonassi Date: Mon, 2 Dec 2024 18:15:20 +0100 Subject: [PATCH 01/16] Give the option to terminate the engine without firing Events.COMPLETED. The default behaviour is not changed. Note that even though Events.COMPLETED is not fired, its timer is updated. --- ignite/engine/engine.py | 27 ++++++++--- ignite/engine/events.py | 7 +-- tests/ignite/contrib/engines/test_common.py | 3 +- tests/ignite/engine/test_engine.py | 51 ++++++++++++++------- 4 files changed, 60 insertions(+), 28 deletions(-) diff --git a/ignite/engine/engine.py b/ignite/engine/engine.py index 27a949cacca..3101a052475 100644 --- a/ignite/engine/engine.py +++ b/ignite/engine/engine.py @@ -4,7 +4,7 @@ import time import warnings import weakref -from collections import defaultdict, OrderedDict +from collections import OrderedDict, defaultdict from collections.abc import Mapping from typing import Any, Callable, Dict, Generator, Iterable, Iterator, List, Optional, Tuple, Union @@ -140,6 +140,7 @@ def __init__(self, process_function: Callable[["Engine", Any], Any]): self._process_function = process_function self.last_event_name: Optional[Events] = None self.should_terminate = False + self.skip_completed_after_termination = False self.should_terminate_single_epoch = False self.should_interrupt = False self.state = State() @@ -538,7 +539,7 @@ def call_interrupt(): self.logger.info("interrupt signaled. Engine will interrupt the run after current iteration is finished.") self.should_interrupt = True - def terminate(self) -> None: + def terminate(self, skip_event_completed: bool = False) -> None: """Sends terminate signal to the engine, so that it terminates completely the run. The run is terminated after the event on which ``terminate`` method was called. The following events are triggered: @@ -547,6 +548,9 @@ def terminate(self) -> None: - :attr:`~ignite.engine.events.Events.TERMINATE` - :attr:`~ignite.engine.events.Events.COMPLETED` + Args: + skip_event_completed: if True, the event `~ignite.engine.events.Events.COMPLETED` is not fired after + `~ignite.engine.events.Events.COMPLETED`. Default is False. Examples: .. testcode:: @@ -614,12 +618,11 @@ def terminate(): 4 34 | 9 2 Engine ended the run at 4 34 - .. versionchanged:: 0.4.10 - Behaviour changed, for details see https://github.com/pytorch/ignite/issues/2669 - + .. versionchanged:: 0.5.1 """ self.logger.info("Terminate signaled. Engine will stop after current iteration is finished.") self.should_terminate = True + self.skip_completed_after_termination = skip_event_completed def terminate_epoch(self) -> None: """Sends terminate signal to the engine, so that it terminates the current epoch. The run @@ -898,6 +901,8 @@ def switch_batch(engine): # If engine was terminated and now is resuming from terminated state # we need to initialize iter_counter as 0 self._init_iter = 0 + # And we reset the skip_completed_after_termination to its default value + self.skip_completed_after_termination = False if self._dataloader_iter is None: self.state.dataloader = data @@ -994,7 +999,11 @@ def _internal_run_as_gen(self) -> Generator[Any, None, State]: # time is available for handlers but must be updated after fire self.state.times[Events.COMPLETED.name] = time_taken handlers_start_time = time.time() - self._fire_event(Events.COMPLETED) + + # do not fire Events.COMPLETED if we terminated the run with flag `skip_completed_after_termination=True` + if self.should_terminate and not self.skip_completed_after_termination or not self.should_terminate: + self._fire_event(Events.COMPLETED) + time_taken += time.time() - handlers_start_time # update time wrt handlers self.state.times[Events.COMPLETED.name] = time_taken @@ -1175,7 +1184,11 @@ def _internal_run_legacy(self) -> State: # time is available for handlers but must be updated after fire self.state.times[Events.COMPLETED.name] = time_taken handlers_start_time = time.time() - self._fire_event(Events.COMPLETED) + + # do not fire Events.COMPLETED if we terminated the run with flag `skip_completed_after_termination=True` + if self.should_terminate and not self.skip_completed_after_termination or not self.should_terminate: + self._fire_event(Events.COMPLETED) + time_taken += time.time() - handlers_start_time # update time wrt handlers self.state.times[Events.COMPLETED.name] = time_taken diff --git a/ignite/engine/events.py b/ignite/engine/events.py index 9dd99348492..940a7fd445e 100644 --- a/ignite/engine/events.py +++ b/ignite/engine/events.py @@ -265,7 +265,8 @@ class Events(EventEnum): - EPOCH_COMPLETED : triggered when the epoch is ended. Note that this is triggered even when :meth:`~ignite.engine.engine.Engine.terminate_epoch()` is called. - - COMPLETED : triggered when engine's run is completed + - COMPLETED : triggered when engine's run is completed or terminated :meth:`~ignite.engine.engine.Engine.terminate()`, + unless the flag `skip_event_completed` is set to True. The table below illustrates which events are triggered when various termination methods are called. @@ -286,7 +287,7 @@ class Events(EventEnum): - ✔ - ✗ * - :meth:`~ignite.engine.engine.Engine.terminate()` - - ✗ + - (✔) - ✔ - ✔ @@ -357,7 +358,7 @@ class CustomEvents(EventEnum): STARTED = "started" """triggered when engine's run is started.""" COMPLETED = "completed" - """triggered when engine's run is completed""" + """triggered when engine's run is completed, or after receiving terminate() call.""" ITERATION_STARTED = "iteration_started" """triggered when an iteration is started.""" diff --git a/tests/ignite/contrib/engines/test_common.py b/tests/ignite/contrib/engines/test_common.py index d0100be9e8d..c93a081c754 100644 --- a/tests/ignite/contrib/engines/test_common.py +++ b/tests/ignite/contrib/engines/test_common.py @@ -1,6 +1,6 @@ import os import sys -from unittest.mock import call, MagicMock +from unittest.mock import MagicMock, call import pytest import torch @@ -8,7 +8,6 @@ from torch.utils.data.distributed import DistributedSampler import ignite.distributed as idist - import ignite.handlers as handlers from ignite.contrib.engines.common import ( _setup_logging, diff --git a/tests/ignite/engine/test_engine.py b/tests/ignite/engine/test_engine.py index 13021242650..351e78d41ce 100644 --- a/tests/ignite/engine/test_engine.py +++ b/tests/ignite/engine/test_engine.py @@ -1,6 +1,6 @@ import os import time -from unittest.mock import call, MagicMock, Mock +from unittest.mock import MagicMock, Mock, call import numpy as np import pytest @@ -42,9 +42,15 @@ def set_interrupt_resume_enabled(self, interrupt_resume_enabled): def test_terminate(self): engine = Engine(lambda e, b: 1) - assert not engine.should_terminate + assert not engine.should_terminate and not engine.skip_completed_after_termination engine.terminate() - assert engine.should_terminate + assert engine.should_terminate and not engine.skip_completed_after_termination + + def test_terminate_and_not_complete(self): + engine = Engine(lambda e, b: 1) + assert not engine.should_terminate and not engine.skip_completed_after_termination + engine.terminate(skip_event_completed=True) + assert engine.should_terminate and engine.skip_completed_after_termination def test_invalid_process_raises_with_invalid_signature(self): with pytest.raises(ValueError, match=r"Engine must be given a processing function in order to run"): @@ -236,25 +242,32 @@ def check_iter_and_data(): assert num_calls_check_iter_epoch == 1 @pytest.mark.parametrize( - "terminate_event, e, i", + "terminate_event, e, i, skip_event_completed", [ - (Events.STARTED, 0, 0), - (Events.EPOCH_STARTED(once=2), 2, None), - (Events.EPOCH_COMPLETED(once=2), 2, None), - (Events.GET_BATCH_STARTED(once=12), None, 12), - (Events.GET_BATCH_COMPLETED(once=12), None, 12), - (Events.ITERATION_STARTED(once=14), None, 14), - (Events.ITERATION_COMPLETED(once=14), None, 14), + (Events.STARTED, 0, 0, True), + (Events.EPOCH_STARTED(once=2), 2, None, True), + (Events.EPOCH_COMPLETED(once=2), 2, None, True), + (Events.GET_BATCH_STARTED(once=12), None, 12, True), + (Events.GET_BATCH_COMPLETED(once=12), None, 12, False), + (Events.ITERATION_STARTED(once=14), None, 14, True), + (Events.ITERATION_COMPLETED(once=14), None, 14, True), + (Events.STARTED, 0, 0, False), + (Events.EPOCH_STARTED(once=2), 2, None, False), + (Events.EPOCH_COMPLETED(once=2), 2, None, False), + (Events.GET_BATCH_STARTED(once=12), None, 12, False), + (Events.GET_BATCH_COMPLETED(once=12), None, 12, False), + (Events.ITERATION_STARTED(once=14), None, 14, False), + (Events.ITERATION_COMPLETED(once=14), None, 14, False), ], ) - def test_terminate_events_sequence(self, terminate_event, e, i): + def test_terminate_events_sequence(self, terminate_event, e, i, skip_event_completed): engine = RecordedEngine(MagicMock(return_value=1)) data = range(10) max_epochs = 5 @engine.on(terminate_event) def call_terminate(): - engine.terminate() + engine.terminate(skip_event_completed) @engine.on(Events.EXCEPTION_RAISED) def assert_no_exceptions(ee): @@ -271,10 +284,15 @@ def assert_no_exceptions(ee): if e is None: e = i // len(data) + 1 + if skip_event_completed: + assert engine.called_events[-1] == (e, i, Events.TERMINATE) + assert engine.called_events[-2] == (e, i, terminate_event) + else: + assert engine.called_events[-1] == (e, i, Events.COMPLETED) + assert engine.called_events[-2] == (e, i, Events.TERMINATE) + assert engine.called_events[-3] == (e, i, terminate_event) + assert engine.called_events[0] == (0, 0, Events.STARTED) - assert engine.called_events[-1] == (e, i, Events.COMPLETED) - assert engine.called_events[-2] == (e, i, Events.TERMINATE) - assert engine.called_events[-3] == (e, i, terminate_event) assert engine._dataloader_iter is None @pytest.mark.parametrize("data, epoch_length", [(None, 10), (range(10), None)]) @@ -1407,3 +1425,4 @@ def check_iter_epoch(): state = engine.run(data, max_epochs=max_epochs) assert state.iteration == max_epochs * len(data) and state.epoch == max_epochs assert num_calls_check_iter_epoch == 1 + From 5cbca177dc943af31ea7350fcc7b3fdf3d45a70a Mon Sep 17 00:00:00 2001 From: Fabio Bonassi Date: Mon, 2 Dec 2024 22:03:10 +0100 Subject: [PATCH 02/16] Update ignite/engine/engine.py Co-authored-by: vfdev --- ignite/engine/engine.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ignite/engine/engine.py b/ignite/engine/engine.py index 3101a052475..12f59c5cf8a 100644 --- a/ignite/engine/engine.py +++ b/ignite/engine/engine.py @@ -550,7 +550,7 @@ def terminate(self, skip_event_completed: bool = False) -> None: Args: skip_event_completed: if True, the event `~ignite.engine.events.Events.COMPLETED` is not fired after - `~ignite.engine.events.Events.COMPLETED`. Default is False. + `~ignite.engine.events.Events.TERMINATE`. Default is False. Examples: .. testcode:: From c907d6a16c284cf7c152c3c29c50b0474fa1b7e0 Mon Sep 17 00:00:00 2001 From: Fabio Bonassi Date: Mon, 2 Dec 2024 22:03:29 +0100 Subject: [PATCH 03/16] Update ignite/engine/engine.py Co-authored-by: vfdev --- ignite/engine/engine.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ignite/engine/engine.py b/ignite/engine/engine.py index 12f59c5cf8a..601c406cc4a 100644 --- a/ignite/engine/engine.py +++ b/ignite/engine/engine.py @@ -618,7 +618,11 @@ def terminate(): 4 34 | 9 2 Engine ended the run at 4 34 - .. versionchanged:: 0.5.1 + .. versionchanged:: 0.4.10 + Behaviour changed, for details see https://github.com/pytorch/ignite/issues/2669 + + .. versionchanged:: 0.5.2 + Added `skip_event_completed` flag """ self.logger.info("Terminate signaled. Engine will stop after current iteration is finished.") self.should_terminate = True From 42d9da6f2fad262fb0b2b464e02a92268e3ea110 Mon Sep 17 00:00:00 2001 From: Fabio Bonassi Date: Mon, 2 Dec 2024 22:04:08 +0100 Subject: [PATCH 04/16] Update ignite/engine/engine.py Co-authored-by: vfdev --- ignite/engine/engine.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ignite/engine/engine.py b/ignite/engine/engine.py index 601c406cc4a..a57f1c72056 100644 --- a/ignite/engine/engine.py +++ b/ignite/engine/engine.py @@ -539,7 +539,7 @@ def call_interrupt(): self.logger.info("interrupt signaled. Engine will interrupt the run after current iteration is finished.") self.should_interrupt = True - def terminate(self, skip_event_completed: bool = False) -> None: + def terminate(self, skip_completed: bool = False) -> None: """Sends terminate signal to the engine, so that it terminates completely the run. The run is terminated after the event on which ``terminate`` method was called. The following events are triggered: From 5a5babd7e0e8670a3646cd27478205f9b770b4d8 Mon Sep 17 00:00:00 2001 From: Fabio Bonassi Date: Mon, 2 Dec 2024 22:04:37 +0100 Subject: [PATCH 05/16] Update ignite/engine/engine.py Co-authored-by: vfdev --- ignite/engine/engine.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ignite/engine/engine.py b/ignite/engine/engine.py index a57f1c72056..717ed02b270 100644 --- a/ignite/engine/engine.py +++ b/ignite/engine/engine.py @@ -626,7 +626,7 @@ def terminate(): """ self.logger.info("Terminate signaled. Engine will stop after current iteration is finished.") self.should_terminate = True - self.skip_completed_after_termination = skip_event_completed + self.skip_completed_after_termination = skip_completed def terminate_epoch(self) -> None: """Sends terminate signal to the engine, so that it terminates the current epoch. The run From b6c9bbfbe921cf91230da951c28f3b7a2de7727e Mon Sep 17 00:00:00 2001 From: Fabio Bonassi Date: Mon, 2 Dec 2024 22:08:49 +0100 Subject: [PATCH 06/16] Update ignite/engine/events.py Co-authored-by: vfdev --- ignite/engine/events.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ignite/engine/events.py b/ignite/engine/events.py index 940a7fd445e..1f1ab6e6726 100644 --- a/ignite/engine/events.py +++ b/ignite/engine/events.py @@ -265,7 +265,7 @@ class Events(EventEnum): - EPOCH_COMPLETED : triggered when the epoch is ended. Note that this is triggered even when :meth:`~ignite.engine.engine.Engine.terminate_epoch()` is called. - - COMPLETED : triggered when engine's run is completed or terminated :meth:`~ignite.engine.engine.Engine.terminate()`, + - COMPLETED : triggered when engine's run is completed or terminated with :meth:`~ignite.engine.engine.Engine.terminate()`, unless the flag `skip_event_completed` is set to True. The table below illustrates which events are triggered when various termination methods are called. From f1d194a1d15dd70464ffaa62d3109fe524c75814 Mon Sep 17 00:00:00 2001 From: Fabio Bonassi Date: Mon, 2 Dec 2024 22:22:34 +0100 Subject: [PATCH 07/16] Argument `skip_event_completed` renamed to `skip_completed` --- ignite/engine/engine.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ignite/engine/engine.py b/ignite/engine/engine.py index 717ed02b270..2e689b389ee 100644 --- a/ignite/engine/engine.py +++ b/ignite/engine/engine.py @@ -549,7 +549,7 @@ def terminate(self, skip_completed: bool = False) -> None: - :attr:`~ignite.engine.events.Events.COMPLETED` Args: - skip_event_completed: if True, the event `~ignite.engine.events.Events.COMPLETED` is not fired after + skip_completed: if True, the event `~ignite.engine.events.Events.COMPLETED` is not fired after `~ignite.engine.events.Events.TERMINATE`. Default is False. Examples: @@ -622,7 +622,7 @@ def terminate(): Behaviour changed, for details see https://github.com/pytorch/ignite/issues/2669 .. versionchanged:: 0.5.2 - Added `skip_event_completed` flag + Added `skip_completed` flag """ self.logger.info("Terminate signaled. Engine will stop after current iteration is finished.") self.should_terminate = True From 5f2cd887328762288c1554f22228a5f04c908b8e Mon Sep 17 00:00:00 2001 From: Fabio Bonassi Date: Mon, 2 Dec 2024 23:32:39 +0100 Subject: [PATCH 08/16] - Fixed docs broken links. - Do not update self.state.times[Events.COMPLETED.name] if terminated - Fixed unit test --- ignite/engine/engine.py | 30 ++++++++++----------- ignite/engine/events.py | 2 +- tests/ignite/contrib/engines/test_common.py | 2 +- tests/ignite/engine/test_engine.py | 11 ++++---- 4 files changed, 22 insertions(+), 23 deletions(-) diff --git a/ignite/engine/engine.py b/ignite/engine/engine.py index 2e689b389ee..14e88d48015 100644 --- a/ignite/engine/engine.py +++ b/ignite/engine/engine.py @@ -4,7 +4,7 @@ import time import warnings import weakref -from collections import OrderedDict, defaultdict +from collections import defaultdict, OrderedDict from collections.abc import Mapping from typing import Any, Callable, Dict, Generator, Iterable, Iterator, List, Optional, Tuple, Union @@ -549,8 +549,8 @@ def terminate(self, skip_completed: bool = False) -> None: - :attr:`~ignite.engine.events.Events.COMPLETED` Args: - skip_completed: if True, the event `~ignite.engine.events.Events.COMPLETED` is not fired after - `~ignite.engine.events.Events.TERMINATE`. Default is False. + skip_completed: if True, the event :attr:`~ignite.engine.events.Events.COMPLETED` is not fired after + :attr:`~ignite.engine.events.Events.TERMINATE`. Default is False. Examples: .. testcode:: @@ -905,8 +905,6 @@ def switch_batch(engine): # If engine was terminated and now is resuming from terminated state # we need to initialize iter_counter as 0 self._init_iter = 0 - # And we reset the skip_completed_after_termination to its default value - self.skip_completed_after_termination = False if self._dataloader_iter is None: self.state.dataloader = data @@ -1002,17 +1000,19 @@ def _internal_run_as_gen(self) -> Generator[Any, None, State]: time_taken = time.time() - start_time # time is available for handlers but must be updated after fire self.state.times[Events.COMPLETED.name] = time_taken - handlers_start_time = time.time() - # do not fire Events.COMPLETED if we terminated the run with flag `skip_completed_after_termination=True` - if self.should_terminate and not self.skip_completed_after_termination or not self.should_terminate: + if self.should_terminate and self.skip_completed_after_termination: + # do not fire Events.COMPLETED if we terminated the run with flag `skip_completed_after_termination=True` + hours, mins, secs = _to_hours_mins_secs(time_taken) + self.logger.info(f"Engine run terminated. Time taken: {hours:02d}:{mins:02d}:{secs:06.3f}") + else: + handlers_start_time = time.time() self._fire_event(Events.COMPLETED) - - time_taken += time.time() - handlers_start_time - # update time wrt handlers - self.state.times[Events.COMPLETED.name] = time_taken - hours, mins, secs = _to_hours_mins_secs(time_taken) - self.logger.info(f"Engine run complete. Time taken: {hours:02d}:{mins:02d}:{secs:06.3f}") + time_taken += time.time() - handlers_start_time + # update time wrt handlers + self.state.times[Events.COMPLETED.name] = time_taken + hours, mins, secs = _to_hours_mins_secs(time_taken) + self.logger.info(f"Engine run completed. Time taken: {hours:02d}:{mins:02d}:{secs:06.3f}") except BaseException as e: self._dataloader_iter = None @@ -1192,7 +1192,7 @@ def _internal_run_legacy(self) -> State: # do not fire Events.COMPLETED if we terminated the run with flag `skip_completed_after_termination=True` if self.should_terminate and not self.skip_completed_after_termination or not self.should_terminate: self._fire_event(Events.COMPLETED) - + time_taken += time.time() - handlers_start_time # update time wrt handlers self.state.times[Events.COMPLETED.name] = time_taken diff --git a/ignite/engine/events.py b/ignite/engine/events.py index 1f1ab6e6726..af5ebbbabe1 100644 --- a/ignite/engine/events.py +++ b/ignite/engine/events.py @@ -266,7 +266,7 @@ class Events(EventEnum): - EPOCH_COMPLETED : triggered when the epoch is ended. Note that this is triggered even when :meth:`~ignite.engine.engine.Engine.terminate_epoch()` is called. - COMPLETED : triggered when engine's run is completed or terminated with :meth:`~ignite.engine.engine.Engine.terminate()`, - unless the flag `skip_event_completed` is set to True. + unless the flag `skip_completed` is set to True. The table below illustrates which events are triggered when various termination methods are called. diff --git a/tests/ignite/contrib/engines/test_common.py b/tests/ignite/contrib/engines/test_common.py index c93a081c754..e14042e62c1 100644 --- a/tests/ignite/contrib/engines/test_common.py +++ b/tests/ignite/contrib/engines/test_common.py @@ -1,6 +1,6 @@ import os import sys -from unittest.mock import MagicMock, call +from unittest.mock import call, MagicMock import pytest import torch diff --git a/tests/ignite/engine/test_engine.py b/tests/ignite/engine/test_engine.py index 351e78d41ce..43957bf45d4 100644 --- a/tests/ignite/engine/test_engine.py +++ b/tests/ignite/engine/test_engine.py @@ -1,6 +1,6 @@ import os import time -from unittest.mock import MagicMock, Mock, call +from unittest.mock import call, MagicMock, Mock import numpy as np import pytest @@ -49,7 +49,7 @@ def test_terminate(self): def test_terminate_and_not_complete(self): engine = Engine(lambda e, b: 1) assert not engine.should_terminate and not engine.skip_completed_after_termination - engine.terminate(skip_event_completed=True) + engine.terminate(skip_completed=True) assert engine.should_terminate and engine.skip_completed_after_termination def test_invalid_process_raises_with_invalid_signature(self): @@ -260,14 +260,14 @@ def check_iter_and_data(): (Events.ITERATION_COMPLETED(once=14), None, 14, False), ], ) - def test_terminate_events_sequence(self, terminate_event, e, i, skip_event_completed): + def test_terminate_events_sequence(self, terminate_event, e, i, skip_completed): engine = RecordedEngine(MagicMock(return_value=1)) data = range(10) max_epochs = 5 @engine.on(terminate_event) def call_terminate(): - engine.terminate(skip_event_completed) + engine.terminate(skip_completed) @engine.on(Events.EXCEPTION_RAISED) def assert_no_exceptions(ee): @@ -284,7 +284,7 @@ def assert_no_exceptions(ee): if e is None: e = i // len(data) + 1 - if skip_event_completed: + if skip_completed: assert engine.called_events[-1] == (e, i, Events.TERMINATE) assert engine.called_events[-2] == (e, i, terminate_event) else: @@ -1425,4 +1425,3 @@ def check_iter_epoch(): state = engine.run(data, max_epochs=max_epochs) assert state.iteration == max_epochs * len(data) and state.epoch == max_epochs assert num_calls_check_iter_epoch == 1 - From 833b56e803a44c005bbd3fd24193f1eb13747bb2 Mon Sep 17 00:00:00 2001 From: Fabio Bonassi Date: Tue, 3 Dec 2024 00:38:17 +0100 Subject: [PATCH 09/16] Update ignite/engine/engine.py Co-authored-by: vfdev --- ignite/engine/engine.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ignite/engine/engine.py b/ignite/engine/engine.py index 14e88d48015..71c4f093560 100644 --- a/ignite/engine/engine.py +++ b/ignite/engine/engine.py @@ -550,7 +550,7 @@ def terminate(self, skip_completed: bool = False) -> None: Args: skip_completed: if True, the event :attr:`~ignite.engine.events.Events.COMPLETED` is not fired after - :attr:`~ignite.engine.events.Events.TERMINATE`. Default is False. + :attr:`~ignite.engine.events.Events.TERMINATE`. Default is False. Examples: .. testcode:: From 9509e0bf93b72f76c024a3071688b813d074f366 Mon Sep 17 00:00:00 2001 From: Fabio Bonassi Date: Tue, 3 Dec 2024 00:43:53 +0100 Subject: [PATCH 10/16] Refactoring and patching. - Engine time logging moved out of the if clause. In the log message "completed" has been replaced with "finished" to avoid confusion. - Same changes applied to the method `_internal_run_legacy()` --- .gitignore | 2 ++ ignite/engine/engine.py | 26 ++++++++++++-------------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.gitignore b/.gitignore index 859e3fe8feb..602f6c10027 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,5 @@ coverage.xml # Virtualenv .venv/ .python-version +.neptune/ +.vscode/ diff --git a/ignite/engine/engine.py b/ignite/engine/engine.py index 71c4f093560..e2a14898607 100644 --- a/ignite/engine/engine.py +++ b/ignite/engine/engine.py @@ -1001,18 +1001,16 @@ def _internal_run_as_gen(self) -> Generator[Any, None, State]: # time is available for handlers but must be updated after fire self.state.times[Events.COMPLETED.name] = time_taken - if self.should_terminate and self.skip_completed_after_termination: - # do not fire Events.COMPLETED if we terminated the run with flag `skip_completed_after_termination=True` - hours, mins, secs = _to_hours_mins_secs(time_taken) - self.logger.info(f"Engine run terminated. Time taken: {hours:02d}:{mins:02d}:{secs:06.3f}") - else: + # do not fire Events.COMPLETED if we terminated the run with flag `skip_completed=True` + if not (self.should_terminate and self.skip_completed_after_termination): handlers_start_time = time.time() self._fire_event(Events.COMPLETED) time_taken += time.time() - handlers_start_time # update time wrt handlers self.state.times[Events.COMPLETED.name] = time_taken - hours, mins, secs = _to_hours_mins_secs(time_taken) - self.logger.info(f"Engine run completed. Time taken: {hours:02d}:{mins:02d}:{secs:06.3f}") + + hours, mins, secs = _to_hours_mins_secs(time_taken) + self.logger.info(f"Engine run finished. Time taken: {hours:02d}:{mins:02d}:{secs:06.3f}") except BaseException as e: self._dataloader_iter = None @@ -1187,17 +1185,17 @@ def _internal_run_legacy(self) -> State: time_taken = time.time() - start_time # time is available for handlers but must be updated after fire self.state.times[Events.COMPLETED.name] = time_taken - handlers_start_time = time.time() - # do not fire Events.COMPLETED if we terminated the run with flag `skip_completed_after_termination=True` - if self.should_terminate and not self.skip_completed_after_termination or not self.should_terminate: + # do not fire Events.COMPLETED if we terminated the run with flag `skip_completed=True` + if not (self.should_terminate and self.skip_completed_after_termination): + handlers_start_time = time.time() self._fire_event(Events.COMPLETED) + time_taken += time.time() - handlers_start_time + # update time wrt handlers + self.state.times[Events.COMPLETED.name] = time_taken - time_taken += time.time() - handlers_start_time - # update time wrt handlers - self.state.times[Events.COMPLETED.name] = time_taken hours, mins, secs = _to_hours_mins_secs(time_taken) - self.logger.info(f"Engine run complete. Time taken: {hours:02d}:{mins:02d}:{secs:06.3f}") + self.logger.info(f"Engine run finished. Time taken: {hours:02d}:{mins:02d}:{secs:06.3f}") except BaseException as e: self._dataloader_iter = None From 44e837ee727c6a63404aef2fb23f8f1789b80357 Mon Sep 17 00:00:00 2001 From: Fabio Bonassi Date: Tue, 3 Dec 2024 00:50:30 +0100 Subject: [PATCH 11/16] Restored .gitignore Sorry for accidentally including it into the previous commit! --- .gitignore | 2 -- 1 file changed, 2 deletions(-) diff --git a/.gitignore b/.gitignore index 602f6c10027..859e3fe8feb 100644 --- a/.gitignore +++ b/.gitignore @@ -44,5 +44,3 @@ coverage.xml # Virtualenv .venv/ .python-version -.neptune/ -.vscode/ From 3f386450e98b92531c592036bbc8f24c8783fdd2 Mon Sep 17 00:00:00 2001 From: vfdev Date: Tue, 3 Dec 2024 00:56:42 +0100 Subject: [PATCH 12/16] Update ignite/engine/events.py --- ignite/engine/events.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ignite/engine/events.py b/ignite/engine/events.py index af5ebbbabe1..4f43f8cb18d 100644 --- a/ignite/engine/events.py +++ b/ignite/engine/events.py @@ -265,8 +265,9 @@ class Events(EventEnum): - EPOCH_COMPLETED : triggered when the epoch is ended. Note that this is triggered even when :meth:`~ignite.engine.engine.Engine.terminate_epoch()` is called. - - COMPLETED : triggered when engine's run is completed or terminated with :meth:`~ignite.engine.engine.Engine.terminate()`, - unless the flag `skip_completed` is set to True. + - COMPLETED : triggered when engine's run is completed or terminated with + :meth:`~ignite.engine.engine.Engine.terminate()`, unless the flag + `skip_completed` is set to True. The table below illustrates which events are triggered when various termination methods are called. From 17e973f9ff46bbc7ecaa57fd8d09725aff0e57bd Mon Sep 17 00:00:00 2001 From: Fabio Bonassi Date: Tue, 3 Dec 2024 09:19:12 +0100 Subject: [PATCH 13/16] Fixed typo in test_engine.py --- tests/ignite/contrib/engines/test_common.py | 2 +- tests/ignite/engine/test_engine.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/ignite/contrib/engines/test_common.py b/tests/ignite/contrib/engines/test_common.py index e14042e62c1..c93a081c754 100644 --- a/tests/ignite/contrib/engines/test_common.py +++ b/tests/ignite/contrib/engines/test_common.py @@ -1,6 +1,6 @@ import os import sys -from unittest.mock import call, MagicMock +from unittest.mock import MagicMock, call import pytest import torch diff --git a/tests/ignite/engine/test_engine.py b/tests/ignite/engine/test_engine.py index 43957bf45d4..e72ef1481f1 100644 --- a/tests/ignite/engine/test_engine.py +++ b/tests/ignite/engine/test_engine.py @@ -242,7 +242,7 @@ def check_iter_and_data(): assert num_calls_check_iter_epoch == 1 @pytest.mark.parametrize( - "terminate_event, e, i, skip_event_completed", + "terminate_event, e, i, skip_completed", [ (Events.STARTED, 0, 0, True), (Events.EPOCH_STARTED(once=2), 2, None, True), From 7e408b7516251edb2a4fd5568ceabadd39827f07 Mon Sep 17 00:00:00 2001 From: Fabio Bonassi Date: Tue, 3 Dec 2024 10:55:04 +0100 Subject: [PATCH 14/16] Parametrized test for engine.terminate(skip_completed) --- tests/ignite/contrib/engines/test_common.py | 2 +- tests/ignite/engine/test_engine.py | 17 +++++++---------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/tests/ignite/contrib/engines/test_common.py b/tests/ignite/contrib/engines/test_common.py index c93a081c754..e14042e62c1 100644 --- a/tests/ignite/contrib/engines/test_common.py +++ b/tests/ignite/contrib/engines/test_common.py @@ -1,6 +1,6 @@ import os import sys -from unittest.mock import MagicMock, call +from unittest.mock import call, MagicMock import pytest import torch diff --git a/tests/ignite/engine/test_engine.py b/tests/ignite/engine/test_engine.py index e72ef1481f1..fcb0299aa22 100644 --- a/tests/ignite/engine/test_engine.py +++ b/tests/ignite/engine/test_engine.py @@ -40,17 +40,14 @@ class TestEngine: def set_interrupt_resume_enabled(self, interrupt_resume_enabled): Engine.interrupt_resume_enabled = interrupt_resume_enabled - def test_terminate(self): + @pytest.mark.parametrize("skip_completed", [True, False]) + def test_terminate(self, skip_completed): engine = Engine(lambda e, b: 1) - assert not engine.should_terminate and not engine.skip_completed_after_termination - engine.terminate() - assert engine.should_terminate and not engine.skip_completed_after_termination - - def test_terminate_and_not_complete(self): - engine = Engine(lambda e, b: 1) - assert not engine.should_terminate and not engine.skip_completed_after_termination - engine.terminate(skip_completed=True) - assert engine.should_terminate and engine.skip_completed_after_termination + assert not engine.should_terminate + assert not engine.skip_completed_after_termination + engine.terminate(skip_completed) + assert engine.should_terminate + assert engine.skip_completed_after_termination == skip_completed def test_invalid_process_raises_with_invalid_signature(self): with pytest.raises(ValueError, match=r"Engine must be given a processing function in order to run"): From cf1cbcd8c0c49fbca62295f8fd72d0c1efb3f39a Mon Sep 17 00:00:00 2001 From: Fabio Bonassi Date: Tue, 3 Dec 2024 13:55:09 +0100 Subject: [PATCH 15/16] Update event table --- ignite/engine/events.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/ignite/engine/events.py b/ignite/engine/events.py index 4f43f8cb18d..ddb54439792 100644 --- a/ignite/engine/events.py +++ b/ignite/engine/events.py @@ -259,12 +259,12 @@ class Events(EventEnum): - TERMINATE_SINGLE_EPOCH : triggered when the run is about to end the current epoch, after receiving a :meth:`~ignite.engine.engine.Engine.terminate_epoch()` or :meth:`~ignite.engine.engine.Engine.terminate()` call. + - EPOCH_COMPLETED : triggered when the epoch is ended. Note that this is triggered even + when :meth:`~ignite.engine.engine.Engine.terminate_epoch()` is called. - TERMINATE : triggered when the run is about to end completely, after receiving :meth:`~ignite.engine.engine.Engine.terminate()` call. - - EPOCH_COMPLETED : triggered when the epoch is ended. Note that this is triggered even - when :meth:`~ignite.engine.engine.Engine.terminate_epoch()` is called. - COMPLETED : triggered when engine's run is completed or terminated with :meth:`~ignite.engine.engine.Engine.terminate()`, unless the flag `skip_completed` is set to True. @@ -272,25 +272,34 @@ class Events(EventEnum): The table below illustrates which events are triggered when various termination methods are called. .. list-table:: - :widths: 24 25 33 18 + :widths: 38 33 28 20 20 :header-rows: 1 * - Method - - EVENT_COMPLETED - TERMINATE_SINGLE_EPOCH + - EPOCH_COMPLETED - TERMINATE + - COMPLETED * - no termination - - ✔ - ✗ + - ✔ - ✗ + - ✔ * - :meth:`~ignite.engine.engine.Engine.terminate_epoch()` - ✔ - ✔ - ✗ + - ✔ * - :meth:`~ignite.engine.engine.Engine.terminate()` - - (✔) + - ✗ - ✔ - ✔ + - ✔ + * - :meth:`~ignite.engine.engine.Engine.terminate(skip_completed=True)` + - ✗ + - ✔ + - ✔ + - ✗ Since v0.3.0, Events become more flexible and allow to pass an event filter to the Engine: From a133dfbf39ebb9d77556cea63c6153a517f09c33 Mon Sep 17 00:00:00 2001 From: Fabio Bonassi Date: Tue, 3 Dec 2024 14:43:51 +0100 Subject: [PATCH 16/16] Fixed documentation --- ignite/engine/events.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ignite/engine/events.py b/ignite/engine/events.py index ddb54439792..87622d3415c 100644 --- a/ignite/engine/events.py +++ b/ignite/engine/events.py @@ -272,7 +272,7 @@ class Events(EventEnum): The table below illustrates which events are triggered when various termination methods are called. .. list-table:: - :widths: 38 33 28 20 20 + :widths: 35 38 28 20 20 :header-rows: 1 * - Method @@ -295,7 +295,7 @@ class Events(EventEnum): - ✔ - ✔ - ✔ - * - :meth:`~ignite.engine.engine.Engine.terminate(skip_completed=True)` + * - :meth:`~ignite.engine.engine.Engine.terminate()` with `skip_completed=True` - ✗ - ✔ - ✔