Importing faster_whisper.vad (or any submodule) eagerly pulls in the full Whisper/ctranslate2/transformers stack via __init__.py
Summary
import faster_whisper — and even the narrower from faster_whisper.vad import get_vad_model — unconditionally imports the full Whisper transcription stack, because Python always runs a package's __init__.py before any submodule import. faster_whisper/__init__.py is:
from faster_whisper.audio import decode_audio
from faster_whisper.transcribe import BatchedInferencePipeline, WhisperModel
from faster_whisper.utils import available_models, download_model, format_timestamp
from faster_whisper.version import __version__faster_whisper.transcribe imports ctranslate2, which (on the currently released 4.8.1; the fix in OpenNMT/CTranslate2#2079 / OpenNMT/CTranslate2#2080 is merged to master but not yet released) eagerly imports ctranslate2.converters.transformers, pulling in the full HuggingFace transformers package. None of this is needed by faster_whisper.vad, which only needs numpy, one helper from faster_whisper.utils, and onnxruntime (already imported lazily, inside SileroVADModel.__init__).
Why this matters
Any application that wants Silero VAD boundary detection without doing Whisper transcription in the same process pays the full cost anyway, because vad.py's own from faster_whisper.utils import get_assets_path forces faster_whisper/__init__.py to run first.
Reproducer
import time
t0 = time.perf_counter()
from faster_whisper.vad import get_vad_model
t1 = time.perf_counter()
print("import time: %.2fs" % (t1 - t0))Measured on Windows, Python 3.13, faster-whisper 1.2.1, ctranslate2 4.8.1:
import time: 7.31s (via `python -X importtime`, cumulative for the faster_whisper.vad import line)Of that, 6.49s is faster_whisper.transcribe -> ctranslate2 -> ctranslate2.converters.transformers -> transformers. None of it is reachable from get_vad_model().
Suggested fix
Same shape as the already-merged OpenNMT/CTranslate2#2080: lazy-load the heavy submodule via PEP 562 __getattr__ in faster_whisper/__init__.py, keeping vad (and the lightweight audio/utils re-exports) eager and deferring transcribe:
from faster_whisper.audio import decode_audio
from faster_whisper.utils import available_models, download_model, format_timestamp
from faster_whisper.version import __version__
__all__ = [
"available_models",
"decode_audio",
"WhisperModel",
"BatchedInferencePipeline",
"download_model",
"format_timestamp",
"__version__",
]
def __getattr__(name):
if name in ("WhisperModel", "BatchedInferencePipeline"):
from faster_whisper.transcribe import BatchedInferencePipeline, WhisperModel
return {"WhisperModel": WhisperModel, "BatchedInferencePipeline": BatchedInferencePipeline}[name]
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")from faster_whisper import WhisperModel and import faster_whisper; faster_whisper.WhisperModel(...) keep working (triggers the lazy import on first access); only code that never touches WhisperModel/BatchedInferencePipeline — like VAD-only users — skips the cost.
Workaround used in the meantime
In our own application we couldn't wait for an upstream fix, so we bypass faster_whisper/__init__.py entirely via importlib, stubbing the package and faster_whisper.utils.get_assets_path in sys.modules before loading the real vad.py file directly:
def _import_faster_whisper_vad():
package_spec = importlib.util.find_spec("faster_whisper")
package_dir = Path(next(iter(package_spec.submodule_search_locations)))
if "faster_whisper" not in sys.modules:
stub_package = types.ModuleType("faster_whisper")
stub_package.__path__ = [str(package_dir)]
sys.modules["faster_whisper"] = stub_package
if "faster_whisper.utils" not in sys.modules:
stub_utils = types.ModuleType("faster_whisper.utils")
stub_utils.get_assets_path = lambda: str(package_dir / "assets")
sys.modules["faster_whisper.utils"] = stub_utils
if "faster_whisper.vad" not in sys.modules:
vad_spec = importlib.util.spec_from_file_location(
"faster_whisper.vad", package_dir / "vad.py"
)
vad_module = importlib.util.module_from_spec(vad_spec)
sys.modules["faster_whisper.vad"] = vad_module
vad_spec.loader.exec_module(vad_module)
return sys.modules["faster_whisper.vad"].get_vad_modelThis drops the import to 0.89s (measured, same machine/versions), but it's a workaround, not a fix — happy to open a PR implementing the __getattr__ approach above if maintainers are open to it.
Environment
- faster-whisper: 1.2.1
- ctranslate2: 4.8.1
- Python: 3.13, Windows
Source: SYSTRAN/faster-whisper