Compare commits
3 Commits
bd27769030
...
f56ef583d3
Author | SHA1 | Date |
---|---|---|
Aleksey Gurtovoy | f56ef583d3 | 5 years ago |
Dylan Baker | 11f1adb7dd | 5 years ago |
sghctoma | aba8792b66 | 5 years ago |
27 changed files with 540 additions and 6 deletions
@ -0,0 +1,11 @@ |
||||
## CUDA dependency |
||||
|
||||
Native support for compiling and linking against the CUDA Toolkit using |
||||
the `dependency` function: |
||||
|
||||
```meson |
||||
project('CUDA test', 'cpp', meson_version: '>= 0.53.0') |
||||
exe = executable('prog', 'prog.cc', dependencies: dependency('cuda')) |
||||
``` |
||||
|
||||
See [the CUDA dependency](Dependencies.md#cuda) for more information. |
@ -0,0 +1,253 @@ |
||||
# Copyright 2013-2019 The Meson development team |
||||
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); |
||||
# you may not use this file except in compliance with the License. |
||||
# You may obtain a copy of the License at |
||||
|
||||
# http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
||||
# Unless required by applicable law or agreed to in writing, software |
||||
# distributed under the License is distributed on an "AS IS" BASIS, |
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
# See the License for the specific language governing permissions and |
||||
# limitations under the License. |
||||
|
||||
import glob |
||||
import re |
||||
import os |
||||
|
||||
from .. import mlog |
||||
from .. import mesonlib |
||||
from ..environment import detect_cpu_family |
||||
|
||||
from .base import (DependencyException, ExternalDependency, HasNativeKwarg) |
||||
|
||||
|
||||
class CudaDependency(ExternalDependency): |
||||
|
||||
supported_languages = ['cuda', 'cpp', 'c'] # see also _default_language |
||||
|
||||
def __init__(self, environment, kwargs): |
||||
HasNativeKwarg.__init__(self, kwargs) # initialize self.for_machine |
||||
compilers = environment.coredata.compilers[self.for_machine] |
||||
language = self._detect_language(compilers) |
||||
if language not in self.supported_languages: |
||||
raise DependencyException('Language \'{}\' is not supported by the CUDA Toolkit. Supported languages are {}.'.format(language, self.supported_languages)) |
||||
|
||||
super().__init__('cuda', environment, language, kwargs) |
||||
self.requested_modules = self.get_requested(kwargs) |
||||
if 'cudart' not in self.requested_modules: |
||||
self.requested_modules = ['cudart'] + self.requested_modules |
||||
|
||||
(self.cuda_path, self.version, self.is_found) = self._detect_cuda_path_and_version() |
||||
if not self.is_found: |
||||
return |
||||
|
||||
if not os.path.isabs(self.cuda_path): |
||||
raise DependencyException('CUDA Toolkit path must be absolute, got \'{}\'.'.format(self.cuda_path)) |
||||
|
||||
# nvcc already knows where to find the CUDA Toolkit, but if we're compiling |
||||
# a mixed C/C++/CUDA project, we still need to make the include dir searchable |
||||
if self.language != 'cuda' or len(compilers) > 1: |
||||
self.incdir = os.path.join(self.cuda_path, 'include') |
||||
self.compile_args += ['-I{}'.format(self.incdir)] |
||||
|
||||
if self.language != 'cuda': |
||||
arch_libdir = self._detect_arch_libdir() |
||||
self.libdir = os.path.join(self.cuda_path, arch_libdir) |
||||
mlog.debug('CUDA library directory is', mlog.bold(self.libdir)) |
||||
else: |
||||
self.libdir = None |
||||
|
||||
self.is_found = self._find_requested_libraries() |
||||
|
||||
@classmethod |
||||
def _detect_language(cls, compilers): |
||||
for lang in cls.supported_languages: |
||||
if lang in compilers: |
||||
return lang |
||||
return list(compilers.keys())[0] |
||||
|
||||
def _detect_cuda_path_and_version(self): |
||||
self.env_var = self._default_path_env_var() |
||||
mlog.debug('Default path env var:', mlog.bold(self.env_var)) |
||||
|
||||
version_reqs = self.version_reqs |
||||
if self.language == 'cuda': |
||||
nvcc_version = self._strip_patch_version(self.get_compiler().version) |
||||
mlog.debug('nvcc version:', mlog.bold(nvcc_version)) |
||||
if version_reqs: |
||||
# make sure nvcc version satisfies specified version requirements |
||||
(found_some, not_found, found) = mesonlib.version_compare_many(nvcc_version, version_reqs) |
||||
if not_found: |
||||
msg = 'The current nvcc version {} does not satisfy the specified CUDA Toolkit version requirements {}.'.format(nvcc_version, version_reqs) |
||||
return self._report_dependency_error(msg, (None, None, False)) |
||||
|
||||
# use nvcc version to find a matching CUDA Toolkit |
||||
version_reqs = ['={}'.format(nvcc_version)] |
||||
else: |
||||
nvcc_version = None |
||||
|
||||
paths = [(path, self._cuda_toolkit_version(path), default) for (path, default) in self._cuda_paths()] |
||||
if version_reqs: |
||||
return self._find_matching_toolkit(paths, version_reqs, nvcc_version) |
||||
|
||||
defaults = [(path, version) for (path, version, default) in paths if default] |
||||
if defaults: |
||||
return (*defaults[0], True) |
||||
|
||||
platform_msg = 'set the CUDA_PATH environment variable' if self._is_windows() \ |
||||
else 'set the CUDA_PATH environment variable/create the \'/usr/local/cuda\' symbolic link' |
||||
msg = 'Please specify the desired CUDA Toolkit version (e.g. dependency(\'cuda\', version : \'>=10.1\')) or {} to point to the location of your desired version.'.format(platform_msg) |
||||
return self._report_dependency_error(msg, (None, None, False)) |
||||
|
||||
def _find_matching_toolkit(self, paths, version_reqs, nvcc_version): |
||||
# keep the default paths order intact, sort the rest in the descending order |
||||
# according to the toolkit version |
||||
defaults, rest = mesonlib.partition(lambda t: not t[2], paths) |
||||
defaults = list(defaults) |
||||
paths = defaults + sorted(rest, key=lambda t: mesonlib.Version(t[1]), reverse=True) |
||||
mlog.debug('Search paths: {}'.format(paths)) |
||||
|
||||
if nvcc_version and defaults: |
||||
default_src = "the {} environment variable".format(self.env_var) if self.env_var else "the \'/usr/local/cuda\' symbolic link" |
||||
nvcc_warning = 'The default CUDA Toolkit as designated by {} ({}) doesn\'t match the current nvcc version {} and will be ignored.'.format(default_src, os.path.realpath(defaults[0][0]), nvcc_version) |
||||
else: |
||||
nvcc_warning = None |
||||
|
||||
for (path, version, default) in paths: |
||||
(found_some, not_found, found) = mesonlib.version_compare_many(version, version_reqs) |
||||
if not not_found: |
||||
if not default and nvcc_warning: |
||||
mlog.warning(nvcc_warning) |
||||
return (path, version, True) |
||||
|
||||
if nvcc_warning: |
||||
mlog.warning(nvcc_warning) |
||||
return (None, None, False) |
||||
|
||||
def _default_path_env_var(self): |
||||
env_vars = ['CUDA_PATH'] if self._is_windows() else ['CUDA_PATH', 'CUDA_HOME', 'CUDA_ROOT'] |
||||
env_vars = [var for var in env_vars if var in os.environ] |
||||
user_defaults = set([os.environ[var] for var in env_vars]) |
||||
if len(user_defaults) > 1: |
||||
mlog.warning('Environment variables {} point to conflicting toolkit locations ({}). Toolkit selection might produce unexpected results.'.format(', '.join(env_vars), ', '.join(user_defaults))) |
||||
return env_vars[0] if env_vars else None |
||||
|
||||
def _cuda_paths(self): |
||||
return ([(os.environ[self.env_var], True)] if self.env_var else []) \ |
||||
+ (self._cuda_paths_win() if self._is_windows() else self._cuda_paths_nix()) |
||||
|
||||
def _cuda_paths_win(self): |
||||
env_vars = os.environ.keys() |
||||
return [(os.environ[var], False) for var in env_vars if var.startswith('CUDA_PATH_')] |
||||
|
||||
def _cuda_paths_nix(self): |
||||
# include /usr/local/cuda default only if no env_var was found |
||||
pattern = '/usr/local/cuda-*' if self.env_var else '/usr/local/cuda*' |
||||
return [(path, os.path.basename(path) == 'cuda') for path in glob.iglob(pattern)] |
||||
|
||||
toolkit_version_regex = re.compile(r'^CUDA Version\s+(.*)$') |
||||
path_version_win_regex = re.compile(r'^v(.*)$') |
||||
path_version_nix_regex = re.compile(r'^cuda-(.*)$') |
||||
|
||||
def _cuda_toolkit_version(self, path): |
||||
version = self._read_toolkit_version_txt(path) |
||||
if version: |
||||
return version |
||||
|
||||
mlog.debug('Falling back to extracting version from path') |
||||
path_version_regex = self.path_version_win_regex if self._is_windows() else self.path_version_nix_regex |
||||
m = path_version_regex.match(os.path.basename(path)) |
||||
if m: |
||||
return m[1] |
||||
|
||||
mlog.warning('Could not detect CUDA Toolkit version for {}'.format(path)) |
||||
return '0.0' |
||||
|
||||
def _read_toolkit_version_txt(self, path): |
||||
# Read 'version.txt' at the root of the CUDA Toolkit directory to determine the tookit version |
||||
version_file_path = os.path.join(path, 'version.txt') |
||||
try: |
||||
with open(version_file_path) as version_file: |
||||
version_str = version_file.readline() # e.g. 'CUDA Version 10.1.168' |
||||
m = self.toolkit_version_regex.match(version_str) |
||||
if m: |
||||
return self._strip_patch_version(m[1]) |
||||
except Exception as e: |
||||
mlog.debug('Could not read CUDA Toolkit\'s version file {}: {}'.format(version_file_path, str(e))) |
||||
|
||||
return None |
||||
|
||||
@classmethod |
||||
def _strip_patch_version(cls, version): |
||||
return '.'.join(version.split('.')[:2]) |
||||
|
||||
def _detect_arch_libdir(self): |
||||
arch = detect_cpu_family(self.env.coredata.compilers.host) |
||||
machine = self.env.machines[self.for_machine] |
||||
msg = '{} architecture is not supported in {} version of the CUDA Toolkit.' |
||||
if machine.is_windows(): |
||||
libdirs = {'x86': 'Win32', 'x86_64': 'x64'} |
||||
if arch not in libdirs: |
||||
raise DependencyException(msg.format(arch, 'Windows')) |
||||
return os.path.join('lib', libdirs[arch]) |
||||
elif machine.is_linux(): |
||||
libdirs = {'x86_64': 'lib64', 'ppc64': 'lib'} |
||||
if arch not in libdirs: |
||||
raise DependencyException(msg.format(arch, 'Linux')) |
||||
return libdirs[arch] |
||||
elif machine.is_osx(): |
||||
libdirs = {'x86_64': 'lib64'} |
||||
if arch not in libdirs: |
||||
raise DependencyException(msg.format(arch, 'macOS')) |
||||
return libdirs[arch] |
||||
else: |
||||
raise DependencyException('CUDA Toolkit: unsupported platform.') |
||||
|
||||
def _find_requested_libraries(self): |
||||
self.lib_modules = {} |
||||
all_found = True |
||||
|
||||
for module in self.requested_modules: |
||||
args = self.clib_compiler.find_library(module, self.env, [self.libdir] if self.libdir else []) |
||||
if args is None: |
||||
self._report_dependency_error('Couldn\'t find requested CUDA module \'{}\''.format(module)) |
||||
all_found = False |
||||
else: |
||||
mlog.debug('Link args for CUDA module \'{}\' are {}'.format(module, args)) |
||||
self.lib_modules[module] = args |
||||
|
||||
return all_found |
||||
|
||||
def _is_windows(self): |
||||
return self.env.machines[self.for_machine].is_windows() |
||||
|
||||
def _report_dependency_error(self, msg, ret_val=None): |
||||
if self.required: |
||||
raise DependencyException(msg) |
||||
|
||||
mlog.debug(msg) |
||||
return ret_val |
||||
|
||||
def log_details(self): |
||||
module_str = ', '.join(self.requested_modules) |
||||
return 'modules: ' + module_str |
||||
|
||||
def log_info(self): |
||||
return self.cuda_path if self.cuda_path else '' |
||||
|
||||
def get_requested(self, kwargs): |
||||
candidates = mesonlib.extract_as_list(kwargs, 'modules') |
||||
for c in candidates: |
||||
if not isinstance(c, str): |
||||
raise DependencyException('CUDA module argument is not a string.') |
||||
return candidates |
||||
|
||||
def get_link_args(self, **kwargs): |
||||
args = [] |
||||
if self.libdir: |
||||
args += self.clib_compiler.get_linker_search_args(self.libdir) |
||||
for lib in self.requested_modules: |
||||
args += self.lib_modules[lib] |
||||
return args |
@ -0,0 +1,2 @@ |
||||
exe = executable('prog', 'prog.c', dependencies: dependency('cuda')) |
||||
test('cudatest', exe) |
@ -0,0 +1,19 @@ |
||||
#include <cuda_runtime.h> |
||||
#include <stdio.h> |
||||
|
||||
int cuda_devices() { |
||||
int result = 0; |
||||
cudaGetDeviceCount(&result); |
||||
return result; |
||||
} |
||||
|
||||
int main() { |
||||
int n = cuda_devices(); |
||||
if (n == 0) { |
||||
printf("No CUDA hardware found. Exiting.\n"); |
||||
return 0; |
||||
} |
||||
|
||||
printf("Found %i CUDA devices.\n", n); |
||||
return 0; |
||||
} |
@ -0,0 +1,2 @@ |
||||
exe = executable('prog', 'prog.cc', dependencies: dependency('cuda')) |
||||
test('cudatest', exe) |
@ -0,0 +1,19 @@ |
||||
#include <cuda_runtime.h> |
||||
#include <iostream> |
||||
|
||||
int cuda_devices() { |
||||
int result = 0; |
||||
cudaGetDeviceCount(&result); |
||||
return result; |
||||
} |
||||
|
||||
int main() { |
||||
int n = cuda_devices(); |
||||
if (n == 0) { |
||||
std::cout << "No CUDA hardware found. Exiting.\n"; |
||||
return 0; |
||||
} |
||||
|
||||
std::cout << "Found " << n << " CUDA devices.\n"; |
||||
return 0; |
||||
} |
@ -0,0 +1,6 @@ |
||||
project('cuda dependency', 'c', 'cpp') |
||||
|
||||
subdir('c') |
||||
subdir('cpp') |
||||
subdir('modules') |
||||
subdir('version_reqs') |
@ -0,0 +1,2 @@ |
||||
exe = executable('prog', 'prog.cc', dependencies: dependency('cuda', modules: ['cublas'])) |
||||
test('cudatest', exe) |
@ -0,0 +1,33 @@ |
||||
#include <cuda_runtime.h> |
||||
#include <cublas_v2.h> |
||||
#include <iostream> |
||||
|
||||
int cuda_devices() { |
||||
int result = 0; |
||||
cudaGetDeviceCount(&result); |
||||
return result; |
||||
} |
||||
|
||||
int main() { |
||||
int n = cuda_devices(); |
||||
if (n == 0) { |
||||
std::cout << "No CUDA hardware found. Exiting.\n"; |
||||
return 0; |
||||
} |
||||
|
||||
std::cout << "Found " << n << " CUDA devices.\n"; |
||||
|
||||
cublasHandle_t handle; |
||||
if (cublasCreate(&handle) != CUBLAS_STATUS_SUCCESS) { |
||||
std::cout << "cuBLAS initialization failed. Exiting.\n"; |
||||
return -1; |
||||
} |
||||
|
||||
std::cout << "Initialized cuBLAS\n"; |
||||
if (cublasDestroy(handle) != CUBLAS_STATUS_SUCCESS) {
|
||||
std::cout << "cuBLAS de-initialization failed. Exiting.\n"; |
||||
return -1; |
||||
} |
||||
|
||||
return 0; |
||||
} |
@ -0,0 +1,2 @@ |
||||
exe = executable('prog', 'prog.cc', dependencies: dependency('cuda', version: ['>=8.5', '<10'], required: false, disabler: true)) |
||||
test('cudatest', exe) |
@ -0,0 +1,28 @@ |
||||
#include <cuda_runtime.h> |
||||
#include <iostream> |
||||
|
||||
int cuda_devices() { |
||||
int result = 0; |
||||
cudaGetDeviceCount(&result); |
||||
return result; |
||||
} |
||||
|
||||
int main() { |
||||
std::cout << "Compiled against CUDA version: " << CUDART_VERSION << "\n"; |
||||
int runtime_version = 0; |
||||
cudaError_t r = cudaRuntimeGetVersion(&runtime_version); |
||||
if (r != cudaSuccess) { |
||||
std::cout << "Couldn't obtain CUDA runtime version (error " << r << "). Exiting.\n"; |
||||
return -1; |
||||
} |
||||
std::cout << "CUDA runtime version: " << runtime_version << "\n"; |
||||
|
||||
int n = cuda_devices(); |
||||
if (n == 0) { |
||||
std::cout << "No CUDA hardware found. Exiting.\n"; |
||||
return 0; |
||||
} |
||||
|
||||
std::cout << "Found " << n << " CUDA devices.\n"; |
||||
return 0; |
||||
} |
@ -0,0 +1,4 @@ |
||||
project('cuda dependency', 'cuda') |
||||
|
||||
subdir('modules') |
||||
subdir('version_reqs') |
@ -0,0 +1,2 @@ |
||||
exe = executable('prog', 'prog.cu', dependencies: dependency('cuda', modules: ['cublas'])) |
||||
test('cudatest', exe) |
@ -0,0 +1,33 @@ |
||||
#include <cuda_runtime.h> |
||||
#include <cublas_v2.h> |
||||
#include <iostream> |
||||
|
||||
int cuda_devices() { |
||||
int result = 0; |
||||
cudaGetDeviceCount(&result); |
||||
return result; |
||||
} |
||||
|
||||
int main() { |
||||
int n = cuda_devices(); |
||||
if (n == 0) { |
||||
std::cout << "No CUDA hardware found. Exiting.\n"; |
||||
return 0; |
||||
} |
||||
|
||||
std::cout << "Found " << n << " CUDA devices.\n"; |
||||
|
||||
cublasHandle_t handle; |
||||
if (cublasCreate(&handle) != CUBLAS_STATUS_SUCCESS) { |
||||
std::cout << "cuBLAS initialization failed. Exiting.\n"; |
||||
return -1; |
||||
} |
||||
|
||||
std::cout << "Initialized cuBLAS\n"; |
||||
if (cublasDestroy(handle) != CUBLAS_STATUS_SUCCESS) { |
||||
std::cout << "cuBLAS de-initialization failed. Exiting.\n"; |
||||
return -1; |
||||
} |
||||
|
||||
return 0; |
||||
} |
@ -0,0 +1,2 @@ |
||||
exe = executable('prog', 'prog.cu', dependencies: dependency('cuda', version: ['>=10.1'], required: false, disabler: true)) |
||||
test('cudatest', exe) |
@ -0,0 +1,28 @@ |
||||
#include <cuda_runtime.h> |
||||
#include <iostream> |
||||
|
||||
int cuda_devices() { |
||||
int result = 0; |
||||
cudaGetDeviceCount(&result); |
||||
return result; |
||||
} |
||||
|
||||
int main() { |
||||
std::cout << "Compiled against CUDA version: " << CUDART_VERSION << "\n"; |
||||
int runtime_version = 0; |
||||
cudaError_t r = cudaRuntimeGetVersion(&runtime_version); |
||||
if (r != cudaSuccess) { |
||||
std::cout << "Couldn't obtain CUDA runtime version (error " << r << "). Exiting.\n"; |
||||
return -1; |
||||
} |
||||
std::cout << "CUDA runtime version: " << runtime_version << "\n"; |
||||
|
||||
int n = cuda_devices(); |
||||
if (n == 0) { |
||||
std::cout << "No CUDA hardware found. Exiting.\n"; |
||||
return 0; |
||||
} |
||||
|
||||
std::cout << "Found " << n << " CUDA devices.\n"; |
||||
return 0; |
||||
} |
@ -0,0 +1,8 @@ |
||||
#include <cuda_runtime.h> |
||||
|
||||
__global__ void kernel (void){ |
||||
} |
||||
|
||||
void do_cuda_stuff() { |
||||
kernel<<<1,1>>>(); |
||||
} |
@ -0,0 +1,4 @@ |
||||
project('cuda dependency', 'cpp', 'cuda') |
||||
|
||||
exe = executable('prog', 'prog.cpp', 'kernel.cu', dependencies: dependency('cuda', modules: ['cublas'])) |
||||
test('cudatest', exe) |
@ -0,0 +1,37 @@ |
||||
#include <cuda_runtime.h> |
||||
#include <cublas_v2.h> |
||||
#include <iostream> |
||||
|
||||
void do_cuda_stuff(); |
||||
|
||||
int cuda_devices() { |
||||
int result = 0; |
||||
cudaGetDeviceCount(&result); |
||||
return result; |
||||
} |
||||
|
||||
int main() { |
||||
int n = cuda_devices(); |
||||
if (n == 0) { |
||||
std::cout << "No CUDA hardware found. Exiting.\n"; |
||||
return 0; |
||||
} |
||||
|
||||
std::cout << "Found " << n << " CUDA devices.\n"; |
||||
|
||||
do_cuda_stuff(); |
||||
|
||||
cublasHandle_t handle; |
||||
if (cublasCreate(&handle) != CUBLAS_STATUS_SUCCESS) { |
||||
std::cout << "cuBLAS initialization failed. Exiting.\n"; |
||||
return -1; |
||||
} |
||||
|
||||
std::cout << "Initialized cuBLAS\n"; |
||||
if (cublasDestroy(handle) != CUBLAS_STATUS_SUCCESS) {
|
||||
std::cout << "cuBLAS de-initialization failed. Exiting.\n"; |
||||
return -1; |
||||
} |
||||
|
||||
return 0; |
||||
} |
Loading…
Reference in new issue