⚡ Inferencia
Cómo un LLM genera texto paso a paso, qué optimizaciones existen y a qué velocidad puedes esperar que funcione en tu hardware.
Un token cada vez
Un LLM genera un token cada vez. No es como una imagen que aparece entera. Es como alguien que escribe letra por letra: cada nueva letra depende de todo lo que ha escrito antes.
Cada iteración = forward pass completo del modelo.
El coste O(N²)
Sin ninguna optimización:
Paso 1: Forward([t1, t2, t3, t4, t5]) → t6
Paso 2: Forward([t1, t2, t3, t4, t5, t6]) → t7
...
Paso N: Forward([t1 ... t_{M+N}]) → t_{M+N+1}
Donde M = tokens de entrada, N = tokens de salida.
FLOPs totales ≈ Σ_{i=1}^{N} (M + i) × O(d_model²)
≈ N × (M + N/2) × O(d_model²)
≈ O(N × (M+N)) ← CUADRÁTICO en tokens generados 💡 Ejemplo: M=50 tokens de prompt, N=100 tokens generados, d_model=4096. FLOPs totales = 100 × (50+50) × 4096² = 167 GFLOP. Pero el 98% del tiempo es mover memoria, no computar. Tiempo real ≈ 14 GB (pesos) / 1008 GB/s × 100 pasos ≈ 1.4 segundos.
Speculative Decoding
Técnica para acelerar el bucle autoregresivo usando un modelo pequeño (draft) para generar K tokens rápidamente, luego el modelo grande (target) los verifica en paralelo.
1. Draft (modelo pequeño, rápido): genera K tokens especulativos
2. Verify (modelo grande): procesa los K tokens en UN forward pass
(paralelo, porque la atención puede ver todos a la vez)
3. Si todos son aceptados → ahorro de K-1 forward passes
Si alguno es rechazado → retroceder y regenerar
Ahorro típico: 2-3× en velocidad. La optimización que lo cambia todo
El problema: en cada paso del bucle, recalculamos las mismas cosas una y otra vez. Los tokens del prompt original no cambian, pero los procesamos cada vez.
La solución: guardar (cachear) las matrices K y V de cada token. Cuando generamos un nuevo token, solo procesamos ese token nuevo y reutilizamos lo guardado de los anteriores.
❌ Sin KV Cache
Paso 1: [A, B, C] → D
Paso 2: [A, B, C, D] → E
Paso 3: [A, B, C, D, E] → F
✅ Con KV Cache
Paso 1: [A, B, C] → D → guardar
Paso 2: [D] → E → guardar K_D, V_D
Paso 3: [E] → F → guardar K_E, V_E
Prefill y Decode
Fase 1: Prefill — procesar el prompt (paralelo)
Entrada: [t1, t2, ..., tM] (todos a la vez)
- Forward completo para todos los tokens
- Atención: matriz M×M
- Se genera el primer token de salida
- Se guarda K_i, V_i para todos los tokens en KV Cache
Coste: ~1 forward pass de tamaño M (rápido, ~50 ms para 100 tokens) Fase 2: Decode — generar tokens uno a uno (serial)
Entrada: [t_{M+k}] (solo el nuevo token)
- Forward de UN SOLO token
- Q_{nuevo}: se calcula para el token nuevo
- K_{nuevo}, V_{nuevo}: se calculan y se añaden a la cache
- Atención: Q_{nuevo} mira a [K_cache + K_{nuevo}]
- FFN para el token nuevo
Coste: ~1 forward pass de tamaño 1 (rápido, ~5 ms por token) 🟢 Ahorro: Para M=50, N=100 tokens: sin KV Cache → ~10,050 unidades; con KV Cache → 150 unidades. Ahorro: ~98%.
El coste oculto: VRAM
La KV Cache ocupa mucha VRAM, proporcional a:
KV_Cache_bytes = 2 × n_layers × d_model × n_tokens × bytes_por_param
Para LLaMA 7B, 4096 tokens, FP16:
= 2 × 32 × 4096 × 4096 × 2
= 2.147 GB
Para 128k tokens (LLaMA 3.1 8B):
= 2 × 32 × 4096 × 131072 × 2
= 68.7 GB ← más que el propio modelo (16 GB en FP16) Técnicas de compresión de KV Cache
| Técnica | Factor | Cómo |
|---|---|---|
| GQA | 8× | 8 queries comparten 1 par KV |
| KV Cache quantization | 2× | Guardar KV en INT8 |
| Budget forcing | Variable | Solo guardar los últimos N tokens |
| H2O (Heavy Hitter) | 5× | Solo guardar tokens con alta atención |
| MLA (DeepSeek) | ~10× | Comprimir KV en espacio latente |
Ancho de banda → tokens/s
La velocidad a la que un LLM genera texto depende casi exclusivamente del ancho de banda de memoria de tu GPU, no de lo rápida que sea haciendo cálculos.
💡 Es como una fábrica embotellando agua: la línea de embotellado es rapidísima (TFLOPS), pero el agua llega por una tubería estrecha (ancho de banda). El cuello de botella es la tubería, no la embotelladora.
La fórmula
Tabla para modelo 7B (batch=1):
| GPU | BW (GB/s) | FP16 | Q4_K_M | INT8 |
|---|---|---|---|---|
| RTX 4090 | 1008 | 72 t/s | 240 t/s | 144 t/s |
| RTX 4080 | 716 | 51 t/s | 170 t/s | 102 t/s |
| RTX 4070 | 504 | 36 t/s | 120 t/s | 72 t/s |
| RTX 4060 | 272 | 19 t/s | 65 t/s | 39 t/s |
| RTX 3090 | 936 | 67 t/s | 223 t/s | 134 t/s |
| A100 | 2039 | 146 t/s | 486 t/s | 291 t/s |
| H100 | 3352 | 239 t/s | 798 t/s | 479 t/s |
| DDR5-4800 (CPU) | 50 | 3.6 t/s | 12 t/s | 7.1 t/s |
⚠️ Las velocidades reales suelen ser 60-80% de la teórica por overhead de atención, softmax y normalización. llama.cpp logra ~70-80% de eficiencia.
Qué significan estas velocidades: 240 t/s → respuesta instantánea (como ChatGPT); 50 t/s → cómodo; 10 t/s → visiblemente lento; 2 t/s → doloroso.
¿Por qué el ancho de banda y no los TFLOPS?
Modelo 7B FP16: 14 GB de pesos
GPU RTX 4090: 1008 GB/s, 82 TFLOPS
Por token:
Leer pesos de VRAM: 14 GB / 1008 GB/s = 13.9 ms
Operaciones: 14 GFLOP / 82 TFLOPS = 0.17 ms
→ 98.8% del tiempo es I/O de memoria
→ 1.2% es cómputo real
Si la GPU fuera 10× más rápida en cómputo:
→ Velocidad pasaría de 72 t/s a 73 t/s (casi nada) Batch Size > 1 (throughput)
En servidores de producción, se usa batch size > 1 para atender a varios usuarios simultáneamente:
Batch=1: 14 GB de pesos → 1 usuario → 72 t/s
Batch=4: 14 GB de pesos → 4 usuarios → 4 × 50 t/s = 200 t/s total
Batch=16: 14 GB de pesos → 16 usuarios → 16 × 30 t/s = 480 t/s total Calculadora de Velocidad
Selecciona un modelo, una GPU y una precisión para calcular los tokens/s estimados y ver el cuello de botella.