Spaces:
Running on Zero
Running on Zero
Download cosmos_framework/utils/profiling.py from hugging-apps/greenwam: direct link, hf CLI and curl.
- Browser
- Download file 9.29 kB
-
https://huggingface.co/spaces/hugging-apps/greenwam/resolve/main/cosmos_framework/utils/profiling.py
- Command line
-
hf download hf://spaces/hugging-apps/greenwam/cosmos_framework/utils/profiling.py
-
curl -L -o profiling.py https://huggingface.co/spaces/hugging-apps/greenwam/resolve/main/cosmos_framework/utils/profiling.py
9.29 kB
| # SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. | |
| # SPDX-License-Identifier: OpenMDW-1.1 | |
| import contextlib | |
| import os | |
| import time | |
| import torch | |
| from cosmos_framework.utils import distributed, log | |
| from cosmos_framework.utils.easy_io import easy_io | |
| # (qsh 2024-11-23) credits | |
| # https://github.com/pytorch/torchtitan/blob/main/torchtitan/profiling.py | |
| # how much memory allocation/free ops to record in memory snapshots | |
| MEMORY_SNAPSHOT_MAX_ENTRIES = 100000 | |
| def validate_profile_schedule(*, profile_freq: int, warmup: int, active: int) -> None: | |
| if active <= 0: | |
| raise ValueError("profile_active must be positive") | |
| if warmup < 0: | |
| raise ValueError("profile_warmup must be non-negative") | |
| if profile_freq < warmup + active: | |
| raise ValueError("profile_freq must be greater than or equal to profile_warmup + profile_active") | |
| def maybe_enable_profiling(config, *, global_step: int = 0): | |
| # get user defined profiler settings | |
| enable_profiling = config.trainer.profiling.enable_profiling | |
| profile_freq = config.trainer.profiling.profile_freq | |
| if enable_profiling: | |
| trace_dir = os.path.join(config.job.path_local, "torch_trace") | |
| if distributed.get_rank() == 0: | |
| os.makedirs(trace_dir, exist_ok=True) | |
| rank = distributed.get_rank() | |
| def trace_handler(prof): | |
| curr_trace_dir_name = "iteration_" + str(prof.step_num) | |
| curr_trace_dir = os.path.join(trace_dir, curr_trace_dir_name) | |
| if not os.path.exists(curr_trace_dir): | |
| os.makedirs(curr_trace_dir, exist_ok=True) | |
| log.info(f"Dumping traces at step {prof.step_num}") | |
| begin = time.monotonic() | |
| if rank in config.trainer.profiling.target_ranks: | |
| prof.export_chrome_trace(f"{curr_trace_dir}/rank{rank}_trace.json.gz") | |
| log.info(f"Finished dumping traces in {time.monotonic() - begin:.2f} seconds") | |
| log.info(f"Profiling active. Traces will be saved at {trace_dir}") | |
| if not os.path.exists(trace_dir): | |
| os.makedirs(trace_dir, exist_ok=True) | |
| warmup = config.trainer.profiling.profile_warmup | |
| active = config.trainer.profiling.profile_active | |
| validate_profile_schedule(profile_freq=profile_freq, warmup=warmup, active=active) | |
| wait = profile_freq - (active + warmup) | |
| with torch.profiler.profile( | |
| activities=[ | |
| torch.profiler.ProfilerActivity.CPU, | |
| torch.profiler.ProfilerActivity.CUDA, | |
| ], | |
| schedule=torch.profiler.schedule(wait=wait, warmup=warmup, active=active), | |
| on_trace_ready=trace_handler, | |
| record_shapes=config.trainer.profiling.record_shape, | |
| profile_memory=config.trainer.profiling.profile_memory, | |
| with_stack=config.trainer.profiling.with_stack, | |
| with_modules=config.trainer.profiling.with_modules, | |
| ) as torch_profiler: | |
| torch_profiler.step_num = global_step | |
| yield torch_profiler | |
| else: | |
| torch_profiler = contextlib.nullcontext() | |
| yield None | |
| def maybe_enable_memory_snapshot(config, *, global_step: int = 0): | |
| enable_snapshot = config.trainer.profiling.enable_memory_snapshot | |
| if enable_snapshot: | |
| if config.trainer.profiling.save_s3: | |
| snapshot_dir = "s3://rundir" | |
| else: | |
| snapshot_dir = os.path.join(config.job.path_local, "memory_snapshot") | |
| if distributed.get_rank() == 0: | |
| os.makedirs(snapshot_dir, exist_ok=True) | |
| rank = torch.distributed.get_rank() | |
| class MemoryProfiler: | |
| def __init__(self, step_num: int, freq: int): | |
| torch.cuda.memory._record_memory_history(max_entries=MEMORY_SNAPSHOT_MAX_ENTRIES) | |
| # when resume training, we start from the last step | |
| self.step_num = step_num | |
| self.freq = freq | |
| def step(self, exit_ctx: bool = False): | |
| self.step_num += 1 | |
| if not exit_ctx and self.step_num % self.freq != 0: | |
| return | |
| if not exit_ctx: | |
| curr_step = self.step_num | |
| dir_name = f"iteration_{curr_step}" | |
| else: | |
| # dump as iteration_0_exit if OOM at iter 1 | |
| curr_step = self.step_num - 1 | |
| dir_name = f"iteration_{curr_step}_exit" | |
| curr_snapshot_dir = os.path.join(snapshot_dir, dir_name) | |
| if not config.trainer.profiling.save_s3 and not os.path.exists(curr_snapshot_dir): | |
| os.makedirs(curr_snapshot_dir, exist_ok=True) | |
| log.info(f"Dumping memory snapshot at step {curr_step}") | |
| begin = time.monotonic() | |
| if rank in config.trainer.profiling.target_ranks: | |
| easy_io.dump( | |
| torch.cuda.memory._snapshot(), | |
| f"{curr_snapshot_dir}/rank{rank}_memory_snapshot.pickle", | |
| ) | |
| log.info(f"Finished dumping memory snapshot in {time.monotonic() - begin:.2f} seconds") | |
| log.info(f"Memory profiler active. Snapshot will be saved at {snapshot_dir}") | |
| profiler = MemoryProfiler(global_step, config.trainer.profiling.profile_freq) | |
| try: | |
| yield profiler | |
| except torch.cuda.OutOfMemoryError: | |
| # Dump what the allocator was holding when it ran out, then let the OOM keep going. | |
| # Swallowing it would end the train loop and return from trainer.train() normally, so | |
| # the process would exit 0 and a run truncated at iteration 3 would be indistinguishable | |
| # from one that reached max_iter — and a launcher configured to resubmit on failure | |
| # would see nothing to resubmit. Bare re-raise: the traceback stays pointed at the | |
| # allocation that failed rather than at this line. | |
| try: | |
| profiler.step(exit_ctx=True) | |
| except Exception: | |
| # The dump is best-effort, and a failed dump must not become the reported cause of | |
| # death: snapshotting an exhausted allocator can OOM again, and the write itself can | |
| # fail. Letting that surface would swap the exception callers see and point the | |
| # traceback at the serializer instead of the allocation. Logged on every rank, since | |
| # the rank that ran out of memory is usually not rank 0. | |
| log.exception("Failed to dump the exit memory snapshot", rank0_only=False) | |
| raise | |
| else: | |
| yield None | |
| def maybe_enable_nsys_profiling(config, *, global_step: int = 0): | |
| """Context manager for Nsight Systems profiling via cudaProfilerStart/Stop. | |
| Usage: launch training with | |
| nsys profile --capture-range=cudaProfilerApi --capture-range-end=stop python ... | |
| and set trainer.profiling.enable_nsys=true, profile_freq=<iter>. | |
| Reuses the torch-profile flags (profile_freq, target_ranks, profile_warmup, | |
| profile_active). The profiler is started `profile_warmup` iterations before | |
| the active range and stopped immediately after it. | |
| """ | |
| enable_nsys = config.trainer.profiling.enable_nsys | |
| if not enable_nsys: | |
| yield None | |
| return | |
| rank = distributed.get_rank() | |
| target_ranks = config.trainer.profiling.target_ranks | |
| freq = config.trainer.profiling.profile_freq | |
| warmup = config.trainer.profiling.profile_warmup | |
| active = config.trainer.profiling.profile_active | |
| validate_profile_schedule(profile_freq=freq, warmup=warmup, active=active) | |
| active_start_iter = freq - active | |
| active_end_iter = freq - 1 | |
| start_iter = max(0, active_start_iter - warmup) | |
| class NsysProfiler: | |
| def __init__(self, step_num: int): | |
| self.step_num = step_num | |
| self._profiling = False | |
| self._start_if_needed() | |
| def _start_if_needed(self) -> None: | |
| if rank not in target_ranks: | |
| return | |
| if self.step_num == start_iter and not self._profiling: | |
| log.info( | |
| f"[Nsys] Starting CUDA profiler at iter {self.step_num} " | |
| f"(active iterations: {active_start_iter}-{active_end_iter})" | |
| ) | |
| torch.cuda.cudart().cudaProfilerStart() | |
| self._profiling = True | |
| def step(self) -> None: | |
| self.step_num += 1 | |
| self._start_if_needed() | |
| if rank not in target_ranks: | |
| return | |
| if self.step_num == active_end_iter + 1 and self._profiling: | |
| torch.cuda.cudart().cudaProfilerStop() | |
| self._profiling = False | |
| log.info(f"[Nsys] Stopped CUDA profiler at iter {self.step_num}") | |
| log.info(f"[Nsys] Profiling enabled. Will capture iter {start_iter}-{active_end_iter} on ranks {target_ranks}") | |
| profiler = NsysProfiler(global_step) | |
| try: | |
| yield profiler | |
| finally: | |
| if profiler._profiling: | |
| torch.cuda.cudart().cudaProfilerStop() | |