负担GPU的小程序?

时间:2019-03-15 17:27:57

标签: cuda gpu

负担GPU并增加测试能耗的最有效方法是什么?

我确实希望程序尽可能小。有特定的内核功能可以完成这项工作吗?

关于Metal或Cuda的任何建议都是完美的。

1 个答案:

答案 0 :(得分:3)

我在这里速写一个可能的解决方案。您将需要进行一些实验,以最大化GPU的热负荷。一般而言,数据移动在能量上是昂贵的,比现代处理器中的计算要昂贵得多。因此,对大量数据进行混洗将增加功耗。同时,我们希望计算单元对功耗产生累加贡献。乘数往往是最大的猪。在现代处理器中,我们可能希望定位FMA(融合乘加)单元。

各种GPU的双精度数学运算吞吐量较低,其他GPU的半精度数学运算吞吐量较低。因此,对于负载的计算部分,我们希望专注于单精度数学。我们希望能够轻松更改计算与内存活动的比率。一种方法是使用POLY_DEPTH步骤,以Horner方案为基础,使用多项式的展开评估。我们循环循环REPS次。在循环之前,我们从全局存储器中检索源数据,在循环终止之后,我们将结果存储到全局存储器中。通过更改REPS,我们可以尝试各种计算/内存平衡设置。

人们可以进一步尝试使用指令级并行性,数据模式(因为乘法器的功耗通常基于位模式而有所不同),并通过使用CUDA流来增加PCIe活动以实现内核执行和PCIe数据传输的重叠。下面我只是使用一些随机常量作为乘数。

很显然,我们想为GPU填充很多线程。为此,我们可以使用一个较小的THREADS_PER_BLK值,以便为填充每个SM提供良好的粒度。我们可能希望将块数选择为SM数的倍数,以尽可能平均地分散负载,或者使用MAX_BLOCKS值来平均划分常见的SM计数。我们应该接触多少源和目标内存将取决于实验:我们可以将LEN元素的数组定义为块数的倍数。最后,我们要执行这样定义和配置的ITER次内核,以创建一段时间的连续负载。

请注意,当我们施加负载时,GPU将变热,这将进一步增加其功耗。为了获得最大的热负载,有必要将负载生成应用程序运行5分钟或更长时间。还要注意,GPU电源管理可能会动态降低时钟频率和电压以降低功耗,并且功率上限可能会在达到散热极限之前启动。您可以 取决于GPU,将功率上限设置为高于nvidia-smi实用程序默认使用的功率上限。

根据TechPowerUp的GPU-Z实用程序的报告,以下程序使我的Quadro P2000保持了最高功率,GPU负载为98%,内存控制器负载为83%-86%。当然,其他GPU也需要进行调整。

#include <stdlib.h>
#include <stdio.h>

#define THREADS_PER_BLK (128)
#define MAX_BLOCKS      (65520)
#define LEN             (MAX_BLOCKS * 1024)
#define POLY_DEPTH      (30)
#define REPS            (2)
#define ITER            (100000)

// Macro to catch CUDA errors in CUDA runtime calls
#define CUDA_SAFE_CALL(call)                                          \
do {                                                                  \
    cudaError_t err = call;                                           \
    if (cudaSuccess != err) {                                         \
        fprintf (stderr, "Cuda error in file '%s' in line %i : %s.\n",\
                 __FILE__, __LINE__, cudaGetErrorString(err) );       \
        exit(EXIT_FAILURE);                                           \
    }                                                                 \
} while (0)

// Macro to catch CUDA errors in kernel launches
#define CHECK_LAUNCH_ERROR()                                          \
do {                                                                  \
    /* Check synchronous errors, i.e. pre-launch */                   \
    cudaError_t err = cudaGetLastError();                             \
    if (cudaSuccess != err) {                                         \
        fprintf (stderr, "Cuda error in file '%s' in line %i : %s.\n",\
                 __FILE__, __LINE__, cudaGetErrorString(err) );       \
        exit(EXIT_FAILURE);                                           \
    }                                                                 \
    /* Check asynchronous errors, i.e. kernel failed (ULF) */         \
    err = cudaDeviceSynchronize();                                    \
    if (cudaSuccess != err) {                                         \
        fprintf (stderr, "Cuda error in file '%s' in line %i : %s.\n",\
                 __FILE__, __LINE__, cudaGetErrorString( err) );      \
        exit(EXIT_FAILURE);                                           \
    }                                                                 \
} while (0)

__global__ void burn (const float * __restrict__ src, 
                      float * __restrict__ dst, int len)
{
    int stride = gridDim.x * blockDim.x;
    int tid = blockDim.x * blockIdx.x + threadIdx.x;
    for (int i = tid; i < len; i += stride) {
        float p = src[i] + 1.0;
        float q = src[i] + 3.0f;
        for (int k = 0; k < REPS; k++) {
#pragma unroll POLY_DEPTH
            for (int j = 0; j < POLY_DEPTH; j++) {
                p = fmaf (p, 0.68073987f, 0.8947237f);
                q = fmaf (q, 0.54639739f, 0.9587058f);
            }
        }
        dst[i] = p + q;
    }
}    

int main (int argc, char *argv[])
{
    float *d_a, *d_b;

    /* Allocate memory on device */
    CUDA_SAFE_CALL (cudaMalloc((void**)&d_a, sizeof(d_a[0]) * LEN));
    CUDA_SAFE_CALL (cudaMalloc((void**)&d_b, sizeof(d_b[0]) * LEN));

    /* Initialize device memory */
    CUDA_SAFE_CALL (cudaMemset(d_a, 0x00, sizeof(d_a[0]) * LEN)); // zero
    CUDA_SAFE_CALL (cudaMemset(d_b, 0xff, sizeof(d_b[0]) * LEN)); // NaN

    /* Compute execution configuration */
    dim3 dimBlock(THREADS_PER_BLK);
    int threadBlocks = (LEN + (dimBlock.x - 1)) / dimBlock.x;
    if (threadBlocks > MAX_BLOCKS) threadBlocks = MAX_BLOCKS;
    dim3 dimGrid(threadBlocks);

    printf ("burn: using %d threads per block, %d blocks, %f GB\n", 
            dimBlock.x, dimGrid.x, 2e-9*LEN*sizeof(d_a[0]));

    for (int k = 0; k < ITER; k++) {
        burn<<<dimGrid,dimBlock>>>(d_a, d_b, LEN);
        CHECK_LAUNCH_ERROR();
    }

    CUDA_SAFE_CALL (cudaFree(d_a));
    CUDA_SAFE_CALL (cudaFree(d_b));

    return EXIT_SUCCESS;
}