setup.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. import io
  2. import os
  3. import re
  4. import subprocess
  5. from typing import List, Set
  6. import sys
  7. from typing import List
  8. from packaging.version import parse, Version
  9. from setuptools import setup, find_packages, Extension
  10. from setuptools.command.build_ext import build_ext
  11. from shutil import which
  12. import torch
  13. from torch.utils.cpp_extension import CUDA_HOME
  14. ROOT_DIR = os.path.dirname(__file__)
  15. MAIN_CUDA_VERSION = "12.1"
  16. def is_sccache_available() -> bool:
  17. return which("sccache") is not None
  18. def is_ccache_available() -> bool:
  19. return which("ccache") is not None
  20. def is_ninja_available() -> bool:
  21. return which("ninja") is not None
  22. def remove_prefix(text, prefix):
  23. if text.startswith(prefix):
  24. return text[len(prefix):]
  25. return text
  26. class CMakeExtension(Extension):
  27. def __init__(self, name: str, cmake_lists_dir: str = '.', **kwa) -> None:
  28. super().__init__(name, sources=[], **kwa)
  29. self.cmake_lists_dir = os.path.abspath(cmake_lists_dir)
  30. class cmake_build_ext(build_ext):
  31. # A dict of extension directories that have been configured.
  32. did_config = {}
  33. # Determine number of compilation jobs and optionally nvcc compile threads.
  34. def compute_num_jobs(self):
  35. try:
  36. # os.sched_getaffinity() isn't universally available, so fall back
  37. # to os.cpu_count() if we get an error here.
  38. num_jobs = len(os.sched_getaffinity(0))
  39. except AttributeError:
  40. num_jobs = os.cpu_count()
  41. nvcc_cuda_version = get_nvcc_cuda_version()
  42. if nvcc_cuda_version >= Version("11.2"):
  43. nvcc_threads = int(os.getenv("NVCC_THREADS", 8))
  44. num_jobs = max(1, round(num_jobs / (nvcc_threads / 4)))
  45. else:
  46. nvcc_threads = None
  47. return num_jobs, nvcc_threads
  48. # Perform cmake configuration for a single extension.
  49. def configure(self, ext: CMakeExtension) -> None:
  50. # If we've already configured using the CMakeLists.txt for
  51. # this extension, exit early.
  52. if ext.cmake_lists_dir in cmake_build_ext.did_config:
  53. return
  54. cmake_build_ext.did_config[ext.cmake_lists_dir] = True
  55. # Select the build type.
  56. # Note: optimization level + debug info are set by the build type
  57. default_cfg = "Debug" if self.debug else "RelWithDebInfo"
  58. cfg = os.getenv("CMAKE_BUILD_TYPE", default_cfg)
  59. # where .so files will be written, should be the same for all extensions
  60. # that use the same CMakeLists.txt.
  61. outdir = os.path.abspath(
  62. os.path.dirname(self.get_ext_fullpath(ext.name)))
  63. cmake_args = [
  64. '-DCMAKE_BUILD_TYPE={}'.format(cfg),
  65. '-DCMAKE_LIBRARY_OUTPUT_DIRECTORY={}'.format(outdir),
  66. '-DCMAKE_ARCHIVE_OUTPUT_DIRECTORY={}'.format(self.build_temp),
  67. ]
  68. verbose = bool(int(os.getenv('VERBOSE', '0')))
  69. if verbose:
  70. cmake_args += ['-DCMAKE_VERBOSE_MAKEFILE=ON']
  71. if is_sccache_available():
  72. cmake_args += [
  73. '-DCMAKE_CXX_COMPILER_LAUNCHER=sccache',
  74. '-DCMAKE_CUDA_COMPILER_LAUNCHER=sccache',
  75. ]
  76. elif is_ccache_available():
  77. cmake_args += [
  78. '-DCMAKE_CXX_COMPILER_LAUNCHER=ccache',
  79. '-DCMAKE_CUDA_COMPILER_LAUNCHER=ccache',
  80. ]
  81. # Pass the python executable to cmake so it can find an exact
  82. # match.
  83. cmake_args += ['-DAPHRODITE_PYTHON_EXECUTABLE={}'.format(sys.executable)]
  84. if _install_punica():
  85. cmake_args += ['-DAPHRODITE_INSTALL_PUNICA_KERNELS=ON']
  86. if _install_hadamard():
  87. cmake_args += ['-DAPHRODITE_INSTALL_HADAMARD_KERNELS=ON']
  88. #
  89. # Setup parallelism and build tool
  90. #
  91. num_jobs, nvcc_threads = self.compute_num_jobs()
  92. if nvcc_threads:
  93. cmake_args += ['-DNVCC_THREADS={}'.format(nvcc_threads)]
  94. if is_ninja_available():
  95. build_tool = ['-G', 'Ninja']
  96. cmake_args += [
  97. '-DCMAKE_JOB_POOL_COMPILE:STRING=compile',
  98. '-DCMAKE_JOB_POOLS:STRING=compile={}'.format(num_jobs),
  99. ]
  100. else:
  101. # Default build tool to whatever cmake picks.
  102. build_tool = []
  103. subprocess.check_call(
  104. ['cmake', ext.cmake_lists_dir, *build_tool, *cmake_args],
  105. cwd=self.build_temp)
  106. def build_extensions(self) -> None:
  107. # Ensure that CMake is present and working
  108. try:
  109. subprocess.check_output(['cmake', '--version'])
  110. except OSError as e:
  111. raise RuntimeError('Cannot find CMake executable') from e
  112. # Create build directory if it does not exist.
  113. if not os.path.exists(self.build_temp):
  114. os.makedirs(self.build_temp)
  115. # Build all the extensions
  116. for ext in self.extensions:
  117. self.configure(ext)
  118. ext_target_name = remove_prefix(ext.name, "aphrodite.")
  119. num_jobs, _ = self.compute_num_jobs()
  120. build_args = [
  121. '--build', '.', '--target', ext_target_name, '-j',
  122. str(num_jobs)
  123. ]
  124. subprocess.check_call(['cmake', *build_args], cwd=self.build_temp)
  125. def _is_hip() -> bool:
  126. return torch.version.hip is not None
  127. def _is_cuda() -> bool:
  128. return torch.version.cuda is not None
  129. def _install_punica() -> bool:
  130. return bool(int(os.getenv("APHRODITE_INSTALL_PUNICA_KERNELS", "0")))
  131. def _install_hadamard() -> bool:
  132. return bool(int(os.getenv("APHRODITE_INSTALL_HADAMARD_KERNELS", "0")))
  133. def get_path(*filepath) -> str:
  134. return os.path.join(ROOT_DIR, *filepath)
  135. def find_version(filepath: str) -> str:
  136. """Extract version information from the given filepath.
  137. Adapted from https://github.com/ray-project/ray/blob/0b190ee1160eeca9796bc091e07eaebf4c85b511/python/setup.py
  138. """
  139. with open(filepath) as fp:
  140. version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]",
  141. fp.read(), re.M)
  142. if version_match:
  143. return version_match.group(1)
  144. raise RuntimeError("Unable to find version string.")
  145. def get_hipcc_rocm_version():
  146. # Run the hipcc --version command
  147. result = subprocess.run(['hipcc', '--version'],
  148. stdout=subprocess.PIPE,
  149. stderr=subprocess.STDOUT,
  150. text=True)
  151. # Check if the command was executed successfully
  152. if result.returncode != 0:
  153. print("Error running 'hipcc --version'")
  154. return None
  155. # Extract the version using a regular expression
  156. match = re.search(r'HIP version: (\S+)', result.stdout)
  157. if match:
  158. # Return the version string
  159. return match.group(1)
  160. else:
  161. print("Could not find HIP version in the output")
  162. return None
  163. def get_nvcc_cuda_version() -> Version:
  164. """Get the CUDA version from nvcc.
  165. Adapted from https://github.com/NVIDIA/apex/blob/8b7a1ff183741dd8f9b87e7bafd04cfde99cea28/setup.py
  166. """
  167. nvcc_output = subprocess.check_output([CUDA_HOME + "/bin/nvcc", "-V"],
  168. universal_newlines=True)
  169. output = nvcc_output.split()
  170. release_idx = output.index("release") + 1
  171. nvcc_cuda_version = parse(output[release_idx].split(",")[0])
  172. return nvcc_cuda_version
  173. def get_aphrodite_version() -> str:
  174. version = find_version(get_path("aphrodite", "__init__.py"))
  175. if _is_hip():
  176. # get the HIP version
  177. hipcc_version = get_hipcc_rocm_version()
  178. if hipcc_version != MAIN_CUDA_VERSION:
  179. rocm_version_str = hipcc_version.replace(".", "")[:3]
  180. version += f"+rocm{rocm_version_str}"
  181. else:
  182. cuda_version = str(get_nvcc_cuda_version())
  183. if cuda_version != MAIN_CUDA_VERSION:
  184. cuda_version_str = cuda_version.replace(".", "")[:3]
  185. version += f"+cu{cuda_version_str}"
  186. return version
  187. def read_readme() -> str:
  188. """Read the README file if present."""
  189. p = get_path("README.md")
  190. if os.path.isfile(p):
  191. return io.open(get_path("README.md"), "r", encoding="utf-8").read()
  192. else:
  193. return ""
  194. def get_requirements() -> List[str]:
  195. """Get Python package dependencies from requirements.txt."""
  196. if _is_hip():
  197. with open(get_path("requirements-rocm.txt")) as f:
  198. requirements = f.read().strip().split("\n")
  199. else:
  200. with open(get_path("requirements.txt")) as f:
  201. requirements = f.read().strip().split("\n")
  202. return requirements
  203. ext_modules = []
  204. if _is_cuda():
  205. ext_modules.append(CMakeExtension(name="aphrodite._moe_C"))
  206. if _install_punica():
  207. ext_modules.append(CMakeExtension(name="aphrodite._punica_C"))
  208. if _install_hadamard():
  209. ext_modules.append(CMakeExtension(name="aphrodite._hadamard_C"))
  210. ext_modules.append(CMakeExtension(name="aphrodite._C"))
  211. setup(
  212. name="aphrodite-engine",
  213. version=get_aphrodite_version(),
  214. author="PygmalionAI",
  215. license="AGPL 3.0",
  216. description="The inference engine for PygmalionAI models",
  217. long_description=read_readme(),
  218. long_description_content_type="text/markdown",
  219. url="https://github.com/PygmalionAI/aphrodite-engine",
  220. project_urls={
  221. "Homepage": "https://pygmalion.chat",
  222. "Documentation": "https://docs.pygmalion.chat",
  223. "GitHub": "https://github.com/PygmalionAI",
  224. "Huggingface": "https://huggingface.co/PygmalionAI",
  225. },
  226. classifiers=[
  227. "Programming Language :: Python :: 3.8",
  228. "Programming Language :: Python :: 3.9",
  229. "Programming Language :: Python :: 3.10",
  230. "Programming Language :: Python :: 3.11",
  231. "License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)", # noqa: E501
  232. "Topic :: Scientific/Engineering :: Artificial Intelligence",
  233. ],
  234. packages=find_packages(exclude=("kernels", "examples",
  235. "tests")),
  236. python_requires=">=3.8",
  237. install_requires=get_requirements(),
  238. ext_modules=ext_modules,
  239. cmdclass={"build_ext": cmake_build_ext},
  240. package_data={
  241. "aphrodite": [
  242. "endpoints/kobold/klite.embd",
  243. "modeling/layers/quantization/hadamard.safetensors", "py.typed"
  244. ]
  245. },
  246. include_package_data=True,
  247. )