Files
TheRock/rocm-systems/projects/rocshmem/python/tests/test_atomics.py
T
derek.holloway 7502bf64a4
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
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

173 lines
5.3 KiB
Python

"""Full host AMO matrix tests.
Binding coverage: 69 host AMO symbols are generated in ``rocshmem4py.cc`` and
re-exported via ``_import_amo_symbols()``. ``test_amo_matrix_completeness``
checks the same 69 (type, op) pairs — there is no drift vs the C++ macro block.
rocSHMEM also declares ``longlong`` fetch/cas/swap/etc. in the header, but these
bindings intentionally expose only ``longlong`` set/add/inc (see comment in
``rocshmem4py.cc``).
Runtime limitation: these are host AMO bindings. IPC no-MPI runtime support
for host AMOs is not implemented yet, so behavioral AMO tests are opt-in via
``ROCSHMEM4PY_RUN_HOST_AMO_IPC=1`` until the runtime grows that support.
"""
import os
import pytest
import rocshmem4py
from rocshmem4py import (
rocshmem_my_pe,
rocshmem_n_pes,
rocshmem_barrier_all,
)
from conftest import requires_torch, requires_multi_pe
try:
import torch
except ImportError:
torch = None # type: ignore[assignment]
requires_host_amo_runtime = pytest.mark.skipif(
os.environ.get("ROCSHMEM4PY_RUN_HOST_AMO_IPC") != "1",
reason=(
"IPC no-MPI host AMOs are not supported in rocSHMEM runtime yet; "
"set ROCSHMEM4PY_RUN_HOST_AMO_IPC=1 only when validating a runtime fix"
),
)
# ---------------------------------------------------------------------------
# Coverage matrix — every (type, op) we expect the binding layer to expose.
# ---------------------------------------------------------------------------
INTEGER_TYPES = ["int", "long", "uint32", "uint64", "size", "ptrdiff"]
FLOAT_TYPES = ["float", "double"]
ALL_NUMERIC_TYPES = INTEGER_TYPES + FLOAT_TYPES
_EXPECTED_OPS = {
"fetch": ALL_NUMERIC_TYPES,
"set": ALL_NUMERIC_TYPES + ["longlong"],
"swap": ALL_NUMERIC_TYPES,
"compare_swap": INTEGER_TYPES, # CAS not provided on float/double.
"fetch_add": INTEGER_TYPES,
"fetch_inc": INTEGER_TYPES,
"add": INTEGER_TYPES + ["longlong"],
"inc": INTEGER_TYPES + ["longlong"],
"fetch_and": ["uint32", "uint64"],
"fetch_or": ["uint32", "uint64"],
"fetch_xor": ["uint32", "uint64"],
"and": ["uint32", "uint64"],
"or": ["uint32", "uint64"],
"xor": ["uint32", "uint64"],
}
def test_amo_matrix_completeness():
"""Every (type, op) in the expected matrix is bound in rocshmem4py."""
missing = []
for op, types in _EXPECTED_OPS.items():
for t in types:
name = f"rocshmem_{t}_atomic_{op}"
if not hasattr(rocshmem4py, name):
missing.append(name)
assert not missing, f"Missing AMO bindings: {missing}"
from rocshmem4py import _AMO_NAMES
expected_count = sum(len(types) for types in _EXPECTED_OPS.values())
assert expected_count == len(_AMO_NAMES), (
f"test matrix ({expected_count}) vs imported AMOs ({len(_AMO_NAMES)})"
)
# longlong: only set/add/inc per rocshMEM header subset in rocshmem4py.cc.
longlong_ops = {
n.removeprefix("rocshmem_longlong_atomic_")
for n in _AMO_NAMES
if "longlong" in n
}
assert longlong_ops == {"set", "add", "inc"}
@requires_torch
@requires_multi_pe
@requires_host_amo_runtime
def test_int_atomic_fetch_add_against_peer0():
"""Every PE atomically adds 1 to peer 0's counter; peer 0 sees n_pes."""
n = rocshmem_n_pes()
me = rocshmem_my_pe()
# Symmetric int counter.
nbytes = 4
buf = rocshmem4py.SymmetricBuffer(nbytes)
try:
t = torch.as_tensor(buf, device="cuda").view(torch.int32).view([1])
t.zero_()
rocshmem_barrier_all()
torch.cuda.synchronize()
rocshmem4py.rocshmem_int_atomic_fetch_add(buf.ptr, 1, 0)
rocshmem_barrier_all()
torch.cuda.synchronize()
if me == 0:
assert int(t.item()) == n
finally:
buf.free()
@requires_torch
@requires_multi_pe
@requires_host_amo_runtime
def test_int_atomic_compare_swap_lock_pattern():
"""Use CAS to acquire a lock from peer 0; only one PE wins."""
me = rocshmem_my_pe()
nbytes = 4
buf = rocshmem4py.SymmetricBuffer(nbytes)
try:
t = torch.as_tensor(buf, device="cuda").view(torch.int32).view([1])
t.zero_()
rocshmem_barrier_all()
torch.cuda.synchronize()
# Each PE attempts CAS(0 -> me+1) on peer 0's counter.
rocshmem4py.rocshmem_int_atomic_compare_swap(buf.ptr, 0, me + 1, 0)
rocshmem_barrier_all()
torch.cuda.synchronize()
if me == 0:
assert 1 <= int(t.item()) <= rocshmem_n_pes()
finally:
buf.free()
@requires_torch
@requires_multi_pe
@requires_host_amo_runtime
def test_uint64_atomic_or_against_peer0():
"""Every PE ORs (1 << me) into peer 0's mask; peer 0 sees full bitmask."""
n = rocshmem_n_pes()
me = rocshmem_my_pe()
nbytes = 8
buf = rocshmem4py.SymmetricBuffer(nbytes)
try:
# uint64 = int64 view here; we set/check via ints.
t = torch.as_tensor(buf, device="cuda").view(torch.int64).view([1])
t.zero_()
rocshmem_barrier_all()
torch.cuda.synchronize()
rocshmem4py.rocshmem_uint64_atomic_or(buf.ptr, 1 << me, 0)
rocshmem_barrier_all()
torch.cuda.synchronize()
if me == 0:
# Use unsigned interpretation: shift mask is fine as int up to 63 PEs.
expected = (1 << n) - 1 if n < 64 else -1
assert int(t.item()) == expected
finally:
buf.free()