from __future__ import annotations import os from datetime import timedelta import torch import torch.distributed as dist def is_dist_avail_and_initialized() -> bool: return dist.is_available() and dist.is_initialized() def get_rank() -> int: if not is_dist_avail_and_initialized(): return 0 return dist.get_rank() def get_world_size() -> int: if not is_dist_avail_and_initialized(): return 1 return dist.get_world_size() def is_main_process() -> bool: return get_rank() == 0 def init_distributed(backend: str = "nccl") -> torch.device: """Initialize distributed training. Validation on 3D medical volumes can take longer than PyTorch's default 10 minute NCCL/RCCL watchdog timeout if some ranks are waiting at a collective. We therefore set a longer timeout by default. The value can be overridden with DIST_TIMEOUT_MINUTES. """ if "RANK" in os.environ and "WORLD_SIZE" in os.environ: local_rank = int(os.environ.get("LOCAL_RANK", 0)) torch.cuda.set_device(local_rank) timeout_min = int(os.environ.get("DIST_TIMEOUT_MINUTES", "180")) dist.init_process_group( backend=backend, init_method="env://", timeout=timedelta(minutes=timeout_min), ) device = torch.device("cuda", local_rank) else: device = torch.device("cuda" if torch.cuda.is_available() else "cpu") return device def barrier(): if is_dist_avail_and_initialized(): if torch.cuda.is_available(): dist.barrier(device_ids=[torch.cuda.current_device()]) else: dist.barrier() def cleanup(): if is_dist_avail_and_initialized(): dist.destroy_process_group() def reduce_mean(tensor: torch.Tensor) -> torch.Tensor: if not is_dist_avail_and_initialized(): return tensor rt = tensor.detach().clone() dist.all_reduce(rt, op=dist.ReduceOp.SUM) rt /= get_world_size() return rt