viernes, 6 de marzo de 2026

Inteligencia Artificial "Low-Cost": Mi Clúster de Inferencia con iGPUs y Vulkan


Inteligencia Artificial "Low-Cost": Mi Clúster de Inferencia con iGPUs y Vulkan

¡Buenas, buenas! Después de una rodadita matutina por las trochas de Pance para despejar la mente y sentir el aire fresco, me quedé pensando en cómo el hardware que a veces subestimamos puede darnos sorpresas increíbles. Hoy no les vengo a hablar de GPUs de miles de dólares que consumen lo mismo que una soldadora eléctrica, ni de granjas de servidores ruidosas que parecen un avión despegando en la sala. Les traigo una historia de aprovechamiento máximo: cómo convertí un puñado de Mini PCs y una laptop veterana en un Clúster de Inferencia de IA plenamente funcional.

Ya dejando a lado tanta cháchara, vamos a lo que nos interesa. La meta era ambiciosa pero clara: correr modelos de lenguaje (LLMs) modernos como Llama 3.1 o Gemma 2 de forma 100% local y privada. El reto no era solo "correrlos", sino hacerlo aprovechando la aceleración por hardware de las gráficas integradas (iGPU) de Intel mediante el backend de Vulkan. Esto demuestra que no siempre necesitas una tarjeta dedicada para tener una experiencia digna; a veces, solo necesitas saberle hablar al silicio que ya tienes.



La Arquitectura: Un "Sancocho" de Nodos con Propósito

Para este proyecto, bautizado en mi inventario como el clúster Vulkan Multi-Nodo, utilicé Proxmox como orquestador base. La clave aquí fue la eficiencia extrema. En lugar de máquinas virtuales pesadas que reservan recursos y desperdician ciclos de reloj, opté por contenedores LXC con Debian 13 (Trixie). Esto reduce el overhead al mínimo, permitiendo que casi la totalidad de la RAM se dedique a cargar los tensores del modelo.

Aquí les presento a los integrantes de este equipo, cada uno con su "personalidad" y rol técnico definido:

  1. Seti (El Líder): Un GMKtec NucBox G3 Plus con el procesador Intel N150. Es la "joya de la corona" gracias a su arquitectura Alder Lake-N. Lo que lo hace especial es que le instalé los drivers Mesa v25.0.7, que manejan Vulkan de forma mucho más madura que las versiones estables de Debian. Es el nodo que rompió todos mis récords internos.

  2. Aurora & Cosmos: Dos guerreros representados por un Beelink MINI S y un S12 (N150 y N100 respectivamente). Estos son el motor constante del clúster. Aunque son procesadores de apenas 6 vatios, su soporte nativo para instrucciones VNNI (Vector Neural Network Instructions) los hace volar en tareas de cuantización, que es básicamente cómo comprimimos los modelos para que quepan en estas cajitas de fósforos.

  3. Yoga (La Veterana): Mi fiel Lenovo Yoga 720 (i7-8550U) heredada de cuando estuve trabajando en NetMidas. Aunque su iGPU UHD 620 ya muestra los años (Año 2018 aprox.) y su arquitectura Coffee Lake se siente cansada frente a los nuevos, la incluí como "línea base". Es fundamental tener un punto de comparación para entender qué tanto hemos avanzado en eficiencia en estos últimos cinco años.

  4. Gateway: Un contenedor Alpine Linux ultra-ligero con Nginx. Su trabajo es ser el "cerebro" que decide a qué nodo enviarle cada petición, actuando como un balanceador de carga de Capa 7 que entiende de tiempos de espera y estados de salud.


El Corazón Técnico: Llama.cpp + Vulkan

La magia no ocurre por arte de obra y gracia de cristo. El motor es llama.cpp, pero compilado específicamente desde la fuente para habilitar el backend de Vulkan (GGML_VULKAN=1). Esto es vital: si usas la versión estándar que viene en muchos instaladores "un solo clic", el proceso recae por defecto en el CPU y la velocidad cae al piso, convirtiendo la charla con la IA en un proceso agónico de un token por minuto.

El Desafío del Passthrough en LXC

Para que los contenedores LXC "vieran" el hardware gráfico, tuve que realizar un passthrough manual de los dispositivos de renderizado. En Proxmox, esto no es solo marcar una casilla en la interfaz gráfica. Hay que editar el archivo de configuración del contenedor (ej. /etc/pve/lxc/100.conf) y añadir las líneas mágicas que permiten el acceso a /dev/dri/renderD128.

Un detalle que me sacó canas y lágrimas: hay que asegurarse de que el GID (Group ID) del grupo render en el host de Proxmox coincida con el del contenedor Debian. Si no, tendrás errores de "Permission Denied" y la iGPU se quedará ahí, mirando sin hacer nada.


Script de Despliegue Parametrizado (llm-server.sh)

Usamos el flag --ngl 99 para indicarle a llama.cpp que intente meter todas las capas del modelo en la memoria de la GPU (VRAM compartida).

#!/bin/bash

################################################################################
# CONTRATO DE EJECUCION - LLAMA.CPP SERVER
#
# DESCRIPCION:
#   Levanta una instancia de llama-server optimizada para iGPU Intel (Vulkan).
#   Detecta automaticamente los hilos fisicos del nodo y aplica offloading
#   total a la GPU para maximizar t/s.
#
# PARAMETROS:
#   -m (Obligatorio) : Nombre del archivo .gguf (debe estar en /root/llama.cpp/models)
#   -p (Opcional)    : Puerto de escucha. Por defecto: 8080
#   -c (Opcional)    : Tamanio del contexto (KV Cache). Por defecto: 4096
#
# FORMA DE EJECUCION:
#   chmod +x llm-server.sh
#   ./llm-server.sh -m llama-3.1-8b.gguf
#   ./llm-server.sh -m gemma-2-2b.gguf -p 8081 -c 2048
#
################################################################################

# --- CONFIGURACION INTERNA ---
LLAMA_BASE_DIR="/root/llama.cpp"
MODEL_DIR="$LLAMA_BASE_DIR/models"
BINARY_PATH="$LLAMA_BASE_DIR/build/bin/llama-server"
DEFAULT_CTX=4096
DEFAULT_PORT=8080
LOG_FILE="$LLAMA_BASE_DIR/llama_server.log"

usage() {
    echo "❌ Error de sintaxis."
    echo "Uso: $0 -m <modelo.gguf> [-p <puerto>] [-c <contexto>]"
    exit 1
}

# --- PARSEO DE ARGUMENTOS ---
while getopts "m:p:c:" opt; do
    case $opt in
        m) MODEL_FILE=$OPTARG ;;
        p) PORT=$OPTARG ;;
        c) CTX=$OPTARG ;;
        *) usage ;;
    esac
done

if [ -z "$MODEL_FILE" ]; then usage; fi

FINAL_PORT=${PORT:-$DEFAULT_PORT}
FINAL_CTX=${CTX:-$DEFAULT_CTX}
MODEL_PATH="$MODEL_DIR/$MODEL_FILE"

# Verificacion de existencia del binario y modelo
if [ ! -f "$BINARY_PATH" ]; then
    echo "❌ Error: No se encuentra el binario en $BINARY_PATH"
    exit 1
fi

if [ ! -f "$MODEL_PATH" ]; then
    echo "❌ Error: El modelo $MODEL_FILE no existe en $MODEL_DIR"
    exit 1
fi

# Deteccion de hilos fisicos (Optimizando para Alder Lake-N / Coffee Lake)
THREADS=$(lscpu | grep "^Core(s) per socket:" | awk '{print $4}')

echo "--------------------------------------------------------"
echo "🖥️  NODE: $(hostname) | 📍 EXEC FROM: $(pwd)"
echo "🚀 INICIANDO: $MODEL_FILE"
echo "📡 PUERTO: $FINAL_PORT | 🧠 CTX: $FINAL_CTX | 🧵 THREADS: $THREADS"
echo "📄 LOG FILE: $LOG_FILE"
echo "--------------------------------------------------------"

# --- EJECUCION ---
# Cambiamos al directorio base para que los recursos relativos del binario funcionen
cd "$LLAMA_BASE_DIR" || exit

# Ejecucion del servidor LLM Llama.cpp
./build/bin/llama-server \
    -m "$MODEL_PATH" \
    --host 0.0.0.0 \
    --port "$FINAL_PORT" \
    --ctx-size "$FINAL_CTX" \
    --n-gpu-layers 99 \
    --flash-attn on \
    --threads "$THREADS" \
    --cont-batching \
    --parallel 2 \
    --slots \
    2>&1 | tee "$LOG_FILE"


Configuración del Servicio (Systemd) para Disponibilidad 24/7

Para que el sistema sea robusto, configuré cada nodo para que el servidor de inferencia se levante automáticamente.

[Unit]
Description=Llama.cpp API Server (Cluster Node)
After=network.target

[Service]
Type=simple
User=root
WorkingDirectory=/root/llama.cpp
# Parametros: -m modelo, -p puerto (opcional), -c contexto (opcional)
ExecStart=/root/llm-server.sh -m gemma-2-2b-q8_0.gguf
Restart=always
RestartSec=5
StandardOutput=append:/root/llama.cpp/llama_server.log
StandardError=inherit
# Acceso a Librerias y Dispositivos iGPU
Environment=LD_LIBRARY_PATH=/usr/local/lib
Environment=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
DeviceAllow=/dev/dri/renderD128 rw
DeviceAllow=/dev/dri/card0 rw

[Install]
WantedBy=multi-user.target


El Balanceador: Nginx como Director de Orquesta

Aquí es donde el "sancocho" se vuelve un clúster de verdad. El Gateway centraliza las peticiones. Si Seti está procesando una consulta larga (como resumir un PDF de 50 páginas), Nginx detecta que el nodo está ocupado y redirige la siguiente pregunta a Aurora o Cosmos.

Implementé una configuración de backup inteligente. La Yoga, al ser la más lenta y ruidosa con sus ventiladores, solo entra en acción si los tres Mini PCs están saturados. Es como tener un ciclista veterano en el equipo: solo lo llamas cuando el terreno se pone realmente feo y necesitas todas las piernas disponibles.

# Configuracion del Cluster de Inferencia LLM
upstream llm_backend {
    # 'least_conn' es critico dado que tienes hardware heterogeneo (Yoga vs Seti)
    least_conn;

    server 10.10.1.51:8080; # Yoga
    server 10.10.1.52:8080; # Aurora
    server 10.10.1.53:8080; # Cosmos
    server 10.10.1.54:8080; # Seti

    # Mantiene hasta 32 conexiones abiertas con los backends para reducir latencia
    keepalive 32;
}

server {
    listen 80;
    server_name _; # Responde a cualquier IP que llegue al contenedor

    # Tamanio maximo de prompt/archivo (64MB es seguro para la mayoria de casos)
    client_max_body_size 64M;

    location / {
        proxy_pass http://llm_backend;
        
        # Cabeceras estandar de Proxy
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # CONFIGURACION PARA LLM STREAMING (CRITICO)
        # Esto permite que los tokens aparezcan uno a uno en la UI
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_cache off;
        proxy_buffering off;
        #proxy_chunked_transfer_encoding on;
        
        # Tiempos de espera (Inferencia puede tardar en modelos grandes)
        proxy_read_timeout 600s;
        proxy_send_timeout 600s;

        # Desactivar compresion para evitar retrasos en el flujo de tokens
        gzip off;
    }
}


Resultados y Conclusiones: La Realidad del Silicio

Después de varias semanas de pruebas intensas, comparando logs mientras me tomaba un tinto cargado, estas son las conclusiones más valiosas para cualquier entusiasta del HomeLab y Self-Hosting:




  • La importancia crítica de los drivers: No me cansaré de decirlo. El nodo Seti logró un pico de 30.9 tokens/s en la fase de "prefill" (cuando la IA lee y procesa tu pregunta) con el modelo Gemma-2 2B. Esto es una bestialidad. Comparado con los ~4.5 t/s de la Yoga, hablamos de una eficiencia casi 7 veces superior en un chip que consume menos que una bombilla LED. Los drivers Mesa v25 son, sin duda, el ingrediente secreto.





  • El Cuello de Botella de la RAM (Single Channel): Aquí está la cruda realidad que no te cuentan en los folletos, DDR4 @ 3200MHz. Al ser Mini PCs económicas con memoria de un solo canal, el bus de datos se satura rápidamente. Aunque la iGPU Xe sea potente, la velocidad de generación final (cuando la IA ya te está respondiendo) se estanca en unos ~2.5 t/s para modelos de 8B. No es para escribir una novela en cinco segundos, pero para un chat de soporte técnico o resúmenes, va sobrado.




  • VNNI y el Salto Generacional: La arquitectura Alder Lake-N marca un hito. Estas instrucciones dedicadas para redes neuronales permiten que estos procesadores "baratos" superen a procesadores i7 de generaciones anteriores que costaban el triple. Es la democratización de la inferencia.


Finalmente, integré todo con Open WebUI corriendo en Docker. Es la "cara bonita" del proyecto, con una interfaz impecable que se conecta al Gateway de Nginx y me permite elegir modelos o crear documentos colaborativos con la IA. Todo esto con la tranquilidad de que mis datos no están alimentando a ninguna corporación; se quedan en mi red, protegidos por mis propios túneles y mi firewall.




Espero que este recorrido por las entrañas de mi infraestructura les sirva de inspiración. A veces la solución no es comprar hardware a escala empresarial o de segunda mano que calienta toda la casa, sino entender cómo hacer que lo pequeño y moderno trabaje en equipo.


Un bit más en la web.