J&M Labs Blog by Milo

Building the future, locally

← Back to Home

Networking DGX Station and DGX Sparks with MikroTik CRS804

In plain English. This is how we tie eight NVIDIA DGX Spark mini computers and one DGX Station GB300 into one fast private network, using two MikroTik CRS804 switches. Each Spark gets one 200G cable. The Station gets two. One 400G cable joins the switches. Five steps, each with a check. If you only remember three things: turn off hardware offload on the switch's factory bridge, force the Station's ports to 200G, and run rdma_topo check on the Station.

200G
per Spark, one cable
24 GB/s
NCCL all_reduce, 8 Sparks, 256 MiB
392 Gb/s
Station, both ports, GPU memory
+1 µs
for crossing to the other switch

This page is the recipe. The build itself (shelves, photos, power) is on the GhettoRak page.

Set-up order and the check after each step Five steps: cable it, set up both switches, set up the Sparks, set up the Station, then test. Each step has a check: link lights, every fabric port 200G with hardware offload, both Spark ports up at MTU 9000, rdma_topo check passing on the Station, and jumbo pings plus RDMA bandwidth between every pair. Do it in this order Check each step before the next one. Most problems show up at the check right after them. 1 Cable check: lights on every leg 2 Switches check: 200G + H flag 3 Sparks check: 2 ports up, MTU 9000 4 Station check: rdma_topo check: OK 5 Test check: pings + RDMA, all pairs No link on the Station? Force its ports to 200G (step 4). No hardware offload? Check the bridges (step 2).

The five steps. Each one ends with a check.

Parts

QtyWhatPart
2MikroTik CRS804 switch (four 400G QSFP-DD cages each)CRS804-4DDQ-hRM
4400G → 2 × 200G breakout, MikroTik-coded, one per two SparksNADDOD Q2Q56-400G-CU2 (2 m) / -CU1 (1 m)
1400G cable, switch to switch, 1 mNADDOD QDD-400G-CU1
1400G → 2 × 200G breakout for the Station, 1 m (ours is Mellanox-coded; it works)NADDOD Q2Q56-400G-CU1
2+Copper patch cords for management (house LAN → switch 1 → switch 2)Cat6a

Use copper (DAC) cables, not optics, at these lengths. We could not make a 0.5 m breakout work. One switch is enough for four Sparks plus the Station, or for eight Sparks.

1. Cabling

Which cable goes into which switch cage Two MikroTik CRS804 switches, each with four QSFP-DD cages. On each switch, cages 1 and 2 take a breakout cable that feeds two DGX Sparks at 200G each. Cage 3 holds the 400G cable between the two switches. Cage 4 on switch 2 takes one breakout that feeds both 200G ports of the DGX Station. Management runs on copper: house LAN to switch 1, switch 1 to switch 2. What plugs in where Each CRS804 has four cages. A breakout cable turns one cage into two 200G ends. Switch 2 · CRS804 · Sparks 1–4 + Station cage 1 Sparks 4 + 3 cage 2 Sparks 1 + 2 cage 3 400G link cage 4 Station mgmt Spark 4 Spark 3 Spark 1 Spark 2 DGX Station GB300 port 0 · port 1 Switch 1 · CRS804 · Sparks 5–8 cage 1 Sparks 8 + 7 cage 2 Sparks 6 + 5 cage 3 400G link cage 4 empty mgmt Spark 8 Spark 7 Spark 6 Spark 5 400G DAC · 1 m house LAN copper mgmt (own network) Cages 1–2: Spark breakouts. Cage 3: switch-to-switch. Cage 4: the Station. Gold = 200G, blue = 400G.

As built. Sparks 1–4 and the Station on switch 2, Sparks 5–8 on switch 1.

Check: link light on every leg.

2. Switches

RouterOS 7.25 on both switches. Management stays on the copper ports. The fast ports get their own bridge, and only that bridge may use the switch chip. If the factory bridge keeps hardware offload, the fabric bridge falls back to the CPU and is slow.

# management: keep the factory bridge for ether ports, offload off
/interface/bridge/port/set [find bridge=bridge] hw=no
/interface/bridge/set bridge name=mgmt

# fabric bridge on the fast ports
/interface/bridge/add name=fabric protocol-mode=rstp igmp-snooping=no
:foreach p in={"qsfp56-dd-1-1";"qsfp56-dd-1-5";"qsfp56-dd-2-1";"qsfp56-dd-2-5"} do={
  /interface/ethernet/set [find name=$p] auto-negotiation=no \
    speed=200G-baseCR4 fec-mode=fec91 l2mtu=9216 mtu=9000
  /interface/bridge/port/add bridge=fabric interface=$p hw=yes
}

# cage 3: the 400G cable to the other switch
/interface/ethernet/set [find name=qsfp56-dd-3-1] auto-negotiation=no \
    speed=400G-baseCR8 fec-mode=fec91 l2mtu=9216 mtu=9000
/interface/bridge/port/add bridge=fabric interface=qsfp56-dd-3-1 hw=yes

# ECN on the default traffic class
/interface/ethernet/switch/qos/tx-manager/queue/set [find traffic-class=1] ecn=yes

Check: /interface/bridge/port/print shows the H (hardware) flag on every fabric port, and /interface/ethernet/monitor qsfp56-dd-1-1 once shows 200G, link ok.

3. Sparks

One 200G cable shows up on a Spark as two interfaces, one per half of the card. Give each half its own subnet. N is the Spark's number. Delete the auto-created "Wired connection" profiles first, then add one netplan file with no gateway:

# /etc/netplan/40-cx7-fabric.yaml
network:
  version: 2
  renderer: NetworkManager
  ethernets:
    enp1s0f0np0:
      addresses: [10.0.0.N/24]
      mtu: 9000
      dhcp4: no
      dhcp6: no
      accept-ra: no
      link-local: []
    enP2p1s0f0np0:
      addresses: [10.0.1.N/24]
      mtu: 9000
      dhcp4: no
      dhcp6: no
      accept-ra: no
      link-local: []

The Sparks link at 200G with auto-negotiation left on, so nothing else is needed. Because both halves sit on one network, each must answer ARP only for its own address. Our Sparks already had these set system-wide; confirm yours, and add them to a file in /etc/sysctl.d/ if not:

sysctl net.ipv4.conf.all.arp_ignore net.ipv4.conf.all.arp_announce net.ipv4.conf.all.rp_filter
# want 1, 2, 2

Check: both interfaces up, 200G, MTU 9000. Keep the Sparks on kernel 6.17 for now: NVIDIA's advisory tells multi-node RoCE users to hold off on 7.0.

4. Station

The Station's ConnectX-8 will not link while it auto-negotiates against the switch's fixed 200G. Force it, and set RS FEC to match. Addresses go in netplan (same shape as the Sparks, host number .9: 10.0.0.9 on enP1p3s0f0np0, 10.0.1.9 on enP1p3s0f1np1). Netplan cannot store the ethtool part, so a small boot service does it:

# /etc/systemd/system/station-fabric-link.service
[Unit]
Description=Force CX8 fabric ports to 200G-CR4 / RS-FEC, set ARP rules
Wants=sys-subsystem-net-devices-enP1p3s0f0np0.device sys-subsystem-net-devices-enP1p3s0f1np1.device
After=sys-subsystem-net-devices-enP1p3s0f0np0.device sys-subsystem-net-devices-enP1p3s0f1np1.device
Before=NetworkManager.service

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/sbin/ethtool -s enP1p3s0f0np0 autoneg off speed 200000 duplex full lanes 4
ExecStart=/usr/sbin/ethtool --set-fec enP1p3s0f0np0 encoding rs
ExecStart=/usr/sbin/ethtool -s enP1p3s0f1np1 autoneg off speed 200000 duplex full lanes 4
ExecStart=/usr/sbin/ethtool --set-fec enP1p3s0f1np1 encoding rs
ExecStart=/usr/sbin/sysctl -q -w net.ipv4.conf.enP1p3s0f0np0.arp_ignore=1
ExecStart=/usr/sbin/sysctl -q -w net.ipv4.conf.enP1p3s0f0np0.arp_announce=2
ExecStart=/usr/sbin/sysctl -q -w net.ipv4.conf.enP1p3s0f0np0.rp_filter=2
ExecStart=/usr/sbin/sysctl -q -w net.ipv4.conf.enP1p3s0f1np1.arp_ignore=1
ExecStart=/usr/sbin/sysctl -q -w net.ipv4.conf.enP1p3s0f1np1.arp_announce=2
ExecStart=/usr/sbin/sysctl -q -w net.ipv4.conf.enP1p3s0f1np1.rp_filter=2

[Install]
WantedBy=multi-user.target
sudo systemctl enable --now station-fabric-link.service

Then fix the GPU path. The card has a direct path to the GB300's memory, called Data Direct. Out of the box, a PCIe isolation setting (ACS) puts the card and the GPU in different groups, and GPU-memory transfers fail with error 524. NVIDIA's tool finds and fixes it (NVIDIA's Station guide):

sudo rdma_topo check              # look for FAIL lines about ACS / iommu_group
sudo rdma_topo write-grub-acs     # writes /etc/default/grub.d/config-acs.cfg
sudo update-grub
# then restart the Station

Check: after the restart, rdma_topo check prints only OK lines, and both ports come back at 200G by themselves.

5. Test

Jumbo pings between every pair, on both subnets. -M do forbids splitting, so a wrong MTU anywhere fails loudly:

ping -c 3 -M do -s 8972 10.0.0.2
ping -c 3 -M do -s 8972 10.0.1.2

Eight Sparks plus the Station gave 128 of 128. Then RDMA bandwidth, one pair at a time (ib_write_bw from perftest; on the Station, a CUDA build adds the GPU flags):

# receiver (Spark 1, first half of the card)
ib_write_bw -F -x 3 -q 4 -D 8 --report_gbits -d rocep1s0f0
# sender (another Spark)
ib_write_bw -F -x 3 -q 4 -D 8 --report_gbits -d rocep1s0f0 10.0.0.1
# Station sender, from GPU memory through Data Direct
ib_write_bw -F -x 3 -q 4 -D 8 --report_gbits -d mlx5_0 \
  --use_cuda_bus_id=<GPU PCI address> --use_cuda_dmabuf --use_data_direct 10.0.0.1

Then NCCL, with these settings on every Spark:

NCCL_IB_HCA=rocep1s0f0,roceP2p1s0f0
NCCL_SOCKET_IFNAME=enp1s0f0np0
NCCL_IB_ROCE_VERSION_NUM=2
NCCL_IB_GID_INDEX=3
NCCL_PROTO=^LL128
NCCL_MAX_NCHANNELS=8

Pin NCCL_SOCKET_IFNAME, or NCCL may start up over Docker or Tailscale. All eight Sparks, four per switch, reached 24 GB/s bus bandwidth in all_reduce at 256 MiB.

What speed to expect

What speed to expect on each path, measured October 29-30, 2026 Bars in gigabits per second: rsync over ssh 6.6, ssh copy 9.3, Spark SSD sustained write 13.6, one TCP stream 42, one Spark port half over RDMA 112, a whole Spark over TCP 185, the Station from host memory 231, and the Station from GPU memory through Data Direct 392. What speed to expect Gb/s, measured on our fabric. Your transfer runs at the slowest layer it touches. rsync over ssh, one big file 6.6 one CPU core of encryption ssh copy, aes128-gcm 9.3 same Spark SSD, sustained write 13.6 1.7 GB/s after ~50 GB One TCP stream 42 one CPU core One Spark port half (RDMA) 112 PCIe Gen5 x4 Whole Spark (TCP, both halves) 185 2 × x4 Station, host memory 231 Gen5 x8 link Station, GPU memory (Data Direct) 392 · both ports, full speed 0 100 200 300 400 For big copies, use several streams (or no ssh) and remember the disk. For GPU data, use Data Direct.

Measured on this fabric, October 29–30, 2026. Gb/s; divide by 8 for GB/s.

Where the speed limits are inside a DGX Spark and a DGX Station Left: a DGX Spark's ConnectX-7 reaches the GB10 chip through two PCIe Gen5 x4 links of about 112 Gb/s each, about 185 Gb/s for the whole Spark. Right: the DGX Station's ConnectX-8 reaches the Grace CPU through one PCIe Gen5 x8 link, about 231 Gb/s, shared with the model drives and the LAN port, and reaches the GB300 GPU through a Gen6 x16 Data Direct path that ran both ports at 392 Gb/s. Inside the boxes: where the speed limits are The 200G cables are rarely the slowest part. The links inside each machine are. DGX Spark DGX Station GB300 ConnectX-7 · one 200G cable GB10 chip CPU and GPU share one memory PCIe Gen5 x4 ~112 Gb/s PCIe Gen5 x4 ~112 Gb/s Whole Spark ≈ 185 Gb/s (TCP), 24 GB/s NCCL No GPUDirect RDMA on GB10: nothing to unlock ConnectX-8 · two 200G ports Grace CPU + /models, LAN, USB GB300 GPU HBM PCIe Gen5 x8 ~231 Gb/s, shared Data Direct · Gen6 x16 392 Gb/s measured Host memory: ~231 Gb/s for both ports GPU memory via Data Direct: 392 Gb/s On the Station, move data to and from GPU memory. On the Spark, ~112 per port half is the ceiling.

Why: the links inside each machine, not the 200G cables, set most of these limits.

When something is wrong

SymptomFix
Station port never linksForce it: ethtool -s <if> autoneg off speed 200000 duplex full lanes 4 and --set-fec <if> encoding rs
Fabric slow, ports missing the H flagThe factory bridge still has hw=yes. Set it to hw=no, then re-add the fabric ports
Normal pings work, -s 8972 pings failMTU 9000 on the host and l2mtu=9216 mtu=9000 on every switch port, the 400G cable included
Odd ARP: the wrong half or port answersarp_ignore=1, arp_announce=2, rp_filter=2
GPU-memory RDMA fails with error 524rdma_topo check, then write-grub-acs, update-grub, restart
NCCL slow or uses the wrong networkSet NCCL_SOCKET_IFNAME and NCCL_IB_HCA as above
Copies stuck under 10 Gb/sssh encryption. Use parallel copies, the rsync daemon, or serve the files
Big copy starts fast, then drops to ~1.7 GB/sThe receiving SSD's fast cache is full. Normal

Versions on this build: RouterOS 7.25 on both switches; Sparks on DGX OS 7.2.3, kernel 6.17.0-1031-nvidia, ConnectX-7 firmware 28.45.4028; Station ConnectX-8 firmware 40.47.1088. More from NVIDIA: Spark clustering, connecting two Stations. From MikroTik: Quality of Service.