# Drei Rechner, ein Modell — llama.cpp RPC Cluster im Heimnetz

Ich habe drei Rechner zu Hause, die sonst meist im Leerlauf laufen. Die Idee: alle drei
per llama.cpp RPC zu einem gemeinsamen VRAM-Pool zusammenschalten und damit Modelle
betreiben, die auf einer einzelnen Karte nicht mehr passen. Dieses Dokument beschreibt,
wie ich das umgesetzt habe — inklusive der Stellen, an denen es hakelig wurde.

---

## Hardware-Übersicht

**Master Node — Windows 11**

- AMD Ryzen 7800 X3D, 32 GB RAM
- MSI GeForce RTX 5070 Ventus X2 OC, 12 GB VRAM

**Worker Node 1 & 2 — Linux (Debian)**

- Intel Core i5, 11. Generation, 16 GB RAM
- NVIDIA RTX 2070, 8 GB VRAM

**Gepoolter VRAM: 12 + 8 + 8 = 28 GB**

---

## Was 28 GB VRAM wirklich bedeuten

Bevor man anfängt, lohnt sich ein ehrlicher Blick auf die Zahlen. Ein 70B-Modell in
Q4_K_M braucht allein für die Gewichte rund 43 GB. Das übersteigt den Pool deutlich.

| Komponente | Bedarf (70B Q4_K_M) | Gepoolter VRAM | Differenz |
|---|---|---|---|
| Modellgewichte | ~43 GB | 28 GB | −15 GB |
| KV-Cache (8K Kontext) | ~5 GB | 0 GB | −5 GB |
| KV-Cache (32K Kontext) | ~25 GB | 0 GB | −25 GB |

Die fehlenden 15 GB Gewichte landen im System-RAM des Master-Nodes. Der KV-Cache
ebenfalls. Das hat Konsequenzen: Die Inferenzgeschwindigkeit liegt realistisch bei
**8–12 Tokens pro Sekunde** — nicht mehr. Wer schnelle Antworten erwartet, wird
enttäuscht sein. Für experimentelle Zwecke und zum Verstehen der Architektur ist es
trotzdem interessant.

Wer in diesem Setup produktiv mit großen Kontexten arbeiten will, kommt an RAG nicht
vorbei — kleines Kontextfenster (z.B. 8K) halten, relevante Passagen per Retrieval
einbinden, VRAM für die Gewichte freihalten.

---

## OS-Wahl für die Worker Nodes

Ich habe Debian auf beiden Intel-Maschinen installiert. Gründe:

- Sehr stabile NVIDIA-Treiberunterstützung
- Gut dokumentierte CUDA-Einrichtung
- Kein Overhead durch Desktop-Umgebung nötig

Ubuntu funktioniert genauso — der Installationsweg ist nahezu identisch.

---

## Linux Worker Nodes einrichten

### Pakete und Build-Toolchain

```bash
sudo apt install build-essential g++ cmake git curl libcurl4-openssl-dev pciutils
```

### NVIDIA-Treiber installieren

APT-Quellen um non-free erweitern:

```bash
sudo sed -i 's/main/main non-free contrib/g' /etc/apt/sources.list
sudo apt update
```

Kernel-Header, DKMS und nvidia-detect installieren:

```bash
sudo apt install linux-headers-$(uname -r) build-essential dkms nvidia-detect
```

Prüfen ob die Karte erkannt wird:

```bash
nvidia-detect
```

Wenn ja, Treiber installieren:

```bash
sudo apt install nvidia-driver nvidia-kernel-dkms
sudo reboot
```

Nach dem Neustart verifizieren:

```bash
nvidia-smi
```

### CUDA Toolkit

```bash
sudo apt install nvidia-cuda-toolkit
```

Compute Capability der GPU ermitteln — das braucht man beim Build:

```bash
nvidia-smi --query-gpu=compute_cap --format=csv
```

---

## llama.cpp auf den Linux Nodes bauen

Ich kompiliere llama.cpp selbst — einerseits für maximale Kompatibilität, andererseits
weil ich wissen will, was da läuft. Auf dem i5 mit acht parallelen Prozessen dauert der
Build ca. 6 Minuten.

### Schritt 1: Normaler CUDA-Build

```bash
git clone https://github.com/ggml-org/llama.cpp
cd llama.cpp
cmake -B build -DGGML_CUDA=ON
cmake --build build --config Release -j 8
```

Den `-j 8`-Parameter an die eigene Kernzahl anpassen.

### Schritt 2: RPC-fähigen Build erstellen

Jetzt nochmal kompilieren, diesmal mit RPC-Support. Das ist ein separater Build-Ordner:

```bash
mkdir build-rpc-cuda
cd build-rpc-cuda
cmake .. -DGGML_CUDA=ON -DGGML_RPC=ON
cmake --build . --config Release
```

Das erzeugt das `rpc-server`-Binary, das auf den Worker Nodes laufen muss.

---

## llama.cpp auf Windows installieren

Ich habe winget verwendet — unkompliziert und ausreichend für den Master-Node:

```powershell
winget install llama.cpp
```

**Wichtig:** Master und Worker müssen denselben llama.cpp-Stand (Git-Commit) verwenden.
Das RPC-Protokoll ist noch in aktiver Entwicklung; Versionsunterschiede führen zu
Abstürzen beim Tensor-Laden. Bei winget immer prüfen, welcher Stand installiert wird,
und die Linux-Worker ggf. auf denselben Commit zurücksetzen.

---

## Netzwerk

Kein WLAN. Verteilte Inferenz reagiert sehr empfindlich auf Latenz und Jitter — WLAN
macht das Ergebnis unbrauchbar. Gigabit-Ethernet per Kabel ist das absolute Minimum.

Das RPC-Protokoll überträgt Modell-Tensoren und Hidden States **unverschlüsselt**. Die
Ports (Standard: 50052, ich nutze 21000) dürfen ausschließlich im lokalen Netz
erreichbar sein. Niemals nach außen öffnen.

Firewall auf den Linux-Nodes: den gewählten Port für die IP des Master-Nodes freigeben,
alles andere sperren.

---

## Worker Nodes starten

Auf beiden Linux-Maschinen ins Build-Verzeichnis wechseln und den RPC-Server starten:

```bash
./rpc-server --host 0.0.0.0 --port 21000
```

`--host 0.0.0.0` lässt den Server auf allen Interfaces lauschen. Das ist nur dann
vertretbar, wenn die Firewall den Zugriff auf die IP des Masters beschränkt.

---

## Master Node: Inferenz starten

Der Master orchestriert das Sharding — die Remote-Worker erscheinen als zusätzliche
CUDA-Devices.

Meine Worker laufen auf `192.168.0.91` und `192.168.0.92`:

```bash
./llama-cli.exe -m llama-3-70b-q4_k_m.gguf \
  --rpc 192.168.0.91:21000,192.168.0.92:21000 \
  --n-gpu-layers 100 \
  --ctx-size 8192
```

- `--rpc`: Adressen der Worker-Nodes, kommagetrennt
- `--n-gpu-layers 100`: So viele Layer wie möglich in den gepoolten VRAM; der Rest
  landet im System-RAM des Masters
- `--ctx-size 8192`: Bei 28 GB VRAM vernünftiger Ausgangswert; größere Kontexte
  kosten schnell alles was noch übrig ist

---

## Performance-Flags

Diese Flags bringen messbare Verbesserungen:

- **`--flash-attn`** — Reduziert Speicherbedarf und Rechenaufwand des
  Attention-Mechanismus. Immer einschalten.
- **`--mlock`** — Verhindert, dass das OS Modellgewichte auf die Auslagerungsdatei
  schreibt. Ohne dieses Flag gibt es Stottern bei längerem Betrieb.
- **`--no-mmap`** — Auf den Worker-Nodes empfehlenswert wenn schnelle NVMe vorhanden
  ist: erzwingt vollständiges Laden des Shards in RAM beim Start.
- **`--threads`** — Für die CPU-Fallback-Verarbeitung auf dem Master: 4–8 Threads
  sind optimal, unabhängig von der Gesamtkernzahl. Mehr Threads erzeugen
  Kontextwechsel-Overhead der die Performance verschlechtert.

---

## Fehlerbehebung

### "RPC Failed to Connect" oder Worker meldet "0 MiB free"

Verbindung klappt, aber der Worker meldet keinen freien VRAM:

1. CUDA-Backend auf dem Worker ist nicht korrekt initialisiert — `nvidia-smi` prüfen
2. Falsches `CUDA_DOCKER_ARCH` beim Build gesetzt
3. `nvidia-cuda-toolkit` unvollständig installiert

### Absturz beim Tensor-Laden

Fast immer ein Versions-Mismatch. llama.cpp-Version auf Master und Worker angleichen —
identischer Git-Commit, nicht nur dieselbe Release-Nummer.

---

## Screenshots

Hier ein paar Aufnahmen aus dem laufenden Betrieb — NVTOP und NVITOP zeigen schön,
wie die VRAM-Last auf die Worker verteilt wird.

*(Screenshots aus dem Wiki-Upload — siehe Originalartikel)*

---

## Fazit

Das Setup läuft. 70B-Modelle sind auf 28 GB VRAM machbar, aber man muss die
Erwartungen kalibrieren: 8–12 TPS, kein großes Kontextfenster, der Master-RAM ist
der eigentliche Flaschenhals. Für das Experimentieren mit Modellen, die sonst gar
nicht laufen würden, ist es trotzdem ein sinnvolles Setup.

Wer ernsthaft mit großen Kontexten und diesem VRAM-Budget arbeiten will, sollte RAG
in Betracht ziehen — kleines Fenster halten, Retrieval macht den Rest.