diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b03f5e4..f598cc41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,14 @@ ## [Unreleased] +### Reliability + +- **Cached models no longer retry HuggingFace when offline.** Loading a fully-downloaded + model now forces offline mode for the duration of the load, so it skips the network HEAD + request (and its 5-retry backoff) for every config file — `config.json`, + `generation_config.json`, and the rest — instead of retrying each one in sequence before the + app becomes ready. + ### Linux - **ROCm setup works on Linux AMD systems.** Docker ROCm builds now keep PyTorch diff --git a/backend/backends/base.py b/backend/backends/base.py index 70ec11ef..672022e8 100644 --- a/backend/backends/base.py +++ b/backend/backends/base.py @@ -14,6 +14,7 @@ from typing import Callable, List, Optional, Tuple import numpy as np from ..utils.audio import normalize_audio, load_audio +from ..utils.hf_offline_patch import force_offline_if_cached from ..utils.progress import get_progress_manager from ..utils.hf_progress import HFProgressTracker, create_hf_progress_callback from ..utils.tasks import get_task_manager @@ -280,7 +281,8 @@ def model_load_progress( ) try: - yield tracker_context + with force_offline_if_cached(is_cached, model_name): + yield tracker_context except Exception as e: # Report error to both managers progress_manager.mark_error(model_name, str(e)) diff --git a/backend/tests/test_model_load_progress_offline.py b/backend/tests/test_model_load_progress_offline.py new file mode 100644 index 00000000..4befe58b --- /dev/null +++ b/backend/tests/test_model_load_progress_offline.py @@ -0,0 +1,55 @@ +""" +Regression test for voicebox #434 (infinite HF retry loop on a cached model). + +``model_load_progress`` is the single context manager every backend's +``_load_model_sync`` uses around its ``from_pretrained()`` call. It already +receives ``is_cached`` but never forwarded it to ``force_offline_if_cached``, +so a fully-cached model still resolved config files against huggingface.co +and ate the default 5-retry backoff per file offline. This asserts the guard +is actually active for the duration of the ``with`` block when ``is_cached`` +is ``True``, and restored on exit. + +NOTE: mutates process-global state in ``huggingface_hub.constants`` and +``transformers.utils.hub`` (via ``force_offline_if_cached``); run serially, +same caveat as ``test_offline_guard.py``. +""" + +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).parent.parent.parent)) + +from backend.backends.base import model_load_progress + + +def _hf_const(): + import huggingface_hub.constants as hf_const + + return hf_const + + +def _tf_hub(): + import transformers.utils.hub as tf_hub + + return tf_hub + + +def test_cached_model_load_forces_offline_mode(): + original_hf = _hf_const().HF_HUB_OFFLINE + original_tf = _tf_hub()._is_offline_mode + + with model_load_progress("test-model", is_cached=True): + assert _hf_const().HF_HUB_OFFLINE is True + assert _tf_hub()._is_offline_mode is True + + assert original_hf == _hf_const().HF_HUB_OFFLINE + assert original_tf == _tf_hub()._is_offline_mode + + +def test_uncached_model_load_does_not_force_offline_mode(): + original_hf = _hf_const().HF_HUB_OFFLINE + + with model_load_progress("test-model", is_cached=False): + assert original_hf == _hf_const().HF_HUB_OFFLINE + + assert original_hf == _hf_const().HF_HUB_OFFLINE