LocalAI/README.md

8.4 KiB
Raw Blame History

LocalAI – zentraler LLM-Server für opencode, Continue & Laravel

Ein Docker-Compose-Stack, der auf alegab-pc (RX 6950 XT 16 GB, Ryzen 9 7900X3D, 62 GB RAM) eine OpenAI-kompatible API bereitstellt. Clients im Heim-LAN und über NetBird nutzen sie zum Programmieren, ganz ohne Cloud-KI.

Client-PC (opencode / VS Code + Continue) ──LAN / NetBird──┐
                                                           ▼
   caddy  (Host-Netz, :8800)   IP-Filter + API-Key
      └─► llama-swap  (nur 127.0.0.1:8080)   lädt/wechselt Modelle
             ├─► llama-server   (llama.cpp, ROCm)    Chat, Autocomplete, Embeddings
             ├─► whisper-server (whisper.cpp, Vulkan) Transkription
             └─► sd-server      (stable-diffusion.cpp, Vulkan) Bilder
                   Modelle: ./models (LLMs per -hf beim ersten Aufruf, whisper/ und sd/ manuell)
Modell-ID Zweck Verhalten
qwen3.5-35b-a3b Hauptmodell Chat/Agent (MoE) eines der Chat-Modelle gleichzeitig geladen
glm-4.7-flash Alternative Chat/Agent (MoE) 〃
qwen3.5-9b schnelles kleines Chat-Modell 〃
qwen2.5-coder-1.5b Autocomplete (FIM) für Continue dauerhaft geladen (GPU)
nomic-embed Embeddings für Continue @codebase dauerhaft geladen (CPU)
whisper Transkription (/v1/audio/transcriptions), Alias whisper-1 dauerhaft geladen (GPU, ~1,2 GB)
z-image Bilder (/v1/images/generations), Aliase gpt-image-1, dall-e-3, dall-e-2 verdrängt das Chat-Modell (Gruppe chat)

Server einrichten

cd ~/workspace/localAI
cp .env.example .env
sed -i "s/^LLM_API_KEY=.*/LLM_API_KEY=$(openssl rand -hex 32)/" .env
chmod 600 .env
$EDITOR .env            # ALLOWED_NETS, GIDs (getent group render video), UID/GID prüfen
sudo ./scripts/firewall-ufw.sh   # einmalig: Port 8800 für LAN + wt0 in ufw öffnen
./scripts/start.sh               # prüfen, starten, Modelle vorladen, Verbindungsdaten zeigen

scripts/start.sh legt eine fehlende .env beim ersten Start auch selbst an: mit neuem API-Key, UID/GID und den GIDs von render/video. Weitere Optionen:

Aufruf Wirkung
./scripts/start.sh starten, Autocomplete + Embeddings + Hauptmodell vorladen
./scripts/start.sh --no-preload nur starten
./scripts/start.sh --model glm-4.7-flash anderes Chat-Modell vorladen
./scripts/start.sh --show-key API-Key im Klartext anzeigen (für Client-Setup)
./scripts/start.sh --pull Basis-Images aktualisieren und eigenes Image neu bauen

Nach einem Reboot startet der Stack von selbst: Der Docker-Dienst ist aktiviert, und die Container haben restart: unless-stopped. Autocomplete und Embeddings lädt llama-swap per hooks.on_startup.preload ebenfalls von selbst, das Chat-Modell kommt beim ersten Request dazu.

Der erste Aufruf eines Modells lädt es von Hugging Face herunter (Hauptmodell ~22 GB). Danach läuft alles offline.

Whisper und das Bildmodell lädt llama-swap nicht selbst herunter (~12 GB):

mkdir -p models/whisper models/sd
HF=https://huggingface.co
curl -L -o models/whisper/ggml-large-v3-turbo-q8_0.bin $HF/ggerganov/whisper.cpp/resolve/main/ggml-large-v3-turbo-q8_0.bin
curl -L -o models/sd/z_image_turbo-Q8_0.gguf $HF/leejet/Z-Image-Turbo-GGUF/resolve/main/z_image_turbo-Q8_0.gguf
curl -L -o models/sd/ae.safetensors $HF/Comfy-Org/z_image_turbo/resolve/main/split_files/vae/ae.safetensors
curl -L -o models/sd/Qwen3-4B-Instruct-2507-Q8_0.gguf $HF/unsloth/Qwen3-4B-Instruct-2507-GGUF/resolve/main/Qwen3-4B-Instruct-2507-Q8_0.gguf

Warum ein eigenes Image?

whisper-server und sd-server gibt es nur im „unified“-Image von llama-swap, und das für AMD nur als Vulkan-Build. Unter Vulkan ist die Prompt-Verarbeitung von llama.cpp auf der RX 6950 XT aber nur halb so schnell wie unter ROCm (~555 statt ~1030 t/s). llama-swap/Dockerfile nimmt deshalb das ROCm-Image als Basis und kopiert die beiden Server aus dem unified-Image dazu. docker compose up -d --build bzw. scripts/start.sh bauen es automatisch. Beide Tags stehen in .env (LLAMA_SWAP_TAG, UNIFIED_TAG). Bei einem Update beide auf dieselbe llama-swap-Version setzen und ./scripts/start.sh --pull ausführen.

Vulkan zählt die iGPU des Ryzen mit. VK_VISIBLE_DEVICES=1 in .env wählt die RX 6950 XT.

Warum Caddy im Host-Netz?

Ports, die Docker veröffentlicht, umgehen ufw. Deshalb ist llama-swap nur an 127.0.0.1 gebunden. Caddy läuft mit network_mode: host, dort greift ufw, und Caddy sieht die echte Quell-IP. Daraus ergeben sich zwei Schutzschichten:

  1. ufw lässt Port 8800 nur aus dem LAN und über wt0 zu.
  2. Caddy prüft zusätzlich die Quell-IP (ALLOWED_NETS) und den Bearer-Token (LLM_API_KEY).

Die API läuft über plain HTTP. Über NetBird ist das unkritisch (WireGuard verschlüsselt den Tunnel), im Heim-LAN ist es akzeptabel.

Clients einrichten

Schnelltest von jedem Client:

curl -H "Authorization: Bearer <KEY>" http://<server>:8800/v1/models

Betrieb

docker compose ps                     # Status
docker compose logs -f llama-swap     # Modell-Laden, Requests
docker compose logs -f caddy          # Zugriffe
docker compose restart                # nach Änderungen an .env / Caddyfile
docker compose up -d --build          # nach Änderungen an llama-swap/Dockerfile oder den Image-Tags
  • llama-swap/config.yaml wird automatisch neu geladen, ein Neustart ist nicht nötig.
  • Web-Oberfläche von llama-swap (Modellstatus, Logs): http://127.0.0.1:8080/ui, nur lokal auf dem Server erreichbar.
  • VRAM-Auslastung: rocm-smi --showmeminfo vram

Modell hinzufügen

Einen Eintrag in llama-swap/config.yaml anlegen, z. B.

  mein-modell:
    cmd: ${server} ${agent-ctx} -hf <hf-user>/<repo>-GGUF:Q4_K_M
    ttl: 3600

Den Eintrag unter routing.router.settings.groups.chat.members aufnehmen. Danach die Client-Configs (client/opencode.json, client/continue/config.yaml) um die neue Modell-ID ergänzen.

GPU-Layer und MoE-Auslagerung in den RAM verteilt llama.cpp automatisch (--fit, Standard: an). --fit-target 3072 hält ~3 GB VRAM für das Autocomplete-Modell frei.

Auf einen anderen PC übertragen

  1. Den Ordner kopieren. models/ kann man mitnehmen, um den Download zu sparen.

  2. .env anpassen: ALLOWED_NETS (LAN-Subnetz, NetBird-Netz), RENDER_GID/VIDEO_GID, HOST_UID/HOST_GID.

  3. Je nach GPU das Image in .env wählen (LLAMA_SWAP_TAG). Das eigene Image (llama-swap/Dockerfile) ist für ROCm gebaut. Bei NVIDIA oder Vulkan nimmt man einfacher direkt das passende unified-…-Image (enthält whisper-/sd-server schon) und ersetzt build: in docker-compose.yml durch image: ghcr.io/mostlygeek/llama-swap:unified-…. VK_VISIBLE_DEVICES an die Geräte-Reihenfolge anpassen.

    GPU Tag-Variante
    AMD (ROCm) vXXX-rocm-bYYYY (wie hier)
    NVIDIA vXXX-cuda-bYYYY. Dann in docker-compose.yml devices/group_add/security_opt durch deploy.resources.reservations.devices (nvidia) ersetzen.
    herstellerunabhängig vXXX-vulkan-bYYYY (nur /dev/dri nötig)
    nur CPU vXXX-cpu-bYYYY

    Verfügbare Tags: https://github.com/mostlygeek/llama-swap/pkgs/container/llama-swap

  4. Threads (--threads im Makro server) an die CPU anpassen.

  5. Firewall des Ziel-PCs entsprechend scripts/firewall-ufw.sh konfigurieren.

Dateien

Datei Inhalt
docker-compose.yml Dienste llama-swap + caddy
llama-swap/Dockerfile ROCm-Image + whisper-server/sd-server
.env / .env.example Secrets, Netze, Ports, GIDs, Image-Tags
llama-swap/config.yaml Modelle, Startparameter, Gruppen
caddy/Caddyfile IP-Filter, API-Key, Reverse Proxy
client/opencode.json Vorlage für opencode-Clients
client/continue/ Vorlage für Continue-Clients
docs/laravel.md Anbindung einer Laravel-App (laravel/ai)
scripts/start.sh Start mit Prüfungen, Preload und Statusausgabe
scripts/firewall-ufw.sh ufw-Freigaben
PLAN.md ursprünglicher Umsetzungsplan
PLAN-multimodal.md Plan + Messwerte für Transkription/Bilder