English · Español
Lab 03 — Implementar seed_everything a mano¶
Pre-requisito: lee
../theory/01-reproducibility.md. Objetivo: reimplementarsrc/utils/seeding.pydesde cero sin mirar el archivo existente nisolutions/03-seeding-ref.md. La implementación existente se moverá a un lado para ti.
§1 Setup¶
- Mueve la implementación existente:
git mv src/utils/seeding.py src/utils/seeding.py.bak. - Crea un
src/utils/seeding.pynuevo solo con la cabecera del docstring — cuerpo vacío. - Verifica que
pytest tests/test_seeding.pyahora 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.pypasa (incluidos los nuevos tests que añadiste). -
mypy --strict src/utils/seeding.pypasa. -
ruff check src/utils/seeding.pyestá limpio. -
bandit src/utils/seeding.pyestá limpio. - Has escrito un párrafo en
learners/borja/phase-00/notes/seeding-notes.mdexplicando: - Por qué
PYTHONHASHSEEDdebe fijarse en el launcher para afectar ahash(str)entre procesos. - Por qué
np.random.seedno siembranp.random.default_rng(...). - Una fuente de no-determinismo que
seed_everythingno puede cubrir (p.ej., orden de reducción de BLAS multihilo). - Diffea tu implementación contra
seeding.py.bak. Lista las diferencias enlearners/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(ogit 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)¶
try: import x except ImportError: x = Nonepermite amypy --strictinferirx: ModuleType | None. Tendrás que estrechar antes de usar.torch.cuda.manual_seed_all(plural) siembra cada dispositivo;manual_seed(singular) siembra solo el actual.cudnn.deterministic = Trueycudnn.benchmark = False— ambos. El primero escoge algoritmos deterministas; el segundo previene autotuning de algoritmo-según-forma que varía entre ejecuciones.numpy.random.default_rng(seed)es la API moderna.seed_everythingdebería sembrar el estado legacy para librerías que no han migrado, pero en tu propio código prefiererng = np.random.default_rng(seed)y pasarngalrededor.
Si recurres a
solutions/03-seeding-ref.mdantes de completar esto, marcadod.lab_attempted_before_solutions: false. La honestidad importa aquí.