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

Arquitectura

Tres modelos CoreML encadenados por chunk de audio:

ComponenteDescripción
CodificadorConformer 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.
DecodificadorRed 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 EOUFusiona 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.

El EOU es ruidoso en el mundo real

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

ModeloTamañoHuggingFace
Parakeet-EOU-120M (CoreML INT8)~120 MBaufklarer/Parakeet-EOU-120M-CoreML-INT8

Rendimiento

MétricaValor
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ómputoNeural 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.

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 usoDictado en vivo, subtitulado en tiempo realTranscripción de archivos, trabajos offline
DecodificadorRNN-T + cabeza EOUToken-and-Duration Transducer
Tamaño de chunk640 ms streamingLote de archivo completo
Memoria de pesos~120 MB500 MB
Throughput~18× tiempo real~32× tiempo real
LatenciaParciales ~340 msSolo al final del archivo
Elige el modelo streaming cuando…

…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.