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:
@@ -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"),
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user