142 lines
8.4 KiB
Markdown
142 lines
8.4 KiB
Markdown
# 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 <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 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_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 |
|