59a0a3844c
PR Test AMD / finish (push) Blocked by required conditions
PR Test NVIDIA ARM / finish (push) Blocked by required conditions
PR Test NVIDIA / finish (push) Blocked by required conditions
PR Test AMD / cancel-on-close (push) Has been skipped
PR Test NVIDIA ARM / scan (push) Has been skipped
PR Test NVIDIA / cancel-on-close (push) Has been skipped
PR Test AMD / scan (push) Has been skipped
PR Test AMD / ${{ matrix.name }} (${{ matrix.runner }}) (push) Waiting to run
PR Test NVIDIA ARM / cancel-on-close (push) Has been skipped
PR Test NVIDIA ARM / ${{ matrix.name }} (${{ matrix.runner }}) (push) Waiting to run
PR Test NVIDIA / scan (push) Has been skipped
Release Docker Images / build (cu129-torch-2.11.0) (push) Has been skipped
PR Test NVIDIA / ${{ matrix.name }} (${{ matrix.runner }}) (push) Waiting to run
Release Docker Images / build (cu130-torch-2.11.0) (push) Has been skipped
Release PyPI / publish (push) Has been skipped
Scheduler Python Test / test (push) Successful in 27m19s
Docs / build (push) Successful in 28m8s
Scheduler C++ Test / test (push) Successful in 28m19s
Scheduler C++ Test / test-flat (push) Successful in 28m18s
Docs / deploy (push) Has been cancelled
833 lines
26 KiB
Python
833 lines
26 KiB
Python
# Copyright (c) 2026 LightSeek Foundation
|
|
#
|
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
# of this software and associated documentation files (the "Software"), to deal
|
|
# in the Software without restriction, including without limitation the rights
|
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
# copies of the Software, and to permit persons to whom the Software is
|
|
# furnished to do so, subject to the following conditions:
|
|
#
|
|
# The above copyright notice and this permission notice shall be included in
|
|
# all copies or substantial portions of the Software.
|
|
#
|
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
# SOFTWARE.
|
|
|
|
"""
|
|
tokenspeed_kernel build script.
|
|
|
|
Compiles .cu files into shared libraries (.so) loaded via tvm_ffi.load_module().
|
|
On systems without an NVIDIA CUDA build target, the build is skipped and the
|
|
package installs as a pure-Python stub.
|
|
"""
|
|
|
|
import ctypes
|
|
import importlib
|
|
import os
|
|
import shutil
|
|
import site
|
|
import subprocess
|
|
import sys
|
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
from datetime import datetime, timezone
|
|
from pathlib import Path
|
|
|
|
from setuptools import Command, find_packages, setup
|
|
from setuptools.command.build_ext import build_ext
|
|
from setuptools.command.build_py import build_py
|
|
from setuptools.command.develop import develop
|
|
from setuptools.command.editable_wheel import editable_wheel
|
|
|
|
ROOT = Path(__file__).resolve().parent
|
|
REQUIREMENTS_DIR = ROOT / "requirements"
|
|
THIRDPARTY_DIR = ROOT / "tokenspeed_kernel" / "thirdparty"
|
|
BASE_VERSION = "0.1.0"
|
|
BACKEND_ENV = "TOKENSPEED_KERNEL_BACKEND"
|
|
VALID_BACKENDS = {"cuda", "rocm"}
|
|
DEFAULT_CUDA_ARCHS = ("100a", "103a")
|
|
|
|
# CUDA kernels source and output directories
|
|
CUDA_CSRC_DIR = THIRDPARTY_DIR / "cuda" / "csrc"
|
|
CUDA_OBJS_DIR = THIRDPARTY_DIR / "cuda" / "objs"
|
|
|
|
# JIT kernels source directory (no pre-compilation, just need sources available)
|
|
JIT_CSRC_DIR = THIRDPARTY_DIR / "jit_kernel" / "csrc"
|
|
|
|
CUDA_HOME = os.environ.get("CUDA_HOME", "/usr/local/cuda")
|
|
NVCC = os.environ.get("FLASHINFER_NVCC", f"{CUDA_HOME}/bin/nvcc")
|
|
CXX = os.environ.get("CXX", "g++")
|
|
|
|
|
|
def _version_date() -> str:
|
|
override = os.environ.get("TOKENSPEED_KERNEL_VERSION_DATE")
|
|
if override:
|
|
return override
|
|
|
|
source_date_epoch = os.environ.get("SOURCE_DATE_EPOCH")
|
|
if source_date_epoch:
|
|
return datetime.fromtimestamp(int(source_date_epoch), tz=timezone.utc).strftime(
|
|
"%Y%m%d"
|
|
)
|
|
|
|
return datetime.now(timezone.utc).strftime("%Y%m%d")
|
|
|
|
|
|
def _git_sha() -> str:
|
|
override = os.environ.get("TOKENSPEED_KERNEL_GIT_SHA") or os.environ.get(
|
|
"GIT_COMMIT"
|
|
)
|
|
if override:
|
|
return override[:8].ljust(8, "0")
|
|
|
|
try:
|
|
return (
|
|
subprocess.check_output(
|
|
["git", "rev-parse", "--short=8", "HEAD"],
|
|
cwd=ROOT,
|
|
stderr=subprocess.DEVNULL,
|
|
text=True,
|
|
)
|
|
.strip()[:8]
|
|
.ljust(8, "0")
|
|
)
|
|
except (OSError, subprocess.CalledProcessError):
|
|
return "00000000"
|
|
|
|
|
|
def _git_branch() -> str:
|
|
for env_name in (
|
|
"TOKENSPEED_KERNEL_GIT_BRANCH",
|
|
"GITHUB_REF_NAME",
|
|
):
|
|
branch = os.environ.get(env_name)
|
|
if branch:
|
|
return branch.removeprefix("refs/heads/")
|
|
|
|
github_ref = os.environ.get("GITHUB_REF")
|
|
if github_ref:
|
|
return github_ref.removeprefix("refs/heads/")
|
|
|
|
try:
|
|
return subprocess.check_output(
|
|
["git", "branch", "--show-current"],
|
|
cwd=ROOT,
|
|
stderr=subprocess.DEVNULL,
|
|
text=True,
|
|
).strip()
|
|
except (OSError, subprocess.CalledProcessError):
|
|
return ""
|
|
|
|
|
|
def _package_version() -> str:
|
|
if _git_branch().startswith("release/"):
|
|
return BASE_VERSION
|
|
|
|
return f"{BASE_VERSION}.dev{_version_date()}+git{_git_sha()}"
|
|
|
|
|
|
def _is_cuda_platform() -> bool:
|
|
def toolkit_available() -> bool:
|
|
if shutil.which(NVCC) is not None:
|
|
return True
|
|
cuda_home = Path(CUDA_HOME)
|
|
return (cuda_home / "bin" / "nvcc").exists()
|
|
|
|
for lib_name in ("libcuda.so.1", "libcuda.so"):
|
|
try:
|
|
libcuda = ctypes.CDLL(lib_name)
|
|
break
|
|
except OSError:
|
|
pass
|
|
else:
|
|
return toolkit_available()
|
|
|
|
try:
|
|
if libcuda.cuInit(0) != 0:
|
|
return toolkit_available()
|
|
count = ctypes.c_int()
|
|
if libcuda.cuDeviceGetCount(ctypes.byref(count)) != 0:
|
|
return toolkit_available()
|
|
if count.value > 0:
|
|
return True
|
|
except AttributeError:
|
|
pass
|
|
|
|
return toolkit_available()
|
|
|
|
|
|
def _is_rocm_platform() -> bool:
|
|
rocm_env_names = (
|
|
"ROCM_HOME",
|
|
"ROCM_PATH",
|
|
"ROCM_VERSION",
|
|
"HIP_PATH",
|
|
"HIP_PLATFORM",
|
|
)
|
|
if any(os.environ.get(name) for name in rocm_env_names):
|
|
return True
|
|
if shutil.which("hipcc") is not None:
|
|
return True
|
|
if Path("/dev/kfd").exists():
|
|
return True
|
|
return Path("/opt/rocm").exists()
|
|
|
|
|
|
def _selected_backend() -> str:
|
|
override = os.environ.get(BACKEND_ENV, "").strip().lower()
|
|
if override:
|
|
if override not in VALID_BACKENDS:
|
|
valid = ", ".join(sorted(VALID_BACKENDS))
|
|
raise RuntimeError(f"{BACKEND_ENV} must be one of: {valid}")
|
|
return override
|
|
|
|
if _is_cuda_platform():
|
|
return "cuda"
|
|
if _is_rocm_platform():
|
|
return "rocm"
|
|
|
|
raise RuntimeError(
|
|
"Unable to detect CUDA or ROCm for tokenspeed_kernel dependencies. "
|
|
f"Set {BACKEND_ENV}=cuda or {BACKEND_ENV}=rocm."
|
|
)
|
|
|
|
|
|
def _read_requirements(path: Path, seen=None) -> list[str]:
|
|
seen = seen or set()
|
|
path = path.resolve()
|
|
if path in seen:
|
|
return []
|
|
seen.add(path)
|
|
|
|
requirements = []
|
|
for raw_line in path.read_text(encoding="utf-8").splitlines():
|
|
line = raw_line.strip()
|
|
if not line or line.startswith("#"):
|
|
continue
|
|
if line.startswith("-r ") or line.startswith("--requirement "):
|
|
include = line.split(maxsplit=1)[1]
|
|
requirements.extend(_read_requirements(path.parent / include, seen))
|
|
continue
|
|
requirements.append(line)
|
|
return requirements
|
|
|
|
|
|
def _selected_install_requires() -> list[str]:
|
|
backend = _selected_backend()
|
|
requirements = []
|
|
requirements.extend(
|
|
_read_requirements(REQUIREMENTS_DIR / f"{backend}-thirdparty.txt")
|
|
)
|
|
|
|
deduped = []
|
|
seen = set()
|
|
for requirement in requirements:
|
|
if requirement not in seen:
|
|
deduped.append(requirement)
|
|
seen.add(requirement)
|
|
return deduped
|
|
|
|
|
|
def _pip_verbose_args(verbose) -> list[str]:
|
|
try:
|
|
level = int(verbose)
|
|
except (TypeError, ValueError):
|
|
level = 1 if verbose else 0
|
|
return ["-" + ("v" * min(level, 3))] if level > 0 else []
|
|
|
|
|
|
def _refresh_python_install_paths() -> None:
|
|
"""Expose packages installed by subprocess pip to this build process."""
|
|
candidates = []
|
|
for paths in (site.getsitepackages(), site.getusersitepackages()):
|
|
if isinstance(paths, str):
|
|
candidates.append(paths)
|
|
else:
|
|
candidates.extend(paths)
|
|
|
|
for path in candidates:
|
|
if path and Path(path).exists():
|
|
site.addsitedir(str(path))
|
|
|
|
importlib.invalidate_caches()
|
|
|
|
|
|
def _install_backend_build_requirements(verbose=False) -> None:
|
|
backend = _selected_backend()
|
|
print(f"Installing {backend} build requirements before native build")
|
|
subprocess.check_call(
|
|
[
|
|
sys.executable,
|
|
"-m",
|
|
"pip",
|
|
"install",
|
|
"-r",
|
|
str(REQUIREMENTS_DIR / f"{backend}.txt"),
|
|
"--no-build-isolation",
|
|
]
|
|
+ _pip_verbose_args(verbose)
|
|
)
|
|
|
|
# The same setup.py process imports build deps immediately after pip adds
|
|
# them. If pip created user site-packages during this run, that path was not
|
|
# present when Python started, so add site paths before resolving headers.
|
|
_refresh_python_install_paths()
|
|
|
|
|
|
def _ensure_cuda_compiler() -> None:
|
|
if shutil.which(NVCC) is None:
|
|
raise RuntimeError(f"CUDA backend selected but nvcc was not found: {NVCC}")
|
|
|
|
|
|
# Kernel groups: each entry produces one .so file.
|
|
# Format: (name, [source_files], extra_ldflags) or
|
|
# (name, [source_files], extra_ldflags, extra_cflags)
|
|
# The 4-tuple form lets a kernel append nvcc flags on top of the global set —
|
|
# e.g., fused_topk_topp needs ``--expt-extended-lambda`` for CUB lambdas.
|
|
KERNEL_GROUPS = [
|
|
(
|
|
"rope",
|
|
[
|
|
CUDA_CSRC_DIR / "rope.cu",
|
|
CUDA_CSRC_DIR / "flashinfer_rope_binding.cu",
|
|
],
|
|
[],
|
|
),
|
|
(
|
|
"deepseek_v4_attention",
|
|
[
|
|
CUDA_CSRC_DIR / "deepseek_v4_attention.cu",
|
|
CUDA_CSRC_DIR / "deepseek_v4_topk.cu",
|
|
CUDA_CSRC_DIR / "deepseek_v4_attention_binding.cu",
|
|
],
|
|
[],
|
|
),
|
|
(
|
|
"dsv3_gemm",
|
|
[
|
|
CUDA_CSRC_DIR / "dsv3_router_gemm_float_out.cu",
|
|
CUDA_CSRC_DIR / "dsv3_router_gemm.cu",
|
|
CUDA_CSRC_DIR / "dsv3_router_gemm_binding.cu",
|
|
],
|
|
["-lcublas", "-lcublasLt"],
|
|
),
|
|
(
|
|
"fp32_router_gemm",
|
|
[
|
|
CUDA_CSRC_DIR / "fp32_router_gemm.cu",
|
|
CUDA_CSRC_DIR / "fp32_router_gemm_entry.cu",
|
|
CUDA_CSRC_DIR / "fp32_router_gemm_binding.cu",
|
|
],
|
|
["-lcublas", "-lcublasLt"],
|
|
),
|
|
(
|
|
"marlin",
|
|
[
|
|
CUDA_CSRC_DIR / "gptq_marlin_repack.cu",
|
|
CUDA_CSRC_DIR / "flashinfer_marlin_binding.cu",
|
|
],
|
|
[],
|
|
),
|
|
(
|
|
"routing",
|
|
[
|
|
CUDA_CSRC_DIR / "routing_flash.cu",
|
|
],
|
|
[],
|
|
),
|
|
(
|
|
"sampling_chain",
|
|
[
|
|
CUDA_CSRC_DIR / "sampling_chain.cu",
|
|
CUDA_CSRC_DIR / "flashinfer_sampling_chain_binding.cu",
|
|
],
|
|
[],
|
|
),
|
|
(
|
|
"fused_topk_topp",
|
|
[
|
|
CUDA_CSRC_DIR / "fused_topk_topp" / "fused_topk_topp.cu",
|
|
CUDA_CSRC_DIR / "fused_topk_topp" / "fused_topk_topp_binding.cu",
|
|
],
|
|
[],
|
|
# --expt-extended-lambda is required by air_topk_stable.cuh's CUB usage.
|
|
["--expt-extended-lambda"],
|
|
),
|
|
(
|
|
"rmsnorm_fused_parallel",
|
|
[
|
|
CUDA_CSRC_DIR / "rmsnorm_fused_parallel.cu",
|
|
CUDA_CSRC_DIR / "flashinfer_rmsnorm_fused_parallel_binding.cu",
|
|
],
|
|
[],
|
|
),
|
|
(
|
|
"merge_state",
|
|
[
|
|
CUDA_CSRC_DIR / "merge_state.cu",
|
|
],
|
|
[],
|
|
),
|
|
(
|
|
"flashinfer_softmax",
|
|
[
|
|
CUDA_CSRC_DIR / "flashinfer_softmax.cu",
|
|
],
|
|
[],
|
|
),
|
|
(
|
|
"silu_fuse_block_quant",
|
|
[
|
|
CUDA_CSRC_DIR / "silu_and_mul_fuse_block_quant.cu",
|
|
CUDA_CSRC_DIR / "silu_and_mul_fuse_block_quant_ep.cu",
|
|
],
|
|
[],
|
|
),
|
|
(
|
|
"silu_fuse_nvfp4_quant",
|
|
[
|
|
CUDA_CSRC_DIR / "silu_and_mul_fuse_nvfp4_quant.cu",
|
|
],
|
|
[],
|
|
),
|
|
(
|
|
"moe_finalize_fuse_shared",
|
|
[
|
|
CUDA_CSRC_DIR / "moe_finalize_fuse_shared.cu",
|
|
],
|
|
[],
|
|
),
|
|
(
|
|
"kvcacheio",
|
|
[
|
|
CUDA_CSRC_DIR / "kvcacheio_transfer.cu",
|
|
CUDA_CSRC_DIR / "flashinfer_kvcacheio_binding.cu",
|
|
],
|
|
[],
|
|
),
|
|
(
|
|
"lm_head_gemm",
|
|
[
|
|
CUDA_CSRC_DIR / "lm_head_gemm.cu",
|
|
CUDA_CSRC_DIR / "lm_head_gemm_binding.cu",
|
|
],
|
|
[],
|
|
),
|
|
(
|
|
"trtllm_comm",
|
|
[
|
|
CUDA_CSRC_DIR / "trtllm_allreduce.cu",
|
|
CUDA_CSRC_DIR / "trtllm_allreduce_fusion.cu",
|
|
CUDA_CSRC_DIR / "trtllm_reducescatter_fusion.cu",
|
|
CUDA_CSRC_DIR / "trtllm_allgather_fusion.cu",
|
|
CUDA_CSRC_DIR / "minimax_reduce_rms.cu",
|
|
],
|
|
[],
|
|
),
|
|
]
|
|
|
|
|
|
class CudaKernelBuilder:
|
|
def __init__(self, kernel_groups, verbose: bool):
|
|
self.kernel_groups = kernel_groups
|
|
self.verbose = verbose
|
|
|
|
# Target GPU architectures: detect from the CUDA driver or use env var override.
|
|
# FLASHINFER_CUDA_ARCH_LIST is accepted for compatibility, but TokenSpeed
|
|
# docs prefer TOKENSPEED_CUDA_ARCH=100 on GB200.
|
|
def _normalize_cuda_arch(self, arch):
|
|
has_suffix = arch.endswith("a")
|
|
arch_clean = arch.rstrip("a")
|
|
if "." in arch_clean:
|
|
major_s, minor_s = arch_clean.split(".", 1)
|
|
major = int(major_s)
|
|
minor = int(minor_s)
|
|
else:
|
|
major = int(arch_clean[:-1])
|
|
minor = int(arch_clean[-1])
|
|
suffix = "a" if has_suffix or major >= 9 else ""
|
|
return f"{major}{minor}{suffix}"
|
|
|
|
def _detect_cuda_archs(self):
|
|
archs = set()
|
|
|
|
arch_list = os.environ.get("FLASHINFER_CUDA_ARCH_LIST", "")
|
|
if arch_list:
|
|
for arch in arch_list.split():
|
|
archs.add(self._normalize_cuda_arch(arch))
|
|
return archs
|
|
|
|
direct = os.environ.get("TOKENSPEED_CUDA_ARCH", "")
|
|
if direct:
|
|
archs.add(self._normalize_cuda_arch(direct))
|
|
return archs
|
|
|
|
if not archs:
|
|
archs.update(DEFAULT_CUDA_ARCHS)
|
|
return archs
|
|
|
|
def _site_paths(self):
|
|
paths = []
|
|
try:
|
|
paths.extend(site.getsitepackages())
|
|
except Exception:
|
|
pass
|
|
paths.extend(sys.path)
|
|
|
|
seen = set()
|
|
for raw_path in paths:
|
|
if not raw_path:
|
|
continue
|
|
path = Path(raw_path).expanduser()
|
|
path_str = str(path)
|
|
if path.exists() and path_str not in seen:
|
|
seen.add(path_str)
|
|
yield path
|
|
|
|
def _cuda_toolkit_roots(self):
|
|
roots = [Path(CUDA_HOME)]
|
|
|
|
seen = set()
|
|
for root in roots:
|
|
root_str = str(root)
|
|
if root.exists() and root_str not in seen:
|
|
seen.add(root_str)
|
|
yield root
|
|
|
|
def _resolve_include_dirs(self):
|
|
dirs = [str(CUDA_CSRC_DIR / "include"), str(CUDA_CSRC_DIR)]
|
|
seen = set(dirs)
|
|
|
|
def _add_dir(path: Path) -> None:
|
|
path_str = str(path)
|
|
if path.exists() and path_str not in seen:
|
|
dirs.append(path_str)
|
|
seen.add(path_str)
|
|
|
|
def _is_complete_cuda_include(path: Path) -> bool:
|
|
return all(
|
|
(path / header).exists() for header in ("cuda_runtime.h", "cublas_v2.h")
|
|
)
|
|
|
|
found_toolkit_headers = False
|
|
for cuda_root in self._cuda_toolkit_roots():
|
|
cuda_include = cuda_root / "include"
|
|
if not _is_complete_cuda_include(cuda_include):
|
|
continue
|
|
_add_dir(cuda_include)
|
|
if (cuda_include / "cccl").exists():
|
|
_add_dir(cuda_include / "cccl")
|
|
found_toolkit_headers = True
|
|
break
|
|
|
|
# Do not mix wheel CUDA headers with an available toolkit.
|
|
if not found_toolkit_headers:
|
|
found_wheel_headers = False
|
|
for base_path in self._site_paths():
|
|
for candidate in sorted(
|
|
base_path.glob("nvidia/cu*/include"), reverse=True
|
|
):
|
|
if not _is_complete_cuda_include(candidate):
|
|
continue
|
|
_add_dir(candidate)
|
|
if (candidate / "cccl").exists():
|
|
_add_dir(candidate / "cccl")
|
|
found_wheel_headers = True
|
|
break
|
|
if found_wheel_headers:
|
|
break
|
|
|
|
try:
|
|
tvm_ffi = importlib.import_module("tvm_ffi")
|
|
_add_dir(Path(tvm_ffi.__file__).parent / "include")
|
|
except ImportError:
|
|
pass
|
|
|
|
# flashinfer bundles TRT-LLM internal FP4 helpers
|
|
# (tensorrt_llm/kernels/quantization_utils.cuh: cvt_warp_fp16_to_fp4,
|
|
# silu_and_mul, cvt_quant_to_fp4_get_sf_out_offset). Expose them so
|
|
# our own fused silu+mul+nvfp4 kernel can reuse them.
|
|
try:
|
|
flashinfer = importlib.import_module("flashinfer")
|
|
fi_root = Path(flashinfer.__file__).parent / "data"
|
|
for sub in (
|
|
fi_root / "csrc" / "nv_internal",
|
|
fi_root / "csrc" / "nv_internal" / "include",
|
|
fi_root / "include",
|
|
fi_root / "cutlass" / "include",
|
|
):
|
|
_add_dir(sub)
|
|
spdlog = fi_root / "spdlog" / "include"
|
|
if (spdlog / "spdlog" / "spdlog.h").exists():
|
|
_add_dir(spdlog)
|
|
return dirs
|
|
except ImportError:
|
|
pass
|
|
if (Path("/usr/include") / "spdlog" / "spdlog.h").exists():
|
|
_add_dir(Path("/usr/include"))
|
|
|
|
return dirs
|
|
|
|
def _resolve_cuda_lib_flags(self):
|
|
cuda_home = Path(CUDA_HOME)
|
|
lib_candidates = []
|
|
for cuda_root in self._cuda_toolkit_roots():
|
|
lib_candidates.extend([cuda_root / "lib64", cuda_root / "lib"])
|
|
for base in self._site_paths():
|
|
lib_candidates.extend(
|
|
sorted(Path(base).glob("nvidia/cu*/lib"), reverse=True)
|
|
)
|
|
|
|
seen_lib_dirs = set()
|
|
unique_lib_candidates = []
|
|
for candidate in lib_candidates:
|
|
candidate_str = str(candidate)
|
|
if candidate.exists() and candidate_str not in seen_lib_dirs:
|
|
unique_lib_candidates.append(candidate)
|
|
seen_lib_dirs.add(candidate_str)
|
|
lib_candidates = unique_lib_candidates
|
|
self._cuda_library_dirs = lib_candidates
|
|
|
|
cuda_lib_dir = lib_candidates[0] if lib_candidates else cuda_home / "lib64"
|
|
for candidate in lib_candidates:
|
|
if (candidate / "libcudart.so").exists() or list(
|
|
candidate.glob("libcudart.so.*")
|
|
):
|
|
cuda_lib_dir = candidate
|
|
break
|
|
|
|
flags = [f"-L{lib_dir}" for lib_dir in lib_candidates] or [f"-L{cuda_lib_dir}"]
|
|
cuda_stubs_dir = cuda_lib_dir / "stubs"
|
|
if cuda_stubs_dir.exists():
|
|
flags.append(f"-L{cuda_stubs_dir}")
|
|
|
|
cudart_so = cuda_lib_dir / "libcudart.so"
|
|
cudart_versioned = sorted(cuda_lib_dir.glob("libcudart.so.*"))
|
|
if cudart_so.exists():
|
|
flags.append("-lcudart")
|
|
elif cudart_versioned:
|
|
flags.append(f"-l:{cudart_versioned[-1].name}")
|
|
else:
|
|
flags.append("-lcudart")
|
|
|
|
flags.append("-lcuda")
|
|
return flags
|
|
|
|
def _resolve_library_ldflag(self, ldflag):
|
|
if not ldflag.startswith("-l") or ldflag.startswith("-l:"):
|
|
return ldflag
|
|
|
|
lib_name = ldflag[2:]
|
|
for lib_dir in getattr(self, "_cuda_library_dirs", []):
|
|
if (lib_dir / f"lib{lib_name}.so").exists():
|
|
return ldflag
|
|
versioned = sorted(lib_dir.glob(f"lib{lib_name}.so.*"))
|
|
if versioned:
|
|
return f"-l:{versioned[-1].name}"
|
|
return ldflag
|
|
|
|
def _prepare_cuda_toolchain_env(self):
|
|
path = os.environ.get("PATH", "")
|
|
path_entries = [entry for entry in path.split(os.pathsep) if entry]
|
|
candidates = [Path(NVCC).resolve().parent]
|
|
|
|
for cuda_root in self._cuda_toolkit_roots():
|
|
candidates.append(cuda_root / "bin")
|
|
candidates.append(cuda_root / "nvvm" / "bin")
|
|
|
|
for base in self._site_paths():
|
|
for cuda_root in sorted(Path(base).glob("nvidia/cu*"), reverse=True):
|
|
candidates.append(cuda_root / "bin")
|
|
candidates.append(cuda_root / "nvvm" / "bin")
|
|
|
|
for candidate in reversed(candidates):
|
|
candidate_str = str(candidate)
|
|
if candidate.exists() and candidate_str not in path_entries:
|
|
path_entries.insert(0, candidate_str)
|
|
if path_entries:
|
|
os.environ["PATH"] = os.pathsep.join(path_entries)
|
|
|
|
def _compile_one(self, src, obj, nvcc_flags, include_dirs, extra_cflags=()):
|
|
include_flags = [f"-I{d}" for d in include_dirs]
|
|
cmd = (
|
|
[NVCC]
|
|
+ nvcc_flags
|
|
+ list(extra_cflags)
|
|
+ include_flags
|
|
+ ["-c", str(src), "-o", str(obj)]
|
|
)
|
|
subprocess.check_call(cmd)
|
|
return obj
|
|
|
|
def run(self):
|
|
self._prepare_cuda_toolchain_env()
|
|
max_jobs = int(os.environ.get("MAX_JOBS", min(os.cpu_count() or 1, 16)))
|
|
total_sources = sum(len(entry[1]) for entry in self.kernel_groups)
|
|
|
|
archs = self._detect_cuda_archs()
|
|
gencode_flags = [
|
|
f"-gencode=arch=compute_{a},code=sm_{a}" for a in sorted(archs)
|
|
]
|
|
nvcc_flags = [
|
|
"-std=c++17",
|
|
"-O3",
|
|
"-DNDEBUG",
|
|
"-use_fast_math",
|
|
"--expt-relaxed-constexpr",
|
|
"--compiler-options=-fPIC",
|
|
"-DFLASHINFER_ENABLE_BF16",
|
|
"-DFLASHINFER_ENABLE_F16",
|
|
"-DENABLE_BF16",
|
|
"-DENABLE_FP8",
|
|
] + gencode_flags
|
|
include_dirs = self._resolve_include_dirs()
|
|
ldflags = ["-shared"] + self._resolve_cuda_lib_flags()
|
|
|
|
# Ensure output directory exists
|
|
CUDA_OBJS_DIR.mkdir(parents=True, exist_ok=True)
|
|
|
|
stale_groups = []
|
|
skipped_groups = 0
|
|
for entry in self.kernel_groups:
|
|
name, sources, extra_ldflags = entry[0], entry[1], entry[2]
|
|
extra_cflags = entry[3] if len(entry) > 3 else []
|
|
out_dir = CUDA_OBJS_DIR / name
|
|
out_dir.mkdir(parents=True, exist_ok=True)
|
|
so_path = out_dir / f"{name}.so"
|
|
if so_path.exists() and all(
|
|
so_path.stat().st_mtime > src.stat().st_mtime for src in sources
|
|
):
|
|
skipped_groups += 1
|
|
continue
|
|
stale_groups.append((name, sources, extra_ldflags, extra_cflags, so_path))
|
|
|
|
stale_sources = sum(len(srcs) for _, srcs, _, _, _ in stale_groups)
|
|
print(
|
|
f"Building {len(stale_groups)}/{len(self.kernel_groups)} kernel group(s) "
|
|
f"({stale_sources}/{total_sources} files, {max_jobs} parallel jobs)..."
|
|
)
|
|
if skipped_groups and self.verbose:
|
|
print(f"Skipped {skipped_groups} up-to-date kernel group(s)")
|
|
|
|
if not stale_groups:
|
|
return
|
|
|
|
with ThreadPoolExecutor(max_workers=max_jobs) as executor:
|
|
group_meta = []
|
|
futures = []
|
|
for name, sources, extra_ldflags, extra_cflags, so_path in stale_groups:
|
|
out_dir = so_path.parent
|
|
objects = []
|
|
for src in sources:
|
|
obj = out_dir / (src.stem + ".o")
|
|
objects.append(obj)
|
|
futures.append(
|
|
executor.submit(
|
|
self._compile_one,
|
|
str(src),
|
|
str(obj),
|
|
nvcc_flags,
|
|
include_dirs,
|
|
extra_cflags,
|
|
)
|
|
)
|
|
group_meta.append((name, objects, extra_ldflags, so_path))
|
|
|
|
for future in as_completed(futures):
|
|
future.result()
|
|
|
|
for name, objects, extra_ldflags, so_path in group_meta:
|
|
extra_ldflags = [
|
|
self._resolve_library_ldflag(ldflag) for ldflag in (extra_ldflags or [])
|
|
]
|
|
link_cmd = (
|
|
[CXX]
|
|
+ [str(o) for o in objects]
|
|
+ ldflags
|
|
+ extra_ldflags
|
|
+ ["-o", str(so_path)]
|
|
)
|
|
subprocess.check_call(link_cmd)
|
|
|
|
|
|
class BuildKernels(build_ext):
|
|
"""Compile CUDA kernels into .so files for the CUDA backend."""
|
|
|
|
def run(self):
|
|
if _selected_backend() != "cuda":
|
|
print(
|
|
f"CUDA backend not selected; skipping CUDA kernel build. "
|
|
f"{self.distribution.get_name()}"
|
|
)
|
|
return
|
|
|
|
_ensure_cuda_compiler()
|
|
verbose = bool(getattr(self, "verbose", False))
|
|
CudaKernelBuilder(KERNEL_GROUPS, verbose=verbose).run()
|
|
|
|
|
|
class BuildNative(Command):
|
|
description = "Build CUDA kernels"
|
|
user_options = []
|
|
|
|
def initialize_options(self):
|
|
pass
|
|
|
|
def finalize_options(self):
|
|
pass
|
|
|
|
def run(self):
|
|
backend = _selected_backend()
|
|
_install_backend_build_requirements(getattr(self, "verbose", False))
|
|
if backend != "cuda":
|
|
print("CUDA backend not selected; skipping CUDA kernel build")
|
|
return
|
|
|
|
self.run_command("build_ext")
|
|
|
|
|
|
class EditableWheelWithBuild(editable_wheel):
|
|
"""Ensure kernels are built during `pip install -e .` (PEP 660)."""
|
|
|
|
def run(self):
|
|
self.run_command("build_native")
|
|
super().run()
|
|
|
|
|
|
class DevelopWithBuild(develop):
|
|
"""Ensure kernels are built during `setup.py develop`."""
|
|
|
|
def run(self):
|
|
self.run_command("build_native")
|
|
super().run()
|
|
|
|
|
|
class BuildPyWithBuild(build_py):
|
|
"""Ensure kernels and vendored deps are built for regular installs."""
|
|
|
|
def run(self):
|
|
self.run_command("build_native")
|
|
super().run()
|
|
|
|
|
|
setup(
|
|
name="tokenspeed_kernel",
|
|
version=_package_version(),
|
|
install_requires=_selected_install_requires(),
|
|
packages=find_packages(),
|
|
package_data={
|
|
"tokenspeed_kernel.thirdparty.cuda": ["objs/**/*.so"],
|
|
},
|
|
cmdclass={
|
|
"build_native": BuildNative,
|
|
"build_ext": BuildKernels,
|
|
"build_py": BuildPyWithBuild,
|
|
"editable_wheel": EditableWheelWithBuild,
|
|
"develop": DevelopWithBuild,
|
|
},
|
|
)
|