← Todas las recetasReceta · 19 de septiembre de 2025

Dos sparks y un MoE: inferencia distribuida casera

Cómo unimos nuestras dos NVIDIA Spark para correr modelos que no caben en una sola: el setup exl3/vLLM que usamos en El Laboratorio, con sus errores y sus números reales.

Dificultadavanzada
Tiempomedia jornada
IngredientesvLLM · EXL3 · Docker · 2x NVIDIA Spark

La receta más ambiciosa del blog: la que da identidad a El Laboratorio. Dos máquinas, 256GB de memoria unificada sumados, y un objetivo: correr los modelos que en una GPU normal son ciencia ficción.

Los límites que empujaron al cluster

Con 121GB por máquina (en la práctica ~110GB útiles), un modelo de 97GB cuantizado entra justo… pero sin margen para la KV cache ni para el sistema. Contextos largos = swap = sufrimiento. La solución: repartir el modelo entre las dos sparks.

Opción A: Pipeline (capas) con llama.cpp RPC

La forma simple: cada máquina carga una parte de las capas del modelo.

# En la worker (gx10-2): sirve RPC
llama-server --rpc 0.0.0.0:50052 -m nada.gguf --no-webui

# En la head (gx10-1): carga el modelo distribuido
llama-server -m modelo-grande.gguf \
  --rpc 100.64.10.12:50052 \
  --host 0.0.0.0 --port 8080 \
  -ngl 999 --ctx-size 32768

Resultado real: DeepSeek-V3-style MoE de ~97GB (UD-IQ3_XXS) corriendo con:

  • decode: ~27 t/s
  • prefill: ~370 t/s
  • contexto 64K con KV en q8

Más que suficiente para chat y automatizaciones; no para servir 20 usuarios.

Opción B: vLLM con tensor parallel (misma máquina)

Si el modelo cabe en UNA spark pero quieres más throughput, --tensor-parallel-size no aplica (una GPU unificada). Pero --data-parallel para servir varias copias sí:

# Dos instancias del mismo modelo en la misma máquina, puertos distintos
# y un nginx delante balanceando = doble throughput

Opción C: Router de modelos (lo que usamos a diario)

La configuración más útil no es “un modelo gigante”: es un router que decide qué máquina atiende qué:

                    ┌─ gx10-1: modelo grande (MoE 97GB) ─── tareas serias
petición → nginx ───┤
                    └─ gx10-2: modelo pequeño (7-14B) ──── chat, automatización

Con n8n o un mini-script: peticiones simples al worker rápido, complejas al head. Latencia percibida 3x mejor, y el modelo grande no se calienta para responder “¿qué tiempo hace?”.

Números honestos (para que compares)

Setup Modelo Decode Prefill Uso ideal
1 spark, llama.cpp 7B Q4 ~60 t/s ~800 t/s chat fluido
1 spark, llama.cpp 70B Q3 ~8 t/s ~150 t/s paciencia
2 sparks (RPC) MoE 97GB ~27 t/s ~370 t/s el sweet spot ✓
2 sparks (router) 14B + MoE ~45 t/s (small) casa completa ✓✓

(Medidas nuestras con GB10, contexto 32-64K, KV q8. Tu mileage may vary, pero poco.)

Los errores que nos hicieron más listos

  1. RPC por WiFi: 2 t/s y desesperación. El cluster va por ethernet o no va. (Los adapters Tailscale van genial para control, para inferencia: cable.)
  2. Contexto gigante por deporte: 256K de contexto comió toda la memoria unificada de las dos máquinas para una conversación de 10K tokens. KV cache = memoria. Mide antes.
  3. Dos modelos grandes en la misma máquina: OOM con estilo. Un modelo grande por máquina, y el resto cache pequeña.
  4. No monitorizar temperaturas: el throttling silencioso te roba 30% de rendimiento y no te enteras. nvtop o el panel de misión en la dash.

El resultado emocional

No es el cluster más rápido del mundo. Pero es nuestro. Y cuando sirves tu primera respuesta de un modelo de 100GB que vive en tu casa, con tu router casero decidiendo qué cerebro atiende cada petición… se te queda una cara de tonto que no paga nadie.