Dictée en streaming
Parakeet-EOU-120M est un petit modèle ASR streaming RNN-T avec une tête explicite de fin d'énoncé (EOU), conçu pour la dictée en temps réel sur le Neural Engine Apple Silicon. Ce guide couvre également DictateDemo, l'application de référence en barre de menus macOS qui relie le modèle streaming à Silero VAD pour une dictée mains libres et collable partout.
De quoi il s'agit
- Partiels en direct — le texte se met à jour pendant que vous parlez, environ 340 ms après chaque chunk
- EOU explicite — le modèle décide quand un énoncé se termine, sans bouton manuel
- Force-finalize piloté par VAD — le filet de sécurité Silero commet les énoncés même lorsque l'EOU cale sur du bruit de fond
- 120 Mo INT8 CoreML — s'exécute sur le Neural Engine, laisse le GPU libre pour d'autres modèles
- 25 langues européennes — même famille de vocabulaire que le NeMo Parakeet TDT en amont
Architecture
Trois modèles CoreML pipelinés par chunk audio :
| Composant | Description |
|---|---|
| Encodeur | Conformer sensible au cache. Prend un chunk mel de 64 trames (640 ms) plus six tenseurs d'état — cache KV d'attention, cache de conv depthwise et un pre_cache mel en boucle qui préfixe l'audio récent pour que la FFT voie un signal continu entre les frontières de chunks. |
| Décodeur | Réseau de prédiction LSTM à étape unique. Consomme le token non-blanc précédent, émet un embedding plus un état (h, c) mis à jour. |
| Joint + tête EOU | Fusionne les sorties de l'encodeur et du décodeur en logits sur vocab + blank + EOU. La classe EOU est le signal dur du modèle qu'un énoncé est terminé. |
Pourquoi un token EOU séparé
Un RNNT ordinaire émet des blancs pendant le silence, que le décodeur absorbe volontiers sans signaler « énoncé terminé ». Une tête EOU dédiée permet au modèle de faire une coupure dure pour commettre le partiel en final, réinitialiser l'état de ponctuation/majuscules et déclencher des actions en aval comme le collage dans une app.
Les clics de clavier, les mouvements de souris et les tonalités de la pièce pendant une pause « silencieuse » peuvent faire que le joint émette occasionnellement un token non-blanc, réinitialisant le timer de debounce EOU et stoppant le commit. Les pipelines de production couplent le joint EOU à un filet de sécurité forceEndOfUtterance() piloté par VAD externe — voir DictateDemo ci-dessous.
Modèle
| Modèle | Taille | HuggingFace |
|---|---|---|
| Parakeet-EOU-120M (CoreML INT8) | ~120 Mo | aufklarer/Parakeet-EOU-120M-CoreML-INT8 |
Performance
| Métrique | Valeur |
|---|---|
| Mémoire des poids | ~120 Mo (INT8) |
| Mémoire d'inférence en pic | ~200 Mo |
| Latence par chunk (série M) | ~30 ms de calcul / 640 ms d'audio (RTF ~0,056) |
| Latence des partiels de bout en bout | ~340 ms (un chunk) |
| Latence de commit (chemin VAD) | ~1 s après arrêt de la parole |
| Cible de calcul | Neural Engine (CoreML) |
Démarrage rapide — transcription par batch
Le modèle streaming se conforme également à SpeechRecognitionModel, donc il fonctionne en remplacement direct pour tout code qui prend un modèle STT générique :
import ParakeetStreamingASR
let model = try await ParakeetStreamingASRModel.fromPretrained()
let text = try model.transcribeAudio(audioSamples, sampleRate: 16000)
Démarrage rapide — streaming async
for await partial in model.transcribeStream(audio: samples, sampleRate: 16000) {
if partial.isFinal { print("FINAL: \(partial.text)") }
else { print("... \(partial.text)") }
}
Chaque PartialTranscript porte text, isFinal, confidence, eouDetected (joint déclenché vs force-finalized) et un segmentIndex monotone.
API de session longue durée (entrée micro)
Pour la dictée en direct, créez une session une fois et alimentez-la en chunks à mesure qu'ils arrivent du micro. La session bufferise en interne et exécute l'encodeur lorsqu'assez d'échantillons s'accumulent, vous permettant donc de pousser des chunks de taille arbitraire :
let session = try model.createSession()
// chaque chunk micro :
let partials = try session.pushAudio(float32Chunk16kHz)
for p in partials {
if p.isFinal { commit(p.text) }
else { showPartial(p.text) }
}
// quand le flux se termine :
let trailing = try session.finalize()
Motif de force-finalize via VAD
Lorsqu'un Silero VAD tourne déjà dans votre pipeline, utilisez-le pour piloter un commit de repli afin que le bruit de fond ne puisse pas stopper le timer de debounce EOU :
if hasPendingUtterance && !vadSpeechActive && vadSilentChunks >= 30 {
// ~960 ms de silence soutenu selon Silero
if let forced = session.forceEndOfUtterance() {
commit(forced.text)
}
hasPendingUtterance = false
}
// garde-fou : ne pas commettre deux fois si le joint a déjà déclenché EOU
if partials.contains(where: { $0.isFinal }) {
hasPendingUtterance = false
}
DictateDemo — application de référence en barre de menus macOS
DictateDemo est un agent complet de barre de menus macOS construit au-dessus de la session streaming. Il s'exécute en arrière-plan, transcrit depuis le micro avec des partiels en direct, auto-commet les énoncés sur EOU ou silence VAD, et colle les résultats dans l'app au premier plan.
- Application de barre de menus avec raccourci global
Cmd+Shift+D - Partiels en direct avec HUD flottant et indicateur de niveau audio
- Force-finalize gardé par VAD (le motif de production ci-dessus)
- Collage dans l'app au premier plan via
Cmd+Shift+V - Le modèle se télécharge automatiquement au premier lancement (~120 Mo)
cd Examples/DictateDemo
swift build
.build/debug/DictateDemo
L'implémentation complète se trouve dans Examples/DictateDemo/DictateDemo/DictateViewModel.swift : un sink audio hors du main thread avec un buffer protégé par verrou, un tick de timer à 300 ms qui le draine, Silero VAD avec report des échantillons restants, et un force-finalize gardé. Les tests de régression correspondants dans Examples/DictateDemo/Tests/DictateDemoTests.swift couvrent les scénarios multi-énoncés, EOU bloqué et silence bruité.
Streaming vs batch Parakeet
| Parakeet-EOU-120M (streaming) | Parakeet TDT 0.6B (batch) | |
|---|---|---|
| Cas d'usage | Dictée en direct, sous-titrage temps réel | Transcription de fichiers, jobs hors ligne |
| Décodeur | RNN-T + tête EOU | Token-and-Duration Transducer |
| Taille de chunk | Streaming 640 ms | Batch sur fichier entier |
| Mémoire des poids | ~120 Mo | 500 Mo |
| Débit | ~18× temps réel | ~32× temps réel |
| Latence | Partiels ~340 ms | Fin de fichier uniquement |
…vous avez besoin de partiels avant que l'utilisateur ait fini de parler. Pour la transcription par batch de fichiers audio, le plus grand Parakeet TDT 0.6B est plus rapide de bout en bout et plus précis. Les deux modèles partagent le même vocabulaire SentencePiece, vous pouvez donc basculer entre eux sans changer la tokenisation.