This guide documents the complete process for setting up inter-node GPU communication using RDMA over Converged Ethernet (RoCE) between NVIDIA DGX Spark nodes.
The nodes have multiple network interfaces and hostnames:
- Main hostnames: spark-01, spark-02 (10.0.0.x network - regular ethernet)
- Mesh hostnames: spark-01-mesh, spark-02-mesh (192.168.100.x network - high-speed RDMA)
- RDMA traffic uses the 192.168.100.x mesh network exclusively
When running MPI/NCCL commands, always use the mesh network IP addresses (192.168.100.1, 192.168.100.2) for best reliability.
- Prerequisites
- Environment Overview
- Initial System Check
- Installing Dependencies
- System Optimization
- Building NCCL Tests
- Testing Inter-Node Communication
- Making Configuration Persistent
- Verification
- Troubleshooting
- 2× NVIDIA DGX Spark nodes (or similar)
- NVIDIA GPUs with GPUDirect RDMA support
- Mellanox ConnectX NICs (or similar RDMA-capable NICs)
- RoCE/InfiniBand cable connecting the nodes
- CUDA 13.0+ installed
- Ubuntu 22.04+ (ARM64 in this case)
- NVIDIA drivers installed
- Basic networking configured
- Nodes: spark-01-mesh (192.168.100.1), spark-02-mesh (192.168.100.2)
- GPUs: NVIDIA GB10 (1 per node)
- CUDA: 13.0.88
- NICs: Mellanox ConnectX-7 (4 ports per node, 1 cable connected)
- Network: 192.168.100.x/24 for high-speed RDMA mesh traffic
- Expected Performance: 12+ GB/s inter-node bandwidth
Run on both nodes:
# Check hostname
hostname
# Check GPU detection
nvidia-smi -L
# Check CUDA version
nvcc --version
# Check GPU-NIC topology
nvidia-smi topo -mExpected output shows GPU with NODE-level connection to NICs (good for GPUDirect RDMA).
# List all network interfaces
ip link show
# Check for Mellanox devices
lspci | grep -i mellanox
# Check RDMA devices
ls /sys/class/infiniband/
# Check RDMA link status
rdma link showIdentify active RDMA interfaces. In our case:
roceP2p1s0f0→enP2p1s0f0np0(active, used for RDMA)
# From spark-01-mesh (192.168.100.1)
ping -c 1 spark-02-mesh # or use IP: 192.168.100.2
ping -c 1 192.168.100.2
# From spark-02-mesh (192.168.100.2)
ping -c 1 spark-01-mesh # or use IP: 192.168.100.1
ping -c 1 192.168.100.1Note: The hosts file should have:
192.168.100.1 spark-01-mesh
192.168.100.2 spark-02-meshRun on both nodes:
sudo apt update
sudo apt install -y git build-essential openmpi-bin libopenmpi-dev
# Verify MPI installation
which mpirun
mpirun --version# Add NVIDIA repository (if not already added)
wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/sbsa/cuda-keyring_1.0-1_all.deb
sudo dpkg -i cuda-keyring_1.0-1_all.deb
# Install NCCL
sudo apt update
sudo apt install -y libnccl2 libnccl-dev
# Verify NCCL installation
ls /usr/include/nccl.hRun on both nodes:
# Set network buffer sizes
sudo sysctl -w net.core.rmem_max=268435456
sudo sysctl -w net.core.wmem_max=268435456
# Make permanent
echo "net.core.rmem_max=268435456" | sudo tee /etc/sysctl.d/90-rdma-tuning.conf
echo "net.core.wmem_max=268435456" | sudo tee -a /etc/sysctl.d/90-rdma-tuning.conf# Check current limit
ulimit -l
# Set unlimited memlock
echo -e "* soft memlock unlimited\n* hard memlock unlimited" | sudo tee -a /etc/security/limits.conf
# Note: Logout/login required for this to take effect# Set MTU on RDMA interfaces (adjust interface names as needed)
sudo ip link set dev enP2p1s0f0np0 mtu 9000
# Verify
ip link show enP2p1s0f0np0 | grep mtuRun on both nodes:
cd /tmp
rm -rf nccl-tests
# Clone NCCL tests
git clone https://github.com/NVIDIA/nccl-tests.git
cd nccl-tests
# Build with MPI support
make MPI=1 MPI_HOME=/usr CUDA_HOME=/usr/local/cuda CC=mpicc CXX=mpicxx
# Install
sudo mkdir -p /usr/local/nccl-tests
sudo cp -r build/* /usr/local/nccl-tests/
# Verify
ls /usr/local/nccl-tests/ | grep all_reduce
/usr/local/nccl-tests/all_reduce_perf --help | head -5On spark-01 (main hostname):
# Generate SSH key if needed
ssh-keygen -t ed25519
# Copy to spark-02-mesh (using mesh network IP)
ssh-copy-id geo@192.168.100.2
# Test
ssh geo@192.168.100.2 hostnameRepeat from spark-02 to spark-01-mesh:
ssh-copy-id geo@192.168.100.1
ssh geo@192.168.100.1 hostnameAdd to ~/.ssh/config for convenience:
Host spark-01-mesh
Hostname 192.168.100.1
User geo
Host spark-02-mesh
Hostname 192.168.100.2
User geoFrom spark-01 (using the mesh network):
# Set environment to avoid X11 issues
export DISPLAY=""
# Run inter-node all-reduce test using IP addresses
mpirun -np 2 -H 192.168.100.1,192.168.100.2 \
--mca btl_tcp_if_include enP2p1s0f0np0 \
--mca oob_tcp_if_include enP2p1s0f0np0 \
-x DISPLAY="" \
-x NCCL_DEBUG=INFO \
-x NCCL_IB_HCA=roceP2p1s0f0 \
-x NCCL_SOCKET_IFNAME=enP2p1s0f0np0 \
-x NCCL_NET_GDR_LEVEL=5 \
/usr/local/nccl-tests/all_reduce_perf -b 8 -e 512M -f 2 -g 1Note: Using IP addresses (192.168.100.1, 192.168.100.2) is more reliable than hostnames for MPI.
Expected results:
- Should see "NET/IB : Using [0]roceP2p1s0f0:1/RoCE"
- Large message bandwidth: ~12 GB/s
- No TCP fallback warnings
On both nodes, create netplan configuration for persistent MTU:
# Check existing netplan files
ls /etc/netplan/
# If your RDMA interface isn't configured with MTU 9000, create/edit:
sudo tee /etc/netplan/90-rdma-mtu.yaml << 'EOF'
network:
version: 2
ethernets:
enP2p1s0f0np0:
renderer: NetworkManager
mtu: 9000
EOF
sudo chmod 600 /etc/netplan/90-rdma-mtu.yaml
sudo netplan applyCreate service to disable unused NICs on both nodes:
# Create systemd service
sudo tee /etc/systemd/system/disable-unused-nics.service << 'EOF'
[Unit]
Description=Disable Unused RDMA NICs
After=network.target
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/local/bin/disable-unused-nics.sh
[Install]
WantedBy=multi-user.target
EOF
# Create the script
sudo tee /usr/local/bin/disable-unused-nics.sh << 'EOF'
#!/bin/bash
# Force unused interfaces down
ip link set dev enp1s0f0np0 down 2>/dev/null
ip link set dev enp1s0f1np1 down 2>/dev/null
ip link set dev enP2p1s0f1np1 down 2>/dev/null
echo "Disabled unused NICs at $(date)"
EOF
sudo chmod +x /usr/local/bin/disable-unused-nics.sh
sudo systemctl daemon-reload
sudo systemctl enable disable-unused-nics.service
sudo systemctl start disable-unused-nics.serviceCreate optimization service on both nodes:
# Create systemd service
sudo tee /etc/systemd/system/rdma-optimize.service << 'EOF'
[Unit]
Description=RDMA Performance Optimization
After=network.target
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/local/bin/rdma-optimize.sh
[Install]
WantedBy=multi-user.target
EOF
# Create optimization script
sudo tee /usr/local/bin/rdma-optimize.sh << 'EOF'
#!/bin/bash
# RDMA Performance Optimization
# Set network buffer sizes
sysctl -w net.core.rmem_max=268435456
sysctl -w net.core.wmem_max=268435456
# Ensure MTU is 9000 on active interface
ip link set dev enP2p1s0f0np0 mtu 9000 2>/dev/null
# Try to load optional peer memory modules
modprobe nv_peer_mem 2>/dev/null || true
modprobe nvidia-peermem 2>/dev/null || true
echo "RDMA optimization applied at $(date)"
EOF
sudo chmod +x /usr/local/bin/rdma-optimize.sh
sudo systemctl daemon-reload
sudo systemctl enable rdma-optimize.service
sudo systemctl start rdma-optimize.serviceSet NCCL environment variables on both nodes:
# Create NCCL environment file
sudo tee /etc/profile.d/nccl-env.sh << 'EOF'
# NCCL Environment Variables for Multi-node GPU
export NCCL_IB_HCA=roceP2p1s0f0
export NCCL_SOCKET_IFNAME=enP2p1s0f0np0
export NCCL_NET_GDR_LEVEL=5
# Optional: Uncomment for debugging
# export NCCL_DEBUG=INFO
EOF
sudo chmod +x /etc/profile.d/nccl-env.sh
# Load for current session
source /etc/profile.d/nccl-env.shCreate test script on both nodes:
tee ~/test_rdma_cluster.sh << 'EOF'
#!/bin/bash
echo "==================================="
echo "RDMA Cluster Configuration Test"
echo "==================================="
echo ""
# Check environment variables
echo "1. NCCL Environment Variables:"
echo " NCCL_IB_HCA=$NCCL_IB_HCA"
echo " NCCL_SOCKET_IFNAME=$NCCL_SOCKET_IFNAME"
echo " NCCL_NET_GDR_LEVEL=$NCCL_NET_GDR_LEVEL"
echo ""
# Check network settings
echo "2. Network Settings:"
echo -n " Buffer sizes: "
sysctl net.core.rmem_max net.core.wmem_max | grep -oE "[0-9]+" | tail -2 | xargs
echo -n " MTU on enP2p1s0f0np0: "
ip link show enP2p1s0f0np0 | grep -oE "mtu [0-9]+" | awk '{print $2}'
echo ""
# Check interface status
echo "3. Interface Status:"
for iface in enp1s0f0np0 enP2p1s0f0np0; do
state=$(ip link show $iface 2>/dev/null | grep -oE "state [A-Z]+" | awk '{print $2}')
echo " $iface: ${state:-not found}"
done
echo ""
# Quick RDMA test
echo "4. Quick RDMA Test (if on spark-01):"
# Note: spark-01 is the main hostname, spark-01-mesh is for the 192.168.100.x network
if [[ "$(hostname)" =~ ^spark-01 ]]; then
echo " Running bandwidth test on mesh network..."
mpirun -np 2 -H 192.168.100.1,192.168.100.2 \
--mca btl_tcp_if_include enP2p1s0f0np0 \
--mca oob_tcp_if_include enP2p1s0f0np0 \
-x DISPLAY="" \
/usr/local/nccl-tests/all_reduce_perf -b 256M -e 512M -f 2 -g 1 2>&1 | \
grep -E "512M|Avg bus"
else
echo " (Run from spark-01 for RDMA test)"
fi
EOF
chmod +x ~/test_rdma_cluster.shAfter completing all steps on both nodes:
- Run the test script on both nodes:
~/test_rdma_cluster.sh- From spark-01, run full bandwidth test:
mpirun -np 2 -H 192.168.100.1,192.168.100.2 \
--mca btl_tcp_if_include enP2p1s0f0np0 \
--mca oob_tcp_if_include enP2p1s0f0np0 \
-x DISPLAY="" \
/usr/local/nccl-tests/all_reduce_perf -b 256M -e 512M -f 2 -g 1 2>&1 | \
grep -E "512M|Avg bus"Expected output:
# Avg bus bandwidth : ~12.04 GB/s
The configuration should persist after reboot. To verify:
- Reboot both nodes
- Run
~/test_rdma_cluster.shon both nodes - Run the bandwidth test from spark-01
# Fix SSH key permissions
chmod 600 ~/.ssh/id_ed25519
chmod 644 ~/.ssh/id_ed25519.pub
# Ensure passwordless SSH using mesh network IPs
ssh-copy-id geo@192.168.100.2 # from spark-01
ssh-copy-id geo@192.168.100.1 # from spark-02- Check RDMA device is active:
rdma link show - Verify correct interface names in NCCL_IB_HCA
- Ensure MTU 9000 is set end-to-end
- Check firewall isn't blocking RDMA ports
- Verify MTU 9000:
ip link show enP2p1s0f0np0 | grep mtu - Check cable quality and connection
- Ensure no other traffic on the RDMA network
- Verify GPUDirect is enabled:
nvidia-smi topo -m
- Disable firewall temporarily for testing:
sudo ufw disable - Specify network interface explicitly with
--mcaoptions - Use IP addresses (192.168.100.1, 192.168.100.2) instead of hostnames for reliability
- Check /etc/hosts has correct mesh network entries:
192.168.100.1 spark-01-mesh 192.168.100.2 spark-02-mesh
- For MPI headers not found: Use
CC=mpicc CXX=mpicxxin make command - For NCCL headers not found: Install libnccl-dev package
- For CUDA issues: Verify CUDA_HOME=/usr/local/cuda is correct
With the cluster configured, you can now:
- Run Distributed PyTorch Training
# Example PyTorch distributed initialization
import torch.distributed as dist
dist.init_process_group(backend='nccl')- Deploy vLLM for Distributed Inference
# On spark-01 (using mesh network for coordination)
vllm serve model_name \
--tensor-parallel-size 2 \
--distributed-init-method tcp://192.168.100.1:29500
# On spark-02 (connecting to spark-01-mesh)
vllm serve model_name \
--tensor-parallel-size 2 \
--distributed-init-method tcp://192.168.100.1:29500- Scale Your Workloads
- Use the 12 GB/s bandwidth for model parallelism
- Distribute large models across nodes
- Implement data parallel training
With this setup, you should achieve:
- Small messages (< 1KB): Low bandwidth due to overhead
- Medium messages (1MB-100MB): 2-10 GB/s
- Large messages (> 100MB): 11-12+ GB/s
- Average bandwidth: ~3.6-3.7 GB/s across all sizes
- This setup uses a single cable between nodes
- The configuration focuses on
roceP2p1s0f0interface - Unused interfaces are disabled to prevent interference
- All settings persist across reboots
- Environment tested on NVIDIA DGX Spark with Ubuntu 24.04 ARM64
Configuration tested and verified to achieve 12+ GB/s inter-node GPU bandwidth