Dictado en streaming
Parakeet-EOU-120M es un pequeño modelo ASR streaming RNN-T con una cabeza explícita de fin de enunciado (EOU), construido para dictado en tiempo real en el Neural Engine de Apple Silicon. Esta guía también cubre DictateDemo, la app de referencia en la barra de menús de macOS que conecta el modelo streaming con Silero VAD para dictado manos libres que pega en cualquier sitio.
Qué es
- Parciales en vivo — el texto se actualiza mientras hablas, ~340 ms tras cada chunk
- EOU explícito — el modelo decide cuándo termina un enunciado, sin botón manual
- Force-finalize dirigido por VAD — un respaldo de Silero confirma enunciados incluso cuando el EOU se atasca con ruido de fondo
- 120 MB INT8 CoreML — se ejecuta en el Neural Engine, dejando la GPU libre para otros modelos
- 25 idiomas europeos — misma familia de vocabulario que el Parakeet TDT original de NeMo
Arquitectura
Tres modelos CoreML encadenados por chunk de audio:
| Componente | Descripción |
|---|---|
| Codificador | Conformer con caché. Toma un chunk mel de 64 frames (640 ms) más seis tensores de estado — caché KV de atención, caché de conv depthwise y un loopback mel pre_cache que antepone audio del pasado reciente para que la FFT vea una señal continua a través de los bordes de chunk. |
| Decodificador | Red de predicción LSTM de un solo paso. Consume el token no-blank anterior, emite un embedding más el estado (h, c) actualizado. |
| Joint + cabeza EOU | Fusiona las salidas del codificador y del decodificador en logits sobre vocab + blank + EOU. La clase EOU es la señal explícita del modelo de que un enunciado ha terminado. |
Por qué un token EOU separado
El RNNT plano emite blanks durante el silencio, que el decodificador absorbe felizmente sin señalizar "enunciado terminado". Una cabeza EOU dedicada permite que el modelo haga un corte explícito para confirmar el parcial como final, reinicie el estado de puntuación/capitalización y dispare acciones downstream como pegar en la app.
Clics de teclado, movimiento del ratón y ruido de la sala durante una pausa "silenciosa" pueden hacer que el joint emita ocasionalmente un token no-blank, reiniciando el debounce de EOU y atascando la confirmación. Los pipelines de producción emparejan el EOU del joint con un respaldo externo forceEndOfUtterance() dirigido por VAD — ver DictateDemo abajo.
Modelo
| Modelo | Tamaño | HuggingFace |
|---|---|---|
| Parakeet-EOU-120M (CoreML INT8) | ~120 MB | aufklarer/Parakeet-EOU-120M-CoreML-INT8 |
Rendimiento
| Métrica | Valor |
|---|---|
| Memoria de pesos | ~120 MB (INT8) |
| Memoria pico de inferencia | ~200 MB |
| Latencia por chunk (series M) | ~30 ms de cómputo / 640 ms de audio (RTF ~0.056) |
| Latencia parcial end-to-end | ~340 ms (un chunk) |
| Latencia de confirmación (ruta VAD) | ~1 s tras dejar de hablar |
| Objetivo de cómputo | Neural Engine (CoreML) |
Inicio rápido — transcripción por lotes
El modelo streaming también cumple SpeechRecognitionModel, así que funciona como reemplazo directo en cualquier código que reciba un modelo STT genérico:
import ParakeetStreamingASR
let model = try await ParakeetStreamingASRModel.fromPretrained()
let text = try model.transcribeAudio(audioSamples, sampleRate: 16000)
Inicio rápido — streaming asíncrono
for await partial in model.transcribeStream(audio: samples, sampleRate: 16000) {
if partial.isFinal { print("FINAL: \(partial.text)") }
else { print("... \(partial.text)") }
}
Cada PartialTranscript lleva text, isFinal, confidence, eouDetected (disparado por el joint vs. force-finalize) y un segmentIndex monótono.
API de sesión de larga duración (entrada de micrófono)
Para dictado en vivo, crea una sesión una vez y aliméntala con chunks a medida que llegan del micrófono. La sesión bufferiza internamente y ejecuta el codificador cuando acumula suficientes muestras, así que puedes empujar chunks de cualquier tamaño:
let session = try model.createSession()
// cada chunk del micrófono:
let partials = try session.pushAudio(float32Chunk16kHz)
for p in partials {
if p.isFinal { commit(p.text) }
else { showPartial(p.text) }
}
// cuando termina el stream:
let trailing = try session.finalize()
Patrón de force-finalize con VAD
Cuando ya tienes un Silero VAD ejecutándose en tu pipeline, úsalo para disparar un respaldo de commit de modo que el ruido de fondo no pueda atascar el debounce de EOU:
if hasPendingUtterance && !vadSpeechActive && vadSilentChunks >= 30 {
// ~960 ms de silencio sostenido según Silero
if let forced = session.forceEndOfUtterance() {
commit(forced.text)
}
hasPendingUtterance = false
}
// guardrail: no hagas doble commit si el joint ya disparó EOU
if partials.contains(where: { $0.isFinal }) {
hasPendingUtterance = false
}
DictateDemo — app de referencia en la barra de menús de macOS
DictateDemo es un agent completo en la barra de menús de macOS construido sobre la sesión streaming. Se ejecuta como app en segundo plano, transcribe desde el micrófono con parciales en vivo, auto-confirma enunciados en EOU o silencio de VAD y pega los resultados en la app frontal.
- App en la barra de menús con atajo global
Cmd+Shift+D - Parciales en vivo con HUD flotante e indicador de nivel de audio
- Force-finalize protegido por VAD (el patrón de producción anterior)
- Pegar en la app frontal con
Cmd+Shift+V - Descarga automática del modelo en el primer lanzamiento (~120 MB)
cd Examples/DictateDemo
swift build
.build/debug/DictateDemo
La implementación completa vive en Examples/DictateDemo/DictateDemo/DictateViewModel.swift: un sink de audio fuera del hilo principal con un buffer protegido por lock, un tic de timer de 300 ms que lo drena, Silero VAD con arrastre de muestras sobrantes y un force-finalize protegido. Las pruebas de regresión correspondientes en Examples/DictateDemo/Tests/DictateDemoTests.swift cubren escenarios de múltiples enunciados, EOU atascado y silencio ruidoso.
Streaming vs Parakeet por lotes
| Parakeet-EOU-120M (streaming) | Parakeet TDT 0.6B (lotes) | |
|---|---|---|
| Caso de uso | Dictado en vivo, subtitulado en tiempo real | Transcripción de archivos, trabajos offline |
| Decodificador | RNN-T + cabeza EOU | Token-and-Duration Transducer |
| Tamaño de chunk | 640 ms streaming | Lote de archivo completo |
| Memoria de pesos | ~120 MB | 500 MB |
| Throughput | ~18× tiempo real | ~32× tiempo real |
| Latencia | Parciales ~340 ms | Solo al final del archivo |
…necesites parciales antes de que el usuario termine de hablar. Para transcripción por lotes de archivos de audio, el Parakeet TDT 0.6B más grande es más rápido end-to-end y más preciso. Los dos modelos comparten el mismo vocabulario SentencePiece, así que puedes intercambiarlos sin cambiar la tokenización.