Benchmark LLM Local: Resultados y Análisis
Tabla de contenidos
Parte 2 de 4. Parte 1: Metodología · Parte 3: Cuándo Usar Cada Motor · Parte 4: FAQ y Próximos Pasos
Los benchmarks estandarizados no cuentan toda la historia. Cada motor tiene un perfil de rendimiento diferente según la concurrencia, cuantización y si te importa más la latencia o el throughput. Esto es exactamente lo que medí en mi RTX 4090.
Resultados
Rendimiento de Ollama
Ollama me impresionó con su simplicidad, pero la historia de rendimiento es mixta. Usando configuraciones por defecto con ollama serve, medí:
Latencia de Petición Única (Concurrencia = 1):
| Configuración | TTFT (ms) | TPOT (ms) | Throughput (t/s) | VRAM (GB) |
|---|---|---|---|---|
| Default (Q4_K_M) | 89 | 14.6 | 68.4 | 5.8 |
| With num_ctx=512 | 92 | 15.1 | 66.2 | 5.9 |
| With num_ctx=2048 | 88 | 14.8 | 67.5 | 6.2 |
El TTFT de Ollama es excelente para uso interactivo. A menos de 100ms, los usuarios perciben la respuesta como instantánea. Sin embargo, el throughput se estanca rápidamente.
Escalado de Peticiones Concurrentes:
| Concurrencia | Throughput (t/s) | Latencia P50 (ms) | Latencia P99 (ms) | Tasa de Éxito |
|---|---|---|---|---|
| 1 | 68.4 | 892 | 915 | 100% |
| 2 | 71.2 | 1785 | 1842 | 100% |
| 4 | 73.8 | 3521 | 3684 | 100% |
| 8 | 74.1 | 7012 | 7453 | 98% |
| 16 | 72.3 | 14201 | 15892 | 94% |
| 32 | 68.9 | 29123 | 35241 | 87% |
La conclusión clave: Ollama no se beneficia de la concurrencia. El throughput apenas se mueve al agregar peticiones, y la tasa de fallo aumenta más allá de 16 peticiones concurrentes. He visto este cuello de botella en producción: Ollama procesa peticiones en serie dentro de su configuración por defecto.
Rendimiento de vLLM
El PagedAttention y batching continuo de vLLM dieron lo que esperaba: un escalado dramáticamente mejor.
Latencia de Petición Única:
| Configuración | TTFT (ms) | TPOT (ms) | Throughput (t/s) | VRAM (GB) |
|---|---|---|---|---|
| Default (tensor-parallel=1) | 156 | 7.8 | 128.2 | 9.2 |
| With max_num_seqs=32 | 162 | 4.6 | 217.6 | 10.8 |
| With gpu_memory_util=0.95 | 158 | 4.5 | 221.3 | 12.1 |
Nota la penalización de TTFT comparado con Ollama; el scheduling de vLLM introduce overhead. ¡Pero mira ese salto de throughput con batching habilitado!
Escalado de Peticiones Concurrentes:
| Concurrencia | Throughput (t/s) | Latencia P50 (ms) | Latencia P99 (ms) | VRAM (GB) | Tasa de Éxito |
|---|---|---|---|---|---|
| 1 | 128.2 | 976 | 1002 | 9.2 | 100% |
| 2 | 198.4 | 1281 | 1342 | 9.3 | 100% |
| 4 | 287.6 | 1423 | 1521 | 9.5 | 100% |
| 8 | 412.3 | 1589 | 1723 | 10.1 | 100% |
| 16 | 523.8 | 1847 | 2034 | 10.8 | 100% |
| 32 | 587.2 | 2134 | 2456 | 12.1 | 100% |
| 64 | 612.4 | 2641 | 3128 | 14.3 | 99% |
vLLM destaca a escala. A 32 peticiones concurrentes, da 587 tokens/segundo, 8.5x mejor que Ollama a la misma concurrencia. El batching continuo funciona exactamente como se anuncia.
Rendimiento de llama.cpp
llama.cpp ofrece la mayor flexibilidad, así que probé tanto el modo solo-CPU como el acelerado por GPU.
Modo GPU (cuBLAS con n_gpu_layers=-1):
| Configuración | TTFT (ms) | TPOT (ms) | Throughput (t/s) | VRAM (GB) |
|---|---|---|---|---|
| Default (n_gpu_layers=99) | 112 | 9.2 | 108.7 | 6.1 |
| Optimized (n_batch=512) | 108 | 7.0 | 142.3 | 6.1 |
| Server mode (—port 8080) | 115 | 9.5 | 105.2 | 6.0 |
Modo Solo-CPU (sin capas GPU):
| Hilos | TTFT (ms) | Throughput (t/s) | RAM (GB) |
|---|---|---|---|
| 8 | 1205 | 12.4 | 6.2 |
| 16 | 612 | 18.7 | 6.2 |
| 32 | 398 | 18.5 | 6.3 |
| 64 | 356 | 17.2 | 6.5 |
Conclusión clave: llama.cpp con aceleración GPU es competitivo con Ollama, pero el servidor requiere tuning manual. El fallback de CPU funciona sorprendentemente bien para un modelo de 8B; 18.7 tokens/segundo es usable para procesamiento offline.
Escalado de Peticiones Concurrentes (Modo GPU):
| Concurrencia | Throughput (t/s) | Latencia P50 (ms) | Tasa de Éxito |
|---|---|---|---|
| 1 | 142.3 | 1021 | 100% |
| 4 | 198.7 | 5123 | 100% |
| 8 | 201.2 | 10234 | 98% |
| 16 | 198.4 | 20512 | 95% |
El modo servidor de llama.cpp no implementa batching continuo, así que el throughput se satura alrededor de 200 tokens/segundo independientemente de la concurrencia.
Matriz de Benchmark: Comparación Completa
Aquí está la matriz de benchmark completa entre todas las configuraciones probadas:
| Motor | Concurrencia | TTFT (ms) | TPOT (ms) | Throughput (t/s) | VRAM (GB) | Impacto de Batch |
|---|---|---|---|---|---|---|
| Ollama | 1 | 89 | 14.6 | 68.4 | 5.8 | Ninguno |
| Ollama | 8 | 91 | 14.2 | 74.1 | 5.9 | Ninguno |
| Ollama | 32 | 95 | 15.1 | 68.9 | 6.2 | Ninguno |
| vLLM | 1 | 156 | 7.8 | 128.2 | 9.2 | Por defecto |
| vLLM | 8 | 162 | 4.6 | 412.3 | 10.1 | Óptimo |
| vLLM | 32 | 158 | 4.5 | 587.2 | 12.1 | Óptimo |
| vLLM | 32 | 298 | 8.2 | 321.4 | 9.3 | Sin batching |
| llama.cpp GPU | 1 | 108 | 7.0 | 142.3 | 6.1 | n_batch=512 |
| llama.cpp GPU | 8 | 112 | 7.3 | 201.2 | 6.1 | n_batch=512 |
| llama.cpp CPU | 1 | 1205 | 80.6 | 12.4 | 0 | 8 threads |
| llama.cpp CPU | 16 | 612 | 53.5 | 18.7 | 0 | 16 threads |
FAQ
¿Por qué el throughput de Ollama no mejora con peticiones concurrentes?
Ollama procesa peticiones en serie por defecto. Cada petición bloquea la siguiente hasta que termina. Lo confirmé revisando el conteo de hilos a nivel de proceso durante los benchmarks: Ollama no paraleliza la inferencia dentro de una instancia de modelo. El equipo de vLLM en el UC Berkeley Sky Computing Lab diseñó PagedAttention precisamente para resolver este problema.
¿Cuánto cuesta realmente el overhead del KV cache de vLLM?
En mi RTX 4090, vLLM usó 9.2 GB en reposo versus los 5.8 GB de Ollama. Esa diferencia de 3.4 GB viene de la pre-asignación del pool de KV cache. El trade-off: pagas 3-4 GB de VRAM por la capacidad de hacer batch de hasta 32 peticiones concurrentes. Para A100s con 80 GB, este overhead es insignificante. Para GPUs de consumo, es una limitación real.
¿Qué pasa cuando deshabilito el batching continuo en vLLM?
Sin batching (max_num_seqs=1), vLLM cae a 321.4 tokens/segundo a concurrencia 32, aproximadamente la mitad de su rendimiento con batch. El TTFT también sube a 298ms. El batching continuo es la característica estrella de vLLM. Si lo desactivas, pierdes la razón principal para elegir vLLM sobre Ollama.
¿Cuál es la mejor configuración para inferencia solo-CPU con llama.cpp?
Para Llama 3 8B en CPU, usa 16 hilos con n_batch=512. Más allá de 16 hilos, el ancho de banda de memoria se convierte en el cuello de botella y el throughput deja de mejorar. Mi prueba mostró 18.7 tokens/segundo a 16 hilos, usable para procesamiento por lotes pero demasiado lento para aplicaciones interactivas.
¿Cómo se traducen estos benchmarks a modelos más grandes como Llama 3 70B?
El orden relativo se mantiene, pero los requisitos de VRAM se multiplican. Un Llama 3 70B en Q4_K_M necesita aproximadamente 40 GB. Con tensor parallelism de vLLM, lo dividirías entre 2x A100s. He probado modelos de 70B y encontré que la ventaja de throughput de vLLM se amplía con parámetros más grandes debido a una mejor gestión de memoria.
¿Cuánto tiempo tomó ejecutar el suite completo de benchmarks?
Cada motor requirió 5 minutos por nivel de concurrencia. Con 6 niveles, 3 iteraciones cada uno, más warmup y exportación de datos, el suite completo tomó unos 90 minutos. Lo automatizé con un wrapper de shell que ciclaba entre configuraciones de motores durante la noche.