| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| import datetime |
| import inspect |
| import logging |
| from contextlib import contextmanager |
| from typing import Any, Dict, Tuple |
|
|
| import torch |
| import torch.distributed as dist |
| from codetiming import Timer |
|
|
| from verl.utils.device import get_torch_device |
| from verl.utils.logger.aggregate_logger import DecoratorLoggerBase |
|
|
|
|
| def _get_current_mem_info(unit: str = "GB", precision: int = 2) -> Tuple[str]: |
| """Get current memory usage.""" |
| assert unit in ["GB", "MB", "KB"] |
| divisor = 1024**3 if unit == "GB" else 1024**2 if unit == "MB" else 1024 |
| mem_allocated = get_torch_device().memory_allocated() |
| mem_reserved = get_torch_device().memory_reserved() |
| |
| |
| |
| mem_free, mem_total = get_torch_device().mem_get_info() |
| mem_used = mem_total - mem_free |
| mem_allocated = f"{mem_allocated / divisor:.{precision}f}" |
| mem_reserved = f"{mem_reserved / divisor:.{precision}f}" |
| mem_used = f"{mem_used / divisor:.{precision}f}" |
| mem_total = f"{mem_total / divisor:.{precision}f}" |
| return mem_allocated, mem_reserved, mem_used, mem_total |
|
|
|
|
| def log_gpu_memory_usage(head: str, logger: logging.Logger = None, level=logging.DEBUG, rank: int = 0): |
| if (not dist.is_initialized()) or (rank is None) or (dist.get_rank() == rank): |
| mem_allocated, mem_reserved, mem_used, mem_total = _get_current_mem_info() |
| message = f"{head}, memory allocated (GB): {mem_allocated}, memory reserved (GB): {mem_reserved}, device memory used/total (GB): {mem_used}/{mem_total}" |
|
|
| if logger is None: |
| print(message) |
| else: |
| logger.log(msg=message, level=level) |
|
|
|
|
| class GPUMemoryLogger(DecoratorLoggerBase): |
| """A decorator class to log GPU memory usage. |
| |
| Example: |
| >>> from verl.utils.debug.performance import GPUMemoryLogger |
| >>> @GPUMemoryLogger(role="actor") |
| >>> def update_actor(self, batch): |
| ... # real actor update logics |
| ... return |
| """ |
|
|
| def __init__(self, role: str, logger: logging.Logger = None, level=logging.DEBUG, log_only_rank_0: bool = True): |
| if dist.is_initialized() and dist.get_world_size() > 1: |
| rank = dist.get_rank() |
| else: |
| rank = 0 |
| super().__init__(role, logger, level, rank, log_only_rank_0) |
|
|
| def __call__(self, decorated_function: callable): |
| def f(*args, **kwargs): |
| return self.log(decorated_function, *args, **kwargs) |
|
|
| return f |
|
|
| def log(self, func, *args, **kwargs): |
| name = func.__name__ |
| mem_allocated, mem_reserved, mem_used, mem_total = _get_current_mem_info() |
| message = f"Before {name}, memory allocated (GB): {mem_allocated}, memory reserved (GB): {mem_reserved}, device memory used/total (GB): {mem_used}/{mem_total}" |
| self.logging_function(message) |
|
|
| output = func(*args, **kwargs) |
|
|
| mem_allocated, mem_reserved, mem_used, mem_total = _get_current_mem_info() |
| message = f"After {name}, memory allocated (GB): {mem_allocated}, memory reserved (GB): {mem_reserved}, device memory used/total (GB): {mem_used}/{mem_total}" |
|
|
| self.logging_function(message) |
| return output |
|
|
|
|
| def log_print(ctn: Any): |
| current_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") |
|
|
| frame = inspect.currentframe().f_back |
| function_name = frame.f_code.co_name |
| line_number = frame.f_lineno |
| file_name = frame.f_code.co_filename.split("/")[-1] |
| print(f"[{current_time}-{file_name}:{line_number}:{function_name}]: {ctn}") |
|
|
|
|
| @contextmanager |
| def _timer(name: str, timing_raw: Dict[str, float]): |
| """Context manager for timing code execution. |
| |
| This utility function measures the execution time of code within its context |
| and accumulates the timing information in the provided dictionary. |
| |
| Args: |
| name (str): The name/identifier for this timing measurement. |
| timing_raw (Dict[str, float]): Dictionary to store timing information. |
| |
| Yields: |
| None: This is a context manager that yields control back to the code block. |
| """ |
| with Timer(name=name, text="{name}: {seconds:.1f} seconds", logger=None) as timer: |
| yield |
| if name not in timing_raw: |
| timing_raw[name] = 0 |
| timing_raw[name] += timer.last |
|
|
|
|
| def reduce_timing(timing_raw: Dict[str, float]) -> Dict[str, float]: |
| """Reduce timing information across all processes. |
| |
| This function uses distributed communication to gather and sum the timing |
| information from all processes in a distributed environment. |
| |
| Args: |
| timing_raw (Dict[str, float]): Dictionary containing timing information. |
| |
| Returns: |
| Dict[str, float]: Reduced timing information. |
| """ |
| if not dist.is_initialized(): |
| return timing_raw |
|
|
| key_list, timing_list = [], [] |
| for key in sorted(timing_raw.keys()): |
| key_list.append(key) |
| timing_list.append(timing_raw[key]) |
| timing_list = torch.tensor(timing_list, dtype=torch.float32, device=torch.cuda.current_device()) |
| torch.distributed.all_reduce(timing_list, op=torch.distributed.ReduceOp.AVG) |
| timing_list = [tensor.item() for tensor in timing_list.to("cpu")] |
| timing_generate = {key_list[i]: timing_list[i] for i in range(len(key_list))} |
| return timing_generate |
|
|