Skip to content

models: download outside Documents so iCloud cannot evict them - #12

Open
andredezzy wants to merge 1 commit into
digimata:masterfrom
andredezzy:fix/models-outside-documents
Open

models: download outside Documents so iCloud cannot evict them#12
andredezzy wants to merge 1 commit into
digimata:masterfrom
andredezzy:fix/models-outside-documents

Conversation

@andredezzy

@andredezzy andredezzy commented Jul 30, 2026

Copy link
Copy Markdown

Problem

WhisperKit's downloadBase defaults to ~/Documents/huggingface (inherited from swift-transformers' HubApi). With iCloud Desktop & Documents sync enabled, model weights are replicated to iCloud and, once the disk fills, evicted to dataless placeholders — st_flags carries SF_DATALESS and st_blocks == 0 while the logical size stays intact.

CoreML mmap()s the weight blob during ANE compilation, and mmap of an evicted file blocks indefinitely. The daemon prints loading <model>... and never reaches listening on fn hold:

Thread ... com.apple.coreml.MLE5ProgramLibrary.lazyInitQueue
  -[MLE5ProgramLibrary _programLibraryHandleWithForceRespecialization:error:]
  e5rt_e5_compiler_compile_from_ir_program
  Espresso::AOT::MILCompilerForE5::Run
  ...
  MIL::Blob::MMapFileReader::MMapFileReader
  __mmap                      <- all 2649 samples parked here, 3s CPU in 18 min

When materialisation partially succeeds instead, the load fails outright:

warmup failed: Error Domain=com.apple.CoreML Code=0 "Unable to load model:
.../AudioEncoder.mlmodelc/" ... Code=3 "Failed to read first word from
.../AudioEncoder.mlmodelc/coremldata.bin. It is not a valid .mlmodelc file."

The files are not corrupt — all 24 verified byte-clean against their HuggingFace etags. Only the read path is broken.

Because the LaunchAgent sets KeepAlive{SuccessfulExit: false}, each failed load is relaunched, so this presents as a daemon that either hangs forever or restarts in a loop.

Observed on macOS 26.4.1 (M2, 16 GB) with whisper-large-v3-turbo on a 94%-full disk. whisper-base.en (145 MB) stayed resident and kept working, so this only bites on larger models — exactly the ones a user switches to deliberately.

Fix

Point downloadBase at ~/Library/Application Support/parrot/huggingface. Model weights are machine-local cache, not user documents: Application Support is neither user-visible, nor treated as documents by backup, nor sync-managed.

Existing installs are left alone rather than silently re-downloading gigabytes — the old Documents copy is reported once on load so the space is reclaimable.

Verification

Built release, replaced the installed binary, deleted ~/Documents/huggingface, then:

$ parrot models download whisper-base.en
loading whisper-base.en...
✓ whisper-base.en ready

$ du -sh ~/Library/Application\ Support/parrot/huggingface
1.7G

Daemon reaches listening on fn hold with ~/Documents clean.

Note

The underlying default belongs to swift-transformers, whose HubApi places every embedding app's models under Documents. That deserves its own issue upstream; this change makes parrot correct regardless of what HubApi defaults to.

Why Application Support and not Caches

Model weights are re-downloadable, which argues for Caches — but the system may
purge Caches under disk pressure, and on this hardware that means a surprise
1.6 GB re-download plus a multi-minute ANE recompile before the next dictation.
Application Support is the correct home for large, app-managed data the user
never browses and cannot cheaply recreate.

Notes for review

  • Both statics are private; nothing outside the actor needs them.
  • No README change: the README never documented the model location, so adding
    one here would widen a bugfix PR.
  • The repository has no PR CI (release.yml only runs on v* tags and
    workflow_dispatch), so this was validated locally with
    swift build -c release — clean, no warnings.

The common case, not just the exotic one

The infinite mmap above needs disk pressure to trigger, but the everyday harm
does not: with Desktop & Documents sync enabled, these weights are uploaded
to the user's iCloud account and counted against their quota. The free tier is
5 GB; whisper-large-v3-turbo alone is ~1.6 GB of it, pushed over the network
without ever being mentioned. They also show up in Finder under Documents and
are carried into backups.

So this is not only about the hang — it is about a dictation tool quietly
placing gigabytes of re-downloadable machine-local cache into the user's
document storage.

WhisperKit's downloadBase defaults to ~/Documents/huggingface. With iCloud
Desktop & Documents sync on, the model weights are replicated and, once the
disk fills, evicted to dataless placeholders (SF_DATALESS, st_blocks=0).

CoreML mmap()s the weight blob during ANE compilation, and mmap of an evicted
file blocks indefinitely: the daemon prints "loading <model>..." and never
reaches "listening on fn hold". A partially materialised read instead fails as
CoreML error 3, "Failed to read first word from AudioEncoder.mlmodelc/
coremldata.bin. It is not a valid .mlmodelc file." Observed on macOS 26.4.1
with whisper-large-v3-turbo (1.6 GB) on a 94%-full disk.

Point downloadBase at Application Support, which is neither user-visible nor
sync-managed. Existing installs keep their Documents copy; note it on load so
the space is reclaimable rather than silently abandoned.
@andredezzy
andredezzy force-pushed the fix/models-outside-documents branch from f3b4c8e to 4a6e3d6 Compare July 31, 2026 00:03
FernandoGomes83 added a commit to FernandoGomes83/parrot that referenced this pull request Jul 31, 2026
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