Самый легкий способ протестировать на существование cuda-способного GPU от cmake?

У нас есть некоторые ночные машины сборки, которым установили cuda библиотеки, но которым не установили cuda-способный GPU. Эти машины способны к созданию cuda-поддерживающих программ, но они не способны к запущению этих программ.

В нашем автоматизированном ночном процессе сборки наши cmake сценарии используют команду cmake

find_package(CUDA)

определить, установлено ли cuda программное обеспечение. Это устанавливает cmake переменную CUDA_FOUND на платформах, которые имеют cuda установленное программное обеспечение. Здорово и это работает отлично. Когда CUDA_FOUND установлен, нормально создавать cuda-поддерживающие программы. Даже когда машина не имеет никакого cuda-способного GPU.

Но cuda-использование тестовых программ естественно приводит к сбою на не-GPU cuda машины, вызывая наш ночной "грязный" взгляд панелей инструментов. Таким образом, я хочу, чтобы cmake постарался не запускать те тесты на таких машинах. Но я все еще хочу создать cuda программное обеспечение на тех машинах.

После получения положительного CUDA_FOUND результат, я хотел бы протестировать на присутствие фактического GPU, и затем установить переменную, сказать CUDA_GPU_FOUND, отразить это.

Что самый простой путь состоит в том, чтобы заставить cmake тестировать на присутствие cuda-способного gpu?

Это должно работать над тремя платформами: Windows с MSVC, Mac и Linux. (Вот почему мы используем cmake во-первых),

Править: Существует несколько красивых предложений в ответах для как запись программа для тестирования на присутствие GPU. То, что все еще отсутствует, является средствами того, чтобы заставлять CMake скомпилировать и запустить эту программу во время конфигурации. Я подозреваю что TRY_RUN команда в CMake будет очень важна здесь, но к сожалению что команда почти не документирована, и я не могу выяснить, как заставить его работать. Эта часть CMake проблемы могла бы быть намного более трудным вопросом. Возможно, я должен был спросить это как два отдельных вопроса...

14
задан Christopher Bruns 18 February 2010 в 02:52
поделиться

4 ответа

Ответ на этот вопрос состоит из двух частей:

  1. Программа для обнаружения наличия графического процессора с поддержкой cuda.
  2. Код CMake для компиляции, запуска и интерпретации результата этой программы во время настройки.

Что касается части 1, программы сниффинга графических процессоров, я начал с ответа, предоставленного fabrizioM, потому что она очень компактна. Я быстро обнаружил, что мне нужны многие детали, найденные в ответе unknown, чтобы заставить его работать хорошо. В итоге я получил следующий исходный файл на 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} , это одно место, где эти подкаталоги, кажется, естественным образом находятся.

18
ответ дан 1 December 2019 в 07:39
поделиться

Вы можете скомпилировать небольшую программу запроса 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);
}
}
3
ответ дан 1 December 2019 в 07:39
поделиться

Напишите простую программу, например

#include<cuda.h>

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

, и проверьте возвращаемое значение.

9
ответ дан 1 December 2019 в 07:39
поделиться

Я только что написал скрипт на чистом Python, который делает некоторые из тех вещей, которые вам, похоже, нужны (я взял многое из этого из проекта 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'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()
4
ответ дан 1 December 2019 в 07:39
поделиться
Другие вопросы по тегам:

Похожие вопросы: