ci / test (push) Successful in 29s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
325 lines
20 KiB
Markdown
325 lines
20 KiB
Markdown
# Auftrag: Sprachmodelle (Spracherkennung + Sprachausgabe) für NEX auf dem knaster-Cluster
|
||
|
||
Eingang 12.10.2026 (vom NEX-Team, ohne Schlüssel). Ausführung **nach** Task 3.2 (GPU-VMs mit Treiber), gern durch einen Subagenten. Im Plan als Task 6.5 geführt.
|
||
|
||
## Abgleich mit dem echten Cluster (Stand 12.10.2026, vor Ausführung lesen)
|
||
|
||
Der Auftrag geht von einem älteren Bild aus. Es gilt:
|
||
|
||
| Auftrag sagt | Wirklichkeit |
|
||
|---|---|
|
||
| 2 Knoten + QDevice | 5 Knoten (pve44, pve46, pve48, pve52, pve61), 5 Stimmen, kein QDevice |
|
||
| Knoten A 2× P2000, Knoten B 2× M4000, GPU per LXC oder VM | Eine Ubuntu-24.04-VM pro GPU-Knoten mit allen Karten (D2): `gpu-pve44`, `gpu-pve46`, `gpu-pve48` je M4000 + P2000, `gpu-pve52` nur M4000. Treiber R580 in der VM |
|
||
| Feste IP per DHCP-Reservierung | D5 ist zurückgestellt. Gäste laufen per DHCP, der Nutzer hat keinen Zugriff auf den DHCP-Server. **Vor der Abnahme klären:** Reservierung durch die DHCP-Verwaltung oder statische IP außerhalb des DHCP-Bereichs |
|
||
|
||
Daraus folgt für die Umsetzung:
|
||
- **Erkennung:** whisper.cpp (CUDA 12, `CMAKE_CUDA_ARCHITECTURES="52;61"`) auf einer **M4000**. Naheliegend ist `gpu-pve52`: Dort steckt nur die M4000, und Placement verteilt keine andere Rolle dorthin. Die P2000 bleiben für embed/rerank (pve44, pve46) und `gpu-small` (pve48). Die M4000 sind im Inventar `disabled` bis zum Benchmark-Tag (Task 3.3); die Messwerte aus 4. Abnahme liefern genau diesen Benchmark für whisper und gehören in Task 3.3.
|
||
- **Ausgabe Stufe 1 (Piper, CPU):** laut Registry `tts-de` im Infra-LXC (`infra`, Task 5.2). Solange `infra` nicht steht, als Container in derselben GPU-VM wie die Erkennung (CPU reicht).
|
||
- **Firewall „nur von 192.168.100.29“:** Proxmox-Firewall an der Netzkarte der VM (`firewall=1`, Regeln für 8178/8179) plus `ufw` in der VM.
|
||
- Globale Vorgabe aus dem Plan passt: whisper-server auf Port 8178, nur von `.29`, `stt.openai.base_url` nur als IP-Literal.
|
||
|
||
## Auftrag (Originaltext)
|
||
|
||
NEX sind Hermes-Agenten auf appsrv08 (192.168.100.29). Sprache läuft dort: Hermes → nex-router → LiteLLM → Sprach-Backend. Das Backend ist austauschbar (Eintrag in LiteLLM). Der Dienst auf dem Cluster ist das **Rückfall-Modell**. Solange kein Cloud-Anbieter Guthaben hat, ist er der einzige aktive Weg. Die NEX-Seite trägt danach nur IP:Port und Modellnamen in LiteLLM ein. Gebraucht werden zwei **OpenAI-kompatible** HTTP-Dienste im internen Netz. Ein Dienst für beides ist auch in Ordnung.
|
||
|
||
Formate laut Hermes-Code: mp3 als Standard, Opus für Sprachnachrichten, rohes PCM mit Sample-Rate-Header für das Streaming.
|
||
|
||
### Hardware und Grenzen
|
||
- Treiber **R580**, die letzte Serie für Maxwell/Pascal.
|
||
- **faster-whisper/CTranslate2 laufen nicht auf CC 5.2.** Auf der M4000 nur whisper.cpp, und zwar als **CUDA-12-Build**, nicht CUDA 13 (`-DGGML_CUDA=ON -DCMAKE_CUDA_ARCHITECTURES="52;61"`).
|
||
- PyTorch-Unterstützung für sm_50/sm_61 in aktuellen Wheels **vorher prüfen** (`python -c "import torch; print(torch.cuda.get_arch_list())"`), nicht annehmen. Keine Tensor-Cores: bei faster-whisper `int8` oder `int8_float32`.
|
||
|
||
### 1. Spracherkennung (Pflicht)
|
||
- Modell **Whisper large-v3-turbo**, Sprache fest `de`.
|
||
- Empfehlung M4000: whisper.cpp `whisper-server` mit `-m ggml-large-v3-turbo.bin -l de -bs 5 --convert --inference-path /v1/audio/transcriptions`, **ohne** `--print-realtime`, `--tmp-dir` auf tmpfs (z. B. `/run/nex-whisper`). Nach dem Start einmal mit 1 s Stille aufwärmen.
|
||
- Alternative P2000: speaches (faster-whisper) mit `deepdml/faster-whisper-large-v3-turbo-ct2`, `int8_float32`.
|
||
- Optional mitmessen: `primeline/whisper-large-v3-turbo-german`.
|
||
- Schnittstelle: `POST /v1/audio/transcriptions`, multipart
|
||
- Felder: `file`, `model`, `language=de`, `response_format`.
|
||
- `file` muss wav, mp3, ogg/opus, webm und m4a annehmen (Umwandlung mit ffmpeg).
|
||
- `response_format`: `json` → `{"text": "…"}`, `text`, `verbose_json` (wenn möglich mit Wort-Wahrscheinlichkeiten).
|
||
- Unbekannte Felder ignorieren, nicht mit Fehler antworten.
|
||
|
||
### 2. Sprachausgabe (Pflicht Stufe 1, optional Stufe 2)
|
||
- **Stufe 1:** Piper Deutsch, CPU reicht. Stimmen: `de_DE-thorsten-high` und eine weibliche deutsche Stimme. Genauen Namen nennen. Server OpenAI-kompatibel, z. B. speaches (kann Piper) oder ein schlanker Wrapper.
|
||
- Schnittstelle: `POST /v1/audio/speech`
|
||
- JSON-Body `{model, voice, input, response_format, speed}`.
|
||
- `response_format` muss können: `mp3` (Standard), `opus` (im Ogg-Container), `wav`, `flac`, `pcm` (rohes int16, little-endian, mono; Sample-Rate im Header `X-Audio-Sample-Rate` oder in `Content-Type: audio/pcm; rate=<Hz>`).
|
||
- Antwort möglichst gestreamt (chunked), damit die Wiedergabe früh beginnt.
|
||
- **Stufe 2 (optional, GPU, nach PyTorch-Prüfung):** eine natürlichere deutsche Stimme.
|
||
- **Lizenz für kommerzielle Nutzung prüfen.** Nach Stand des NEX-Teams sind XTTS-v2 (Coqui Public Model License) und die Gewichte von Fish Speech/OpenAudio **nicht kommerziell**.
|
||
- Chatterbox Multilingual (MIT) ist ein Kandidat.
|
||
- Kokoro hat nach diesem Stand keine deutsche Stimme. Prüfen, nicht voraussetzen.
|
||
|
||
### 3. Für beide Dienste
|
||
- `GET /health` → 200.
|
||
- Feste IP in `192.168.100.0/24` (siehe Abgleich oben). Ports zum Beispiel 8178 (Erkennung) und 8179 (Ausgabe).
|
||
- Firewall: Zugriff **nur von 192.168.100.29**. LiteLLM läuft dort in einem Bridge-Netz und erscheint mit der Host-IP.
|
||
- Kein API-Key nötig. Erzwingt der Server einen, einen festen Dummy-Wert nennen (kein Geheimnis).
|
||
- Autostart nach Neustart (systemd oder Docker `restart: unless-stopped`), Energiesparen aus, Sicherheitsupdates.
|
||
- **Datenschutz:** keine Ablage von Audio oder Text; Temp-Dateien nur im RAM, alte regelmäßig löschen; **keine Transkripte oder vorgelesenen Texte in Logs.**
|
||
|
||
### 4. Abnahme
|
||
Von appsrv08 aus prüfen, Testaudio ohne Kundendaten, danach löschen.
|
||
- **Erkennung:**
|
||
- `curl -s -w ' %{time_total}s\n' -F file=@t10.wav -F model=whisper -F language=de -F response_format=json http://IP:8178/v1/audio/transcriptions`
|
||
- Text muss deutsch und plausibel sein.
|
||
- Median aus 3 Läufen: 10 s Audio **≤ 4 s** (Ziel ≤ 3 s), 60 s Audio **≤ 12 s**. Den ersten Aufruf getrennt notieren.
|
||
- **Ausgabe:** `curl -s -o t.mp3 -w ' TTFB %{time_starttransfer}s gesamt %{time_total}s\n' -H 'Content-Type: application/json' -d '{"model":"piper","voice":"de_DE-thorsten-high","input":"Guten Tag, hier ist Ihr NEX-Assistent. Die Baubesprechung in Haslach ist am vierten November.","response_format":"mp3"}' http://IP:8179/v1/audio/speech`
|
||
- Ergebnis abspielbar, Zeit bis zum ersten Byte möglichst ≤ 1 s.
|
||
- Dasselbe mit `pcm` (Sample-Rate-Header prüfen) und `opus`.
|
||
- **Last:** 2 Erkennungen gleichzeitig. Mit `nvidia-smi` prüfen, dass GPU-Last und VRAM steigen.
|
||
|
||
### 5. Rückmeldung (genau so)
|
||
Je Dienst: Basis-URL (IP:Port) · Knoten und GPU · Software mit Version oder Image-Digest · Modellname(n), wie im Feld `model` erwartet · Stimmen-IDs · unterstützte `response_format` · PCM-Sample-Rate · Messwerte (Median, erster Aufruf, VRAM, RAM) · Autostart nach Neustart getestet (ja/nein) · Auffälligkeiten.
|
||
|
||
### 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.
|
||
|
||
**Energiesparen und Updates:**
|
||
- Die VM hat kein Suspend und kein Energiesparen (Ubuntu-Server-Cloud-Image).
|
||
- Die GPU läuft im Leerlauf in P5 mit 17 W. Der Kontext bleibt geladen, weil whisper-server läuft.
|
||
- Die Energieeinstellungen der Proxmox-Knoten habe ich laut Vorgabe nicht angefasst.
|
||
- Sicherheitsupdates laufen per `unattended-upgrades`.
|
||
|
||
**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 und bei 10-s-Aufrufen, bis 2179 MiB nach 60-s-Audio, im Paralleltest kein weiterer Anstieg |
|
||
| 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 %, Mittel 78 %. Der Leerlauf (0 %) wurde separat gemessen; der erste Wert im Log (85 %) war noch Nachlauf.
|
||
- **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 <IP> to any port 8178:8179 proto tcp`, danach `sudo ufw delete allow from <IP> 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.
|