Ich hatte zu Testzwecken auf meinem PC zum ersten Test ein LM Studio am laufen mit einer 16 GB Grafikkarte und 48 GB RAM. Darauf liefen meine ersten Schritte mit selbstgehosteten Sprachmodellen. Ich war fasziniert und wollte mehr erfahren, insbesondere, wie diese Technologie skaliert werden kann.
Zuerst lief bei mir OpenClaw mit unterschiedlichen Modellen, was eben in den PC „hineingepasst“ hat. OpenClaw war stark in der Verwendung von Werkzeugen, mir persönlich aber zu vergesslich. Also bin ich zu Hermes Agent gewechselt. Und dort wollte ich auch ein größeres Modell testen.
Nach ein paar Nächten habe ich dann alles ans Laufen gebracht. Es gab mehrere Bugs und Probleme: Qwen3-VL-MoE Pydantic-Bug (NGC vLLM 0.19), NVIDIA Container Runtime Bug, Mistral Tokenizer-Validator zu strikt, Worker-Reconnect nach Head-Restart, Qwen2.5-VL-72B Tool-Calling zu zurückhaltend, NCCL-Multinode-Hänger…
Jetzt habe ich als Ergebnis ein 235B-MoE-Vision-Modell auf 2× DGX Spark mit Hermes-Tool-Calling am Laufen. Das fühlt sich gut an. Nicht perfekt, aber gut. Die Spezifikationen meines „End“setups:
- vLLM 0.20.2 (Mainline mit Spark-Optimierungen)
- Qwen3-VL-235B-A22B-Instruct-AWQ (235B Total, 22B aktiv pro Token, MoE)
- Tensor-Parallel über 2 Nodes via RoCE 200 GbE
- Vision + Tool-Calling (Hermes-Parser)
- 65k Context
- ~5-8 Min Recovery-Zeit nach Reboot und Auto-Restart bei Crash
Voraussetzungen
- 2× NVIDIA DGX Spark (GB10 Blackwell, 128 GB Unified Memory pro Node)
- 200 GbE Direktverbindung über ConnectX-7 (NIC
enp1s0f1np1) - Ubuntu 24.04 mit NVIDIA Driver 580.x, CUDA 13.2
- Docker mit NVIDIA Container Runtime
- User
adminin Docker-Gruppe auf beiden Nodes
Updates durchführen
sudo apt update
sudo apt dist-upgrade -y
sudo fwupdmgr refresh
sudo fwupdmgr upgrade -y
sudo reboot
Netzwerk-Setup
Beide Nodes müssen mit dem 200 GbE Kabel miteinander verbunden sein. Im Folgenden wird diese Verbindung konfiguriert.
Statische IPs auf den 200 GbE Ports konfigurieren:
- Node 1:
192.168.100.10/24aufenp1s0f1np1, MTU 9000 - Node 2:
192.168.100.11/24aufenp1s0f1np1, MTU 9000
Netplan auf Node 1
sudo tee /etc/netplan/40-cx7.yaml > /dev/null <<'EOF'
network:
version: 2
ethernets:
enp1s0f1np1:
addresses:
- 192.168.100.10/24
dhcp4: no
mtu: 9000
EOF
sudo chmod 600 /etc/netplan/40-cx7.yaml
sudo netplan apply
sudo reboot
Netplan auf Node 2
sudo tee /etc/netplan/40-cx7.yaml > /dev/null <<'EOF'
network:
version: 2
ethernets:
enp1s0f1np1:
addresses:
- 192.168.100.11/24
dhcp4: no
mtu: 9000
EOF
sudo chmod 600 /etc/netplan/40-cx7.yaml
sudo netplan apply
sudo reboot
Damit sollten sich die zwei Nodes gegeneinander pingen können.
Auf Node 1:
ping -c 4 192.168.100.11
Auf Node 2:
ping -c 4 192.168.100.10
Schritt 1: Passwordless SSH einrichten
Damit der Node 1 im geclusterten Modus den Cluster Node 2 starten kann, benötigt der einen passwortlosen zugriff auf den Node 2. Dazu werden die ssh-Keys zur Authentifizierung ausgetauscht.
Auf beiden Nodes:
ssh-keygen -t ed25519 -N ''
Auf Node 1:
ssh-copy-id admin@192.168.100.11
Auf Node 2:
ssh-copy-id admin@192.168.100.10
Verifizieren von Node 1 aus:
ssh admin@192.168.100.11 'nvidia-smi --query-gpu=name --format=csv,noheader'
# Erwartet: NVIDIA GB10 (ohne Passwort)
Schritt 2: uvx installieren (beide Nodes)
uvx wird benötigt, um den Download via hf zu starten und die modelle auf beide Nodes zu verteilen.
curl -LsSf https://astral.sh/uv/install.sh | sh
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
which uvx
Schritt 3: Repo klonen (beide Nodes)
Hier verwende ich nicht das Repo von nvidia. Das basiert auf einer alten Version von vllm und hat einen Bug bei Multi-Node-Setups. Daher geht es mit dem auf git verfügbaren aktuellen vllm – extra für DGX Spark Multi Node – weiter:
https://github.com/eugr/spark-vllm-docker
cd ~
git clone https://github.com/eugr/spark-vllm-docker.git
Schritt 4: Image bauen und auf Node 2 verteilen
Nur auf Node 1:
cd ~/spark-vllm-docker
./build-and-copy.sh -c 192.168.100.11
Dauer: ~5-10 Min (lädt prebuilt Wheels, baut Runner-Image, kopiert auf Node 2).
Verifizieren:
docker images | grep vllm-node
ssh admin@192.168.100.11 'docker images | grep vllm-node'
Schritt 5: Cluster-Konfiguration erstellen
Auf Node 1:
cd ~/spark-vllm-docker
./launch-cluster.sh --setup --check-config
Bestätigt beide Nodes interaktiv. Erzeugt .env:
CLUSTER_NODES=192.168.100.10,192.168.100.11
COPY_HOSTS=192.168.100.11
LOCAL_IP=192.168.100.10
ETH_IF=enp1s0f1np1
IB_IF=rocep1s0f1,roceP2p1s0f1
Schritt 6: Modell laden (und automatisch auf beide Nodes verteilen)
cd ~/spark-vllm-docker
./hf-download.sh QuantTrio/Qwen3-VL-235B-A22B-Instruct-AWQ -c --copy-parallel
Dauer: ~15-30 Min (~120 GB Download + paralleler RoCE-Sync).
Verifizieren:
du -sh ~/.cache/huggingface/hub/models--QuantTrio--Qwen3-VL-235B-A22B-Instruct-AWQ
ssh admin@192.168.100.11 'du -sh ~/.cache/huggingface/hub/models--QuantTrio--Qwen3-VL-235B-A22B-Instruct-AWQ'
Schritt 7: Manueller Cluster-Test
Auf Node 1:
cd ~/spark-vllm-docker
./launch-cluster.sh exec \
vllm serve QuantTrio/Qwen3-VL-235B-A22B-Instruct-AWQ \
--served-model-name qwen3-vl-235b-awq \
--port 8000 --host 0.0.0.0 \
--tensor-parallel-size 2 \
--distributed-executor-backend ray \
--gpu-memory-utilization 0.85 \
--max-model-len 65536 \
--load-format fastsafetensors \
--enable-auto-tool-choice \
--tool-call-parser hermes \
--trust-remote-code
Wartezeit bis Application startup complete: ~5-7 Min.
In zweiter SSH-Session auf Node 1 verifizieren:
curl -s http://localhost:8000/v1/models | python3 -m json.tool
Tool-Call-Test auch auf Node 1:
curl -s http://localhost:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "qwen3-vl-235b-awq",
"messages": [{"role":"user","content":"Wie ist das Wetter in Berlin?"}],
"tools": [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get current weather",
"parameters": {
"type": "object",
"properties": {"location": {"type": "string"}},
"required": ["location"]
}
}
}],
"tool_choice": "auto"
}' | python3 -m json.tool
Erwartung: tool_calls ist gefüllt mit get_weather und Berlin.
Test stoppen mit Ctrl+C im laufenden Terminal.
Schritt 8: systemd-Service einrichten
Hier wird der Autostart Service auf dem Node 1 eingerichtet. Im Autostart wird auch der Node 2 mit hochgefahren, also ist kein Autostart auf Node 2 zusätzlich notwendig.
Auf Node 1:
sudo nano /etc/systemd/system/vllm-spark.service
Inhalt
[Unit]
Description=vLLM Cluster Mode (Qwen3-VL-235B-A22B-AWQ)
After=docker.service network-online.target
Requires=docker.service
Wants=network-online.target
[Service]
Type=simple
User=admin
WorkingDirectory=/home/admin/spark-vllm-docker
ExecStartPre=-/usr/bin/docker rm -f vllm_node
ExecStartPre=-/usr/bin/ssh admin@192.168.100.11 docker rm -f vllm_node
ExecStart=/home/admin/spark-vllm-docker/launch-cluster.sh exec \
vllm serve QuantTrio/Qwen3-VL-235B-A22B-Instruct-AWQ \
--served-model-name qwen3-vl-235b-awq \
--port 8000 --host 0.0.0.0 \
--tensor-parallel-size 2 \
--distributed-executor-backend ray \
--gpu-memory-utilization 0.85 \
--max-model-len 65536 \
--load-format fastsafetensors \
--enable-auto-tool-choice \
--tool-call-parser hermes \
--trust-remote-code
ExecStop=/home/admin/spark-vllm-docker/launch-cluster.sh stop
Restart=on-failure
RestartSec=60
TimeoutStartSec=900
TimeoutStopSec=180
[Install]
WantedBy=multi-user.target
Aktivieren und starten:
sudo systemctl daemon-reload
sudo systemctl enable vllm-spark.service
sudo systemctl start vllm-spark.service
sudo journalctl -u vllm-spark -f
Die Zwei Nodes verbrauchen damit jeweils ca. 115 GB – 120 GB ihres Speichers.
Fertigstellen
Nun kann das Modell in Hermes konfiguriert werden. Dazu muss man einfach auf dem Hermes-Rechner die Konfiguration anpassen:
hermes model
Url: http://AAA.XXX.YYY.ZZZ:8000/v1
Model: qwen3-vl-235b-awq
Damit kann Hermes Agent das lokale Modell verwenden.