rpx_benchmark.determinism

Deterministic seeding for reproducible benchmark runs.

Seeds every stochastic source the toolkit is likely to touch: random, numpy, torch (CPU + CUDA + MPS), and HuggingFace transformers / datasets where present. Safe to call even when those libraries aren't installed — the function touches only the backends that import successfully.

Example

Imperative::

from rpx_benchmark.determinism import seed_all
seed_all(42)

Context manager (restores RNG state on exit)::

from rpx_benchmark.determinism import deterministic

with deterministic(42):
    result = runner.run()

Design notes

  • Seeding is best-effort across backends. Calling code should treat this as "best chance at determinism", not a hard guarantee — non-deterministic CUDA kernels, cuDNN algorithm selection, and multi-threaded data loaders can still introduce run-to-run variance.
  • The context manager snapshots and restores the standard-library and numpy RNG state so nested determinism blocks don't leak state. Torch / transformers / datasets expose no cheap state-snapshot primitive, so their post-exit state is the sequence advanced during the block (same as re-seeding without a context manager).
  1"""Deterministic seeding for reproducible benchmark runs.
  2
  3Seeds every stochastic source the toolkit is likely to touch:
  4``random``, ``numpy``, ``torch`` (CPU + CUDA + MPS), and
  5HuggingFace ``transformers`` / ``datasets`` where present. Safe to
  6call even when those libraries aren't installed — the function
  7touches only the backends that import successfully.
  8
  9Example
 10-------
 11
 12Imperative::
 13
 14    from rpx_benchmark.determinism import seed_all
 15    seed_all(42)
 16
 17Context manager (restores RNG state on exit)::
 18
 19    from rpx_benchmark.determinism import deterministic
 20
 21    with deterministic(42):
 22        result = runner.run()
 23
 24Design notes
 25------------
 26- Seeding is *best-effort across backends*. Calling code should treat
 27  this as "best chance at determinism", not a hard guarantee —
 28  non-deterministic CUDA kernels, cuDNN algorithm selection, and
 29  multi-threaded data loaders can still introduce run-to-run variance.
 30- The context manager snapshots and restores the standard-library and
 31  numpy RNG state so nested determinism blocks don't leak state.
 32  Torch / transformers / datasets expose no cheap state-snapshot
 33  primitive, so their post-exit state is the sequence advanced during
 34  the block (same as re-seeding without a context manager).
 35"""
 36
 37from __future__ import annotations
 38
 39import os
 40import random
 41from contextlib import contextmanager
 42from typing import Iterator
 43
 44__all__ = ["seed_all", "deterministic", "RPX_SEED"]
 45
 46
 47#: Canonical project-wide seed. MMDDYYYY-encoded 05/06/2026 — the date
 48#: the team committed to a single deterministic seed across the whole
 49#: benchmark (bootstrap CI, test data generation, sparse-depth /
 50#: keypoint-pair samplers, ORD pixel-pair sampler, etc.). Stored here so
 51#: there's exactly one place to change if the project policy shifts.
 52#: Python's int literal grammar disallows a leading zero, so the
 53#: storage form is ``5_062_026``; the underscores keep the
 54#: month-day-year segmentation visible at a glance.
 55RPX_SEED: int = 5_062_026
 56
 57
 58def _seed_python(seed: int) -> None:
 59    random.seed(seed)
 60    # PYTHONHASHSEED affects dict ordering in child subprocesses; the
 61    # running interpreter snapshots it at startup so setting it here
 62    # only helps processes spawned later (e.g. DataLoader workers).
 63    os.environ["PYTHONHASHSEED"] = str(seed)
 64
 65
 66def _seed_numpy(seed: int) -> None:
 67    try:
 68        import numpy as np  # noqa: PLC0415
 69    except ImportError:
 70        return
 71    np.random.seed(seed)
 72
 73
 74def _seed_torch(seed: int) -> None:
 75    try:
 76        import torch  # noqa: PLC0415
 77    except ImportError:
 78        return
 79    torch.manual_seed(seed)
 80    if hasattr(torch, "cuda") and torch.cuda.is_available():
 81        torch.cuda.manual_seed_all(seed)
 82    mps = getattr(torch, "mps", None)
 83    if mps is not None and hasattr(mps, "manual_seed"):
 84        try:
 85            mps.manual_seed(seed)
 86        except Exception:
 87            pass
 88
 89
 90def _seed_hf(seed: int) -> None:
 91    """Seed transformers / datasets when they're importable.
 92
 93    Both libraries expose a ``set_seed`` helper that internally seeds
 94    Python, NumPy, and PyTorch — we still call our own seeders first
 95    so the HF helpers only fill gaps we might have missed.
 96    """
 97    try:
 98        from transformers import set_seed as _tf_set_seed  # noqa: PLC0415
 99
100        _tf_set_seed(seed)
101    except ImportError:
102        pass
103
104
105def seed_all(seed: int) -> None:
106    """Seed every stochastic backend detectable in the current env.
107
108    Parameters
109    ----------
110    seed : int
111        Seed forwarded to Python ``random``, NumPy, PyTorch (CPU +
112        CUDA + MPS), and ``transformers.set_seed`` when available.
113    """
114    _seed_python(seed)
115    _seed_numpy(seed)
116    _seed_torch(seed)
117    _seed_hf(seed)
118
119
120@contextmanager
121def deterministic(seed: int) -> Iterator[None]:
122    """Run a block with deterministic RNG and restore on exit.
123
124    Python ``random`` and NumPy RNG state are snapshotted and
125    restored. Torch / transformers state is merely re-seeded; see the
126    module docstring for the full scope.
127
128    Parameters
129    ----------
130    seed : int
131
132    Yields
133    ------
134    None
135    """
136    py_state = random.getstate()
137    np_state = None
138    try:
139        import numpy as np  # noqa: PLC0415
140
141        np_state = np.random.get_state()
142    except ImportError:
143        np = None  # type: ignore[assignment]
144
145    seed_all(seed)
146    try:
147        yield
148    finally:
149        random.setstate(py_state)
150        if np_state is not None:
151            import numpy as _np  # noqa: PLC0415
152
153            _np.random.set_state(np_state)
def seed_all(seed: int) -> None:
106def seed_all(seed: int) -> None:
107    """Seed every stochastic backend detectable in the current env.
108
109    Parameters
110    ----------
111    seed : int
112        Seed forwarded to Python ``random``, NumPy, PyTorch (CPU +
113        CUDA + MPS), and ``transformers.set_seed`` when available.
114    """
115    _seed_python(seed)
116    _seed_numpy(seed)
117    _seed_torch(seed)
118    _seed_hf(seed)

Seed every stochastic backend detectable in the current env.

Parameters

seed : int Seed forwarded to Python random, NumPy, PyTorch (CPU + CUDA + MPS), and transformers.set_seed when available.

@contextmanager
def deterministic(seed: int) -> Iterator[NoneType]:
121@contextmanager
122def deterministic(seed: int) -> Iterator[None]:
123    """Run a block with deterministic RNG and restore on exit.
124
125    Python ``random`` and NumPy RNG state are snapshotted and
126    restored. Torch / transformers state is merely re-seeded; see the
127    module docstring for the full scope.
128
129    Parameters
130    ----------
131    seed : int
132
133    Yields
134    ------
135    None
136    """
137    py_state = random.getstate()
138    np_state = None
139    try:
140        import numpy as np  # noqa: PLC0415
141
142        np_state = np.random.get_state()
143    except ImportError:
144        np = None  # type: ignore[assignment]
145
146    seed_all(seed)
147    try:
148        yield
149    finally:
150        random.setstate(py_state)
151        if np_state is not None:
152            import numpy as _np  # noqa: PLC0415
153
154            _np.random.set_state(np_state)

Run a block with deterministic RNG and restore on exit.

Python random and NumPy RNG state are snapshotted and restored. Torch / transformers state is merely re-seeded; see the module docstring for the full scope.

Parameters

seed : int

Yields

None

RPX_SEED: int = 5062026