DEV Community

Nucleus OS
Nucleus OS

Posted on

Bundling a 1.9GB MLX Model Inside a macOS DMG

We build Sovereign Voice at Eidetic Works — a local-first TTS app for Mac that runs Qwen3-TTS on Apple Silicon via MLX. This is a side project alongside our main work on Nucleus OS.

The problem

When we launched on Product Hunt, we got 2,738 pageviews. Zero activations.

The reason? A 9-minute model download on first launch.

The app shipped as a 403MB DMG — small, fast to download. But on first launch, the daemon downloaded a 1.9GB model from HuggingFace Hub. Users saw "Warming up…" for 549 seconds (measured on an M3 Pro) and assumed the app was frozen.

3 real users tried it. All churned within hours.

The fix: bundle the model in the DMG

Instead of downloading the model on first launch, we bundled it directly into the app bundle's Resources directory before creating the DMG.

Step 1: Copy the HF cache snapshot into the build

In our build script, before the hdiutil create call, we added a step that searches for the model in the local HF cache and copies it into the app bundle. hdiutil auto-sizes the DMG based on content. The DMG went from 403MB to 1.9GB.

Step 2: Copy-on-first-launch (the read-only problem)

After code signing and notarization, the Resources directory is read-only. huggingface_hub writes lock files during cache lookups even when the model is present. Pointing HF_HOME directly at the bundled path would crash.

The solution: on first launch, copy the bundled weights to a writable location (~/Library/Application Support/Sovereign Voice/hf_cache/), then set HF_HOME there and HF_HUB_OFFLINE=1 so huggingface_hub resolves from local cache without any network access.

Step 3: UI feedback during the copy

The copy takes about 5-10 seconds (local SSD to SSD). We added a "Preparing models for first use…" status to the existing boot overlay.

Results

Metric Before After
DMG size 403MB 1.9GB
First launch wait 549s (9 min) ~10s (local copy)
Network required Yes (1.9GB download) No (fully offline)
Model engine Qwen3-TTS 0.6B 8-bit (Apache-2.0) Same

The tradeoff: the DMG is 5x larger. But users expect a big download for a local AI app — they don't expect a 9-minute wait after installing.

Why not just add a progress bar?

We tried. The existing download screen already had a progress bar with MB/total and percentage. But the download happens inside the daemon (mlx_audio.load_model), which emits only stdout log lines — no structured progress events. The frontend progress bar was dead code.

We added a dynamic ETA to the download screen as a fallback, but the real fix was eliminating the download entirely.

Stack

  • Engine: Qwen3-TTS-0.6B-8bit via mlx-audio 0.4.5 (Apache-2.0)
  • Runtime: MLX on Apple Silicon (Metal)
  • App: Tauri 2.x + React
  • Daemon: Python, frozen via PyInstaller (589MB binary)
  • Build: PyInstaller, manual .app assembly, hdiutil DMG, codesign, notarize

Try it

Sovereign Voice is free to try (25 generations, 1 voice). Pro is $19 one-time — all voices, voice cloning, studio mode, unlimited generations. No subscription.

Works fully offline after install. Apple Silicon Macs only (M1+).

This is a side project at Eidetic Works. Our main product is Nucleus OS — an open-source agent control plane for cross-tool memory.

Top comments (0)