负担GPU并增加测试能耗的最有效方法是什么?
我确实希望程序尽可能小。有特定的内核功能可以完成这项工作吗?
关于Metal或Cuda的任何建议都是完美的。
答案 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;
}