Source code for pimm.engines.hooks.profiling
"""Runtime profiling hooks."""
import sys
import os
import torch
from .default import HookBase
from .builder import HOOKS
[docs]
@HOOKS.register_module()
class RuntimeProfiler(HookBase):
"""Run a short ``torch.profiler`` trace before normal training proceeds.
Runs once in ``before_train``: iterates ``warm_up + 1`` batches from the
train loader under ``torch.profiler.profile`` (CPU+CUDA activities), running
forward (and backward, on the ``"loss"`` output) per the ``forward`` /
``backward`` flags. It writes a TensorBoard trace into
``<save_path>/logdir/``, logs the top operators sorted by ``sort_by``, and —
when ``memory=True`` — records and dumps a CUDA memory snapshot to
``<save_path>/memory_snapshot.pickle``. With ``interrupt=True`` it calls
``sys.exit(0)`` after profiling so the process stops before real training.
Registered as ``RuntimeProfiler``.
Args:
forward (bool): Run the model forward pass during profiling. Defaults to
``True``.
backward (bool): Run ``loss.backward()`` during profiling. Defaults to
``True``.
interrupt (bool): Exit the process (``sys.exit(0)``) after profiling
instead of continuing into training. Defaults to ``False``.
warm_up (int): Number of warm-up iterations before the active profiling
window (the loop runs ``warm_up + 1`` steps). Defaults to ``2``.
sort_by (str): Key for sorting the printed operator table, e.g.
``"cuda_time_total"``. Defaults to ``"cuda_time_total"``.
row_limit (int): Maximum rows in the printed operator table. Defaults to
``30``.
memory (bool): Record and dump a CUDA memory-history snapshot. Defaults
to ``True``.
Note:
Intended as a diagnostic run, typically with ``interrupt=True`` so the
job exits after collecting the trace. Requires CUDA for the memory
snapshot.
Example:
Add to ``cfg.hooks`` for a one-off profiling run; once in ``before_train``
it traces a few steps, then (with ``interrupt=True``) exits before real
training begins:
.. code-block:: python
hooks = [dict(type="RuntimeProfiler", warm_up=2, interrupt=True)]
# → runs warm_up+1 batches under torch.profiler, writes a TensorBoard
# trace to <save_path>/logdir/, dumps <save_path>/memory_snapshot.pickle,
# logs the top-30 ops by cuda_time_total, then calls sys.exit(0)
"""
def __init__(
self,
forward=True,
backward=True,
interrupt=False,
warm_up=2,
sort_by="cuda_time_total",
row_limit=30,
memory=True,
):
self.forward = forward
self.backward = backward
self.interrupt = interrupt
self.warm_up = warm_up
self.sort_by = sort_by
self.row_limit = row_limit
self.memory = memory
[docs]
def before_train(self):
self.trainer.logger.info("Profiling runtime ...")
from torch.profiler import profile, record_function, ProfilerActivity
if self.memory:
torch.cuda.memory._record_memory_history()
logdir = self.trainer.cfg.save_path + "/logdir/"
if not os.path.exists(logdir):
os.makedirs(logdir)
# schedule needs: wait + warmup + active steps (times repeat)
# loop runs warm_up + 1 iterations, so we match the schedule accordingly
num_steps = self.warm_up + 1
with profile(
activities=[ProfilerActivity.CPU, ProfilerActivity.CUDA],
on_trace_ready=torch.profiler.tensorboard_trace_handler(
logdir, use_gzip=True
),
schedule=torch.profiler.schedule(wait=0, warmup=1, active=max(1, num_steps - 1), repeat=1),
record_shapes=True,
profile_memory=True,
with_stack=True,
) as prof:
for i, input_dict in enumerate(self.trainer.train_loader):
if i == num_steps:
break
for key in input_dict.keys():
if isinstance(input_dict[key], torch.Tensor):
input_dict[key] = input_dict[key].cuda(non_blocking=True)
if self.forward:
# with record_function("model_forward"):
output_dict = self.trainer.model(input_dict)
else:
output_dict = self.trainer.model(input_dict)
loss = output_dict["loss"]
if self.backward:
# with record_function("model_backward"):
loss.backward()
prof.step()
self.trainer.logger.info(f"Profile: [{i + 1}/{num_steps}]")
if self.forward or self.backward:
self.trainer.logger.info(
"Profile: \n"
+ str(
prof.key_averages().table(
sort_by=self.sort_by, row_limit=self.row_limit
)
)
)
# prof.export_chrome_trace(
# os.path.join(self.trainer.cfg.save_path, "trace.json")
# )
if self.memory:
torch.cuda.memory._dump_snapshot(
os.path.join(self.trainer.cfg.save_path, "memory_snapshot.pickle")
)
if self.interrupt:
sys.exit(0)