Hi, everyone.
I was given two Nvidia Spark DGX servers (and I might get a third one in the next few days).
I installed Antirez’s new Dwarfstar 4 on the first one, and I have to say it works pretty well, but now I want to try connecting the two servers so they can share resources.
I haven’t found much information online about the procedure.
From what I understand, I need to:
- connect the servers using a QSFP cable (which I already have)
- set two IP addresses on the same network (e.g., 169.254.1.1 and 169.254.1.2)
- create a user on both machines and copy the SSH keys to both
- configure RDMA/NCCL (which I don’t know what it is or how to do it)
I also read that if you have more than one machine, you can’t use a QSFP cable but need to use a 10Gb switch.
I’m getting a bit lost among all this information.
How one just gets given multiple DGX servers?
I developed a RAG that was very well received, and an American user came to my office and gave me these servers to help me develop faster.
I used to use an Nvidia 4060 Ti with 16 GB.
I was speechless.
This is my procedure to connect 2 dgx spark.
I hope this will be helpful to someone.
PROCEDURE TO LINK TWO NVIDIA SPARK DGX
connect QSFP on same port (no cross connection)
check if the connection is detected with ibdev2netdev
root@sparky-h:# ibdev2netdev
rocep1s0f0 port 1 ==> enp1s0f0np0 (Up)
rocep1s0f1 port 1 ==> enp1s0f1np1 (Down)
roceP2p1s0f0 port 1 ==> enP2p1s0f0np0 (Up)
roceP2p1s0f1 port 1 ==> enP2p1s0f1np1 (Down)
root@sparky-b:# ibdev2netdev
rocep1s0f0 port 1 ==> enp1s0f0np0 (Up)
rocep1s0f1 port 1 ==> enp1s0f1np1 (Down)
roceP2p1s0f0 port 1 ==> enP2p1s0f0np0 (Up)
roceP2p1s0f1 port 1 ==> enP2p1s0f1np1 (Down)
configure the network
root@sparky-h:# ip addr add 10.10.0.1/24 dev enp1s0f0np0
root@sparky-h:# ip link set dev enp1s0f0np0 mtu 9000
and
root@sparky-b:# ip addr add 10.10.0.2/24 dev enp1s0f0np0
root@sparky-b:# ip link set dev enp1s0f0np0 mtu 9000
check connection
root@sparky-h:# ifconfig enp1s0f0np0
enp1s0f0np0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 9000
inet 10.10.0.1 netmask 255.255.255.0 broadcast 0.0.0.0
ether 4c:bb:47:80:3e:50 txqueuelen 1000 (Ethernet)
RX packets 18357 bytes 4251415 (4.2 MB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 10055 bytes 1786256 (1.7 MB)
TX errors 0 dropped 2 overruns 0 carrier 0 collisions 0
root@sparky-b:# ifconfig enp1s0f0np0
enp1s0f0np0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 9000
inet 10.10.0.2 netmask 255.255.255.0 broadcast 0.0.0.0
ether 4c:bb:47:2a:ef:64 txqueuelen 1000 (Ethernet)
RX packets 385 bytes 85970 (85.9 KB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 238 bytes 40892 (40.8 KB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
root@sparky-h:# ping 10.10.0.2
PING 10.10.0.2 (10.10.0.2) 56(84) bytes of data.
64 bytes from 10.10.0.2: icmp_seq=1 ttl=64 time=1.30 ms
64 bytes from 10.10.0.2: icmp_seq=2 ttl=64 time=0.730 ms
^C
--- 10.10.0.2 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1002ms
rtt min/avg/max/mdev = 0.730/1.016/1.303/0.286 ms
root@sparky-b:# ping 10.10.0.1
PING 10.10.0.1 (10.10.0.1) 56(84) bytes of data.
64 bytes from 10.10.0.1: icmp_seq=1 ttl=64 time=0.602 ms
64 bytes from 10.10.0.1: icmp_seq=2 ttl=64 time=0.727 ms
^C
--- 10.10.0.1 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1039ms
rtt min/avg/max/mdev = 0.602/0.664/0.727/0.062 ms
disable ufw (temp)
root@sparky-h:# ufw disable
root@sparky-b:# ufw disable
check speed
root@sparky-b:# ib_write_bw -d rocep1s0f0 -x 3
Waiting for client to connect... *
then
root@sparky-h:# ib_write_bw -d rocep1s0f0 -x 3 10.10.0.2
RDMA_Write BW Test
Dual-port : OFF Device : rocep1s0f0
Number of qps : 1 Transport type : IB
Connection type : RC Using SRQ : OFF
PCIe relax order: ON
ibv_wr* API : ON
TX depth : 128
CQ Moderation : 1
Mtu : 4096[B]
Link type : Ethernet
GID index : 3
Max inline data : 0[B]
rdma_cm QPs : OFF
Data ex. method : Ethernet
local address: LID 0000 QPN 0x0134 PSN 0x1100d8 RKey 0x03a300 VAddr 0x00f550d5fbd000
GID: 00:00:00:00:00:00:00:00:00:00:255:255:10:10:00:01
remote address: LID 0000 QPN 0x0134 PSN 0xc833d0 RKey 0x020700 VAddr 0x00f1c2b03cd000
GID: 00:00:00:00:00:00:00:00:00:00:255:255:10:10:00:02
BW peak[MB/sec] BW average[MB/sec] MsgRate[Mpps]
65536 5000 1591.42 1345.57 0.021529
ignore the speed and creare a dedicated user
(on sparky-h and sparky-b)
useradd -m -s /bin/bash aiuser
passwd aiuser
usermod -aG docker aiuser
usermod -aG sudo aiuser
usermod -aG _ssh aiuser
(on sparky-h)
aiuser@sparky-h:~$ su - aiuser
aiuser@sparky-h:~$ ssh-keygen -t rsa -b 4096
aiuser@sparky-h:~$ ssh-copy-id -p 10110 aiuser@10.10.0.2
aiuser@sparky-h:~$ ssh-copy-id -p 10110 aiuser@10.10.0.1
aiuser@sparky-h:~$ ssh-copy-id -p 10110 aiuser@127.0.0.1
if you use a different port:
aiuser@sparky-h:$ vi ~/.ssh/config
Host 10.10.0.*
Port 10110
StrictHostKeyChecking no
(try connection from sparky-h to sparky-b)
aiuser@sparky-h:~$ ssh aiuser@10.10.0.2
make deepuser folder and create some files (as aiuser)
aiuser@sparky-h:~$ mkdir -p /home/aiuser/deepspeed
aiuser@sparky-h:~$ cd /home/aiuser/deepspeed
aiuser@sparky-h:~/deepspeed$ cat > ds_config.json
{
"train_micro_batch_size_per_gpu": 1,
"zero_optimization": {
"stage": 3,
"offload_optimizer": {
"device": "none"
},
"offload_param": {
"device": "none"
},
"overlap_comm": true,
"contiguous_gradients": true
},
"bf16": {
"enabled": true
},
"zero_allow_untested_optimizer": true
}
aiuser@sparky-h:~/deepspeed$ cat > test_deepseek.py
import os
import deepspeed
import torch
import torch.distributed as dist
def main():
deepspeed.init_distributed()
local_rank = int(os.environ.get("LOCAL_RANK", 0))
torch.cuda.set_device(local_rank)
if local_rank == 0:
print("cluster initialized successfully with gpus.")
print("testing nccl all-reduce bandwidth across dgx stations...")
tensor = torch.ones(1024, device="cuda") * local_rank
dist.all_reduce(tensor)
if local_rank == 0:
print("communication test completed successfully.")
if dist.is_initialized():
dist.destroy_process_group()
if name == "main":
main()
aiuser@sparky-h:~/deepspeed$ cat > hostfile
10.10.0.1 slots=1
10.10.0.2 slots=1
copy deepspeed on sparvy-b
aiuser@sparky-h:~/deepspeed$ cd ..
aiuser@sparky-h:~$ scp -r -P 10110 deepspeed aiuser@10.10.0.2:.
create the venv for deepspeed
aiuser@sparky-h:~/deepspeed$ python3 -m venv venv_deepspeed
aiuser@sparky-h:~/deepspeed$ source venv_deepspeed/bin/activate
(venv_deepspeed) aiuser@sparky-h:~/deepspeed$ pip install --upgrade pip setuptools wheel numpy
(venv_deepspeed) aiuser@sparky-h:~/deepspeed$ pip install --no-cache-dir torch --index-url https://download.pytorch.org/whl/cu130
(venv_deepspeed) aiuser@sparky-h:~/deepspeed$ python -c "import torch; print(torch.version, torch.cuda.is_available())"
check "2.12.1+cu130 True"
(venv_deepspeed) aiuser@sparky-h:~/deepspeed$ DS_ARCH_TYPE=aarch64 pip install deepspeed --no-build-isolation --force-reinstall
error? no problem, relaunch
(venv_deepspeed) aiuser@sparky-h:~/deepspeed$ DS_ARCH_TYPE=aarch64 pip install deepspeed --no-build-isolation --force-reinstall
(venv_deepspeed) aiuser@sparky-h:~/deepspeed$ deepspeed --version
(venv_deepspeed) aiuser@sparky-h:~/deepspeed$ python -c "import torch; print('Nome GPU:', torch.cuda.get_device_name(0)); print('CUDA:', torch.cuda.is_available())"
check for
Nome GPU: NVIDIA GB10
Compatibile sm_121: True
repeat step 10 on sparky-b
root@sparky-b:# su - aiuser
create and activate venv
pip install all
etc
install pdsh
root@sparky-h:# apt-get update && sudo apt-get install -y pdsh
do the magic
(venv_deepspeed) aiuser@sparky-h:~/deepspeed$ deepspeed --hostfile=hostfile --master_addr="10.10.0.1" --master_port=12345 --venv_script=/home/aiuser/deepspeed/venv_deepspeed/bin/activate test_deepseek.py --deepspeed ds_config.json
check
...
10.10.0.1: testing nccl all-reduce bandwidth across dgx stations...
10.10.0.2: communication test completed successfully.
10.10.0.1: communication test completed successfully.
10.10.0.1: [2026-06-20 04:04:18,130] [INFO] [launch.py:367:main] Process 790368 exits successfully.
10.10.0.2: [2026-06-20 08:04:18,383] [INFO] [launch.py:367:main] Process 1054192 exits successfully.
STEP 2 - IMPROOVEMENT
on both spark
root@sparky-h:# vi /etc/ssh/sshd_config
and add
PermitUserEnvironment yes
save and restart sshd
root@sparky-h:# service ssh restart
root@sparky-h:# systemctl restart ssh
(venv_deepspeed) aiuser@sparky-h:~/deepspeed$ vi ~/.ssh/environment
needed by deepspeed stuff
export NCCL_DEBUG=INFO
export NCCL_IB_DISABLE=0
export NCCL_NET_GDR_LEVEL=2
export NCCL_IB_HCA=rocep1s0f0
export NCCL_SOCKET_IFNAME=enp1s0f0np0
export NCCL_IB_GID_INDEX=3
export NCCL_TOPO_DUMP_FILE=/tmp/nccl_topo.xml
check if the env is ok
(venv_deepspeed) aiuser@sparky-h:~/deepspeed$ ssh aiuser@10.10.0.2 env | grep NCCL
export NCCL_IB_GID_INDEX=3
export NCCL_DEBUG=INFO
export NCCL_IB_DISABLE=0
export NCCL_NET_GDR_LEVEL=2
export NCCL_IB_HCA=rocep1s0f0
export NCCL_SOCKET_IFNAME=enp1s0f0np0
export NCCL_TOPO_DUMP_FILE=/tmp/nccl_topo.xml
final test
(venv_deepspeed) aiuser@sparky-h:~/deepspeed$ NCCL_DEBUG=INFO NCCL_DEBUG_SUBSYS=INIT,NET deepspeed deepspeed --hostfile=hostfile --master_addr="10.10.0.1" --master_port=12345 --venv_script=/home/aiuser/deepspeed/venv_deepspeed/bin/activate test_deepseek.py --deepspeed ds_config.json
...
10.10.0.2: sparky-b:1081380:1081380 [0] NCCL INFO Connected all rings, use ring PXN 0 GDR 0
10.10.0.1: sparky-h:809186:809186 [0] NCCL INFO Connected all rings, use ring PXN 0 GDR 0
10.10.0.2: communication test completed successfully.
10.10.0.1: communication test completed successfully.
10.10.0.1: sparky-h:809186:809186 [0] NCCL INFO comm 0x3f07ceb0 rank 0 nranks 2 cudaDev 0 busId f01000 - Destroy COMPLETE
10.10.0.2: sparky-b:1081380:1081380 [0] NCCL INFO comm 0x39545890 rank 1 nranks 2 cudaDev 0 busId f01000 - Destroy COMPLETE
10.10.0.2: sparky-b:1081380:1081380 [0] NCCL INFO ENV/Plugin: Closing env plugin ncclEnvDefault
10.10.0.1: sparky-h:809186:809186 [0] NCCL INFO ENV/Plugin: Closing env plugin ncclEnvDefault
10.10.0.1: [2026-06-20 04:31:18,532] [INFO] [launch.py:367:main] Process 809186 exits successfully.
10.10.0.2: [2026-06-20 08:31:18,826] [INFO] [launch.py:367:main] Process 1081380 exits successfully.
OK, and now???
Now we must check if the if is used
(venv_deepspeed) aiuser@sparky-h:~/deepspeed$ NCCL_DEBUG=INFO NCCL_DEBUG_SUBSYS=INIT,NET,GRAPH,ENV deepspeed test_deepseek.py
search the magic words on output
...
sparky-h:811135:811135 [0] NCCL INFO NET/IB : Made virtual device [0] name=rocep1s0f0 speed=200000 ndevs=1
sparky-h:811135:811135 [0] NCCL INFO NET/IB : Made virtual device [1] name=roceP2p1s0f0 speed=200000 ndevs=1
sparky-h:811135:811135 [0] NCCL INFO NET/IB : Using [0]rocep1s0f0:1/RoCE [1]roceP2p1s0f0:1/RoCE [RO]; OOB enP7s7:192.168.10.40<0>
sparky-h:811135:811135 [0] NCCL INFO Initialized NET plugin IB
sparky-h:811135:811135 [0] NCCL INFO Assigned NET plugin IB to comm
sparky-h:811135:811135 [0] NCCL INFO GIN/Plugin: Could not find: libnccl-gin.so
sparky-h:811135:811135 [0] NCCL INFO Assigned GIN plugin GIN_IB_GDAKI to comm
sparky-h:811135:811135 [0] NCCL INFO Assigned RMA plugin GIN_IB_PROXY to comm
sparky-h:811135:811135 [0] NCCL INFO Using network IB
sparky-h:811135:811135 [0] NCCL INFO [Rank 0] ncclCommInitRankConfig comm 0x1ef1b350 rank 0 nranks 1 cudaDev 0 nvmlDev 0 busId f01000 commId 0xf1a257e180d55d20 - Init START
...
perfect! we are not in TCP
but "nranks 1" = we are not in distributed :o((
try this
(venv_deepspeed) aiuser@sparky-h:~/deepspeed$ deepspeed --hostfile=hostfile --master_addr=10.10.0.1 --master_port=12345 test_deepseek.py
...
10.10.0.1: [2026-06-20 04:40:22,397] [INFO] [launch.py:168:main] nnodes=2, num_local_procs=1, node_rank=0
...
ok! 2 nnodes. SOOOO HAPPY!