# Cómo optimizar la inferencia de LLM y bajar tus costos un 80%

Aprendé a reducir el costo de tu inferencia de LLM hasta un 80% usando prompt caching, decodificación especulativa y routing semántico.

*infra · 2026-09-02 · [mauriciosneira.com](https://mauriciosneira.com/blog/optimizacion-costos-inferencia-llm)*

---

Tu bot de soporte B2B recibe miles de mensajes por día y manda cada uno directo a un modelo de frontera. A fin de mes, la factura de los proveedores te liquida el margen operativo. 

En este post te explico cómo bajar esos costos hasta un 80% usando una arquitectura asimétrica: darle a cada consulta el cómputo justo, sin perder calidad ni sumar latencia.

---

## 1. Prompt caching: mecánica interna y ordenamiento estricto de prefijos

Los modelos autoregresivos procesan tokens en dos fases con cuellos de botella de hardware distintos:

1. **Prefill (Compute-bound):** procesa el contexto de entrada en paralelo calculando las matrices de atención Key-Value (KV). 
2. **Decode (Memory-bound):** genera tokens de forma secuencial. Cada paso transfiere todos los pesos del modelo y el KV Cache acumulado desde la memoria VRAM hacia los núcleos de cómputo.

El prompt caching permite reutilizar tensores KV ya computados para secuencias idénticas. Motores modernos como SGLang (mediante RadixAttention, como detallan en el [paper de SGLang de LMSYS](https://lmsys.org/blog/2024-01-17-sglang/)) o vLLM (con [PagedAttention](https://docs.vllm.ai/en/latest/assets/design/paged_attention/value.png)) organizan la memoria KV en árboles de prefijos indexados por bloques de tokens (típicamente de 16 o 32 tokens).

```mermaid
flowchart TD
    A["Request entrante"] --> B["Tokenizador + Hash de Prefijo"]
    B --> C{"Prefijo en Radix Tree (VRAM)"}
    C -- "Cache Hit (Prefijo idéntico)" --> D["Reutilización de tensores KV (Lectura VRAM)"]
    C -- "Cache Miss / Desalojo LRU" --> E["Prefill completo en GPU"]
    D --> F["Prefill solo de tokens dinámicos"]
    E --> G["Decodificación token a token"]
    F --> G
    G --> H["Respuesta al cliente"]
```

### El modelo de costos real

* **En infraestructura propia:** el costo marginal del hit es casi nulo, porque sólo leés VRAM indexada. Pero el cache no es infinito y aplica políticas de desalojo LRU (Least Recently Used). Si tu tráfico supera la capacidad de VRAM reservada para KV Cache, entrás en cache thrashing y la GPU recalcula prefijos todo el tiempo.
* **En APIs de frontera comerciales:** el prefijo cacheado no es gratis. Leerlo suele costar **~10%** del precio normal de input en los tiers actuales (hubo tiers viejos al 25%), con un TTL de entre 5 y 60 minutos. Ojo: escribir el cache la primera vez suele tener un recargo (p. ej. +25% en Anthropic), así que el break-even llega recién si el prefijo se reutiliza varias veces.

### Anatomía de un payload optimizado

Para garantizar el hit rate en tu bot de soporte B2B, armá el payload aislando los bloques estáticos de los dinámicos. Si cambiás un solo byte al inicio del prompt, invalidás todo el árbol de bloques posterior:

```python
# ❌ MAL: Variables dinámicas al inicio invalidan el KV Cache en cada request
bad_messages = [
    {
        "role": "system",
        "content": f"Fecha actual: {datetime.now().isoformat()}. Usuario: {user_id}. Respondé siempre en JSON."
    },
    {"role": "system", "content": SYSTEM_INSTRUCTIONS_AND_SCHEMAS}, # 4k tokens (NUNCA CACHEA)
    {"role": "user", "content": user_query}
]

# ✅ BIEN: Prefijo estático idéntico (Hit > 80%), contexto dinámico al final
optimized_messages = [
    {
        "role": "system", 
        "content": SYSTEM_INSTRUCTIONS_AND_SCHEMAS # Bloque estático e inmutable (Cache Hit)
    },
    {
        "role": "user", 
        "content": f"Metadatos de sesión: [Fecha: {datetime.now().date()}, ID: {user_id}]\nConsulta: {user_query}"
    }
]
```

---

## 2. Decodificación especulativa: aceleración y trade-offs

La decodificación tradicional hace N pasadas hacia adelante (forward passes) para generar N tokens. La decodificación especulativa usa modelos borrador compactos o cabezas de predicción basadas en árboles de tokens para acelerar el proceso en tres pasos:

1. Un modelo borrador (draft model) liviano genera de forma especulativa una secuencia o árbol de K tokens candidatos.
2. El modelo principal evalúa los K tokens en un **único paso de prefill paralelo**.
3. Se aceptan los primeros M tokens (M ≤ K) que coincidan con la distribución del modelo principal. El resto se descarta y el ciclo recomienza.

```
Paso 1 (Draft):      [Token A] -> [Token B] -> [Token C] (3 pasos secuenciales chicos)
Paso 2 (Target):     Evalúa [A, B, C] en 1 forward pass paralelo
Resultado:           Acepta [A, B], rechaza [C]. Generación final: 2 tokens en 1 ciclo.
```

### El trade-off de throughput vs. contención de recursos

La ganancia depende de la tasa de aceptación de tokens (α):

* **Textos estructurados (JSON, SQL, código repetitivo):** α ≈ 75–85%. La latencia total cae a la mitad y el throughput se duplica. En nuestro bot de soporte B2B, esto aplica directo cuando extraemos entidades o devolvemos esquemas fijos.
* **Generación creativa o razonamiento estocástico:** α < 40%. El draft model consume ancho de banda de memoria y cómputo que compite con el modelo base. La latencia por token termina siendo peor que con decodificación autoregresiva simple.

---

## 3. Arquitectura de routing semántico para optimizar la inferencia de LLM

No todas las consultas del bot de soporte necesitan un modelo gigante. Una arquitectura asimétrica reparte la carga en tres niveles de especialización:

```mermaid
flowchart TD
    Req["Request de Usuario"] --> R["Router Semántico Local (Clasificador lineal en CPU)"]
    R -- "Intención determinista" --> T0["Tier 0: Embedding Cache / Regex"]
    R -- "Extracción / Clasificación" --> T1["Tier 1: Modelo Abierto Compacto (Clase 7B-9B FP8)"]
    R -- "Razonamiento Profundo" --> T2["Tier 2: High-Tier Frontier API"]
    
    T1 --> Val{"Validación de Esquema"}
    Val -- "Válido" --> Res["Respuesta al Usuario"]
    Val -- "Inválido / Error" --> Fallback["Fallback a Tier 2"]
    Fallback --> Res
    T0 --> Res
    T2 --> Res
```

### Niveles de ejecución (Tiers)

| Nivel | Infraestructura / Modelo | Latencia p95 | Costo por 1M tokens (promedio) | Tareas asignadas |
| :--- | :--- | :--- | :--- | :--- |
| **Tier 0 (Determinista)** | Redis Vector Store / Reglas léxicas | < 15 ms | $0.00 | FAQs exactas, comandos del sistema, navegación. |
| **Tier 1 (SLM Especializado)** | Modelo abierto (7B–9B) en GPU local (instancias L4/L40S o H100 compartidas) | ~120 - 200 ms | $0.05 - $0.20 | Clasificación, extracción, reformulación de queries, formateo JSON. |
| **Tier 2 (Frontera / Razonamiento)** | High-Tier Frontier API / Modelos MoE gigantes | ~1.5 - 4.0 s | $2.50 - $15.00 | Razonamiento lógico complejo, síntesis de documentos masivos. |

### Cómo implementar el router sin penalizar la latencia

El router no tiene que ser otro LLM. Conviene correrlo en memoria con un clasificador lineal ligero entrenado sobre embeddings (por ejemplo usando un encoder compacto local o SetFit) o reglas semánticas rápidas, una estrategia que ya analicé al explicar [cómo rutear requests entre LLMs](/blog/ruteo-inferencia-system-one). 

Además, la decisión entre [usar modelos generalistas o especializados](/blog/llms-produccion-especializado-generalista) cambia por completo los costos operativos. La latencia de este routing tiene que quedarse entre **5 y 12 ms** en CPU para no degradar el camino crítico.

### Ejemplo ilustrativo (cifras hipotéticas)

Pensemos en nuestro sistema de soporte B2B procesando **~1.2M de requests diarios** (cifras hipotéticas para dimensionar el impacto, no una medición real):

* **Arquitectura original:** 100% de requests dirigidos a un modelo de frontera sin caching estructurado.  
  * *Costo mensual:* del orden de **~$28k USD**, latencia p50 ~**1.8 s**.
* **Arquitectura asimétrica implementada:**
  * **Tier 0 (40% del tráfico):** consultas transaccionales resueltas vía semantic cache y respuestas deterministas. Costo de inferencia: **$0 USD**.
  * **Tier 1 (45% del tráfico):** extracción y resolución de incidentes estándar usando un modelo abierto clase 8B cuantizado en FP8. Costo: **~$380 USD/mes**.
  * **Tier 2 (15% del tráfico):** casos complejos de troubleshooting derivados al modelo de frontera con prefijos fijados en KV Cache (tasa de hit del 78%). Costo: **~$5,400 USD/mes**.
* **Resultado:** el gasto mensual bajaría a **~$5,8k USD (ahorro de ~80%)** y la latencia p50 a un par de cientos de ms.

---

## 4. Cuándo NO optimizar: trade-offs y antipatrones

1. **Cuantización agresiva en tareas de lógica:** pasar pesos a INT4 reduce el consumo de VRAM, pero rompe el seguimiento de esquemas estrictos (tool-calling) y la precisión matemática. Para cargas críticas, yo prefiero usar **FP8 combinado** (pesos, activaciones y KV Cache en formato FP8 E4M3/E5M2 sobre hardware con soporte nativo de Tensor Cores), que mantiene la calidad intacta.
2. **Entrenamiento de adaptadores (LoRA) en etapas tempranas:** si las especificaciones de tu bot de soporte B2B cambian todas las semanas, reentrenar y validar adaptadores te va a costar más que pagar las APIs directas. Primero optimizá arquitectura y routing; el fine-tuning viene después.
3. **Gestión de hardware dedicado sin volumen base:** levantar instancias con aceleradores como H100, H200 o B200 sin asegurar más del 60% de uso promedio en VRAM y compute te va a salir más caro que consumir endpoints serverless, por culpa del hardware ocioso.

---

## Plan de implementación técnica

Si querés bajar el costo de inferencia manteniendo la confiabilidad en tu sistema, seguí estos pasos:

1. **Estandarizar el prefijo de mensajes:** mové esquemas de herramientas, instrucciones del sistema y ejemplos few-shot a un único bloque estático inicial idéntico en bytes en todos los microservicios.
2. **Habilitar FP8 KV Cache en tus motores:** si desplegás modelos abiertos en infraestructura propia (vLLM/SGLang), configurá `--kv-cache-dtype fp8` para duplicar el espacio de contexto concurrente sin perder calidad.
3. **Implementar fallback con presupuesto de latencia:** si el Tier 1 no valida la salida contra el esquema Pydantic, mandá el request a Tier 2. Para que el p99 no se dispare por latencia acumulada, poné un timeout estricto en el modelo chico (ej. 500 ms) antes de cortar y escalar la llamada.
