|
16 | 16 |
|
17 | 17 | import os
|
18 | 18 | import platform
|
| 19 | +import re |
19 | 20 | import sys
|
20 | 21 | import sysconfig
|
| 22 | +import tempfile |
21 | 23 | import warnings
|
22 | 24 | from argparse import ArgumentParser
|
23 | 25 | from contextlib import contextmanager
|
|
27 | 29 | from shutil import copy, copytree, rmtree
|
28 | 30 | from subprocess import DEVNULL, CalledProcessError, check_output, run
|
29 | 31 | from textwrap import dedent
|
30 |
| -from typing import List |
| 32 | +from typing import Sequence |
31 | 33 |
|
32 | 34 | try:
|
33 | 35 | from packaging.requirements import Requirement
|
@@ -120,7 +122,8 @@ def create_venv(project_dir: Path):
|
120 | 122 | return venv_prefix
|
121 | 123 |
|
122 | 124 |
|
123 |
| -def setup_venv(project_dir: Path, requirements_file: Path, no_venv: bool): |
| 125 | +def setup_venv(project_dir: Path, requirements_file: Path, |
| 126 | + no_venv: bool) -> tuple[Path, Path]: |
124 | 127 | """Creates/updates a venv and installs requirements.
|
125 | 128 |
|
126 | 129 | Args:
|
@@ -279,14 +282,147 @@ def generate_fmha_cu(project_dir, venv_python):
|
279 | 282 | os.chdir(project_dir)
|
280 | 283 |
|
281 | 284 |
|
| 285 | +def create_cuda_stub_links(cuda_stub_dir: str, missing_libs: list[str]) -> str: |
| 286 | + """ |
| 287 | + Creates symbolic links for CUDA stub libraries in a temporary directory. |
| 288 | +
|
| 289 | + Args: |
| 290 | + cuda_stub_dir (str): Path to the directory containing CUDA stubs. |
| 291 | + missing_libs: Versioned names of the missing libraries. |
| 292 | +
|
| 293 | + Returns: |
| 294 | + str: Path to the temporary directory where links were created. |
| 295 | + """ |
| 296 | + cuda_stub_path = Path(cuda_stub_dir) |
| 297 | + if not cuda_stub_path.exists(): |
| 298 | + raise RuntimeError( |
| 299 | + f"CUDA stub directory '{cuda_stub_dir}' does not exist.") |
| 300 | + |
| 301 | + # Create a temporary directory for the symbolic links |
| 302 | + temp_dir = tempfile.mkdtemp(prefix="cuda_stub_links_") |
| 303 | + temp_dir_path = Path(temp_dir) |
| 304 | + |
| 305 | + version_pattern = r'\.\d+' |
| 306 | + for missing_lib in filter(lambda x: re.search(version_pattern, x), |
| 307 | + missing_libs): |
| 308 | + # Define `so` as the first part of `missing_lib` with trailing '.' and digits removed |
| 309 | + so = cuda_stub_path / re.sub(version_pattern, '', missing_lib) |
| 310 | + so_versioned = temp_dir_path / missing_lib |
| 311 | + |
| 312 | + # Check if the library exists in the original directory |
| 313 | + if so.exists(): |
| 314 | + try: |
| 315 | + # Create the symbolic link in the temporary directory |
| 316 | + so_versioned.symlink_to(so) |
| 317 | + except OSError as e: |
| 318 | + # Clean up the temporary directory on error |
| 319 | + rmtree(temp_dir) |
| 320 | + raise RuntimeError( |
| 321 | + f"Failed to create symbolic link for '{missing_lib}' in temporary directory '{temp_dir}': {e}" |
| 322 | + ) |
| 323 | + else: |
| 324 | + warnings.warn( |
| 325 | + f"Warning: Source library '{so}' does not exist and was skipped." |
| 326 | + ) |
| 327 | + |
| 328 | + # Return the path to the temporary directory where the links were created |
| 329 | + return str(temp_dir_path) |
| 330 | + |
| 331 | + |
| 332 | +def check_missing_libs(so_prefix: str) -> list[str]: |
| 333 | + result = build_run(f"ldd {so_prefix}.cpython*.so", |
| 334 | + capture_output=True, |
| 335 | + text=True) |
| 336 | + missing = [] |
| 337 | + for line in result.stdout.splitlines(): |
| 338 | + if "not found" in line: |
| 339 | + lib_name = line.split()[ |
| 340 | + 0] # Extract the library name before "=> not found" |
| 341 | + if lib_name not in missing: |
| 342 | + missing.append(lib_name) |
| 343 | + return missing |
| 344 | + |
| 345 | + |
| 346 | +def generate_python_stubs_linux(binding_type: str, venv_python: Path, |
| 347 | + deep_ep: bool): |
| 348 | + is_nanobind = binding_type == "nanobind" |
| 349 | + if is_nanobind: |
| 350 | + build_run(f"\"{venv_python}\" -m pip install nanobind") |
| 351 | + build_run(f"\"{venv_python}\" -m pip install pybind11-stubgen") |
| 352 | + |
| 353 | + env_stub_gen = os.environ.copy() |
| 354 | + cuda_home_dir = env_stub_gen.get("CUDA_HOME") or env_stub_gen.get( |
| 355 | + "CUDA_PATH") or "/usr/local/cuda" |
| 356 | + missing_libs = check_missing_libs("bindings") |
| 357 | + cuda_stub_dir = f"{cuda_home_dir}/lib64/stubs" |
| 358 | + |
| 359 | + if missing_libs and Path(cuda_stub_dir).exists(): |
| 360 | + # Create symbolic links for the CUDA stubs |
| 361 | + link_dir = create_cuda_stub_links(cuda_stub_dir, missing_libs) |
| 362 | + ld_library_path = env_stub_gen.get("LD_LIBRARY_PATH") |
| 363 | + env_stub_gen["LD_LIBRARY_PATH"] = ":".join( |
| 364 | + filter(None, [link_dir, cuda_stub_dir, ld_library_path])) |
| 365 | + else: |
| 366 | + link_dir = None |
| 367 | + |
| 368 | + try: |
| 369 | + if is_nanobind: |
| 370 | + build_run(f"\"{venv_python}\" -m nanobind.stubgen -m bindings -O .", |
| 371 | + env=env_stub_gen) |
| 372 | + else: |
| 373 | + build_run( |
| 374 | + f"\"{venv_python}\" -m pybind11_stubgen -o . bindings --exit-code", |
| 375 | + env=env_stub_gen) |
| 376 | + build_run( |
| 377 | + f"\"{venv_python}\" -m pybind11_stubgen -o . deep_gemm_cpp_tllm --exit-code", |
| 378 | + env=env_stub_gen) |
| 379 | + if deep_ep: |
| 380 | + build_run( |
| 381 | + f"\"{venv_python}\" -m pybind11_stubgen -o . deep_ep_cpp_tllm --exit-code", |
| 382 | + env=env_stub_gen) |
| 383 | + finally: |
| 384 | + if link_dir: |
| 385 | + rmtree(link_dir) |
| 386 | + |
| 387 | + |
| 388 | +def generate_python_stubs_windows(binding_type: str, venv_python: Path, |
| 389 | + pkg_dir: Path, lib_dir: Path): |
| 390 | + if binding_type == "nanobind": |
| 391 | + print("Windows not yet supported for nanobind stubs") |
| 392 | + exit(1) |
| 393 | + else: |
| 394 | + build_run(f"\"{venv_python}\" -m pip install pybind11-stubgen") |
| 395 | + stubgen = "stubgen.py" |
| 396 | + stubgen_contents = """ |
| 397 | + # Loading torch, trt before bindings is required to avoid import errors on windows. |
| 398 | + # isort: off |
| 399 | + import torch |
| 400 | + import tensorrt as trt |
| 401 | + # isort: on |
| 402 | + import os |
| 403 | + import platform |
| 404 | +
|
| 405 | + from pybind11_stubgen import main |
| 406 | +
|
| 407 | + if __name__ == "__main__": |
| 408 | + # Load dlls from `libs` directory before launching bindings. |
| 409 | + if platform.system() == "Windows": |
| 410 | + os.add_dll_directory(r\"{lib_dir}\") |
| 411 | + main() |
| 412 | + """.format(lib_dir=lib_dir) |
| 413 | + (pkg_dir / stubgen).write_text(dedent(stubgen_contents)) |
| 414 | + build_run(f"\"{venv_python}\" {stubgen} -o . bindings") |
| 415 | + (pkg_dir / stubgen).unlink() |
| 416 | + |
| 417 | + |
282 | 418 | def main(*,
|
283 | 419 | build_type: str = "Release",
|
284 | 420 | generator: str = "",
|
285 | 421 | build_dir: Path = None,
|
286 | 422 | dist_dir: Path = None,
|
287 | 423 | cuda_architectures: str = None,
|
288 | 424 | job_count: int = None,
|
289 |
| - extra_cmake_vars: List[str] = list(), |
| 425 | + extra_cmake_vars: Sequence[str] = tuple(), |
290 | 426 | extra_make_targets: str = "",
|
291 | 427 | trt_root: str = '/usr/local/tensorrt',
|
292 | 428 | nccl_root: str = None,
|
@@ -361,7 +497,7 @@ def main(*,
|
361 | 497 |
|
362 | 498 | if on_windows:
|
363 | 499 | # Windows does not support multi-device currently.
|
364 |
| - extra_cmake_vars.extend(["ENABLE_MULTI_DEVICE=0"]) |
| 500 | + extra_cmake_vars = list(extra_cmake_vars) + ["ENABLE_MULTI_DEVICE=0"] |
365 | 501 |
|
366 | 502 | # The Ninja CMake generator is used for our Windows build
|
367 | 503 | # (Easier than MSBuild to make compatible with our Docker image)
|
@@ -703,81 +839,14 @@ def get_binding_lib(subdirectory, name):
|
703 | 839 | dirs_exist_ok=True)
|
704 | 840 |
|
705 | 841 | if not skip_stubs:
|
706 |
| - with working_directory(project_dir): |
707 |
| - if binding_type == "nanobind": |
708 |
| - build_run(f"\"{venv_python}\" -m pip install nanobind") |
709 |
| - else: |
710 |
| - build_run( |
711 |
| - f"\"{venv_python}\" -m pip install pybind11-stubgen") |
712 | 842 | with working_directory(pkg_dir):
|
713 | 843 | if on_windows:
|
714 |
| - if binding_type == "nanobind": |
715 |
| - print("Windows not yet supported for nanobind stubs") |
716 |
| - exit(1) |
717 |
| - else: |
718 |
| - stubgen = "stubgen.py" |
719 |
| - stubgen_contents = """ |
720 |
| - # Loading torch, trt before bindings is required to avoid import errors on windows. |
721 |
| - # isort: off |
722 |
| - import torch |
723 |
| - import tensorrt as trt |
724 |
| - # isort: on |
725 |
| - import os |
726 |
| - import platform |
727 |
| -
|
728 |
| - from pybind11_stubgen import main |
729 |
| -
|
730 |
| - if __name__ == "__main__": |
731 |
| - # Load dlls from `libs` directory before launching bindings. |
732 |
| - if platform.system() == "Windows": |
733 |
| - os.add_dll_directory(r\"{lib_dir}\") |
734 |
| - main() |
735 |
| - """.format(lib_dir=lib_dir) |
736 |
| - (pkg_dir / stubgen).write_text(dedent(stubgen_contents)) |
737 |
| - build_run(f"\"{venv_python}\" {stubgen} -o . bindings") |
738 |
| - (pkg_dir / stubgen).unlink() |
739 |
| - else: |
740 |
| - env_ld = os.environ.copy() |
741 |
| - |
742 |
| - new_library_path = "/usr/local/cuda/compat:/usr/local/cuda/compat/lib:/usr/local/cuda/compat/lib.real" |
743 |
| - if 'LD_LIBRARY_PATH' in env_ld: |
744 |
| - new_library_path += f":{env_ld['LD_LIBRARY_PATH']}" |
745 |
| - |
746 |
| - result = build_run("find /usr -name *libnvidia-ml.so*", |
747 |
| - capture_output=True, |
748 |
| - text=True) |
749 |
| - assert result.returncode == 0, f"Failed to run find *libnvidia-ml.so*: {result.stderr}" |
750 |
| - |
751 |
| - # Build containers only contain stub version of libnvidia-ml.so and not the real version. |
752 |
| - # If real version not in system, we need to create symbolic link to stub version to prevent import errors. |
753 |
| - if "libnvidia-ml.so.1" not in result.stdout: |
754 |
| - if "libnvidia-ml.so" in result.stdout: |
755 |
| - line = result.stdout.splitlines()[0] |
756 |
| - path = os.path.dirname(line) |
757 |
| - new_library_path += f":{path}" |
758 |
| - build_run(f"ln -s {line} {path}/libnvidia-ml.so.1") |
759 |
| - else: |
760 |
| - print( |
761 |
| - f"Failed to find libnvidia-ml.so: {result.stderr}", |
762 |
| - file=sys.stderr) |
763 |
| - exit(1) |
764 |
| - |
765 |
| - env_ld["LD_LIBRARY_PATH"] = new_library_path |
766 |
| - if binding_type == "nanobind": |
767 |
| - build_run( |
768 |
| - f"\"{venv_python}\" -m nanobind.stubgen -m bindings -O .", |
769 |
| - env=env_ld) |
770 |
| - else: |
771 |
| - build_run( |
772 |
| - f"\"{venv_python}\" -m pybind11_stubgen -o . bindings --exit-code", |
773 |
| - env=env_ld) |
774 |
| - if deep_ep_cuda_architectures: |
775 |
| - build_run( |
776 |
| - f"\"{venv_python}\" -m pybind11_stubgen -o . deep_ep_cpp_tllm --exit-code", |
777 |
| - env=env_ld) |
778 |
| - build_run( |
779 |
| - f"\"{venv_python}\" -m pybind11_stubgen -o . deep_gemm_cpp_tllm --exit-code", |
780 |
| - env=env_ld) |
| 844 | + generate_python_stubs_windows(binding_type, venv_python, |
| 845 | + pkg_dir, lib_dir) |
| 846 | + else: # on linux |
| 847 | + generate_python_stubs_linux( |
| 848 | + binding_type, venv_python, |
| 849 | + bool(deep_ep_cuda_architectures)) |
781 | 850 |
|
782 | 851 | if not skip_building_wheel:
|
783 | 852 | if dist_dir is None:
|
|
0 commit comments