2010-02-17 5 views
12

Nous avons des machines de génération de nuit qui ont le cuda libraries installé, mais qui n'ont pas de GPU compatible cuda. Ces machines sont capables de construire des programmes compatibles avec Cuda, mais elles ne sont pas capables d'exécuter ces programmes.Le moyen le plus simple de tester l'existence d'un GPU compatible cuda de cmake?

Dans notre processus nocturne automatisé, nos scripts CMake utilisez la commande cmake

find_package(CUDA)

pour déterminer si le logiciel est installé cuda. Cela définit la variable cmake CUDA_FOUND sur les plateformes sur lesquelles le logiciel cuda est installé. C'est génial et ça fonctionne parfaitement. Lorsque CUDA_FOUND est défini, il est possible de créer des programmes compatibles avec Cuda. Même lorsque la machine ne dispose pas de GPU compatible cuda.

Mais les programmes de test utilisant cuda échouent naturellement sur les machines cuda non-GPU, ce qui rend nos tableaux de bord nocturnes "sales". Je veux donc que cmake évite d'exécuter ces tests sur de telles machines. Mais je veux toujours construire le logiciel cuda sur ces machines. Après avoir obtenu un résultat positif CUDA_FOUND, je voudrais tester la présence d'un véritable GPU, puis définir une variable, par exemple CUDA_GPU_FOUND, pour refléter cela.

Quel est le moyen le plus simple pour que cmake teste la présence d'une CPU compatible cuda?

Cela doit fonctionner sur trois plates-formes: Windows avec MSVC, Mac et Linux. (C'est pourquoi nous utilisons cmake en premier lieu)

EDIT: Il ya quelques bonnes suggestions dans les réponses pour comment écrire un programme pour tester la présence d'un GPU. Ce qui manque encore, c'est le moyen d'obtenir CMake pour compiler et exécuter ce programme au moment de la configuration. Je suspecte que la commande TRY_RUN dans CMake sera critique ici, mais malheureusement cette commande est nearly undocumented, et je ne peux pas comprendre comment le faire fonctionner. Cette partie CMake du problème pourrait être une question beaucoup plus difficile. Peut-être que j'aurais dû poser cette question comme deux questions distinctes ...

Répondre

17

La réponse à cette question se compose de deux parties:

  1. Un programme pour détecter la présence d'un GPU CUDA compatible. CMon code pour compiler, exécuter et interpréter le résultat de ce programme au moment de la configuration.

Pour la partie 1, le programme de reniflage gpu, j'ai commencé avec la réponse fournie par fabrizioM parce qu'elle est si compacte. J'ai rapidement découvert que j'avais besoin de beaucoup de détails trouvés dans la réponse d'inconnu pour que ça fonctionne bien.Ce que j'ai fini avec le fichier source C suivant, que je has_cuda_gpu.c nommé:

#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 */ 
} 

Notez que le code de retour est égal à zéro dans le cas où se trouve un GPU permis cuda. En effet, sur l'une de mes machines has-cuda-but-no-GPU, ce programme génère une erreur d'exécution avec un code de sortie différent de zéro. Ainsi, tout code de sortie différent de zéro est interprété comme "cuda ne fonctionne pas sur cette machine".

Vous pourriez vous demander pourquoi je n'utilise pas le mode d'émulation cuda sur des machines non-GPU. C'est parce que le mode d'émulation est buggé. Je veux seulement déboguer mon code, et contourner les bugs dans le code cuda GPU. Je n'ai pas le temps de déboguer l'émulateur.

La deuxième partie du problème est le code cmake pour utiliser ce programme de test. Après un peu de lutte, je l'ai compris. Le bloc suivant fait partie d'un fichier CMakeLists.txt plus:

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) 

Ceci définit une variable booléenne CUDA_HAVE_GPU dans CMake qui peut ensuite être utilisé pour déclencher des opérations conditionnelles.

Il m'a fallu beaucoup de temps pour comprendre que les paramètres d'inclusion et de liaison doivent aller dans la strophe CMAKE_FLAGS, et quelle devrait être la syntaxe. Le try_run documentation est très léger, mais il y a plus d'informations dans le try_compile documentation, qui est une commande étroitement liée. J'avais encore besoin de parcourir le web pour trouver des exemples de try_compile et try_run avant de commencer à travailler.

Un autre détail délicat mais important est le troisième argument à try_run, le "bindir". Vous devriez probablement toujours régler ceci sur ${CMAKE_BINARY_DIR}. En particulier, ne le définissez pas sur ${CMAKE_CURRENT_BINARY_DIR} si vous êtes dans un sous-répertoire de votre projet. CMake s'attend à trouver le sous-répertoire CMakeFiles/CMakeTmp dans bindir et émet des erreurs si ce répertoire n'existe pas. Utilisez simplement ${CMAKE_BINARY_DIR}, qui est un emplacement où ces sous-répertoires semblent résider naturellement.

+0

On peut éviter de maintenir et de compiler un programme séparé en utilisant à la place CMake pour exécuter un outil installé en même temps que le moteur d'exécution CUDA, comme nvidia-smi. Vois ma réponse. – mabraham

3

Vous pouvez compiler un petit programme de requête GPU si cuda a été trouvé. voici une simple que vous pouvez adopter les besoins:

#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 c'est un très bon début pour la partie qui renifle le GPU. Mais j'hésite à accepter cette réponse sans la partie cmake. –

+0

@Christopher pas de problème, malheureusement je ne connais pas cmake (j'utilise automake). http://www.gnu.org/software/hello/manual/autoconf/Runtime.html est une partie pertinente d'autoconf. Peut-être cela vous aidera-t-il à trouver la fonction correspondante de cmake? – Anycorn

7

écrire un programme simple comme

#include<cuda.h> 

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

et vérifier la valeur de retour.

+0

+1 Cette réponse, ainsi que celle de unknown, m'a permis de bien commencer à résoudre ce problème. –

4

Je viens d'écrire un pur script Python qui fait certaines des choses dont vous semblez avoir besoin (j'en ai pris beaucoup dans le projet pystream). Il s'agit simplement d'un wrapper pour certaines fonctions de la bibliothèque d'exécution CUDA (elle utilise ctypes). Regardez la fonction main() pour voir l'exemple d'utilisation. Aussi, sachez que je viens de l'écrire, donc il est susceptible de contenir des bugs. Utiliser avec précaution.

#!/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

C'est une idée intéressante d'utiliser python. De cette façon, la partie cmake inclurait vraisemblablement FIND_PACKAGE (PythonInterp) et EXECUTE_PROCESS (...), ce qui semble être plus simple.D'un autre côté, je suis préoccupé par le fait que ce script python est plutôt long, et il semblerait que cela dépende de certains aspects de l'API CUDA qui pourraient changer. –

+0

D'accord. La classe DeviceProp peut devoir être mise à jour avec chaque nouvelle version d'exécution CUDA. –

+0

Je reçois une erreur: sauf OSError, e: [SyntaxError: syntaxe invalide] dans python 3.5 – programmer

1

Une approche utile consiste à exécuter des programmes qui CUDA a installés, tels que nvidia-smi, pour voir ce qu'ils reviennent.

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

On peut également interroger linux/proc ou lspci. Voir l'exemple complet de CMake au https://github.com/gromacs/gromacs/blob/master/cmake/gmxDetectGpu.cmake

Questions connexes