C:\ProgramData\NVIDIA Corporation\CUDA Samples\v11.0\0_Simple\simpleMultiGPU
__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)。
这个内核函数可以在并行计算中进行归约操作,用于对大规模数据集进行求和等聚合操作。
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");
TGPUplan plan[MAX_GPU_COUNT];:定义了一个TGPUplan类型的数组plan,用于存储每个GPU的计划信息。
float h_SumGPU[MAX_GPU_COUNT];:定义了一个存储每个GPU归约结果的浮点型数组h_SumGPU。
float sumGPU;:定义了一个用于存储GPU归约结果总和的浮点型变量sumGPU。
double sumCPU, diff;:定义了一个用于存储CPU归约结果总和和差值的双精度变量sumCPU和diff。
int i, j, gpuBase, GPU_N;:定义了一些整型变量用于循环和存储GPU数量等信息。
const int BLOCK_N = 32;:定义了每个块中的线程块数量。
const int THREAD_N = 256;:定义了每个线程块中的线程数量。
const int ACCUM_N = BLOCK_N * THREAD_N;:定义了每个GPU需要处理的数据数量。
int ngpus;:定义了整型变量ngpus用于存储GPU的数量。
cudaGetDeviceCount(&ngpus);:获取当前系统中的GPU数量,并将结果存储在ngpus变量中。
for (int i = 0; i < ngpus; i++):循环遍历每个GPU。
cudaDeviceProp devProp;:定义了cudaDeviceProp结构体变量devProp,用于存储GPU的属性信息。
cudaGetDeviceProperties(&devProp, i);:获取第i个GPU的属性信息,并将结果存储在devProp变量中。
printf(“Device %d has compute capability %d.%d. \n”, i, devProp.major, devProp.minor);:打印第i个GPU的计算能力信息。
printf(“Starting simpleMultiGPU\n”);:打印消息表示开始使用多GPU进行归约计算。
checkCudaErrors(cudaGetDeviceCount(&GPU_N));:获取可用的CUDA设备数量,并将结果存储在GPU_N变量中。
if (GPU_N > MAX_GPU_COUNT):如果可用的CUDA设备数量超过了最大GPU数量,则将GPU_N设置为最大GPU数量。
printf(“CUDA-capable device count: %i\n”, GPU_N);:打印可用的CUDA设备数量。
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;
}
首先,通过循环遍历每个GPU,将dataN设置为DATA_N / GPU_N,其中DATA_N是数据的总大小。这样可以均匀地将数据分配给每个GPU。
对于余数部分,使用循环来处理。在每次循环中,将dataN递增1。这样可以确保将余数部分的数据均匀地分配给前几个GPU。
接下来,使用一个变量gpuBase来跟踪图形处理器的数据范围。通过循环遍历每个GPU,为h_Sum分配一个指针,该指针指向主机上存储每个GPU的结果的数组h_SumGPU的相应位置。然后,更新gpuBase以反映已分配的数据范围。
通过以上步骤,代码实现了将数据分配给每个图形处理器,并为每个处理器分配了相应的结果存储空间。
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;
}
}
首先,通过循环遍历每个GPU,使用cudaSetDevice将当前设备设置为相应的GPU。
对于每个GPU,使用cudaStreamCreate创建一个流(stream),该流用于在GPU上执行异步操作。
分配设备内存。使用cudaMalloc为每个GPU分配输入数据d_Data的内存,大小为plan[i].dataN * sizeof(float)。使用cudaMalloc为每个GPU分配结果数据d_Sum的内存,大小为ACCUM_N * sizeof(float)。
分配主机内存。使用cudaMallocHost为每个GPU分配结果数据从设备传输到主机的临时缓冲区h_Sum_from_device的内存,大小为ACCUM_N * sizeof(float)。使用cudaMallocHost为每个GPU分配输入数据h_Data的主机内存,大小为plan[i].dataN * sizeof(float)。
在循环中,为每个GPU的输入数据h_Data随机初始化一些数据。
循环j从0到plan[i].dataN - 1,遍历每个GPU的输入数据数组h_Data的索引。
对于每个索引j,使用(float)rand() / (float)RAND_MAX生成一个随机值,并将其赋值给对应GPU的输入数据h_Data[j]。
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));
}
创建一个计时器对象StopWatchInterface *timer,并通过sdkCreateTimer()函数进行初始化。
启动计时器,通过sdkStartTimer(&timer)函数开始计时。
使用循环遍历每个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
设置当前设备为第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提供的辅助函数,用于计时器操作。
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);
初始化变量sumGPU为0。
使用循环遍历每个GPU,将其结果累加到sumGPU中。
停止计时器,通过sdkStopTimer(&timer)函数停止计时。
使用sdkGetTimerValue(&timer)函数获取GPU计算时间,并输出到控制台。
计算CPU结果,初始化变量sumCPU为0。
使用循环遍历每个GPU的数据,将其值累加到sumCPU中。
比较GPU和CPU的计算结果,计算相对差异,并输出到控制台。
清理和关闭操作:释放CPU内存资源,包括plan[i].h_Data;使用exit()函数退出程序,根据差异是否小于1e-5判断程序是否执行成功。
总结:
上述代码段首先计算GPU结果的总和sumGPU,并通过计时器获取GPU计算时间。然后,在主机CPU上计算CPU结果的总和sumCPU。最后,比较GPU和CPU的计算结果并输出差异。清理阶段释放CPU内存资源,并根据计算结果的差异判断程序的执行状态。
该代码片段展示了一个使用多个GPU进行并行计算的示例。主要步骤如下:
通过循环遍历每个GPU,将dataN设置为DATA_N / GPU_N,其中DATA_N是数据的总大小。这样可以均匀地将数据分配给每个GPU。
对于余数部分,通过将dataN递增1来处理。这样可以确保所有数据都被正确地分配给每个GPU。
为每个GPU分配相关的内存和流。在每个GPU上,使用cudaSetDevice设置当前设备,然后使用cudaMalloc和cudaMallocHost分配设备和主机内存。
在GPU上启动计时器,并使用异步操作进行数据传输和计算。使用cudaMemcpyAsync将输入数据从主机复制到GPU,然后使用reduceKernel函数执行GPU计算,最后使用cudaMemcpyAsync将结果从GPU复制回主机。
使用cudaStreamSynchronize等待所有操作完成,然后对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进行并行计算,并通过动态负载平衡实现了更高的效率和吞吐量。