Skip to content

Commit 98ccd6e

Browse files
committed
Add SM arch bitcode lookup
1 parent c544da9 commit 98ccd6e

2 files changed

Lines changed: 172 additions & 18 deletions

File tree

cuda_pathfinder/cuda/pathfinder/_static_libs/find_bitcode_lib.py

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
import functools
55
import os
6+
import re
67
from dataclasses import dataclass
78
from typing import NoReturn, TypedDict
89

@@ -74,13 +75,24 @@ def _no_such_file_in_dir(dir_path: str, filename: str, error_messages: list[str]
7475
attachments.append(f' Directory does not exist: "{dir_path}"')
7576

7677

78+
def _filename_with_sm_arch(filename: str, sm_arch: str) -> str:
79+
if not sm_arch:
80+
return filename
81+
82+
if not re.match(r"^sm[0-9]+[a-z]?$", sm_arch):
83+
raise ValueError(f"Invalid sm_arch: '{sm_arch}' must match 'sm[0-9]+[a-z]?'")
84+
85+
stem, ext = os.path.splitext(filename)
86+
return f"{stem}_{sm_arch}{ext}"
87+
88+
7789
class _FindBitcodeLib:
78-
def __init__(self, name: str) -> None:
90+
def __init__(self, name: str, sm_arch: str = "") -> None:
7991
if name not in _SUPPORTED_BITCODE_LIBS_INFO: # Updated reference
8092
raise ValueError(f"Unknown bitcode library: '{name}'. Supported: {', '.join(SUPPORTED_BITCODE_LIBS)}")
8193
self.name: str = name
8294
self.config: _BitcodeLibInfo = _SUPPORTED_BITCODE_LIBS_INFO[name] # Updated reference
83-
self.filename: str = self.config["filename"]
95+
self.filename: str = _filename_with_sm_arch(self.config["filename"], sm_arch)
8496
self.rel_path: str = self.config["rel_path"]
8597
self.site_packages_dirs: tuple[str, ...] = self.config["site_packages_dirs"]
8698
self.error_messages: list[str] = []
@@ -130,14 +142,23 @@ def raise_not_found_error(self) -> NoReturn:
130142
raise BitcodeLibNotFoundError(f'Failure finding "{self.filename}": {err}\n{att}')
131143

132144

133-
def locate_bitcode_lib(name: str) -> LocatedBitcodeLib:
145+
def locate_bitcode_lib(name: str, sm_arch: str = "") -> LocatedBitcodeLib:
134146
"""Locate a bitcode library by name.
135147
148+
When ``sm_arch`` is set, locate the architecture-specific bitcode filename
149+
with ``_{sm_arch}`` inserted before the ``.bc`` suffix.
150+
151+
Args:
152+
name: Name of the supported bitcode library to locate.
153+
sm_arch: Optional SM architecture suffix, such as ``"sm90"`` or
154+
``"sm90a"``. If set, it must match ``sm[0-9]+[a-z]?``.
155+
136156
Raises:
137-
ValueError: If ``name`` is not a supported bitcode library.
157+
ValueError: If ``name`` is not a supported bitcode library, or if
158+
``sm_arch`` is set but does not match ``sm[0-9]+[a-z]?``.
138159
BitcodeLibNotFoundError: If the bitcode library cannot be found.
139160
"""
140-
finder = _FindBitcodeLib(name)
161+
finder = _FindBitcodeLib(name, sm_arch)
141162

142163
abs_path = finder.try_site_packages()
143164
if abs_path is not None:
@@ -170,11 +191,20 @@ def locate_bitcode_lib(name: str) -> LocatedBitcodeLib:
170191

171192

172193
@functools.cache
173-
def find_bitcode_lib(name: str) -> str:
194+
def find_bitcode_lib(name: str, sm_arch: str = "") -> str:
174195
"""Find the absolute path to a bitcode library.
175196
197+
When ``sm_arch`` is set, find the architecture-specific bitcode filename
198+
with ``_{sm_arch}`` inserted before the ``.bc`` suffix.
199+
200+
Args:
201+
name: Name of the supported bitcode library to find.
202+
sm_arch: Optional SM architecture suffix, such as ``"sm90"`` or
203+
``"sm90a"``. If set, it must match ``sm[0-9]+[a-z]?``.
204+
176205
Raises:
177-
ValueError: If ``name`` is not a supported bitcode library.
206+
ValueError: If ``name`` is not a supported bitcode library, or if
207+
``sm_arch`` is set but does not match ``sm[0-9]+[a-z]?``.
178208
BitcodeLibNotFoundError: If the bitcode library cannot be found.
179209
"""
180-
return locate_bitcode_lib(name).abs_path
210+
return locate_bitcode_lib(name, sm_arch).abs_path

cuda_pathfinder/tests/test_find_bitcode_lib.py

Lines changed: 134 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,6 @@ def _bitcode_lib_info(libname: str):
2323
return find_bitcode_lib_module._SUPPORTED_BITCODE_LIBS_INFO[libname]
2424

2525

26-
def _bitcode_lib_filename(libname: str) -> str:
27-
return _bitcode_lib_info(libname)["filename"]
28-
29-
3026
@pytest.fixture
3127
def clear_find_bitcode_lib_cache():
3228
find_bitcode_lib_module.find_bitcode_lib.cache_clear()
@@ -36,9 +32,9 @@ def clear_find_bitcode_lib_cache():
3632
get_cuda_path_or_home.cache_clear()
3733

3834

39-
def _make_bitcode_lib_file(dir_path: Path, libname: str) -> str:
35+
def _make_bitcode_lib_file(dir_path: Path, filename: str) -> str:
4036
dir_path.mkdir(parents=True, exist_ok=True)
41-
file_path = dir_path / _bitcode_lib_filename(libname)
37+
file_path = dir_path / filename
4238
file_path.touch()
4339
return str(file_path)
4440

@@ -92,14 +88,16 @@ def test_locate_bitcode_lib(info_summary_append, libname):
9288
@pytest.mark.usefixtures("clear_find_bitcode_lib_cache")
9389
@pytest.mark.parametrize("libname", SUPPORTED_BITCODE_LIBS)
9490
def test_locate_bitcode_lib_search_order(monkeypatch, tmp_path, libname):
91+
filename = _bitcode_lib_info(libname)["filename"]
92+
9593
site_packages_lib_dir = _site_packages_bitcode_lib_dir_under(tmp_path / "site-packages", libname)
96-
site_packages_path = _make_bitcode_lib_file(site_packages_lib_dir, libname)
94+
site_packages_path = _make_bitcode_lib_file(site_packages_lib_dir, filename)
9795

9896
conda_prefix = tmp_path / "conda-prefix"
99-
conda_path = _make_bitcode_lib_file(_bitcode_lib_dir_under(_conda_anchor(conda_prefix), libname), libname)
97+
conda_path = _make_bitcode_lib_file(_bitcode_lib_dir_under(_conda_anchor(conda_prefix), libname), filename)
10098

10199
cuda_home = tmp_path / "cuda-home"
102-
cuda_home_path = _make_bitcode_lib_file(_bitcode_lib_dir_under(cuda_home, libname), libname)
100+
cuda_home_path = _make_bitcode_lib_file(_bitcode_lib_dir_under(cuda_home, libname), filename)
103101

104102
site_packages_sub_dirs = tuple(
105103
tuple(rel_dir.split("/")) for rel_dir in _bitcode_lib_info(libname)["site_packages_dirs"]
@@ -135,6 +133,84 @@ def find_expected_sub_dir(sub_dir):
135133
assert located_lib.found_via == "CUDA_PATH"
136134

137135

136+
@pytest.mark.usefixtures("clear_find_bitcode_lib_cache")
137+
@pytest.mark.skipif("nvshmem_device" not in SUPPORTED_BITCODE_LIBS, reason="nvshmem_device is not supported")
138+
def test_locate_bitcode_lib_with_sm_arch_search_order(monkeypatch, tmp_path):
139+
libname = "nvshmem_device"
140+
sm_arch = "sm90"
141+
filename = "libnvshmem_device_sm90.bc"
142+
143+
site_packages_lib_dir = _site_packages_bitcode_lib_dir_under(tmp_path / "site-packages", libname)
144+
site_packages_path = _make_bitcode_lib_file(site_packages_lib_dir, filename)
145+
146+
conda_prefix = tmp_path / "conda-prefix"
147+
conda_path = _make_bitcode_lib_file(_bitcode_lib_dir_under(_conda_anchor(conda_prefix), libname), filename)
148+
149+
cuda_home = tmp_path / "cuda-home"
150+
cuda_home_path = _make_bitcode_lib_file(_bitcode_lib_dir_under(cuda_home, libname), filename)
151+
152+
site_packages_sub_dirs = tuple(
153+
tuple(rel_dir.split("/")) for rel_dir in _bitcode_lib_info(libname)["site_packages_dirs"]
154+
)
155+
156+
def find_expected_sub_dir(sub_dir):
157+
assert sub_dir in site_packages_sub_dirs
158+
if sub_dir == site_packages_sub_dirs[0]:
159+
return [str(site_packages_lib_dir)]
160+
return []
161+
162+
monkeypatch.setattr(
163+
find_bitcode_lib_module,
164+
"find_sub_dirs_all_sitepackages",
165+
find_expected_sub_dir,
166+
)
167+
monkeypatch.setenv("CONDA_PREFIX", str(conda_prefix))
168+
monkeypatch.setenv("CUDA_HOME", str(cuda_home))
169+
monkeypatch.delenv("CUDA_PATH", raising=False)
170+
171+
located_lib = locate_bitcode_lib(libname, sm_arch=sm_arch)
172+
assert located_lib.abs_path == site_packages_path
173+
assert located_lib.filename == filename
174+
assert located_lib.found_via == "site-packages"
175+
assert find_bitcode_lib(libname, sm_arch=sm_arch) == site_packages_path
176+
os.remove(site_packages_path)
177+
find_bitcode_lib_module.find_bitcode_lib.cache_clear()
178+
179+
located_lib = locate_bitcode_lib(libname, sm_arch=sm_arch)
180+
assert located_lib.abs_path == conda_path
181+
assert located_lib.filename == filename
182+
assert located_lib.found_via == "conda"
183+
os.remove(conda_path)
184+
185+
located_lib = locate_bitcode_lib(libname, sm_arch=sm_arch)
186+
assert located_lib.abs_path == cuda_home_path
187+
assert located_lib.filename == filename
188+
assert located_lib.found_via == "CUDA_PATH"
189+
190+
191+
@pytest.mark.usefixtures("clear_find_bitcode_lib_cache")
192+
@pytest.mark.skipif("nvshmem_device" not in SUPPORTED_BITCODE_LIBS, reason="nvshmem_device is not supported")
193+
def test_find_bitcode_lib_cache_keeps_sm_arch_separate(monkeypatch, tmp_path):
194+
libname = "nvshmem_device"
195+
site_packages_lib_dir = _site_packages_bitcode_lib_dir_under(tmp_path / "site-packages", libname)
196+
sm80_path = _make_bitcode_lib_file(site_packages_lib_dir, "libnvshmem_device_sm80.bc")
197+
sm90_path = _make_bitcode_lib_file(site_packages_lib_dir, "libnvshmem_device_sm90.bc")
198+
sm90a_path = _make_bitcode_lib_file(site_packages_lib_dir, "libnvshmem_device_sm90a.bc")
199+
200+
monkeypatch.setattr(
201+
find_bitcode_lib_module,
202+
"find_sub_dirs_all_sitepackages",
203+
lambda _sub_dir: [str(site_packages_lib_dir)],
204+
)
205+
monkeypatch.delenv("CONDA_PREFIX", raising=False)
206+
monkeypatch.delenv("CUDA_HOME", raising=False)
207+
monkeypatch.delenv("CUDA_PATH", raising=False)
208+
209+
assert find_bitcode_lib(libname, sm_arch="sm80") == sm80_path
210+
assert find_bitcode_lib(libname, sm_arch="sm90") == sm90_path
211+
assert find_bitcode_lib(libname, sm_arch="sm90a") == sm90a_path
212+
213+
138214
@pytest.mark.usefixtures("clear_find_bitcode_lib_cache")
139215
def test_find_bitcode_lib_not_found_error_includes_cuda_home_directory_listing(monkeypatch, tmp_path):
140216
cuda_home = tmp_path / "cuda-home"
@@ -156,12 +232,44 @@ def test_find_bitcode_lib_not_found_error_includes_cuda_home_directory_listing(m
156232
find_bitcode_lib("device")
157233

158234
message = str(exc_info.value)
159-
expected_missing_file = os.path.join(str(lib_dir), _bitcode_lib_filename("device"))
235+
expected_missing_file = os.path.join(str(lib_dir), _bitcode_lib_info("device")["filename"])
160236
assert f"No such file: {expected_missing_file}" in message
161237
assert f'listdir("{lib_dir}"):' in message
162238
assert "README.txt" in message
163239

164240

241+
@pytest.mark.usefixtures("clear_find_bitcode_lib_cache")
242+
@pytest.mark.skipif("nvshmem_device" not in SUPPORTED_BITCODE_LIBS, reason="nvshmem_device is not supported")
243+
def test_find_bitcode_lib_with_sm_arch_not_found_error_uses_arch_specific_filename(monkeypatch, tmp_path):
244+
libname = "nvshmem_device"
245+
sm_arch = "sm90"
246+
expected_filename = "libnvshmem_device_sm90.bc"
247+
248+
cuda_home = tmp_path / "cuda-home"
249+
lib_dir = _bitcode_lib_dir_under(cuda_home, libname)
250+
lib_dir.mkdir(parents=True, exist_ok=True)
251+
extra_file = lib_dir / "libnvshmem_device.bc"
252+
extra_file.touch()
253+
254+
monkeypatch.setattr(
255+
find_bitcode_lib_module,
256+
"find_sub_dirs_all_sitepackages",
257+
lambda _sub_dir: [],
258+
)
259+
monkeypatch.delenv("CONDA_PREFIX", raising=False)
260+
monkeypatch.setenv("CUDA_HOME", str(cuda_home))
261+
monkeypatch.delenv("CUDA_PATH", raising=False)
262+
263+
with pytest.raises(BitcodeLibNotFoundError, match=rf'Failure finding "{expected_filename}"') as exc_info:
264+
find_bitcode_lib(libname, sm_arch=sm_arch)
265+
266+
message = str(exc_info.value)
267+
expected_missing_file = os.path.join(str(lib_dir), expected_filename)
268+
assert f"No such file: {expected_missing_file}" in message
269+
assert f'listdir("{lib_dir}"):' in message
270+
assert "libnvshmem_device.bc" in message
271+
272+
165273
@pytest.mark.usefixtures("clear_find_bitcode_lib_cache")
166274
def test_find_bitcode_lib_not_found_error_without_cuda_home(monkeypatch):
167275
monkeypatch.setattr(
@@ -183,3 +291,19 @@ def test_find_bitcode_lib_not_found_error_without_cuda_home(monkeypatch):
183291
def test_find_bitcode_lib_invalid_name():
184292
with pytest.raises(ValueError, match="Unknown bitcode library"):
185293
find_bitcode_lib_module.locate_bitcode_lib("invalid")
294+
295+
296+
@pytest.mark.parametrize(
297+
"sm_arch",
298+
[
299+
"../sm90",
300+
"compute90",
301+
"sm_90",
302+
"sm",
303+
"sm90/extra",
304+
"sm90A",
305+
],
306+
)
307+
def test_find_bitcode_lib_invalid_sm_arch(sm_arch):
308+
with pytest.raises(ValueError, match="must match"):
309+
find_bitcode_lib_module.locate_bitcode_lib("device", sm_arch=sm_arch)

0 commit comments

Comments
 (0)