import ctranslate2 eagerly imports torch (via converters + specs), even for inference-only use
Nobody has claimed this yet.
- Dominant language
- C++
- Stars
- 4.7k
- Forks
- 536
- Avg merge
- 12h 12m
- Merged PRs (30d)
- 4
Description
Summary
Importing the top-level package —
import ctranslate2
— unconditionally imports PyTorch into the process if torch is installed in the environment, even when the user only does inference (Translator / Generator / Whisper) and never touches the converters. torch is only actually needed by the model-conversion code paths, not by inference, so pulling it in at package-import time is unnecessary for a large class of users.
Why this matters (real-world impact)
Inference-only users who happen to have torch in the same environment (very common — e.g. an app that also runs a torch-based component such as demucs, in the same Python process) pay for it in two ways:
-
Startup cost. Importing
torch(2.8.0 + CUDA) adds several seconds toimport ctranslate2(see reproducer: ~4 s total, dominated by torch/CUDA init) even though inference never calls torch. -
Cross-runtime interference. In our application, the same process also runs onnxruntime. We measured that merely having torch imported in the process makes onnxruntime inference ~4× slower on GPU (and measurably slower on CPU) — a known interaction (DLL / OpenMP / CUDA allocator) between torch and onnxruntime. Because
import ctranslate2(used only for faster-whisper inference) drags torch in, we were forced to move faster-whisper into a separate subprocess purely to keep the CTranslate2 side torch-free. A lazy import in CTranslate2 would remove the need for that workaround for anyone mixing CT2 inference with onnxruntime (or any other native runtime that dislikes sharing a process with torch).
The core, provable point is narrow and framework-agnostic: import ctranslate2 imports torch when it doesn't need to for inference. The onnxruntime slowdown is just our motivation for noticing it.
Root cause (verified on master, and on 4.8.0 / 4.8.1)
python/ctranslate2/__init__.py eagerly imports the converters and specs submodules:
from ctranslate2 import converters, models, specs
from ctranslate2.version import __version__
Both of those submodule trees do a module-level import torch:
-
converters/__init__.pyimportstransformers.py, whose top of file has (lines 7-10 on master):try: import huggingface_hub import torch import transformers except ImportError: pass -
specs/__init__.pyimportsmodel_spec.py, whose top of file has (lines 17-22 on master):try: import torch torch_is_available = True except ImportError: torch_is_available = False
Note that most other torch imports in the converters are already lazy (function-level) — e.g. converters/fairseq.py, converters/opennmt_py.py, converters/utils.py, converters/eole_ct2.py. So there is already precedent in the codebase for importing torch only inside the code path that uses it. The two module-level sites above are the remaining eager ones.
Reproducer
import sys, time
t0 = time.perf_counter()
import ctranslate2
t1 = time.perf_counter()
print("ctranslate2 version :", ctranslate2.__version__)
print("import time : %.2fs" % (t1 - t0))
print("torch in sys.modules:", "torch" in sys.modules)
print("transformers loaded :", "transformers" in sys.modules)
Output (Windows, Python 3.11, ctranslate2 4.8.0, torch 2.8.0+cu128):
ctranslate2 version : 4.8.0
import time : 4.01s
torch in sys.modules: True
transformers loaded : False
torch is imported, while transformers is not (it isn't installed here) — showing the converter dependency tree is incomplete anyway, yet torch still gets loaded. This is pure overhead for an inference-only import.
Proposed fix (lazy import)
Make torch load only when a converter / spec code path that actually needs it runs. Two low-risk options, ideally combined:
-
Lazy-load the
converterssubmodule in__init__.pyvia PEP 562__getattr__, so an inference-onlyimport ctranslate2never pulls the converter dependency tree (transformers,huggingface_hub,torch):from ctranslate2 import models, specs from ctranslate2.version import __version__ def __getattr__(name): if name == "converters": import ctranslate2.converters as converters return converters raise AttributeError(f"module {__name__!r} has no attribute {name!r}")ctranslate2.converters.XxxConverterkeeps working (imported on first access); thect2-*-converterCLIs are unaffected. -
Defer
torchinspecs/model_spec.py. Replace the module-leveltry: import torchwith a lazy helper (import inside the function(s) that build torch tensors), and computetorch_is_availableon demand. This removes the eager torch import from thespecspath, which is loaded even in option 1.
Both keep behavior identical for converter users, and make inference-only import ctranslate2 torch-free.
Environment
- CTranslate2: 4.8.0 (root cause also present verbatim on
master) - Python: 3.11, Windows
- torch: 2.8.0+cu128 (present in env because another, unrelated component uses it)
I did a quick search of existing issues and didn't find this reported; apologies + please close as duplicate if I missed one. Happy to open a PR implementing the lazy-import approach above if you're open to it.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Run the provided import reproducer with torch installed, then inspect python/ctranslate2/init.py, converters/init.py, converters/transformers.py, and specs/model_spec.py. Defer the converter and torch imports while preserving converter access and spec behavior. Done means inference-only import ctranslate2 no longer places torch in sys.modules.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python, pytorch
- Domain
- backend, performance
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 74/100