github.com/johnnyeven/libtools@v0.0.0-20191126065708-61829c1adf46/third_party/gpus/find_cuda_config.py (about) 1 # Copyright 2019 The TensorFlow Authors. All Rights Reserved. 2 # 3 # Licensed under the Apache License, Version 2.0 (the "License"); 4 # you may not use this file except in compliance with the License. 5 # You may obtain a copy of the License at 6 # 7 # http://www.apache.org/licenses/LICENSE-2.0 8 # 9 # Unless required by applicable law or agreed to in writing, software 10 # distributed under the License is distributed on an "AS IS" BASIS, 11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 # See the License for the specific language governing permissions and 13 # limitations under the License. 14 # ============================================================================== 15 """Prints CUDA library and header directories and versions found on the system. 16 17 The script searches for CUDA library and header files on the system, inspects 18 them to determine their version and prints the configuration to stdout. 19 The paths to inspect and the required versions are specified through environment 20 variables. If no valid configuration is found, the script prints to stderr and 21 returns an error code. 22 23 The list of libraries to find is specified as arguments. Supported libraries are 24 CUDA (includes cuBLAS), cuDNN, NCCL, and TensorRT. 25 26 The script takes a list of base directories specified by the TF_CUDA_PATHS 27 environment variable as comma-separated glob list. The script looks for headers 28 and library files in a hard-coded set of subdirectories from these base paths. 29 If TF_CUDA_PATHS is not specified, a OS specific default is used: 30 31 Linux: /usr/local/cuda, /usr, and paths from 'ldconfig -p'. 32 Windows: CUDA_PATH environment variable, or 33 C:\\Program Files\\NVIDIA GPU Computing Toolkit\\CUDA\\* 34 35 For backwards compatibility, some libraries also use alternative base 36 directories from other environment variables if they are specified. List of 37 library-specific environment variables: 38 39 Library Version env variable Additional base directories 40 ---------------------------------------------------------------- 41 CUDA TF_CUDA_VERSION CUDA_TOOLKIT_PATH 42 cuBLAS TF_CUBLAS_VERSION CUDA_TOOLKIT_PATH 43 cuDNN TF_CUDNN_VERSION CUDNN_INSTALL_PATH 44 NCCL TF_NCCL_VERSION NCCL_INSTALL_PATH, NCCL_HDR_PATH 45 TensorRT TF_TENSORRT_VERSION TENSORRT_INSTALL_PATH 46 47 Versions environment variables can be of the form 'x' or 'x.y' to request a 48 specific version, empty or unspecified to accept any version. 49 50 The output of a found library is of the form: 51 tf_<library>_version: x.y.z 52 tf_<library>_header_dir: ... 53 tf_<library>_library_dir: ... 54 """ 55 56 import io 57 import os 58 import glob 59 import platform 60 import re 61 import subprocess 62 import sys 63 64 # pylint: disable=g-import-not-at-top 65 try: 66 from shutil import which 67 except ImportError: 68 from distutils.spawn import find_executable as which 69 # pylint: enable=g-import-not-at-top 70 71 72 class ConfigError(Exception): 73 pass 74 75 76 def _is_linux(): 77 return platform.system() == "Linux" 78 79 80 def _is_windows(): 81 return platform.system() == "Windows" 82 83 84 def _is_macos(): 85 return platform.system() == "Darwin" 86 87 88 def _matches_version(actual_version, required_version): 89 """Checks whether some version meets the requirements. 90 91 All elements of the required_version need to be present in the 92 actual_version. 93 94 required_version actual_version result 95 ----------------------------------------- 96 1 1.1 True 97 1.2 1 False 98 1.2 1.3 False 99 1 True 100 101 Args: 102 required_version: The version specified by the user. 103 actual_version: The version detected from the CUDA installation. 104 Returns: Whether the actual version matches the required one. 105 """ 106 if actual_version is None: 107 return False 108 109 # Strip spaces from the versions. 110 actual_version = actual_version.strip() 111 required_version = required_version.strip() 112 return actual_version.startswith(required_version) 113 114 115 def _at_least_version(actual_version, required_version): 116 actual = [int(v) for v in actual_version.split(".")] 117 required = [int(v) for v in required_version.split(".")] 118 return actual >= required 119 120 121 def _get_header_version(path, name): 122 """Returns preprocessor defines in C header file.""" 123 for line in io.open(path, "r", encoding="utf-8").readlines(): 124 match = re.match("#define %s +(\d+)" % name, line) 125 if match: 126 return match.group(1) 127 return "" 128 129 130 def _cartesian_product(first, second): 131 """Returns all path combinations of first and second.""" 132 return [os.path.join(f, s) for f in first for s in second] 133 134 135 def _get_ld_config_paths(): 136 """Returns all directories from 'ldconfig -p'.""" 137 if not _is_linux(): 138 return [] 139 ldconfig_path = which("ldconfig") or "/sbin/ldconfig" 140 output = subprocess.check_output([ldconfig_path, "-p"]) 141 pattern = re.compile(".* => (.*)") 142 result = set() 143 for line in output.splitlines(): 144 try: 145 match = pattern.match(line.decode("ascii")) 146 except UnicodeDecodeError: 147 match = False 148 if match: 149 result.add(os.path.dirname(match.group(1))) 150 return sorted(list(result)) 151 152 153 def _get_default_cuda_paths(cuda_version): 154 if not cuda_version: 155 cuda_version = "*" 156 elif not "." in cuda_version: 157 cuda_version = cuda_version + ".*" 158 159 if _is_windows(): 160 return [ 161 os.environ.get( 162 "CUDA_PATH", 163 "C:\\Program Files\\NVIDIA GPU Computing Toolkit\\CUDA\\v%s\\" % 164 cuda_version) 165 ] 166 return ["/usr/local/cuda-%s" % cuda_version, "/usr/local/cuda", "/usr" 167 ] + _get_ld_config_paths() 168 169 170 def _header_paths(): 171 """Returns hard-coded set of relative paths to look for header files.""" 172 return [ 173 "", 174 "include", 175 "include/cuda", 176 "include/*-linux-gnu", 177 "extras/CUPTI/include", 178 "include/cuda/CUPTI", 179 ] 180 181 182 def _library_paths(): 183 """Returns hard-coded set of relative paths to look for library files.""" 184 return [ 185 "", 186 "lib64", 187 "lib", 188 "lib/*-linux-gnu", 189 "lib/x64", 190 "extras/CUPTI/*", 191 ] 192 193 194 def _not_found_error(base_paths, relative_paths, filepattern): 195 base_paths = "".join(["\n '%s'" % path for path in sorted(base_paths)]) 196 relative_paths = "".join(["\n '%s'" % path for path in relative_paths]) 197 return ConfigError( 198 "Could not find any %s in any subdirectory:%s\nof:%s\n" % 199 (filepattern, relative_paths, base_paths)) 200 201 202 def _find_file(base_paths, relative_paths, filepattern): 203 for path in _cartesian_product(base_paths, relative_paths): 204 for file in glob.glob(os.path.join(path, filepattern)): 205 return file 206 raise _not_found_error(base_paths, relative_paths, filepattern) 207 208 209 def _find_library(base_paths, library_name, required_version): 210 """Returns first valid path to the requested library.""" 211 if _is_windows(): 212 filepattern = library_name + ".lib" 213 elif _is_macos(): 214 filepattern = "%s*.dylib" % (".".join(["lib" + library_name] + 215 required_version.split(".")[:1])) 216 else: 217 filepattern = ".".join(["lib" + library_name, "so"] + 218 required_version.split(".")[:1]) + "*" 219 return _find_file(base_paths, _library_paths(), filepattern) 220 221 222 def _find_versioned_file(base_paths, relative_paths, filepattern, 223 required_version, get_version): 224 """Returns first valid path to a file that matches the requested version.""" 225 for path in _cartesian_product(base_paths, relative_paths): 226 for file in glob.glob(os.path.join(path, filepattern)): 227 actual_version = get_version(file) 228 if _matches_version(actual_version, required_version): 229 return file, actual_version 230 raise _not_found_error( 231 base_paths, relative_paths, 232 filepattern + " matching version '%s'" % required_version) 233 234 235 def _find_header(base_paths, header_name, required_version, get_version): 236 """Returns first valid path to a header that matches the requested version.""" 237 return _find_versioned_file(base_paths, _header_paths(), header_name, 238 required_version, get_version) 239 240 241 def _find_cuda_config(base_paths, required_version): 242 243 def get_header_version(path): 244 version = int(_get_header_version(path, "CUDA_VERSION")) 245 if not version: 246 return None 247 return "%d.%d" % (version // 1000, version % 1000 // 10) 248 249 cuda_header_path, header_version = _find_header(base_paths, "cuda.h", 250 required_version, 251 get_header_version) 252 cuda_version = header_version # x.y, see above. 253 254 cuda_library_path = _find_library(base_paths, "cudart", cuda_version) 255 256 def get_nvcc_version(path): 257 pattern = "Cuda compilation tools, release \d+\.\d+, V(\d+\.\d+\.\d+)" 258 for line in subprocess.check_output([path, "--version"]).splitlines(): 259 match = re.match(pattern, line.decode("ascii")) 260 if match: 261 return match.group(1) 262 return None 263 264 nvcc_name = "nvcc.exe" if _is_windows() else "nvcc" 265 nvcc_path, nvcc_version = _find_versioned_file(base_paths, [ 266 "", 267 "bin", 268 ], nvcc_name, cuda_version, get_nvcc_version) 269 270 nvvm_path = _find_file(base_paths, [ 271 "nvvm/libdevice", 272 "share/cuda", 273 "lib/nvidia-cuda-toolkit/libdevice", 274 ], "libdevice*.10.bc") 275 276 cupti_header_path = _find_file(base_paths, _header_paths(), "cupti.h") 277 cupti_library_path = _find_library(base_paths, "cupti", required_version) 278 279 cuda_binary_dir = os.path.dirname(nvcc_path) 280 nvvm_library_dir = os.path.dirname(nvvm_path) 281 282 # XLA requires the toolkit path to find ptxas and libdevice. 283 # TODO(csigg): pass in both directories instead. 284 cuda_toolkit_paths = ( 285 os.path.normpath(os.path.join(cuda_binary_dir, "..")), 286 os.path.normpath(os.path.join(nvvm_library_dir, "../..")), 287 ) 288 if cuda_toolkit_paths[0] != cuda_toolkit_paths[1]: 289 raise ConfigError("Inconsistent CUDA toolkit path: %s vs %s" % 290 cuda_toolkit_paths) 291 292 return { 293 "cuda_version": cuda_version, 294 "cuda_include_dir": os.path.dirname(cuda_header_path), 295 "cuda_library_dir": os.path.dirname(cuda_library_path), 296 "cuda_binary_dir": cuda_binary_dir, 297 "nvvm_library_dir": nvvm_library_dir, 298 "cupti_include_dir": os.path.dirname(cupti_header_path), 299 "cupti_library_dir": os.path.dirname(cupti_library_path), 300 "cuda_toolkit_path": cuda_toolkit_paths[0], 301 } 302 303 304 def _find_cublas_config(base_paths, required_version, cuda_version): 305 306 if _at_least_version(cuda_version, "10.1"): 307 308 def get_header_version(path): 309 version = ( 310 _get_header_version(path, name) 311 for name in ("CUBLAS_VER_MAJOR", "CUBLAS_VER_MINOR", 312 "CUBLAS_VER_PATCH")) 313 return ".".join(version) 314 315 header_path, header_version = _find_header(base_paths, "cublas_api.h", 316 required_version, 317 get_header_version) 318 # cuBLAS uses the major version only. 319 cublas_version = header_version.split(".")[0] 320 321 if not _matches_version(cuda_version, cublas_version): 322 raise ConfigError("cuBLAS version %s does not match CUDA version %s" % 323 (cublas_version, cuda_version)) 324 325 else: 326 # There is no version info available before CUDA 10.1, just find the file. 327 header_path = _find_file(base_paths, _header_paths(), "cublas_api.h") 328 # cuBLAS version is the same as CUDA version (x.y). 329 cublas_version = required_version 330 331 library_path = _find_library(base_paths, "cublas", cublas_version) 332 333 return { 334 "cublas_include_dir": os.path.dirname(header_path), 335 "cublas_library_dir": os.path.dirname(library_path), 336 } 337 338 339 def _find_cudnn_config(base_paths, required_version): 340 341 def get_header_version(path): 342 version = ( 343 _get_header_version(path, name) 344 for name in ("CUDNN_MAJOR", "CUDNN_MINOR", "CUDNN_PATCHLEVEL")) 345 return ".".join(version) 346 347 header_path, header_version = _find_header(base_paths, "cudnn.h", 348 required_version, 349 get_header_version) 350 cudnn_version = header_version.split(".")[0] 351 352 library_path = _find_library(base_paths, "cudnn", cudnn_version) 353 354 return { 355 "cudnn_version": cudnn_version, 356 "cudnn_include_dir": os.path.dirname(header_path), 357 "cudnn_library_dir": os.path.dirname(library_path), 358 } 359 360 361 def _find_nccl_config(base_paths, required_version): 362 363 def get_header_version(path): 364 version = ( 365 _get_header_version(path, name) 366 for name in ("NCCL_MAJOR", "NCCL_MINOR", "NCCL_PATCH")) 367 return ".".join(version) 368 369 header_path, header_version = _find_header(base_paths, "nccl.h", 370 required_version, 371 get_header_version) 372 nccl_version = header_version.split(".")[0] 373 374 library_path = _find_library(base_paths, "nccl", nccl_version) 375 376 return { 377 "nccl_version": nccl_version, 378 "nccl_include_dir": os.path.dirname(header_path), 379 "nccl_library_dir": os.path.dirname(library_path), 380 } 381 382 383 def _find_tensorrt_config(base_paths, required_version): 384 385 def get_header_version(path): 386 version = ( 387 _get_header_version(path, name) 388 for name in ("NV_TENSORRT_MAJOR", "NV_TENSORRT_MINOR", 389 "NV_TENSORRT_PATCH")) 390 # `version` is a generator object, so we convert it to a list before using 391 # it (muitiple times below). 392 version = list(version) 393 if not all(version): 394 return None # Versions not found, make _matches_version returns False. 395 return ".".join(version) 396 397 try: 398 header_path, header_version = _find_header(base_paths, "NvInfer.h", 399 required_version, 400 get_header_version) 401 except ConfigError: 402 # TensorRT 6 moved the version information to NvInferVersion.h. 403 header_path, header_version = _find_header(base_paths, "NvInferVersion.h", 404 required_version, 405 get_header_version) 406 407 tensorrt_version = header_version.split(".")[0] 408 library_path = _find_library(base_paths, "nvinfer", tensorrt_version) 409 410 return { 411 "tensorrt_version": tensorrt_version, 412 "tensorrt_include_dir": os.path.dirname(header_path), 413 "tensorrt_library_dir": os.path.dirname(library_path), 414 } 415 416 417 def _list_from_env(env_name, default=[]): 418 """Returns comma-separated list from environment variable.""" 419 if env_name in os.environ: 420 return os.environ[env_name].split(",") 421 return default 422 423 424 def _get_legacy_path(env_name, default=[]): 425 """Returns a path specified by a legacy environment variable. 426 427 CUDNN_INSTALL_PATH, NCCL_INSTALL_PATH, TENSORRT_INSTALL_PATH set to 428 '/usr/lib/x86_64-linux-gnu' would previously find both library and header 429 paths. Detect those and return '/usr', otherwise forward to _list_from_env(). 430 """ 431 if env_name in os.environ: 432 match = re.match("^(/[^/ ]*)+/lib/\w+-linux-gnu/?$", os.environ[env_name]) 433 if match: 434 return [match.group(1)] 435 return _list_from_env(env_name, default) 436 437 438 def _normalize_path(path): 439 """Returns normalized path, with forward slashes on Windows.""" 440 path = os.path.normpath(path) 441 if _is_windows(): 442 path = path.replace("\\", "/") 443 return path 444 445 446 def find_cuda_config(): 447 """Returns a dictionary of CUDA library and header file paths.""" 448 libraries = [argv.lower() for argv in sys.argv[1:]] 449 cuda_version = os.environ.get("TF_CUDA_VERSION", "") 450 base_paths = _list_from_env("TF_CUDA_PATHS", 451 _get_default_cuda_paths(cuda_version)) 452 base_paths = [path for path in base_paths if os.path.exists(path)] 453 454 result = {} 455 if "cuda" in libraries: 456 cuda_paths = _list_from_env("CUDA_TOOLKIT_PATH", base_paths) 457 result.update(_find_cuda_config(cuda_paths, cuda_version)) 458 459 cuda_version = result["cuda_version"] 460 cublas_paths = base_paths 461 if tuple(int(v) for v in cuda_version.split(".")) < (10, 1): 462 # Before CUDA 10.1, cuBLAS was in the same directory as the toolkit. 463 cublas_paths = cuda_paths 464 cublas_version = os.environ.get("TF_CUBLAS_VERSION", "") 465 result.update( 466 _find_cublas_config(cublas_paths, cublas_version, cuda_version)) 467 468 if "cudnn" in libraries: 469 cudnn_paths = _get_legacy_path("CUDNN_INSTALL_PATH", base_paths) 470 cudnn_version = os.environ.get("TF_CUDNN_VERSION", "") 471 result.update(_find_cudnn_config(cudnn_paths, cudnn_version)) 472 473 if "nccl" in libraries: 474 nccl_paths = _get_legacy_path("NCCL_INSTALL_PATH", base_paths) 475 nccl_version = os.environ.get("TF_NCCL_VERSION", "") 476 result.update(_find_nccl_config(nccl_paths, nccl_version)) 477 478 if "tensorrt" in libraries: 479 tensorrt_paths = _get_legacy_path("TENSORRT_INSTALL_PATH", base_paths) 480 tensorrt_version = os.environ.get("TF_TENSORRT_VERSION", "") 481 result.update(_find_tensorrt_config(tensorrt_paths, tensorrt_version)) 482 483 for k, v in result.items(): 484 if k.endswith("_dir") or k.endswith("_path"): 485 result[k] = _normalize_path(v) 486 487 return result 488 489 490 def main(): 491 try: 492 for key, value in sorted(find_cuda_config().items()): 493 print("%s: %s" % (key, value)) 494 except ConfigError as e: 495 sys.stderr.write(str(e)) 496 sys.exit(1) 497 498 499 if __name__ == "__main__": 500 main()