Skip to content

fix(download): interrupted first download no longer bricks streaming ASR managers (#819); Unified int8 A16 compat docs (#828) - #829

Merged
Alex-Wengg merged 3 commits into
mainfrom
fix/819-interrupted-download-cache
Aug 1, 2026
Merged

Alex-Wengg merged 3 commits into
mainfrom
fix/819-interrupted-download-cache

Conversation

@Alex-Wengg

@Alex-Wengg Alex-Wengg commented Aug 1, 2026 •

Copy link
Copy Markdown
Member

Fixes #819.

Problem

StreamingUnifiedAsrManager, UnifiedAsrManager, StreamingNemotronAsrManager, and StreamingEouAsrManager gated their cache-or-download decision on bare file/directory existence and then called MLModel.load directly. An interrupted first download (e.g. app backgrounded midway through the ~550 MB encoder fetch) leaves the encoder .mlmodelc directory present but containing only weights/weight.bin.partial and no root coremldata.bin — the gate reports "cached", the load fails identically on every launch, nothing ever re-downloads, and the app stays broken until its data is deleted.

The hardened path (ModelHub.loadModels: layout validation + purge-and-retry) never ran for these managers because they need per-model MLModelConfigurations (encoder on ANE, decoder/joint on CPU) that loadModels' single computeUnits parameter cannot express.

Fix

1. ModelHub.loadWithRecovery — a recovery wrapper for self-loading managers with the same guarantees as loadModels:

  • Real cache-validity check via new ModelCache.incompleteFiles: every required .mlmodelc must have its root coremldata.bin and contain no *.partial staging file; plain files must exist. An interrupted download therefore re-enters ModelHub.download, which resumes the .partial via HTTP Range instead of restarting from zero.
  • Purge-and-retry on load failure: cache purged, re-downloaded, load retried once — except in offline mode, on cancellation, and on transient network errors, where the cache is preserved and the error rethrown (mirroring loadModels' guards).

All four managers now declare their required file set and load through the wrapper. The partial-file scan is scoped to required bundles so a leftover partial from an unneeded bundle (e.g. fp16 encoder when int8 is requested) doesn't force a listing round-trip on every load. Nemotron's decoder_joint.mlmodelc/metadata.json stay out of the validity set because its loader treats them as optional (the download registry still fetches them).

2. SenseVoice / Paraformer — the same existence-only gates, hardened the same way: modelsExist now judges load-readiness instead of bare existence, and downloadAndLoad routes through loadWithRecovery. Public API shapes (download()/load(from:)/modelsExist) unchanged.

3. Truncated vs. hardware-incompatible diagnosis (per the #819 discussion / #828): when the purge-and-redownload retry also fails — in both loadModels and loadWithRecovery — ModelHub.logLoadFailureSizeDiagnosis compares each required file's on-disk bytes against the published HuggingFace tree sizes and logs which look-alike failure class this is:

Best-effort diagnostics: needs the network for the listing, silent in offline mode, skipped on cancellation, never throws.

4. #828: Parakeet Unified hardware compatibility docs + int8 load-failure hint — the int8 Unified 0.6B encoder fails to load on A16 (iPhone 14 Pro) on every compute unit, including .cpuOnly (CoreML "Failed to build the model execution plan", -14), even from an intact download; fp16 loads on the same device. The failure reads like a corrupt download, which sent the #828 reporter into #819's cache loop for days.

  • Documentation/Models.md: Parakeet Unified 0.6B was absent from the model catalog entirely. Added batch + streaming entries and a Model Sources row with the compatibility note the reporter asked for: int8 verified on M-series, known not to load on A16 (any compute unit), other A-series unverified — use encoderPrecision: .fp16 on iOS.
  • Documentation/Benchmarks.md: same iOS note in the Unified benchmark section, next to the int8-is-default recommendation.
  • Unified{,Streaming}AsrManager.loadModels(from:): when the int8 encoder load throws, log the A-series caveat and the .fp16 escape hatch before rethrowing, so the diagnosis appears at the load site instead of only after the recovery path's purge-and-retry exhausts itself.

The purge-and-retry flow is intentionally unchanged: a full-size-but-corrupt cache is still a real failure class that purging fixes, and gating the purge on the best-effort HF size check would regress that recovery; the post-retry size diagnosis (point 3) already labels the incompatibility case.

Verification

CI note

The first tests.yml run failed on testLoadWithRecoveryOfflineLoadFailureDoesNotPurgeCache — a test-isolation bug, not a library bug: the test placed its fixture cache directly in the shared temp directory, where parallel test processes from other suites create/purge the same silero-vad repo path. Fixed by giving each test a UUID-unique models root.

🤖 Generated with Claude Code

…ASR managers (#819)

StreamingUnifiedAsrManager, UnifiedAsrManager, StreamingNemotronAsrManager,
and StreamingEouAsrManager gated their cache-or-download decision on bare
file/directory existence and then called MLModel.load directly. An
interrupted first download (app backgrounded mid-way through the ~550 MB
encoder fetch) leaves the encoder .mlmodelc directory present but
containing only weights/weight.bin.partial and no root coremldata.bin —
the gate reports "cached", the load fails, nothing ever re-downloads, and
the app stays broken until its data is deleted. The hardened path
(ModelHub.loadModels: layout validation + purge-and-retry) never ran for
these managers because they need per-model MLModelConfigurations (encoder
on ANE, decoder/joint on CPU) that loadModels' single computeUnits cannot
express.

Add ModelHub.loadWithRecovery, a recovery wrapper for self-loading
managers with the same guarantees as loadModels:

- Cache validity is judged per required file via the new
  ModelCache.incompleteFiles: every .mlmodelc must have its root
  coremldata.bin and contain no *.partial staging file; plain files must
  exist. An interrupted download therefore re-enters ModelHub.download,
  which resumes the .partial via HTTP Range instead of restarting.
- On load failure the repo cache is purged, re-downloaded, and the load
  retried once — except in offline mode, on cancellation, and on
  transient network errors, where the cache is preserved and the error
  rethrown (mirroring loadModels' guards).

The partial-file scan is scoped to required bundles so a leftover partial
from a bundle the caller does not need (e.g. the fp16 encoder when int8
is requested) does not force a listing round-trip on every load.

Nemotron's decoder_joint.mlmodelc and metadata.json stay out of the
validity set because loadModels(from:) treats them as optional; the
download registry still fetches them.

SenseVoiceModels/ParaformerModels have the same existence-only gate but a
different public download()/load(from:) API split; left for a follow-up.

Fixes #819
@github-actions

github-actions Bot commented Aug 1, 2026 •

Copy link
Copy Markdown

PocketTTS Smoke Test ✅

Check Result
Build ✅
Model download ✅
Model load ✅
Synthesis pipeline ✅
Output WAV ✅ (161.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 Aug 1, 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.90x Real-time factor (higher = faster)
Total Audio 470.6s Total audio duration processed
Total Time 61.1s Total processing time

Streaming Metrics

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

Test runtime: 1m8s • 07/31/2026, 11:34 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 Aug 1, 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 12.53x >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.712 27.1 Fetching diarization models
Model Compile 9.734 11.6 CoreML compilation
Audio Load 0.032 0.0 Loading audio file
Segmentation 22.554 26.9 VAD + speech detection
Embedding 83.518 99.7 Speaker embedding extraction
Clustering (VBx) 0.094 0.1 Hungarian algorithm + VBx clustering
Total 83.766 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 • 106.2s processing • Test runtime: 2m 0s • 07/31/2026, 11:58 PM EST

@github-actions

github-actions Bot commented Aug 1, 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% 759.7x faster 50
VOiCES 94.0% 89.3% 100.0% 94.3% 798.9x 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 Aug 1, 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: 0m35s

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 Aug 1, 2026

Copy link
Copy Markdown

✅ Nemotron Multilingual Benchmark — FLEURS

FLEURS en_us, chunk 2240ms, 100 samples, B1 fused decode path. Same English audio against both shipped models.

Model Language WER RTFx
latin/ (pruned 2828) English 7.84% 6.1x
multilingual/ (full 13087) English 7.91% 5.9x
Logs (tail)
[latin / English]

Language     | Prompt   | WER%   | CER%   | RTFx   | Duration  | Processed | Skipped
--------------------------------------------------------------------------------
en_us        | en-US    | 7.8    | 3.5    | 6.1    | 953.9s    | 100       | -
--------------------------------------------------------------------------------
AVERAGE      | —        | 7.8    | 3.5    | 6.1   


[multilingual / English]

Language     | Prompt   | WER%   | CER%   | RTFx   | Duration  | Processed | Skipped
--------------------------------------------------------------------------------
en_us        | en-US    | 7.9    | 3.6    | 5.9    | 953.9s    | 100       | -
--------------------------------------------------------------------------------
AVERAGE      | —        | 7.9    | 3.6    | 5.9   

@github-actions

github-actions Bot commented Aug 1, 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 23.77x >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.722 26.5 Fetching diarization models
Model Compile 5.024 11.4 CoreML compilation
Audio Load 0.079 0.2 Loading audio file
Segmentation 13.241 30.0 Detecting speech regions
Embedding 22.068 50.0 Extracting speaker voices
Clustering 8.827 20.0 Grouping same speakers
Total 44.153 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 • 44.1s diarization time • Test runtime: 2m 26s • 07/31/2026, 11:46 PM EST

@github-actions

github-actions Bot commented Aug 1, 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.36x ✅
test-other 1.35% 0.00% 3.43x ✅

Parakeet v2 (English-optimized)

Dataset WER Avg WER Med RTFx Status
test-clean 0.80% 0.00% 5.09x ✅
test-other 1.00% 0.00% 3.36x ✅

Streaming (v3)

Metric Value Description
WER 0.00% Word Error Rate in streaming mode
RTFx 0.61x Streaming real-time factor
Avg Chunk Time 1.483s Average time to process each chunk
Max Chunk Time 1.623s Maximum chunk processing time
First Token 1.771s 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.56x Streaming real-time factor
Avg Chunk Time 1.588s Average time to process each chunk
Max Chunk Time 2.153s Maximum chunk processing time
First Token 1.562s 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: 8m59s • 08/01/2026, 12:02 AM 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

…vs-incompatible load diagnosis

Extends the loadWithRecovery hardening to the two remaining
existence-only gates and adds the size diagnosis discussed in #819:

- SenseVoiceModels/ParaformerModels: modelsExist now judges load-readiness
  (root coremldata.bin present, no *.partial staging files) instead of
  bare existence, and downloadAndLoad routes through
  ModelHub.loadWithRecovery for completeness-checked download plus
  purge-and-retry. The public download()/load(from:)/modelsExist API
  shapes are unchanged.

- ModelHub.logLoadFailureSizeDiagnosis: when the purge-and-redownload
  retry ALSO fails (both in loadModels and loadWithRecovery), compare
  each required file's on-disk bytes against the published HuggingFace
  tree sizes and log which failure class this is: short files mean a
  truncated cache (clear + re-download helps), all-full-size means the
  model cannot run on this hardware/OS (#828 — re-download cannot help).
  CoreML's "Unable to load model" reads identically in both cases, which
  cost the #819 reporters days. Best-effort: needs the network for the
  listing, silent in offline mode, skipped on cancellation, never throws.
  The pure size comparison lives in ModelCache.undersizedFiles for
  testability.

Also fixes a CI-only test-isolation bug in ModelCacheCompletenessTests:
the loadWithRecovery tests placed a silero-vad cache directly in the
shared temp directory, where parallel test processes from other suites
create and purge the same repo path; each test now uses a UUID-unique
models root.

Verified live against the real HF VAD repo: both diagnosis branches log
correctly (intact -> incompatibility message; truncated weight file ->
truncated message with byte counts), and the SenseVoice/Paraformer gates
reject the interrupted-download shape while accepting complete caches.
@github-actions

github-actions Bot commented Aug 1, 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 19.4x >1.0x ✅
Speakers 4/4 - -

Sortformer High-Latency • ES2004a • Runtime: 2m 42s • 2026-08-01T03:35:05.868Z

…ailure hint

Issue #828: the int8 Unified 0.6B encoder fails to load on A16 (iPhone
14 Pro) on every compute unit — including .cpuOnly — with CoreML
'Failed to build the model execution plan' (-14), even from an intact
download. fp16 loads and transcribes on the same device. The failure
reads like a corrupt download, which sent the reporter into #819's
cache loop for days.

- Documentation/Models.md: Parakeet Unified 0.6B was absent from the
  model catalog entirely. Add batch + streaming entries and a Model
  Sources row, with the hardware note the reporter asked for: int8
  verified on M-series, known not to load on A16 (any compute unit),
  other A-series unverified, use fp16 on iOS.
- Documentation/Benchmarks.md: same note in the Unified benchmark
  section, next to the int8-is-default recommendation.
- Unified{,Streaming}AsrManager.loadModels(from:): when the int8
  encoder load throws, log the A-series caveat and the
  encoderPrecision: .fp16 escape hatch before rethrowing, so the
  failure is diagnosable at the load site instead of only after
  loadWithRecovery's purge-and-retry exhausts itself.

The purge-and-retry flow is intentionally unchanged: a full-size-but-
corrupt cache is still a real failure class that purging fixes, and
gating the purge on the best-effort HF size check would regress that
recovery; the post-retry size diagnosis (d9116ce) already labels the
incompatibility case.
@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown

✅ Nemotron Multilingual Benchmark — FLEURS

FLEURS en_us, chunk 2240ms, 100 samples, B1 fused decode path. Same English audio against both shipped models.

Model Language WER RTFx
latin/ (pruned 2828) English 7.84% 5.9x
multilingual/ (full 13087) English 7.91% 5.5x
Logs (tail)
[latin / English]

Language     | Prompt   | WER%   | CER%   | RTFx   | Duration  | Processed | Skipped
--------------------------------------------------------------------------------
en_us        | en-US    | 7.8    | 3.5    | 5.9    | 953.9s    | 100       | -
--------------------------------------------------------------------------------
AVERAGE      | —        | 7.8    | 3.5    | 5.9   


[multilingual / English]

Language     | Prompt   | WER%   | CER%   | RTFx   | Duration  | Processed | Skipped
--------------------------------------------------------------------------------
en_us        | en-US    | 7.9    | 3.6    | 5.5    | 953.9s    | 100       | -
--------------------------------------------------------------------------------
AVERAGE      | —        | 7.9    | 3.6    | 5.5   

@Alex-Wengg Alex-Wengg changed the title fix(download): interrupted first download no longer bricks streaming ASR managers (#819) fix(download): interrupted first download no longer bricks streaming ASR managers (#819); Unified int8 A16 compat docs (#828) Aug 1, 2026
@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown

✅ Nemotron Multilingual Benchmark — FLEURS

FLEURS en_us, chunk 2240ms, 100 samples, B1 fused decode path. Same English audio against both shipped models.

Model Language WER RTFx
latin/ (pruned 2828) English 7.89% 5.7x
multilingual/ (full 13087) English 7.91% 5.6x
Logs (tail)
[latin / English]

Language     | Prompt   | WER%   | CER%   | RTFx   | Duration  | Processed | Skipped
--------------------------------------------------------------------------------
en_us        | en-US    | 7.9    | 3.5    | 5.7    | 953.9s    | 100       | -
--------------------------------------------------------------------------------
AVERAGE      | —        | 7.9    | 3.5    | 5.7   


[multilingual / English]

Language     | Prompt   | WER%   | CER%   | RTFx   | Duration  | Processed | Skipped
--------------------------------------------------------------------------------
en_us        | en-US    | 7.9    | 3.6    | 5.6    | 953.9s    | 100       | -
--------------------------------------------------------------------------------
AVERAGE      | —        | 7.9    | 3.6    | 5.6   

@Alex-Wengg
Alex-Wengg merged commit e8bd3a2 into main Aug 1, 2026
13 checks passed
@Alex-Wengg
Alex-Wengg deleted the fix/819-interrupted-download-cache branch August 1, 2026 04:05
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.

StreamingUnifiedAsrManager: interrupted first download permanently bricks model loading (cache check is directory-existence only)

1 participant