English · Español
01 — Vision Transformers: ViT, CLIP, DINOv2, SigLIP¶
🇪🇸 La idea central de ViT (Dosovitskiy 2020) es trivial pero impactante: una imagen es una secuencia de parches. Aplicas patchify + un transformer estándar (igual al de la Fase 17) y obtienes un encoder visual competitivo. CLIP (Radford 2021) añade la pieza que importa para X2: alinear ese encoder visual con un encoder de texto vía pérdida contrastiva simétrica.
Este fichero deriva la matemática de patchify de ViT desde cero, recorre el InfoNCE simétrico de CLIP, y repasa las mejoras de DINOv2 / SigLIP relevantes para los labs 00 y 01.
Referencias: - Dosovitskiy et al., An Image is Worth 16×16 Words, ICLR 2021. (arXiv:2010.11929) - Radford et al., Learning Transferable Visual Models From Natural Language Supervision, ICML 2021. (arXiv:2103.00020) - Oquab et al., DINOv2, TMLR 2024. (arXiv:2304.07193) - Zhai et al., Sigmoid Loss for Language Image Pre-training (SigLIP), ICCV 2023. (arXiv:2303.15343)
ViT: patchify es la única idea nueva¶
El Vision Transformer (vision transformer) (ViT) coge un transformer estándar idéntico al transformer de lenguaje de la Fase 17 y le mete parches de imagen en vez de tokens de palabra. La única novedad arquitectónica es el paso de embedding: patchify.
Matemática de patchify (memorízala)¶
Entrada: una imagen RGB de forma (H, W, C) = (224, 224, 3). Elegimos un tamaño de parche \(P = 16\).
Paso 1. Reshape de la imagen en parches no solapados de \(P \times P \times C\):
así obtenemos \(H_p \cdot W_p = 14 \cdot 14 = 196\) parches, cada uno de tamaño \((16, 16, 3)\).
Paso 2. Flatten de cada parche a un vector de dimensión \(P^2 \cdot C = 16 \cdot 16 \cdot 3 = 768\). Apilar en una matriz X_patches ∈ R^{196 × 768}.
Paso 3. Proyección lineal a la dim de embedding \(d_{model}\). Para ViT-Base, \(d_{model} = 768\) así que esta proyección es cuadrada. Para ViT-Large, \(d_{model} = 1024\) así que es una proyección hacia arriba.
Paso 4. Prepend del token [CLS] aprendible. Ahora X ∈ R^{197 × d_model}. (La representación final del token [CLS] es lo que usas para clasificación — análogo a BERT.)
Paso 5. Suma de position embeddings aprendibles P ∈ R^{197 × d_model}. Estos son aprendibles (no sinusoidales como en el transformer original). El orden 1-D corresponde a un raster scan en orden de filas sobre los parches; ViT muestra que el modelo aprende la estructura 2-D igualmente.
Paso 6. Forward por N bloques estándar de encoder transformer (Fase 17, sin máscara causal). Tomar la representación final del [CLS].
Paso 7. Una cabeza de clasificación (linear → softmax sobre \(K\) clases) sobre la representación del [CLS].
Patchify como einsum¶
El paso patchify-reshape es un único einsum (la implementación es una línea de NumPy o PyTorch):
# x: (B, H, W, C); H, W deben ser divisibles por P.
# Produce (B, H_p * W_p, P*P*C).
import numpy as np
def patchify(x: np.ndarray, P: int) -> np.ndarray:
B, H, W, C = x.shape
Hp, Wp = H // P, W // P
# Rearrange: (B, Hp, P, Wp, P, C) -> (B, Hp*Wp, P*P*C)
x = x.reshape(B, Hp, P, Wp, P, C)
x = np.einsum("bhpwqc->bhwpqc", x) # group patch coords last
x = x.reshape(B, Hp * Wp, P * P * C)
return x
Esto es mecánicamente todo lo que hay en "visión-como-transformer". La vista por patchify de una imagen es lossy en resolución espacial (un parche (16, 16, 3) se colapsa a un único token; sin atención dentro del parche) pero el campo receptivo dentro de cada bloque transformer ve la imagen completa vía atención desde el token 1, así que el contexto global se recupera inmediatamente.
Conteo de parámetros para ViT-Base¶
ViT-Base/16 (la config canónica):
- Patch embedding \(\mathbf{W}_E\): \(768 \times 768 = 589\,824\) params (+768 bias).
- Token
[CLS]: \(768\). - Position embedding: \(197 \times 768 = 151\,296\).
- 12 bloques transformer. Cada bloque (en forma Fase 17): \(4 \cdot d^2\) para QKV+O attention + \(8 \cdot d^2\) para MLP (expansión 4×) = \(12 d^2 = 12 \cdot 768^2 = 7\,077\,888\) params (más norms y biases, ~10k cada uno).
- Total: \(\approx 0.74 \text{M} + 12 \cdot 7.08 \text{M} \approx 86 \text{M}\) params.
Compara con el mini-GPT de §A13 (Fase 17): \(\sim 103\,680\) params. ViT-Base es ~830× más grande.
Para el lab 00 usamos un ViT diminuto: \(d = 64\), 4 bloques, imagen \(32 \times 32\), parche \(4 \times 4\). Conteo de tokens = \(64\) parches + 1 [CLS] = \(65\). Total params ~\(200\) k, entrenable en CPU.
Por qué ViT bate a las CNN (al final)¶
El titular del paper original de ViT: ViT iguala a ResNet sobre ImageNet solo cuando se pre-entrena sobre JFT-300M (un dataset propietario de 300 M imágenes). Sobre ImageNet-1k solo, ViT pierde contra ResNet.
La razón mecánica: las CNN codifican equivarianza a la traslación y bias de campo-receptivo local en su arquitectura. ViT no tiene ninguno — tiene que aprender ambos de los datos. Dados suficientes datos (≥ 100 M imágenes), los aprende, y entonces supera a la CNN porque puede enrutar información globalmente desde la capa 1.
Para la tarea de íconos de gramática §A13 en el lab 00 los íconos son sintéticos y pequeños (\(32 \times 32\), 5 tiempos × 3 personas = 15 clases con 60 ejemplos cada una). ViT funcionará — la tarea es fácil — pero la lección es mecánica (patchify + transformer), no de rendimiento.
CLIP: entrenamiento contrastivo imagen-texto¶
CLIP (Contrastive Language-Image Pretraining) es el paper que hizo del alineamiento imagen-texto un commodity. Es dos encoders + una pérdida:
- Encoder de imagen. Un ViT o ResNet. Produce un embedding de imagen \(\mathbf{z}_I \in \mathbb{R}^d\) (típicamente el token
[CLS], L2-normalizado). - Encoder de texto. Un transformer (autoregresivo sobre texto). Produce un embedding de texto \(\mathbf{z}_T \in \mathbb{R}^d\) desde la representación del token
[EOS], L2-normalizado. - Pérdida InfoNCE simétrica. Dado un batch de \(N\) pares (imagen, texto), el modelo debe escoger el texto correcto para cada imagen y la imagen correcta para cada texto.
Pérdida CLIP en código¶
Dado un batch de \(N\) pares, \(\mathbf{Z}_I \in \mathbb{R}^{N \times d}\) y \(\mathbf{Z}_T \in \mathbb{R}^{N \times d}\) (ambos con filas L2-normalizadas):
import numpy as np
def clip_loss(Z_I: np.ndarray, Z_T: np.ndarray, tau: float = 0.07) -> float:
# Z_I, Z_T: (N, d), cada fila L2-normalizada.
N = Z_I.shape[0]
# Matriz de similitud coseno, escalada por 1/tau. Forma (N, N).
logits = Z_I @ Z_T.T / tau
# Ground truth: la diagonal es el match correcto.
labels = np.arange(N)
# Simétrico: imagen-como-query Y texto-como-query.
loss_i2t = cross_entropy(logits, labels) # rows
loss_t2i = cross_entropy(logits.T, labels) # cols
return (loss_i2t + loss_t2i) / 2
Los dos términos son simétricos — la misma matriz se usa tanto fila a fila como columna a columna. La temperatura \(\tau\) (CLIP usa un parámetro aprendible, inicializado a \(0.07\)) escala los logits antes del softmax. Menor \(\tau\) → softmax más afilado → más presión contrastiva.
Por qué importa la simetría¶
Sin simetría, el modelo puede colapsar: todos los embeddings de texto se mapean a un único punto, todos los embeddings de imagen se mapean a un único punto cerca de él, y la diagonal de \(\mathbf{Z}_I \mathbf{Z}_T^\top\) es alta mientras que la fuera-de-diagonal es baja pero solo en una dirección. La pérdida simétrica fuerza a la fuera-de-diagonal a ser baja en ambas direcciones, que es lo que previene el colapso.
Escala de entrenamiento real de CLIP¶
- 400 M pares (imagen, texto) de internet público (el dataset WIT, interno).
- Encoder de imagen: ViT-L/14 (~300 M params).
- Encoder de texto: transformer (~63 M params, vocab 49k, contexto 77 tokens).
- Batch size: 32\,768 (= \(N\) en la pérdida arriba). La pérdida contrastiva está fundamentalmente acotada por batch-size — batch más grande = más negativos = pérdida más dura, más informativa.
- 32 GPUs V100 × 12 días. ~\(10^{22}\) FLOPs.
El requisito de batch-size es la razón de ingeniería por la que CLIP es difícil de reproducir en hardware pequeño. El lab 01 usa \(N = 32\) en un único batch de CPU — cualitativamente la misma pérdida pero con presión contrastiva mucho más débil. Lo compensamos con un dataset pequeño y de baja diversidad (20 verbos × íconos de tiempo verbal; los negativos son fáciles porque la tarea es pequeña).
Usos downstream de CLIP¶
- Clasificación zero-shot. "Clasifica la imagen \(x\) sobre \(K\) clases cuyos nombres tienes como texto". Embebe cada nombre de clase, toma el más cercano en coseno. SOTA en muchos benchmarks sin ningún entrenamiento sobre la tarea downstream.
- Retrieval. Imagen → texto y texto → imagen. Motores de búsqueda en producción (Pinterest, Unsplash) usan embeddings de tipo CLIP.
- Extractor de features para modelos multimodal downstream. El encoder de visión de LLaVA es CLIP-ViT-L/14 congelado. (Ver
theory/04-llava-and-vision-language.md.)
DINOv2: pretraining (pretraining) visual auto-supervisado sin texto¶
DINOv2 (Oquab et al. 2024, Meta) entrena un ViT sin ninguna etiqueta de texto usando una pérdida contrastiva de auto-destilación entre dos vistas aumentadas de la misma imagen. La salida es un encoder visual que:
- Bate a CLIP-ViT en tareas de predicción densa (segmentación, profundidad) a igual conteo de parámetros.
- Iguala a CLIP-ViT en clasificación/retrieval.
- Tiene features más limpias para fusion (fusion) downstream con LLM — las features de DINOv2 se usan cada vez más como backbone en modelos clase LLaVA (p. ej. algunas variantes de Llama-3-Vision).
Por qué DINOv2 importa para X2: las features de DINOv2 son las que usarías hoy (2026) si construyeras un nuevo LLM multimodal desde cero y quisieras el encoder visual congelado más fuerte. CLIP es lo que se usó en 2022–2024.
El truco de entrenamiento (relevante para la Fase 18 / dinámica de entrenamiento): un ViT teacher (EMA de los pesos del student) y un ViT student. Ambos ven crops diferentes de la misma imagen. El student se entrena para predecir la representación [CLS] del teacher. También hay un objetivo a nivel de parche vía masked image modeling. El objetivo combinado produce features que destacan tanto en tareas globales (a nivel de imagen) como locales (a nivel de parche).
No implementamos DINOv2 en los labs. Trátalo como el punto de referencia para "cuál es el backbone visual SOTA actual para modelos multimodal en 2026".
SigLIP: pérdida sigmoide como mejora de CLIP¶
SigLIP (Zhai et al. 2023) es un cambio sobre CLIP: reemplaza softmax-InfoNCE con cross-entropy binaria por par (sigmoide).
Concretamente, en vez de:
SigLIP usa:
donde \(y_{ij} = 1\) si \((i, j)\) es un par verdadero y \(0\) en caso contrario, y \(b\) es un bias aprendible inicializado negativo (de modo que los pares fuera-de-diagonal empiezan cerca de \(\sigma(\text{negativo}) \approx 0\), casando con el prior de que los pares aleatorios no matchean).
Por qué esto es mejor:
- Memoria. El denominador del softmax escala con el batch size \(N\) (cada fila suma sobre las \(N\) columnas). La sigmoide está totalmente desacoplada — cada par \((i, j)\) es independiente. Puedes shardear la matriz de pares a través de dispositivos sin un all-gather.
- Sin requisito de batch-size. SigLIP funciona bien a batch sizes pequeños (≤ 1k). CLIP necesita batch ≥ 16k para ser competitivo.
- Calidad ligeramente mejor. SigLIP-B/16 supera a CLIP-B/16 en la mayoría de benchmarks por 1–2 puntos a igual cómputo.
Por qué seguimos enseñando CLIP primero: el softmax simétrico es conceptualmente más limpio (es una distribución de probabilidad; conecta con la cross-entropy de la Fase 18 directamente), y sigue siendo el baseline dominante. SigLIP es el reemplazo grado-producción.
Para el lab 01 implementamos el InfoNCE simétrico de CLIP. SigLIP es un cambio de una línea que puedes hacer como objetivo de stretch.
Resumen del lado visual¶
- Un vision transformer es el bloque transformer de la Fase 17 + patchify +
[CLS]+ position embedding 2-D. Nada más. - Patchify:
(224, 224, 3) → 14 × 14 × (16, 16, 3) → 196 tokens de dim 768. Un einsum. - CLIP: dos encoders, pérdida softmax-simétrica contrastiva. Acotado por batch-size a escala.
- DINOv2: auto-supervisado; produce features más fuertes para fusion multimodal downstream. Punto de referencia, no implementado.
- SigLIP: reemplazo drop-in para CLIP con pérdida sigmoide. Menos memoria, sin dependencia de batch-size. En producción hoy.
Siguiente: theory/02-audio-models.md cubre el lado de audio — la modalidad que no encaja en un frame de "tokens con position embeddings" tan limpiamente como las imágenes.