English · Español
04 — Anatomía del manifiesto: un ejemplo trabajado de "qué loggear cuándo"¶
El manifiesto del experimento no es burocracia; es la única forma de distinguir "mi código cambió" de "la máquina cambió" cuando los números se desvían. Aquí lo desmontamos campo a campo con un ejemplo concreto.
El §3 de 01-reproducibility.md mostró la forma de un manifiesto. Esta página cierra el bucle con una historia trabajada de diagnóstico de drift: dos manifiestos que discrepan en un solo número, y el recorrido campo a campo de cómo triajeas la causa.
§1 Los dos manifiestos¶
Un learner corre experiments/softmax-bench/run.py el lunes y otra vez el martes. El manifiesto del lunes reporta mean_loss: 0.4321; el del martes reporta mean_loss: 0.4327. ¿De dónde vino el drift?
// monday.json (excerpt)
{
"git_sha": "a1b2c3d",
"git_dirty": false,
"seed": 42,
"versions": { "python": "3.11.9", "numpy": "2.0.1", "torch": "not installed" },
"hardware": { "cpu": "Intel i5-8250U", "ram_gb": 62, "os": "Fedora 43" },
"env": { "OMP_NUM_THREADS": "8", "PYTHONHASHSEED": "42" },
"mean_loss": 0.4321
}
// tuesday.json (excerpt)
{
"git_sha": "a1b2c3d",
"git_dirty": false,
"seed": 42,
"versions": { "python": "3.11.9", "numpy": "2.0.2", "torch": "not installed" },
"hardware": { "cpu": "Intel i5-8250U", "ram_gb": 62, "os": "Fedora 43" },
"env": { "OMP_NUM_THREADS": "8", "PYTHONHASHSEED": "42" },
"mean_loss": 0.4327
}
El único campo que difiere: numpy 2.0.1 → numpy 2.0.2. El git sha, semilla, hardware, env y OMP threads son todos idénticos. Coste del diagnóstico: 30 segundos. Para esto es el manifiesto.
Sin el manifiesto, quedarías reducido a bisectar en el tiempo, reejecutar con cambios hipotetizados y discutir contigo mismo sobre si imaginaste el drift. Con él, miras el diff entre los dos y la respuesta es una línea.
§2 Campo a campo — qué te compra cada uno¶
| Campo | Qué puedes descartar / confirmar cuando el campo discrepa |
|---|---|
git_sha + git_dirty |
Drift de código. Sha distinto → bisecta commits. Dirty → reejecuta tras commitear. |
seed |
Drift de ruta RNG. Semilla distinta → drift esperado; la pregunta es si la distribución de salidas es estable, no la estimación puntual. |
versions[python] |
Cambios a nivel de intérprete (p.ej., orden de dict, formato de float). Raros pero reales. |
versions[numpy] |
Cambios a nivel de binding BLAS; upgrades de LAPACK dentro del wheel. Causa común de drift pequeño. |
versions[torch] |
Versión de cuDNN, cambios de kernels ATen. Gran driver de drift entre versiones menores de pytorch. |
hardware[cpu] |
Diferencias entre rutas AVX-512 vs AVX2; cambios de ancho SIMD cambian el orden de suma. |
hardware[gpu] |
sm_70 vs sm_80 → el default de TF32 cambia en Ampere+. |
env[OMP_NUM_THREADS] |
Sumas BLAS multihilo en órdenes distintos para conteos de hilos distintos. Fíjalo para determinismo. |
env[MKL_NUM_THREADS] |
Misma historia para Intel MKL. |
env[PYTHONHASHSEED] |
Orden cross-proceso de hash(str) (ver break/00-break-pythonhashseed.md). |
wall_seconds |
Regresiones de rendimiento o throttling térmico de CPU (8 s → 30 s es una historia de hardware, no de código). |
started_at / finished_at |
Efectos de hora del día (otros procesos compitiendo por cache, indexado en background en un portátil recién encendido). |
Leyendo la tabla en la otra dirección: cuando escribes un manifiesto, cada campo está ahí porque alguna causa específica de drift necesita que sea diagnosticable después. No hay campos decorativos.
§3 Qué hacer cuando faltan campos que ojalá hubieras loggeado¶
Un manifiesto de hace tres meses carece de OMP_NUM_THREADS. La ejecución de hoy hace drift. No puedes retroactivamente saber qué OMP había en la ejecución antigua. Opciones, en orden creciente de dolor:
- Reejecuta hoy variando los campos faltantes. Fija
OMP_NUM_THREADSa 1, 2, 4, 8 y mira qué valor reproduce el número antiguo. Si un único setting reproduce, esa era la variable faltante. - Examina el historial antiguo de la shell (
history,~/.zsh_history). Si encuentras el comando del launcher, el env puede ser reconstruible. - Marca la ejecución como "no reproducible — schema demasiado pobre" en el diario y añade el campo al schema del manifiesto en adelante.
La lección es unidireccional: los schemas solo se enriquecen con el tiempo. Añadir un campo es barato; quitar uno está prohibido por el contrato de reproducibilidad.
§4 La regla "mínimo-pero-no-demasiado-mínimo"¶
Un manifiesto con solo git_sha es demasiado mínimo. Un manifiesto con capturas de pantalla y el contenido de cada .bashrc es demasiado rico. La línea correcta:
Si el campo es plausiblemente cargante para un resultado numérico, lóggealo. Si no, no.
Concretamente, logguea: - Todo lo que afecta a la ruta del RNG (semilla, hash seed, versiones de librería). - Todo lo que afecta al orden de reducción de BLAS (conteos de hilo, ancho SIMD de CPU). - Todo lo que afecta a la ruta de float (flags de precisión, TF32, determinismo de cuDNN). - El git sha + bit dirty. - Wall time + timestamps de start/finish (para triaje de drift de rendimiento).
No logguees:
- Settings del editor, tema de la shell, versión del kernel a menos que tengas una regresión específica relacionada con el kernel que rastrear.
- Rutas de archivo que son relativas al repo y reconstruibles desde el git sha.
- El os.environ entero (riesgo de privacidad — captura secretos accidentalmente).
§5 La conexión con §A13¶
El generador de corpus de gramática (Fase 12) producirá 600 formas. Cada regeneración escribe un manifiesto que contiene la semilla, la versión del corpus, el checksum del grid de gramática y la cobertura de pares bilingües. Si una evaluación downstream hace drift en la Fase 20, el manifiesto te dice si cambió el corpus o cambió la evaluación. Sin él, "el modelo empeoró" es irresoluble.
§6 Referencias¶
- Pineau et al., Improving Reproducibility in Machine Learning Research (checklist de reproducibilidad ML), J. Mach. Learn. Res. 22 (2021).
- Joel Grus, Reproducibility in ML: Why It Matters and How to Achieve It — notas de charla, 2019.
- La página de docs de PyTorch sobre
torch.use_deterministic_algorithmslista cada op no determinista conocida en el framework — útil cuando tu manifiesto no puede explicar el drift.
§7 Siguiente lectura¶
→ Lab 04 (nuevo): escribe un helper de manifest-diff que tome dos rutas de manifiesto y solo imprima los campos que cambiaron, ordenados por "probabilidad de causar drift".