| 1 | # SPDX-License-Identifier: Apache-2.0 |
| 2 | # SPDX-FileCopyrightText: Copyright contributors to the vLLM project |
| 3 | import argparse |
| 4 | import json |
| 5 | import multiprocessing |
| 6 | import os |
| 7 | import sys |
| 8 | from shutil import which |
| 9 | |
| 10 | try: |
| 11 | # Try to get CUDA_HOME from PyTorch installation, which is the |
| 12 | # most reliable source of truth for vLLM's build. |
| 13 | from torch.utils.cpp_extension import CUDA_HOME |
| 14 | except ImportError: |
| 15 | print("Warning: PyTorch not found. Falling back to CUDA_HOME environment variable.") |
| 16 | CUDA_HOME = os.environ.get("CUDA_HOME") |
| 17 | |
| 18 | |
| 19 | def get_python_executable(): |
| 20 | """Get the current Python executable, which is used to run this script.""" |
| 21 | return sys.executable |
| 22 | |
| 23 | |
| 24 | def get_cpu_cores(): |
| 25 | """Get the number of CPU cores.""" |
| 26 | return multiprocessing.cpu_count() |
| 27 | |
| 28 | |
| 29 | def generate_presets(output_path="CMakeUserPresets.json", force_overwrite=False): |
| 30 | """Generates the CMakeUserPresets.json file.""" |
| 31 | print("Attempting to detect your system configuration...") |
| 32 | |
| 33 | # Detect NVCC |
| 34 | nvcc_path = None |
| 35 | if CUDA_HOME: |
| 36 | prospective_path = os.path.join(CUDA_HOME, "bin", "nvcc") |
| 37 | if os.path.exists(prospective_path): |
| 38 | nvcc_path = prospective_path |
| 39 | print(f"Found nvcc via torch.utils.cpp_extension.CUDA_HOME: {nvcc_path}") |
| 40 | |
| 41 | if not nvcc_path: |
| 42 | nvcc_path = which("nvcc") |
| 43 | if nvcc_path: |
| 44 | print(f"Found nvcc in PATH: {nvcc_path}") |
| 45 | |
| 46 | if not nvcc_path: |
| 47 | nvcc_path_input = input( |
| 48 | "Could not automatically find 'nvcc'. Please provide the full " |
| 49 | "path to nvcc (e.g., /usr/local/cuda/bin/nvcc): " |
| 50 | ) |
| 51 | nvcc_path = nvcc_path_input.strip() |
| 52 | print(f"Using NVCC path: {nvcc_path}") |
| 53 | |
| 54 | # Detect Python executable |
| 55 | python_executable = get_python_executable() |
| 56 | if python_executable: |
| 57 | print(f"Found Python via sys.executable: {python_executable}") |
| 58 | else: |
| 59 | python_executable_prompt = ( |
| 60 | "Could not automatically find Python executable. Please provide " |
| 61 | "the full path to your Python executable for vLLM development " |
| 62 | "(typically from your virtual environment, e.g., " |
| 63 | "/home/user/venvs/vllm/bin/python): " |
| 64 | ) |
| 65 | python_executable = input(python_executable_prompt).strip() |
| 66 | if not python_executable: |
| 67 | raise ValueError( |
| 68 | "Could not determine Python executable. Please provide it manually." |
| 69 | ) |
| 70 | |
| 71 | print(f"Using Python executable: {python_executable}") |
| 72 | |
| 73 | # Get CPU cores |
| 74 | cpu_cores = get_cpu_cores() |
| 75 | nvcc_threads = min(4, cpu_cores) |
| 76 | cmake_jobs = max(1, cpu_cores // nvcc_threads) |
| 77 | print( |
| 78 | f"Detected {cpu_cores} CPU cores. " |
| 79 | f"Setting NVCC_THREADS={nvcc_threads} and CMake jobs={cmake_jobs}." |
| 80 | ) |
| 81 | |
| 82 | # Get vLLM project root (assuming this script is in vllm/tools/) |
| 83 | project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) |
| 84 | print(f"VLLM project root detected as: {project_root}") |
| 85 | |
| 86 | # Ensure python_executable path is absolute or resolvable |
| 87 | if not os.path.isabs(python_executable) and which(python_executable): |
| 88 | python_executable = os.path.abspath(which(python_executable)) |
| 89 | elif not os.path.isabs(python_executable): |
| 90 | print( |
| 91 | f"Warning: Python executable '{python_executable}' is not an " |
| 92 | "absolute path and not found in PATH. CMake might not find it." |
| 93 | ) |
| 94 | |
| 95 | cache_variables = { |
| 96 | "CMAKE_CUDA_COMPILER": nvcc_path, |
| 97 | "CMAKE_BUILD_TYPE": "Release", |
| 98 | "VLLM_PYTHON_EXECUTABLE": python_executable, |
| 99 | "CMAKE_INSTALL_PREFIX": "${sourceDir}", |
| 100 | "CMAKE_CUDA_FLAGS": "", |
| 101 | "NVCC_THREADS": str(nvcc_threads), |
| 102 | } |
| 103 | |
| 104 | # Detect compiler cache |
| 105 | if which("sccache"): |
| 106 | print("Using sccache for compiler caching.") |
| 107 | for launcher in ("C", "CXX", "CUDA", "HIP"): |
| 108 | cache_variables[f"CMAKE_{launcher}_COMPILER_LAUNCHER"] = "sccache" |
| 109 | elif which("ccache"): |
| 110 | print("Using ccache for compiler caching.") |
| 111 | for launcher in ("C", "CXX", "CUDA", "HIP"): |
| 112 | cache_variables[f"CMAKE_{launcher}_COMPILER_LAUNCHER"] = "ccache" |
| 113 | else: |
| 114 | print("No compiler cache ('ccache' or 'sccache') found.") |
| 115 | |
| 116 | configure_preset = { |
| 117 | "name": "release", |
| 118 | "binaryDir": "${sourceDir}/cmake-build-release", |
| 119 | "cacheVariables": cache_variables, |
| 120 | } |
| 121 | if which("ninja"): |
| 122 | print("Using Ninja generator.") |
| 123 | configure_preset["generator"] = "Ninja" |
| 124 | cache_variables["CMAKE_JOB_POOLS"] = f"compile={cmake_jobs}" |
| 125 | else: |
| 126 | print("Ninja not found, using default generator. Build may be slower.") |
| 127 | |
| 128 | presets = { |
| 129 | "version": 6, |
| 130 | # Keep in sync with CMakeLists.txt and requirements/build/cuda.txt |
| 131 | "cmakeMinimumRequired": {"major": 3, "minor": 26, "patch": 1}, |
| 132 | "configurePresets": [configure_preset], |
| 133 | "buildPresets": [ |
| 134 | { |
| 135 | "name": "release", |
| 136 | "configurePreset": "release", |
| 137 | "jobs": cmake_jobs, |
| 138 | } |
| 139 | ], |
| 140 | } |
| 141 | |
| 142 | output_file_path = os.path.join(project_root, output_path) |
| 143 | |
| 144 | if os.path.exists(output_file_path): |
| 145 | if force_overwrite: |
| 146 | print(f"Overwriting existing file '{output_file_path}'") |
| 147 | else: |
| 148 | overwrite = ( |
| 149 | input(f"'{output_file_path}' already exists. Overwrite? (y/N): ") |
| 150 | .strip() |
| 151 | .lower() |
| 152 | ) |
| 153 | if overwrite != "y": |
| 154 | print("Generation cancelled.") |
| 155 | return |
| 156 | |
| 157 | try: |
| 158 | with open(output_file_path, "w") as f: |
| 159 | json.dump(presets, f, indent=4) |
| 160 | print(f"Successfully generated '{output_file_path}'") |
| 161 | print("\nTo use this preset:") |
| 162 | print(f"1. Ensure you are in the vLLM root directory: cd {project_root}") |
| 163 | print("2. Initialize CMake: cmake --preset release") |
| 164 | print("3. Build+install: cmake --build --preset release --target install") |
| 165 | |
| 166 | except OSError as e: |
| 167 | print(f"Error writing file: {e}") |
| 168 | |
| 169 | |
| 170 | if __name__ == "__main__": |
| 171 | parser = argparse.ArgumentParser() |
| 172 | parser.add_argument( |
| 173 | "--force-overwrite", |
| 174 | action="store_true", |
| 175 | help="Force overwrite existing CMakeUserPresets.json without prompting", |
| 176 | ) |
| 177 | |
| 178 | args = parser.parse_args() |
| 179 | generate_presets(force_overwrite=args.force_overwrite) |