2010-02-17 23 views
12

Tenemos algunas máquinas de construcción nocturna que tienen el cuda libraries instalado, pero que no tienen instalada una GPU compatible con cuda. Estas máquinas son capaces de construir programas habilitados para cuda, pero no son capaces de ejecutar estos programas.¿La manera más fácil de probar la existencia de una GPU compatible con cuda de cmake?

En nuestro proceso de generación automatizada noche, nuestros scripts cmake utilice el comando cmake

find_package(CUDA)

para determinar si está instalado el software CUDA. Esto establece la variable cmake CUDA_FOUND en plataformas que tienen instalado el software cuda. Esto es genial y funciona perfectamente. Cuando se establece CUDA_FOUND, está bien crear programas habilitados para cuda. Incluso cuando la máquina no tiene GPU compatible con cuda.

Pero los programas de prueba que usan cuda naturalmente fallan en las máquinas cpu que no son GPU, haciendo que nuestros tableros nocturnos parezcan "sucios". Así que quiero que cmake evite ejecutar esas pruebas en tales máquinas. Pero todavía quiero construir el software cuda en esas máquinas.

Después de obtener un resultado positivo CUDA_FOUND, me gustaría probar la presencia de una GPU real, y luego establecer una variable, digamos CUDA_GPU_FOUND, para reflejar esto.

¿Cuál es la forma más sencilla de hacer que cmake pruebe la presencia de un gpu compatible con cuda?

Esto necesita funcionar en tres plataformas: Windows con MSVC, Mac y Linux. (Es por eso que usamos cmake en primer lugar)

EDIT: Hay un par de buenas sugerencias en las respuestas de cómo escribir un programa para probar la presencia de una GPU. Lo que aún falta es el medio para que CMake compile y ejecute este programa en el momento de la configuración. Sospecho que el comando TRY_RUN en CMake será crítico aquí, pero desafortunadamente ese comando es nearly undocumented, y no puedo encontrar la manera de hacerlo funcionar. Este CMake parte del problema podría ser una pregunta mucho más difícil. Tal vez debería haber hecho esto como dos preguntas separadas ...

Respuesta

17

La respuesta a esta pregunta consta de dos partes:

  1. Un programa para detectar la presencia de una GPU cuda-capaz.
  2. CMake code para compilar, ejecutar e interpretar el resultado de ese programa en el momento de la configuración.

Para la parte 1, el programa de olfateo de gpu, comencé con la respuesta provista por fabrizioM porque es muy compacta. Rápidamente descubrí que necesitaba muchos de los detalles encontrados en la respuesta de unknown para que funcione bien.Lo que terminó con es el siguiente fichero fuente C, que nombré 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 */ 
} 

en cuenta que el código de retorno es cero en el caso de que se encuentre una GPU CUDA habilitado. Esto se debe a que en una de mis máquinas has-cuda-pero-no-GPU, este programa genera un error de tiempo de ejecución con un código de salida distinto de cero. Por lo tanto, cualquier código de salida distinto de cero se interpreta como "cuda no funciona en esta máquina".

Usted podría preguntarse por qué no utilizo el modo de emulación de CUDA en máquinas no-GPU. Es porque el modo de emulación tiene errores. Solo quiero depurar mi código y solucionar los errores en el código de la GPU de cuda. No tengo tiempo para depurar el emulador.

La segunda parte del problema es el código de cmake para utilizar este programa de prueba. Después de un poco de lucha, lo he descubierto. El siguiente bloque es parte de un archivo más grande 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) 

Esto establece una variable booleana CUDA_HAVE_GPU en cmake que posteriormente se puede utilizar para desencadenar operaciones condicionales.

Me tomó mucho tiempo para darse cuenta de que la incluyen y parámetros de enlace tienen que ir en la CMAKE_FLAGS estrofa, y lo que la sintaxis debe ser. El try_run documentation es muy ligero, pero hay más información en el try_compile documentation, que es un comando estrechamente relacionado. Todavía necesitaba buscar en la web ejemplos de try_compile y try_run antes de hacer que esto funcione.

Otro detalle importante es complicado pero el tercer argumento a try_run, el "bindir". Probablemente siempre deba establecer esto en ${CMAKE_BINARY_DIR}. En particular, no lo configure en ${CMAKE_CURRENT_BINARY_DIR} si se encuentra en un subdirectorio de su proyecto. CMake espera encontrar el subdirectorio CMakeFiles/CMakeTmp dentro de bindir, y arroja errores si ese directorio no existe. Solo use ${CMAKE_BINARY_DIR}, que es una ubicación donde esos subdirectorios parecen residir naturalmente.

+0

Uno puede evitar el mantenimiento y la compilación de un programa separado al usar CMake para ejecutar una herramienta que se instala junto con el tiempo de ejecución de CUDA, como nvidia-smi. Ver mi respuesta – mabraham

3

Puede compilar un pequeño programa de consulta GPU si se encontró cuda. aquí es simple puede adoptar las necesidades:

#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); 
} 
} 
+0

1 este es un muy buen punto de partida para la parte que olfatea la GPU. Pero dudo en aceptar esta respuesta sin la parte de cmake. –

+0

@Christopher no hay problema, lamentablemente no sé cmake (yo uso automake). http://www.gnu.org/software/hello/manual/autoconf/Runtime.html es una parte relevante de autoconf. Tal vez te ayude a encontrar la función cmake correspondiente – Anycorn

7

Escribir un programa simple como

#include<cuda.h> 

int main(){ 
    int deviceCount; 
    cudaError_t e = cudaGetDeviceCount(&deviceCount); 
    return e == cudaSuccess ? deviceCount : -1; 
} 

y comprobar el valor de retorno.

+0

+1 Esta respuesta, junto con las desconocidas, me dio un gran comienzo para resolver este problema. –

4

acabo escribió un script en Python puro, que hace algunas de las cosas que parecen necesitar (Tomé gran parte de este proyecto desde el pystream). Básicamente es solo un contenedor para algunas funciones en la biblioteca de tiempo de ejecución de CUDA (usa ctypes). Mire la función main() para ver ejemplos de uso. Además, tenga en cuenta que acabo de escribirlo, por lo que es probable que contenga errores. Usar con precaución.

#!/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's 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() 
+0

Esa es una idea interesante para usar Python. De esta forma, la parte cmake presumiblemente incluiría FIND_PACKAGE (PythonInterp) y EXECUTE_PROCESS (...), que parece que podría ser más simple.Por otro lado, me preocupa que el script de Python sea bastante largo, y parece que podría depender de aspectos de la API de CUDA que podrían cambiar. –

+0

De acuerdo. La clase DeviceProp puede necesitar ser actualizada con cada nueva versión de tiempo de ejecución de CUDA. –

+0

Aparece un error: excepto OSError, e: [SyntaxError: sintaxis no válida] en python 3.5 – programmer

1

Un enfoque útil es ejecutar programas que CUDA ha instalado, como nvidia-smi, para ver qué devuelven.

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

También se puede consultar linux/proc o lspci. Véase el ejemplo CMake trabajado completamente en https://github.com/gromacs/gromacs/blob/master/cmake/gmxDetectGpu.cmake

Cuestiones relacionadas