fix(diar): sherpa abschnittsweise laufen lassen (OOM bei langen Aufnahmen)
Der sherpa-Diarisierungslauf brauchte RAM quadratisch zur Gesprächslänge (gemessen 349 MB bei 10 min, 1389 MB bei 20 min, 2643 MB bei 30 min). Die 54-min-Aufnahme #99 riss 5,4 GB, wurde OOM-getötet und nahm wegen OOMPolicy=stop die ganze App mit — in der GUI "Transkription fehlgeschlagen" und "failed to fetch". Die v2-Pipeline verwirft sherpas Labels und nutzt nur die Turn-Grenzen, also laeuft der Pass jetzt in 600-s-Abschnitten (je +-5 s Kontext) und die Grenzen werden mit Zeit-Offset zusammengesetzt. Zusätzlich OOMPolicy=continue, damit ein OOM-getöteter Helfer die App nicht mehr stoppt. Verifiziert an #99: sherpa-Spitze 363 MB, 726 Turns, 8 Sprecher, Analyse vollstaendig (done/done).
This commit is contained in:
+60
-2
@@ -141,6 +141,64 @@ sitzt einer sicher darauf, wirkt der Name wie ein Marker-Tag = cannot-link.
|
||||
widersprechenden Stimmabdruck (`identify #id: Stimmabdruck "X" schlägt unsicheren Marker "Y"`). In #79
|
||||
hatte der Marker bei 6,6 s Saschas Cluster „Markus" getauft.
|
||||
|
||||
## Abschnittsweiser sherpa-Lauf (seit 2026-08-07) — der RAM-Bedarf wuchs quadratisch
|
||||
|
||||
**Symptom:** Aufnahme #99 (53:56) endete mit „Transkription fehlgeschlagen“, die GUI zeigte
|
||||
„failed to fetch“. In Wahrheit war die Transkription längst fertig (8666 Wörter, RTF 0,394);
|
||||
gestorben ist der sherpa-Lauf danach:
|
||||
|
||||
```
|
||||
Out of memory: Killed process 3107072 (sherpa-onnx-off) total-vm:8387852kB, anon-rss:5463416kB
|
||||
callrec.service: A process of this unit has been killed by the OOM killer.
|
||||
callrec.service: Failed with result 'oom-kill'.
|
||||
```
|
||||
|
||||
**Gemessen** (gleiche Aufnahme, Anfangsstücke verschiedener Länge, A76 auf 1,608 GHz, `VmHWM`):
|
||||
|
||||
| Audio | Spitzen-RSS | Turns | Laufzeit |
|
||||
|---|---|---|---|
|
||||
| 10 min | 349 MB | 130 | 130 s |
|
||||
| 20 min | 1389 MB | 242 | 300 s |
|
||||
| 30 min | 2643 MB | 361 | 459 s |
|
||||
| 54 min | **5,4 GB → OOM** | — | Abbruch nach ~14 min |
|
||||
|
||||
Doppelte Länge = **vierfacher** Speicher: sherpa hält das ganze Gespräch plus seinen Clustering-Zustand
|
||||
im RAM (O(n²) über die Turns). Auf 8 GB ohne echten Swap (nur zram) ist damit bei ~45 min Schluss.
|
||||
|
||||
**Fix:** v2 wirft sherpas **Labels** ohnehin weg und braucht nur die **Turn-Grenzen** — also läuft der
|
||||
Lauf jetzt in Abschnitten von 600 s (`DIAR_CHUNK_SEC`, `0` schaltet ab) und die Grenzen werden mit
|
||||
Zeit-Offset zusammengesetzt. Der Speicher bleibt damit auf dem Niveau eines Abschnitts, **unabhängig
|
||||
von der Gesprächslänge** — und weil die Kosten quadratisch waren, ist es zusätzlich schneller
|
||||
(6 × ~150 s statt eines Laufs, der nach 14 min noch nicht fertig war).
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
WAV["WAV 54 min"] --> CUT["PCM-Slicing in 600-s-Abschnitte<br/>je ±5 s Kontext<br/>(pyannote schaut über 10-s-Fenster)"]
|
||||
CUT --> S1["sherpa Abschnitt 1<br/>0–600 s"]
|
||||
CUT --> S2["sherpa Abschnitt 2<br/>600–1200 s"]
|
||||
CUT --> SN["… Abschnitt n"]
|
||||
S1 --> CLIP["nur Turns im EIGENEN Bereich behalten,<br/>auf ihn beschneiden, + t0 verschieben"]
|
||||
S2 --> CLIP
|
||||
SN --> CLIP
|
||||
CLIP --> ALL["Turn-Grenzen der ganzen Aufnahme<br/>(#99: 726 Turns)"]
|
||||
ALL --> V2["weiter wie Pipeline v2<br/>(Fenster → Embeddings → AHC)"]
|
||||
```
|
||||
|
||||
- Kontext von ±5 s pro Abschnitt, damit die Naht die Grenzerkennung nicht verwischt; die Turns des
|
||||
Kontexts gehören dem Nachbar-Abschnitt und werden verworfen.
|
||||
- **Preis:** ein Turn, der genau auf einer Naht liegt, wird in zwei geteilt. Das Re-Clustering vergibt
|
||||
beiden Hälften ohnehin denselben Sprecher — bei 600 s Abschnittslänge sind das ≤ 5 Nähte pro Stunde.
|
||||
- Der **Legacy-Pfad** (`DIAR_MODE=legacy`) läuft weiter unzerteilt: er braucht sherpas eigenes,
|
||||
globales Clustering und lässt sich nicht abschnittsweise rechnen.
|
||||
- **Zweite Sicherung:** `OOMPolicy=continue` (Drop-in `system/etc/systemd/system/callrec.service.d/oom.conf`).
|
||||
systemd stoppt eine Unit per Default komplett, sobald *ein* Kindprozess OOM-getötet wird — genau das
|
||||
riss die App mit (daher „failed to fetch“). Diarisierung und Analyse sind im Code bereits nicht-fatal,
|
||||
also überlebt die App jetzt einen OOM-getöteten Helfer und behält Transkript + Aufnahme.
|
||||
|
||||
**Verifiziert** an #99 (Re-Diarisierung 2026-08-07): 6 Abschnitte, Spitzen-RSS **363 MB** statt 5,4 GB,
|
||||
726 Turns → 1932 Fenster → **8 Sprecher**, 6 Live-Marker verankert, Diarisierung 13,5 min, keine
|
||||
OOM-Meldung.
|
||||
|
||||
## Harte Grenzen / Sackgassen
|
||||
|
||||
- **Überlappendes gleichzeitiges Sprechen** ist mit Mono-Mix prinzipiell nicht trennbar.
|
||||
@@ -148,8 +206,8 @@ hatte der Marker bei 6,6 s Saschas Cluster „Markus" getauft.
|
||||
- sherpa-Quantisierung q5_0: langsamer UND schlechter auf diesem aarch64-Build — tot.
|
||||
- Wer im ganzen Gespräch weniger als `min(20 s, 2 % der Sprechzeit)` redet, wird einem anderen Sprecher
|
||||
zugeschlagen (Weak-Fold) — sonst überleben Übersprech-Fragmente als „Sprecher".
|
||||
- Diarization-Timeout skaliert mit Dauer (`diarTimeoutMs()` = Dauer × 2,5, min. 20 min).
|
||||
(`parse-options.cc:373`-Ausgabe ist harmloses argv-Echo.)
|
||||
- Diarization-Timeout skaliert mit Dauer (`timeoutForSec()` = Dauer × 2,5, min. 20 min — bei
|
||||
abschnittsweisem Lauf pro Abschnitt). (`parse-options.cc:373`-Ausgabe ist harmloses argv-Echo.)
|
||||
- **v1 ist nicht weg:** `DIAR_MODE=legacy` schaltet auf die alte Anchor-Merge-Konsolidierung zurück
|
||||
(`consolidateSpeakers()` bleibt als Fallback, wenn die v2-Fenster-Embeddings scheitern).
|
||||
|
||||
|
||||
@@ -3,6 +3,43 @@
|
||||
Chronik der Entwicklungsschritte. **Regel: jede zukünftige Änderung bekommt hier einen Eintrag** (Datum,
|
||||
Was, Warum, betroffene Dateien/Backups).
|
||||
|
||||
## 2026-08-07 — Lange Aufnahmen scheiterten: sherpa-Diarisierung lief in den OOM-Killer
|
||||
|
||||
**Symptom:** Aufnahme #99 (53:56) endete mit „Transkription fehlgeschlagen“ (Badge „KI-Fehler“), beim
|
||||
Abruf der Analyse zeigte die GUI „failed to fetch“. Verdacht lag auf dem eMMC-Umzug vom 2026-08-03 —
|
||||
**war es nicht**: Speicher-Setup (zram 3,9 GB, kein Swapfile), RAM (7,83 GB) und `armbian-zram-config`
|
||||
sind vor und nach dem Umzug identisch (alte SD-Root gegengeprüft).
|
||||
|
||||
**Tatsächliche Ursache** (aus `dmesg`): die Transkription war längst fertig (8666 Wörter, RTF 0,394),
|
||||
gestorben ist der sherpa-Lauf danach —
|
||||
`Out of memory: Killed process (sherpa-onnx-off) … anon-rss:5463416kB`. Und weil systemd eine Unit per
|
||||
Default komplett stoppt, sobald ein Kindprozess OOM-getötet wird (`OOMPolicy=stop`), riss das die ganze
|
||||
App mit: `callrec.service: Failed with result 'oom-kill'` → abgebrochene Analyse, leerer Fetch im
|
||||
Browser. Warum es früher ging: der RAM-Bedarf wächst **quadratisch** mit der Gesprächslänge — gemessen
|
||||
349 MB (10 min), 1389 MB (20 min), 2643 MB (30 min), 5,4 GB (54 min). Bis ~45 min passt es, danach nicht.
|
||||
|
||||
**Fix 1 (Ursache):** sherpa läuft jetzt in Abschnitten von 600 s (`DIAR_CHUNK_SEC`, `0` = aus), je mit
|
||||
±5 s Kontext; die Turn-Grenzen werden mit Zeit-Offset zusammengesetzt. Zulässig, weil die v2-Pipeline
|
||||
sherpas Labels ohnehin verwirft und nur die Grenzen nutzt. Speicher bleibt damit dauerhaft auf
|
||||
Abschnitts-Niveau und der Lauf ist zusätzlich schneller (quadratische Kosten).
|
||||
|
||||
**Fix 2 (Schadensbegrenzung):** `OOMPolicy=continue` — ein OOM-getöteter Helfer (sherpa/whisper/rkllm)
|
||||
darf die App nicht mehr mitreißen; Diarisierung und Analyse sind im Code bereits nicht-fatal.
|
||||
|
||||
**Verifiziert an #99** (Re-Transkription + Re-Diarisierung, 2026-08-07): sherpa-Spitze **363 MB** statt
|
||||
5,4 GB, 6 Abschnitte (131/103/119/160/158/55 Turns) → 726 Turns → 1932 Fenster → **8 Sprecher**, alle
|
||||
6 Live-Marker verankert und alle 8 Sprecher benannt (Fabio Haase, Merle, Dennis, Nico, Lisa, Kai, Kim,
|
||||
Artur), Transkript 51.406 Zeichen, Zusammenfassung 15.930 Zeichen, 20 LLM-Abschnitte, Analyse 5073 s,
|
||||
189 Passagen indexiert, `status=done` / `analysis_status=done`, kein Neustart, kein OOM, 64,7 °C.
|
||||
|
||||
Dateien: `app/diar.js` (Board-Backup `diar.js.bak-chunk`),
|
||||
`system/etc/systemd/system/callrec.service.d/oom.conf` (neu), Kapitel
|
||||
[04 – Sprechertrennung](04-diarization.md).
|
||||
|
||||
**Nebenbefund (offen):** während der Analyse belegt `rkllm_server` 6,5 GB RSS (NPU-Modell, shared) und
|
||||
das System hat nur noch ~100 MB frei — funktioniert, aber ohne Reserve. Wenn wieder etwas OOM-nah
|
||||
wird, ist das der Kandidat.
|
||||
|
||||
## 2026-08-03 — System von der SD-Karte auf die eMMC umgezogen
|
||||
|
||||
**Warum:** alles lag auf der SD-Karte (System, Datenbank, Aufnahmen, Modelle), die verlötete eMMC lag
|
||||
|
||||
Reference in New Issue
Block a user