Logo IES Simarro
PP1-Simarro Centro de excelencia en IA y BD

Manual Dual-Node DGX Spark

Configuración de inferencia distribuida mediante interconexión de 200Gb/s.

SPARK A SPARK B ConnectX-7 RDMA 200 Gb/s
Las configuraciones de un solo nodo solo requieren los pasos 1, 2 y 7 (Token de HuggingFace). La configuración de red y SSH se automatiza mediante los scripts oficiales de NVIDIA de los pasos 4 y 5.

1. Sistema Operativo y Controladores

  • SO: Ubuntu 22.04 LTS o superior.
  • Arquitectura: Grace Blackwell.

Verifica que los controladores de NVIDIA estén operativos:

Bash
nvidia-smi

2. Docker con NVIDIA Container Runtime

Docker debe estar instalado con NVIDIA Container Runtime configurado. Verifica el acceso utilizando CUDA 13 en Ubuntu 22.04:

Bash
docker run --rm --gpus all nvidia/cuda:13.0.1-base-ubuntu22.04 nvidia-smi

3. Cables de Interconexión Aprobados

Para la conexión directa de 200GbE entre los puertos CX-7, NVIDIA recomienda exclusivamente estos modelos:

  • Amphenol: NJAAKK-N911 (QSFP to QSFP112, 400mm).
  • Luxshare: LMTQF022-SD-R (QSFP112 400G DAC, 400mm).
Nota: Los puertos CX-7 de la DGX Spark solo soportan configuración Ethernet.

4. Configuración de Red (Automatizada con Netplan)

NVIDIA proporciona un archivo de configuración para facilitar la asignación de IPs en las interfaces CX-7. Ejecuta esto en ambos nodos:

# Descargar y aplicar configuración de Netplan
Bash
sudo wget -O /etc/netplan/40-cx7.yaml https://github.com/NVIDIA/dgx-spark-playbooks/raw/main/nvidia/connect-two-sparks/assets/cx7-netplan.yaml
sudo chmod 600 /etc/netplan/40-cx7.yaml
sudo netplan apply

5. Script de Descubrimiento y SSH

Este paso identifica automáticamente los sistemas interconectados y configura el SSH sin contraseña. Ejecútalo en ambos nodos:

Bash
# Descargar y ejecutar script de descubrimiento
wget https://github.com/NVIDIA/dgx-spark-playbooks/raw/refs/heads/main/nvidia/connect-two-sparks/assets/discover-sparks
chmod +x discover-sparks
./discover-sparks

6. Verificación Detallada de Interfaces (ibdev2netdev)

Confirma que los puertos CX-7 están en estado "(Up)" tras aplicar la red. Es fundamental verificar la conexión física:

Bash
# Comprobar estado de los puertos de red
ibdev2netdev

Ejemplo de salida detallada:

roceP2p1s0f0 port 1 ==> enP2p1s0f0np0 (Down)
roceP2p1s0f1 port 1 ==> enP2p1s0f1np1 (Up)
rocep1s0f0 port 1 ==> enp1s0f0np0 (Down)
rocep1s0f1 port 1 ==> enp1s0f1np1 (Up)
Importante: Usa una interfaz que aparezca como "(Up)". Puedes ignorar interfaces con prefijo enP2p... y priorizar siempre las que comienzan por enp1.... Si nada aparece como "(Up)", revisa el cable QSFP.

7. Identificación de IPs ConnectX-7 (CX-7)

Localiza la IP de alta velocidad necesaria para la comunicación entre nodos. Ejecuta en ambos servidores:

Bash
# Ver IP de la interfaz ConnectX-7 activa
ip addr show enp1s0f1np1
# Probar latencia de red de alta velocidad
Bash
ping <IP-CX7-DEL-OTRO-NODO>
Nota Crítica Anota estas IPs. Son las que usarás en el Paso 3 (vLLM) como MASTER_ADDR para garantizar los 200Gbps de ancho de banda.

Consulta la documentación adicional si es necesario:

Guía de NVIDIA

8. Configuración del Firewall

Asegúrate de que los siguientes puertos estén abiertos entre ambos nodos:

  • 6379 - Ray GCS
  • 8265 - Ray Dashboard
  • 8000 - vLLM API

Ejemplo de Configuración Práctica

Datos del ejemplo:

  • Nodo A: 169.254.123.147
  • Nodo B: 169.254.214.62
1. En el NODO A (IP ...147)

Debe dar permiso a la IP del B (...62):

Bash (Terminal Nodo A)
# Permitir tráfico entrante DESDE el Nodo B
sudo ufw allow from 169.254.214.62 to any port 6379 proto tcp comment 'Ray GCS desde Nodo B'
sudo ufw allow from 169.254.214.62 to any port 8265 proto tcp comment 'Ray Dashboard desde Nodo B'
sudo ufw allow from 169.254.214.62 to any port 8000 proto tcp comment 'vLLM API desde Nodo B'

# Recargar firewall
sudo ufw reload
2. En el NODO B (IP ...62)

Debe dar permiso a la IP del A (...147):

Bash (Terminal Nodo B)
# Permitir tráfico entrante DESDE el Nodo A
sudo ufw allow from 169.254.123.147 to any port 6379 proto tcp comment 'Ray GCS desde Nodo A'
sudo ufw allow from 169.254.123.147 to any port 8265 proto tcp comment 'Ray Dashboard desde Nodo A'
sudo ufw allow from 169.254.123.147 to any port 8000 proto tcp comment 'vLLM API desde Nodo A'

# Recargar firewall
sudo ufw reload
3. Comprobación final

Verifica que las reglas se han cargado correctamente en ambos nodos:

Bash
sudo ufw status

Deberías ver que el estado es active y que las reglas especifican la IP de origen correcta.

9. Autenticación Hugging Face

Modelos como Llama-3 o Gemma requieren autorización previa:

# Instalar la CLI de Hugging Face (ejecutar en ambos nodos)
Bash
pip install huggingface_hub
# Iniciar sesión en Hugging Face (ejecutar en ambos nodos)
Bash
hf auth login

Alternativamente, puedes establecer el token en tu entorno local:

Config
HF_TOKEN="hf_your_token_here"

NCCL (NVIDIA Collective Communications Library) es fundamental para optimizar la transferencia de datos entre las GPUs de ambos nodos.

Documentación Técnica: Guía oficial para profundizar en NCCL.
Abrir Guía NCCL

Paso 1: Configurar conectividad de red

Asegúrate de haber completado la conexión física mediante cable QSFP y de tener configurado el SSH sin contraseña entre ambos nodos.

Paso 2: Construir NCCL con soporte Blackwell

Ejecuta estos comandos en ambos nodos para compilar NCCL desde el código fuente con soporte para la arquitectura Blackwell:

# Instalar dependencias y clonar repositorio
Bash
sudo apt-get update && sudo apt-get install -y libopenmpi-dev
git clone -b v2.28.9-1 https://github.com/NVIDIA/nccl.git ~/nccl/
# Compilar para Blackwell (sm_121)
Bash
cd ~/nccl/
make -j src.build NVCC_GENCODE="-gencode=arch=compute_121,code=sm_121"
# Configurar variables de entorno
Bash
export CUDA_HOME="/usr/local/cuda"
export MPI_HOME="/usr/lib/aarch64-linux-gnu/openmpi"
export NCCL_HOME="$HOME/nccl/build/"
export LD_LIBRARY_PATH="$NCCL_HOME/lib:$CUDA_HOME/lib64/:$MPI_HOME/lib:$LD_LIBRARY_PATH"

Paso 3: Construir suite de pruebas NCCL

Compila los tests de NCCL en ambos nodos para verificar el rendimiento:

Bash
git clone https://github.com/NVIDIA/nccl-tests.git ~/nccl-tests/
cd ~/nccl-tests/
make MPI=1

Paso 4: Identificar interfaz activa e IPs

Identifica qué puertos de red están activos (Up) utilizando ibdev2netdev e inspecciona su IP:

Bash
ibdev2netdev
ip addr show enp1s0f1np1
Utiliza la interfaz que aparezca como "(Up)". En DGX Spark, suele ser una interfaz que comienza por enp1.
# Ejecutar test de comunicación (reemplaza las IPs con las tuyas)
Bash
# Configurar variables de la interfaz activa
export UCX_NET_DEVICES=enp1s0f1np1
export NCCL_SOCKET_IFNAME=enp1s0f1np1
export OMPI_MCA_btl_tcp_if_include=enp1s0f1np1

# Ejecutar test de ancho de banda estándar
mpirun -np 2 -H :1,:1 \
--mca plm_rsh_agent "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking =no" \
-x LD_LIBRARY_PATH=$LD_LIBRARY_PATH \
$HOME/nccl-tests/build/all_gather_perf

# Ejecutar test con buffer grande para maximizar los 200Gbps
mpirun -np 2 -H :1,:1 \
--mca plm_rsh_agent "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no" \
-x LD_LIBRARY_PATH=$LD_LIBRARY_PATH \
$HOME/nccl-tests/build/all_gather_perf -b 16G -e 16G -f 2

Una vez verificado NCCL, configuraremos vLLM para servir modelos de lenguaje a gran escala aprovechando la potencia de los dos nodos Spark.

Paso 1: Script de despliegue del cluster

Descarga el script que coordina la configuración del cluster Ray en ambos nodos:

Bash
wget https://raw.githubusercontent.com/vllm-project/vllm/refs/heads/main/examples/online_serving/run_cluster.sh
chmod +x run_cluster.sh

Paso 2: Imagen de vLLM desde NVIDIA NGC

Configura Docker y descarga la imagen oficial optimizada:

Bash
docker pull nvcr.io/nvidia/vllm:25.11-py3
export VLLM_IMAGE=nvcr.io/nvidia/vllm:25.11-py3

Paso 3: Iniciar el Nodo Maestro (Ray Head)

Ejecuta esto en el Nodo 1. Este nodo coordinará la inferencia distribuida y servirá el endpoint de la API.

Bash
# En el Nodo 1, iniciar nodo maestro
# Obtener la IP de la interfaz de alta velocidad
# Usa la interfaz que aparece como "(Up)" en ibdev2netdev
export MN_IF_NAME=enp1s0f1np1
export VLLM_HOST_IP=$(ip -4 addr show $MN_IF_NAME | grep -oP '(?<=inet\s)\d+(\.\d+){3}')
echo "Usando interfaz $MN_IF_NAME con IP $VLLM_HOST_IP"

bash run_cluster.sh $VLLM_IMAGE $VLLM_HOST_IP --head ~/.cache/huggingface \
-e VLLM_HOST_IP=$VLLM_HOST_IP \
-e UCX_NET_DEVICES=$MN_IF_NAME \
-e NCCL_SOCKET_IFNAME=$MN_IF_NAME \
-e OMPI_MCA_btl_tcp_if_include=$MN_IF_NAME \
-e GLOO_SOCKET_IFNAME=$MN_IF_NAME \
-e TP_SOCKET_IFNAME=$MN_IF_NAME \
-e RAY_memory_monitor_refresh_ms=0 \
-e MASTER_ADDR=$VLLM_HOST_IP

Paso 4: Iniciar Nodo Trabajador (Ray Worker)

Conecta el Nodo 2 al cluster Ray como nodo trabajador para proporcionar recursos adicionales de GPU.

Bash
# En el Nodo 2, unirse como trabajador
# Establecer el nombre de la interfaz (la misma que en el Nodo 1)
export MN_IF_NAME=enp1s0f1np1
# Obtener la propia IP del Nodo 2
export VLLM_HOST_IP=$(ip -4 addr show $MN_IF_NAME | grep -oP '(?<=inet\s)\d+(\.\d+){3}')

# IMPORTANTE: Establecer HEAD_NODE_IP a la IP del Nodo 1
# Debes obtener este valor del Nodo 1 (ejecuta: echo $VLLM_HOST_IP en Nodo 1)
export HEAD_NODE_IP=
echo "Worker IP: $VLLM_HOST_IP, conectando al nodo maestro en: $HEAD_NODE_IP"

bash run_cluster.sh $VLLM_IMAGE $HEAD_NODE_IP --worker ~/.cache/huggingface \
-e VLLM_HOST_IP=$VLLM_HOST_IP \
-e UCX_NET_DEVICES=$MN_IF_NAME \
-e NCCL_SOCKET_IFNAME=$MN_IF_NAME \
-e OMPI_MCA_btl_tcp_if_include=$MN_IF_NAME \
-e GLOO_SOCKET_IFNAME=$MN_IF_NAME \
-e TP_SOCKET_IFNAME=$MN_IF_NAME \
-e RAY_memory_monitor_refresh_ms=0 \
-e MASTER_ADDR=$HEAD_NODE_IP

Paso 5: Servir Llama 3.3 70B

Una vez que el cluster esté listo, localiza el contenedor e inicia el servidor de vLLM:

Bash
# En el Nodo 1, encontrar el nombre del contenedor (será node-)
export VLLM_CONTAINER=$(docker ps --format '{{.Names}}' | grep -E '^node-[0-9]+$')
echo "Contenedor encontrado: $VLLM_CONTAINER"

# Iniciar servidor dentro del contenedor
docker exec -it $VLLM_CONTAINER /bin/bash -c '
vllm serve meta-llama/Llama-3.3-70B-Instruct \
--tensor-parallel-size 2 --max_model_len 2048'
Despliegue de Llama 3.1 405B (Opcional) Para modelos de 405B, utiliza la versión cuantizada AWQ INT4 para que quepa en la memoria VRAM compartida.

# Descargar modelo 405B cuantizado
Bash
huggingface-cli download hugging-quants/Meta-Llama-3.1-405B-Instruct-AWQ-INT4
echo "Modelo 405B cuantizado descargado."
# Lanzar servidor de inferencia 405B con parámetros restringidos
Bash
# En el Nodo 1, lanzar con parámetros de memoria limitada
export VLLM_CONTAINER=$(docker ps --format '{{.Names}}' | grep -E '^node-[0-9]+$')
echo "Lanzando 405B en: $VLLM_CONTAINER"

docker exec -it $VLLM_CONTAINER /bin/bash -c '
vllm serve hugging-quants/Meta-Llama-3.1-405B-Instruct \
--tensor-parallel-size 2 --max_model_len 256 --gpu-memory-utilization 1.0 \
--max-num-seqs 1 --max_num_batched_tokens 256'
echo "Servidor vLLM para Llama 3.1 405B iniciado con parámetros restringidos."

Verificación y Pruebas

Para probar que el servidor está respondiendo correctamente a través de la red distribuida:

Curl
curl http://localhost:8000/v1/completions \
-H "Content-Type: application/json" \
-d '{
  "model": "meta-llama/Llama-3.3-70B-Instruct",
  "prompt": "Escribe un haiku sobre una GPU",
  "max_tokens": 32,
  "temperature": 0.7
}'

Monitoreo y Validación

  • Ray Dashboard: http://<IP_NODO_MAESTRO>:8265
  • Health Check: curl http://localhost:8000/health
  • Uso de VRAM:
    Bash
    nvidia-smi --query-gpu=memory.used,memory.total --format=csv