Skip to content

Commit aba0d6e

Browse files
authored
Speed up import transformers by building the package distribution mapping lazily (#50047)
Build the package distribution mapping lazily importlib.metadata.packages_distributions() stats every file of each distribution without a top_level.txt (torch >= 2.14 ships none). Build it on first use, and try the import name as distribution name before it.
1 parent 955b6d3 commit aba0d6e

4 files changed

Lines changed: 47 additions & 34 deletions

File tree

‎src/transformers/modeling_flash_attention_utils.py‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
is_torch_xpu_available,
3535
logging,
3636
)
37-
from .utils.import_utils import PACKAGE_DISTRIBUTION_MAPPING, is_tracing
37+
from .utils.import_utils import _package_distribution_mapping, is_tracing
3838

3939

4040
logger = logging.get_logger(__name__)
@@ -103,7 +103,8 @@ def is_flash_attn_available():
103103
"general_availability_check": is_flash_attn_2_available,
104104
"pkg_availability_check": lambda *args, **kwargs: (
105105
importlib.util.find_spec("flash_attn") is not None
106-
and "flash-attn" in [pkg.replace("_", "-") for pkg in PACKAGE_DISTRIBUTION_MAPPING.get("flash_attn", [])]
106+
and "flash-attn"
107+
in [pkg.replace("_", "-") for pkg in _package_distribution_mapping().get("flash_attn", [])]
107108
),
108109
"supported_devices": (
109110
(is_torch_cuda_available, "cuda"),
@@ -126,7 +127,7 @@ def is_flash_attn_available():
126127
"pkg_availability_check": lambda *args, **kwargs: (
127128
importlib.util.find_spec("flash_attn_interface") is not None
128129
and "flash-attn-3"
129-
in [pkg.replace("_", "-") for pkg in PACKAGE_DISTRIBUTION_MAPPING.get("flash_attn_interface", [])]
130+
in [pkg.replace("_", "-") for pkg in _package_distribution_mapping().get("flash_attn_interface", [])]
130131
),
131132
"supported_devices": ((is_torch_cuda_available, "cuda"),),
132133
"cuda_min_major_version": 8, # Ampere
@@ -136,7 +137,8 @@ def is_flash_attn_available():
136137
"general_availability_check": is_flash_attn_4_available,
137138
"pkg_availability_check": lambda *args, **kwargs: (
138139
importlib.util.find_spec("flash_attn") is not None
139-
and "flash-attn-4" in [pkg.replace("_", "-") for pkg in PACKAGE_DISTRIBUTION_MAPPING.get("flash_attn", [])]
140+
and "flash-attn-4"
141+
in [pkg.replace("_", "-") for pkg in _package_distribution_mapping().get("flash_attn", [])]
140142
),
141143
"supported_devices": ((is_torch_cuda_available, "cuda"),),
142144
"cuda_min_major_version": 9, # Hopper

‎src/transformers/utils/import_utils.py‎

Lines changed: 28 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,9 @@
4949
logger = logging.get_logger(__name__) # pylint: disable=invalid-name
5050

5151

52-
PACKAGE_DISTRIBUTION_MAPPING = importlib.metadata.packages_distributions()
52+
@functools.cache
53+
def _package_distribution_mapping() -> dict[str, list[str]]:
54+
return importlib.metadata.packages_distributions()
5355

5456

5557
def _candidate_distribution_names(pkg_name: str) -> list[str]:
@@ -61,7 +63,7 @@ def _candidate_distribution_names(pkg_name: str) -> list[str]:
6163
"""
6264
# Per PEP 503, underscores and hyphens are equivalent in package names.
6365
normalized_pkg_name = pkg_name.replace("_", "-")
64-
distributions = PACKAGE_DISTRIBUTION_MAPPING.get(pkg_name, [])
66+
distributions = _package_distribution_mapping().get(pkg_name, [])
6567
candidates = [
6668
*(name for name in (normalized_pkg_name, pkg_name) if name in distributions),
6769
*distributions,
@@ -77,21 +79,26 @@ def _is_package_available(pkg_name: str, return_version: bool = False) -> tuple[
7779
package_exists = spec is not None
7880
package_version = "N/A"
7981
if package_exists and return_version:
80-
for distribution_name in _candidate_distribution_names(pkg_name):
81-
try:
82-
package_version = importlib.metadata.version(distribution_name)
83-
break
84-
except importlib.metadata.PackageNotFoundError:
85-
continue
86-
else:
87-
# No metadata under any candidate name (editable install without a `dist-info`, for example).
88-
# Last resort: importing defeats the lazy imports these checks guard, costing every
89-
# `import transformers` the package's whole import tree.
90-
package = importlib.import_module(pkg_name)
91-
package_version = getattr(package, "__version__", "N/A")
92-
# No version + no __file__ means a namespace package (PEP 420) shadowing on sys.path, not a real install.
93-
if package_version == "N/A" and getattr(package, "__file__", None) is None:
94-
package_exists = False
82+
try:
83+
# Most packages are distributed under their import name. Try it before building the mapping, which
84+
# stats every file of each distribution without a `top_level.txt`, `torch` >= 2.14 among them
85+
package_version = importlib.metadata.version(pkg_name)
86+
except importlib.metadata.PackageNotFoundError:
87+
for distribution_name in _candidate_distribution_names(pkg_name):
88+
try:
89+
package_version = importlib.metadata.version(distribution_name)
90+
break
91+
except importlib.metadata.PackageNotFoundError:
92+
continue
93+
else:
94+
# No metadata under any candidate name (editable install without a `dist-info`, for example).
95+
# Last resort: importing defeats the lazy imports these checks guard, costing every
96+
# `import transformers` the package's whole import tree.
97+
package = importlib.import_module(pkg_name)
98+
package_version = getattr(package, "__version__", "N/A")
99+
# No version + no __file__ means a namespace package (PEP 420) shadowing on sys.path, not a real install.
100+
if package_version == "N/A" and getattr(package, "__file__", None) is None:
101+
package_exists = False
95102
logger.debug(f"Detected {pkg_name} version: {package_version}")
96103

97104
if return_version:
@@ -1227,7 +1234,7 @@ def is_flash_attn_2_available(kernels_fallback_ok: bool = False) -> bool:
12271234
is_available, flash_attn_version = _is_package_available("flash_attn", return_version=True)
12281235
# FA4 is also distributed under "flash_attn", hence we need to check the naming here
12291236
is_available = is_available and "flash-attn" in [
1230-
pkg.replace("_", "-") for pkg in PACKAGE_DISTRIBUTION_MAPPING.get("flash_attn", [])
1237+
pkg.replace("_", "-") for pkg in _package_distribution_mapping().get("flash_attn", [])
12311238
]
12321239

12331240
# Only allow versions >= 2.3.3 to avoid very old legacy workarounds that are now 2+ years old
@@ -1260,7 +1267,7 @@ def is_flash_attn_3_available(kernels_fallback_ok: bool = False) -> bool:
12601267
is_available = _is_package_available("flash_attn_interface")[0]
12611268
# Resolving and ensuring the proper name of FA3 being associated
12621269
is_available = is_available and "flash-attn-3" in [
1263-
pkg.replace("_", "-") for pkg in PACKAGE_DISTRIBUTION_MAPPING.get("flash_attn_interface", [])
1270+
pkg.replace("_", "-") for pkg in _package_distribution_mapping().get("flash_attn_interface", [])
12641271
]
12651272
if is_available and is_torch_cuda_available():
12661273
return True
@@ -1289,7 +1296,7 @@ def is_flash_attn_4_available() -> bool:
12891296
# NOTE: FA2 seems to distribute the `cute` subdirectory even if only FA2 has been installed
12901297
# -> check for the proper (normalized) distribution name
12911298
is_available = is_available and "flash-attn-4" in [
1292-
pkg.replace("_", "-") for pkg in PACKAGE_DISTRIBUTION_MAPPING.get("flash_attn", [])
1299+
pkg.replace("_", "-") for pkg in _package_distribution_mapping().get("flash_attn", [])
12931300
]
12941301

12951302
return is_available and is_torch_cuda_available()
@@ -1300,7 +1307,7 @@ def is_flash_attn_greater_or_equal(library_version: str) -> bool:
13001307
is_available, flash_attn_version = _is_package_available("flash_attn", return_version=True)
13011308
# FA4 is also distributed under "flash_attn", hence we need to check the naming here
13021309
is_available = is_available and "flash-attn" in [
1303-
pkg.replace("_", "-") for pkg in PACKAGE_DISTRIBUTION_MAPPING.get("flash_attn", [])
1310+
pkg.replace("_", "-") for pkg in _package_distribution_mapping().get("flash_attn", [])
13041311
]
13051312

13061313
if not is_available:

‎tests/utils/test_import_utils.py‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,17 +105,17 @@ def test_is_package_available_unmapped_distribution_does_not_import():
105105

106106
with (
107107
patch("transformers.utils.import_utils.importlib.util.find_spec", return_value=object()),
108-
patch("transformers.utils.import_utils.PACKAGE_DISTRIBUTION_MAPPING", {}),
108+
patch("transformers.utils.import_utils._package_distribution_mapping", return_value={}),
109109
patch("transformers.utils.import_utils.importlib.metadata.version", return_value="1.2.3") as version,
110110
patch("transformers.utils.import_utils.importlib.import_module") as import_module,
111111
):
112112
assert _is_package_available(pkg_name, return_version=True) == (True, "1.2.3")
113-
version.assert_called_once_with(pkg_name.replace("_", "-"))
113+
version.assert_called_once_with(pkg_name)
114114
import_module.assert_not_called()
115115

116116

117117
def test_candidate_distribution_names():
118-
with patch("transformers.utils.import_utils.PACKAGE_DISTRIBUTION_MAPPING", {"PIL": ["pillow"]}):
118+
with patch("transformers.utils.import_utils._package_distribution_mapping", return_value={"PIL": ["pillow"]}):
119119
# A mapped import name prefers its distribution, but keeps the import name as a fallback.
120120
assert _candidate_distribution_names("PIL") == ["pillow", "PIL"]
121121
# An unmapped import name falls back on itself, normalized first, without duplicates.
@@ -160,7 +160,9 @@ def fake_is_package_available(pkg_name: str, return_version: bool = False) -> tu
160160
try:
161161
with (
162162
patch("transformers.utils.import_utils._is_package_available", side_effect=fake_is_package_available),
163-
patch("transformers.utils.import_utils.PACKAGE_DISTRIBUTION_MAPPING", fake_distribution_mapping),
163+
patch(
164+
"transformers.utils.import_utils._package_distribution_mapping", return_value=fake_distribution_mapping
165+
),
164166
patch("transformers.utils.import_utils.is_torch_cuda_available", return_value=cuda_available),
165167
patch("transformers.utils.import_utils.is_torch_mlu_available", return_value=False),
166168
patch("transformers.utils.import_utils.is_torch_musa_available", return_value=False),

‎tests/utils/test_modeling_utils.py‎

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@
9494
WEIGHTS_NAME,
9595
)
9696
from transformers.utils.import_utils import (
97-
PACKAGE_DISTRIBUTION_MAPPING,
97+
_package_distribution_mapping,
9898
is_flash_attn_2_available,
9999
is_flash_attn_3_available,
100100
is_flash_attn_4_available,
@@ -3328,14 +3328,16 @@ def test_transformers_sdpa_force_gqa_env_var_overrides_use_gqa_in_sdpa_attention
33283328

33293329
def test_flash_attn_available_no_keyerror_when_missing_from_distribution_map(self):
33303330
# Regression test for https://github.com/huggingface/transformers/issues/45520.
3331-
# When flash_attn is importable but not present in PACKAGE_DISTRIBUTION_MAPPING
3331+
# When flash_attn is importable but not present in the package distribution mapping
33323332
# (e.g. installed via a non-standard wheel), the availability checks must not raise
33333333
# a KeyError; they should simply return False.
33343334
stripped_map = {
3335-
k: v for k, v in PACKAGE_DISTRIBUTION_MAPPING.items() if k not in ("flash_attn", "flash_attn_interface")
3335+
k: v for k, v in _package_distribution_mapping().items() if k not in ("flash_attn", "flash_attn_interface")
33363336
}
3337-
with patch("transformers.utils.import_utils.PACKAGE_DISTRIBUTION_MAPPING", stripped_map):
3338-
with patch("transformers.modeling_flash_attention_utils.PACKAGE_DISTRIBUTION_MAPPING", stripped_map):
3337+
with patch("transformers.utils.import_utils._package_distribution_mapping", return_value=stripped_map):
3338+
with patch(
3339+
"transformers.modeling_flash_attention_utils._package_distribution_mapping", return_value=stripped_map
3340+
):
33393341
self.assertFalse(is_flash_attn_2_available())
33403342
self.assertFalse(is_flash_attn_3_available())
33413343
self.assertFalse(is_flash_attn_4_available())

0 commit comments

Comments
 (0)