Самый простой способ проверить наличие графического процессора cuda для CMake от cmake?
У нас есть ночные машины для сборки, в которых установлены cuda libraries, но у которых нет графического процессора с поддержкой Cuda. Эти машины способны создавать cuda-совместимые программы, но они не могут запускать эти программы.
В нашем автоматизированном процессе ночной сборки наши скрипты cmake используют команду cmake
find_package(CUDA)
чтобы определить, установлено ли программное обеспечение cuda. Это устанавливает переменную cmake CUDA_FOUND
на платформах, на которых установлено программное обеспечение cuda. Это здорово и прекрасно работает. Если параметр CUDA_FOUND
установлен, все в порядке, чтобы создавать программы с поддержкой cuda. Даже когда машина не имеет графического процессора с поддержкой cuda.
Но тестовые программы, использующие cuda, естественно терпят неудачу на машинах cuda, отличных от GPU, что приводит к тому, что наши ночные панели выглядят "грязными". Поэтому я хочу, чтобы cmake не выполнял эти тесты на таких машинах. Но я все еще хочу создать программное обеспечение cuda на этих машинах.
После получения положительного результата CUDA_FOUND
я хотел бы проверить наличие фактического графического процессора, а затем установить переменную, скажем CUDA_GPU_FOUND
, чтобы отразить это.
Каков самый простой способ получить cmake для проверки наличия gpu-совместимого cuda?
Это должно работать на трех платформах: Windows с MSVC, Mac и Linux. (Вот почему мы используем cmake в первую очередь)
РЕДАКТИРОВАТЬ: В ответах есть несколько хороших предложений по написанию программы для проверки наличия графического процессора. То, что все еще отсутствует, является средством получения CMake для компиляции и запуска этой программы во время настройки. Я подозреваю, что команда TRY_RUN
в CMake будет здесь критической, но, к сожалению, эта команда почти недокументирована, и я не могу понять, как сделать он работает. Эта проблема CMake может быть гораздо более сложным вопросом. Возможно, я должен был задать это как два отдельных вопроса...
Ответы
Ответ 1
Ответ на этот вопрос состоит из двух частей:
- Программа для обнаружения присутствия графического процессора, поддерживающего cuda.
- Код CMake для компиляции, запуска и интерпретации результата этой программы во время настройки.
Для части 1, программы gpu sniffing, я начал с ответа, предоставленного fabrizioM, потому что он настолько компактен. Я быстро обнаружил, что мне нужно много деталей, найденных в неизвестном ответе, чтобы заставить его работать хорошо. В результате я получил следующий исходный файл C, который я назвал has_cuda_gpu.c
:
#include <stdio.h>
#include <cuda_runtime.h>
int main() {
int deviceCount, device;
int gpuDeviceCount = 0;
struct cudaDeviceProp properties;
cudaError_t cudaResultCode = cudaGetDeviceCount(&deviceCount);
if (cudaResultCode != cudaSuccess)
deviceCount = 0;
/* machines with no GPUs can still report one emulation device */
for (device = 0; device < deviceCount; ++device) {
cudaGetDeviceProperties(&properties, device);
if (properties.major != 9999) /* 9999 means emulation only */
++gpuDeviceCount;
}
printf("%d GPU CUDA device(s) found\n", gpuDeviceCount);
/* don't just return the number of gpus, because other runtime cuda
errors can also yield non-zero return values */
if (gpuDeviceCount > 0)
return 0; /* success */
else
return 1; /* failure */
}
Обратите внимание, что код возврата равен нулю в случае, когда найден графический объект с поддержкой cuda. Это связано с тем, что на одной из моих машин has-cuda-but-no-GPU эта программа генерирует ошибку времени выполнения с ненулевым кодом выхода. Таким образом, любой ненулевой код выхода интерпретируется как "cuda не работает на этой машине".
Вы можете спросить, почему я не использую режим эмуляции cuda на компьютерах, отличных от GPU. Это связано с тем, что режим эмуляции неисправен. Я только хочу отлаживать свой код и работать с ошибками в коде CUDA GPU. У меня нет времени для отладки эмулятора.
Вторая часть проблемы - это код cmake для использования этой тестовой программы. После некоторой борьбы я понял это. Следующий блок является частью более крупного файла CMakeLists.txt
:
find_package(CUDA)
if(CUDA_FOUND)
try_run(RUN_RESULT_VAR COMPILE_RESULT_VAR
${CMAKE_BINARY_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/has_cuda_gpu.c
CMAKE_FLAGS
-DINCLUDE_DIRECTORIES:STRING=${CUDA_TOOLKIT_INCLUDE}
-DLINK_LIBRARIES:STRING=${CUDA_CUDART_LIBRARY}
COMPILE_OUTPUT_VARIABLE COMPILE_OUTPUT_VAR
RUN_OUTPUT_VARIABLE RUN_OUTPUT_VAR)
message("${RUN_OUTPUT_VAR}") # Display number of GPUs found
# COMPILE_RESULT_VAR is TRUE when compile succeeds
# RUN_RESULT_VAR is zero when a GPU is found
if(COMPILE_RESULT_VAR AND NOT RUN_RESULT_VAR)
set(CUDA_HAVE_GPU TRUE CACHE BOOL "Whether CUDA-capable GPU is present")
else()
set(CUDA_HAVE_GPU FALSE CACHE BOOL "Whether CUDA-capable GPU is present")
endif()
endif(CUDA_FOUND)
Это устанавливает логическую переменную CUDA_HAVE_GPU
в cmake, которая впоследствии может использоваться для запуска условных операций.
Мне потребовалось много времени, чтобы выяснить, что параметры include и link должны идти в строфе CMAKE_FLAGS, и каков должен быть синтаксис. Документация try_run очень легкая, но в документации try_compile есть дополнительная информация, который является тесно связанной командой. Мне все еще нужно было прочесывать веб-страницы для примеров try_compile и try_run, прежде чем заставить это работать.
Еще одна сложная, но важная деталь - это третий аргумент try_run
, "bindir". Вероятно, вы должны всегда устанавливать значение ${CMAKE_BINARY_DIR}
. В частности, не устанавливайте его в ${CMAKE_CURRENT_BINARY_DIR}
, если вы находитесь в подкаталоге вашего проекта. CMake рассчитывает найти подкаталог CMakeFiles/CMakeTmp
внутри bindir и выводит ошибки, если этот каталог не существует. Просто используйте ${CMAKE_BINARY_DIR}
, который является одним местом, где эти подкаталоги, кажется, находятся в естественном состоянии.
Ответ 2
Напишите простую программу, например
#include<cuda.h>
int main (){
int deviceCount;
cudaError_t e = cudaGetDeviceCount(&deviceCount);
return e == cudaSuccess ? deviceCount : -1;
}
и проверьте возвращаемое значение.
Ответ 3
Я просто написал чистый Python script, который делает некоторые из вещей, которые вам кажутся вам (я взял много из этого проекта pystream). Это в основном просто оболочка для некоторых функций в библиотеке времени выполнения CUDA (она использует ctypes). Посмотрите на функцию main(), чтобы увидеть пример использования. Кроме того, имейте в виду, что я просто написал его, поэтому он может содержать ошибки. Следует использовать с осторожностью.
#!/bin/bash
import sys
import platform
import ctypes
"""
cudart.py: used to access pars of the CUDA runtime library.
Most of this code was lifted from the pystream project (it BSD licensed):
http://code.google.com/p/pystream
Note that this is likely to only work with CUDA 2.3
To extend to other versions, you may need to edit the DeviceProp Class
"""
cudaSuccess = 0
errorDict = {
1: 'MissingConfigurationError',
2: 'MemoryAllocationError',
3: 'InitializationError',
4: 'LaunchFailureError',
5: 'PriorLaunchFailureError',
6: 'LaunchTimeoutError',
7: 'LaunchOutOfResourcesError',
8: 'InvalidDeviceFunctionError',
9: 'InvalidConfigurationError',
10: 'InvalidDeviceError',
11: 'InvalidValueError',
12: 'InvalidPitchValueError',
13: 'InvalidSymbolError',
14: 'MapBufferObjectFailedError',
15: 'UnmapBufferObjectFailedError',
16: 'InvalidHostPointerError',
17: 'InvalidDevicePointerError',
18: 'InvalidTextureError',
19: 'InvalidTextureBindingError',
20: 'InvalidChannelDescriptorError',
21: 'InvalidMemcpyDirectionError',
22: 'AddressOfConstantError',
23: 'TextureFetchFailedError',
24: 'TextureNotBoundError',
25: 'SynchronizationError',
26: 'InvalidFilterSettingError',
27: 'InvalidNormSettingError',
28: 'MixedDeviceExecutionError',
29: 'CudartUnloadingError',
30: 'UnknownError',
31: 'NotYetImplementedError',
32: 'MemoryValueTooLargeError',
33: 'InvalidResourceHandleError',
34: 'NotReadyError',
0x7f: 'StartupFailureError',
10000: 'ApiFailureBaseError'}
try:
if platform.system() == "Microsoft":
_libcudart = ctypes.windll.LoadLibrary('cudart.dll')
elif platform.system()=="Darwin":
_libcudart = ctypes.cdll.LoadLibrary('libcudart.dylib')
else:
_libcudart = ctypes.cdll.LoadLibrary('libcudart.so')
_libcudart_error = None
except OSError, e:
_libcudart_error = e
_libcudart = None
def _checkCudaStatus(status):
if status != cudaSuccess:
eClassString = errorDict[status]
# Get the class by name from the top level of this module
eClass = globals()[eClassString]
raise eClass()
def _checkDeviceNumber(device):
assert isinstance(device, int), "device number must be an int"
assert device >= 0, "device number must be greater than 0"
assert device < 2**8-1, "device number must be < 255"
# cudaDeviceProp
class DeviceProp(ctypes.Structure):
_fields_ = [
("name", 256*ctypes.c_char), # < ASCII string identifying device
("totalGlobalMem", ctypes.c_size_t), # < Global memory available on device in bytes
("sharedMemPerBlock", ctypes.c_size_t), # < Shared memory available per block in bytes
("regsPerBlock", ctypes.c_int), # < 32-bit registers available per block
("warpSize", ctypes.c_int), # < Warp size in threads
("memPitch", ctypes.c_size_t), # < Maximum pitch in bytes allowed by memory copies
("maxThreadsPerBlock", ctypes.c_int), # < Maximum number of threads per block
("maxThreadsDim", 3*ctypes.c_int), # < Maximum size of each dimension of a block
("maxGridSize", 3*ctypes.c_int), # < Maximum size of each dimension of a grid
("clockRate", ctypes.c_int), # < Clock frequency in kilohertz
("totalConstMem", ctypes.c_size_t), # < Constant memory available on device in bytes
("major", ctypes.c_int), # < Major compute capability
("minor", ctypes.c_int), # < Minor compute capability
("textureAlignment", ctypes.c_size_t), # < Alignment requirement for textures
("deviceOverlap", ctypes.c_int), # < Device can concurrently copy memory and execute a kernel
("multiProcessorCount", ctypes.c_int), # < Number of multiprocessors on device
("kernelExecTimeoutEnabled", ctypes.c_int), # < Specified whether there is a run time limit on kernels
("integrated", ctypes.c_int), # < Device is integrated as opposed to discrete
("canMapHostMemory", ctypes.c_int), # < Device can map host memory with cudaHostAlloc/cudaHostGetDevicePointer
("computeMode", ctypes.c_int), # < Compute mode (See ::cudaComputeMode)
("__cudaReserved", 36*ctypes.c_int),
]
def __str__(self):
return """NVidia GPU Specifications:
Name: %s
Total global mem: %i
Shared mem per block: %i
Registers per block: %i
Warp size: %i
Mem pitch: %i
Max threads per block: %i
Max treads dim: (%i, %i, %i)
Max grid size: (%i, %i, %i)
Total const mem: %i
Compute capability: %i.%i
Clock Rate (GHz): %f
Texture alignment: %i
""" % (self.name, self.totalGlobalMem, self.sharedMemPerBlock,
self.regsPerBlock, self.warpSize, self.memPitch,
self.maxThreadsPerBlock,
self.maxThreadsDim[0], self.maxThreadsDim[1], self.maxThreadsDim[2],
self.maxGridSize[0], self.maxGridSize[1], self.maxGridSize[2],
self.totalConstMem, self.major, self.minor,
float(self.clockRate)/1.0e6, self.textureAlignment)
def cudaGetDeviceCount():
if _libcudart is None: return 0
deviceCount = ctypes.c_int()
status = _libcudart.cudaGetDeviceCount(ctypes.byref(deviceCount))
_checkCudaStatus(status)
return deviceCount.value
def getDeviceProperties(device):
if _libcudart is None: return None
_checkDeviceNumber(device)
props = DeviceProp()
status = _libcudart.cudaGetDeviceProperties(ctypes.byref(props), device)
_checkCudaStatus(status)
return props
def getDriverVersion():
if _libcudart is None: return None
version = ctypes.c_int()
_libcudart.cudaDriverGetVersion(ctypes.byref(version))
v = "%d.%d" % (version.value//1000,
version.value%100)
return v
def getRuntimeVersion():
if _libcudart is None: return None
version = ctypes.c_int()
_libcudart.cudaRuntimeGetVersion(ctypes.byref(version))
v = "%d.%d" % (version.value//1000,
version.value%100)
return v
def getGpuCount():
count=0
for ii in range(cudaGetDeviceCount()):
props = getDeviceProperties(ii)
if props.major!=9999: count+=1
return count
def getLoadError():
return _libcudart_error
version = getDriverVersion()
if version is not None and not version.startswith('2.3'):
sys.stdout.write("WARNING: Driver version %s may not work with %s\n" %
(version, sys.argv[0]))
version = getRuntimeVersion()
if version is not None and not version.startswith('2.3'):
sys.stdout.write("WARNING: Runtime version %s may not work with %s\n" %
(version, sys.argv[0]))
def main():
sys.stdout.write("Driver version: %s\n" % getDriverVersion())
sys.stdout.write("Runtime version: %s\n" % getRuntimeVersion())
nn = cudaGetDeviceCount()
sys.stdout.write("Device count: %s\n" % nn)
for ii in range(nn):
props = getDeviceProperties(ii)
sys.stdout.write("\nDevice %d:\n" % ii)
#sys.stdout.write("%s" % props)
for f_name, f_type in props._fields_:
attr = props.__getattribute__(f_name)
sys.stdout.write( " %s: %s\n" % (f_name, attr))
gpuCount = getGpuCount()
if gpuCount > 0:
sys.stdout.write("\n")
sys.stdout.write("GPU count: %d\n" % getGpuCount())
e = getLoadError()
if e is not None:
sys.stdout.write("There was an error loading a library:\n%s\n\n" % e)
if __name__=="__main__":
main()
Ответ 4
Вы можете скомпилировать небольшую программу запросов GPU, если cuda найден. вот простой, который вы можете принять:
#include <stdlib.h>
#include <stdio.h>
#include <cuda.h>
#include <cuda_runtime.h>
int main(int argc, char** argv) {
int ct,dev;
cudaError_t code;
struct cudaDeviceProp prop;
cudaGetDeviceCount(&ct);
code = cudaGetLastError();
if(code) printf("%s\n", cudaGetErrorString(code));
if(ct == 0) {
printf("Cuda device not found.\n");
exit(0);
}
printf("Found %i Cuda device(s).\n",ct);
for (dev = 0; dev < ct; ++dev) {
printf("Cuda device %i\n", dev);
cudaGetDeviceProperties(&prop,dev);
printf("\tname : %s\n", prop.name);
printf("\ttotalGlobablMem: %lu\n", (unsigned long)prop.totalGlobalMem);
printf("\tsharedMemPerBlock: %i\n", prop.sharedMemPerBlock);
printf("\tregsPerBlock: %i\n", prop.regsPerBlock);
printf("\twarpSize: %i\n", prop.warpSize);
printf("\tmemPitch: %i\n", prop.memPitch);
printf("\tmaxThreadsPerBlock: %i\n", prop.maxThreadsPerBlock);
printf("\tmaxThreadsDim: %i, %i, %i\n", prop.maxThreadsDim[0], prop.maxThreadsDim[1], prop.maxThreadsDim[2]);
printf("\tmaxGridSize: %i, %i, %i\n", prop.maxGridSize[0], prop.maxGridSize[1], prop.maxGridSize[2]);
printf("\tclockRate: %i\n", prop.clockRate);
printf("\ttotalConstMem: %i\n", prop.totalConstMem);
printf("\tmajor: %i\n", prop.major);
printf("\tminor: %i\n", prop.minor);
printf("\ttextureAlignment: %i\n", prop.textureAlignment);
printf("\tdeviceOverlap: %i\n", prop.deviceOverlap);
printf("\tmultiProcessorCount: %i\n", prop.multiProcessorCount);
}
}
Ответ 5
Один полезный подход - запустить программы, которые CUDA установил, например nvidia-smi, чтобы увидеть, что они возвращают.
find_program(_nvidia_smi "nvidia-smi")
if (_nvidia_smi)
set(DETECT_GPU_COUNT_NVIDIA_SMI 0)
# execute nvidia-smi -L to get a short list of GPUs available
exec_program(${_nvidia_smi_path} ARGS -L
OUTPUT_VARIABLE _nvidia_smi_out
RETURN_VALUE _nvidia_smi_ret)
# process the stdout of nvidia-smi
if (_nvidia_smi_ret EQUAL 0)
# convert string with newlines to list of strings
string(REGEX REPLACE "\n" ";" _nvidia_smi_out "${_nvidia_smi_out}")
foreach(_line ${_nvidia_smi_out})
if (_line MATCHES "^GPU [0-9]+:")
math(EXPR DETECT_GPU_COUNT_NVIDIA_SMI "${DETECT_GPU_COUNT_NVIDIA_SMI}+1")
# the UUID is not very useful for the user, remove it
string(REGEX REPLACE " \\(UUID:.*\\)" "" _gpu_info "${_line}")
if (NOT _gpu_info STREQUAL "")
list(APPEND DETECT_GPU_INFO "${_gpu_info}")
endif()
endif()
endforeach()
check_num_gpu_info(${DETECT_GPU_COUNT_NVIDIA_SMI} DETECT_GPU_INFO)
set(DETECT_GPU_COUNT ${DETECT_GPU_COUNT_NVIDIA_SMI})
endif()
endif()
Можно также запросить linux/proc или lspci. См. Полностью обработанный пример CMake в https://github.com/gromacs/gromacs/blob/master/cmake/gmxDetectGpu.cmake