¿Qué cambió entre SDXL y SD3?

SDXL fue el pico de la era U-Net. Usaba una U-Net convolucional como su eliminador de ruido, un schedule de ruido DDPM para controlar el proceso de difusión directo e inverso, dos codificadores de texto CLIP (CLIP-L y OpenCLIP-G) para la comprensión del texto, y capas de atención cruzada para inyectar el condicionamiento de texto en la ruta de imagen. Los tokens de texto servían como keys y values, los tokens de imagen servían como queries, y estas capas de atención cruzada se insertaban periódicamente a lo largo de la U-Net. Entre capas de atención cruzada, las características de imagen se procesaban solas a través de convoluciones.

Stable Diffusion 3 (Esser et al., 2024) cambió casi todo. La U-Net fue reemplazada por un Multimodal Diffusion Transformer (MMDiT) . El schedule de ruido DDPM fue reemplazado por rectified flow matching (caminos en línea recta de ruido a datos en lugar de las trayectorias curvas de DDPM). Los codificadores de texto duales se expandieron a triple codificadores de texto : CLIP-L, CLIP-G y T5-XXL. Y la atención cruzada fue reemplazada por atención multimodal conjunta , donde tokens de texto e imagen entran en el mismo cálculo de auto-atención en cada capa.

La única constante: el VAE . El autoencoder convolucional que comprime imágenes del espacio de píxeles a latentes y las decodifica de vuelta mantuvo el mismo diseño general (e incluso eso se mejoró — SD3 envió un VAE de mayor calidad con 16 canales latentes en lugar de 4). Todo lo demás se reconstruyó desde cero.

import json, js

# What changed from SDXL to SD3
components = [
    ("Denoiser",        "U-Net (conv)",         "MMDiT (transformer)"),
    ("Noise schedule",  "DDPM (curved)",        "Rectified flow (straight)"),
    ("Text encoders",   "CLIP-L + OpenCLIP-G",  "CLIP-L + CLIP-G + T5-XXL"),
    ("Text conditioning","Cross-attention",      "Joint multimodal attention"),
    ("VAE channels",    "4",                     "16"),
    ("Param count",     "~3.5B (U-Net)",        "2B / 8B (MMDiT)"),
]

rows = []
for comp, sdxl, sd3 in components:
    rows.append([comp, sdxl, sd3])

js.window.py_table_data = json.dumps({
    "headers": ["Component", "SDXL", "SD3"],
    "rows": rows
})

print("Almost nothing survived the transition unchanged.")

MMDiT: Multimodal Diffusion Transformer

El artículo anterior cubrió el Diffusion Transformer (DiT) , que reemplazó la U-Net con un transformer pero era solo condicional por clase (generaba clases de ImageNet, no descripciones de texto arbitrarias). MMDiT (Esser et al., 2024) resuelve el condicionamiento de texto con una idea única y elegante: poner los tokens de texto y los tokens de imagen en el mismo cálculo de atención . No a través de atención cruzada, donde el texto aparece solo como keys/values. No a través de una ruta separada. En la misma secuencia, a través del mismo softmax.

Esto es exactamente lo que sucede en cada bloque MMDiT. La red recibe dos flujos de tokens: tokens de texto $x_{\text{txt}} \in \mathbb{R}^{N_{\text{txt}} \times d_{\text{txt}}}$ y tokens de imagen $x_{\text{img}} \in \mathbb{R}^{N_{\text{img}} \times d_{\text{img}}}$, donde $N_{\text{txt}}$ y $N_{\text{img}}$ son las longitudes de secuencia respectivas y $d_{\text{txt}}$, $d_{\text{img}}$ son sus dimensiones de características (que pueden diferir porque el texto viene de un codificador de lenguaje y las imágenes vienen de un VAE).

Paso 1: Proyecciones QKV separadas. Cada modalidad tiene sus propias matrices de proyección aprendidas que mapean a una dimensión de atención compartida $d_k$:

$$Q_{\text{txt}} = x_{\text{txt}} W_Q^{\text{txt}}, \quad K_{\text{txt}} = x_{\text{txt}} W_K^{\text{txt}}, \quad V_{\text{txt}} = x_{\text{txt}} W_V^{\text{txt}}$$
$$Q_{\text{img}} = x_{\text{img}} W_Q^{\text{img}}, \quad K_{\text{img}} = x_{\text{img}} W_K^{\text{img}}, \quad V_{\text{img}} = x_{\text{img}} W_V^{\text{img}}$$

¿Por qué proyecciones separadas? Las características de texto (de T5-XXL, dimensión 4096) y las características de imagen (del VAE, dimensión dependiente del modelo) viven en diferentes espacios de representación. Matrices $W_Q$, $W_K$, $W_V$ separadas permiten que cada modalidad se mapee a un espacio de atención compartido de dimensión $d_k$ a su manera. Después de la proyección, $Q_{\text{txt}}$, $K_{\text{txt}}$, $V_{\text{txt}}$, $Q_{\text{img}}$, $K_{\text{img}}$ y $V_{\text{img}}$ todos tienen la misma dimensionalidad.

Paso 2: Concatenar. Las queries, keys y values de ambas modalidades se concatenan a lo largo de la dimensión de secuencia:

$$Q = [Q_{\text{txt}}; \; Q_{\text{img}}], \quad K = [K_{\text{txt}}; \; K_{\text{img}}], \quad V = [V_{\text{txt}}; \; V_{\text{img}}]$$

Si el texto tiene $N_{\text{txt}} = 77$ tokens y la imagen tiene $N_{\text{img}} = 1024$ tokens (un latente de 32x32 con parches 2x2), la secuencia concatenada tiene $N = 77 + 1024 = 1101$ tokens. El costo de atención es $\mathcal{O}(N^2 \cdot d_k)$, así que esto es más costoso que atender solo a tokens de imagen ($1024^2$) — pero el cálculo conjunto permite que cada token atienda a todos los demás independientemente de la modalidad.

Paso 3: Auto-atención estándar. Ejecutar la atención usual de producto punto escalado en la secuencia concatenada:

$$\text{Attention}(Q, K, V) = \text{softmax}\!\left(\frac{Q K^\top}{\sqrt{d_k}}\right) V$$

La matriz de atención tiene forma $(N_{\text{txt}} + N_{\text{img}}) \times (N_{\text{txt}} + N_{\text{img}})$. Cada token de imagen atiende a cada token de texto, y cada token de texto atiende a cada token de imagen. No hay enmascaramiento — ambas modalidades se ven mutuamente en su totalidad. Cuando $d_k$ es grande, $\frac{QK^\top}{\sqrt{d_k}}$ produce logits moderados y softmax distribuye la atención ampliamente. Cuando $d_k$ es pequeño, los logits son mayores y la atención se vuelve más aguda. La normalización $\sqrt{d_k}$ asegura que los gradientes permanezcan estables independientemente de la dimensión de proyección.

Paso 4: Separar y procesar por separado. La salida de atención se divide de vuelta en componentes de texto e imagen a lo largo de la dimensión de secuencia. Cada componente luego pasa por su propio MLP separado (red feed-forward), ya que las dos modalidades pueden necesitar diferentes transformaciones post-atención.

Este diseño tiene una consecuencia crucial fácil de pasar por alto: los tokens de texto se actualizan atendiendo a los tokens de imagen . En el enfoque de atención cruzada usado por SD 1.x / 2.x / XL, el codificador de texto se ejecuta una vez, produce un embedding fijo, y ese embedding se usa sin cambios durante todo el proceso de eliminación de ruido. La representación de texto es estática . En MMDiT, la representación de texto evoluciona durante la eliminación de ruido. En cada capa, los tokens de texto absorben información de los tokens de imagen y los tokens de imagen absorben información de los tokens de texto. Para la capa final, la representación de texto ha sido moldeada por el contenido de la imagen y viceversa. Esta integración bidireccional en cada capa es lo que distingue a MMDiT de enfoques de condicionamiento más simples.

💡 En atención cruzada (SDXL), los tokens de texto forman los keys y values pero nunca aparecen como queries — proporcionan información pero nunca la reciben. En MMDiT, los tokens de texto son tanto queries COMO keys/values. Atienden a los tokens de imagen y se modifican por lo que ven. Esto significa que el modelo puede, por ejemplo, aprender que la palabra "rojo" en el prompt debería fortalecer su asociación con una región particular de la imagen a medida que progresa la eliminación de ruido.

SD3 viene en varios tamaños: SD3-Medium (2B parámetros), SD3.5-Large (8B parámetros), y SD3.5-Turbo (una variante destilada de SD3.5-Large optimizada para menos pasos de eliminación de ruido). El escalado de 2B a 8B sigue el mismo patrón de mejora log-lineal que DiT demostró: más parámetros en el transformer mejoran sistemáticamente la calidad de imagen.

Triple codificadores de texto

SD 1.5 usaba un codificador de texto (CLIP-L). SDXL usaba dos (CLIP-L + OpenCLIP-G). SD3 usa tres : CLIP ViT-L/14, CLIP ViT-bigG y T5-XXL. ¿Por qué seguir añadiendo codificadores? Porque cada uno contribuye algo diferente.

CLIP ViT-L/14 produce embeddings de 768 dimensiones. Fue entrenado con coincidencia contrastiva imagen-texto en cientos de millones de pares imagen-leyenda. Su fortaleza es alinear conceptos visuales con frases descriptivas cortas.

CLIP ViT-bigG produce embeddings de 1280 dimensiones. Es un modelo CLIP más grande entrenado en datos más amplios. Captura un rango más amplio de conceptos visuales, pero ambos modelos CLIP comparten una limitación fundamental: producen un embedding agrupado (un único vector que resume el prompt completo) y tienen una ventana de contexto de 77 tokens.

T5-XXL produce embeddings de 4096 dimensiones y opera de manera completamente diferente. Es un modelo de lenguaje puro (un transformer codificador-decodificador con 4.7B parámetros), no entrenado en imágenes en absoluto. Lo que aporta es una profunda comprensión del lenguaje : razonamiento composicional ("un cubo rojo encima de una esfera azul"), relaciones espaciales ("a la izquierda de"), negación ("sin gafas"), conteo ("tres gatos"), y comprensión de prompts largos (su ventana de contexto es de 512 tokens, no 77). Crucialmente, T5 produce embeddings secuenciales — un vector por token, preservando la estructura del prompt.

¿Cómo se usan estas tres codificaciones? Sirven a dos rutas de condicionamiento diferentes:

  • Embeddings CLIP agrupados -> condicionamiento AdaLN. Los vectores agrupados de ambos modelos CLIP se concatenan en un único vector $c_{\text{pool}} \in \mathbb{R}^{2048}$ (768 + 1280). Este vector se alimenta a AdaLN-Zero, el mismo mecanismo que DiT usa para condicionamiento por paso de tiempo. Modula la escala y desplazamiento de la normalización de capa en cada bloque MMDiT. Esto proporciona una señal de condicionamiento global — el estilo general, el estado de ánimo y el concepto visual del prompt.
  • Embeddings de tokens T5 -> tokens de secuencia en MMDiT. Los embeddings por token de T5-XXL se proyectan a la dimensión oculta de MMDiT y se usan como los tokens de texto en el cálculo de atención conjunta. Estos son los tokens que se concatenan con los tokens de imagen, permitiendo al modelo atender a palabras y frases específicas en posiciones específicas del prompt. Esto proporciona condicionamiento de grano fino — qué objeto va dónde, de qué color es, y cómo se relaciona con otros objetos.

Esta ruta dual da a SD3 tanto alineación visual (de CLIP, que fue entrenado para coincidir imágenes y texto) como comprensión profunda del lenguaje (de T5, que fue entrenado en corpus masivos de texto). CLIP le dice al modelo cómo se ve un "atardecer". T5 le dice al modelo que "un gato sentado en una alfombra junto a dos perros" significa exactamente un gato, una alfombra y dos perros, con arreglos espaciales específicos.

import json, js

# SD3 text encoder comparison
encoders = [
    ("CLIP ViT-L/14",  768,  77,  "Pooled (1 vector)",   "Visual concepts, short captions"),
    ("CLIP ViT-bigG",  1280, 77,  "Pooled (1 vector)",   "Broader visual understanding"),
    ("T5-XXL",         4096, 512, "Sequential (per-token)", "Compositional language, long prompts"),
]

rows = []
for name, dim, ctx, out_type, strength in encoders:
    rows.append([name, str(dim), str(ctx), out_type, strength])

js.window.py_table_data = json.dumps({
    "headers": ["Encoder", "Dim", "Ctx", "Output Type", "Strength"],
    "rows": rows
})

print("Pooled CLIP vectors (768 + 1280 = 2048-dim) -> AdaLN global conditioning")
print("T5 sequential tokens (4096-dim each)          -> MMDiT joint attention tokens")
print("\nTotal text encoder parameters: ~5.1B (vs ~700M for SDXL's dual CLIP)")
💡 T5-XXL solo tiene 4.7B parámetros, más que todo el eliminador de ruido SD3-Medium (2B). Los codificadores de texto están congelados durante el entrenamiento de SD3 — sus pesos nunca se actualizan. Sirven puramente como extractores de características. En tiempo de inferencia, puedes eliminar T5-XXL completamente (a costa de reducción en la comprensión de prompts) para ahorrar VRAM.

Flux: bloques de flujo único y flujo doble

Flux (Black Forest Labs, 2024) fue construido por los mismos investigadores que crearon Stable Diffusion — dejaron Stability AI y fundaron Black Forest Labs . Flux toma el concepto MMDiT de SD3 y lo lleva más lejos con una arquitectura híbrida que transiciona del procesamiento específico por modalidad a la unificación completa de modalidades a medida que los tokens avanzan por la red.

El transformer de Flux tiene dos tipos de bloques:

Bloques de flujo doble (capas tempranas). Son estructuralmente idénticos a los bloques MMDiT. Los tokens de texto e imagen tienen proyecciones QKV separadas, MLPs separados y normalizaciones de capa separadas. Solo comparten el cálculo de atención (QKV concatenados, un solo softmax). Las dos modalidades mantienen sus propias rutas de procesamiento mientras intercambian información a través de la atención.

Bloques de flujo único (capas tardías). Aquí, los tokens de texto e imagen están completamente concatenados en una secuencia unificada. Ya no hay distinción entre proyecciones de texto y proyecciones de imagen — ambas modalidades comparten las mismas matrices $W_Q$, $W_K$, $W_V$ y el mismo MLP. Los tokens se tratan como una única secuencia agnóstica a la modalidad.

La transición de flujo doble a flujo único codifica una hipótesis sobre cómo deben fusionarse las modalidades: las capas tempranas necesitan procesamiento específico por modalidad porque las representaciones de texto e imagen comienzan en espacios muy diferentes (texto de T5, imágenes de un VAE). Proyecciones separadas en capas tempranas permiten que cada modalidad adapte sus características crudas a una forma adecuada para la atención conjunta. Para las capas tardías, las representaciones han sido suficientemente alineadas a través de atención cross-modal repetida de modo que las proyecciones separadas se vuelven redundantes — un único conjunto compartido de pesos puede manejar ambas modalidades.

Más allá de este diseño de bloques híbridos, Flux introduce varias innovaciones arquitectónicas:

  • Rotary Position Embeddings (RoPE) para imágenes. RoPE, originalmente diseñado para secuencias de texto 1D, se extiende a 2D para tokens de imagen. Cada token de imagen recibe una codificación posicional basada en su posición $(fila, col)$ en la cuadrícula latente. Esto permite al modelo generalizar a diferentes resoluciones de imagen más elegantemente que los embeddings posicionales sinusoidales fijos.
  • Atención paralela. En lugar de calcular la atención y luego el FFN secuencialmente (el diseño transformer estándar), Flux los calcula en paralelo y suma los resultados. Dada entrada $x$, un bloque estándar calcula $x + \text{FFN}(x + \text{Attn}(x))$, mientras un bloque paralelo calcula $x + \text{Attn}(x) + \text{FFN}(x)$. Esto fue introducido por PaLM (Chowdhery et al., 2022) y ofrece una ventaja práctica de throughput: los cálculos de atención y FFN pueden solaparse en la GPU, reduciendo el tiempo de reloj por capa.
  • Destilación de guía. La guía sin clasificador (CFG) requiere dos pases hacia adelante por paso de eliminación de ruido: uno condicionado y uno incondicionado. Esto duplica el cómputo de inferencia. Flux entrena un modelo estudiante destilado que aproxima la salida guiada por CFG en un solo pase hacia adelante. El estudiante aprende a predecir lo que el maestro (el pipeline CFG completo) produciría, eliminando la sobrecarga de cómputo 2x. Flux.1-schnell usa esta destilación para generar imágenes en tan solo 4 pasos de eliminación de ruido.

La formulación de atención paralela vale la pena entenderla con precisión. En el diseño serial estándar:

$$h = x + \text{Attn}(\text{Norm}_1(x))$$
$$\text{out} = h + \text{FFN}(\text{Norm}_2(h))$$

El FFN debe esperar la salida de atención $h$ antes de poder comenzar. En el diseño paralelo:

$$\text{out} = x + \text{Attn}(\text{Norm}_1(x)) + \text{FFN}(\text{Norm}_2(x))$$

Ambas ramas leen la misma entrada $x$, así que pueden ejecutarse simultáneamente. El precio es una ligera degradación de calidad (el FFN ya no se beneficia de ver la representación actualizada por atención), pero a escala esto es insignificante y la ganancia de throughput es significativa.

Flux viene en tres variantes: Flux.1-dev (12B parámetros, alta calidad, pesos abiertos), Flux.1-schnell (destilado, genera en 4 pasos, licencia Apache-2.0), y Flux.1-pro (variante comercial solo por API). Con 12B parámetros, Flux.1-dev está entre los modelos abiertos de texto a imagen más grandes, y la mejora de calidad sobre SD3-Medium (2B) es sustancial.

# Flux architecture: block types
print("Flux.1-dev Architecture (12B parameters)")
print("=" * 60)
print()

# Approximate architecture based on public information
double_stream = 19
single_stream = 38
hidden_dim = 3072
heads = 24

print(f"Double-stream blocks (MMDiT-style):  {double_stream}")
print(f"Single-stream blocks (unified):      {single_stream}")
print(f"Total transformer blocks:            {double_stream + single_stream}")
print(f"Hidden dimension:                    {hidden_dim}")
print(f"Attention heads:                     {heads}")
print()
print("Token flow through the network:")
print("-" * 60)
print(f"Layers  1-{double_stream}:  text and image have SEPARATE projections")
print(f"         (double-stream: separate W_Q, W_K, W_V per modality)")
print(f"         Both modalities share the attention computation")
print()
print(f"Layers {double_stream+1}-{double_stream + single_stream}: text and image share ALL projections")
print(f"         (single-stream: one unified W_Q, W_K, W_V)")
print(f"         Modalities are treated as one sequence")
print()
print("Hypothesis: early layers align different representation spaces,")
print("later layers operate on an already-unified representation.")

¿Qué mejoró realmente?

Los cambios arquitectónicos solo son interesantes si producen mejoras visibles. ¿Qué pueden hacer SD3 y Flux que SDXL no podía?

Renderizado de texto. Esta es la mejora más dramática. Pídele a SDXL que genere una imagen conteniendo la palabra "HOLA" y típicamente obtendrás caracteres ilegibles y confusos. SD3 y Flux pueden generar texto legible en imágenes — letreros, portadas de libros, camisetas con texto y fachadas de tiendas con letras correctas. ¿Por qué? El codificador T5-XXL entiende el texto a nivel de carácter. Los codificadores CLIP, entrenados en pares imagen-texto, tratan las palabras como conceptos visuales. T5, entrenado en corpus masivos de texto, entiende la secuencia real de caracteres y puede comunicar esto al eliminador de ruido a través de los embeddings secuenciales de grano fino en la atención conjunta de MMDiT.

Prompts composicionales. "Un cubo rojo sobre una esfera azul" suena simple, pero SDXL frecuentemente confundía qué atributos pertenecían a qué objeto — produciendo un cubo azul sobre una esfera roja, o un objeto púrpura que parecía mezclar ambos. Este es el problema de vinculación de atributos : asociar correctamente adjetivos con sus sustantivos. La comprensión lingüística de T5 combinada con la atención conjunta (donde los tokens de color pueden atender directamente a tokens de objetos específicos en cada capa) mejora dramáticamente la vinculación de atributos.

Adherencia al prompt. Escribe un prompt detallado de 50 palabras para SDXL y muchos detalles serán ignorados. Los codificadores CLIP de SDXL tienen una ventana de contexto de 77 tokens — todo más allá se trunca. T5-XXL soporta 512 tokens. Combinado con la representación secuencial más rica (embeddings por token en lugar de un vector agrupado), SD3 y Flux siguen prompts largos y detallados con más fidelidad.

Menos artefactos. Manos con el número incorrecto de dedos, rostros con características distorsionadas y objetos con geometría imposible — estos artefactos persistentes de difusión se reducen (aunque no se eliminan) en SD3 y Flux. La atención global en cada capa (cada parche de imagen atiende a todos los demás parches) ayuda a mantener coherencia estructural en toda la imagen, en lugar de depender del campo receptivo limitado de la U-Net a resoluciones altas.

Lo que T5 contribuye específicamente se puede resumir como comprensión de: relaciones espaciales ("arriba", "detrás", "a la izquierda de"), negación ("sin sombrero" — CLIP tiene dificultades con la negación porque su entrenamiento contrastivo no distingue bien "con" de "sin"), conteo ("tres gatos" produce tres gatos con más fiabilidad, no dos o cuatro), y vinculación de atributos ("un hombre alto con camisa roja de pie junto a una mujer baja con vestido azul").

import json, js

# Capability comparison across SD generations
capabilities = [
    ("Text rendering",      "None",     "Poor",     "Good",     "Excellent"),
    ("Attribute binding",   "Poor",     "Fair",     "Good",     "Very Good"),
    ("Prompt adherence",    "Fair",     "Fair",     "Good",     "Excellent"),
    ("Spatial reasoning",   "Poor",     "Poor",     "Fair",     "Good"),
    ("Negation handling",   "None",     "Poor",     "Fair",     "Fair"),
    ("Counting accuracy",   "Poor",     "Poor",     "Fair",     "Good"),
    ("Hand/face quality",   "Poor",     "Fair",     "Good",     "Very Good"),
    ("Max text tokens",     "77",       "77",       "77+512",   "77+512"),
    ("Native resolution",   "512",      "1024",     "1024",     "1024+"),
    ("Denoiser params",     "~860M",    "~3.5B",    "2-8B",     "12B"),
]

rows = []
for cap, sd15, sdxl, sd3, flux in capabilities:
    rows.append([cap, sd15, sdxl, sd3, flux])

js.window.py_table_data = json.dumps({
    "headers": ["Capability", "SD 1.5", "SDXL", "SD3", "Flux"],
    "rows": rows
})

print("Key enablers of improvement:")
print("  T5-XXL encoder   -> text rendering, compositional understanding")
print("  Joint attention   -> attribute binding, spatial coherence")
print("  Flow matching     -> fewer steps needed, cleaner denoising")
print("  Transformer scale -> global coherence (no conv receptive field limits)")
💡 Puedes ejecutar SD3 sin T5-XXL para ahorrar VRAM (T5-XXL solo requiere ~8GB en float16). El modelo sigue funcionando — CLIP proporciona suficiente señal para prompts simples. Pero prompts complejos, renderizado de texto y vinculación precisa de atributos se degradan notablemente. T5 es lo que convierte a SD3 de "SDXL ligeramente mejor" en un salto cualitativo en comprensión de prompts.

Quiz

Pon a prueba tu comprensión de las arquitecturas de SD3 y Flux.

¿Cuál es la diferencia clave entre atención cruzada (SDXL) y atención conjunta (MMDiT) para condicionamiento de texto?

¿Por qué SD3 usa proyecciones QKV separadas para tokens de texto e imagen en bloques MMDiT?

¿Cuál es el rol principal del codificador T5-XXL en SD3, comparado con los dos codificadores CLIP?

¿Por qué Flux usa bloques de flujo doble en capas tempranas y bloques de flujo único en capas tardías?