# 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 ```bash 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): ```bash 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`](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 - **opencode** (Terminal-Agent): [docs/opencode.md](docs/opencode.md) - **Continue** (VS-Code-Plugin): [docs/continue.md](docs/continue.md) - **Laravel** (laravel/ai – Agent, Spracheingabe, Bilder): [docs/laravel.md](docs/laravel.md) Schnelltest von jedem Client: ```bash curl -H "Authorization: Bearer " http://:8800/v1/models ``` ## Betrieb ```bash 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. ```yaml mein-modell: cmd: ${server} ${agent-ctx} -hf /-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`](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 |