LocalAI/README.md

146 lines
9.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# LocalAI – zentraler LLM-Server für opencode, Continue, Laravel & Web-Chat
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 / Browser) ──LAN / NetBird──┐
▼
caddy (Host-Netz) :8800 API: IP-Filter + API-Key :8801 Web-Chat: IP-Filter
├─► open-webui (nur 127.0.0.1:3000) Web-Chat mit eigenem Login ──┐
└─► 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: Ports 8800 + 8801 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, WebUI-Schlüssel, 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 sieht auch die iGPU des Ryzen, und die Geräte-Reihenfolge wechselt zwischen Reboots. `VK_DEVICE=1002:73a5` in `.env` (PCI-ID der RX 6950 XT, `lspci -nn | grep -i vga`) lässt Mesa nur diese Karte anzeigen. Kontrolle: `curl -s 127.0.0.1:8080/logs/stream/whisper | grep ggml_vulkan` muss die RX 6950 XT zeigen.
### 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 die Ports 8800 (API) und 8801 (Web-Chat) nur aus dem LAN und über `wt0` zu.
2. **Caddy** prüft zusätzlich die Quell-IP (`ALLOWED_NETS`), bei der API außerdem den Bearer-Token (`LLM_API_KEY`). Beim Web-Chat übernimmt der Login von Open WebUI diese Rolle.
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)
- **Web-Chat** (Open WebUI im Browser, `http://<server>:8801`): [docs/webchat.md](docs/webchat.md)
Schnelltest von jedem Client:
```bash
curl -H "Authorization: Bearer <KEY>" http://<server>: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 logs -f open-webui # Web-Chat
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 <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`](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_DEVICE` auf die PCI-ID der neuen GPU setzen.
| 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, open-webui, 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 (API + Web-Chat) |
| `docs/webchat.md` | Web-Chat (Open WebUI) |
| `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 |