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()