Streaming-Diktat
Parakeet-EOU-120M ist ein kleines RNN-T-Streaming-ASR-Modell mit explizitem End-of-Utterance-Head (EOU), gebaut für Echtzeit-Diktat auf der Neural Engine von Apple Silicon. Diese Anleitung behandelt auch DictateDemo, die macOS-Menüleisten-Referenz-App, die das Streaming-Modell mit Silero VAD zu einem freihändigen Diktat mit "Einfügen überall" verdrahtet.
Was es ist
- Live-Teilergebnisse — der Text aktualisiert sich während des Sprechens, ~340 ms nach jedem Chunk
- Explizites EOU — das Modell entscheidet, wann eine Äußerung endet, kein manueller Button
- VAD-getriebenes Force-Finalize — Silero-Backstop committet Äußerungen auch dann, wenn EOU bei Hintergrundgeräuschen hängen bleibt
- 120 MB INT8 CoreML — läuft auf der Neural Engine und lässt die GPU für andere Modelle frei
- 25 europäische Sprachen — selbe Vokabularfamilie wie Upstream NeMo Parakeet TDT
Architektur
Drei CoreML-Modelle werden pro Audio-Chunk in einer Pipeline ausgeführt:
| Komponente | Beschreibung |
|---|---|
| Encoder | Cache-bewusster Conformer. Nimmt einen 64-Frame-Mel-Chunk (640 ms) plus sechs Zustandstensoren entgegen — Attention-KV-Cache, Depthwise-Conv-Cache und ein pre_cache-Mel-Loopback, der Audio aus der jüngsten Vergangenheit voranstellt, damit die FFT ein kontinuierliches Signal über Chunk-Grenzen hinweg sieht. |
| Decoder | Einschritt-LSTM-Prediction-Netzwerk. Konsumiert das vorherige Non-Blank-Token und gibt ein Embedding plus aktualisierten (h, c)-Zustand aus. |
| Joint + EOU-Head | Fusioniert Encoder- und Decoder-Ausgaben zu Logits über vocab + blank + EOU. Die EOU-Klasse ist das harte Signal des Modells, dass eine Äußerung abgeschlossen ist. |
Warum ein separates EOU-Token
Einfaches RNNT gibt in Pausen Blanks aus, die der Decoder widerspruchslos absorbiert, ohne "Äußerung beendet" zu signalisieren. Ein dedizierter EOU-Head erlaubt dem Modell einen harten Schnitt, um den Partial zu einem Finalen zu committen, den Zeichensetzungs-/Großschreibungs-Zustand zurückzusetzen und nachgelagerte Aktionen wie "in App einfügen" auszulösen.
Tastaturklicks, Mausbewegungen und Raumton während einer "stillen" Pause können dazu führen, dass der Joint gelegentlich ein Non-Blank-Token ausgibt, was den EOU-Debounce-Timer zurücksetzt und den Commit verzögert. Produktive Pipelines koppeln das Joint-EOU mit einem externen VAD-gesteuerten forceEndOfUtterance()-Backstop — siehe DictateDemo unten.
Modell
| Modell | Größe | HuggingFace |
|---|---|---|
| Parakeet-EOU-120M (CoreML INT8) | ~120 MB | aufklarer/Parakeet-EOU-120M-CoreML-INT8 |
Leistung
| Metrik | Wert |
|---|---|
| Gewichtsspeicher | ~120 MB (INT8) |
| Spitzen-Inferenzspeicher | ~200 MB |
| Chunk-Latenz (M-Serie) | ~30 ms Compute / 640 ms Audio (RTF ~0,056) |
| Partial-Latenz Ende-zu-Ende | ~340 ms (ein Chunk) |
| Commit-Latenz (VAD-Pfad) | ~1 s nach Sprechende |
| Compute-Ziel | Neural Engine (CoreML) |
Schnellstart — Batch-Transkription
Das Streaming-Modell erfüllt auch SpeechRecognitionModel und funktioniert damit als Drop-in-Ersatz für jeden Code, der ein generisches STT-Modell erwartet:
import ParakeetStreamingASR
let model = try await ParakeetStreamingASRModel.fromPretrained()
let text = try model.transcribeAudio(audioSamples, sampleRate: 16000)
Schnellstart — Async-Streaming
for await partial in model.transcribeStream(audio: samples, sampleRate: 16000) {
if partial.isFinal { print("FINAL: \(partial.text)") }
else { print("... \(partial.text)") }
}
Jedes PartialTranscript enthält text, isFinal, confidence, eouDetected (Joint hat ausgelöst vs. Force-Finalized) und einen monotonen segmentIndex.
Langlebige Session-API (Mikrofoneingang)
Für Live-Diktat legst du einmal eine Session an und fütterst sie mit Chunks, sobald sie vom Mikrofon eintreffen. Die Session puffert intern und führt den Encoder aus, wenn genügend Samples angesammelt sind, sodass du beliebige Chunk-Größen pushen kannst:
let session = try model.createSession()
// je Mikrofon-Chunk:
let partials = try session.pushAudio(float32Chunk16kHz)
for p in partials {
if p.isFinal { commit(p.text) }
else { showPartial(p.text) }
}
// wenn der Stream endet:
let trailing = try session.finalize()
VAD-Force-Finalize-Muster
Wenn in deiner Pipeline bereits ein Silero VAD läuft, nutze es als Fallback-Commit, damit Hintergrundgeräusche den EOU-Debounce-Timer nicht blockieren können:
if hasPendingUtterance && !vadSpeechActive && vadSilentChunks >= 30 {
// ~960 ms anhaltende Stille laut Silero
if let forced = session.forceEndOfUtterance() {
commit(forced.text)
}
hasPendingUtterance = false
}
// Sicherung: kein doppeltes Commit, wenn der Joint bereits EOU ausgelöst hat
if partials.contains(where: { $0.isFinal }) {
hasPendingUtterance = false
}
DictateDemo — macOS-Menüleisten-Referenz-App
DictateDemo ist ein vollständiger macOS-Menüleisten-Agent, der auf der Streaming-Session aufbaut. Er läuft als Hintergrund-App, transkribiert vom Mikrofon mit Live-Teilergebnissen, committet Äußerungen automatisch bei EOU oder VAD-Stille und fügt Ergebnisse in die vorderste App ein.
- Menüleisten-App mit globalem Hotkey
Cmd+Shift+D - Live-Teilergebnisse mit schwebendem HUD und Audio-Level-Anzeige
- VAD-abgesichertes Force-Finalize (das produktive Muster oben)
- Einfügen in vorderste App mit
Cmd+Shift+V - Modell wird beim ersten Start automatisch heruntergeladen (~120 MB)
cd Examples/DictateDemo
swift build
.build/debug/DictateDemo
Die vollständige Implementierung lebt in Examples/DictateDemo/DictateDemo/DictateViewModel.swift: eine Off-Main-Audio-Senke mit lock-geschütztem Puffer, ein 300-ms-Timer-Tick, der ihn leert, Silero VAD mit Übertragung restlicher Samples und ein abgesichertes Force-Finalize. Die passenden Regressionstests in Examples/DictateDemo/Tests/DictateDemoTests.swift decken Mehrfach-Äußerungen, hängenbleibende EOU und Szenarien mit lauter Stille ab.
Streaming- vs. Batch-Parakeet
| Parakeet-EOU-120M (Streaming) | Parakeet TDT 0.6B (Batch) | |
|---|---|---|
| Anwendungsfall | Live-Diktat, Echtzeit-Untertitelung | Dateitranskription, Offline-Jobs |
| Decoder | RNN-T + EOU-Head | Token-and-Duration-Transducer |
| Chunk-Größe | 640 ms Streaming | Batch über ganze Datei |
| Gewichtsspeicher | ~120 MB | 500 MB |
| Durchsatz | ~18-fache Echtzeit | ~32-fache Echtzeit |
| Latenz | ~340 ms Teilergebnisse | Nur am Dateiende |
…du Teilergebnisse brauchst, bevor der Benutzer fertig spricht. Für Batch-Transkription von Audiodateien ist das größere Parakeet TDT 0.6B Ende-zu-Ende schneller und genauer. Beide Modelle teilen sich dasselbe SentencePiece-Vokabular, sodass du zwischen ihnen wechseln kannst, ohne die Tokenisierung zu ändern.