Skip to content

fix(asr): recover whole-window blank decodes with a length-perturbation ladder (#909) - #910

Merged
Alex-Wengg merged 5 commits into
mainfrom
fix/909-blank-window
Sep 11, 2026
Merged

fix(asr): recover whole-window blank decodes with a length-perturbation ladder (#909)#910
Alex-Wengg merged 5 commits into
mainfrom
fix/909-blank-window

Conversation

@Alex-Wengg

@Alex-Wengg Alex-Wengg commented Sep 11, 2026

Copy link
Copy Markdown
Member

Fixes #909.

What the model does

Parakeet TDT v3 has input cuts on which it emits nothing: the joint predicts blank at every frame from a fresh state, for 11 to 13 s of clear read speech. It reproduces in batch on the exact span (121-123859-0002 0–13.0 s empty, 0–12.9 s and 0–14.0 s correct), flips with tenths of a second of length, and the fp16 MLX port of the same checkpoint blanks on the same cuts (4507-16021-0032 at 5–14.5, 5–14.8, 5–15.0, 5–15.2 s all empty in parakeet-mlx, 5–14 and 5–15.5 correct). So this is the model, not CoreML quantization, and no encoder precision removes it.

Inside the pipeline the encoder output on a blank cut is finite, without NaNs, just lower in magnitude (median frame norm 0.38 vs 0.60 on the neighbouring good cut), and the joint never beats blank from the SOS state. The outcome sits on a knife edge with respect to the length inputs: the mel normalization moves by about 1 % between the 12 s and 13 s inputs, and declaring the zero padding valid to the encoder alone, or to the preprocessor alone, or trimming 0.2 s, each flips a different subset of the cuts back.

Change

AsrManager.executeMLInferenceWithTimings, which every decode path goes through (single-shot, batch chunks, streaming windows), recovers an empty decode of a window that carries speech (≥ 2 s of audio, RMS ≥ −50 dBFS over the actual length) by re-running from a deep copy of the decoder state the first attempt started from, with a ladder of length declarations, and keeps the first non-empty result:

  1. encoderFull: mel_length set to the padded frame count;
  2. preprocessorFull: audio_length set to the padded length;
  3. trimmedTail: the audio declared 0.2 s shorter on a frame boundary, with that tail silenced;
  4. trimmedTail + encoderFull;
  5. trimmedTail + preprocessorFull.

The decoder still stops at the real frames under every policy. A good window is never touched: the ladder runs only after an empty decode, at one extra preprocessor + encoder + decoder pass per step, and a silent window (no speech energy) is left empty. Trimming costs the final 0.2 s, which the next window's overlap covers everywhere but at the very end of a stream, so it comes last.

The ladder recovers 10 of the 11 reproduced spans (the 13.1 s cut of case A is the holdout) and both real streaming losses. Docs: new "Empty-Window Recovery" section in LongTranscription.md.

Tests

EmptyDecodeRecoveryTests: the energy/length gate (speech-level tone retries; silence, near-silence, under 2 s, and zero padding do not), the mel_length override (only the length changes, fused frontends pass through), and the ladder order and names. The model-level behavior is pinned by the batch benchmark and the streaming fixtures.

Verification

Release build, M5 Pro. The three fixture clips at chunk sizes 6 to 11 and in batch, and the #855 repro clip, are unchanged (the ladder never engages on them). 40 longest LibriSpeech test-clean files (18 min, 2,691 reference words, same normalization as #908), no file worse in any run:

before (#908 head) this PR
streaming, chunk 11 s (default) 4.68% 4.05%
streaming, chunk 7 s 7.43% 4.05%
batch 4.72% 3.16%

Batch improves too: the last, end-aligned chunk of a file can land on such a cut just as a streaming window can.

CI benchmark note

The CI ASR benchmark on this branch reports v3 test-other at 1.59% where the #908 heads reported 1.19%. The per-file artifacts of the two runs differ in exactly one record, 1688-142285-0002.flac, whose hypothesis string is byte-identical in both (You don't mean that you thought me so silly.) yet scores 0% WER in the #908 run and 10% WER / 2.9% CER in the #910 run. The other 99 records across both models and both subsets are identical. Locally the same file scores 0% in eight separate processes on this build, and the recovery ladder never engages on any of the 25 CI files for v2 or v3 on either subset. The CI scorer is producing different numbers for identical text between runs; filed separately as a benchmark nondeterminism.

Round 2

  • Blank means no tokens at all. A streaming re-decode whose new audio holds no speech has an empty visible sequence but tokens suppressed before the cutoff: the window decoded fine and those tokens are seam evidence. isWholeWindowBlank requires both lists empty; regression added for an all-suppressed window.
  • Credible recoveries only. The energy gate is a non-silence test, not a speech test. A recovered hypothesis now replaces the empty decode only with at least two tokens at a mean confidence of 0.7; the genuine recoveries of the reproduced cuts score 0.89 to 0.93 over 33 to 62 tokens. On 80 MUSAN noise and 80 MUSAN music files the model emits nothing on the first decode of every file, the ladder ran on all of them and accepted nothing.
  • Scoped to v3, the model the blank was demonstrated on; v2, the 110m model and the Japanese model keep the plain path.
  • Budget (added in round 2, revised in round 3, removed in round 4; see below).

All reproduced spans still recover, both real streaming losses still recover, the fixture clips are unchanged, and the 40-file WER is unchanged by the gates: 4.05% / 4.05% / 3.16%.

Round 3

  • The round-2 budget could disable the fix indefinitely. It suspended the ladder until a window decoded normally, which a run of pathological speech windows never produces, and the counter outlived the file on a reused manager, so non-speech audio could poison unrelated audio. Replaced by EmptyDecodeRecoveryBudget: after two consecutive failed recoveries the ladder degrades to its first policy (encoderFull, one extra pass per empty window, the policy that recovered most of the reproduced cuts), so the recoverable speech window that follows non-speech audio is still probed; the full ladder returns every fifth empty window; any recovery or normal decode restores it. The budget is reset at every batch transcribe entry and at the streaming manager's startStreaming, reset and error recovery. Regression covers two non-speech failures followed by a probed window, the periodic full ladder, and the per-session reset.

Reproduced spans and both real streaming losses still recover; the 40-file WER on this head is unchanged: 4.05% / 4.05% / 3.16%.

Round 4

  • No budget at all. A reduced ladder loses for good a one-off cut that only a later policy flips: the next window's overlap covers only part of it, and single-shot decoding has no later chance. And any per-session budget needs every entry point to reset it, which transcribeDiskBacked did not. With nothing cheaper than the model itself to tell speech from music or noise, the budget is gone: every window that decodes to nothing and carries energy gets all five policies, and there is no state to reset. EmptyDecodeRecoveryBudget, its tests, the reset hook and the streaming reset calls are removed.
  • Cost, measured on 30 s of MUSAN music on an M5 Pro, net of model load: batch 0.0 s → 0.5 s, streaming at the default chunk 0.4 s → 1.0 s. Still tens of times faster than real time; documented in LongTranscription.md.

All ten previously recovered spans and both real streaming losses still recover (the 13.1 s cut of case A remains the holdout); the 40-file WER on this head is unchanged: 4.05% / 4.05% / 3.16%.

…on ladder (#909)

Parakeet TDT v3 has input cuts on which it emits nothing: the joint
predicts blank at every frame from a fresh state for 11-13 s of clear
speech. It reproduces in batch on the exact span, flips with tenths of a
second of length, and the fp16 MLX port of the same checkpoint blanks on
the same cuts, so this is the model, not CoreML quantization. The encoder
output on such a cut is finite but lower in magnitude, and the outcome
sits on a knife edge with respect to the length inputs (the mel
normalization moves ~1% between a 12 s and a 13 s input).

executeMLInferenceWithTimings, which every decode path goes through, now
recovers an empty decode of a window that carries speech (>= 2 s, RMS >=
-50 dBFS) by re-running from a copy of the entry decoder state with a
ladder of length declarations - padding declared valid to the encoder,
to the preprocessor, the audio declared 0.2 s shorter with that tail
silenced, and the two combinations - keeping the first non-empty result.
A good window is never touched. The ladder recovers 10 of the 11
reproduced spans and both real streaming losses.

40 longest LibriSpeech test-clean files: streaming WER 4.68% -> 4.05%
at chunk 11, 7.43% -> 4.05% at chunk 7; batch 4.72% -> 3.16%.
@github-actions

github-actions Bot commented Sep 11, 2026

Copy link
Copy Markdown

Parakeet EOU Benchmark Results ✅

Status: Benchmark passed
Chunk Size: 320ms
Files Tested: 100/100

Performance Metrics

Metric Value Description
WER (Avg) 7.03% Average Word Error Rate
WER (Med) 4.17% Median Word Error Rate
RTFx 7.97x Real-time factor (higher = faster)
Total Audio 470.6s Total audio duration processed
Total Time 60.6s Total processing time

Streaming Metrics

Metric Value Description
Avg Chunk Time 0.061s Average chunk processing time
Max Chunk Time 0.121s Maximum chunk processing time
EOU Detections 0 Total End-of-Utterance detections

Test runtime: 1m8s • 09/11/2026, 02:03 PM EST

RTFx = Real-Time Factor (higher is better) • Processing includes: Model inference, audio preprocessing, state management, and file I/O

@github-actions

github-actions Bot commented Sep 11, 2026

Copy link
Copy Markdown

Supertonic3 Smoke Test ✅

Check Result
Build
Model download (incl. VectorEstimatorVariants/ int4 buckets)
Model load
Synthesis pipeline (--ve-variant int4)
Output WAV ✅ (364.7 KB)

Runtime: 0m47s

Note: CI VMs lack a physical Neural Engine; the ANE-bucketed VectorEstimator falls back to CPU here. This validates download + variant resolution + synthesis, not ANE residency/perf.

@github-actions

github-actions Bot commented Sep 11, 2026

Copy link
Copy Markdown

Offline VBx Pipeline Results

Speaker Diarization Performance (VBx Batch Mode)

Optimal clustering with Hungarian algorithm for maximum accuracy

Metric Value Target Status Description
DER 10.4% <20% Diarization Error Rate (lower is better)
RTFx 8.77x >1.0x Real-Time Factor (higher is faster)

Offline VBx Pipeline Timing Breakdown

Time spent in each stage of batch diarization

Stage Time (s) % Description
Model Download 22.750 19.0 Fetching diarization models
Model Compile 9.750 8.1 CoreML compilation
Audio Load 0.074 0.1 Loading audio file
Segmentation 31.091 26.0 VAD + speech detection
Embedding 119.242 99.6 Speaker embedding extraction
Clustering (VBx) 0.150 0.1 Hungarian algorithm + VBx clustering
Total 119.695 100 Full VBx pipeline

Speaker Diarization Research Comparison

Offline VBx achieves competitive accuracy with batch processing

Method DER Mode Description
FluidAudio (Offline) 10.4% VBx Batch On-device CoreML with optimal clustering
FluidAudio (Streaming) 17.7% Chunk-based First-occurrence speaker mapping
Research baseline 18-30% Various Standard dataset performance

Pipeline Details:

  • Mode: Offline VBx with Hungarian algorithm for optimal speaker-to-cluster assignment
  • Segmentation: VAD-based voice activity detection
  • Embeddings: WeSpeaker-compatible speaker embeddings
  • Clustering: PowerSet with VBx refinement
  • Accuracy: Higher than streaming due to optimal post-hoc mapping

🎯 Offline VBx Test • AMI Corpus ES2004a • 1049.0s meeting audio • 150.5s processing • Test runtime: 2m 38s • 09/11/2026, 02:13 PM EST

@github-actions

github-actions Bot commented Sep 11, 2026

Copy link
Copy Markdown

PocketTTS Smoke Test ✅

Check Result
Build
Model download
Model load
Synthesis pipeline
Output WAV ✅ (146.3 KB)

Runtime: 0m6s

Note: PocketTTS uses CoreML MLState (macOS 15) KV cache + Mimi streaming state. CI VM lacks physical GPU — audio quality and performance may differ from Apple Silicon.

@github-actions

github-actions Bot commented Sep 11, 2026

Copy link
Copy Markdown

VAD Benchmark Results

Performance Comparison

Dataset Accuracy Precision Recall F1-Score RTFx Files
MUSAN 94.0% 89.3% 100.0% 94.3% 558.3x faster 50
VOiCES 94.0% 89.3% 100.0% 94.3% 612.3x faster 50

Dataset Details

  • MUSAN: Music, Speech, and Noise dataset - standard VAD evaluation
  • VOiCES: Voices Obscured in Complex Environmental Settings - tests robustness in real-world conditions

✅: Average F1-Score above 70%

@github-actions

github-actions Bot commented Sep 11, 2026

Copy link
Copy Markdown

Speaker Diarization Benchmark Results

Speaker Diarization Performance

Evaluating "who spoke when" detection accuracy

Metric Value Target Status Description
DER 15.1% <30% Diarization Error Rate (lower is better)
JER 24.9% <25% Jaccard Error Rate
RTFx 17.90x >1.0x Real-Time Factor (higher is faster)

Diarization Pipeline Timing Breakdown

Time spent in each stage of speaker diarization

Stage Time (s) % Description
Model Download 11.532 19.7 Fetching diarization models
Model Compile 4.942 8.4 CoreML compilation
Audio Load 0.229 0.4 Loading audio file
Segmentation 17.578 30.0 Detecting speech regions
Embedding 29.297 50.0 Extracting speaker voices
Clustering 11.719 20.0 Grouping same speakers
Total 58.627 100 Full pipeline

Speaker Diarization Research Comparison

Research baselines typically achieve 18-30% DER on standard datasets

Method DER Notes
FluidAudio 15.1% On-device CoreML
Research baseline 18-30% Standard dataset performance

Note: RTFx shown above is from GitHub Actions runner. On Apple Silicon with ANE:

  • M2 MacBook Air (2022): Runs at 150 RTFx real-time
  • Performance scales with Apple Neural Engine capabilities

🎯 Speaker Diarization Test • AMI Corpus ES2004a • 1049.0s meeting audio • 58.6s diarization time • Test runtime: 2m 58s • 09/11/2026, 02:17 PM EST

@github-actions

github-actions Bot commented Sep 11, 2026

Copy link
Copy Markdown

ASR Benchmark Results ✅

Status: All benchmarks passed

Parakeet v3 (multilingual)

Dataset WER Avg WER Med RTFx Status
test-clean 0.57% 0.00% 4.06x
test-other 1.59% 0.00% 2.55x

Parakeet v2 (English-optimized)

Dataset WER Avg WER Med RTFx Status
test-clean 0.80% 0.00% 4.13x
test-other 1.56% 0.00% 2.62x

Streaming (v3)

Metric Value Description
WER 0.00% Word Error Rate in streaming mode
RTFx 0.45x Streaming real-time factor
Avg Chunk Time 1.964s Average time to process each chunk
Max Chunk Time 2.515s Maximum chunk processing time
First Token 2.408s Latency to first transcription token
Total Chunks 31 Number of chunks processed

Streaming (v2)

Metric Value Description
WER 0.00% Word Error Rate in streaming mode
RTFx 0.44x Streaming real-time factor
Avg Chunk Time 2.079s Average time to process each chunk
Max Chunk Time 3.089s Maximum chunk processing time
First Token 2.107s Latency to first transcription token
Total Chunks 31 Number of chunks processed

Streaming tests use 5 files with 0.5s chunks to simulate real-time audio streaming

25 files per dataset • Test runtime: 9m41s • 09/11/2026, 02:13 PM EST

RTFx = Real-Time Factor (higher is better) • Calculated as: Total audio duration ÷ Total processing time
Processing time includes: Model inference on Apple Neural Engine, audio preprocessing, state resets between files, token-to-text conversion, and file I/O
Example: RTFx of 2.0x means 10 seconds of audio processed in 5 seconds (2x faster than real-time)

Expected RTFx Performance on Physical M1 Hardware:

• M1 Mac: ~28x (clean), ~25x (other)
• CI shows ~0.5-3x due to virtualization limitations

Testing methodology follows HuggingFace Open ASR Leaderboard

@github-actions

github-actions Bot commented Sep 11, 2026

Copy link
Copy Markdown

Sortformer High-Latency Benchmark Results

ES2004a Performance (30.4s latency config)

Metric Value Target Status
DER 30.3% <35%
Miss Rate 28.2% - -
False Alarm 0.9% - -
Speaker Error 1.2% - -
RTFx 15.8x >1.0x
Speakers 4/4 - -

Sortformer High-Latency • ES2004a • Runtime: 3m 38s • 2026-09-11T18:06:47.189Z

…only credible recoveries (#909)

Review round 2. A streaming re-decode whose new audio holds no speech has
an empty visible sequence but tokens suppressed before the cutoff: the
window decoded fine and those tokens are seam evidence, so the ladder must
not run on it. isWholeWindowBlank requires both lists empty; regression
added.

The energy gate is a non-silence test, not a speech test, so music or
noise can reach the ladder. A recovered hypothesis now replaces the empty
decode only when it is credible: at least two tokens at a mean confidence
of 0.7 (genuine recoveries of the reproduced cuts score 0.89-0.93 over
33-62 tokens). On 80 MUSAN noise and 80 music files the ladder accepted
nothing. The recovery is enabled for parakeet-tdt-0.6b-v3 only, the model
it was demonstrated on.
#909)

Music and noise decode to nothing on every window and pass the energy
gate (160 of 160 MUSAN files), so without a budget a non-speech stream
paid five extra passes per window while accepting nothing. The ladder now
suspends after two consecutive failed recoveries and resumes when a window
decodes normally.
…ed per transcription (#909)

Review round 3. The previous budget suspended the ladder until a window
decoded normally, which a run of pathological speech windows never does,
and the counter outlived the file on a reused manager, so non-speech
audio could disable the fix indefinitely for unrelated audio.

EmptyDecodeRecoveryBudget replaces the counter: after two consecutive
failed recoveries the ladder degrades to its first policy (encoderFull,
one extra pass per empty window, the policy that recovered most of the
reproduced cuts) so the recoverable speech window that follows is still
probed, the full ladder returns every fifth empty window, and any
recovery or normal decode restores it. The budget is reset at every batch
transcribe entry and at the streaming manager's startStreaming, reset and
error recovery. Regression covers two non-speech failures followed by a
probed window, the periodic full ladder, and the per-session reset.
… the budget (#909)

Review round 4. A reduced ladder loses for good a one-off cut that only a
later policy flips (the next window's overlap covers only part of it, and
single-shot decoding has no later chance), and any per-session budget
needs every entry point to reset it (transcribeDiskBacked did not). With
nothing cheaper than the model to tell speech from music or noise, the
budget is removed: every window that decodes to nothing and carries
energy gets all five policies. Measured cost on 30 s of MUSAN music on an
M5 Pro, net of model load: batch 0.0 s -> 0.5 s, streaming 0.4 s -> 1.0 s,
still tens of times faster than real time.
@Alex-Wengg
Alex-Wengg merged commit c7562fa into main Sep 11, 2026
13 checks passed
@Alex-Wengg
Alex-Wengg deleted the fix/909-blank-window branch September 11, 2026 22:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Parakeet v3 CoreML: whole-window blank (no tokens) on specific 11–13 s spans, batch-reproducible; loses 4–19 words in streaming

1 participant