10 El bloque completo y la profundidad
Dónde estamos. Tenemos todas las piezas: atención (Cap. 4), multi-cabeza (5), FFN (6), el andamiaje residual+norma (7) y el sentido del orden (8). Toca montarlas en un bloque completo, apilar ese bloque muchas veces, y seguir el viaje de un token de principio a fin. Al terminar este capítulo habrás construido un transformer entero —el final de la Parte de fundamentos—.
10.1 La idea en una frase
Un transformer es, casi entero, una pila de bloques idénticos; cada bloque mezcla (atención) y luego procesa (FFN), con su andamiaje, y se repite N veces.
10.2 Conceptos clave y su papel en el transformer
Antes de entrar en detalle, definimos los términos de este capítulo y para qué sirve cada uno dentro de un transformer:
- Bloque (Pre-LN). Definición: la unidad que se repite, formada por dos subcapas con el patrón normalizar → subcapa → sumar. En el transformer: es el ladrillo básico; apilarlo N veces es, casi entero, el modelo.
- Subcapa de atención (mezclar). Definición: la operación que deja a cada token mirar a los demás y traerse información. En el transformer: la primera línea del bloque; mueve información entre posiciones.
- Subcapa FFN (procesar). Definición: una red que transforma cada token por separado. En el transformer: la segunda línea del bloque; cada token “piensa” sobre lo que acaba de recoger, sin mirar a los vecinos.
- Flujo residual. Definición: el vector de cada token que atraviesa todo el modelo, al que cada subcapa suma su contribución. En el transformer: el hilo conductor; lo inicializan los embeddings, lo editan los bloques y su valor final se traduce en la salida.
- Máscara causal. Definición: un filtro que pone a −∞ las posiciones futuras antes del softmax, dejándolas a peso 0. En el transformer: lo que impide “hacer trampa” mirando el futuro; convierte el modelo en un generador.
- Profundidad (apilar capas). Definición: el número de bloques apilados (12, 32, 96…). En el transformer: permite componer patrones simples en conceptos abstractos; por eso ciertas habilidades solo emergen con profundidad.
- Desembeber (head de salida). Definición: la proyección final del vector de cada token de vuelta al vocabulario. En el transformer: traduce el estado interno en una predicción (logits sobre las palabras posibles).
Con estas piezas, el resto del capítulo es solo verlas encajar en un bloque y repetirlo.
10.3 El bloque, ensamblado
Cada bloque hace exactamente dos cosas, las dos que ya conoces, en este orden (estilo Pre-LN, el moderno):
\[ x \leftarrow x + \text{Atención}(\text{Norm}(x)) \] \[ x \leftarrow x + \text{FFN}(\text{Norm}(x)) \]
Qué hace cada línea:
- Línea 1 — mezclar: normaliza, deja que cada token mire a los demás (atención multi-cabeza) y suma el resultado al flujo residual.
- Línea 2 — procesar: normaliza, cada token piensa por su cuenta (FFN) y suma el resultado.
Y ya está. Ese patrón —normalizar → subcapa → sumar, dos veces— es todo el bloque. La inteligencia del modelo no está en un bloque sofisticado, sino en repetir este bloque sencillo muchas veces sobre un buen flujo de información.
10.4 La máscara causal: lo que hace que pueda generar
Hay un detalle que decide si el modelo entiende o genera: qué puede mirar cada token. En un modelo generativo (tipo GPT), cada token solo puede atender a sí mismo y a los anteriores, nunca a los futuros. Eso se consigue con una máscara causal: antes del softmax se ponen a −∞ las posiciones futuras, de modo que su peso queda en 0.
¿Para qué sirve? Para que el modelo aprenda a predecir la siguiente palabra sin “hacer trampa” mirando la respuesta. Si pudiera ver el futuro, predecir sería trivial y no aprendería nada útil. (En un modelo de comprensión como BERT no se enmascara: cada token ve todo; lo veremos en el Cap. 10.)
import torch
n = 5 # 5 tokens
mask = torch.triu(torch.full((n, n), float('-inf')), diagonal=1)
print(mask) # triángulo superior = -inf (el futuro, bloqueado)10.5 Apilar en profundidad: ¿para qué tantas capas?
Un transformer no tiene un bloque: tiene muchos (GPT-2 small: 12; LLaMA-2-7B: 32; los mayores, 80–100+). ¿Por qué? Porque la profundidad permite componer:
- Las capas bajas detectan patrones superficiales (sintaxis, el token anterior).
- Las capas medias y altas combinan eso en conceptos cada vez más abstractos (referencias, semántica, hechos).
Cada bloque lee del flujo residual lo que dejaron los anteriores y añade su contribución. Es por eso que ciertas habilidades solo aparecen con profundidad: las induction heads del Cap. 5, por ejemplo, necesitan al menos dos capas para formarse (una encuentra el patrón, la siguiente lo copia).
🧩 Analogía. Una sola pasada es una lectura superficial. Apilar bloques es releer el texto muchas veces, y en cada relectura entender una capa más de sentido —de las palabras sueltas, a las frases, a las ideas—.
10.6 El viaje completo de un token
Juntando toda la Parte de fundamentos, este es el recorrido de principio a fin:
- Tokenizar (Cap. 2): el texto → números.
- Embeddings + posición (Cap. 3, 8): cada token → un vector, situado en el flujo residual, con su información de orden.
- N bloques (Cap. 4–7): mezclar (atención) → procesar (FFN), una y otra vez, refinando el vector de cada token.
- Norma final + “desembeber” (Cap. 12): el vector final de cada token se proyecta de vuelta al vocabulario para dar una predicción (p. ej., la siguiente palabra).
El flujo residual es el hilo que recorre todo: lo inicializan los embeddings, lo editan los bloques, y su valor final se traduce en la salida.
10.7 Un transformer mínimo, en código
Todo lo anterior cabe en unas pocas líneas legibles:
import torch, torch.nn as nn
class Bloque(nn.Module):
def __init__(self, d_model, n_heads, d_ff):
super().__init__()
self.norm1 = nn.LayerNorm(d_model)
self.attn = nn.MultiheadAttention(d_model, n_heads, batch_first=True)
self.norm2 = nn.LayerNorm(d_model)
self.ffn = nn.Sequential(nn.Linear(d_model, d_ff), nn.GELU(),
nn.Linear(d_ff, d_model))
def forward(self, x, mask):
h = self.norm1(x)
x = x + self.attn(h, h, h, attn_mask=mask)[0] # mezclar
x = x + self.ffn(self.norm2(x)) # procesar
return x
class GPTmini(nn.Module):
def __init__(self, vocab, d_model=768, n_heads=12, d_ff=3072, n_capas=12, ctx=1024):
super().__init__()
self.tok = nn.Embedding(vocab, d_model) # embeddings de token
self.pos = nn.Embedding(ctx, d_model) # posición (aprendida, simple)
self.bloques = nn.ModuleList([Bloque(d_model, n_heads, d_ff) for _ in range(n_capas)])
self.norm_f = nn.LayerNorm(d_model)
self.head = nn.Linear(d_model, vocab) # "desembeber" -> logits
def forward(self, ids):
n = ids.shape[1]
x = self.tok(ids) + self.pos(torch.arange(n)) # init del flujo residual
mask = torch.triu(torch.full((n, n), float('-inf')), diagonal=1)
for b in self.bloques:
x = b(x, mask) # N bloques
return self.head(self.norm_f(x)) # logits sobre el vocabularioEso es —en esencia— un GPT. Lo demás (entrenarlo, generar texto) es lo que viene en los Cap. 11–12. Lo importante: ya entiendes cada línea.
10.8 Resumen
- El transformer es una pila de bloques idénticos; cada bloque = normalizar → atención → sumar, luego normalizar → FFN → sumar.
- La máscara causal (bloquear el futuro) es lo que convierte al modelo en generador; sin ella (BERT), cada token ve todo → comprensión.
- La profundidad permite componer patrones simples en conceptos abstractos; por eso hay 12, 32 o 96 capas, y por eso ciertas habilidades emergen con la profundidad.
- El viaje completo: tokenizar → embeddings+posición → N bloques → norma final → desembeber → predicción. El flujo residual es el hilo conductor.
Siguiente (Capítulo 10): hemos montado un decoder (causal, generativo). Pero cambiando la máscara y la conexión salen tres familias de modelos —BERT, GPT, T5—. Esas son las arquitecturas.
10.9 Ejercicios
- El patrón. Escribe, de memoria, las dos líneas que forman un bloque Pre-LN. ¿Qué hace cada una?
- La máscara. ¿Por qué un modelo generativo debe ocultar el futuro durante el entrenamiento? ¿Qué pasaría si no lo hiciera?
- Profundidad. Da un ejemplo (del Cap. 5) de una habilidad que necesita más de una capa para existir, y explica por qué.
- El recorrido. Ordena estos pasos: desembeber · embeddings · tokenizar · N bloques · añadir posición.