ToB企服应用市场:ToB评测及商务社交产业平台

标题: CUDA从入门到精通(六)——CUDA编程模型(二) [打印本页]

作者: 刘俊凯    时间: 2024-12-20 09:01
标题: CUDA从入门到精通(六)——CUDA编程模型(二)
1. 核函数范例限定符

CUDA 核函数的常用函数范例限定符及其相关信息的表格:
限定符执行端调用方式备注__global__装备端(GPU)从主机代码使用 <<<...>>> 调用核函数用于声明核函数,在 GPU 上执行。只能从主机代码调用。通常没有返回值。__device__装备端(GPU)只能从装备代码(核函数或其他装备函数)调用用于声明装备函数,只能在 GPU 上执行,不能从主机代码调用。__host__主机端(CPU)只能从主机代码调用用于声明主机函数,必须在 CPU 上执行,不能从装备代码调用。__host__ __device__主机端(CPU)和装备端(GPU)可以从主机或装备代码调用该函数可以在主机和装备上执行,适用于需要兼容主机和装备的通用函数。__launch_bounds__装备端(GPU)用于核函数声明用于提示编译器优化线程块的巨细和寄存器的使用。__restrict__装备端(GPU)用于指针参数声明用于声明指针,告诉编译器该指针所指向的内存不会被其他指针修改,有助于性能优化。
在 CUDA 编程中,核函数(kernel functions)是由 GPU 上的线程执行的函数。只管 CUDA 提供了强盛的并行计算能力,但在使用核函数时也存在一些限制。以下是一些重要的限制:
2. 核函数限制

1. 返回值限制


2. 线程和块的限制


3. 内存限制


4. 装备函数限制


5. 递归限制


6. 线程同步限制


7. 装备属性限制


8. 装备内存分配限制


9. 计算能力限制


10. 装备和主机之间的数据传输


3.核函数计时

在 CUDA 编程中,计时核函数的执行时间是评估性能的重要步骤。可以使用 CUDA 提供的事件(events)来精确测量核函数的执行时间。以下是实现核函数计时的步骤和示例代码。
1. 使用 CUDA 事件计时

CUDA 事件是用于测量时间的高精度工具。通过创建事件并在核函数执行前后纪录时间,可以计算出核函数的执行时间。
  1. #include <iostream>
  2. #include <cuda_runtime.h>
  3. __global__ void kernel_function() {
  4.     // 核函数代码
  5.     int idx = threadIdx.x + blockIdx.x * blockDim.x;
  6.     // 进行一些计算 if (idx < 1000) {
  7.         // 示例计算
  8.         float value = idx * 2.0f;
  9.     }
  10. }
  11. int main() {
  12.     // 创建 CUDA 事件
  13.     cudaEvent_t start, stop;
  14.     cudaEventCreate(&start);
  15.     cudaEventCreate(&stop);
  16.     // 设置线程块和网格大小
  17.     int blockSize = 256;
  18.     int numBlocks = (1000 + blockSize - 1) / blockSize;
  19.     // 记录开始事件
  20.     cudaEventRecord(start);
  21.     // 调用核函数 kernel_function<<<numBlocks, blockSize>>>();
  22.     // 记录结束事件 cudaEventRecord(stop);
  23.     // 等待事件完成
  24.     cudaEventSynchronize(stop);
  25.     // 计算时间 float milliseconds = 0;
  26.     cudaEventElapsedTime(&milliseconds, start, stop);
  27.     // 输出执行时间
  28.     std::cout << "Kernel execution time: " << milliseconds << " ms" << std::endl;
  29.     // 清理事件
  30.     cudaEventDestroy(start);
  31.     cudaEventDestroy(stop);
  32.     return 0;
  33. }
复制代码

除了使用 CUDA 提供的 硬件性能计数器(如 CPI计时器)外,您还可以基于 CPU计时器nvprof 工具举行核函数执行时间的计时。下面我会具体介绍这两种方法。
2. 基于 CPU 计时器计时

虽然 CUDA 核函数运行在 GPU 上,但我们仍然可以使用 CPU计时器 来测量 CUDA 程序的执行时间,尤其是对核函数调用前后以及数据传输的时间举行测量。常用的 CPU 计时器有 std::chrono 和 clock(),它们可以用于测量 CPU 时间。
std::chrono 是 C++11 引入的时间库,提供高精度计时器,可以用来精确地测量 CUDA 核函数的执行时间。std::chrono::high_resolution_clock 是一个高精度时钟,它提供了较高的时间分辨率。
  1. #include <iostream>
  2. #include <chrono>
  3. #include <cuda_runtime.h>
  4. __global__ void kernel_function() {
  5.     int idx = threadIdx.x + blockIdx.x * blockDim.x;
  6.     // 核函数中进行一些计算
  7.     if (idx < 1000) {
  8.         float value = idx * 2.0f;
  9.     }
  10. }
  11. int main() {
  12.     // 使用 std::chrono 高精度计时器
  13.     auto start = std::chrono::high_resolution_clock::now();
  14.     // 设置线程块和网格大小
  15.     int blockSize = 256;
  16.     int numBlocks = (1000 + blockSize - 1) / blockSize;
  17.     // 调用核函数
  18.     kernel_function<<<numBlocks, blockSize>>>();
  19.     // 等待核函数执行完毕
  20.     cudaDeviceSynchronize();
  21.     // 记录结束时间
  22.     auto end = std::chrono::high_resolution_clock::now();
  23.     // 计算执行时间
  24.     std::chrono::duration<float> duration = end - start;
  25.     std::cout << "Kernel execution time: " << duration.count() << " seconds." << std::endl;
  26.     return 0;
  27. }
复制代码

这种方法适用于需要在 主机端(CPU)计时 CUDA 核函数的场景,但需要留意的是,它只能计时核函数的总执行时间,不能提供 GPU 上具体的硬件性能数据。
3. 使用 nvprof 计时

nvprof 是 NVIDIA Profiler,一个命令行工具,可以大概提供丰富的性能分析数据,帮助你了解 CUDA 程序的执行情况,包罗内存传输、核函数执行时间、硬件性能计数等。使用 nvprof,你可以轻松地获取核函数的执行时间和其他性能指标。
使用 nvprof 计时
nvprof 可以用来纪录 CUDA 核函数的执行时间、内存传输情况以及硬件级别的性能指标(如执行周期、指令数等)。它是 NVIDIA Profiler 工具的一部分,非常适用于性能分析。
  1. ==12345== Profiling application: ./my_program
  2. ==12345== Metrics result:
  3. ==12345==   Metric 'time_elapsed' is 1500.0 ms
  4. ==12345==   Metric 'sm__cycles_elapsed.avg' is 2000000
  5. ==12345==   Metric 'sm__inst_executed.avg' is 1000000
复制代码
4. 计算 CPI

如前所述,CPI(Cycles Per Instruction)可以通过以下公式计算:
                                         CPI                            =                                       sm__cycles_elapsed.avg                               sm__inst_executed.avg                                            \text{CPI} = \frac{\text{sm\_\_cycles\_elapsed.avg}}{\text{sm\_\_inst\_executed.avg}}                     CPI=sm__inst_executed.avgsm__cycles_elapsed.avg​
在上面的例子中:

所以:
                                         CPI                            =                                       2000000                               1000000                                      =                            2.0                                  \text{CPI} = \frac{2000000}{1000000} = 2.0                     CPI=10000002000000​=2.0
这意味着每条指令在该核函0数执行中平均斲丧 2 个周期。

方法优点缺点基于 CPU 计时器(如 std::chrono)简单易用,适用于对 CUDA 核函数举行快速计时只能测量核函数的总执行时间,无法提供硬件级别的性能数据基于 nvprof 工具计时提供具体的性能分析数据,支持多种硬件级别的计数器指标(如执行周期、指令数等)重要是后期分析工具,不得当嵌入程序中实时计时,且有额外的运行开销 选择哪种计时方式取决于您的需求:

4. 不同的线程数目和块数拥有不同的性能

1. 资源使用率


2. 线程调理


3. 内存访问模式


4. 计算与内存传输的平衡


5. 线程块的巨细


6. 装备特性


7. 负载平衡


不同的网格和块数目会影响 CUDA 程序的性能,重要是因为它们影响了资源使用率、线程调理、内存访问模式、计算与内存传输的平衡、线程块的巨细、装备特性和负载平衡等因素。为了获得最佳性能,开发者需要根据具体的应用场景和目的 GPU 的特性,公道配置网格和块的数目。通常,举行性能测试和基准测试是找到最佳配置的有效方法。
5. 装备管理

在 CUDA 编程中,查询 GPU 装备信息、选择最佳 GPU 装备并举行装备管理是性能优化的重要步骤。以下是怎样使用不同的 API 查询装备信息,选择最佳 GPU,使用 nvidia-smi
查询 GPU 信息以及在运行时设置装备的具体方法。
1. 使用 CUDA API 查询装备信息

CUDA 提供了多个 API 函数来查询 GPU 装备的各种信息,如装备数目、属性、内存、计算能力等。
1.1 查询装备数目

  1. #include <iostream>
  2. #include <cuda_runtime.h>
  3. int main() {
  4.     int deviceCount;
  5.     cudaError_t err = cudaGetDeviceCount(&deviceCount);
  6.    
  7.     if (err != cudaSuccess) {
  8.         std::cerr << "Error getting device count: " << cudaGetErrorString(err) << std::endl;
  9.         return -1;
  10.     }
  11.     std::cout << "Number of CUDA devices: " << deviceCount << std::endl;
  12.     return 0;
  13. }
复制代码

1.2 获取装备属性

每个 CUDA 装备都有一个 cudaDeviceProp 结构体,包罗装备的各种信息。比方,内存巨细、计算能力、每个线程块的最大线程数等。
  1. #include <iostream>
  2. #include <cuda_runtime.h>
  3. void printDeviceProperties(int deviceId) {
  4.     cudaDeviceProp prop;
  5.     cudaGetDeviceProperties(&prop, deviceId);
  6.    
  7.     std::cout << "Device " << deviceId << ": " << prop.name << std::endl;
  8.     std::cout << "  Total Global Memory: " << prop.totalGlobalMem / (1024 * 1024) << " MB" << std::endl;
  9.     std::cout << "  Shared Memory per Block: " << prop.sharedMemPerBlock / 1024 << " KB" << std::endl;
  10.     std::cout << "  Max Threads per Block: " << prop.maxThreadsPerBlock << std::endl;
  11.     std::cout << "  Compute Capability: " << prop.major << "." << prop.minor << std::endl;
  12. }
  13. int main() {
  14.     int deviceCount;
  15.     cudaGetDeviceCount(&deviceCount);
  16.     for (int i = 0; i < deviceCount; ++i) {
  17.         printDeviceProperties(i);
  18.     }
  19.     return 0;
  20. }
复制代码

1.3 获取当前装备

使用 cudaGetDevice() 可以获取当前选择的装备。
  1. int currentDevice;
  2. cudaGetDevice(&currentDevice);
  3. std::cout << "Current device is: " << currentDevice << std::endl;
复制代码
1.4 设置装备

使用 cudaSetDevice() 可以在程序中选择要使用的 GPU 装备。
  1. int deviceId = 1;  // 假设选择设备 1
  2. cudaSetDevice(deviceId);
复制代码
2. 选择最佳 GPU 装备

选择最佳 GPU 装备通常基于多个因素,如内存巨细、计算能力、使用的应用场景等。你可以选择具有最大内存或最高计算能力的装备。
比方,以下代码选择具有最大全局内存的装备作为最佳装备:
  1. int bestDevice = 0;
  2. size_t maxMemory = 0;
  3. int deviceCount;
  4. cudaGetDeviceCount(&deviceCount);
  5. for (int i = 0; i < deviceCount; ++i) {
  6.     cudaDeviceProp prop;
  7.     cudaGetDeviceProperties(&prop, i);
  8.    
  9.     if (prop.totalGlobalMem > maxMemory) {
  10.         maxMemory = prop.totalGlobalMem;
  11.         bestDevice = i;
  12.     }
  13. }
  14. std::cout << "Best device is: " << bestDevice << " with " << maxMemory / (1024 * 1024) << " MB memory." << std::endl;
  15. // 选择最佳设备
  16. cudaSetDevice(bestDevice);
复制代码
3. 使用 nvidia-smi
查询 GPU 信息


nvidia-smi
是 NVIDIA 提供的一个命令行工具,用于查询 GPU 状态和管理 GPU 资源。你可以通过 nvidia-smi
查看 GPU 的具体信息,如 GPU 使用情况、温度、内存使用量等。
3.1 查询 GPU 状态

在命令行中使用 nvidia-smi
查询 GPU 状态:
  1. nvidia-smi
复制代码
输出示例:
  1. +-----------------------------------------------------------------------------+
  2. | NVIDIA-SMI 460.32.03    Driver Version: 460.32.03    CUDA Version: 11.2     |
  3. |-------------------------------+----------------------+----------------------+
  4. | GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
  5. | Fan  Temp  Perf  Pwr:Usage/Cap| Memory-Usage | GPU-Util  Compute M. |
  6. |===============================+======================+======================|
  7. |   0  Tesla K80           Off  | 00000000:00:1E.0 Off |                    0 |
  8. | N/A   39C    P8    29W / 149W |    0MiB / 11441MiB |      0%      Default |
  9. +-------------------------------+----------------------+----------------------+
复制代码
3.2 查询特定 GPU 的信息

你也可以查询特定 GPU 的信息,比方,查询装备 ID 为 0 的 GPU:
  1. nvidia-smi
  2. -i 0
复制代码
3.3 查看 GPU 内存和使用情况

要查看 GPU 的内存使用情况:
  1. nvidia-smi
  2. --query-gpu=memory.used,memory.free,memory.total --format=csv
复制代码
输出示例:
  1. memory.used [MiB], memory.free [MiB], memory.total [MiB]
  2. 0 MiB, 11264 MiB, 11441 MiB
复制代码
3.4 使用 nvidia-smi
执行任务


您还可以使用 nvidia-smi
在命令行中启动或制止 GPU 任务。比方,查看 GPU 使用情况并限制其计算任务:
  1. nvidia-smi
  2. -i 0 --persistence-mode=1
复制代码
4. 运行时设置装备

CUDA 允许在运行时动态选择 GPU 装备。可以通过以下步骤在应用中举行装备选择:
4.1 获取装备数目

通过 cudaGetDeviceCount() 获取当前系统中的可用 GPU 数目。
4.2 根据装备特性选择装备

根据装备的性能指标(如计算能力、内存巨细等),选择最佳的 GPU。
4.3 设置装备

通过 cudaSetDevice(deviceId) 选择指定的 GPU 装备举行计算。
4.4 同步装备

如果您的程序在多个装备上并行执行,您可以使用 cudaDeviceSynchronize() 来同步装备的执行,确保当前装备的所有任务完成后才举行下一步操纵。
功能CUDA API命令行工具 (nvidia-smi
)查询可用装备数目cudaGetDeviceCount(&deviceCount)N/A查询装备属性cudaGetDeviceProperties(&prop, deviceId)N/A获取当前装备 IDcudaGetDevice(&currentDevice)N/A选择装备cudaSetDevice(deviceId)N/A查询装备内存和使用情况N/Anvidia-smi
--query-gpu=memory.used,memory.free,memory.total --format=csv获取装备信息cudaGetDeviceProperties()nvidia-smi
装备信息过滤cudaDeviceGetAttribute()(如最大线程数、内存等)nvidia-smi
-i <device_id> 通过结合使用 CUDA API 和 nvidia-smi
,可以灵活地查询和选择 GPU 装备,在程序运行时举行装备管理和优化。这有助于提高程序的性能,尤其在多 GPU 系统中。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4