Files
AstrAI/setup.py
T

215 lines
5.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import os
import shutil
import subprocess
import sys
import warnings
from pathlib import Path
from setuptools import setup
from setuptools.command.build import build as _build
from setuptools.command.build_ext import build_ext as _build_ext
from setuptools.command.editable_wheel import editable_wheel as _editable_wheel
sys.path.insert(0, str(Path(__file__).parent))
os.makedirs("astrai/extension/lib", exist_ok=True)
def _should_build():
force = os.environ.get("CSRC_KERNELS", "").strip().lower()
if force == "true":
return True
if force == "false":
return False
try:
import torch
return shutil.which("nvcc") is not None and torch.cuda.is_available()
except Exception:
return False
def _torch_prefix():
"""Return the torch install dir (site-packages/torch) used for headers/libs."""
try:
import torch
return str(Path(torch.__file__).parent.resolve())
except Exception:
return os.environ.get("TORCH_HOME", "")
def _python_include():
import sysconfig
return sysconfig.get_path("include")
def _python_soabi():
import sysconfig
ext = sysconfig.get_config_var("EXT_SUFFIX").lstrip(".")
return ext[: -len(".so")]
class _CMakeBuildExt(_build_ext):
def run(self):
src = Path(__file__).parent
build_dir = src / "build" / "cmake"
torch_home = _torch_prefix()
if not torch_home:
raise RuntimeError(
"torch not found; cannot build kernels. "
"Activate the environment or set TORCH_HOME."
)
nvcc_ver = _cuda_toolkit_version()
torch_cuda = _torch_cuda_version()
if (
nvcc_ver is not None
and torch_cuda is not None
and nvcc_ver[0] != int(torch_cuda.split(".")[0])
):
warnings.warn(
f"CUDA version mismatch: nvcc is {nvcc_ver[0]}.{nvcc_ver[1]} "
f"but torch was built with CUDA {torch_cuda}. "
f"Install a matching torch wheel.",
stacklevel=2,
)
cmake = shutil.which("cmake")
if cmake is None:
raise RuntimeError("cmake not found on PATH; install it to build kernels")
parallel = os.environ.get("BUILD_PARALLEL", "4")
cfg = [
cmake,
"-S",
str(src / "csrc"),
"-B",
str(build_dir),
f"-DTORCH_HOME={torch_home}",
f"-DPYTHON_INCLUDE_DIR={_python_include()}",
f"-DPY_SOABI={_python_soabi()}",
]
arch = os.environ.get("ASTRAI_CUDA_ARCH")
if not arch:
arch = _detect_cuda_arch()
if arch:
try:
if int(str(arch)) < 89:
warnings.warn(
f"FP8 operator disabled: CUDA compute capability {arch} "
"requires 89 or newer.",
stacklevel=2,
)
except ValueError:
warnings.warn(
f"Could not parse ASTRAI_CUDA_ARCH={arch!r}; "
"FP8 capability will be decided by CMake.",
stacklevel=2,
)
cfg.append(f"-DASTRAI_CUDA_ARCH={arch}")
subprocess.run(cfg, check=True)
subprocess.run([cmake, "--build", str(build_dir), "-j", parallel], check=True)
# After compilation finishes, verify mandatory CUDA kernels to confirm build succeeded.
# CMake may report partialtarget success even if some architecturespecific kernels are skipped.
# Prevent editable install from reporting success when critical kernel shared objects are missing.
lib_dir = src / "astrai" / "extension" / "lib"
required = (
"attn_decode",
"attn_prefill",
"attn_paged_decode",
"attn_paged_prefill",
"rotary_emb",
)
missing = [name for name in required if not any(lib_dir.glob(f"{name}.*.so"))]
if missing:
raise RuntimeError(
"CUDA build completed without some required kernel modules!"
)
def _cuda_toolkit_version():
import shutil
import subprocess
nvcc = shutil.which("nvcc")
if nvcc is None:
return None
try:
out = subprocess.check_output(
[nvcc, "--version"], stderr=subprocess.STDOUT, text=True
)
for line in out.splitlines():
if "release" in line:
ver = line.split("release")[1].split(",")[0].strip()
return tuple(int(x) for x in ver.split("."))
except Exception:
pass
return None
def _detect_cuda_arch():
"""Detect real GPU compute capability via torch (nvidia-smi may be spoofed).
Returns something like ``"89"`` or ``"103"``, or ``None`` if unavailable.
"""
try:
import torch
if torch.cuda.is_available():
major, minor = torch.cuda.get_device_capability()
return f"{major}{minor}"
except Exception:
pass
return None
def _torch_cuda_version():
try:
import torch
return torch.version.cuda
except Exception:
return None
class _NullBuildExt(_build_ext):
def build_extensions(self):
pass
class _Build(_build):
"""Run the CMake kernel build as part of setuptools' build lifecycle."""
def run(self):
if _should_build():
self.run_command("build_ext")
super().run()
class _EditableWheel(_editable_wheel):
"""Run the CMake kernel build for PEP 660 editable installations."""
def run(self):
if _should_build():
self.run_command("build_ext")
super().run()
cmdclass = {}
if _should_build():
cmdclass["build_ext"] = _CMakeBuildExt
else:
cmdclass["build_ext"] = _NullBuildExt
cmdclass["build"] = _Build
cmdclass["editable_wheel"] = _EditableWheel
setup(
ext_modules=[],
cmdclass=cmdclass,
)