Source code for nvalchemi.distributed._runtime

# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# 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.
"""Recommended distributed runtime manager for nvalchemi workflows."""

from __future__ import annotations

import contextlib
import logging
import os
import warnings
from collections.abc import Iterator
from typing import Any

import torch
from physicsnemo.distributed import (
    DistributedManager,
    PhysicsNeMoUninitializedDistributedManagerWarning,
)
from torch import distributed as dist

logger = logging.getLogger(__name__)

__all__ = [
    "DistributedManager",
    "PhysicsNeMoUninitializedDistributedManagerWarning",
    "collective_device",
    "resolve_global_rank",
    "resolve_world_size",
]


[docs] def resolve_world_size() -> int: """Resolve world size from PhysicsNeMo, torch.distributed, or environment.""" if DistributedManager.is_initialized(): return int(DistributedManager().world_size) if dist.is_available() and dist.is_initialized(): return int(dist.get_world_size()) world_size = int(os.environ.get("WORLD_SIZE", 1)) return world_size
[docs] def resolve_global_rank(global_rank: int | None = None) -> int: """Resolve global rank from an explicit value, distributed state, or env.""" if global_rank is not None: return int(global_rank) if DistributedManager.is_initialized(): return int(DistributedManager().rank) if dist.is_available() and dist.is_initialized(): return int(dist.get_rank()) rank = int(os.environ.get("RANK", 0)) return rank
[docs] def collective_device(fallback: torch.device | str = "cpu") -> torch.device: """Resolve the rank-local device for distributed tensor collectives.""" if dist.is_available() and dist.is_initialized(): try: backend = dist.get_backend() except RuntimeError: backend = None if backend != "nccl": return torch.device("cpu") if DistributedManager.is_initialized(): device = torch.device(DistributedManager().device) elif torch.cuda.is_available(): index = int(os.environ.get("LOCAL_RANK", 0)) device = torch.device("cuda", index) else: device = torch.device(fallback) if device.type == "cuda" and not torch.cuda.is_available(): return torch.device("cpu") return device
# Full-precision fp32 lands far below this; reduced precision far above. _REDUCED_PRECISION_THRESHOLD = 1e-5 _warned_reduced_precision = False def pin_fp32() -> None: """Force full-precision fp32 matmul and convolution. A distributed forward pads to different shapes than a single-process one, so under reduced-precision fp32 (TF32) the backend can pick a different kernel for each and the results separate by far more than fp32 rounding. Also sets ``NVIDIA_TF32_OVERRIDE``, which is what reaches ``mp.spawn`` / ``torchrun`` workers — they inherit the environment, not the torch flags. Call before the process builds a CUDA context. Returns ------- None """ os.environ.setdefault("NVIDIA_TF32_OVERRIDE", "0") torch.backends.cuda.matmul.allow_tf32 = False torch.backends.cudnn.allow_tf32 = False torch.set_float32_matmul_precision("highest") for holder in (torch.backends.cuda.matmul, torch.backends.cudnn): if hasattr(holder, "fp32_precision"): try: holder.fp32_precision = "ieee" except Exception: # pragma: no cover - varies by torch version # Non-fatal: the primary flags above already pin precision, and # this attribute only exists on some torch versions. logger.warning("could not set fp32_precision", exc_info=True) @contextlib.contextmanager def pinned_fp32() -> Iterator[None]: """Pin full-precision fp32 for the duration of the block, then restore. The scoped counterpart to :func:`pin_fp32`, for callers that need one comparison at full precision without changing the rest of the process. Prefer :func:`pin_fp32` for a whole run: it also sets the environment variable that ``mp.spawn`` / ``torchrun`` workers inherit, and restoring that on exit would unpin the workers. Yields ------ None """ saved: list[tuple[Any, str, Any]] = [ ( torch.backends.cuda.matmul, "allow_tf32", torch.backends.cuda.matmul.allow_tf32, ), (torch.backends.cudnn, "allow_tf32", torch.backends.cudnn.allow_tf32), ] saved.extend( (holder, "fp32_precision", holder.fp32_precision) for holder in (torch.backends.cuda.matmul, torch.backends.cudnn) if hasattr(holder, "fp32_precision") ) # Reading the global precision raises once legacy (``allow_tf32``) and new # (``fp32_precision``) APIs have both been written, which ``pin_fp32`` does. try: saved_precision = torch.get_float32_matmul_precision() except RuntimeError: # pragma: no cover - depends on prior calls saved_precision = None try: pin_fp32() yield finally: for holder, attr, value in saved: try: setattr(holder, attr, value) except Exception: # pragma: no cover - varies by torch version logger.warning("could not restore %s", attr, exc_info=True) if saved_precision is not None: torch.set_float32_matmul_precision(saved_precision) def _is_reduced_precision(device: str | torch.device | None = None) -> bool: """Whether fp32 matmul currently runs on the reduced-precision path. Measured rather than read off the backend flags: which kernel runs depends on torch version, backend and shape. """ if not torch.cuda.is_available(): return False try: gen = torch.Generator(device="cpu").manual_seed(0) a = torch.randn(512, 512, generator=gen).to(device or "cuda") b = torch.randn(512, 512, generator=gen).to(device or "cuda") ref = a.double() @ b.double() err = (((a @ b).double() - ref).abs().max() / ref.abs().max()).item() except Exception: # pragma: no cover - a probe must not break a forward logger.debug("fp32 precision probe failed", exc_info=True) return False return err > _REDUCED_PRECISION_THRESHOLD def warn_if_reduced_precision( device: str | torch.device | None = None, ) -> None: """Warn once per process if reduced-precision fp32 is in force.""" global _warned_reduced_precision if _warned_reduced_precision or not torch.cuda.is_available(): return _warned_reduced_precision = True if not _is_reduced_precision(device): return warnings.warn( "Reduced-precision fp32 (TF32) is enabled; distributed and " "single-process results can then differ by much more than fp32 rounding. " "Call nvalchemi.distributed.pin_fp32() before building models if this " "run must match a reference.", UserWarning, stacklevel=3, )