fix(stt): treat a partial whisper cache as a cache miss

An interrupted first download leaves refs/main plus a snapshot folder
without model.bin. snapshot_download(local_files_only=True) returns that
folder rather than raising LocalEntryNotFoundError, so ctranslate2 fails
with RuntimeError "Unable to open file 'model.bin'" and the online path
never ran, leaving STT permanently broken with a misleading error. Fall
through to the download when the local attempt fails that way. The
loading tests are also gated on faster_whisper being installed, so the
module collects when the voice extra is absent.

Review finding: partial-cache RuntimeError bypassed the online fallback.
This commit is contained in:
teknium1
2026-09-14 21:02:32 -07:00
committed by Teknium
parent 0bb460b488
commit 8ecc3dd71e
2 changed files with 22 additions and 1 deletions
+17
View File
@@ -351,6 +351,10 @@ class TestTranscribeLocalCommand:
# _transcribe_local — additional tests
# ============================================================================
@pytest.mark.skipif(
not __import__("importlib").util.find_spec("faster_whisper"),
reason="faster_whisper not installed",
)
class TestLocalModelLoading:
def test_cached_model_load_never_uses_online_resolution(self):
cached_model = object()
@@ -391,6 +395,19 @@ class TestLocalModelLoading:
call("base", local_files_only=False, device="auto", compute_type="auto"),
]
def test_partial_cache_is_treated_as_a_cache_miss(self):
# An interrupted first download leaves refs/main + a snapshot without model.bin;
# snapshot_download(local_files_only=True) returns that folder and ctranslate2
# raises RuntimeError, so the online path must still run.
from tools.transcription_local import _create_whisper_model
downloaded_model = object()
side_effect = [RuntimeError("Unable to open file 'model.bin' in model '/cache/snap'"), downloaded_model]
with patch("faster_whisper.WhisperModel", side_effect=side_effect) as model_cls:
assert _create_whisper_model("base", device="cpu", compute_type="int8") is downloaded_model
assert [c.kwargs["local_files_only"] for c in model_cls.call_args_list] == [True, False]
@pytest.mark.skipif(
not __import__("importlib").util.find_spec("faster_whisper"),
+5 -1
View File
@@ -140,7 +140,11 @@ def _create_whisper_model(model_name: str, *, device: str, compute_type: str):
kwargs = {"device": device, "compute_type": compute_type}
try:
return WhisperModel(model_name, local_files_only=True, **kwargs)
except _hub_cache_miss_error():
except (_hub_cache_miss_error(), RuntimeError) as exc:
# An interrupted first download leaves a snapshot folder without the weights;
# snapshot_download still returns it and ctranslate2 raises "Unable to open file".
if isinstance(exc, RuntimeError) and "Unable to open file" not in str(exc):
raise
logger.info("faster-whisper model '%s' is not cached; downloading it from the Hugging Face Hub", model_name)
# huggingface_hub surfaces every Hub/network failure as an OSError subclass