# Copyright 2020 - 2021 MONAI Consortium
# 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.
"""
Wrapper around NVIDIA Tools Extension for profiling MONAI ignite workflow
"""
from typing import TYPE_CHECKING, Optional, Tuple, Union
from monai.config import IgniteInfo
from monai.utils import ensure_tuple, min_version, optional_import
_nvtx, _ = optional_import("torch._C._nvtx", descriptor="NVTX is not installed. Are you sure you have a CUDA build?")
if TYPE_CHECKING:
from ignite.engine import Engine, Events
else:
Engine, _ = optional_import("ignite.engine", IgniteInfo.OPT_IMPORT_VERSION, min_version, "Engine")
Events, _ = optional_import("ignite.engine", IgniteInfo.OPT_IMPORT_VERSION, min_version, "Events")
__all__ = ["RangeHandler", "RangePushHandler", "RangePopHandler", "MarkHandler"]
[docs]class RangeHandler:
"""
Attach a NVTX range to a pair of Ignite events.
It pushes an NVTX range at the first event and pops it at the second event.
Stores zero-based depth of the range that is started.
Args:
events: a string, pair of Ignite events, pair of Ignite event literals, or pair of Ignite events and literals.
If a single string is provided, it should describe the base name of a pair of default Ignite events
with _STARTED and _COMPLETED postfix (like "EPOCH" for Events.EPOCH_STARTED and Events.EPOCH_COMPLETED).
The accepted events are: BATCH, ITERATION, EPOCH, and ENGINE.
If pair of literals, each should be the literal equivalent of an Ignite event, fo instance:
("EPOCH_STARTED" and "EPOCH_COMPLETED").
One can combine events and literals, like (Events.EPOCH_STARTED and "EPOCH_COMPLETED").
For the complete list of Events,
check https://pytorch.org/ignite/generated/ignite.engine.events.Events.html.
msg: ASCII message to associate with range.
If not provided, the name of first event will be assigned to the NVTX range.
"""
def __init__(
self, events: Union[str, Tuple[Union[str, Events], Union[str, Events]]], msg: Optional[str] = None
) -> None:
self.events = self.resolve_events(events)
if msg is None:
if isinstance(events, str):
# assign the prefix of the events
msg = events
else:
# combine events' names
msg = "/".join([e.name for e in self.events])
self.msg = msg
self.depth = None
[docs] def resolve_events(self, events: Union[str, Tuple]) -> Tuple[Events, Events]:
"""
Resolve the input events to create a pair of Ignite events
"""
events = ensure_tuple(events)
if len(events) == 1:
return self.create_paired_events(events[0])
if len(events) == 2:
return self.get_event(events[0]), self.get_event(events[1])
raise ValueError(f"Exactly two Ignite events should be provided [received {len(events)}].")
[docs] def create_paired_events(self, event: str) -> Tuple[Events, Events]:
"""
Create pair of Ignite events from a event prefix name
"""
event = event.upper()
event_prefix = {"": "", "ENGINE": "", "EPOCH": "EPOCH_", "ITERATION": "ITERATION_", "BATCH": "GET_BATCH_"}
return self.get_event(event_prefix[event] + "STARTED"), self.get_event(event_prefix[event] + "COMPLETED")
def get_event(self, event: Union[str, Events]) -> Events:
return Events[event.upper()] if isinstance(event, str) else event
[docs] def attach(self, engine: Engine) -> None:
"""
Attach an NVTX Range to specific Ignite events
Args:
engine: Ignite Engine, it can be a trainer, validator or evaluator.
"""
engine.add_event_handler(self.events[0], self.range_push)
engine.add_event_handler(self.events[1], self.range_pop)
def range_push(self):
self.depth = _nvtx.rangePushA(self.msg)
def range_pop(self):
_nvtx.rangePop()
[docs]class RangePushHandler:
"""
At a specific event, pushes a range onto a stack of nested range span.
Stores zero-based depth of the range that is started.
Args:
msg: ASCII message to associate with range
"""
def __init__(self, event: Union[str, Events], msg: Optional[str] = None) -> None:
self.event = Events[event.upper()] if isinstance(event, str) else event
if msg is None:
msg = self.event.name
self.msg = msg
self.depth = None
[docs] def attach(self, engine: Engine) -> None:
"""
Push an NVTX range at a specific Ignite event
Args:
engine: Ignite Engine, it can be a trainer, validator or evaluator.
"""
engine.add_event_handler(self.event, self.range_push)
def range_push(self):
self.depth = _nvtx.rangePushA(self.msg)
[docs]class RangePopHandler:
"""
At a specific event, pop a previously pushed range.
Stores zero-based depth of the range that is started.
Args:
msg: ASCII message to associate with range
"""
def __init__(self, event: Union[str, Events]) -> None:
self.event = Events[event.upper()] if isinstance(event, str) else event
[docs] def attach(self, engine: Engine) -> None:
"""
Pop an NVTX range at a specific Ignite event
Args:
engine: Ignite Engine, it can be a trainer, validator or evaluator.
"""
engine.add_event_handler(self.event, self.range_pop)
def range_pop(self):
_nvtx.rangePop()
[docs]class MarkHandler:
"""
Mark an instantaneous event that occurred at some point.
Args:
msg: ASCII message to associate with range
"""
def __init__(self, event: Union[str, Events], msg: Optional[str] = None) -> None:
self.event = Events[event.upper()] if isinstance(event, str) else event
if msg is None:
msg = self.event.name
self.msg = msg
[docs] def attach(self, engine: Engine) -> None:
"""
Add an NVTX mark to a specific Ignite event
Args:
engine: Ignite Engine, it can be a trainer, validator or evaluator.
"""
engine.add_event_handler(self.event, self.mark)
def mark(self):
_nvtx.markA(self.msg)