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