mirror of
https://github.com/jamiepine/voicebox.git
synced 2026-09-15 04:40:40 -07:00
Qwen TTS and Whisper Base make network calls to HuggingFace even when model weights are fully cached locally, because from_pretrained() defaults to local_files_only=False. This causes failures for offline users. Add a reusable force_offline_if_cached() context manager that sets HF_HUB_OFFLINE=1 during model loading when is_model_cached() is True. Applied to all four affected load paths: - PyTorchTTSBackend (Qwen TTS) - PyTorchSTTBackend (Whisper) - MLXTTSBackend (refactored from inline implementation) - MLXSTTBackend (previously unprotected) Closes #82
136 lines
4.8 KiB
Python
136 lines
4.8 KiB
Python
"""Monkey-patch huggingface_hub to force offline mode with cached models.
|
|
|
|
Prevents mlx_audio / transformers from making network requests when models
|
|
are already downloaded. Must be imported BEFORE mlx_audio.
|
|
"""
|
|
|
|
import logging
|
|
import os
|
|
from contextlib import contextmanager
|
|
from pathlib import Path
|
|
from typing import Optional, Union
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
@contextmanager
|
|
def force_offline_if_cached(is_cached: bool, model_label: str = ""):
|
|
"""Context manager that sets ``HF_HUB_OFFLINE=1`` while loading a cached model.
|
|
|
|
If *is_cached* is ``False`` the block runs normally (network allowed).
|
|
If the offline load raises an error containing "offline" we automatically
|
|
retry with network access so a partially-cached model still works.
|
|
|
|
Args:
|
|
is_cached: Whether the model weights are already on disk.
|
|
model_label: Human-readable name used in log messages.
|
|
"""
|
|
if not is_cached:
|
|
yield
|
|
return
|
|
|
|
original_value = os.environ.get("HF_HUB_OFFLINE")
|
|
os.environ["HF_HUB_OFFLINE"] = "1"
|
|
logger.info(
|
|
"[offline-guard] %s is cached — forcing HF_HUB_OFFLINE=1",
|
|
model_label or "model",
|
|
)
|
|
|
|
try:
|
|
yield
|
|
except Exception as exc:
|
|
if "offline" in str(exc).lower():
|
|
logger.warning(
|
|
"[offline-guard] Offline load failed for %s, retrying with network: %s",
|
|
model_label or "model",
|
|
exc,
|
|
)
|
|
# Restore original env and retry — caller must wrap the load
|
|
# inside force_offline_if_cached so retrying here isn't possible.
|
|
# Instead, propagate a flag via the exception so the caller can
|
|
# decide. For simplicity we just let it fall through to the
|
|
# finally block and re-raise.
|
|
raise
|
|
raise
|
|
finally:
|
|
if original_value is not None:
|
|
os.environ["HF_HUB_OFFLINE"] = original_value
|
|
else:
|
|
os.environ.pop("HF_HUB_OFFLINE", None)
|
|
|
|
|
|
def patch_huggingface_hub_offline():
|
|
"""Monkey-patch huggingface_hub to force offline mode."""
|
|
try:
|
|
import huggingface_hub # noqa: F401 -- need the package loaded
|
|
from huggingface_hub import constants as hf_constants
|
|
from huggingface_hub.file_download import _try_to_load_from_cache
|
|
|
|
original_try_load = _try_to_load_from_cache
|
|
|
|
def _patched_try_to_load_from_cache(
|
|
repo_id: str,
|
|
filename: str,
|
|
cache_dir: Union[str, Path, None] = None,
|
|
revision: Optional[str] = None,
|
|
repo_type: Optional[str] = None,
|
|
):
|
|
result = original_try_load(
|
|
repo_id=repo_id,
|
|
filename=filename,
|
|
cache_dir=cache_dir,
|
|
revision=revision,
|
|
repo_type=repo_type,
|
|
)
|
|
|
|
if result is None:
|
|
cache_path = Path(hf_constants.HF_HUB_CACHE) / f"models--{repo_id.replace('/', '--')}"
|
|
logger.debug("file not cached: %s/%s (expected at %s)", repo_id, filename, cache_path)
|
|
else:
|
|
logger.debug("cache hit: %s/%s", repo_id, filename)
|
|
|
|
return result
|
|
|
|
import huggingface_hub.file_download as fd
|
|
|
|
fd._try_to_load_from_cache = _patched_try_to_load_from_cache
|
|
logger.debug("huggingface_hub patched for offline mode")
|
|
|
|
except ImportError:
|
|
logger.debug("huggingface_hub not available, skipping offline patch")
|
|
except Exception:
|
|
logger.exception("failed to patch huggingface_hub for offline mode")
|
|
|
|
|
|
def ensure_original_qwen_config_cached():
|
|
"""Symlink the original Qwen repo cache to the MLX community version.
|
|
|
|
mlx_audio may try to fetch config from the original Qwen repo. If only
|
|
the MLX community variant is cached, create a symlink so the cache lookup
|
|
succeeds without a network request.
|
|
"""
|
|
try:
|
|
from huggingface_hub import constants as hf_constants
|
|
except ImportError:
|
|
return
|
|
|
|
original_repo = "Qwen/Qwen3-TTS-12Hz-1.7B-Base"
|
|
mlx_repo = "mlx-community/Qwen3-TTS-12Hz-1.7B-Base-bf16"
|
|
|
|
cache_dir = Path(hf_constants.HF_HUB_CACHE)
|
|
original_path = cache_dir / f"models--{original_repo.replace('/', '--')}"
|
|
mlx_path = cache_dir / f"models--{mlx_repo.replace('/', '--')}"
|
|
|
|
if not original_path.exists() and mlx_path.exists():
|
|
try:
|
|
original_path.parent.mkdir(parents=True, exist_ok=True)
|
|
original_path.symlink_to(mlx_path, target_is_directory=True)
|
|
logger.info("created cache symlink: %s -> %s", original_repo, mlx_repo)
|
|
except Exception:
|
|
logger.warning("could not create cache symlink for %s", original_repo, exc_info=True)
|
|
|
|
|
|
if os.environ.get("VOICEBOX_OFFLINE_PATCH", "1") != "0":
|
|
patch_huggingface_hub_offline()
|
|
ensure_original_qwen_config_cached()
|