Estación de trabajo para LLM local: de 20 a 60 tokens por segundo con dos GPU
Una estación Xeon de segunda mano con dos RTX 5060 Ti, Qwen 27B, paralelismo de tensores y NCCL: reglas para elegir el hardware, ganancias reales de velocidad y el cálculo de amortización frente a los precios de las API.
Un año de experimentos con inferencia local enseña mucho, sobre todo decepción. Incluso con bastante optimismo, en cuanto hay que hacer trabajo de verdad, gana la suscripción en la nube. Pero últimamente hay presión en sentido contrario. Las suscripciones compran menos trabajo cada mes. Los tiempos en que se podía usar GitHub Copilot o Cursor sin vigilar el presupuesto parecen lejanos, y el dinero que antes duraba un mes puede esfumarse en un día. Eso basta para tomarse en serio una instalación local. Lo que sigue cuenta cómo un stack inestable de 20 tokens por segundo se convirtió en una estación de trabajo fiable que promedia 60 tokens por segundo y alcanza picos bastante más altos.
Hardware que tiene sentido
Internet está lleno de gente que apila varios Mac Pro con cientos de gigabytes de memoria unificada, ejecuta modelos en una Raspberry Pi (eso tiene su lugar, pero ese lugar es la IA en el edge) o presume de un token por minuto exprimido desde un SSD. Eso es un circo. Las reglas reales son más sencillas:
- La capacidad de VRAM va primero. El modelo y el contexto tienen que caber en la VRAM, y 32 GB o 48 GB son hoy los puntos óptimos asequibles.
- Las GPU adicionales sirven sobre todo para que quepa más, no para ganar velocidad. Una segunda tarjeta añade VRAM para modelos más grandes y contextos más largos, pero repartir el trabajo añade sobrecarga de comunicación, así que espera una ligera ralentización en lugar de una aceleración.
- Con varias tarjetas, la topología importa. Busca conexiones entre GPU con gran ancho de banda y baja latencia, idealmente detrás de un mismo switch PCIe o al menos mediante líneas conectadas a la CPU. Una ruta a través del chipset es mucho peor, sobre todo con paralelismo de tensores.
- La alimentación y la refrigeración forman parte del diseño. Dos o tres GPU consumen mucha energía y vuelcan otro tanto de calor en la caja, así que importan la potencia de la fuente, el flujo de aire, la separación entre ranuras y el TDP de las tarjetas.
El segundo punto es difícil de cumplir en placas de consumo. Dos ranuras PCIe sobre el papel no garantizan una buena ruta entre las tarjetas, y las placas que la ofrecen no son baratas.
La alternativa son las estaciones de trabajo empresariales de segunda mano. Muchas vienen con CPU Xeon, montones de líneas PCIe y fuentes de serie de 1 kW o más, porque se construyeron para varias GPU profesionales. Meter dos o tres tarjetas es lo normal, y a menudo sale mucho más barato que una plataforma de consumo moderna comparable. Hay concesiones: DDR4 antigua, PCIe Gen4 o incluso Gen3 y CPU con varias generaciones de retraso.
La inferencia es muy distinta de los juegos, pero las pruebas publicadas de escalado PCIe para la RTX 5090 muestran que incluso las GPU muy rápidas pierden sorprendentemente poco rendimiento con un ancho de banda reducido, y eso bastó para animarse a probar. Al fin y al cabo, casi todo lo decide la economía.
La elección fueron dos NVIDIA RTX 5060 Ti de 16 GB, el camino más barato hacia 32 GB de VRAM con una eficiencia energética excelente, en una estación Xeon por las razones anteriores. Una tarjeta no cabía físicamente, aunque las ranuras y el reparto de líneas encajaban, así que quedó fijada a la caja con un riser PCIe y una brida. Llamémoslo ingeniería polaca de bridas.
Modelos que caben
Este hardware mueve una lista corta de modelos, cada uno con una ventana de contexto de 160k a 256k con la caché KV cuantizada. Dos de Google: gemma-4-26b-a4b y su variante QAT. Dos de Qwen: qwen3.6-35b-a3b y qwen3.6-27b en q4_k_s.
El caballo de batalla diario pasó a ser Qwen de 27B, y no solo por preferencia personal. Los rankings y benchmarks pueden engañar, pero si se repasan unos cuantos destacan dos cosas. Pocos modelos funcionan en un hardware tan modesto, y Qwen lidera esa categoría con clara ventaja. Además, es uno de los pocos que pueden competir con modelos frontera pequeños, de los que ya han demostrado que cumplen de forma fiable tareas pequeñas y bien definidas.
Dado el tamaño de contexto y sus capacidades, lo mejor es usarlo en una orquestación de agentes junto a un modelo en la nube más grande que planifica el trabajo y reparte tareas concretas. La programación totalmente autónoma, o incluso parcialmente autónoma, todavía está fuera del alcance de la inferencia local por sí sola. En cambio, un esquema híbrido da resultados realmente buenos.
El software y por qué el primer intento fue lento
Primero llegó LM Studio, porque es sencillo. Es excelente para experimentar y entender las distintas opciones de llama.cpp y de los modelos. También ayuda a depurar la capa de integración entre las herramientas que usan los modelos expuestos. No siempre es evidente si los ajustes pasados a través de algo como OpenCode llegan realmente a las peticiones, y LM Studio permite verlos, encontrar huecos y corregirlos.
Pero con Qwen3.6-27B promediaba unos 20 tokens por segundo incluso con flash attention activado, y la estabilidad flojeaba: aparecían timeouts extraños una y otra vez.
Los dos cambios que funcionaron
Paralelismo de tensores
Desde entonces, el paralelismo de tensores con CUDA ha llegado a LM Studio, según el registro de cambios de la 0.4.15, pero no siempre fue así, y el runtime precompilado que se probó aquí seguía dejando rendimiento sin aprovechar. Primero, ¿qué es?
El enfoque tradicional con varias GPU pone algunas capas en la GPU 0, otras en la GPU 1 y pasa las activaciones de una a otra:
Model
│
┌────────┴────────┐
│ │
GPU 0 GPU 1
layers 0–N layers N–end
│ │
└───────→─────────┘
Esto permite ejecutar un modelo que no cabe en una sola tarjeta, y nada más. Para un único flujo, las dos GPU nunca trabajan juntas en la misma capa. El trabajo avanza en cadena: la GPU 0 procesa una parte de las capas, las activaciones se trasladan y la GPU 1 termina el resto.
El paralelismo de tensores, en cambio, reparte el cálculo de cada capa entre las GPU:
One layer
│
┌───────┴───────┐
│ │
GPU 0 GPU 1
part of tensor part of tensor
│ │
└───────┬───────┘
│
next layer
La diferencia está descrita en la guía de varias GPU del repositorio de llama.cpp. El paralelismo de pipeline, el modo layer, coloca capas distintas en GPU distintas y hace pasar los tokens de forma secuencial. Minimiza el tráfico entre tarjetas, pero para escalar necesita muchos tokens en proceso a la vez. El paralelismo de tensores, el modo tensor, divide cada capa entre las GPU y ejecuta varias reducciones entre tarjetas por capa. Puede paralelizar cualquier carga, pero depende mucho de la velocidad de la interconexión. El modo pipeline maximiza el rendimiento por lotes y el modo tensor minimiza la latencia.
Con ambas GPU trabajando en la misma capa, Qwen3.6-27B llegó a unos 50 tokens por segundo. Pero eso crea un problema nuevo. Si dos GPU procesan juntas cada capa, tienen que comunicarse constantemente.
NCCL
NCCL de NVIDIA, la Collective Communications Library, se creó precisamente para este tipo de intercambio entre GPU. Operaciones como AllReduce son la base del trabajo distribuido en GPU, y sin una capa colectiva eficiente el paralelismo de tensores puede pasar demasiado tiempo moviendo datos en lugar de calculando.
La sorpresa fue que, en el runtime precompilado de llama.cpp de la versión de LM Studio probada, el soporte de NCCL estaba desactivado. El paralelismo de tensores no necesita NCCL obligatoriamente, porque llama.cpp puede ejecutar las reducciones necesarias con su propio código, pero NCCL ofrece un backend optimizado para ese tráfico.
Esto es lo que ocurre. Con paralelismo de tensores, las GPU intercambian resultados intermedios, por ejemplo tras una parte de una multiplicación de matrices:
Matrix operation
│
┌─────────┴─────────┐
│ │
GPU 0 GPU 1
computes part computes part
│ │
└─────────┬─────────┘
│
AllReduce
│
combined result
AllReduce combina los resultados parciales de todas las GPU y devuelve el resultado común a cada una. llama.cpp tiene su propia implementación, así que la opción integrada funciona. Aquí, el AllReduce de NCCL resultó más rápido.
Unas horas después
La parte dolorosa (bibliotecas que faltan en Fedora, compilación, malabares con los parámetros de llama y del modelo) no merece contarse con detalle, en parte porque este rincón del software cambia muy deprisa. El resultado en cifras medias:
- Reparto por capas: unos 20 tokens por segundo
- Paralelismo de tensores con AllReduce integrado: unos 50
- Paralelismo de tensores con NCCL: unos 60
Los picos son más altos. Un log de llama.cpp de una ejecución muestra el procesamiento del prompt a unos 950–970 tokens por segundo y la decodificación a unos 78–84 tokens por segundo:
113.07.960.883 I slot print_timing: id 0 | task 972 | prompt processing, n_tokens = 4810, progress = 0.79, t = 4.96 s / 969.87 tokens per second
113.08.772.504 I slot print_timing: id 0 | task 972 | prompt processing, n_tokens = 5580, progress = 0.92, t = 5.77 s / 966.90 tokens per second
113.09.409.053 I slot print_timing: id 0 | task 972 | prompt processing, n_tokens = 6092, progress = 1.00, t = 6.41 s / 950.75 tokens per second
...
114.19.200.672 I slot print_timing: id 0 | task 972 | n_decoded = 5384, tg = 77.34 t/s, tg_3s = 83.57 t/s
114.22.229.460 I slot print_timing: id 0 | task 972 | n_decoded = 5639, tg = 77.63 t/s, tg_3s = 84.19 t/s
114.25.259.925 I slot print_timing: id 0 | task 972 | n_decoded = 5894, tg = 77.89 t/s, tg_3s = 84.15 t/s
114.28.274.967 I slot print_timing: id 0 | task 972 | n_decoded = 6149, tg = 78.15 t/s, tg_3s = 84.58 t/s
114.31.308.233 I slot print_timing: id 0 | task 972 | n_decoded = 6405, tg = 78.38 t/s, tg_3s = 84.40 t/s
114.34.343.320 I slot print_timing: id 0 | task 972 | n_decoded = 6661, tg = 78.59 t/s, tg_3s = 84.35 t/s
114.37.376.671 I slot print_timing: id 0 | task 972 | n_decoded = 6913, tg = 78.75 t/s, tg_3s = 83.08 t/s
114.40.405.502 I slot print_timing: id 0 | task 972 | n_decoded = 7169, tg = 78.94 t/s, tg_3s = 84.52 t/s
114.43.413.731 I slot print_timing: id 0 | task 972 | n_decoded = 7421, tg = 79.09 t/s, tg_3s = 83.77 t/s
114.46.451.228 I slot print_timing: id 0 | task 972 | n_decoded = 7673, tg = 79.21 t/s, tg_3s = 82.96 t/s
Todo esto con Qwen3.6-27B. Gemma-4-26B-QAT puede ir el doble de rápido y, aunque quizá sea menos precisa que Qwen, como asistente se siente casi instantánea.
Las cifras absolutas importan menos que la proporción. Es hardware viejo y de segunda mano que costó unos $1,100, y estos cambios aproximadamente triplicaron el rendimiento inicial.
¿Sale a cuenta?
Partamos de estos supuestos:
- Coste del hardware: $1,100.
- Precios de la API: $0.36 por entrada y $2.80 por salida por millón de tokens para un Qwen 27B alojado en OpenRouter. No es una comparación del todo justa con un modelo local cuantizado, pero hace falta algún supuesto.
- Carga de programación: unos 15 tokens de entrada por cada token de salida, porque al programar se lee más de lo que se escribe.
- Procesamiento del prompt a 700 tokens por segundo.
- Generación de salida a 60 tokens por segundo.
El procesamiento del prompt y la generación van uno detrás de otro. Para 15 tokens de entrada más 1 de salida, el tiempo es 15 / 700 + 1 / 60, unos 0.0381 segundos. Eso equivale a unos 393.75 tokens de entrada y 26.25 de salida por segundo, o alrededor de 1.42M de entrada y 94.5K de salida por hora.
Pasar la misma carga por la API costaría unos $0.51 de entrada (1.4175 por $0.36) y $0.26 de salida (0.0945 por $2.80). Así que la máquina sustituye unos $0.775 de gasto en API por hora de inferencia activa, y el punto de equilibrio es $1,100 entre $0.775, unas 1.420 horas.
Ojo, se trata de horas de inferencia real. Nadie tiene un modelo a plena carga ocho horas seguidas, así que la amortización en tiempo de calendario es mucho más larga. Y la electricidad, el mantenimiento, las averías y los repuestos ni siquiera están incluidos.
Entonces, ¿sale a cuenta? Cada uno tendrá que hacer ese cálculo. Para un asistente de IA sencillo, la economía no convence, y el plazo de amortización es tan largo que la API puede resultar simplemente más práctica. Pero si se añaden orquestación de agentes, tareas sin supervisión y pipelines, el panorama se invierte: la inferencia local de pronto parece mucho más sensata. Aquí, los pipelines semiautónomos con pocos puntos de decisión mantienen la máquina ocupada unas 12 horas al día, y esa cifra sigue creciendo.