-
-
Notifications
You must be signed in to change notification settings - Fork 22.3k
[Do not merge!] [Build] Migrate vendored DeepGEMM from pybind to TORCH_LIBRARY (abi3) #48962
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
b732219
6eca74f
64f707f
b90ba30
46ecb2d
a384ac6
5b8e87a
e94f882
2d3e63d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -28,9 +28,9 @@ if(DEEPGEMM_SRC_DIR) | |
| message(STATUS "DeepGEMM using local DEEPGEMM_SRC_DIR: ${deepgemm_SOURCE_DIR}") | ||
| else() | ||
| # Keep in sync with tools/install_deepgemm.sh | ||
| set(_DEEPGEMM_UPSTREAM_REPO "https://github.com/deepseek-ai/DeepGEMM.git") | ||
| # Pinned to the tip of the nv_dev branch (SM120 support). | ||
| set(_DEEPGEMM_UPSTREAM_TAG "8b1392b978f5a03c828dd1711090d7fb50958b8a") | ||
| set(_DEEPGEMM_UPSTREAM_REPO "https://github.com/cleonard530/DeepGEMM.git") | ||
| # NOTE: This is currently targeting nv-dev branch due to sm120 support | ||
| set(_DEEPGEMM_UPSTREAM_TAG "043dbdab90685351185774bd74cfb4c24c716f4e") | ||
|
|
||
| set(_deepgemm_fc_root "${FETCHCONTENT_BASE_DIR}") | ||
| if(NOT _deepgemm_fc_root) | ||
|
|
@@ -40,7 +40,7 @@ else() | |
| set(_deepgemm_bin "${_deepgemm_fc_root}/deepgemm-build") | ||
| set(_deepgemm_sub "${_deepgemm_fc_root}/deepgemm-subbuild") | ||
|
|
||
| if(EXISTS "${_deepgemm_src}/csrc/python_api.cpp") | ||
| if(EXISTS "${_deepgemm_src}/deep_gemm/_C.py") | ||
| set(deepgemm_SOURCE_DIR "${_deepgemm_src}") | ||
| set(deepgemm_BINARY_DIR "${_deepgemm_bin}") | ||
| else() | ||
|
|
@@ -90,33 +90,17 @@ if(DEEPGEMM_ARCHS) | |
| # | ||
| # DeepGEMM integration notes | ||
| # -------------------------- | ||
| # We vendor DeepGEMM into vllm/third_party/deep_gemm/ and bundle a | ||
| # `_C.cpython-X.Y-*.so` for every CPython in `requires-python`. The | ||
| # per-Python build is delegated to tools/build_deepgemm_C.py. | ||
| # We vendor DeepGEMM into vllm/third_party/deep_gemm/ and bundle: | ||
| # - deep_gemm/_C.py (Python shim over torch.ops.deep_gemm) | ||
| # - deep_gemm/_C_extension.abi3.so (single limited-API extension) | ||
| # The build is delegated to tools/build_deepgemm_C.py (setup.py build_ext). | ||
| # | ||
| # Why per-Python: DeepGEMM's binding uses PYBIND11_MODULE, which links | ||
| # private CPython symbols — a single `_C.abi3.so` is not viable today | ||
| # (see #41476 / #41512 for the failed attempt). | ||
| # | ||
| # TODOs (tracked in vllm-project/vllm#42431): | ||
| # - Replace DeepGEMM's pybind11 binding with a TORCH_LIBRARY + shim | ||
| # binding (cf. vllm-flash-attention/csrc/common/pytorch_shim.h) to | ||
| # collapse to one `_C.abi3.so`. Needs either an upstream change or | ||
| # a maintained binding fork in vLLM. | ||
| # - AOT-compile DeepGEMM's CUDA kernels instead of runtime JIT to drop | ||
| # the vendored CUTLASS/CCCL headers and the CUDA-toolkit-at-runtime | ||
| # requirement. | ||
| # TODO: AOT-compile DeepGEMM's CUDA kernels instead of runtime JIT to drop | ||
| # the vendored CUTLASS/CCCL headers and the CUDA-toolkit-at-runtime | ||
| # requirement. | ||
| # | ||
|
|
||
| # DEEPGEMM_PYTHON_INTERPRETERS: ":"-separated target Python paths. | ||
| # Empty/unset → fall back to the build interpreter (editable installs). | ||
| # (Empty-but-set env vars test as DEFINED in cmake — treat as unset.) | ||
| if(NOT "$ENV{DEEPGEMM_PYTHON_INTERPRETERS}" STREQUAL "") | ||
| string(REPLACE ":" ";" _dg_pythons "$ENV{DEEPGEMM_PYTHON_INTERPRETERS}") | ||
| else() | ||
| set(_dg_pythons "${Python_EXECUTABLE}") | ||
| endif() | ||
| message(STATUS "DeepGEMM _C will be built for: ${_dg_pythons}") | ||
| message(STATUS "DeepGEMM extension will be built with: ${Python_EXECUTABLE}") | ||
|
|
||
| # add_custom_command does no implicit header scanning; glob explicitly so | ||
| # header-only edits in DeepGEMM/cutlass/fmt re-trigger the rebuild. | ||
|
|
@@ -127,40 +111,29 @@ if(DEEPGEMM_ARCHS) | |
| "${deepgemm_SOURCE_DIR}/deep_gemm/include/*.hpp" | ||
| "${deepgemm_SOURCE_DIR}/deep_gemm/include/*.cuh") | ||
|
|
||
| set(_dg_markers) | ||
| set(_dg_seen_soabis) | ||
| foreach(_pybin IN LISTS _dg_pythons) | ||
| execute_process( | ||
| COMMAND "${_pybin}" -c | ||
| "import sysconfig; print(sysconfig.get_config_var('SOABI'))" | ||
| OUTPUT_VARIABLE _dg_soabi | ||
| OUTPUT_STRIP_TRAILING_WHITESPACE | ||
| COMMAND_ERROR_IS_FATAL ANY) | ||
| # Dedup interpreters that resolve to the same CPython. | ||
| if(_dg_soabi IN_LIST _dg_seen_soabis) | ||
| continue() | ||
| endif() | ||
| list(APPEND _dg_seen_soabis "${_dg_soabi}") | ||
| set(_dg_dir "${CMAKE_CURRENT_BINARY_DIR}/deepgemm_C_${_dg_soabi}") | ||
| set(_dg_marker "${_dg_dir}/.built") | ||
| add_custom_command( | ||
| OUTPUT "${_dg_marker}" | ||
| COMMAND "${Python_EXECUTABLE}" | ||
| "${CMAKE_SOURCE_DIR}/tools/build_deepgemm_C.py" | ||
| "${deepgemm_SOURCE_DIR}" "${_dg_dir}" "${_pybin}" | ||
| COMMAND "${CMAKE_COMMAND}" -E touch "${_dg_marker}" | ||
| DEPENDS "${CMAKE_SOURCE_DIR}/tools/build_deepgemm_C.py" | ||
| "${deepgemm_SOURCE_DIR}/csrc/python_api.cpp" | ||
| ${_dg_headers} | ||
| COMMENT "Building DeepGEMM _C for ${_pybin}" | ||
| VERBATIM) | ||
| list(APPEND _dg_markers "${_dg_marker}") | ||
| install(DIRECTORY "${_dg_dir}/" | ||
| DESTINATION vllm/third_party/deep_gemm | ||
| COMPONENT _deep_gemm_C | ||
| FILES_MATCHING PATTERN "_C.cpython-*.so") | ||
| endforeach() | ||
| add_custom_target(_deep_gemm_C ALL DEPENDS ${_dg_markers}) | ||
| set(_dg_dir "${CMAKE_CURRENT_BINARY_DIR}/deepgemm_C") | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This block replaced the per-Python build loop with a single abi3 build |
||
| set(_dg_marker "${_dg_dir}/.built") | ||
| add_custom_command( | ||
| OUTPUT "${_dg_marker}" | ||
| COMMAND "${Python_EXECUTABLE}" | ||
| "${CMAKE_SOURCE_DIR}/tools/build_deepgemm_C.py" | ||
| "${deepgemm_SOURCE_DIR}" "${_dg_dir}" | ||
| COMMAND "${CMAKE_COMMAND}" -E touch "${_dg_marker}" | ||
| DEPENDS "${CMAKE_SOURCE_DIR}/tools/build_deepgemm_C.py" | ||
| "${deepgemm_SOURCE_DIR}/csrc/python_api.cpp" | ||
| "${deepgemm_SOURCE_DIR}/deep_gemm/_C.py" | ||
| "${deepgemm_SOURCE_DIR}/setup.py" | ||
| ${_dg_headers} | ||
| COMMENT "Building DeepGEMM _C_extension (abi3)" | ||
| VERBATIM) | ||
| add_custom_target(_deep_gemm_C ALL DEPENDS "${_dg_marker}") | ||
|
|
||
| install(DIRECTORY "${_dg_dir}/" | ||
| DESTINATION vllm/third_party/deep_gemm | ||
| COMPONENT _deep_gemm_C | ||
| FILES_MATCHING | ||
| PATTERN "_C.py" | ||
| PATTERN "_C_extension*.so") | ||
|
|
||
| # | ||
| # Vendor DeepGEMM Python package files | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,85 +1,52 @@ | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
| # SPDX-FileCopyrightText: Copyright contributors to the vLLM project | ||
| """Build DeepGEMM's `_C` pybind11 extension for <TARGET_PY>. | ||
| """Build DeepGEMM's TORCH_LIBRARY extension and copy vendored artifacts. | ||
|
|
||
| Driven from cmake/external_projects/deepgemm.cmake. The driver runs against | ||
| the build interpreter's torch; <TARGET_PY> is only consulted for INCLUDEPY | ||
| and SOABI, so target venvs don't need torch installed. | ||
| DeepGEMM now registers ops via TORCH_LIBRARY into ``deep_gemm._C_extension`` | ||
| (abi3) and exposes the legacy API through ``deep_gemm/_C.py``. This driver | ||
| delegates to DeepGEMM's ``setup.py build_ext --inplace`` and copies the shim | ||
| plus extension into the cmake output directory. | ||
|
|
||
| Usage: python build_deepgemm_C.py <DEEPGEMM_SRC_DIR> <OUTPUT_DIR> <TARGET_PY> | ||
| Usage: python build_deepgemm_C.py <DEEPGEMM_SRC_DIR> <OUTPUT_DIR> | ||
| """ | ||
|
|
||
| import json | ||
| import os | ||
| import shutil | ||
| import subprocess | ||
| import sys | ||
| from pathlib import Path | ||
|
|
||
| import torch | ||
| from torch.utils import cpp_extension | ||
|
|
||
| if len(sys.argv) != 4: | ||
| sys.exit(f"usage: {sys.argv[0]} <SRC> <OUT> <TARGET_PY>") | ||
| if len(sys.argv) != 3: | ||
| sys.exit(f"usage: {sys.argv[0]} <SRC> <OUT>") | ||
|
|
||
| src = Path(sys.argv[1]).resolve() | ||
| out = Path(sys.argv[2]).resolve() | ||
| target_py = sys.argv[3] | ||
| _pkg = src / "deep_gemm" | ||
| out.mkdir(parents=True, exist_ok=True) | ||
|
|
||
| info = json.loads( | ||
| subprocess.check_output( | ||
| [ | ||
| target_py, | ||
| "-c", | ||
| "import sysconfig, json; " | ||
| "print(json.dumps({k: sysconfig.get_config_var(k) " | ||
| "for k in ('EXT_SUFFIX', 'INCLUDEPY')}))", | ||
| ] | ||
| ).decode() | ||
| if not (_pkg / "_C.py").is_file(): | ||
| sys.exit( | ||
| f"DeepGEMM source at {src} is missing deep_gemm/_C.py; " | ||
| "expected TORCH_LIBRARY migration layout" | ||
| ) | ||
|
|
||
| # Avoid DeepGEMM's clean-git assertion when vendoring a local dirty tree. | ||
| env = os.environ.copy() | ||
| env.pop("DG_SKIP_CUDA_BUILD", None) | ||
|
|
||
| print(f"[build_deepgemm_C] building in {src} with {sys.executable}", flush=True) | ||
| subprocess.check_call( | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use deep_gemm's setup.py to build deep_gemm
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This call also gets Python_EXECUTABLE so building DeepGEMM's abi3 extension always tracks whatever Python vLLM is currently being built for. |
||
| [sys.executable, "setup.py", "build_ext", "--inplace"], | ||
| cwd=src, | ||
| env=env, | ||
| ) | ||
|
|
||
| cuda_home = cpp_extension.CUDA_HOME | ||
| if cuda_home is None: | ||
| sys.exit("CUDA_HOME not found; cannot build DeepGEMM _C") | ||
| # CCCL lives outside the standard CUDAToolkit search (mirrors DeepGEMM's setup.py). | ||
| includes = [ | ||
| info["INCLUDEPY"], | ||
| f"{cuda_home}/include", | ||
| f"{cuda_home}/include/cccl", | ||
| str(src / "csrc"), | ||
| str(src / "deep_gemm/include"), | ||
| str(src / "third-party/cutlass/include"), | ||
| str(src / "third-party/cutlass/tools/util/include"), | ||
| str(src / "third-party/fmt/include"), | ||
| *cpp_extension.include_paths(device_type="cuda"), | ||
| ] | ||
| shim = _pkg / "_C.py" | ||
| shutil.copy2(shim, out / shim.name) | ||
|
|
||
| cmd = [ | ||
| os.environ.get("CXX", "g++"), | ||
| "-shared", | ||
| "-fPIC", | ||
| "-std=c++20", | ||
| "-O3", | ||
| "-g0", | ||
| "-Wno-psabi", | ||
| "-Wno-deprecated-declarations", | ||
| "-DTORCH_API_INCLUDE_EXTENSION_H", | ||
| "-DTORCH_EXTENSION_NAME=_C", | ||
| f"-D_GLIBCXX_USE_CXX11_ABI={int(torch.compiled_with_cxx11_abi())}", | ||
| *(f"-I{p}" for p in includes), | ||
| str(src / "csrc/python_api.cpp"), | ||
| *(f"-L{p}" for p in cpp_extension.library_paths(device_type="cuda")), | ||
| f"-L{cuda_home}/lib64", | ||
| "-ltorch", | ||
| "-ltorch_python", | ||
| "-ltorch_cpu", | ||
| "-ltorch_cuda", | ||
| "-lc10", | ||
| "-lc10_cuda", | ||
| "-lcudart", | ||
| "-lnvrtc", | ||
| "-o", | ||
| str(out / f"_C{info['EXT_SUFFIX']}"), | ||
| ] | ||
| print("[build_deepgemm_C] " + " ".join(cmd), flush=True) | ||
| subprocess.check_call(cmd) | ||
| so_files = sorted(_pkg.glob("_C_extension*.so")) | ||
| if not so_files: | ||
| sys.exit(f"DeepGEMM build did not produce deep_gemm/_C_extension*.so under {src}") | ||
| for so in so_files: | ||
| shutil.copy2(so, out / so.name) | ||
| print(f"[build_deepgemm_C] installed {so.name} -> {out}", flush=True) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,41 +1,45 @@ | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
| # SPDX-FileCopyrightText: Copyright contributors to the vLLM project | ||
|
|
||
| """Assert the installed vLLM has a `_C.cpython-X.Y-*.so` for every CPython | ||
| covered by `requires-python`. Fails closed if a Python's `.so` is missing | ||
| from the wheel — i.e. the regression that surfaced in #41476/#41512. | ||
| """Assert the vendored DeepGEMM package has the TORCH_LIBRARY binding layout. | ||
|
|
||
| Run from a CI test job after vLLM is installed, e.g. the H100 deepgemm | ||
| kernel tests in .buildkite/test_areas/kernels.yaml. | ||
| Expects ``deep_gemm/_C.py`` plus a single ``_C_extension*.so`` (abi3) under | ||
| ``vllm.third_party.deep_gemm``. Run after vLLM is installed, e.g. the H100 | ||
| deepgemm kernel tests in .buildkite/test_areas/kernels.yaml. | ||
| """ | ||
|
|
||
| import importlib.util | ||
| import os | ||
| import sys | ||
| from pathlib import Path | ||
|
|
||
| import regex as re | ||
| import tomllib | ||
|
|
||
| SO_RE = re.compile(r"^_C\.cpython-(\d)(\d+)-") | ||
|
|
||
|
|
||
| def required_pythons() -> list[str]: | ||
| pyproject = Path(__file__).resolve().parent.parent / "pyproject.toml" | ||
| spec = tomllib.loads(pyproject.read_text())["project"]["requires-python"] | ||
| m = re.match(r">=3\.(\d+),<3\.(\d+)", spec) | ||
| if not m: | ||
| sys.exit(f"unexpected requires-python format: {spec!r}") | ||
| return [f"3.{v}" for v in range(int(m[1]), int(m[2]))] | ||
|
|
||
|
|
||
| spec = importlib.util.find_spec("vllm.third_party.deep_gemm") | ||
| if spec is None or spec.origin is None: | ||
| sys.exit("vllm.third_party.deep_gemm not importable; is vllm installed?") | ||
| pkg_dir = Path(spec.origin).parent | ||
|
|
||
| found = {f"{m[1]}.{m[2]}" for f in os.listdir(pkg_dir) if (m := SO_RE.match(f))} | ||
| required = required_pythons() | ||
| missing = [v for v in required if v not in found] | ||
| print(f"deepgemm _C: found {sorted(found)}, required {required}, missing {missing}") | ||
| sys.exit(1 if missing else 0) | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This file used to check each required python version in |
||
| def main() -> int: | ||
| spec = importlib.util.find_spec("vllm.third_party.deep_gemm") | ||
| if spec is None or spec.origin is None: | ||
| print( | ||
| "vllm.third_party.deep_gemm not importable; is vllm installed?", | ||
| file=sys.stderr, | ||
| ) | ||
| return 1 | ||
| pkg_dir = Path(spec.origin).parent | ||
|
|
||
| shim = pkg_dir / "_C.py" | ||
| so_files = sorted(pkg_dir.glob("_C_extension*.so")) | ||
| missing = [] | ||
| if not shim.is_file(): | ||
| missing.append("_C.py") | ||
| if not so_files: | ||
| missing.append("_C_extension*.so") | ||
|
|
||
| print( | ||
| f"deepgemm vendored binding: shim={shim.is_file()}, " | ||
| f"extensions={[p.name for p in so_files]}" | ||
| ) | ||
| if missing: | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Now, it checks |
||
| print(f"missing: {missing}", file=sys.stderr) | ||
| return 1 | ||
| return 0 | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| raise SystemExit(main()) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This used to link private CPython symbols, so each python needed it's own C.cpython-3XY-….so.
Now, it builds against the CPython Limited/Stable ABI (Py_LIMITED_API), so one _C_extension.abi3.so works across supported CPythons.