Skip to content

English · Español

Break 00 — Eliminar la conexión residual (residual connection) de un MLP profundo

🇪🇸 Rompemos la "autopista del gradiente". Esperamos varios síntomas: pérdida que se queda en NaN, ranking de capas profundas con grad ≈ 0, y curva de entrenamiento que no baja. La práctica te debe convencer de por qué los residuales son el ingrediente que permite redes profundas — no un truco.

Anclajes: LYNX_CORTEX.md §4 / PHASE 10; teoría §03 residuales de esta fase; .claude/commands/break.md.


La rotura

En src/minimodel/nn/blocks.py (el fichero donde vive el bloque del MLP de 50 capas del Lab 03):

class ResidualBlock(Module):
    def __init__(self, d: int) -> None:
        super().__init__()
        self.norm = RMSNorm(d)
        self.fc1 = Linear(d, 4 * d)
        self.fc2 = Linear(4 * d, d)

    def forward(self, x: Tensor) -> Tensor:
        h = self.fc2(self.fc1(self.norm(x)).gelu())
        # BUG: removed the residual.
        # return x + h   <- restore this
        return h

Eliminación de una sola línea. Compón 50 de estos en una cadena.

Predice, luego ejecuta

La varianza del forward pass a través de una pila residual es:

\[ \mathrm{Var}(x_{L}) \approx \mathrm{Var}(x_0) + \sum_{\ell=1}^{L} \mathrm{Var}(f_{\ell}(x_{\ell-1})) \]

Crece linealmente con la profundidad — acotada si cada f_ℓ preserva la varianza. Sin el residual:

\[ \mathrm{Var}(x_{L}) = \prod_{\ell=1}^{L} (\mathrm{gain}_{\ell})^2 \cdot \mathrm{Var}(x_0) \]

Si gain_ℓ ≈ 1, OK. Si gain_ℓ se desvía incluso un poco (0.95 o 1.05), la varianza se multiplica hasta desaparecer o explota para el paso 50.

Backward pass: el gradiente respecto a x_0 es

\[ \frac{\partial L}{\partial x_0} = \prod_{\ell=1}^{L} \frac{\partial x_{\ell}}{\partial x_{\ell-1}} \]

Sin el residual, cada factor es ∂f_ℓ/∂x, normalmente cercano a la unidad pero nunca exactamente 1. Compón 50 de esos y obtienes gradientes que se desvanecen o explotan — el problema de gradiente desvanecido de la Fase 14 revivido en forma de MLP.

Con el residual, cada factor es I + ∂f_ℓ/∂x — y la identidad garantiza un camino de ganancia unitaria.

Predicciones

  • Pérdida en el paso 100 con residuales: decreciente, ~80% de la inicial.
  • Pérdida en el paso 100 sin residuales: plana cerca de la pérdida inicial, o NaN (si la magnitud de inicialización está mal ajustada).
  • Magnitud de ∇_{W1} (gradiente del peso de la capa más profunda) con residuales: ~1e-3.
  • Magnitud de ∇_{W1} sin residuales: <1e-7 (desvanecida) o >1e+5 (explotada).
  • Curvas de entrenamiento: con residual se alcanza >85% de val acc en ~500 pasos; sin residual o no converge o tarda 10× más.

Escribe tus predicciones en learners/borja/phase-10/notes/breaks.md antes de ejecutar.

Observa

just exp 10-residual-depth --tag broken-no-residual

Diagnósticos:

  1. Norma del gradiente por capa — registra en cada paso. Debería ser ~1 para residual, derivar a 0 o inf sin residual.
  2. Superposición de curvas de pérdida de entrenamiento.
  3. Histograma de pesos tras 100 pasos — sin residuales, la mayoría apenas se han movido (firma del gradiente desvanecido).

Síntoma que verá Borja

  • nan en la pérdida de entrenamiento dentro de 50 pasos O pérdida plana cerca del valor inicial.
  • La norma del gradiente de la capa más profunda lee 0.0 o inf desde el paso 1.
  • El entrenamiento nunca supera a un baseline de MLP de 1 capa.

Causa oculta (una frase)

El residual return x + h fue reemplazado por return h, matando la autopista de gradiente de identidad y reactivando los gradientes desvanecidos/explosivos en una pila de 50 capas.

Cascada de pistas

  1. Imprime las normas de gradiente por capa durante el primer paso de entrenamiento. ¿Son constantes a través de las capas, o se encogen/explotan con la profundidad?
  2. La teoría §03 de la Fase 10 deriva ∂y/∂x = I + ∂f/∂x para y = x + f(x). ¿Qué garantiza el término I? ¿Qué pasa si falta I?
  3. Compara la sentencia de retorno de ResidualBlock.forward con la derivación teórica del §03.

Diff de la corrección

def forward(self, x: Tensor) -> Tensor:
    h = self.fc2(self.fc1(self.norm(x)).gelu())
    return x + h                       # restored

Por qué esto enseña el concepto

He et al. (2015) mostró que añadir conexiones skip de identidad te permite entrenar redes de 152 capas; sin ellas, incluso 20 capas fallan. La tarea §A13 sólo necesita un MLP de 2 capas — pero la Fase 10 te obliga a romper una pila de 50 capas precisamente para que sientas por qué la profundidad necesita residuales, antes de que la Fase 17 apile 6 bloques transformer (donde cada bloque tiene su propia autopista residual). La lección aquí es idéntica a la que necesita el laboratorio de la Fase 17.


Siguiente: el /break de la Fase 11 sobre entrenamiento BPE monolingüe.