Hugging Face · LLM Course · Capítulo 1
Transformers: por qué todo lo que ya usás corre sobre esto
Tenés práctica con código pero nunca viste la teoría de punta a punta. Este capítulo te da el mapa completo: qué es un transformer, por qué casi todo LLM moderno tira solo la mitad de esa arquitectura, y cómo el texto sale, literalmente, un token a la vez.
Antes de leerNinguno: este es el punto de partida del recorrido. Si preferís la base matemática antes que el modelo, arrancá por fast.ai lección 3 (ver la ruta «fundamentos primero» de la portada).
🎯 Al terminar vas a poder
- Explicar con tus palabras qué distingue un LLM de un modelo de NLP clásico, y por qué casi todo lo que usás hoy es decoder-only.
- Seguir a mano el cálculo de atención — Query, Key, Value, puntajes, softmax, suma ponderada — sobre un ejemplo de tres tokens.
- Calcular cuánta memoria ocupa el KV cache de un modelo concreto por token y por conversación, y explicar por qué GQA lo reduce.
- Elegir
temperature,top_p,top_kyrepetition_penaltycon criterio, y diagnosticar una salida repetitiva o incoherente a partir de ellos. - Explicar por qué un fine-tune no borra el sesgo del modelo base y qué capas de mitigación existen encima.
0. Por qué este capítulo pesa más de lo que parece
El curso original de Hugging Face nació en 2022 como un curso de NLP (procesamiento de lenguaje natural) y fue mutando a curso de LLMs a medida que el campo lo hizo. Eso importa para vos: vas a encontrar frases pensadas para un mundo con BERT y GPT-2 como protagonistas, en un momento donde el protagonista real es un puñado de modelos decoder-only enormes (Llama, Gemma, Qwen, DeepSeek, GPT, Claude). La mecánica de fondo no cambió, pero el énfasis sí, y este capítulo está reordenado para reflejar eso.
🟢 Actualización 2026
Casi ningún proyecto nuevo hoy arranca fine-tuneando un encoder tipo BERT desde cero. La ruta por defecto es: agarrar un LLM decoder-only pre-entrenado, y resolver la tarea con prompting, RAG, o un fine-tune liviano (LoRA/QLoRA). Vas a ver esa arquitectura en todos lados porque es, en la práctica, la única que te vas a cruzar el 90% del tiempo — por eso este capítulo le da más peso que el original.
Como venís con experiencia práctica (ya usaste modelos, APIs, quizás algún fine-tune) pero sin la teoría de base, el objetivo acá no es que memorices nombres de arquitecturas. Es que entiendas por qué funcionan así, para que cuando leas un paper o un README de un modelo nuevo, ya tengas dónde encajar la información nueva.
1. De NLP a LLM: qué cambió realmente
NLP (Natural Language Processing) es el campo completo: todo lo que tiene que ver con que una máquina entienda o genere lenguaje humano. Análisis de sentimiento, traducción, reconocer que "Hugging Face" es el nombre de una empresa y no una acción física — todo eso es NLP, y existe desde antes de que existieran los transformers.
Un LLM (Large Language Model) es un tipo particular de modelo de NLP que se volvió tan grande y se entrenó con tantos datos que dejó de necesitar un modelo distinto por tarea. En vez de entrenar un clasificador de sentimiento, otro de traducción y otro de resúmenes, entrenás un solo modelo que aprendió lenguaje de forma tan general que resuelve las tres cosas con el prompt correcto.
📜 Lo que decía el curso original
"Un LLM es un modelo de IA entrenado con enormes cantidades de texto que puede entender y generar texto similar al humano, reconocer patrones del lenguaje, y realizar una amplia variedad de tareas sin entrenamiento específico por tarea." Sigue siendo correcto — es la definición de base que no cambió.
Lo que sí distingue a un LLM del resto:
Escala
De millones a cientos de miles de millones de parámetros.
Generalidad
Una tarea nueva no requiere reentrenar el modelo, solo redactar bien el prompt.
Aprendizaje en contexto
Le mostrás 2-3 ejemplos dentro del prompt y ya ajusta su comportamiento.
Capacidades emergentes
A cierta escala aparecen habilidades que nadie programó explícitamente (razonar paso a paso, seguir instrucciones complejas).
Y las limitaciones que vienen de fábrica con esa escala — las vas a necesitar en la cabeza todo el curso: alucinan con total confianza, no "entienden" el mundo (operan sobre patrones estadísticos), heredan sesgos de sus datos de entrenamiento, y tienen una ventana de contexto finita (aunque hoy esa ventana llega a millones de tokens en algunos modelos, algo impensado en 2022).
2. Qué hace un transformer, en la práctica
Antes de mirar el motor, veamos qué hace el auto andando. La librería transformers de Hugging
Face resume todo el trabajo sucio (preprocesar texto, correr el modelo, decodificar la salida) en una sola
función: pipeline().
from transformers import pipeline
classifier = pipeline("sentiment-analysis")
classifier("Llevo toda mi vida esperando un curso así.")
[{'label': 'POSITIVE', 'score': 0.9598}]Bajo el capó, ese pipeline() siempre hace tres pasos, sin importar la tarea:
- Preprocesa el texto a un formato numérico que el modelo entiende (tokenización).
- Pasa esos números por el modelo.
- Post-procesa la salida cruda para devolverte algo legible.
La misma función sirve para tareas radicalmente distintas con solo cambiar el nombre de la tarea y, opcionalmente, el modelo:
# Clasificación sin haber entrenado esas categorías (zero-shot)
classifier = pipeline("zero-shot-classification")
classifier(
"Este es un curso sobre la librería Transformers",
candidate_labels=["educación", "política", "negocios"],
)
# Generación de texto con un modelo puntual del Hub
generator = pipeline("text-generation", model="HuggingFaceTB/SmolLM2-360M")
generator("En este curso vamos a aprender a", max_new_tokens=30)
🟢 El mismo pipeline con Gemma 4
SmolLM2-360M es un modelo base chico, elegido para que el ejemplo corra en CPU. El modelo que
usás hoy entra en el mismo pipeline: pipeline("text-generation", model="google/gemma-4-E2B-it",
device_map="cuda", dtype=torch.bfloat16), con una diferencia: es un modelo instruct, así
que se le pasan mensajes de chat (gen([{"role": "user", "content": "…"}], max_new_tokens=40))
y no un string suelto. Con texto crudo un instruct degenera ("aprender a aprender a a a…"). Probado en la
5090 con transformers 5.16: 1,5 s por respuesta de 40 tokens y unos 10 GB de VRAM.
Un detalle que muerde la primera vez: el original usa max_length=30, que cuenta
prompt + generación — si tu prompt ya tiene 25 tokens, te quedan 5 de respuesta.
max_new_tokens cuenta solo los tokens nuevos, que es casi siempre lo que querés decir;
si pasás los dos, max_new_tokens tiene prioridad.
Esto es exactamente lo mismo que hacés hoy cuando llamás a una API de un LLM alojado (Claude, GPT, Llama vía un endpoint): mandás texto, el sistema tokeniza, corre el modelo, decodifica, te devuelve texto. La única diferencia es cuánta de esa cadena controlás vos.
✏️ Para probar
Si tenés Python a mano: instalá transformers, corré el ejemplo de zero-shot-classification
con una frase tuya y tres etiquetas inventadas. Fijate que el modelo nunca vio esas etiquetas en
entrenamiento — igual te da un score por cada una.
Familia completa de tareas que vas a encontrar (texto, imagen, audio, multimodal) — no memorices la lista, volvé a ella cuando la necesites:
| Pipeline | Qué hace |
|---|---|
text-generation | Completa un prompt |
text-classification | Asigna una categoría a un texto |
summarization | Resume preservando lo esencial |
translation | Traduce entre idiomas |
question-answering | Extrae una respuesta de un contexto dado |
image-classification / automatic-speech-recognition | Lo mismo, pero para imagen y audio |
3. El truco de fondo: atención
El original deja esto para el capítulo 2. Acá lo adelantamos porque sin esta idea, todo lo que sigue (arquitecturas, inferencia) es memorizar nombres sin entender por qué existen.
Antes de los transformers (2017), los modelos de lenguaje procesaban texto palabra por palabra, en orden, arrastrando un "resumen" del contexto anterior (redes recurrentes, RNN/LSTM). El problema: ese resumen se degrada con frases largas — es como intentar recordar el principio de un párrafo largo mientras leés la última línea.
La autoatención (self-attention) resuelve esto de otra forma: en vez de comprimir todo el contexto en un resumen, cada palabra puede "mirar" directamente a cualquier otra palabra de la frase y decidir cuánto peso ponerle, todo en paralelo. Tomemos:
"El banco donde nos sentamos estaba frente al banco donde saqué la plata."
Para saber que el primer "banco" es un asiento y el segundo una entidad financiera, el modelo necesita mirar palabras lejanas ("sentamos", "saqué la plata"). La autoatención calcula, para cada palabra, un puntaje de relevancia contra todas las demás palabras de la frase, y usa esos puntajes para construir una representación de esa palabra que ya incorpora el contexto relevante — sin importar qué tan lejos esté.
Query, Key, Value: cómo se calcula ese puntaje y de dónde salen las matrices
Para entender de dónde salen estas letras sin misterio, hay que mirar el flujo de tensores. Cada token entra a una capa del transformer como un vector numérico continuo de dimensión hidden_size (denotado matemáticamente como — por ejemplo, 4.096 números en Llama 3 8B, o 2.048 en Gemma 2 2B). Ese vector es su embedding (que ya incluye la información de en qué posición de la frase está).
Dentro de cada capa de atención viven tres matrices de pesos entrenables: , y . Cuando el vector del token pasa por la capa, la GPU hace tres multiplicaciones de matrices en paralelo para proyectar ese token en tres roles funcionales distintos:
- Query (Q) = x · WQ — "Qué estoy buscando": el vector de búsqueda que este token dispara para interrogar al resto de la frase.
- Key (K) = x · WK — "Qué etiqueta ofrezco": el descriptor o índice con el que este token se hace visible para responder a las consultas de otros.
- Value (V) = x · WV — "Qué información aporto": el contenido semántico real que este token transmitirá a quien decida prestarle atención.
La metáfora de fondo es un diccionario o base de datos clave-valor continua y diferenciable: hacés una consulta (Query), la contrastás contra las etiquetas de los registros disponibles (Keys), y según la afinidad o coincidencia que encuentres, extraés una combinación de sus contenidos (Values).
El puntaje de coincidencia entre el token i y el token j es el producto punto entre la Query de i y la Key de j (): un escalar que resulta alto y positivo cuando ambos vectores apuntan en la misma dirección en el espacio latente.
¿Por qué se divide por ? Si dos vectores de dimensión tienen componentes aleatorias independientes con media 0 y varianza 1, su producto punto tiene media 0 pero varianza igual a . Con dimensiones típicas modernas como , los productos punto pueden llegar fácilmente a magnitudes de 30 o 40. Al aplicar softmax sobre valores tan dispares, la función se satura: el valor mayor se lleva un peso de 0,99999 y los demás quedan en 0. Peor aún para el entrenamiento: las derivadas del softmax en zonas saturadas son casi cero (desvanecimiento del gradiente), impidiendo que la red aprenda. Dividir por (por ejemplo, ) escala la varianza de vuelta a 1, manteniendo los puntajes en una zona donde el softmax distribuye atención de forma suave.
Un mini ejemplo numérico trabajado con tres tokens — quedémonos con "banco", "sentamos" y "plata" y miremos qué pasa cuando la Query del primer "banco" se compara con las Keys de los tres. Supongamos que los productos punto, ya divididos por √dk, dan 2,0 · 4,0 · 1,0:
| Key de… | Puntaje escalado | epuntaje | Peso (softmax) |
|---|---|---|---|
| banco | 2,0 | 7,39 | 7,39 / 64,71 ≈ 0,11 |
| sentamos | 4,0 | 54,60 | 54,60 / 64,71 ≈ 0,84 |
| plata | 1,0 | 2,72 | 2,72 / 64,71 ≈ 0,04 |
salida(banco) = 0,11 · V_banco + 0,84 · V_sentamos + 0,04 · V_plata
El vector nuevo de "banco" es 84% "sentamos" — el modelo acaba de decidir, sin ninguna regla escrita a mano, que en esta frase "banco" es un asiento. Notá dos cosas: los pesos suman 1 (0,11 + 0,84 + 0,04 ≈ 1), y una diferencia de solo 2 puntos entre puntajes se convirtió en una diferencia de 8× entre pesos — así de agresivo es el softmax, y por eso importa el escalado por √dk.
Cómo se lee. Para cada Query, el producto punto contra todas las Keys (la matriz Q·Kᵀ tiene una fila por token y una columna por token), dividido por la raíz de la dimensión de las Keys para que el softmax no se sature, convertido en pesos que suman 1, y usados para promediar los Values. Es la tabla de arriba escrita para todos los tokens a la vez.
De dónde sale head_dim y por qué Multi-Head Attention
Si el vector de un token tiene 4.096 dimensiones, ¿por qué no hacemos una sola gran atención con vectores de 4.096?
Porque una sola atención calcularía un único número escalar de similitud entre cada par de tokens. El lenguaje humano es inherentemente multidimensional: la palabra "banco" necesita atender a "sentamos" para resolver su rol semántico de asiento, a "estaba" para la concordancia de tiempo verbal, a "frente al" para la relación espacial, y al punto final para el cierre sintáctico de la oración. Un único escalar obligaría a promediar todas esas preguntas distintas en un solo número, borrando los matices.
La solución es Multi-Head Attention (MHA): proyectar el espacio total a múltiples sub-espacios paralelos llamados "cabezas" (heads).
- Tomamos el tamaño total del embedding:
hidden_size(ej. 4.096). - Definimos la cantidad de cabezas de atención:
num_attention_heads(ej. 32). -
El tamaño de cada sub-espacio es el cociente exacto entre ambos:
head_dim = hidden_size // num_attention_heads = 4096 // 32 = 128
head_dim no es un hiperparámetro arbitrario: es la dimensión real del vector sobre el que opera cada cabeza de forma independiente. En Llama 3 8B, cada una de las 32 cabezas tiene vectores Q, K y V de 128 números. Una cabeza se especializa en concordancia de género/número; otra en dependencias a larga distancia; otra en puntuación.
Al terminar el cálculo en paralelo, las salidas de las 32 cabezas (cada una de tamaño 128) se concatenan lado a lado: . Ese vector reconstituido se multiplica por una última matriz aprendida, la proyección de salida , y vuelve a integrarse al flujo del modelo.
🟢 La trampa cuadrática de la atención: O(N²)
Si tu texto tiene tokens, cada token genera una Query que debe multiplicarse contra las Keys de los tokens. La matriz de puntajes tiene tamaño .
Con 2.048 tokens, esa matriz tiene unos 4,19 millones de elementos. Con 131.072 tokens (128k de contexto), tiene 17.179 millones de elementos por cabeza y por capa. El costo de cómputo y la memoria temporal para calcular la atención crecen con el cuadrado de la longitud del texto (). Todo el esfuerzo de ingeniería de la última década (FlashAttention, ventanas deslizantes, KV cache, GQA) existe para domar esta curva cuadrática.
4. Las tres formas de armar un transformer
Un transformer completo tiene dos mitades: un encoder (lee y entiende texto de entrada) y un decoder (genera texto de salida, palabra por palabra). Casi todo modelo que vas a usar es una de estas tres combinaciones:
| Arquitectura | Atención | Mejor para | Ejemplos |
|---|---|---|---|
| Encoder-only | Bidireccional (ve toda la frase) | Clasificación, NER, QA extractivo | BERT, ModernBERT, RoBERTa |
| Decoder-only | Causal (solo ve lo anterior) | Generación de texto, chat, código | GPT, Llama, Gemma, DeepSeek, Claude |
| Encoder-decoder | Bidireccional + causal | Traducción, resumen, reescritura | T5, BART, mBART |
🟢 Actualización 2026
El original le da el mismo peso a las tres familias. En la práctica actual, decoder-only ganó por goleada para propósito general: es más simple de escalar, y un decoder-only bien entrenado puede hacer traducción o resumen igual de bien con el prompt correcto, sin necesitar la mitad "encoder" separada. Los encoder-only sobreviven fuerte en un nicho específico: embeddings (buscar texto similar, RAG) y clasificación de alto volumen donde no necesitás generar texto, solo una etiqueta rápida y barata.
5. Cómo un LLM genera texto, token a token
Esta sección no existía en el curso original de 2022 — se agregó después porque, con LLMs, entender inferencia (cómo se genera texto, no cómo se entrena) se volvió tan importante como entender el entrenamiento. Es, seguramente, la parte más útil de este capítulo para vos si ya usás LLMs por API.
Generar una respuesta pasa por dos fases bien distintas:
1. Prefill
El modelo procesa todo tu prompt de una sola vez: lo tokeniza, lo convierte a vectores, corre una pasada por la red. Es la fase que determina cuánto tardás en ver el primer token (time to first token).
2. Decode
El modelo genera un token, lo agrega a la secuencia, y repite: cada token nuevo depende de todos los anteriores. Es autoregresivo — por eso ves las respuestas "aparecer" de a poco en un chat.
En cada paso del decode, el modelo no elige "la" siguiente palabra — calcula una probabilidad para cada palabra posible de su vocabulario y después aplica una estrategia de muestreo para elegir una:
- Temperature: más alta = más aleatorio/creativo, más baja = más determinista y repetible.
- Top-p (nucleus sampling): solo considera las palabras cuya probabilidad acumulada llega a, por ejemplo, el 90%, descartando la cola larga de opciones improbables.
- Top-k: variante más simple — solo mira las k palabras más probables.
- Penalización por repetición: baja artificialmente la probabilidad de palabras ya usadas, para que el modelo no quede dando vueltas en el mismo lugar.
Estos son, literalmente, los parámetros que ves en cualquier API de LLM (temperature,
top_p, max_tokens). Ahora sabés qué está pasando adentro cuando los tocás.
Valores de partida razonables — después ajustás mirando la salida, no la teoría:
| Perilla | Qué hace por dentro | Valores típicos |
|---|---|---|
temperature | Divide los logits antes del softmax: <1 afila la distribución (más determinista), >1 la aplana (más azar). 0 equivale a elegir siempre el más probable (greedy). | 0–0,3 para extracción, código o JSON; 0,7–1,0 para conversación y escritura |
top_p | Ordena las opciones por probabilidad y se queda con el menor conjunto cuya suma llega a p — la cola se descarta. | 0,9–0,95 (1,0 = sin recorte) |
top_k | Se queda con los k tokens más probables, sin mirar cuánta probabilidad acumulan. | 40–50 (transformers usa 50 por defecto); muchas APIs ni lo exponen |
repetition_penalty | Multiplica hacia abajo la probabilidad de tokens que ya aparecieron en el contexto. | 1,0 = apagado; 1,1–1,2 si el modelo entra en bucle; más que eso empieza a degradar el texto |
Las perillas se combinan: primero se aplica la penalización por repetición y la temperatura, después
top_k y top_p recortan la lista, y recién ahí se muestrea. Con
temperature=0 (o do_sample=False en transformers) el resto no
interviene — siempre sale el token más probable.
top_k y top_p no cambian la forma, cortan la
cola: uno por cantidad fija, el otro por masa de probabilidad acumulada.
🟢 Prefill vs. Decode: el choque físico entre cómputo y ancho de banda
Generar texto expone dos regímenes de hardware totalmente opuestos:
- Prefill (Compute-bound): El prompt entra entero. Para 1.000 tokens de prompt, la GPU multiplica matrices densas de contra los pesos del modelo (operaciones tipo GEMM). La GPU trabaja en su punto óptimo: los núcleos tensoriales están 100% ocupados porque la intensidad aritmética (cantidad de operaciones matemáticas por cada byte leído de memoria) es altísima. El límite acá son los TFLOPs de cómputo.
- Decode (Memory-bandwidth bound): Genera un solo token por vez (). Para calcular el siguiente token de un vector de , la GPU está obligada a leer todos los pesos de las 32 capas desde la VRAM a los núcleos de cómputo (en un modelo de 8B en 16 bits, son ~16 GB de pesos transferidos por cada token emitido). La GPU casi no hace cuentas matemáticas: pasa el 95% del tiempo esperando que los datos viajen por el bus de memoria. El límite acá no son los TFLOPs, sino los GB/s de ancho de banda de la memoria (memory bandwidth).
El KV Cache desarmado: ¿por qué K y V, y por qué NUNCA Q?
Imaginemos qué pasaría en el paso de decode sin optimizar. Supongamos que ya se emitieron 100 tokens y la red va a predecir el token 101.
Para que el nuevo token sepa qué responder, debe calcular la atención contra todos los tokens anteriores (posiciones 1 a 100). Si no guardáramos nada en memoria, tendríamos que volver a pasar los 100 tokens previos por las 32 capas de la red. Para el token 102, pasaríamos 101 tokens. Para el token 103, 102 tokens. El costo de generar tokens escalaría de forma cuadrática: .
Pero hay un principio fundamental que lo cambia todo: la atención causal. En un modelo decoder-only, los tokens pasados tienen estrictamente prohibido mirar al futuro (sus conexiones hacia adelante están enmascaradas). Eso significa que cuando entra el token 101, las representaciones intermedias de los tokens 1 al 100 en cada capa son idénticas e inmutables. El token 5 siempre produjo la misma Key y el mismo Value en la capa 12, tanto en el paso 10 como en el paso 100.
Ahora miremos qué pasa en el paso 101 a nivel de atención:
- El token nuevo 101 genera su vector Query (), su Key () y su Value ().
- ¿Quién es el que pregunta en este paso? Únicamente el token 101. Él busca información con su .
- ¿A quiénes consulta? A los tokens pasados (1 al 100) más a sí mismo. Compara su contra .
- ¿Qué extrae si hay coincidencia? Pondera los valores .
¿Por qué se guardan K y V? Porque todas las futuras Queries que nazcan en los pasos 102, 103, 104... van a necesitar volver a consultar las Keys y Values de todos los tokens precedentes. Si los guardamos en una memoria rápida (el KV Cache), cada paso de decode solo tiene que procesar el token nuevo ( token hacia adelante) y concatenar su nueva y al cache existente.
¿Y por qué NO se guarda Query (Q)? Porque una vez que el token 101 terminó de consultar al pasado y el modelo emitió la distribución para el token 102, la Query no le sirve a nadie más en la historia del universo. El siguiente paso tendrá su propia Query (). Ningún token futuro compara nada contra la Query vieja de un token anterior. Guardar Q sería tirar un 33% más de memoria a la basura sin ninguna utilidad funcional.
La anatomía del tensor en VRAM y la cuenta real
En la memoria de la GPU, el KV Cache no es un concepto abstracto: es un tensor concreto reservado por capa. Para cada capa , la GPU mantiene dos tensores (uno para K, uno para V) con dimensiones:
shape = [batch_size, num_kv_heads, max_seq_len, head_dim]
Ahora desarmemos término por término la famosa fórmula de memoria:
bytes por token = 2 × capas × kv_heads × head_dim × bytes_por_numero
- 2: Porque guardamos exactamente dos tensores: el vector Key y el vector Value.
- capas (): Cada una de las 32 capas extrae transformaciones distintas, por lo que cada capa tiene su propio par de matrices y y produce representaciones K y V diferentes.
- kv_heads: La cantidad de cabezas de atención asignadas a K y V.
- head_dim: La longitud de cada vector individual por cabeza ().
- bytes_por_numero: La precisión numérica utilizada. En BF16 o FP16 son 2 bytes; en FP8 es 1 byte; en INT4 son 0,5 bytes.
Veamos la cuenta con Llama 3 8B (32 capas, 8 KV heads, head_dim=128, en BF16 con 2 bytes):
2 × 32 capas × 8 kv_heads × 128 head_dim × 2 bytes = 131.072 bytes = 128 KiB por token
Multiplicá eso por la longitud de contexto:
- A 8.192 tokens: .
- A 131.072 tokens (128k): ¡solo para el KV Cache de una sola conversación!
Si sumás los ~16 GB de los pesos del modelo en BF16 más los 16 GB del KV Cache a 128k, necesitás 32 GB de VRAM para atender a un solo usuario. Si quisieras atender 4 usuarios en simultáneo con contexto lleno, necesitarías de VRAM. Por eso el KV Cache es el factor determinante en el costo y la viabilidad de servir modelos.
MHA vs. MQA vs. GQA: la solución arquitectónica
Fijate en el factor más modificable de la fórmula: kv_heads.
- MHA (Multi-Head Attention clásica): Cada cabeza de Query tiene su propia cabeza de Key y Value. En Llama 3 8B tendríamos 32 cabezas de Query y 32 cabezas de K/V. El cache ocuparía (¡cuatro veces más, 64 GB a 128k!).
- MQA (Multi-Query Attention - Shazeer, 2019): Todas las cabezas de Query comparten una única cabeza de Key y Value (). El cache se reduce 32 veces (16 KiB por token), pero el modelo pierde expresividad y sufre en tareas complejas de razonamiento o múltiples hechos en contexto largo.
- GQA (Grouped-Query Attention - Ainslie et al., 2023): El estándar de la industria hoy (Llama 3, Gemma 2/4, Mistral, Qwen 2.5). Se dividen las 32 cabezas de Query en 8 grupos de 4. Las 4 cabezas de Query de cada grupo comparten un único par K/V (). El resultado es el Santo Grial: un 75% menos de memoria en KV Cache comparado con MHA, con una calidad de razonamiento y perplejidad virtualmente idéntica.
Y si además guardás el KV Cache en 8 bits (usando FP8 con el flag --kv-cache-dtype fp8 en vLLM),
el multiplicador de bytes baja de 2 a 1, reduciendo la memoria de nuevo a la mitad: de 16 GiB a 8 GiB a 128k tokens.
6. Sesgos y límites reales
Todo modelo entrenado con texto masivo de internet (o incluso con datos "curados" como Wikipedia) hereda los sesgos de esos datos. El ejemplo clásico del curso original sigue siendo ilustrativo:
from transformers import pipeline
unmasker = pipeline("fill-mask", model="bert-base-uncased")
unmasker("This man works as a [MASK].")
# → lawyer, carpenter, doctor, waiter, mechanic
unmasker("This woman works as a [MASK].")
# → nurse, waitress, teacher, maid, prostitute
BERT se entrenó con Wikipedia y BookCorpus — fuentes que en 2018 se consideraban "neutras" comparadas con scrapear todo internet. Igual el sesgo aparece. La conclusión del curso original sigue siendo válida al 100%: fine-tunear el modelo con tus propios datos no borra el sesgo de base, porque ese sesgo vive en los pesos preentrenados, no en la capa que vos agregás encima.
🟢 Actualización 2026
Lo nuevo desde entonces no es que el problema se haya resuelto — es que aparecieron capas de mitigación que lo hacen menos visible en el uso cotidiano: RLHF y post-entrenamiento de alineación (por qué Claude o GPT no te contestan lo mismo que un modelo base sin ajustar), guardrails a nivel de aplicación, y RAG (anclar la respuesta a documentos reales en vez de dejar que el modelo "recuerde" de memoria, que es donde más alucina). Ninguna de estas capas elimina el sesgo de fondo — lo empujan hacia abajo en la pila.
Cuando una alucinación tiene consecuencias reales (un número financiero equivocado, por ejemplo) las capas de arriba no alcanzan — hace falta verificación explícita. Ver el gate anti-alucinación para un caso real, con un incidente concreto de por medio.
7. Errores comunes
Cosas que van a pasarte la primera vez que uses estas piezas a mano — y cómo leerlas.
| Síntoma | Causa probable | Qué hacer |
|---|---|---|
text-generation devuelve una respuesta cortada o casi vacía | max_length cuenta el prompt; con un prompt largo no queda lugar para generar | Usar max_new_tokens (§2) |
| El modelo repite la misma frase en bucle | Decodificación greedy o temperatura muy baja sin penalización por repetición | Muestrear con temperature≈0,7, top_p≈0,9 y repetition_penalty≈1,1 (§5) |
| Texto incoherente, palabras inventadas | Temperatura > 1 o top_p/top_k demasiado permisivos: la cola improbable entra en juego | Bajar la temperatura a ≤ 1,0 y recortar con top_p 0,9 (§5) |
| Le hacés una pregunta y el modelo la "continúa" en vez de contestarla | Es un modelo base, no instruct: solo aprendió a completar texto | Usar la variante -Instruct y su chat template (Capítulo 2, §3) |
| La conversación se pone lenta o revienta la VRAM a medida que crece | El KV cache crece linealmente con el contexto; el cómputo de atención, cuadráticamente | Acotar el contexto, elegir modelos con GQA, KV cache en 8 bits (§5) |
zero-shot-classification da un score alto a una etiqueta absurda | Siempre reparte probabilidad entre las etiquetas que le diste — no tiene opción "ninguna" | Agregar una etiqueta "otro" o exigir un umbral mínimo (§2) |
| Después de fine-tunear, el modelo sigue mostrando el sesgo del original | El sesgo vive en los pesos preentrenados, no en la capa que ajustaste | Mitigar en la aplicación (RAG, verificación, guardrails), no esperar que el fine-tune lo borre (§6) |
8. Resumen y mapa mental
Si te quedás con cinco ideas de este capítulo, que sean estas:
- NLP es el campo; LLM es un tipo de modelo de NLP que resuelve muchas tareas con un solo modelo grande.
pipeline()siempre hace lo mismo: tokenizar → correr el modelo → decodificar. Es el mismo patrón detrás de cualquier API de LLM que uses.- La autoatención deja que cada palabra mire directamente a cualquier otra, sin importar la distancia — por eso reemplazó a las redes recurrentes.
- Hay tres arquitecturas (encoder / decoder / encoder-decoder), pero hoy casi todo lo que usás es decoder-only.
- Generar texto tiene dos fases (prefill y decode) y un puñado de perillas (temperature, top-p, top-k) que ahora sabés qué hacen de verdad.
- El KV cache es memoria pura: 2 × capas × kv_heads × head_dim × bytes por token — y GQA es la razón por la que un modelo de 8B entra con 128k de contexto en una sola GPU.
El capítulo original de Hugging Face sigue con "Cómo 🤗 Transformers resuelve tareas" y un quiz. Lo primero se cubre en el Capítulo 2 (tokenizer, cuerpo del modelo, head); el quiz lo tenés acá abajo, en Ponete a prueba.
9. Ponete a prueba
Respondé antes de abrir cada respuesta. Si fallás una, volvé a la sección indicada.
1. ¿Por qué un modelo decoder-only puede traducir o resumir si "solo" aprendió a predecir el siguiente token?
Porque a la escala de un LLM, predecir bien el siguiente token exige haber aprendido la estructura del lenguaje de forma tan general que cualquier tarea se puede plantear como "continuá este texto": el prompt describe la tarea y el modelo la completa. Por eso no hace falta un head ni un modelo distinto por tarea, que era la norma con BERT. (§1, §4)
2. En el ejemplo de tres tokens, los puntajes escalados eran 2,0 · 4,0 · 1,0. ¿Qué pasaría con los pesos si no dividieras por √dk y los puntajes crudos fueran 16 · 32 · 8?
El softmax se satura: e32 es tan mayor que e16 que el peso de "sentamos" se va a ≈1,0 y los otros dos a ≈0. El token deja de mezclar contexto y, peor, el gradiente por esas posiciones se vuelve casi cero — el escalado existe justamente para mantener los puntajes en un rango donde el softmax reparte y aprende. (§3)
3. ¿Cuál de las dos fases de generación determina el time to first token, y cuál se hace más lenta a medida que la respuesta crece?
El prefill (procesar todo el prompt de una vez) determina el tiempo hasta el primer token. El decode es autoregresivo: cada token nuevo atiende a todos los anteriores, así que el trabajo por token — y el KV cache — crece con la longitud de la conversación. (§5)
4. Calculá el KV cache de Llama 3 8B en bf16 con 32.768 tokens de contexto.
128 KiB por token × 32.768 tokens = 4 GiB. Si el mismo modelo usara MHA (32 KV heads en vez de 8) serían 16 GiB; con el cache en fp8, 2 GiB. (§5)
5. ¿Por qué se cachean las Keys y Values pero nunca las Queries, y por qué las Keys y Values viejas no necesitan recalcularse?
Porque en un decoder causal los tokens pasados no pueden mirar hacia adelante: la representación de los tokens anteriores en cada capa es matemáticamente inmutable. En cada paso de decode, solo el token nuevo genera una Query para interrogar al pasado; una vez emitido el nuevo token, esa Query ya cumplió su función y no le sirve a ningún paso posterior. Las Keys y Values, en cambio, son el catálogo que todos los tokens futuros van a seguir consultando. (§5)
6. Un modelo te devuelve la misma frase repetida cinco veces. ¿Qué perillas mirás primero y en qué dirección?
Primero temperature: si está en 0 o muy baja, el modelo siempre elige el token más probable y un bucle se retroalimenta. Subirla a ≈0,7 con top_p ≈0,9 suele alcanzar; si persiste, repetition_penalty ≈1,1. Ir más allá de 1,2–1,3 en la penalización empieza a romper el texto. (§5)
7. Fine-tuneaste BERT con datos propios, sin sesgo de género. ¿Por qué el ejemplo de [MASK] sigue dando "nurse" para "woman"?
Porque el fine-tune ajusta (o agrega) una capa encima de los pesos preentrenados, y el sesgo vive en esos pesos, aprendidos de Wikipedia y BookCorpus. Las mitigaciones reales operan en otro nivel: alineación post-entrenamiento, RAG para anclar respuestas a documentos, guardrails y verificación explícita en la aplicación. (§6)
10. Para practicar
✏️ Ejercicio 1 — max_length vs. max_new_tokens, medido
Corré el mismo prompt con las dos perillas y contá tokens. Corre en CPU (el modelo pesa ~700 MB la primera vez).
Deberías ver que con max_length=30 la respuesta es más corta que con max_new_tokens=30,
y que la diferencia es exactamente la longitud del prompt en tokens.
from transformers import pipeline, AutoTokenizer
model_id = "HuggingFaceTB/SmolLM2-360M"
tok = AutoTokenizer.from_pretrained(model_id)
gen = pipeline("text-generation", model=model_id)
prompt = "En este curso vamos a aprender a usar transformers para"
print("tokens del prompt:", len(tok(prompt)["input_ids"]))
for kwargs in ({"max_length": 30}, {"max_new_tokens": 30}):
salida = gen(prompt, do_sample=False, **kwargs)[0]["generated_text"]
print(kwargs, "→", len(tok(salida)["input_ids"]), "tokens en total")
Pista / qué esperar
Con max_length=30 el total da 30 (o menos, si el modelo emite fin de secuencia antes); con max_new_tokens=30 da prompt + 30. Probá después con do_sample=True, temperature=0.7, top_p=0.9 y corré tres veces: la salida cambia cada vez — con do_sample=False, nunca.
✏️ Ejercicio 2 — el softmax del ejemplo, y qué le hace la temperatura
Reproducí la tabla de §3 en NumPy y después dividí los puntajes por distintas temperaturas. Solo NumPy, sin modelos.
import numpy as np
def softmax(x):
e = np.exp(x - x.max())
return e / e.sum()
puntajes = np.array([2.0, 4.0, 1.0]) # banco, sentamos, plata
print(softmax(puntajes).round(3)) # ≈ [0.114 0.844 0.042]
for T in (0.2, 0.5, 1.0, 2.0, 5.0):
print(T, softmax(puntajes / T).round(3))
Pista / qué esperar
Con T = 0,2 el peso de "sentamos" se va a ≈1,0 (esto es lo que hace temperature baja al muestrear: casi greedy). Con T = 5 los tres pesos se acercan a 0,33 — la distribución se aplana y cualquier token puede salir. Es la misma fórmula, con los mismos puntajes; solo cambió el divisor.
✏️ Ejercicio 3 — el KV cache de cualquier modelo, desde su config.json
Cargá solo la configuración (sin pesos) y aplicá la fórmula de §5, capa por capa: en Gemma 4 el número de
cabezas KV, el head_dim y la ventana cambian entre capas locales y globales. Compará cuánto
ocupa el contexto en un modelo homogéneo y en Gemma 4.
from transformers import AutoConfig
def kv_cache_bytes(model_id, contexto, bytes_por_valor=2):
c = AutoConfig.from_pretrained(model_id).get_text_config() # en multimodales, el config del texto
capas = c.num_hidden_layers
tipos = getattr(c, "layer_types", None) or ["full_attention"] * capas
ventana = getattr(c, "sliding_window", None) or contexto
k_eq_v = getattr(c, "attention_k_eq_v", False) # Gemma 4: las globales guardan solo K
compartidas = getattr(c, "num_kv_shared_layers", 0) or 0 # Gemma 4 E2B/E4B: las últimas N no guardan
total = 0
for i in range(capas - compartidas):
lc = c.per_layer_config[i] # kv_heads y head_dim pueden variar por capa
kv_heads = getattr(lc, "num_key_value_heads", None) or lc.num_attention_heads
head_dim = getattr(lc, "head_dim", None) or lc.hidden_size // lc.num_attention_heads
if tipos[i] == "sliding_attention":
total += 2 * kv_heads * head_dim * bytes_por_valor * min(contexto, ventana)
else:
total += (1 if k_eq_v else 2) * kv_heads * head_dim * bytes_por_valor * contexto
return total
for m in ["HuggingFaceTB/SmolLM2-360M", "Qwen/Qwen2.5-0.5B", "google/gemma-4-E2B-it", "google/gemma-4-31B-it"]:
print(f"{m:28s}", " | ".join(f"{kv_cache_bytes(m, n) / 2**30:5.2f} GiB @ {n // 1024}K" for n in (8192, 32768, 131072)))
Pista / qué esperar
Salida en transformers 5.16 (los configs se descargan solos, sin pesos): SmolLM2-360M 0,31 / 1,25 / 5,00 GiB; Qwen2.5-0.5B 0,09 / 0,38 / 1,50; Gemma 4 E2B 0,05 / 0,19 / 0,76; Gemma 4 31B 1,09 / 2,03 / 5,78 GiB a 8K / 32K / 128K. Cuando num_key_value_heads es menor que num_attention_heads el modelo usa GQA y el cache baja en proporción. Y fijate por qué el 31B, con 60 capas y 16 cabezas KV, ocupa menos que SmolLM2 a 128K: 50 de sus capas guardan solo 1024 tokens. Si leés c.num_key_value_heads directo en el 31B, transformers 5.16 lanza AmbiguousGlobalPerLayerAttributeError: desde esa versión esos dos atributos son por capa, que es exactamente el supuesto que rompió el backend de tus fixes de vLLM.
11. Fuentes y para seguir
- Hugging Face LLM Course — Chapter 1: Transformer models — el capítulo original; leelo después de este para ver el mismo mapa con el énfasis de 2022.
- Attention Is All You Need (Vaswani et al., 2017) — el paper del transformer; la sección 3.2 es la atención de producto punto escalado que seguiste a mano en §3.
- The Illustrated Transformer (Jay Alammar) — la mejor explicación visual de Q/K/V y multi-head; ideal si el ejemplo de §3 te dejó con ganas de ver las matrices.
- GQA: Training Generalized Multi-Query Transformer Models from Multi-Head Checkpoints (Ainslie et al., 2023) — de dónde sale el factor kv_heads de la cuenta de §5.
- The Curious Case of Neural Text Degeneration (Holtzman et al., 2019) — el paper que introdujo nucleus sampling (top-p) y muestra por qué el greedy entra en bucle.
- Transformers docs — Generation strategies — la referencia de
generate(): cada perilla de §5 con su nombre exacto de parámetro.