cuda sample_MultiGPU(3)

C:\ProgramData\NVIDIA Corporation\CUDA Samples\v11.0\0_Simple\simpleMultiGPU

在GPU上执行的归约(reduce)内核函数。该函数用于将输入数组d_Input中的元素求和,并将结果存储在d_Result数组中

__global__ static void reduceKernel(float *d_Result, float *d_Input, int N)
{
    const int     tid = blockIdx.x * blockDim.x + threadIdx.x;
    const int threadN = gridDim.x * blockDim.x;
    float sum = 0;

    for (int pos = tid; pos < N; pos += threadN)
        sum += d_Input[pos];

    d_Result[tid] = sum;
}

这是一个在GPU上执行的归约(reduce)内核函数。该函数用于将输入数组d_Input中的元素求和,并将结果存储在d_Result数组中。

函数首先计算线程的全局唯一ID,通过将块索引乘以块内线程数量,再加上线程索引得到。接着,通过计算全局线程总数threadN,可以确定每个线程需要处理的输入数据位置。

之后,函数使用循环遍历输入数组,从当前线程的位置(tid)开始,以步长为threadN遍历整个数组。在循环中,将输入数组中的元素累加到sum变量中。

最后,将每个线程计算得到的sum值存储到输出数组d_Result中,每个线程的结果对应于其线程ID(tid)。

这个内核函数可以在并行计算中进行归约操作,用于对大规模数据集进行求和等聚合操作。

使用多个GPU进行归约计算的准备工作,包括获取GPU数量、打印GPU的计算能力信息等

TGPUplan      plan[MAX_GPU_COUNT];

    //GPU reduction results
    float     h_SumGPU[MAX_GPU_COUNT];

    float sumGPU;
    double sumCPU, diff;

    int i, j, gpuBase, GPU_N;

    const int  BLOCK_N = 32;
    const int THREAD_N = 256;
    const int  ACCUM_N = BLOCK_N * THREAD_N;
    int ngpus;
    cudaGetDeviceCount(&ngpus);

    for (int i = 0; i < ngpus; i++)
    {
        cudaDeviceProp devProp;
        cudaGetDeviceProperties(&devProp, i);
        printf("Device %d has compute capability %d.%d. \n"i, devProp.major, devProp.minor);
    }

    printf("Starting simpleMultiGPU\n");
    checkCudaErrors(cudaGetDeviceCount(&GPU_N));

    if (GPU_N > MAX_GPU_COUNT)
    {
        GPU_N = MAX_GPU_COUNT;
    }

    printf("CUDA-capable device count: %i\n", GPU_N);

    printf("Generating input data...\n\n");
  1. TGPUplan plan[MAX_GPU_COUNT];:定义了一个TGPUplan类型的数组plan,用于存储每个GPU的计划信息。

  2. float h_SumGPU[MAX_GPU_COUNT];:定义了一个存储每个GPU归约结果的浮点型数组h_SumGPU。

  3. float sumGPU;:定义了一个用于存储GPU归约结果总和的浮点型变量sumGPU。

  4. double sumCPU, diff;:定义了一个用于存储CPU归约结果总和和差值的双精度变量sumCPU和diff。

  5. int i, j, gpuBase, GPU_N;:定义了一些整型变量用于循环和存储GPU数量等信息。

  6. const int BLOCK_N = 32;:定义了每个块中的线程块数量。

  7. const int THREAD_N = 256;:定义了每个线程块中的线程数量。

  8. const int ACCUM_N = BLOCK_N * THREAD_N;:定义了每个GPU需要处理的数据数量。

  9. int ngpus;:定义了整型变量ngpus用于存储GPU的数量。

  10. cudaGetDeviceCount(&ngpus);:获取当前系统中的GPU数量,并将结果存储在ngpus变量中。

  11. for (int i = 0; i < ngpus; i++):循环遍历每个GPU。

  12. cudaDeviceProp devProp;:定义了cudaDeviceProp结构体变量devProp,用于存储GPU的属性信息。

  13. cudaGetDeviceProperties(&devProp, i);:获取第i个GPU的属性信息,并将结果存储在devProp变量中。

  14. printf(“Device %d has compute capability %d.%d. \n”, i, devProp.major, devProp.minor);:打印第i个GPU的计算能力信息。

  15. printf(“Starting simpleMultiGPU\n”);:打印消息表示开始使用多GPU进行归约计算。

  16. checkCudaErrors(cudaGetDeviceCount(&GPU_N));:获取可用的CUDA设备数量,并将结果存储在GPU_N变量中。

  17. if (GPU_N > MAX_GPU_COUNT):如果可用的CUDA设备数量超过了最大GPU数量,则将GPU_N设置为最大GPU数量。

  18. printf(“CUDA-capable device count: %i\n”, GPU_N);:打印可用的CUDA设备数量。

  19. printf(“Generating input data…\n\n”);:打印消息表示正在生成输入数据。
    该代码片段主要展示了使用多个GPU进行归约计算的准备工作,包括获取GPU数量、打印GPU的计算能力信息等。
    接下来是生成输入数据和实际的归约计算部分的代码

为每个图形处理器分配数据范围的过程

for (i = 0; i < GPU_N; i++)
    {
        plan[i].dataN = DATA_N / GPU_N;
    }

    //考虑“奇数”数据大小
    for (i = 0; i < DATA_N % GPU_N; i++)
    {
        plan[i].dataN++;
    }

    //为图形处理器分配数据范围
    gpuBase = 0;

    for (i = 0; i < GPU_N; i++)
    {
        plan[i].h_Sum = h_SumGPU + i;
        gpuBase += plan[i].dataN;
    }
  1. 首先,通过循环遍历每个GPU,将dataN设置为DATA_N / GPU_N,其中DATA_N是数据的总大小。这样可以均匀地将数据分配给每个GPU。

  2. 对于余数部分,使用循环来处理。在每次循环中,将dataN递增1。这样可以确保将余数部分的数据均匀地分配给前几个GPU。

  3. 接下来,使用一个变量gpuBase来跟踪图形处理器的数据范围。通过循环遍历每个GPU,为h_Sum分配一个指针,该指针指向主机上存储每个GPU的结果的数组h_SumGPU的相应位置。然后,更新gpuBase以反映已分配的数据范围。

  4. 通过以上步骤,代码实现了将数据分配给每个图形处理器,并为每个处理器分配了相应的结果存储空间。

为每个GPU分配内存以及为其分配相应的流和主机内存的过程

  for (i = 0; i < GPU_N; i++)
    {
        checkCudaErrors(cudaSetDevice(i));
        checkCudaErrors(cudaStreamCreate(&plan[i].stream));
        //Allocate memory
        checkCudaErrors(cudaMalloc((void **)&plan[i].d_Data, plan[i].dataN * sizeof(float)));
        checkCudaErrors(cudaMalloc((void **)&plan[i].d_Sum, ACCUM_N * sizeof(float)));
        checkCudaErrors(cudaMallocHost((void **)&plan[i].h_Sum_from_device, ACCUM_N * sizeof(float)));
        checkCudaErrors(cudaMallocHost((void **)&plan[i].h_Data, plan[i].dataN * sizeof(float)));

        for (j = 0; j < plan[i].dataN; j++)
        {
            plan[i].h_Data[j] = (float)rand() / (float)RAND_MAX;
        }
    }
  1. 首先,通过循环遍历每个GPU,使用cudaSetDevice将当前设备设置为相应的GPU。

  2. 对于每个GPU,使用cudaStreamCreate创建一个流(stream),该流用于在GPU上执行异步操作。

  3. 分配设备内存。使用cudaMalloc为每个GPU分配输入数据d_Data的内存,大小为plan[i].dataN * sizeof(float)。使用cudaMalloc为每个GPU分配结果数据d_Sum的内存,大小为ACCUM_N * sizeof(float)。

  4. 分配主机内存。使用cudaMallocHost为每个GPU分配结果数据从设备传输到主机的临时缓冲区h_Sum_from_device的内存,大小为ACCUM_N * sizeof(float)。使用cudaMallocHost为每个GPU分配输入数据h_Data的主机内存,大小为plan[i].dataN * sizeof(float)。

  5. 在循环中,为每个GPU的输入数据h_Data随机初始化一些数据。

循环j从0到plan[i].dataN - 1,遍历每个GPU的输入数据数组h_Data的索引。
对于每个索引j,使用(float)rand() / (float)RAND_MAX生成一个随机值,并将其赋值给对应GPU的输入数据h_Data[j]。

  1. 通过以上步骤,代码为每个GPU分配了设备内存和主机内存,并创建了与每个GPU关联的流,为后续的数据传输和计算做准备

在每个GPU上进行计算的过程,并处理GPU结果

StopWatchInterface *timer = NULL;
    sdkCreateTimer(&timer);
    // start the timer
    sdkStartTimer(&timer);
    //Copy data to GPU, launch the kernel and copy data back. All asynchronously
    for (i = 0; i < GPU_N; i++)
    {
        //Set device
        checkCudaErrors(cudaSetDevice(i));

        //Copy input data from CPU
        checkCudaErrors(cudaMemcpyAsync(plan[i].d_Data, plan[i].h_Data, plan[i].dataN * sizeof(float), cudaMemcpyHostToDevice, plan[i].stream));

        //Perform GPU computations
        reduceKernel<<<BLOCK_N, THREAD_N, 0, plan[i].stream>>>(plan[i].d_Sum, plan[i].d_Data, plan[i].dataN);
        getLastCudaError("reduceKernel() execution failed.\n");

        //Read back GPU results
        checkCudaErrors(cudaMemcpyAsync(plan[i].h_Sum_from_device, plan[i].d_Sum, ACCUM_N *sizeof(float), cudaMemcpyDeviceToHost, plan[i].stream));
    }

    //Process GPU results
    for (i = 0; i < GPU_N; i++)
    {
        float sum;

        //Set device
        checkCudaErrors(cudaSetDevice(i));

        //Wait for all operations to finish
        cudaStreamSynchronize(plan[i].stream);

        //Finalize GPU reduction for current subvector
        sum = 0;

        for (j = 0; j < ACCUM_N; j++)
        {
            sum += plan[i].h_Sum_from_device[j];
        }

        *(plan[i].h_Sum) = (float)sum;

        //Shut down this GPU
        checkCudaErrors(cudaFreeHost(plan[i].h_Sum_from_device));
        checkCudaErrors(cudaFree(plan[i].d_Sum));
        checkCudaErrors(cudaFree(plan[i].d_Data));
        checkCudaErrors(cudaStreamDestroy(plan[i].stream));
    }

  1. 创建一个计时器对象StopWatchInterface *timer,并通过sdkCreateTimer()函数进行初始化。

  2. 启动计时器,通过sdkStartTimer(&timer)函数开始计时。

  3. 使用循环遍历每个GPU,进行以下操作:

设置当前设备为第i个GPU,通过cudaSetDevice(i)函数将输入数据从CPU异步复制到GPU,通过cudaMemcpyAsync()函数实现。即将plan[i].h_Data复制到plan[i].d_Data。

执行GPU计算,调用reduceKernel内核函数。使用<<>>语法指定内核的网格和块配置,并将结果存储在plan[i].d_Sum中。
从GPU异步读取计算结果,通过cudaMemcpyAsync()函数实现。即将plan[i].d_Sum复制到plan[i].h_Sum_from_device

  1. 对于每个GPU,进行以下操作:

设置当前设备为第i个GPU,通过cudaSetDevice(i)函数。

等待所有操作完成,通过cudaStreamSynchronize(plan[i].stream)函数进行同步。

对当前子向量进行GPU归约计算,将结果存储在sum变量中。

将归约结果赋值给plan[i].h_Sum指向的内存位置,即*(plan[i].h_Sum) = (float)sum。
释放相关的内存和流资源,包括plan[i].h_Sum_from_device、plan[i].d_Sum、plan[i].d_Data和plan[i].stream。

代码中使用的sdkCreateTimer()、sdkStartTimer()、sdkStopTimer()和sdkGetTimerValue()等函数是NVIDIA提供的辅助函数,用于计时器操作。

计算GPU和CPU结果并进行比较

sumGPU = 0;

    for (i = 0; i < GPU_N; i++)
    {
        sumGPU += h_SumGPU[i];
    }

    sdkStopTimer(&timer);
    printf("  GPU Processing time: %f (ms)\n\n", sdkGetTimerValue(&timer));
    sdkDeleteTimer(&timer);

    // Compute on Host CPU
    printf("Computing with Host CPU...\n\n");

    sumCPU = 0;

    for (i = 0; i < GPU_N; i++)
    {
        for (j = 0; j < plan[i].dataN; j++)
        {
            sumCPU += plan[i].h_Data[j];
        }
    }

    // Compare GPU and CPU results
    printf("Comparing GPU and Host CPU results...\n");
    diff = fabs(sumCPU - sumGPU) / fabs(sumCPU);
    printf("  GPU sum: %f\n  CPU sum: %f\n", sumGPU, sumCPU);
    printf("  Relative difference: %E \n\n", diff);

    // Cleanup and shutdown
    for (i = 0; i < GPU_N; i++)
    {
        checkCudaErrors(cudaSetDevice(i));
        checkCudaErrors(cudaFreeHost(plan[i].h_Data));
    }

    exit((diff < 1e-5) ? EXIT_SUCCESS : EXIT_FAILURE);
  1. 初始化变量sumGPU为0。

  2. 使用循环遍历每个GPU,将其结果累加到sumGPU中。

  3. 停止计时器,通过sdkStopTimer(&timer)函数停止计时。

  4. 使用sdkGetTimerValue(&timer)函数获取GPU计算时间,并输出到控制台。

  5. 计算CPU结果,初始化变量sumCPU为0。

  6. 使用循环遍历每个GPU的数据,将其值累加到sumCPU中。

  7. 比较GPU和CPU的计算结果,计算相对差异,并输出到控制台。

  8. 清理和关闭操作:释放CPU内存资源,包括plan[i].h_Data;使用exit()函数退出程序,根据差异是否小于1e-5判断程序是否执行成功。

总结:
上述代码段首先计算GPU结果的总和sumGPU,并通过计时器获取GPU计算时间。然后,在主机CPU上计算CPU结果的总和sumCPU。最后,比较GPU和CPU的计算结果并输出差异。清理阶段释放CPU内存资源,并根据计算结果的差异判断程序的执行状态。

总结

该代码片段展示了一个使用多个GPU进行并行计算的示例。主要步骤如下:

  1. 初始化多个GPU设备并打印它们的计算能力。
  2. 根据GPU的数量将数据均匀分配给每个GPU。

通过循环遍历每个GPU,将dataN设置为DATA_N / GPU_N,其中DATA_N是数据的总大小。这样可以均匀地将数据分配给每个GPU。
对于余数部分,通过将dataN递增1来处理。这样可以确保所有数据都被正确地分配给每个GPU。

  1. 为每个GPU分配内存空间,并将输入数据从主机内存复制到各个GPU设备。

为每个GPU分配相关的内存和流。在每个GPU上,使用cudaSetDevice设置当前设备,然后使用cudaMalloc和cudaMallocHost分配设备和主机内存。

  1. 使用CUDA核函数在每个GPU上执行并行计算。

在GPU上启动计时器,并使用异步操作进行数据传输和计算。使用cudaMemcpyAsync将输入数据从主机复制到GPU,然后使用reduceKernel函数执行GPU计算,最后使用cudaMemcpyAsync将结果从GPU复制回主机。

  1. 将计算结果从GPU设备复制回主机内存。
  2. 对每个GPU的计算结果进行汇总和处理。

使用cudaStreamSynchronize等待所有操作完成,然后对GPU结果进行汇总和处理。

  1. 对比GPU和主机CPU的计算结果,并计算它们之间的相对差异。
  2. 清理和释放GPU资源。
    代码中涉及的主要函数和概念包括:

cudaGetDeviceCount():获取GPU设备数量。
cudaGetDeviceProperties():获取GPU设备的属性。
cudaSetDevice():设置当前活动的GPU设备。
cudaStreamCreate():创建CUDA流,用于异步执行GPU命令。
cudaMalloc()和cudaMallocHost():在GPU和主机上分配内存空间。
cudaMemcpyAsync():异步复制数据到GPU设备或从GPU设备复制数据回主机。
cudaStreamSynchronize():等待流中的操作完成。
cudaFree()和cudaFreeHost():释放GPU和主机上的内存空间。
getLastCudaError():检查CUDA函数执行是否出错。
sdkCreateTimer()、sdkStartTimer()、sdkStopTimer()和sdkDeleteTimer():创建、启动、停止和删除计时器。

最后,代码比较了GPU和CPU的计算结果,并打印了它们之间的相对差异。如果差异小于给定阈值,则程序返回EXIT_SUCCESS,否则返回EXIT_FAILURE。

总的来说,该代码展示了如何使用多个GPU进行并行计算,并通过动态负载平衡实现了更高的效率和吞吐量。

你可能感兴趣的:(cuda,python,人工智能,linux)