Model Downloads and Caching
Status: Current Last updated: 2026-09-16 03:36 EDT
The contract
batchalign3 downloads every ML model it needs automatically, the first time a command needs it. You never have to seed models, run setup scripts, or remember which language pack lives where. The only error you should ever see related to model downloads is one of these:
- “Failed to download …: network unreachable”, your machine can’t reach the internet (or the upstream is down). Try again when you have network.
- “Failed to download …: disk full”, free some space and retry.
- “Failed to download …: HTTP 401/403”, a configured custom model requires
authentication. BA3’s three PINNED released local Pyannote artifacts
(
talkbank/dia-forkand its segmentation/embedding dependencies) are public and ungated, but that engine also fetches one UNPINNED PLDA calibration artifact whose default is currently the GATEDpyannote/speaker-diarization-community-1repository; the model-access error names the repository and the fix (a Hugging Face token). See diarize.
If you see anything else, anything along the lines of “capability table is unavailable”, “resources.json could not be read”, “model not found locally” , that is a batchalign3 bug. file an issue on GitHub. You should not have to think about model storage.
What downloads, when, and roughly how big
Every download is one-time. After the first run, the model lives in your local cache and the same command runs without any download.
| When you run | What downloads | Approximate size | Approximate first-run wait |
|---|---|---|---|
morphotag (any language) | Stanza resource catalog (resources.json) | ~1 MB | 1-2 seconds |
morphotag (first time for a language) | Stanza language pack for that language | 250-500 MB | 30 s to 2 min |
morphotag --retokenize on a Cantonese file (@Languages: yue) | Nothing extra, PyCantonese is bundled | Not applicable | Instant |
morphotag --retokenize on a Mandarin file (@Languages: cmn/zho) | Stanza Chinese tokenizer | ~200 MB | 30-60 s |
transcribe (Whisper engine) | Whisper ASR model from HuggingFace | 0.5-3 GB depending on model size | 1-10 min |
align (Whisper engine) | Whisper FA model from HuggingFace | ~3 GB | 3-10 min |
align (Wave2Vec engine, default) | Wave2Vec MMS_FA bundle from torchaudio | ~1.2 GB | 1-5 min |
align --lang yue (Cantonese FA) | Wave2Vec Cantonese model | ~1 GB | 1-5 min |
transcribe --diarization enabled --speaker-engine pyannote or standalone diarize (local default) | Pyannote talkbank/dia-fork and its pinned dependencies (public, ungated) PLUS an unpinned PLDA artifact currently gated behind accepted terms + a Hugging Face token | ~500 MB | 1-3 min once authorized |
transcribe --diarization enabled (default speaker backend) or diarize --speaker-engine pyannote-ai | No local speaker model; uses paid pyannoteAI Precision-2 | N/A | Provider latency only |
translate (Seamless engine) | SeamlessM4T from HuggingFace | ~2.4 GB | 2-8 min |
transcribe (utterance segmentation, certain languages) | BERT utterance model from HuggingFace | ~400 MB | 1-3 min |
These sizes are ballpark. Real numbers depend on the upstream artifact and your network speed.
Which revision you get
The models BA3 loads by default are PINNED: each resolves to an exact revision
rather than to whatever the upstream repository happens to hold today. Hugging
Face models are pinned to a commit (openai/whisper-large-v3 at
06f233fe06e710322aca913c1bc4249a0d71fce1, for instance), and the ModelScope
models the Paraformer engine loads, the checkpoint together with its
voice-activity and punctuation models, are pinned to the tag v2.0.4, because
ModelScope publishes no commit behind a tag. Cloud engines have no weights to
pin; their request names the provider’s own model instead.
That covers the utterance-segmentation boundary models too, so a transcribe
or utseg run in English, Mandarin or Cantonese loads a known revision
(talkbank/CHATUtterance-en at 764ec3f762c2e24df2def8df98b5fe34940085c6, for
instance). Because the revision is now known before the model loads rather than
read back afterwards, every file those runs write records it: the engine=
field of the [fc-ba3 utseg ...] comment is always <model id>@<revision>.
What that buys you: the same command on the same media loads the same weights next month as it does today, and a repository that moves upstream cannot silently change your output.
An override still loads. Passing your own model (for example
--engine-overrides '{"funaudio_model":"..."}') leaves that model unpinned
rather than refusing the job, and BA3 records it at the revision actually
observed when it loaded, so the run still says which weights produced it.
What an override cannot do is name its revision BEFORE the model loads, which
is why a run with an unpinned model does not use the UTR ASR result cache
(see below): no stored row could promise it came from the same weights.
What you’ll see on first run
Every download surfaces in your console, the TUI, the desktop app, and the
web dashboard at http://<host>:8001/dashboard/jobs/<id>: whichever UI
you’re using. This is a deliberate UX commitment (see the time
transparency principle): you should
always know what batchalign3 is doing and roughly how long it will take.
Example: a brand-new install running batchalign3 morphotag input/ output/
will show a sequence like:
Downloading Stanza resource catalog (one-time, ~1 MB; future runs will be instant)…
Stanza resource catalog ready.
Downloading Stanza language pack for eng (en) (one-time, ~250-500 MB; future runs will use the local cache)…
Loading Stanza English…
Processing input/file1.cha
Processing input/file2.cha
…
A fresh transcribe run:
Downloading openai/whisper-large-v3 for ASR (one-time, ~3 GB; future runs will use the local cache)…
Loading Whisper-large-v3 onto GPU…
Calling Rev.AI for utterance-timing recovery…
Processing recordings/r001.wav
…
If you see “Downloading X…” and the run sits there for a while, that is expected, the download is running in the background. The libraries also print their own progress bars to your terminal stderr.
After the first successful run, the same command on the same language runs without any download, typically in seconds (for text NLP) to a few minutes (for audio passes, dominated by inference, not loading).
Where models are stored
batchalign3 uses each library’s own cache. Locations vary by OS because each library follows its own platform conventions:
| Library | macOS | Linux | Windows |
|---|---|---|---|
| Stanza (1.11+) | ~/Library/Caches/stanza/<resver>/resources/ | ~/.cache/stanza/<resver>/resources/ | %LocalAppData%\stanza\<resver>\resources\ |
| HuggingFace (Whisper, Wave2Vec, SeamlessM4T, pyannote, BERT) | ~/.cache/huggingface/hub/ | ~/.cache/huggingface/hub/ | %LocalAppData%\huggingface\hub\ |
| torchaudio (Wave2Vec MMS_FA bundle) | ~/.cache/torch/hub/torchaudio/ | ~/.cache/torch/hub/torchaudio/ | %LocalAppData%\torch\hub\torchaudio\ |
| PyCantonese | Bundled in the package, no separate cache | (same) | (same) |
<resver> in the Stanza path is the resource-catalog version (e.g.
1.11.0), which Stanza bumps independently of the package version.
Combined cache size for a multi-language workflow can reach 10-30 GB.
Customizing cache locations
Each library honors a standard environment variable for cache override. Useful when you want to put caches on an external drive, a shared mount, or a smaller SSD:
| Library | Environment variable |
|---|---|
| Stanza | STANZA_RESOURCES_DIR |
| HuggingFace | HF_HOME (controls all subpaths) or HF_HUB_CACHE (just the model hub) |
| torchaudio | TORCH_HOME (controls all torch hub caches) |
Example: offload everything to an external drive:
export STANZA_RESOURCES_DIR="/Volumes/External/caches/stanza"
export HF_HOME="/Volumes/External/caches/huggingface"
export TORCH_HOME="/Volumes/External/caches/torch"
batchalign3 daemon start
If you set these before the first model download, batchalign3 downloads straight to the new location. If you set them after you’ve already downloaded somewhere else, models will re-download, copy the existing cache directories first to avoid that.
Working offline
After the first successful download, local-model commands work offline for the same languages and engines. Cloud engines such as Rev.AI and pyannoteAI still require their services. To enforce strictly offline Hugging Face behavior (and surface a clear error if a local model is missing rather than attempting a download), set:
export HF_HUB_OFFLINE=1
export TRANSFORMERS_OFFLINE=1
Stanza always tries the local cache first; no equivalent flag is needed.
In strict offline mode, the user-facing error for a missing model is along the lines of “model X not in local cache; offline mode is enabled”, actionable, distinct from the download-failed errors above.
Pre-seeding for offline / air-gapped deployments
For deployments where the worker will run without internet (CI runners, air-gapped fleets, conference demos), do the first download on a machine with internet, then copy the cache directories to the offline target.
Stanza catalog + English pack:
python -c "import stanza; stanza.download('en')"
HuggingFace models (download to a known local path you can rsync):
python -c "
from huggingface_hub import snapshot_download
for repo in [
'openai/whisper-large-v3',
'talkbank/dia-fork',
'facebook/hf-seamless-m4t-medium',
]:
snapshot_download(repo)
"
Then copy the cache directories listed above to the offline machine, set
HF_HUB_OFFLINE=1 / TRANSFORMERS_OFFLINE=1, and run.
Disk-space management
To free disk space by removing cached models (they will re-download next use):
# macOS
rm -rf ~/Library/Caches/stanza/
rm -rf ~/.cache/huggingface/hub/
rm -rf ~/.cache/torch/hub/
# Linux
rm -rf ~/.cache/stanza/
rm -rf ~/.cache/huggingface/hub/
rm -rf ~/.cache/torch/hub/
Removing caches mid-job is safe: any in-flight download will continue, and future jobs will re-download what they need. Removing caches while a daemon is running will not crash the daemon, the next job that needs the missing model will simply re-download it (and surface a download notification to you, as documented above).
Result caching (separate from model caching)
batchalign3 caches audio-bound intermediate evidence so repeated runs of
align, selected transcribe stages, or standalone diarize on the same
media with the same settings do not redo expensive service calls or model
inference. The cache distinguishes final
forced-alignment projection, raw forced-alignment worker evidence, UTR ASR,
raw Rev transcript evidence, raw speaker evidence, and normalized speaker
segments.
The authoritative list of cached task kinds is in
crates/batchalign/src/chat_ops/cache_key.rs::CacheTaskName. Every kind is
scoped to an identity of its own, alongside language, evidence/projection
revision, and relevant inputs, so changing any of those produces a fresh
entry. Which identity depends on the kind:
- Forced alignment is scoped to the engine version reported by the exact selected worker, because its rows are namespaced before inference runs.
- UTR ASR is scoped to the pinned plan instead: the engine together with the exact models it was pinned to, which is known before dispatch without loading anything. A run whose ASR model is unpinned has no such identity, so it infers without reading or writing UTR ASR rows rather than pooling them with another plan’s.
In server mode, worker models remain loaded between jobs as well; result caching and warm workers remove different costs.
Text NLP tasks are not cached. Running morphotag, utseg,
translate, or coref twice on the same file runs the model twice. The
CLI accepts --override-media-cache-tasks morphosyntax for backward-
compatible scripting but emits a warning (“batchalign3 does not cache text NLP”) and ignores it.
To force re-computation of cached audio tasks (e.g., after a model update):
batchalign3 align --override-media-cache corpus/ -o output/ --lang eng
batchalign3 transcribe --override-media-cache recordings/ -o transcripts/ --lang eng
--override-media-cache clears all audio-task caches for the run. For
finer control, pass --override-media-cache-tasks with one or more of
forced_alignment, utr_asr, rev_asr_evidence, or
speaker_diarization_raw_evidence. The last two are useful for controlled
transcription experiments: refresh Rev without buying another speaker job, or
refresh speaker evidence while replaying the same Rev response.
The result cache lives at:
| OS | Path |
|---|---|
| macOS | ~/Library/Caches/batchalign3/cache.db |
| Linux | ~/.cache/batchalign3/cache.db |
| Windows | %LocalAppData%\batchalign3\cache.db |
You can rm it at any time; new runs will start a fresh cache.
When something goes wrong
Three classes of error you might legitimately see, and what to do:
Network unreachable. “Failed to download Stanza catalog: network
unreachable” or “Failed to download model X: connection timeout”. Check
your internet connection or proxy; retry. If you’re on a corporate
network with a firewall, you may need to allow https://huggingface.co
and https://raw.githubusercontent.com.
Disk full. “Failed to write model file: no space left on device”. Free up space (see “Disk-space management” above) or move caches to a larger drive (see “Customizing cache locations”). Then retry.
Authentication. “Failed to download X: HTTP 401/403”. The model
requires a HuggingFace auth token. The standard batchalign3 install does
not need any, if you’ve configured a custom model that requires auth,
set HF_TOKEN in the daemon environment.
What you should never see. Errors mentioning “capability table”, “resources.json”, “model not installed”, or any internal-implementation language. If you see one of these, batchalign3 is failing to download something it should have downloaded automatically. File a bug on GitHub and include the error message verbatim plus the full command you ran. The team will fix the on-demand path; you will not be asked to seed models manually.
Related references
- Time transparency UX principle, why downloads (and other slow operations) always surface to the UI.
- Developer-facing model downloads doc, internals: load paths, cache invalidation, test strategy.
This page last changed: 2026-09-16 (commit 197c81e6). The whole book last changed: 2026-09-16 (commit 34d249d8).