seeding.py 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778
  1. """Centralized seeding helpers for reproducible training and evaluation.
  2. Reproducibility matters here because the whole point of the harness is to
  3. compare ensemble members and quantify uncertainty. If weight initialization,
  4. dropout, dataloader shuffling, and BNN sampling are not seeded, "ensemble
  5. diversity" is accidental and results are not reproducible across runs.
  6. """
  7. import os
  8. import random
  9. from typing import Optional
  10. import numpy as np
  11. import torch
  12. # Numpy requires seeds in the uint32 range; torch/python are more permissive,
  13. # so we clamp everything to this range for consistency.
  14. _UINT32 = 2**32
  15. def seed_everything(seed: int, deterministic: bool = False) -> int:
  16. """Seed all relevant RNGs (python, numpy, torch, CUDA).
  17. Args:
  18. seed: The master seed. Clamped into the uint32 range.
  19. deterministic: If True, force cuDNN into deterministic mode. This makes
  20. convolutions reproducible at some throughput cost. Leave False for
  21. training speed; set True when exact reproduction is required.
  22. Returns:
  23. The (clamped) seed actually applied, for logging.
  24. """
  25. seed = int(seed) % _UINT32
  26. os.environ["PYTHONHASHSEED"] = str(seed)
  27. random.seed(seed)
  28. np.random.seed(seed)
  29. torch.manual_seed(seed)
  30. if torch.cuda.is_available():
  31. torch.cuda.manual_seed_all(seed)
  32. if deterministic:
  33. torch.backends.cudnn.deterministic = True
  34. torch.backends.cudnn.benchmark = False
  35. # Opt into deterministic algorithms where available; warn_only so a
  36. # missing deterministic kernel degrades gracefully instead of crashing.
  37. torch.use_deterministic_algorithms(True, warn_only=True)
  38. return seed
  39. def derive_seed(base_seed: int, index: int, stream: int = 0) -> int:
  40. """Derive a stable, distinct seed for ensemble member ``index``.
  41. ``stream`` separates otherwise-colliding families (e.g. normal vs. Bayesian
  42. ensembles) so that normal-member-0 and bayesian-member-0 do not share an
  43. identical RNG stream.
  44. Args:
  45. base_seed: The experiment master seed.
  46. index: The ensemble member index (0-based).
  47. stream: An offset identifying the RNG family.
  48. Returns:
  49. A seed in the uint32 range, deterministic in all three arguments.
  50. """
  51. return (int(base_seed) + stream * 100_000 + int(index)) % _UINT32
  52. def torch_generator(seed: Optional[int]) -> Optional[torch.Generator]:
  53. """Return a seeded ``torch.Generator`` for DataLoader shuffling, or None.
  54. Giving the training DataLoader its own generator makes the shuffle order a
  55. pure function of ``seed`` rather than of global RNG state mutated elsewhere.
  56. """
  57. if seed is None:
  58. return None
  59. return torch.Generator().manual_seed(int(seed) % _UINT32)