Skip to content

fix: download progress stuck at 95% and slow first transcription - #16

Merged
jatinkrmalik merged 3 commits into
mainfrom
fix/download-progress-and-prewarm
Mar 5, 2026
Merged

fix: download progress stuck at 95% and slow first transcription#16
jatinkrmalik merged 3 commits into
mainfrom
fix/download-progress-and-prewarm

Conversation

@jatinkrmalik

Copy link
Copy Markdown
Member

Issues Fixed

1. Download progress stuck at 95%

Root cause: Three race conditions in the simulated progress system:

  1. The progress simulation task used try? await Task.sleep which silently swallowed CancellationError. When the download completed and progressTask.cancel() was called, the loop kept running and wrote 0.95 after the completion handler wrote 1.0.

  2. No Task.isCancelled guard between the sleep and the progress update, so a cancelled task could still fire off one more stale update.

  3. In AppState.downloadModel, refreshModelStatuses() set downloadProgress = nil immediately after the download, but the final onProgress(1.0) callback was still in-flight on @MainActor, so the 1.0 update would arrive after the nil — leaving the UI stuck showing the last simulated value.

Fix:

  • Use try await Task.sleep (throws on cancel) + explicit Task.isCancelled guard
  • Cap simulated progress at 90% for a clearer visual jump to 100%
  • Add 50ms delay after cancelling the progress task before sending 1.0
  • Add 100ms delay in downloadModel before refreshModelStatuses() to let the final callback settle

2. First transcription extremely slow after switching models

Root cause: WhisperService.loadModel() was not prewarming the CoreML pipeline. Without prewarming, the first call to transcribe() triggers CoreML's Metal shader compilation and ANE graph optimization — which can take 10-30 seconds depending on model size.

Fix: Set config.prewarm = true in WhisperKitConfig. This moves the pipeline compilation cost to model load time (where the user already expects a loading phase) instead of surprising them on the first dictation.

Files Changed

  • Sources/VocaMac/Services/ModelManager.swift — Fixed progress simulation race conditions
  • Sources/VocaMac/Models/AppState.swift — Fixed progress callback ordering
  • Sources/VocaMac/Services/WhisperService.swift — Enabled model prewarming

Download progress fixes:
- Use 'try await Task.sleep' (not 'try?') so cancellation properly stops
  the simulated progress loop instead of silently swallowing the error
- Add Task.isCancelled guard to prevent stale progress updates after
  the download completes
- Add small delay after cancelling progress task before sending the
  final 1.0 update, preventing the race where 0.95 overwrites 1.0
- Cap simulated progress at 90% (not 95%) for a more visible jump
  to completion
- Fix race condition in AppState.downloadModel where
  refreshModelStatuses() could clear downloadProgress before the
  final progress callback settled on MainActor

Slow first transcription fix:
- Enable WhisperKit model prewarming (config.prewarm = true) when
  loading models. This compiles the CoreML Metal/ANE pipeline at load
  time instead of lazily on the first transcription request, which was
  causing extreme latency on the first dictation after switching models.
WhisperKit stores downloaded models at:
  downloadBase/models/argmaxinc/whisperkit-coreml/<model_name>/

But ModelManager was checking for models at:
  downloadBase/<model_name>/

This caused isModelDownloaded() to always return false, so:
- Downloaded models always showed 'Download & Load' instead of 'Load'
- Model disk usage showed 0 bytes
- modelFolder() returned nil, forcing re-downloads on every load

Added modelStorageBase computed property that resolves to the correct
nested path and updated all methods that check/access model files.
Removes all VocaMac data:
- Running process
- Downloaded models (~/.../Application Support/VocaMac/)
- CoreML compilation cache
- Launcher scripts (~/.local/bin/vocamac*)
- App bundle (VocaMac.app)
- UserDefaults/preferences
- Build artifacts (optional --keep-build flag)

Usage: ./scripts/uninstall.sh [--keep-build]
@jatinkrmalik
jatinkrmalik merged commit 7a10494 into main Mar 5, 2026
1 check passed
@jatinkrmalik
jatinkrmalik deleted the fix/download-progress-and-prewarm branch August 22, 2026 05:32
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.

1 participant