- PagedAttentionParams uses flat KV pool + req_to_token + kv_indptr/qo_indptr instead of page_table - MMA split-KV decode and split-Q prefill kernels with indirect ragged-batch addressing - Prefill kernel accepts 4D mask (causal-aware); decode kernel supports 2D mask - CudaBackend is inference-only: kv_cache=None raises, no torch fallback - benchmark.py: required --ckpt, --backend/--compare options - Parallel build isolates build-temp/build-lib per subprocess - Standalone test covers decode/prefill with mask, 27 cases pass
153 lines
5.5 KiB
Python
153 lines
5.5 KiB
Python
import os
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import warnings
|
|
from pathlib import Path
|
|
|
|
from setuptools import setup
|
|
from setuptools.command.build_ext import build_ext as _build_ext
|
|
|
|
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 shutil
|
|
|
|
import torch
|
|
|
|
return shutil.which("nvcc") is not None and torch.cuda.is_available()
|
|
except Exception:
|
|
return False
|
|
|
|
|
|
ext_modules = []
|
|
cmdclass = {}
|
|
|
|
if _should_build():
|
|
import torch
|
|
from torch.utils.cpp_extension import BuildExtension, CUDAExtension
|
|
|
|
from csrc.build import REGISTRY, cuda_toolkit_version
|
|
|
|
# Preflight: warn if nvcc major version != torch's bundled CUDA major version.
|
|
# A mismatch (e.g. nvcc 13.0 + cu128 torch) causes cryptic ABI/header errors.
|
|
nvcc_ver = cuda_toolkit_version()
|
|
torch_cuda = torch.version.cuda
|
|
if nvcc_ver is not None and torch_cuda is not None:
|
|
torch_major = int(torch_cuda.split(".")[0])
|
|
if nvcc_ver[0] != torch_major:
|
|
warnings.warn(
|
|
f"CUDA version mismatch: nvcc is {nvcc_ver[0]}.{nvcc_ver[1]} "
|
|
f"but torch was built with CUDA {torch_cuda}. "
|
|
f"This may cause compilation errors. "
|
|
f"Install a matching torch wheel: "
|
|
f"pip install torch --index-url "
|
|
f"https://download.pytorch.org/whl/cu{nvcc_ver[0]}{nvcc_ver[1]}",
|
|
stacklevel=2,
|
|
)
|
|
|
|
_torch_lib = torch.utils.cpp_extension.library_paths()[0]
|
|
|
|
for name, info in REGISTRY.items():
|
|
ext_modules.append(
|
|
CUDAExtension(
|
|
f"astrai.extension.lib.{name}",
|
|
info["sources"],
|
|
extra_compile_args={
|
|
"cxx": info["cxx_flags"],
|
|
"nvcc": info["nvcc_flags"],
|
|
},
|
|
extra_link_args=[f"-Wl,-rpath,{_torch_lib}"],
|
|
)
|
|
)
|
|
|
|
# Parallel build — each extension is an independent ninja project, so we
|
|
# can compile them concurrently. BuildExtension compiles them serially by
|
|
# default; this subclass dispatches each extension to a subprocess.
|
|
# Set BUILD_PARALLEL=N to override (default: min(n_exts, 4)).
|
|
_single_ext = os.environ.get("ASTRAI_BUILD_SINGLE_EXT", "")
|
|
|
|
class ParallelBuildExtension(BuildExtension):
|
|
def build_extensions(self):
|
|
if _single_ext:
|
|
self.extensions = [e for e in self.extensions if e.name == _single_ext]
|
|
if not self.extensions:
|
|
return
|
|
super().build_extensions()
|
|
return
|
|
|
|
n = len(self.extensions)
|
|
max_workers = int(os.environ.get("BUILD_PARALLEL", 8))
|
|
if max_workers <= 1 or n <= 1:
|
|
super().build_extensions()
|
|
return
|
|
|
|
# Each subprocess gets its own build-temp / build-lib so the
|
|
# ninja files (build.ninja, .ninja_log) never race. The built
|
|
# .so files are then collected into the parent's build_lib so the
|
|
# normal setuptools copy steps (inplace / editable wheel) work.
|
|
names = [e.name for e in self.extensions]
|
|
env = {**os.environ, "BUILD_PARALLEL": "1"}
|
|
base = os.path.join("build", "parallel")
|
|
os.makedirs(base, exist_ok=True)
|
|
procs = {}
|
|
for i in range(0, len(names), max_workers):
|
|
batch = names[i : i + max_workers]
|
|
for name in batch:
|
|
e = {**env, "ASTRAI_BUILD_SINGLE_EXT": name}
|
|
tag = name.replace(".", "_")
|
|
subdir = os.path.join(base, tag)
|
|
cmd = [
|
|
sys.executable,
|
|
__file__,
|
|
"build_ext",
|
|
"--build-temp",
|
|
os.path.join(subdir, "temp"),
|
|
"--build-lib",
|
|
os.path.join(subdir, "lib"),
|
|
]
|
|
procs[name] = subprocess.Popen(
|
|
cmd, env=e, stdout=subprocess.PIPE, stderr=subprocess.STDOUT
|
|
)
|
|
for name in batch:
|
|
out, _ = procs[name].communicate()
|
|
if procs[name].returncode != 0:
|
|
sys.stdout.write(out.decode())
|
|
raise RuntimeError(
|
|
f"parallel build failed for {name} "
|
|
f"(exit {procs[name].returncode})"
|
|
)
|
|
self._collect_extensions(
|
|
os.path.join(base, name.replace(".", "_"), "lib")
|
|
)
|
|
|
|
def _collect_extensions(self, sub_lib):
|
|
src = os.path.join(sub_lib, "astrai", "extension", "lib")
|
|
if not os.path.isdir(src):
|
|
return
|
|
dst = os.path.join(self.build_lib, "astrai", "extension", "lib")
|
|
os.makedirs(dst, exist_ok=True)
|
|
for f in os.listdir(src):
|
|
if f.endswith(".so"):
|
|
shutil.copy2(os.path.join(src, f), os.path.join(dst, f))
|
|
|
|
cmdclass["build_ext"] = ParallelBuildExtension
|
|
|
|
if not cmdclass:
|
|
|
|
class _NullBuildExt(_build_ext):
|
|
def build_extensions(self):
|
|
pass
|
|
|
|
cmdclass["build_ext"] = _NullBuildExt
|
|
|
|
setup(ext_modules=ext_modules, cmdclass=cmdclass)
|