Skip to content

English · Español

Lab 03 — Implementar seed_everything a mano

Pre-requisito: lee ../theory/01-reproducibility.md. Objetivo: reimplementar src/utils/seeding.py desde cero sin mirar el archivo existente ni solutions/03-seeding-ref.md. La implementación existente se moverá a un lado para ti.

§1 Setup

  1. Mueve la implementación existente: git mv src/utils/seeding.py src/utils/seeding.py.bak.
  2. Crea un src/utils/seeding.py nuevo solo con la cabecera del docstring — cuerpo vacío.
  3. Verifica que pytest tests/test_seeding.py ahora falla (esperado — aún no has implementado).

No mirarás seeding.py.bak hasta el paso 6.

§2 Tu tarea

Implementa dos funciones:

§2.1 seed_everything(seed: int) -> None

Siembra cada fuente RNG descrita en ../theory/01-reproducibility.md §1.

Cubre como mínimo: - PYTHONHASHSEED (vía os.environ, con la advertencia de teoría §1.1). - random (stdlib). - numpy.random — tanto legacy como la cuestión de default_rng (respóndela en un comentario de código). - torch si es importable (CPU + per-CUDA-device + cuDNN deterministic + cuDNN benchmark off).

Restricciones: - Debe funcionar cuando NumPy y/o PyTorch no están instalados — envuelve los imports en try/except. - Debe estar tipado, pasando mypy --strict. - No debe tener efectos colaterales más allá de sembrar los RNG y fijar variables de entorno.

§2.2 log_versions() -> dict[str, str]

Devuelve un dict que mapea nombre de librería → cadena de versión para python, numpy, torch, scipy. Para cada librería: - Si es importable → usa __version__. - Si no es importable → valor es "not installed".

Debe funcionar sin lanzar excepción incluso cuando ninguna lib opcional esté presente.

§3 Tests

tests/test_seeding.py ya existe. Reejecútalo. Debería pasar una vez tu implementación sea correcta.

Añade al menos un test basado en propiedades usando hypothesis a tests/test_seeding.py:

from hypothesis import given, strategies as st

@given(st.integers(min_value=0, max_value=2**31 - 1))
def test_seed_determinism(seed: int) -> None:
    seed_everything(seed)
    a = [random.random() for _ in range(5)]
    seed_everything(seed)
    b = [random.random() for _ in range(5)]
    assert a == b

(Sí, esto se te da — es la forma del test que también deberías escribir para numpy.random, y, si está instalado, torch.randn.)

§4 Condiciones de parada

  • pytest tests/test_seeding.py pasa (incluidos los nuevos tests que añadiste).
  • mypy --strict src/utils/seeding.py pasa.
  • ruff check src/utils/seeding.py está limpio.
  • bandit src/utils/seeding.py está limpio.
  • Has escrito un párrafo en learners/borja/phase-00/notes/seeding-notes.md explicando:
  • Por qué PYTHONHASHSEED debe fijarse en el launcher para afectar a hash(str) entre procesos.
  • Por qué np.random.seed no siembra np.random.default_rng(...).
  • Una fuente de no-determinismo que seed_everything no puede cubrir (p.ej., orden de reducción de BLAS multihilo).
  • Diffea tu implementación contra seeding.py.bak. Lista las diferencias en learners/borja/phase-00/notes/seeding-notes.md §"Comparación" — para cada una: qué versión es mejor, por qué.
  • Decide: ¿te quedas con la tuya o restauras seeding.py.bak? Defiende la decisión.
  • Elimina seeding.py.bak (o git rm).
  • Commit: lab: phase-00 reimplement seed_everything from scratch.

§5 Qué habrás aprendido

  • La mecánica de cada RNG en un stack Python+NumPy+PyTorch.
  • La diferencia entre "el RNG está sembrado" y "el programa es determinista" (BLAS multihilo, TF32, etc.).
  • Cómo diseñar una función que degrada elegantemente cuando faltan deps opcionales.
  • Que comparar tu propia implementación contra una referencia es el ejercicio más útil de esta fase — no porque la tuya sea necesariamente peor, sino porque el delta es la lección.

§6 Pistas (úsalas con moderación)

  1. try: import x except ImportError: x = None permite a mypy --strict inferir x: ModuleType | None. Tendrás que estrechar antes de usar.
  2. torch.cuda.manual_seed_all (plural) siembra cada dispositivo; manual_seed (singular) siembra solo el actual.
  3. cudnn.deterministic = True y cudnn.benchmark = False — ambos. El primero escoge algoritmos deterministas; el segundo previene autotuning de algoritmo-según-forma que varía entre ejecuciones.
  4. numpy.random.default_rng(seed) es la API moderna. seed_everything debería sembrar el estado legacy para librerías que no han migrado, pero en tu propio código prefiere rng = np.random.default_rng(seed) y pasa rng alrededor.

Si recurres a solutions/03-seeding-ref.md antes de completar esto, marca dod.lab_attempted_before_solutions: false. La honestidad importa aquí.