diff --git a/docs/auftraege/2026-10-12-sprachmodelle-nex.md b/docs/auftraege/2026-10-12-sprachmodelle-nex.md index ef1649e..81317e6 100644 --- a/docs/auftraege/2026-10-12-sprachmodelle-nex.md +++ b/docs/auftraege/2026-10-12-sprachmodelle-nex.md @@ -75,3 +75,244 @@ Je Dienst: Basis-URL (IP:Port) · Knoten und GPU · Software mit Version oder Im ### Gegenseite (NEX, zur Information) Die Agenten fragen nur noch neutrale Namen an: `nex-stt` für die Erkennung, `nex-tts` für die Ausgabe. Welches Backend dahinter antwortet und in welcher Reihenfolge, legt allein LiteLLM fest: heute das Cluster, später ein Cloud-Anbieter davor und das Cluster als Rückfall. nex-router 0.2.3 wird um die Sprachausgabe (gestreamt) erweitert. Nach der Rückmeldung trägt das NEX-Team die Endpunkte in LiteLLM ein und testet die Kette. Live erst mit dem neuen Router-Image. + +## Rückmeldung (Stand 12.10.2026, Task 6.2) + +Umgesetzt in `gpu-pve52` (VM 252 auf pve52). Getestet in der VM (localhost) und von ops mit einer temporären ufw-Regel, die danach wieder entfernt wurde. Von appsrv08 noch **nicht** geprüft, das bleibt für das NEX-Team. Testaudio kam aus Piper (zwei Sätze zur Baubesprechung, keine Kundendaten) und wurde danach gelöscht. + +**Feste IP fehlt (D5):** +- Die VM hat ihre Adresse per DHCP: heute `192.168.100.174`, MAC `bc:24:11:92:4b:eb`. +- Die Adresse hat den Neustart überstanden, ist aber nicht garantiert. +- Vor dem Eintrag in LiteLLM: Reservierung auf diese MAC einrichten oder eine statische Adresse vergeben. + +**API-Key:** Keiner nötig, kein Dummy-Wert. Beide Dienste ignorieren das Feld `model`, jeder Wert geht. + +### Erkennung (`nex-stt`) + +**Basis-URL:** `http://192.168.100.174:8178` +- Pfad: `POST /v1/audio/transcriptions` +- Zustand: `GET /health` → `{"status":"ok"}`, solange das Modell lädt: 503 + +**Knoten und GPU:** pve52, VM `gpu-pve52`, Quadro M4000 (8 GB, CC 5.2), Treiber 580.178.04 + +**Software:** +- whisper.cpp `whisper-server` **v1.9.4** (Commit `927cfce34f31707e17f2bff35c349632fb9e2c3a`) +- Build: CUDA 12.6.3, `-DGGML_CUDA=ON -DCMAKE_CUDA_ARCHITECTURES="52;61"`, statisch gelinkt +- Image `knaster/nex-whisper:v1.9.4-cu12.6`, ID `sha256:be4987ff63df…` +- Basis-Images per Digest gepinnt: `nvidia/cuda:12.6.3-{devel,runtime}-ubuntu24.04` +- ffmpeg 6.1.1 für `--convert` + +**Modell:** `ggml-large-v3-turbo.bin` (f16) +- Quelle: HF `ggerganov/whisper.cpp`, Revision `5359861c…` +- sha256 `1fc70f77…e2bc69`, gegen Hugging Face geprüft +- Feld `model`: beliebig, z. B. `whisper` oder `nex-stt` + +**Parameter:** +- `-l de -bs 5 --convert --inference-path /v1/audio/transcriptions --tmp-dir /run/nex-whisper` +- `--tmp-dir` liegt auf tmpfs (512 MB) +- ohne `--print-realtime`, Flash-Attention an +- Nach jedem Start wird mit 1 s Stille aufgewärmt. + +**Eingangsformate:** wav, mp3, ogg/opus, webm/opus und m4a/aac, alle über ffmpeg getestet. + +**`response_format`:** +- `json` → `{"text": "…"}` +- `text` +- `verbose_json`: Segmente mit `words[].probability`, `avg_logprob`, `no_speech_prob`, dazu `language_probabilities` +- außerdem `srt` und `vtt` + +**Unbekannte Felder werden ignoriert.** Geprüft mit `model=gpt-4o-transcribe`, `timestamp_granularities[]=word`, `foo=bar` und `stream=false`, Antwort jeweils 200. + +**PCM-Sample-Rate:** entfällt; ffmpeg wandelt intern auf 16 kHz mono. + +**Messwerte (M4000, `-bs 5`):** + +| Messung | Ergebnis | +|---|---| +| 10 s Audio, erster Aufruf nach Start und Aufwärmen | 3,00 s (nach `docker restart`), 2,94 s (erster Aufruf der Abnahmeserie) | +| 10 s Audio, Median aus 3 Läufen | **2,94 s** (Grenze 4 s, Ziel ≤ 3 s erreicht) | +| 60 s Audio, erster Aufruf | 8,63 s | +| 60 s Audio, Median aus 3 Läufen | **8,63 s** (Grenze 12 s) | +| 5 s Audio, von ops über das Netz | 2,43 s | +| Start bis bereit (Modell im Cache, inkl. Aufwärmen) | 4,1 s | +| VRAM | 1965 MiB nach dem Laden, 2179 MiB ab der ersten Erkennung, danach konstant | +| RAM des Containers | 286 MiB | +| Leerlauf der Karte | P5, 17 W | + +**Last mit 2 Erkennungen gleichzeitig (je 60 s):** +- Antworten nach 8,8 s und 17,5 s. whisper-server arbeitet sie nacheinander ab (globaler Mutex), die zweite wartet. +- GPU-Last stieg auf 100 % (Leerlauf 0 %, Mittel 78 %). +- **Der VRAM steigt nicht**, er bleibt bei 2179 MiB, weil der Kontext einmal geladen wird. + +**Autostart nach Neustart getestet: ja.** Die VM wurde über Proxmox neu gestartet (`qm reboot 252`). 34 s nach dem Boot lieferten beide `/health` 200, die erste Erkennung danach war normal. + +**Auffälligkeiten:** +1. **Gleichzeitige Anfragen laufen nacheinander** (siehe Last). +2. **Mindestdauer etwa 2,3 s pro Aufruf**, auch bei kurzen Clips. Der Encoder rechnet immer ein 30-s-Fenster. +3. **Format der `json`-Antwort:** Der Text beginnt mit einem Leerzeichen und hat Zeilenumbrüche zwischen den Segmenten. Für Hermes ggf. `strip()` bzw. Umbrüche durch Leerzeichen ersetzen. +4. **Content-Type bei `text`:** `text/html; charset=utf-8`. +5. **Qualität am synthetischen Testaudio:** + - Sauber: „Baubesprechung in Haslach ist am 4. November um 10 Uhr“. + - „NEX“ wurde meist als „NX“ erkannt. + - Im Teil mit der leisen Kerstin-Stimme gab es einzelne Fehler, z. B. „Baugüro“ und „Baulätung“. + - Echte Aufnahmen und die Dialekt-Testsätze stehen in Task 3.3 an. +6. **Log:** Es enthält nur den Upload-Dateinamen und die Audiolänge (z. B. `Received request: t10.wav`), **keinen Text**. Gegen den Testtext gegrepped: 0 Treffer. + - ffmpeg läuft mit `-loglevel error`. + - Log-Rotation: Treiber `local`, 2 × 5 MB. +7. **Temp-Dateien:** + - Nur im tmpfs, nach jeder Anfrage gelöscht; nach den Tests 0 Dateien. + - Bei ffmpeg-Fehlern bleibt eine Upload-Datei liegen. Ein Aufräumer löscht Dateien älter als 10 min. +8. **Endpunkt `POST /load`:** Er existiert (Modellwechsel). Erreichbar ist er nur von `.29`, und im Container liegt nur dieses eine Modell. +9. **Proxmox-Firewall:** Abweichend vom Abgleich oben ist sie **nicht** aktiviert (Vorgabe). Es gilt nur **ufw in der VM**: + - 8178/8179 nur von `192.168.100.29` + - SSH aus `192.168.100.0/24` + - sonst `deny incoming` + - Beide Dienste laufen im Host-Netz, weil veröffentlichte Docker-Ports ufw umgehen würden. + - Negativtest von ops (.173): ohne Regel Zeitüberschreitung auf beiden Ports. + +### Ausgabe (`nex-tts`) + +**Basis-URL:** `http://192.168.100.174:8179` +- Pfad: `POST /v1/audio/speech` +- Zustand: `GET /health` → `{"status":"ok","voices":[…],"default":"de_DE-thorsten-high"}` +- außerdem `GET /v1/models` und `GET /v1/audio/voices` + +**Knoten und Hardware:** pve52, VM `gpu-pve52`, nur CPU. Die VM hat 4 Kerne (i7-7700); der Container ist auf 3 Kerne und 2 GB begrenzt. + +**Software:** +- eigener schlanker Wrapper: FastAPI 0.141.1, uvicorn 0.53.0, piper-tts **1.8.0** (GPL-3.0, eigener Dienst), onnxruntime 1.30.0, ffmpeg 5.1.9 +- Image `knaster/nex-tts-piper:1.8.0`, ID `sha256:d9e65ee77836…` +- Basis `python:3.12-slim-bookworm`, per Digest gepinnt + +**Warum nicht speaches:** Der Wrapper ist klein und gut zu prüfen. Er streamt satzweise, setzt die PCM-Header nach Vorgabe und schreibt weder Text noch Audio auf die Platte. Der bisherige knaster-`tts_server` konnte nur wav. + +**Modellname (`model`):** beliebig, z. B. `piper` oder `nex-tts`. + +**Stimmen-IDs:** +- `de_DE-thorsten-high`: männlich, 22050 Hz, CC0, Standard +- `de_DE-kerstin-low`: weiblich, 16000 Hz, Datensatz CC0 +- Dateien aus HF `rhasspy/piper-voices`, Revision `c10ece1a…`, sha256 geprüft. +- Kurzformen `thorsten` und `kerstin` gehen ebenfalls. +- Unbekannte Stimmen (z. B. `alloy`) fallen ohne Fehler auf die Standardstimme. Der Header `X-Voice` nennt die verwendete Stimme. + +**Weitere weibliche Stimmen**, nicht genommen: +- `de_DE-ramona-low` und `de_DE-eva_k-x_low`: M-AILABS, Lizenz „See URL“, kommerzielle Nutzung nicht belegt +- `de_DE-mls-medium`: mehrere Sprecher + +**`response_format`:** +- Standard ist `mp3` (`audio/mpeg`). +- `opus` (Ogg, 48 kHz, `audio/ogg`), `aac`, `flac` und `pcm` werden **gestreamt** (chunked). Die Header kommen erst mit dem ersten Audioblock, deshalb misst TTFB echtes Audio. +- `wav` kommt vollständig mit korrektem Header; das passt zu `knaster bench tts` und zum Canary. +- `speed` (0,25–4) wird auf `length_scale` abgebildet. +- Unbekannte Felder werden ignoriert. +- `input` ist auf 5000 Zeichen begrenzt. + +**PCM-Sample-Rate:** **24000 Hz** für alle Stimmen, wie OpenAI. +- Format: int16 LE, mono, umgerechnet per ffmpeg. +- Header: `X-Audio-Sample-Rate: 24000` und `Content-Type: audio/pcm; rate=24000`. +- Mit `PCM_SAMPLE_RATE=0` käme die native Rate der Stimme. + +**Messwerte (Auftragssatz, thorsten-high, je 3 Läufe):** + +| Format | TTFB | gesamt | Audio | +|---|---|---|---| +| mp3 | 0,67–0,91 s | 1,42–1,68 s | 5,25 s | +| pcm | 0,72–0,87 s | 1,48–1,62 s | 5,31 s | +| opus | 0,64–0,72 s | 1,54–1,61 s | 5,28 s | + +- Von ops über das Netz: mp3 TTFB 0,72 s. +- Erste Synthese nach einem Neustart (wav, 10 s Audio): 2,7 s gesamt. Die Stimmen werden beim Start aufgewärmt. +- RAM: 616 MiB. Kein VRAM. + +**Autostart nach Neustart getestet: ja**, wie bei der Erkennung. + +**Auffälligkeiten:** +1. **Log:** kein Access-Log, Level `warning`. + - Gegen den Testtext gegrepped: 0 Treffer. + - Piper meldet gelegentlich `Missing phoneme from id map` (ohne Text). + - onnxruntime meldet beim Start eine harmlose Telemetrie-Warnung. +2. **Gleichzeitige Synthesen:** höchstens 2 (`TTS_PARALLEL`). Die CPU teilt sich der Dienst mit whisper-server, das die CPU aber kaum nutzt. +3. **Ablageort:** Laut Registry (`tts-de`) gehört Piper in den Infra-LXC. Vorerst läuft es hier. + +### Stufe 2 (nur geprüft, nichts installiert) + +**PyTorch auf der M4000:** +- In einem Wegwerf-Container geprüft: `torch 2.9.1+cu126`, `get_arch_list()` = `sm_50, sm_60, sm_70, sm_75, sm_80, sm_86, sm_90`. +- Die M4000 (5, 2) rechnet eine Matrixmultiplikation korrekt. +- **2.9.1 ist die letzte Version mit cu126-Wheels.** Laut den Build-Skripten haben cu128/cu129/cu130 kein sm_5x/sm_6x mehr, also ist das die Obergrenze für Maxwell/Pascal. + +**Chatterbox Multilingual (ResembleAI):** +- Lizenz **MIT** (HF-Karte), Deutsch enthalten. +- Läuft grundsätzlich mit PyTorch ≤ 2.9.1 auf sm_52, aber ohne Tensor-Cores in fp32. +- Langsam erwartet. Nicht gemessen. + +**Kokoro-82M:** `VOICES.md` listet **keine deutsche Stimme**, nur en, ja, zh, es, fr, hi, it und pt-br. + +**XTTS-v2 und Fish Speech:** nicht geprüft, laut NEX-Team nicht kommerziell. + +**Empfehlung:** Stufe 2 mit `tts-de-hq` (Qwen3-TTS) auf der Ada (Task 6.4) statt auf der M4000. + +### Notiz für Task 3.3 (Benchmark M4000, whisper) + +**Ergebnis:** +- CC 5.2 **rechnet** mit dem CUDA-12.6-Build. `system_info` zeigt `CUDA : ARCHS = 520,610`. +- `large-v3-turbo` f16 mit `-bs 5`: 10 s Audio in 2,94 s (RTF 0,29), 60 s in 8,63 s (RTF 0,14). +- Mit `-nfa` (Flash-Attention aus) sind es 3,21 s für 10 s, also bleibt Flash-Attention an. +- Speicher: VRAM 2,2 GB, RAM 0,3 GB. + +**Noch offen:** +- P2000 nicht gemessen +- Dialekt-Testsätze +- `primeline/whisper-large-v3-turbo-german`: bräuchte eine ggml-Umwandlung +- Strom unter Last + +**Offener Punkt Registry:** Den Slot `stt-de` habe ich **nicht** angelegt. +- `SlotSpec.kind` (core `registry.py`) kennt kein `stt`, Placement (`placement.py`) und Canary ebenfalls nicht. +- Ohne Core-Änderung schlägt `knaster validate` fehl, und der Canary würde den Slot als Chat proben. +- Gehört zu 3.3 bzw. 5.2, zusammen mit dem Umzug von `tts-de`. + +### Betrieb + +**Dateien im Repo:** +- `deploy/compose/nex-speech/` (Compose, `.env.example`, Dockerfiles, `piper/app.py`) +- Rolle `platform/proxmox/ansible/roles/nex-speech` +- Playbook `platform/proxmox/ansible/playbooks/nex-speech.yml`, nur für `gpu-pve52` + +**Ausrollen von ops:** + +```sh +cd /root/cluster-knaster && git pull +cd platform/proxmox/tofu && . /root/.config/knaster/tofu.env && bash ../scripts/refresh-vm-access.sh +cd ../ansible && ansible-playbook playbooks/nex-speech.yml +``` + +**Was die Rolle macht:** +- ufw-Regeln setzen und ufw einschalten +- `docker-buildx` installieren +- Modelle nach `/srv/models/{whisper,piper}` laden, mit sha256 +- Compose nach `/opt/nex-speech` kopieren, `.env` aus den Rollen-Variablen schreiben +- bauen und starten, danach auf `/health` beider Dienste warten +- Ein zweiter Lauf ändert nichts (`changed=0`). + +**Build:** +- Der erste whisper-Build dauert in der VM etwa 15 min. +- Das devel-Image (11,5 GB) bleibt als Cache liegen. Freier Platz auf `/` danach: 18 GB. + +**Prüfen in der VM:** +- `curl -s localhost:8178/health; curl -s localhost:8179/health` +- `sudo docker compose -f /opt/nex-speech/docker-compose.yml ps` +- Logs mit `sudo docker logs nex-stt` bzw. `nex-tts`. Sie enthalten keine Texte. + +**Firewall in der VM:** +- `sudo ufw status` +- Zum Testen von einem anderen Rechner eine Regel nur vorübergehend setzen: `sudo ufw insert 1 allow from to any port 8178:8179 proto tcp`, danach `sudo ufw delete allow from to any port 8178:8179 proto tcp`. +- Weitere Compose-Stacks in dieser VM mit veröffentlichten Ports (`ports:`) umgehen ufw. Für Sprachdienste deshalb im Host-Netz bleiben. + +**Updates:** +- Ubuntu-Sicherheitsupdates laufen per `unattended-upgrades`. Der NVIDIA-Treiber ist gehalten. +- Images sind gepinnt. Ein Wechsel geht nur bewusst über die Rollenvariablen `nex_speech_env`: + - whisper: `WHISPER_TAG` und `WHISPER_COMMIT` gemeinsam + - Piper: `requirements.txt` und `PIPER_TTS_VERSION` +- Danach das Playbook erneut laufen lassen. + +**Parameter ändern:** In `nex_speech_env`, z. B. `WHISPER_EXTRA_ARGS: "-nfa"` oder `PCM_SAMPLE_RATE: "0"`, dann das Playbook laufen lassen. diff --git a/docs/superpowers/plans/2026-10-12-aufbau-cluster-knaster.md b/docs/superpowers/plans/2026-10-12-aufbau-cluster-knaster.md index 94f9d5c..deeec29 100644 --- a/docs/superpowers/plans/2026-10-12-aufbau-cluster-knaster.md +++ b/docs/superpowers/plans/2026-10-12-aufbau-cluster-knaster.md @@ -349,6 +349,9 @@ Ersetzt PLAN §8 „1 Data Plane“. - [ ] **6.1 GPU-Rollen:** `deploy/compose/gpu-llamacpp` in den GPU-VMs nach der Zuordnung aus Task 3.3, also Embedding und Rerank redundant auf zwei Knoten, Chat für verifier und signature. Prüfung: Canary-Checks grün (`knaster` Canary-Prober). - [ ] **6.2 STT und TTS für NEX** – Auftrag vom 12.10. mit Abgleich: `docs/auftraege/2026-10-12-sprachmodelle-nex.md` (Erkennung whisper.cpp auf einer M4000, naheliegend `gpu-pve52`; Ausgabe Piper auf CPU, Port 8179; Abnahme und Rückmeldeformat dort). Läuft nach Task 3.2, gern als Subagent. **Blockiert für die Abnahme:** feste IP (D5). Bisheriger Text (nex-STT-Plan Tasks 2–4): `whisper-server` als zusätzlicher Dienst in der GPU-VM des in Task 3.3 gewählten Knotens. Neuer Slot `stt-de` im Katalog. Port 8178 nur von `192.168.100.29` (Firewall der VM bzw. des Gasts). Prüfung: Von appsrv08 liefert `curl -F file=@probe.wav http://:8178/v1/audio/transcriptions` deutschen Text, von einem anderen Rechner wird der Zugriff abgelehnt. + - [x] (12.10.) Umgesetzt in `gpu-pve52`: `nex-stt` (whisper.cpp v1.9.4, CUDA 12.6, sm_52/61, `large-v3-turbo`, Port 8178) und `nex-tts` (Piper 1.8.0, `de_DE-thorsten-high` + `de_DE-kerstin-low`, Port 8179) als `deploy/compose/nex-speech`, Rolle/Playbook `nex-speech`. ufw in der VM (8178/8179 nur von `.29`, SSH aus dem LAN), Proxmox-Firewall bewusst aus. + - [x] (12.10.) Tests in der VM und von ops (temporäre Regel, danach entfernt): 10 s → 2,94 s, 60 s → 8,63 s (Median), TTS-TTFB 0,64–0,91 s (mp3/pcm/opus), Autostart nach `qm reboot 252` geprüft, keine Texte in Logs. Rückmeldung im Auftrag. + - [ ] Offen: feste IP (D5, heute DHCP `192.168.100.174`), Abnahme von appsrv08 durch das NEX-Team, Slot `stt-de` (Core kennt kein `kind: stt`), `tts-de` später nach `infra`. - [ ] **6.3 Lokale Slots in LiteLLM (D4):** `embed-default`, `rerank-default`, `verifier-small` und `signature-extractor` als Modelle im LiteLLM auf appsrv08 eintragen (IP-Literale). Prüfung: `curl http://192.168.100.29:/v1/models` listet die Slots, ein Aufruf je Slot antwortet. - [ ] **6.4 Ada-Worker (D3)** · **Planner:** „LLM-Worker: direkte Inferenz-Schnittstelle für LiteLLM (zusätzlich zum Job-Poll), Tunnel, Registry, Fallback-Kette“ - Offene Schritte der Worker-Registry v2 im Repo `mygodel/nex` abschließen.