CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
137 lines
4.6 KiB
Python
137 lines
4.6 KiB
Python
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 Advanced Micro Devices, Inc. All rights reserved.
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
"""Shared pytest fixtures for the RCCL fork's mpi-marked test suite.
|
|
|
|
All fixtures here exist for ``tests/test_collectives.py`` and
|
|
``tests/test_interop.py``. The unit-style files (``test_loader_stubs.py``,
|
|
``test_shim_surface.py``, ``test_rocm_extensions.py``) do not import any
|
|
of these fixtures and remain runnable in a single-rank CPU environment.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
|
|
import pytest
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# MPI / hardware availability checks (module-level, evaluated at collect time
|
|
# so the unit-style files never get blocked on a missing MPI runtime).
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def _maybe_mpi():
|
|
"""Return MPI module if available, else None (no exception)."""
|
|
try:
|
|
from mpi4py import MPI # type: ignore
|
|
except Exception: # pragma: no cover - mpi4py optional on CPU hosts
|
|
return None
|
|
return MPI
|
|
|
|
|
|
def _maybe_torch():
|
|
try:
|
|
import torch # type: ignore
|
|
except Exception: # pragma: no cover
|
|
return None
|
|
return torch
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Fixtures
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def mpi_world():
|
|
"""``MPI.COMM_WORLD`` or skip if mpi4py / a live MPI runtime is missing."""
|
|
MPI = _maybe_mpi()
|
|
if MPI is None:
|
|
pytest.skip("mpi4py not installed; rerun under `mpirun -np N pytest -m mpi`")
|
|
return MPI.COMM_WORLD
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def rank_info(mpi_world):
|
|
"""Tuple of ``(rank, nranks)`` for the current MPI run."""
|
|
return mpi_world.Get_rank(), mpi_world.Get_size()
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def device(rank_info):
|
|
"""torch.device assigned to this rank as ``cuda:rank % device_count``.
|
|
|
|
Skips the entire session if either torch or any visible GPU is missing.
|
|
"""
|
|
torch = _maybe_torch()
|
|
if torch is None:
|
|
pytest.skip("torch not installed; required for GPU buffer ownership")
|
|
if not torch.cuda.is_available() or torch.cuda.device_count() == 0:
|
|
pytest.skip("no GPU visible to torch.cuda")
|
|
rank, _ = rank_info
|
|
dev_id = rank % torch.cuda.device_count()
|
|
dev = torch.device(f"cuda:{dev_id}")
|
|
torch.cuda.set_device(dev)
|
|
return dev
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def comm(mpi_world, rank_info, device):
|
|
"""Session-scoped NCCL communicator, torn down collectively at exit.
|
|
|
|
Yields a ``nccl.core.Communicator``; the caller can read
|
|
``rank``/``nranks`` from the ``rank_info`` fixture or via the
|
|
communicator's own attributes.
|
|
"""
|
|
import nccl.core as nccl
|
|
|
|
rank, nranks = rank_info
|
|
root = 0
|
|
unique_id = nccl.get_unique_id() if rank == root else None
|
|
unique_id = mpi_world.bcast(unique_id, root=root)
|
|
|
|
nccl_comm = nccl.Communicator.init(nranks=nranks, rank=rank, unique_id=unique_id)
|
|
try:
|
|
yield nccl_comm
|
|
finally:
|
|
# destroy() is collective; all ranks must call it.
|
|
nccl_comm.destroy()
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Helpers exposed as plain functions (imported by individual test modules).
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def require_at_least_n_ranks(rank_info, n: int) -> None:
|
|
"""``pytest.skip`` when fewer than ``n`` ranks were spawned."""
|
|
_, nranks = rank_info
|
|
if nranks < n:
|
|
pytest.skip(f"requires >= {n} MPI ranks (got {nranks})")
|
|
|
|
|
|
def xfail_if_fewer_than_8_gpus():
|
|
"""xfail marker for the 8-GPU run if the host does not have 8 GPUs visible.
|
|
|
|
The 8-GPU run goes green if hardware is available, otherwise it is
|
|
marked xfail with the reason recorded in the test file.
|
|
|
|
The probe is intentionally cheap — it only inspects ``torch.cuda``
|
|
and an optional ``RCCL4PY_FORCE_8GPU_XFAIL=1`` knob (used by CI to
|
|
force the xfail path on hosts that do report 8 devices but cannot
|
|
link them).
|
|
"""
|
|
torch = _maybe_torch()
|
|
visible = torch.cuda.device_count() if torch and torch.cuda.is_available() else 0
|
|
forced = os.environ.get("RCCL4PY_FORCE_8GPU_XFAIL") == "1"
|
|
if forced or visible < 8:
|
|
return pytest.mark.xfail(
|
|
reason=(
|
|
f"8-GPU run not supported on this host: visible GPUs={visible}, "
|
|
f"RCCL4PY_FORCE_8GPU_XFAIL={int(forced)}"
|
|
),
|
|
strict=False,
|
|
)
|
|
return pytest.mark.usefixtures() # no-op marker so callers can apply unconditionally
|