Files
VoiceStudio/docs/downloading-models.md
T
4cc55ab852 Fast model downloads: Xet fast path + accurate progress (FDL W0–W2 + W4) (#424)
* feat(downloads): Xet fast path + accurate progress (FDL W0–W2)

Make model downloads fast and show accurate downloaded/remaining/speed.
Research confirmed hf-xet already implements the IDM/uGet technique
(content-defined chunking, parallel byte-range gets, dedup, resume), and
the spike found all 25 catalog repos are Xet-backed — so the win is
driving Xet well + accurate progress, not a custom downloader.

W1 — maximize + guarantee Xet:
- pin huggingface_hub>=1.7 + hf-xet>=1.1 (was transitive); no hf_transfer
- drive snapshot_download with explicit tqdm_class + max_workers + endpoint
- opt-in HF_XET_HIGH_PERFORMANCE / HDD sequential-write knobs (default off)
- /system/info reports fast_download {xet_enabled, xet_version, high_perf}

W2 — accurate progress:
- dry_run preflight -> install_plan event (exact total/cached/remaining)
- utils/download_aggregator.py: one overall bar; byte bars (by id) vs the
  "Fetching N files" count bar; windowed rate; emits one 'aggregate' event
- frontend overall bar (speed/remaining/ETA), cached-skip,  fast badge

Known limit (verified live): under Xet+hf_hub 1.7.2 per-file byte bars
never advance/close via tqdm, so mid-download the bar is file-granular and
bytes flush to the exact total on completion. Classic-LFS/mirror repos get
true byte progress (W4).

Drive-by: download.py used os.walk without importing os (latent NameError
in _validate_snapshot_has_weights on every install) — fixed.

Tests: tests/backend/setup/test_download_preflight.py (10). Spike + plan
under .planning/quick/260613-fdl-fast-model-downloads/.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(downloads): opt-in mirror + cancel + docs (FDL W4)

- mirror (FDL-10): snapshot_download(endpoint=) honours prefs hf_endpoint /
  env HF_ENDPOINT on preflight + download (per-call, no process-wide env).
  Documented as the classic-LFS path (no Xet) for restricted networks.
- cancel (FDL-11): POST /models/install/cancel {repo_id} stops further
  retries at the next boundary, emits install_cancelled, clears the cooldown
  (cancel is intent, not failure). Frontend treats it as a terminator.
- docs (FDL-12): docs/downloading-models.md (Xet fast path, progress
  semantics + byte-speed limitation, opt-in tuning, mirror, cancel,
  troubleshooting) + README pointer. Docs-sync rule satisfied.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(planning): model-management v2 cleanup plan (mm2)

GSD plan for cleaning the model-management subsystem: registry unload-on-
switch + per-engine unload() (fixes VRAM leak), model_lifecycle facade,
unified idle/timeout config, bounded cooldowns, sidecar VRAM self-report,
cache-fallback logging. Planning artifact only — no code.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(downloads): reconcile with main's HF_HUB_DISABLE_XET; honest status

Rebasing onto main surfaced that main forces HF_HUB_DISABLE_XET=1 (classic
LFS) because Xet progress bypasses the tqdm hook — the same limitation found
here. Reconcile instead of fight:

- /system/info fast_download now reports runtime truth: xet_installed +
  xet_active (installed AND not HF_HUB_DISABLE_XET) + xet_enabled alias. The
   badge only shows when Xet actually runs; startup log says
  "downloads: Xet disabled → legacy LFS".
- complete(): clear the rate window before the final flush so crediting the
  full size in one step can't emit an absurd instantaneous rate.
- docs/downloading-models.md rewritten: default is legacy LFS for accurate
  progress; Xet is opt-in via HF_HUB_DISABLE_XET=0. hf-xet pin stays (ready
  for a future Xet progress hook).

W2 (preflight total/remaining + aggregate bar + exact completion) is the
value on either path; W1's "maximize Xet" is dormant by main's design.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(downloads): opt-in segmented multi-connection accelerator (FDL W3)

Since main forces Xet off (HF_HUB_DISABLE_XET=1), the default path is
single-stream legacy LFS — so a segmented downloader is the way to get BOTH
parallel speed and live byte progress.

- services/segmented_download.py: async multi-connection Range downloader for
  one file — parallel byte-ranges, resume (.part + manifest), per-segment
  short-read truncation guard, optional sha256/etag verify, cancel, and a
  single-stream fallback when the server won't range. Auth-safe: the HF
  Authorization header is sent only to huggingface.co/hf.co and never
  forwarded to a CDN host on redirect (unit-tested).
- dispatch (download.py): opt-in via prefs segmented_downloader / env
  OMNIVOICE_SEGMENTED_DOWNLOAD (default off). When on and Xet inactive,
  fetches each file into the HF cache mirroring hf_hub_download (blobs +
  snapshot symlinks + refs/main), feeding real bytes to the aggregator. Any
  failure falls back to snapshot_download — never breaks a correct install.
- fix: complete() was adding a full total on top of accumulated segmented
  bytes (2x); now replaces byte bars so the sum is exactly total.

Verified live (accelerator on): real byte progress to ~16.6 MB/s, final
bytes==total, /models installed=True, delete frees correctly.

Tests: test_segmented_download.py (7) + aggregator double-count regression.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(downloads): relocate FDL tests to top-level; loop-isolate segmented test

CI runs the full suite, which exposed a pre-existing test-isolation leak:
several tests/backend/** fixtures purge core.*/services.* from sys.modules
under a temp OMNIVOICE_DATA_DIR and never restore, leaving core.config/core.db
bound to a dead temp dir. It only bites when collection order puts a purging
test ahead of a real-DB reader (test_longform_jobs). Adding tests under
tests/backend/setup/ reordered collection and tripped it.

Fix without touching the shared (fragile) fixtures or risking class-identity
breakage from a blanket sys.modules restore:
- move the two FDL test files to top-level tests/ (tests/test_fdl_*.py) so
  tests/backend/** collection order is identical to main — longform passes.
- rewrite the segmented test to run each case under asyncio.run() (fresh loop)
  instead of asyncio.get_event_loop(), which an earlier async test can leave
  closed in the full suite.

Full suite green locally: 1364 passed, 0 failed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 20:15:15 +05:30

5.1 KiB

Downloading models — speed & troubleshooting

OmniVoice downloads models from the Hugging Face Hub on first use. This page explains how downloads are made fast, how to read the progress, and what to do on slow or restricted networks.

Download backend: legacy LFS by default (accurate progress)

OmniVoice ships hf_xet (Hugging Face's chunked, parallel, dedup transfer backend — the IDM/uGet-style fast path), but currently runs with Xet disabled (HF_HUB_DISABLE_XET=1, set by the app). Reason: Xet's transfer reports progress out-of-band and bypasses the byte-level progress hook, so the download UI couldn't show real bytes/speed. Until a proper Xet progress hook lands, the app forces the classic LFS path, which streams through the standard progress reporter and gives accurate downloaded/remaining/speed.

State is reported at Settings → About / GET /system/info:

  • fast_download.xet_installedhf_xet present (true)
  • fast_download.xet_active — whether Xet actually drives downloads (false by default, because of HF_HUB_DISABLE_XET)
  • the fast download badge in Settings → Models appears only when Xet is active.

The backend logs one line at startup, e.g. downloads: Xet disabled → legacy LFS (hf_xet 1.4.2 installed=True).

Re-enabling Xet (advanced, opt-in)

Power users who want Xet's speed and don't mind coarser progress can set HF_HUB_DISABLE_XET=0. With Xet active, the overall bar advances by file and snaps to the exact total on completion (per-file byte speed isn't shown, which is exactly why it's off by default). Xet needs a 64-bit OS (all supported OmniVoice platforms).

Reading the progress

When a download starts you'll see, in order:

  1. Resolving — the app fetches the file list and computes an exact plan: total size, how much is already cached, and how much will actually download (shown before any bytes move).
  2. Downloading — one overall bar with the file count (e.g. 3/7 files), total size, and — on networks where per-byte progress is reported — live speed and ETA.
  3. Done — the bar lands on 100% at the true total size.

Note: the exact total and "already cached / to download" split are known up front (a pre-flight resolve), so remaining is accurate from the start. Live per-byte speed appears once a file is large enough to stream over several seconds; very small/fast files may jump straight to done. The bar always lands on the exact total at completion. (If Xet is re-enabled, progress becomes file-granular — see above.)

Advanced / opt-in tuning

All of these default off and apply to every platform identically. Set them as environment variables (or via Settings → API keys / environment).

Setting Env var Effect
Segmented accelerator OMNIVOICE_SEGMENTED_DOWNLOAD=1 Multi-connection downloader (parallel byte-ranges) for the legacy-LFS path — restores parallel speed and shows live byte speed/ETA. Falls back to the normal download on any error; files land in the standard cache. Best paired with Xet disabled (the default).
Max parallel files OMNIVOICE_DOWNLOAD_MAX_WORKERS (default 8) Files fetched at once. Xet already parallelises within a file, so raising this rarely helps and uses more memory.
High-performance mode HF_XET_HIGH_PERFORMANCE=1 Maximum throughput. Needs lots of RAM and bandwidth — can hurt low-RAM machines. Leave off unless you have headroom.
Spinning-disk (HDD) HF_XET_RECONSTRUCT_WRITE_SEQUENTIALLY=1 Sequential writes; avoids parallel-write thrash on HDDs. Leave off on SSD/NVMe.

Restricted networks / mirrors (e.g. China)

If huggingface.co is slow or blocked, point the client at a mirror:

HF_ENDPOINT=https://hf-mirror.com

Set it as an environment variable (or in Settings → environment) before downloading. Caveats:

  • A mirror serves the classic download path, not Xet — you lose chunk-dedup and Xet's parallel fetch, but you gain reachability. On the classic path, per-byte speed/ETA is shown continuously.
  • Russia and some networks have no official mirror; use a VPN/tunnel.

Cancelling a download

Settings → Models lets you cancel an in-flight install. Cancellation stops further retries and clears the failure cooldown so you can restart immediately. A file that's already streaming finishes first — cancellation takes effect at the next retry boundary.

Troubleshooting

  • Stuck on "Resolving…" — the Hub is slow to return metadata, or you're rate-limited without a token. Add a token (see docs/setup/huggingface-token.md) and retry.
  • Very slow / stalling — try a mirror (above), or a wired connection. High-performance mode only helps if RAM and bandwidth are plentiful.
  • "download finished but no model weights were found" — the download was interrupted and left a partial snapshot. Delete the model in Settings → Models and install it again.
  • Out of disk — model sizes are shown in the catalog; free space or change the cache location with HF_HOME / HF_HUB_CACHE.