Importing faster_whisper.vad (or any submodule) eagerly pulls in the full Whisper/ctranslate2/transformers stack via __init__.py

Author: TakemicomCreated Aug 22, 2026Updated Aug 22, 2026

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:

python
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

python
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:

python
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:

python
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_model

This 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