← Volver al blog
steply / blog · como-funciona-um-llm-por-baixo-dos-panos-transformer-tokens.md
$ steply blog open como-funciona-um-llm-por-baixo-dos-panos-transformer-tokens
▸ loading article…
✓ ready

Cómo funciona un LLM por debajo: del token al transformer, sin dejar ningún detalle afuera

porSteply13 min de lectura

Los LLM se volvieron un commodity de producto, pero casi nadie que arma un agente, escribe un prompt o diseña un RAG sabe qué está pasando del otro lado del POST /v1/messages. Este post lo abre todo. Tokenización, embeddings, arquitectura transformer, mecanismo de atención, generación autorregresiva, sampling, KV cache, mixture of experts, cuantización, RLHF. Una wiki técnica densa, del átomo (token) al sistema completo (un modelo servido en producción). Sin mística. Matemática e ingeniería.

Quien entiende esto promptea mejor, debuguea mejor, elige mejor modelo, dimensiona mejor la infraestructura. Quien no entiende paga 10x por un resultado peor. La diferencia entre las dos curvas es este post.

1. Token: la unidad atómica

Un LLM no ve texto. Ve enteros. El texto que entra se convierte primero en una secuencia de tokens, y cada token es un entero entre 0 y vocab_size (típicamente 32k a 256k). El algoritmo estándar es BPE (Byte Pair Encoding) o variantes (Tiktoken de OpenAI, SentencePiece de Google, tokenizer propio de Anthropic).

BPE funciona así: empieza con cada byte como token. Cuenta los pares de tokens más frecuentes en el corpus de entrenamiento. Fusiona el par más común en un token nuevo. Repite hasta alcanzar el vocab_size deseado. Resultado: las palabras comunes se vuelven 1 token, las palabras raras se vuelven 2 a 5 tokens, los caracteres especiales se vuelven su propio token.

Ejemplos prácticos con el tokenizer GPT-4o:

  • 'hello' = 1 token
  • 'antidisestablishmentarianism' = 6 tokens
  • 'São Paulo' = 3 tokens (el portugués es menos eficiente que el inglés porque el vocab está dominado por el inglés)
  • '你好' = 2 tokens (los idiomas con script propio son caros)
  • 1 token en inglés = ~4 caracteres = ~0.75 palabra

Consecuencia operativa: el precio de la API es por token, no por palabra. El contenido en portugués cuesta ~30% más que el mismo contenido en inglés. El código compacto (Python, JS) es eficiente. El código verboso (Java, COBOL) es caro. El JSON sin indentación es eficiente. El JSON con indentación cuesta 30% más al pedo.

2. Embedding: del entero al vector

Un token es un entero. Un modelo opera en vectores. La primera capa de cualquier transformer es la embedding table: una matriz de tamaño [vocab_size, d_model], donde d_model es la dimensión interna (Llama 70B: 8192, clase GPT-4: estimado 12288+).

Cada token se convierte en el vector de la fila correspondiente en la tabla. Ese vector es la representación inicial. Va a sufrir N transformaciones por las capas siguientes hasta volverse una predicción.

Un detalle matemático que importa: la embedding table se aprende en el pre-entrenamiento. Los 12k números que representan el token 'hello' no fueron elegidos por un humano, fueron optimizados para minimizar la loss. Los tokens semánticamente cercanos quedan cerca en el espacio vectorial. Por eso el embedding de los tokens 'rey' y 'reina' tiene una diferencia similar a 'hombre' y 'mujer'. La geometría emerge.

Los embeddings de un LLM son distintos de los embeddings de un modelo dedicado (text-embedding-3-large de OpenAI, BGE). El embedding de un LLM se entrena para predecir el próximo token. Un embedding model se entrena para similitud semántica. No uses el embedding intermedio de un LLM para búsqueda semántica, va a andar peor que un modelo dedicado.

3. Positional encoding: cómo sabe el modelo el orden

La atención es invariante al orden (va a quedar claro más adelante). Sin información posicional, 'perro muerde hombre' y 'hombre muerde perro' se vuelven la misma sopa para el modelo. Solución: sumar/concatenar información de posición al embedding.

Métodos en orden cronológico:

  • Sinusoidal (Transformer original, 2017): la posición se vuelve una combinación de senos y cosenos de varias frecuencias. No tiene parámetro aprendido. Generaliza a secuencias más largas que las del entrenamiento, en teoría.
  • Learned positional embedding (GPT-2): una matriz [max_seq, d_model] aprendida. Simple, pero no generaliza más allá del max_seq visto en el entrenamiento.
  • RoPE (Rotary Position Embedding): usado en Llama, Mistral, Qwen. Aplica una rotación a los vectores de query/key proporcional a la posición. Ventajas: codifica la posición relativa de forma natural y extrapola mejor con técnicas como NTK-aware scaling.
  • ALiBi (Attention with Linear Bias): usado en algunos modelos. Agrega un bias decreciente a los scores de atención basado en la distancia. Más simple, buena extrapolación.

Por qué importa: la forma del positional encoding determina qué tan bien maneja el modelo el contexto largo. Llama 3.1 extendió de 8k a 128k con NTK-aware scaling del RoPE, sin reentrenamiento completo. Sin esa técnica, el modelo degrada catastróficamente más allá del entrenamiento.

4. Atención: el corazón del transformer

La atención es el mecanismo que permite que cada token 'mire' a otros tokens de la secuencia y tome información relevante. Sin eso, el modelo no puede relacionar 'ella' al final de una frase con 'María' al comienzo.

Para cada token en la posición i, computa tres vectores: Query (Q), Key (K), Value (V). Todos vía multiplicación por matrices aprendidas W_Q, W_K, W_V.

Score de atención entre el token i y el token j: score(i,j) = (Q_i · K_j) / sqrt(d_k). La división por sqrt(d_k) estabiliza el gradiente. Un softmax sobre todos los scores de cada i lo normaliza en una distribución de probabilidad.

Output para el token i: sum_j (attention(i,j) * V_j). Cada token se vuelve una combinación ponderada de los values de todos los tokens, pesada por qué tan relevantes son (atención).

Causal masking: en un LLM generativo (decoder-only), la atención está enmascarada. El token i solo puede atender a los tokens j ≤ i. Impide 'ver el futuro' durante el entrenamiento, que es lo que hace posible entrenar para predecir el próximo token.

Multi-head attention: en vez de una atención, hace N en paralelo (h = 32, 64, 128). Cada head aprende a enfocarse en patrones diferentes (una se ocupa de la sintaxis, otra de la correferencia, otra de la semántica). Los outputs se concatenan y se proyectan a d_model.

5. Variaciones de la atención que cambian el costo

La atención estándar (MHA) es O(n²) en memoria y compute. Para secuencias largas, se vuelve un cuello de botella. Optimizaciones que se volvieron estándar:

  • MQA (Multi-Query Attention): todas las heads comparten la misma K y V, solo Q es separado. Reduce drásticamente la memoria del KV cache, con pérdida mínima de calidad. Usado en PaLM.
  • GQA (Grouped-Query Attention): un punto medio. Heads agrupadas en G grupos, cada grupo comparte K/V. G=8 es común. Usado en Llama 2 70B+, Llama 3, Mistral.
  • FlashAttention: una implementación que reorganiza el compute para minimizar lectura/escritura en la memoria HBM de la GPU. 2x a 4x más rápido sin cambiar la matemática. Hoy es el default en cualquier entrenamiento/inferencia serio.
  • Sliding Window Attention: cada token solo atiende a una ventana local. Mistral 7B lo usa. Permite secuencias largas con costo lineal en vez de cuadrático.
  • Sparse Attention: patrones predefinidos de qué tokens atienden a cuáles. Longformer, BigBird.

6. Feed-forward: la otra mitad del bloque

Cada bloque transformer tiene dos subcomponentes: atención y una feed-forward network (FFN). La FFN es un MLP de dos capas aplicado token por token (sin mezcla entre tokens, ese es trabajo de la atención).

Estructura: FFN(x) = W_2 * activation(W_1 * x + b_1) + b_2. La dimensión intermedia es típicamente 4x d_model. Activación clásica: ReLU. Los modernos usan SwiGLU (GLU con Swish), que da una ganancia medible sin costo adicional relevante.

La FFN es donde vive la mayor parte de los parámetros del modelo. Llama 70B: ~70% de los pesos son FFN, ~25% atención, ~5% embeddings. Por eso es el blanco principal de Mixture of Experts (próxima sección).

7. Mixture of Experts: cómo correr un modelo grande sin activarlo entero

MoE reemplaza la FFN densa por N FFN paralelas ('experts') más un router que elige los top-K experts por token (generalmente K=2). Solo corren los K experts elegidos, los otros quedan dormidos.

Mixtral 8x7B: 8 experts, top-2 activos. Total ~47B parámetros, pero solo ~13B activados por forward. La inferencia corre al costo de 13B, la calidad llega cerca de un 70B denso.

Trade-off: ahorra compute, pero la memoria sigue igual. Necesitás tener todos los experts en VRAM aunque uses solo 2. Por eso MoE es favorito en el data center (mucha VRAM disponible) y cae mal en el edge (memoria escasa).

Modelos MoE notables: Mixtral 8x7B/8x22B, DeepSeek-V3 (256 experts, 9 activos), Qwen 2.5 MoE, GPT-4 (presunto MoE por la arquitectura inferida).

8. Normalización y residual: la estabilidad que posibilita entrenar profundo

Cada bloque tiene residual connections: output = input + sublayer(input). Permite que el gradiente fluya a través de N capas sin desvanecerse. Sin residuals, un modelo de más de ~10 capas no entrena.

Layer normalization: normaliza las activaciones por muestra. RMSNorm (una versión simplificada, usada en Llama) saca la media y normaliza solo por la raíz cuadrática media. Más barato, calidad equivalente.

Posicionamiento: Pre-norm (norm antes de la sublayer) es el estándar moderno, entrena de forma más estable que Post-norm (del paper original).

9. La arquitectura entera: apilando bloques

Un modelo es N bloques transformer apilados (Llama 70B: 80 bloques, GPT-3 175B: 96 bloques). Cada bloque tiene atención + FFN + residuals + norms. La secuencia completa de un forward:

  1. Token IDs → embedding (lookup en la tabla).
  2. Sumar positional encoding (o aplicar RoPE a los Q/K dentro de la atención).
  3. Para cada bloque (1 a N): atención (multi-head, con causal mask) + residual + norm; FFN + residual + norm.
  4. Norm final.
  5. Proyección a vocab_size vía la 'lm_head' (típicamente atada con el embedding para ahorrar parámetros): produce logits de tamaño vocab_size para cada posición.
  6. Logits → probabilidades vía softmax.

10. Inferencia autorregresiva: cómo se genera el texto

Un LLM genera token a token. Cada token nuevo depende de todos los anteriores.

tokens = encode(prompt)
while not done:
 logits = model.forward(tokens)
 next_logits = logits[-1] # último token
 next_token = sample(next_logits)
 tokens.append(next_token)
 if next_token == EOS: done = True
return decode(tokens)

Sin optimización, esto es O(n²) por generación: cada token nuevo recomputa la atención sobre todos los anteriores. Inviable.

11. KV cache: la optimización que hace viable la inferencia

Observación clave: con cada token nuevo, Q cambia pero la K y V de los tokens pasados son las mismas. Cachear la K y V de todos los tokens ya procesados elimina la recomputación.

Memoria ocupada: 2 * n_layers * n_heads * d_head * seq_len * 2 bytes (FP16). Llama 70B con 8k de contexto: ~40GB de KV cache. Por eso un modelo de 70B necesita una GPU con 80GB+ de VRAM aunque el modelo en sí sea más chico.

GQA reduce el KV cache en ~8x (Llama 70B con GQA: ~5GB de KV en 8k de contexto). La razón práctica de que toda la industria haya adoptado GQA: viabilizar el contexto largo.

La cuantización del KV cache (FP8, INT8) lo reduce otros 2x a 4x. Estándar al servir en producción.

12. Sampling: cómo se elige el token entre N posibilidades

El modelo produce una distribución de probabilidad sobre el vocab entero (32k a 256k tokens). El sampling decide cuál elegir.

  • Greedy: siempre el de mayor probabilidad. Determinístico. Bueno para tareas con una única respuesta correcta (extracción, clasificación).
  • Temperature: divide los logits por T antes del softmax. T < 1 concentra en la probabilidad alta (más determinístico). T > 1 la dispersa (más creativo). T = 0 equivale a greedy.
  • Top-k: considera solo los K tokens más probables, pone en cero el resto. K típico de 40 a 50.
  • Top-p (nucleus): considera los tokens cuya probabilidad acumulada llega a P. Estándar P=0.9 a 0.95. Adapta el tamaño del conjunto a la distribución (más flexible que top-k).
  • Repetition penalty: reduce la probabilidad de los tokens ya generados. Combate los loops.
  • Min-p: una variación reciente. Considera solo tokens con probabilidad ≥ P * max_prob. Más robusto que top-p en casos de cola larga.

Combinaciones típicas en producción: T=0 (extracción), T=0.7+top_p=0.9 (chat), T=1.0+top_p=0.95 (creativo).

13. Pre-entrenamiento: donde vive el 99% del costo

El pre-entrenamiento es donde el modelo aprende lenguaje. Objetivo: predicción del próximo token sobre un corpus gigante (billones de tokens). Loss: cross-entropy entre la distribución predicha y el token real.

Datos típicos: web (Common Crawl, FineWeb), código (GitHub, The Stack), libros, papers, Wikipedia. La curaduría se volvió un diferencial: dedup, filtro de calidad, balanceo de dominio. DeepSeek y Llama 3 mostraron que 'la calidad del data > la cantidad'.

Compute: la clase GPT-4 usó unos estimados 10^25 FLOPs. Llama 3 405B: 3.8 * 10^25. Entrenar un modelo frontier cuesta cientos de millones en GPUs. Por eso el ecosistema está dominado por pocos labs.

Tiempo: del orden de meses, en clusters de miles de GPUs (Llama 3 405B: 16k H100s por ~54 días).

14. Post-entrenamiento: lo que transforma un modelo bruto en útil

Un modelo pre-entrenado es un completador de texto. Para responder una pregunta, seguir una instrucción, rechazar contenido peligroso, necesita post-entrenamiento. Tres fases típicas.

SFT (Supervised Fine-Tuning): continúa el entrenamiento con pares (prompt, respuesta ideal) escritos por un humano. Enseña el formato de respuesta, el tono, la capacidad de seguir instrucciones. Dataset: 10k a 1M ejemplos.

RLHF (Reinforcement Learning from Human Feedback): los humanos comparan pares de respuestas, eligen la mejor. Entrena un reward model que aprende a puntuar respuestas. Después, el modelo principal se optimiza vía PPO para maximizar el reward. Caro, inestable, pero trajo a ChatGPT a la existencia.

DPO (Direct Preference Optimization): una alternativa moderna al RLHF. Optimiza directo sobre los pares de preferencia, sin un reward model intermedio. Más simple, resultados comparables. Adoptado en Llama 3, Mistral, Qwen.

Variaciones recientes: RLAIF (la IA genera el feedback), Constitutional AI (Anthropic: el modelo critica sus propias respuestas basándose en principios), Online DPO. El campo evoluciona rápido.

15. Cuantización: correr un modelo grande en hardware más chico

Los pesos del modelo son floats. El entrenamiento en BF16 (16 bits). La inferencia puede usar menor precisión con pérdida mínima de calidad.

  • FP16/BF16: 16 bits. Estándar para inferencia sin optimización.
  • INT8: 8 bits. ~2x menos memoria, ~2x más rápido. Pérdida <1% en la mayoría de los benchmarks.
  • INT4: 4 bits. ~4x menos memoria. Pérdida perceptible pero usable (GPTQ, AWQ son técnicas estándar).
  • FP8: 8 bits floating point. Nuevo, requiere hardware compatible (H100, Blackwell). Mejor calidad que INT8.
  • 1.58 bits (BitNet): extremo. Pesos en {-1, 0, 1}. Investigación prometedora, todavía no en producción.

La cuantización es lo que permite correr Llama 70B en una GPU de 24GB (INT4) en vez de exigir una A100 80GB (FP16). Habilita LLM local en hardware de consumidor.

16. Context window: límite, costo y degradación

El context window es el máximo de tokens que el modelo acepta. Hoy varía de 8k (modelos chicos) a 2M (Gemini 1.5 Pro, en preview), con Claude y GPT-4 en 200k a 1M.

Límites duros: el KV cache crece lineal, la atención crece cuadrático. Incluso con FlashAttention y GQA, un contexto muy largo es caro y lento. La latencia de TTFT (time to first token) puede pasar de 30s para un prompt de 1M tokens.

Límite blando: degradación de calidad a lo largo del contexto. Los modelos olvidan la información del medio más que la del comienzo o el final ('lost in the middle'). Benchmarks como needle-in-haystack lo miden. Los modelos modernos pasan el needle, pero degradan en tareas que exigen razonar sobre múltiples puntos del contexto largo.

17. Servir en producción: vLLM, TGI y lo que importa

Frameworks de inferencia optimizada:

  • vLLM: PagedAttention (gestión del KV cache en páginas, evita la fragmentación), continuous batching, prefix caching. El default de la industria para self-hosted.
  • TGI (Text Generation Inference): HuggingFace. Maduro, pero vLLM lo superó en features.
  • SGLang: enfocado en structured generation, RadixAttention para cache compartido entre requests.
  • TensorRT-LLM: NVIDIA, performance máxima en hardware NVIDIA, más trabajoso de configurar.
  • llama.cpp: CPU/Metal/CUDA, enfocado en cuantización y edge. El default para correr local.

Optimizaciones que entran en producción seria: continuous batching (mezcla requests en diferentes estados en el mismo forward pass, en vez de un batch sincrónico), speculative decoding (un modelo chico propone los próximos tokens, el modelo grande valida en paralelo), prefix caching (cachea la KV de un prefix común entre requests, la base del prompt caching de Anthropic).

18. El reencuadre: lo que esta wiki te permite hacer

Conociendo este stack, las decisiones se vuelven técnicas en vez de al voleo. Por qué un prompt en inglés cuesta menos: tokenización. Por qué un system prompt largo entra en el cache: prefix caching. Por qué GQA habilitó los contextos largos: KV cache. Por qué MoE tiene inferencia barata pero memoria cara: arquitectura. Por qué el modelo pierde la noción en el medio de un contexto grande: degradación de atención en secuencias largas, no un bug del prompt.

Todo lo que parecía un detalle del proveedor pasa a ser un parámetro que podés ajustar (propio o reclamarle al proveedor). Es la diferencia entre un usuario de LLM y un ingeniero de LLM. Este post entero es la frontera entre los dos.