| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778 |
- """Centralized seeding helpers for reproducible training and evaluation.
- Reproducibility matters here because the whole point of the harness is to
- compare ensemble members and quantify uncertainty. If weight initialization,
- dropout, dataloader shuffling, and BNN sampling are not seeded, "ensemble
- diversity" is accidental and results are not reproducible across runs.
- """
- import os
- import random
- from typing import Optional
- import numpy as np
- import torch
- # Numpy requires seeds in the uint32 range; torch/python are more permissive,
- # so we clamp everything to this range for consistency.
- _UINT32 = 2**32
- def seed_everything(seed: int, deterministic: bool = False) -> int:
- """Seed all relevant RNGs (python, numpy, torch, CUDA).
- Args:
- seed: The master seed. Clamped into the uint32 range.
- deterministic: If True, force cuDNN into deterministic mode. This makes
- convolutions reproducible at some throughput cost. Leave False for
- training speed; set True when exact reproduction is required.
- Returns:
- The (clamped) seed actually applied, for logging.
- """
- seed = int(seed) % _UINT32
- os.environ["PYTHONHASHSEED"] = str(seed)
- random.seed(seed)
- np.random.seed(seed)
- torch.manual_seed(seed)
- if torch.cuda.is_available():
- torch.cuda.manual_seed_all(seed)
- if deterministic:
- torch.backends.cudnn.deterministic = True
- torch.backends.cudnn.benchmark = False
- # Opt into deterministic algorithms where available; warn_only so a
- # missing deterministic kernel degrades gracefully instead of crashing.
- torch.use_deterministic_algorithms(True, warn_only=True)
- return seed
- def derive_seed(base_seed: int, index: int, stream: int = 0) -> int:
- """Derive a stable, distinct seed for ensemble member ``index``.
- ``stream`` separates otherwise-colliding families (e.g. normal vs. Bayesian
- ensembles) so that normal-member-0 and bayesian-member-0 do not share an
- identical RNG stream.
- Args:
- base_seed: The experiment master seed.
- index: The ensemble member index (0-based).
- stream: An offset identifying the RNG family.
- Returns:
- A seed in the uint32 range, deterministic in all three arguments.
- """
- return (int(base_seed) + stream * 100_000 + int(index)) % _UINT32
- def torch_generator(seed: Optional[int]) -> Optional[torch.Generator]:
- """Return a seeded ``torch.Generator`` for DataLoader shuffling, or None.
- Giving the training DataLoader its own generator makes the shuffle order a
- pure function of ``seed`` rather than of global RNG state mutated elsewhere.
- """
- if seed is None:
- return None
- return torch.Generator().manual_seed(int(seed) % _UINT32)
|