أسهل طريقة لاختبار لوجود وحدة معالجة الرسومات CUDA قادرة على cmake؟

StackOverflow https://stackoverflow.com/questions/2285185

سؤال

لدينا بعض آلات البناء الليلية التي لديها مكتبات CUDA تم تثبيته ، ولكن ليس لديه وحدة معالجة الرسومات 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 لاختبار وجود وحدة معالجة الرسومات القادر على CUDA؟

يحتاج هذا إلى العمل على ثلاثة منصات: Windows مع MSVC و Mac و Linux. (لهذا السبب نستخدم Cmake في المقام الأول)

تعديل: هناك بضعة اقتراحات جيدة المظهر في الإجابات حول كيفية كتابة برنامج لاختبار لوجود وحدة معالجة الرسومات. ما لا يزال مفقودًا هو وسيلة الحصول على cmake لتجميع وتشغيل هذا البرنامج في وقت التكوين. أظن أن TRY_RUN سيكون الأمر في Cmake أمرًا بالغ الأهمية هنا ، ولكن لسوء الحظ هذا الأمر تقريبا غير موثقة, ، ولا يمكنني معرفة كيفية جعلها تعمل. قد يكون هذا الجزء Cmake من المشكلة سؤالًا أكثر صعوبة. ربما كان ينبغي علي طرح هذا كأسفين منفصلين ...

هل كانت مفيدة؟

المحلول

تتكون الإجابة على هذا السؤال من جزأين:

  1. برنامج لاكتشاف وجود وحدة معالجة الرسومات CUDA قادرة على cuda.
  2. رمز CMAKE لتجميع وتشغيل وتفسير نتيجة هذا البرنامج في وقت التكوين.

بالنسبة للجزء 1 ، برنامج استنشاق GPU ، بدأت بالإجابة التي قدمها 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 ولكن لا GPU ، يولد هذا البرنامج خطأ في وقت التشغيل مع رمز الخروج غير الصفر. لذلك يتم تفسير أي رمز خروج غير صفري على أنه "لا يعمل CUDA على هذا الجهاز".

قد تسأل لماذا لا أستخدم وضع مضاهاة CUDA على آلات غير GPU. ذلك لأن وضع المحاكاة هو عربات التي تجرها الدواب. أريد فقط تصحيح الكود الخاص بي ، وأعمل حول الأخطاء في كود GPU CUDA. ليس لدي وقت لتصحيح المحاكي.

الجزء الثاني من المشكلة هو رمز 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 والذي يمكن استخدامه لاحقًا لإثارة العمليات المشروطة.

استغرق الأمر مني وقتًا طويلاً لمعرفة أن معلمات التضمين والربط تحتاج إلى الذهاب في CMAKE_FLAGS Stanza ، وما ينبغي أن يكون بناء الجملة. ال توثيق 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}, ، وهو مكان واحد يبدو فيه أن هذه الدلالات الفرعية يقيمون بشكل طبيعي.

نصائح أخرى

اكتب برنامجًا بسيطًا مثل

#include<cuda.h>

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

وتحقق من قيمة الإرجاع.

لقد كتبت للتو نصًا نقيًا للبيثون يقوم ببعض الأشياء التي يبدو أنك تحتاجها (أخذت الكثير من هذا من مشروع Pystream). إنه في الأساس مجرد غلاف لبعض الوظائف في مكتبة وقت تشغيل CUDA (يستخدم ctypes). انظر إلى الوظيفة الرئيسية () لرؤية الاستخدام على سبيل المثال. أيضًا ، كن على علم بأنني كتبت للتو ، لذلك من المحتمل أن تحتوي على الأخطاء. استخدم بحذر.

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

يمكنك تجميع برنامج استعلام 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);
}
}

تتمثل إحدى الطرق المفيدة في تشغيل البرامج التي قامت بها 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

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top