LocalAI/PLAN-cluster.md
2026-10-17 23:15:26 +02:00

7.5 KiB

Plan: NVIDIA-Windows-Rechner als flexibler Cluster hinter dem bestehenden Gateway

Context

Mehrere Windows-PCs mit NVIDIA-GPUs sollen zusätzliche Rechenleistung liefern. Der bestehende Linux-PC (AMD, Docker: Caddy + llama-swap) bleibt der einzige Endpunkt (:8800, gleicher API-Key). Jeder Knoten kann jedes Cluster-Modell ausführen und wechselt bei Bedarf (llama-swap). Linux ist auf den Knoten nicht nötig: llama-swap (llama-swap_265_windows_amd64.zip) und llama.cpp (llama-bXXXXX-bin-win-cuda-{12.4|13.4}-x64.zip + cudart-…) gibt es fertig für Windows.

Erkenntnisse aus der Recherche:

  • llama-swap v265 leitet über peers pro Modell an einen Server weiter (URL-Pfad wird angehängt). Das Load-Balancing übernimmt deshalb Caddy.
  • llama-swap löst ${env.VAR} in der Config auf, also kommen keine Secrets ins Git.
  • Ältere llama.cpp-Builds werden gelöscht (b11610 gibt es schon nicht mehr). Der Installer wählt den Build über die GitHub-API (latest oder fest).
  • Caddy auf dem Host-Netz ist für den Container wegen ufw schwer erreichbar. Deshalb läuft der Loadbalancer als eigener Container im Compose-Netz.

Architektur

Client ─► Caddy :8800 (IP-Filter, API-Key) ─► llama-swap (AMD, lokal)
                                              │  lokale Modelle wie bisher
                                              └─ peers: cluster-<m> ─► http://cluster-lb:8090/m/cluster-<m>
cluster-lb (Caddy-Container, generiert):
   /m/cluster-qwen3.5-35b-a3b/*  ─► Knoten [A, B, C]   (Stammknoten A)
   /m/cluster-glm-4.7-flash/*    ─► Knoten [B, C, A]   (Stammknoten B)
   /m/cluster-qwen3.5-9b/*       ─► Knoten [C, A, B]   (Stammknoten C)
Win-Knoten: llama-swap.exe :8080 mit ALLEN cluster-Modellen, API-Key, Firewall nur Gateway-IP

Routing ohne unnötige Modellwechsel: Für jedes Modell enthält der Pool alle Knoten, aber in rotierter Reihenfolge. Dazu kommen lb_policy first und unhealthy_request_count <Slots>:

  • Eine Anfrage geht an den Stammknoten des Modells. Dort ist das Modell meist schon geladen und der Prompt-Cache noch vorhanden.
  • Ist der Stammknoten belegt oder ausgefallen, geht sie an den nächsten Knoten. Der lädt das Modell dann, das ist der flexible Fall.
  • Sind alle Knoten belegt, wartet Caddy (lb_try_duration 5m, lb_try_interval 1s), bis einer frei wird.
  • Health-Checks auf /health alle 10 s, ausgefallene Knoten werden übersprungen.
  • Wenn es mehr Modelle als Knoten gibt, teilen sich Modelle einen Stammknoten (Zuordnung reihum).

Änderungen auf dem Gateway (Linux)

  1. docker-compose.yml: neuer Service cluster-lb (caddy:${CADDY_TAG}), nur im Compose-Netz und ohne Port nach außen. Er mountet caddy/cluster.Caddyfile. llama-swap bekommt CLUSTER_API_KEY als Umgebungsvariable.
  2. Neu scripts/cluster-gen.sh: liest CLUSTER_NODES="192.168.178.50:8080 192.168.178.51:8080 …" und CLUSTER_SLOTS (Standard 1) aus .env. Die Modellliste kommt aus cluster/windows/node-config.yaml (einzige Quelle, Schlüssel cluster-*). Daraus erzeugt das Skript caddy/cluster.Caddyfile (in .gitignore), mit einem handle_path /m/<modell>/* und rotierter Upstream-Liste pro Modell. Ohne Knoten entsteht eine gültige Datei, die 503 liefert. scripts/start.sh ruft das Skript vor docker compose up auf.
  3. llama-swap/config.yaml: Block peers: mit einem Eintrag pro Cluster-Modell, proxy: http://cluster-lb:8090/m/<modell>, apiKey: ${env.CLUSTER_API_KEY}, dazu großzügige timeouts.responseHeader für Modellwechsel und Download.
  4. .env.example / .env: CLUSTER_API_KEY (start.sh erzeugt ihn wie LLM_API_KEY, wenn er fehlt), CLUSTER_NODES, CLUSTER_SLOTS.
  5. Neu scripts/cluster-status.sh: zeigt pro Knoten /health, das geladene Modell (/running) und die Antwortzeit. start.sh zeigt die Ausgabe im Statusblock, wenn Knoten gesetzt sind.

Windows-Knoten-Skripte (cluster/windows/)

  • install-node.ps1 (als Admin; wiederholbar, dient auch zum Update): Parameter -GatewayIP und -ApiKey (Pflicht), -Port 8080, -InstallDir C:\localai-node, -LlamaBuild latest, -Cuda auto|12.4|13.4, -Preload <modell> (optional), -HfToken, -KeepSleep, -Force.
    1. Prüft Admin-Rechte, nvidia-smi (GPU, VRAM, Compute Capability, Treiber) und freien Speicherplatz (Warnung unter 60 GB, weil alle Modelle auf jedem Knoten liegen können).
    2. CUDA-Variante: 13.4, wenn Compute Capability ≥ 7.5 und Treiber ≥ 580, sonst 12.4.
    3. Lädt llama-swap und llama.cpp CUDA + cudart herunter, entpackt sie nach bin\ und testet llama-server.exe --version.
    4. Schreibt config.yaml aus der Vorlage. Bestehende Dateien bleiben unangetastet, außer bei -Force.
    5. Erzeugt start-node.cmd: setzt LLAMA_CACHE, HF_TOKEN und NODE_API_KEY, startet llama-swap.exe -config … -listen 0.0.0.0:PORT und schreibt das Log nach logs\.
    6. Firewall-Regel: TCP-Port eingehend nur von GatewayIP.
    7. Geplante Aufgabe LocalAI-Node: Start beim Booten, als SYSTEM, ohne Laufzeitlimit, Neustart bei Absturz.
    8. Standby am Netzteil aus (powercfg), außer bei -KeepSleep.
    9. Startet die Aufgabe, wartet auf /health und gibt die Zeile für CLUSTER_NODES aus.
  • download-models.ps1 (optional): lädt alle Cluster-Modelle vorab herunter, indem es jedes Modell einmal kurz anfragt. So dauert die erste echte Anfrage nicht wegen eines 20-GB-Downloads.
  • node-config.yaml (Vorlage, Quelle der Modellliste): alle Cluster-Chat-Modelle (cluster-qwen3.5-35b-a3b, cluster-glm-4.7-flash, cluster-qwen3.5-9b) in einer Swap-Gruppe, Pfade mit /, --fit mit --fit-target 1024 (passt sich automatisch an den VRAM des Knotens an), -np <Slots>. Dazu apiKeys: ["${env.NODE_API_KEY}"], optional Preload, healthCheckTimeout: 3600.
  • install-node.cmd: Doppelklick-Wrapper, fordert Admin-Rechte an, setzt -ExecutionPolicy Bypass und fragt Gateway-IP und Key ab.
  • status-node.ps1: Zustand der Aufgabe, /health, /running, nvidia-smi, letzte Logzeilen.
  • uninstall-node.ps1: entfernt Aufgabe und Firewall-Regel und löscht optional das Verzeichnis (-KeepModels).

Doku

  • Neu docs/cluster.md: Architektur und Routing (Stammknoten, Überlauf), Ablauf (Key → Knoten installieren → IP in CLUSTER_NODES → ./scripts/start.sh), Modell hinzufügen (nur in node-config.yaml + peers, dann Knoten mit -Force aktualisieren). Grenzen: Ein Modell, das größer als VRAM+RAM eines Knotens ist, wird nicht über mehrere PCs verteilt. Kleine GPUs lagern per --fit in den RAM aus und sind dann langsamer.
  • README.md: Abschnitt „Cluster“ mit Link, Dateiliste ergänzen. Die Client-Doku nennt die cluster-*-Modellnamen.

Verifikation

  1. cluster-gen.sh mit 0, 1 und 3 Test-Knoten ausführen und jeweils caddy validate (Docker, caddy:2.10.2-alpine) laufen lassen. Außerdem docker compose config.
  2. Kette ohne Windows testen: Als Fake-Knoten dient eine zweite llama-swap-Linux-Binary bzw. ein Echo-Server im Scratchpad, auf zwei Ports. Prüfen:
    • /v1/models zeigt cluster-*.
    • Die Anfrage läuft über :8800 → peer → cluster-lb → Stammknoten.
    • Streaming funktioniert.
    • Bei belegtem Stammknoten geht die Anfrage an den nächsten Knoten.
    • Ein gestoppter Knoten wird übersprungen.
  3. PowerShell-Syntax mit pwsh parsen, falls verfügbar. Den echten Lauf auf einem Windows-PC macht der Nutzer; der Installer endet mit einem Selbsttest (/health).
  4. Der bestehende Betrieb bleibt unverändert: ./scripts/start.sh startet weiter erfolgreich, auch ohne Knoten.