English · Español
Fase 18 — Árbol de decisión de stability check¶
🇪🇸 Cuando un run se porta raro, no improvises. Camina este árbol de arriba a abajo. Cada nodo es una pregunta con un umbral numérico explícito; cada hoja te lleva a un fix concreto o a la Fase 19.
Una checklist ejecutable que un learner recorre cuando un run de entrenamiento de la Fase 18 se comporta mal. Úsala antes de abrir la Fase 19 — muchos síntomas de "el entrenamiento está roto" son problemas de configuración, no de dinámica.
Cómo usarla¶
- Ten
dashboard.html(o tus métricas en vivo) abierto. - Ten
experiments/<run>/manifest.jsonabierto (seed, versions, config). - Recorre de §1 a §6 en orden. No te saltes pasos. El orden es por probabilidad × baratura del check.
- La primera hoja que coincida es tu fix. Aplica, re-ejecuta y vuelve a caminar desde §1.
§1 — ¿Arrancó el run?¶
Q1.1 ¿Está la loss en el step 0 dentro de \(\ln V \pm 0.5\) donde \(V\) es el tamaño de vocab?
- Para el vocab §A13 \(\approx 512\): espera \(\ln 512 \approx 6.24\).
- Si loss(step 0) > 8.0: el init está roto o el bias de la cabeza LM es no-cero. Ve a §5.
- Si loss(step 0) < 4.0: el modelo no está sin entrenar. O la seed está mal, o cargaste un checkpoint por error. Comprueba manifest.json.
- Si no: pasa, ve a §2.
Q1.2 ¿Está la norma del gradient del primer batch en \([0.1, 10]\)? - Si grad-norm(step 1) > 100: init mal (ver §5) o tu loss tiene la reducción mal (mean vs sum cruzados). - Si grad-norm(step 1) < 0.001: los datos son degenerados — todo target token es el mismo, o la loss mask es todo cero. - Si no: pasa, ve a §2.
§2 — ¿Se está aplicando el schedule de LR?¶
Q2.1 Grafica LR vs step. ¿La curva coincide con el warmup + cosine configurado?
- Si el LR es constante ≠ 0: el scheduler nunca hizo step. Bug habitual: el optimizer se reconstruye en cada step, perdiendo el state del schedule. Fix: instancia el optimizer + scheduler una sola vez fuera del bucle de entrenamiento.
- Si el LR es constante = 0: el warmup está configurado pero el multiplicador se aplica a un param_group["lr"] de 0. Fix: inicializa param_group["lr"] = lr_max y deja que el scheduler lo escale.
- Si el LR salta de forma discontinua: estás usando step decay, no cosine. Iguala a la config.
- Si no: pasa, ve a §3.
Q2.2 En el step warmup_steps, ¿está el LR dentro del 5% de lr_max?
- Si LR en step W es < 0.5 × lr_max: off-by-one en la fórmula de warmup. t / W vs (t+1) / W importa con W pequeño.
- Si no: pasa, ve a §3.
§3 — ¿Son los gradients razonables?¶
Q3.1 Grafica la norma del gradient pre-clip. Media móvil sobre una ventana de 20 steps.
- Si rolling-mean(grad-norm) > 5.0: el entrenamiento está en el régimen "siempre clipping". O el LR es demasiado alto, o el batch size demasiado pequeño, o un batch tiene un ejemplo patológico. Reduce lr_max 2× y vuelve a caminar.
- Si rolling-mean(grad-norm) < 0.01: entrenamiento muerto. O la loss mask anula la mayoría de tokens, o todos los parámetros están congelados, o el LR es tan pequeño que no aterriza ninguna actualización.
- Si no: pasa.
Q3.2 ¿Algún step individual con grad-norm > 100?
- Si sí una vez en 200 steps: aceptable, el clipping lo manejó. Investiga ese batch — anota su índice.
- Si sí más de 3 veces en 200 steps: inestabilidad. Ve a la Fase 19 stability-check.md §2 (picos de loss).
- Si no: pasa, ve a §4.
§4 — ¿Se mueven las losses?¶
Q4.1 Grafica la loss con una EMA de 50 steps. ¿Decrece monótonamente sobre los primeros 200 steps?
- Si train-loss(step 200) > train-loss(step 50): el entrenamiento está divergiendo o estático. Comprueba LR (§2), comprueba el optimizer state (§6).
- Si train-loss decrece pero la pendiente es <0.001 por 100 steps: el LR puede ser demasiado bajo. Prueba lr_max × 3 y vuelve a caminar.
- Si no: pasa.
Q4.2 ¿La val loss sigue a la train loss dentro de 0.5?
- Si val − train > 1.0 en el step 500: sobreajuste rápido. Sube weight_decay (prueba 0.2), o reduce capacidad, o aumenta datos (dentro del scope §A13 significa más variantes de conjugación).
- Si val < train por > 0.3: fuga de datos. El set de val contiene items del de train.
- Si no: pasa, ve a §5.
§5 — ¿Es el modelo arquitectónicamente sólido?¶
Q5.1 Imprime las estadísticas iniciales de pesos: mean, std, min, max por cada grupo de parámetro con nombre.
- Esperado (con Kaiming para ReLU/GELU o Xavier escalado):
- Std de peso Linear: \(\sqrt{2 / \text{fan\_in}}\) (Kaiming) o \(\sqrt{2 / (\text{fan\_in} + \text{fan\_out})}\) (Xavier).
- Para nuestro mini-GPT: la std de peso Linear debería estar en \([0.02, 0.1]\).
- Std del embedding: 0.02 (la convención GPT).
- Bias y escala de LayerNorm: 0 y 1 respectivamente.
- Si la std es 100× lo esperado: el fallo de mal init de la Fase 19. Ve a la Fase 19.
- Si la mean ≠ 0 por más de 0.01 en pesos Linear: re-inicializa.
Q5.2 Forward de un batch, registra magnitudes de activación en cada capa. - Esperado: la norma de Frobenius de las activaciones crece como mucho ~1.5× por capa. - Si la salida de una capa es > 10× su entrada: normalización ausente o rota. Comprueba la colocación de LN/RMSNorm.
§6 — ¿Es coherente el optimizer state?¶
Q6.1 ¿Están los param_groups correctamente divididos para weight decay?
- Tensores 2-D+ (weights, embeddings): deberían estar en el grupo decay con weight_decay = 0.1.
- Tensores 1-D (biases, escala/bias de LN): deberían estar en el grupo no_decay con weight_decay = 0.0.
- Si todo está en un grupo: los biases derivan a cero a lo largo del entrenamiento. Arregla la división.
Q6.2 ¿Están m y v (los momentos) acumulando?
- Tras 100 steps, registra ||m||_F y ||v||_F para un tensor de parámetros.
- Si \(\|v\|_F\) es próximo a cero en el step 100: el optimizer state no persiste entre steps. El optimizer se está reconstruyendo. Arregla.
- Si \(\|m\|_F = \|g\|_F\) exactamente: falta la corrección de sesgo. Aplica \(\hat m_t = m_t / (1 - \beta_1^t)\).
Q6.3 ¿Está la acumulación de gradient sumando correctamente?
- Si acumulas \(k\) micro-batches, el gradient efectivo es \(\sum / k\), no \(\sum\).
- Si la grad-norm es exactamente \(k\)× lo esperado: olvidaste el /k. Arregla.
Cuando llegas al fondo¶
Si todos los checks pasan y el run sigue portándose mal, el problema es dinámica, no configuración. Pasa a docs/phase-19-training-dynamics/stability-check.md.
Si un check te apuntó a un fix, aplícalo, re-ejecuta y vuelve a caminar desde §1. La mayoría de los runs estables atraviesan el árbol entero en una lectura.
Umbrales numéricos — tarjeta de referencia rápida¶
| Síntoma | Umbral | Primera acción |
|---|---|---|
| loss(step 0) lejos de \(\ln V\) | \(\|\Delta\| > 0.5\) | comprueba init |
| grad-norm(step 1) enorme | \(> 100\) | comprueba init / reducción de loss |
| grad-norm rolling | \(> 5\) | reduce LR 2× |
| grad-norm rolling | \(< 0.01\) | comprueba la loss mask |
| gap val − train | \(> 1.0\) en el step 500 | sube weight_decay |
| crecimiento de activación por capa | \(> 10\times\) entrada | comprueba la normalización |
| \(\|v\|_F\) en el step 100 | \(\approx 0\) | optimizer state perdido |
Documento hermano: docs/phase-19-training-dynamics/stability-check.md — para picos, NaN, overflow fp16, divergencia.