"""Device selection and reproducibility helpers. Centralised so the same logic can't drift between trainers and inference. """ from __future__ import annotations import random import numpy as np import torch def pick_device() -> torch.device: """Return the most capable torch device available. Order of preference: Apple Silicon MPS, NVIDIA CUDA, CPU. Chosen here because the project's primary target is M-series Macs; CUDA support is a free bonus when running on a workstation. """ if torch.backends.mps.is_available(): return torch.device("mps") if torch.cuda.is_available(): return torch.device("cuda") return torch.device("cpu") def seed_everything(seed: int) -> None: """Set Python, NumPy and PyTorch RNG seeds for reproducible runs. Note: full bitwise reproducibility on MPS isn't guaranteed by PyTorch; this gets us *seed-driven* identical splits and broadly identical training trajectories, which is what we want for science. """ random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) if torch.cuda.is_available(): torch.cuda.manual_seed_all(seed)