前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >【cuda 编程】gpu_burn 源码解析

【cuda 编程】gpu_burn 源码解析

原创
作者头像
Librant
修改2024-11-30 16:44:59
修改2024-11-30 16:44:59
30600
代码可运行
举报
运行总次数:0
代码可运行

1 gpu_burn 简介

gpu_burn 是一款专为多 GPU 设计的、通过 CUDA 实现高强度压力测试的工具。它旨在帮助系统管理员、研究人员和硬件发烧友深入了解GPU的潜能。

gpu_burn是一个开源项目,其源码结构简洁明了,支持快速构建和自定义配置。

gpu_burn 源码

Multi-GPU CUDA stress test

2 gpu_burn 代码结构

gpu_burn 整体代码结构比较简单,其核心代码在 1000行左右;

  • gpu_burn-drv.cpp(主要源码)
  • compare.cu

核心头文件:

代码语言:c
代码运行次数:0
复制
#include "cublas_v2.h"  // cublas 库文件
#include <cuda.h>       // cuda_driver_api 驱动库文件

gpu_burn 使用的是 cuda_driver_api 中的函数;没有调用 cuda_runtime_api 中的函数;在编译 .cu 文件需要用到 nvcc 编译工具生成 ptx 文件;

2.1 总体框图

单个GPU流程
单个GPU流程
  1. 单机单GPU的 gpu_burn 总体流程,主进程和子进程之间通过管道的方式进行通信。 在子进程中,启动 startBurn() 函数调用,对 GPU 进行压测; 在主进程中,启动 listenClients() 函数监听读管道中的信息;
  2. 在源码解析开头,先简单介绍下,gpu_burn 是如何对 GPU 进行压测的;子进程中 启动 startBurn() 主要是调用了 cublas 库中的矩阵运算 API 进行 C = αOP(A)OP(B) + βC 的运算,A,B 分别为 SIZE * SIZE 大小的方阵;(SIZE = 8192),方阵中的每个元素可以选择为单精度或者双精度的浮点数;
  3. gpu_burn 是区分 Tegra 平台(Jetson 系列)和 非 Tegra 平台,通过编译宏 IS_JETSON 进行区分;

2.2 gpu_burn-drv.cpp 源码解析

2.2.1 main()

  • 变量定义
代码语言:c
代码运行次数:0
复制
int runLength = 10; // 压测时长,单位为 秒
bool useDoubles = false; // 是否使用双精度
bool useTensorCores = false; // 是否使用 tensorRT 核
int thisParam = 0; // 参数个数
ssize_t useBytes = 0; // 0 == use USEMEM% of free mem 分配使用的内存字节数
int device_id = -1; // GPU 设备编号,默认从 0 开始
char *kernelFile = (char *)COMPARE_KERNEL; // 默认使用的 ptx 文件
#define COMPARE_KERNEL "compare.ptx" // 默认编译生成的 ptx 文件名,可以通过参数指定
  • argc >= 2 && std::string(argvi).find("-h") != std::string::npos: showHelp(); gpu_burn 的参数使用
代码语言:md
复制
GPU Burn
Usage: gpu_burn [OPTIONS] [TIME]

-m X Use X MB of memory
-m N% Use N% of the available GPU memory
-d Use doubles
-tc Try to use Tensor cores (if available)
-l List all GPUs in the system
-i N Execute only on GPU N
-c FILE Use FILE as compare kernel. Default is compare.ptx
-stts T Set timeout threshold to T seconds for using SIGTERM to abort child processes before using SIGKILL. Default is 30
-h Show this help message

Example:
gpu-burn -d 3600 # burns all GPUs with doubles for an hour
gpu-burn -m 50% # burns using 50% of the available GPU memory
gpu-burn -l # list GPUs
gpu-burn -i 2 # burns only GPU of index 2
  • argc >= 2 && std::string(argvi).find("-l") != std::string::npos:
代码语言:c
代码运行次数:0
复制
int count = initCuda(); // 初始化 cuda,调用 gpu 驱动函数之前需要执行,并返回 cuda 设备数量
if (count == 0) {
    throw std::runtime_error("No CUDA capable GPUs found.\n");
}
for (int i_dev = 0; i_dev < count; i_dev++) {
    CUdevice device_l; 
    CUdevprop device_p;
    char device_name[255]; // GPU 设备名称
    checkError(cuDeviceGet(&device_l, i_dev));
    checkError(cuDeviceGetName(device_name, 255, device_l));
    checkError(cuDeviceGetProperties(&device_p, device_l));
    size_t device_mem_l;
    checkError(cuDeviceTotalMem(&device_mem_l, device_l));
    printf("ID %i: %s, %ldMB\n", i_dev, device_name, device_mem_l / 1000 / 1000);
}
代码语言:c
代码运行次数:0
复制
typedef struct CUdevprop_st {
     int maxThreadsPerBlock;
     int maxThreadsDim[3];
     int maxGridSize[3];
     int sharedMemPerBlock;     // 每个块的共享内存大小
     int totalConstantMemory;   // 常量内存大小
     int SIMDWidth;             // SIMD Width(单指令多数据宽度)
     int memPitch;
     int regsPerBlock;
     int clockRate;
     int textureAlign
  } CUdevprop;

参数解析完成后,调用 lanch() 函数:

代码语言:c
代码运行次数:0
复制
if (useDoubles)
    launch<double>(runLength, useDoubles, useTensorCores, useBytes,
                    device_id, kernelFile, sigterm_timeout_threshold_secs);
else
    launch<float>(runLength, useDoubles, useTensorCores, useBytes,
                    device_id, kernelFile, sigterm_timeout_threshold_secs);

2.2.2 initCuda()

  • cuInit(0)
    • 初始化 CUDA 驱动程序 API 初始化驱动程序 API,必须在当前进程中驱动程序 API 中的任何其他函数之前调用。目前,Flags参数必须为 0。如果尚未调用 cuInit(),则驱动程序 API 中的任何函数都将返回 CUDA_ERROR_NOT_INITIALIZED。
  • cuDeviceGetCount(&deviceCount)
    • 返回 *count 计算能力大于或等于 1.0 且可供执行的设备数量。如果没有这样的设备,cuDeviceGetCount() 将返回 0。

2.2.2 launch()

代码语言:c
代码运行次数:0
复制
template <class T>
void launch(int runLength, bool useDoubles, bool useTensorCores,
            ssize_t useBytes, int device_id, const char * kernelFile,
            std::chrono::seconds sigterm_timeout_threshold_secs)
  • 查看 GPU 设备信息 - Jetson: /proc/device-tree/model Jetson-AGX - 非 Jetson: nvidia-smi -L GPU 0: NVIDIA H100 80GB HBM3 (UUID: GPU-XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXX)
  • 初始化 A,B 矩阵
    • 分配内存
    • 初始化伪随机数
代码语言:c
代码运行次数:0
复制
// Initting A and B with random data
T *A = (T *)malloc(sizeof(T) * SIZE * SIZE);
T *B = (T *)malloc(sizeof(T) * SIZE * SIZE);
srand(10);
for (size_t i = 0; i < SIZE * SIZE; ++i) {
    A[i] = (T)((double)(rand() % 1000000) / 100000.0);
    B[i] = (T)((double)(rand() % 1000000) / 100000.0);
}
  • 创建命名管道,用于父子进程之间的通信
    • mainPipe0 将是管道读取端的 fd
    • mainPipe1 将是管道写入端的 fd
代码语言:c
代码运行次数:0
复制
int mainPipe[2];
pipe(mainPipe);
  • std::vector<int> clientPipes;
    • 存子进程的读管道,父进程读取
  • std::vector<pid_t> clientPids;
    • 存子进程的 PID
  • device_id > -1 :判断是否指定了压测显卡 ID,不指定将对所有的显卡进行压测;
    • device_id 初始化为 -1

为了简化流程,这里流程就介绍单 GPU 卡的压测流程,对于多 GPU 卡的压测是类似的,留给有兴趣的读者去探索;

代码语言:c
代码运行次数:0
复制
pid_t myPid = fork();
if (!myPid) {
    // Child
    close(mainPipe[0]);
    int writeFd = mainPipe[1];
    initCuda();
    int devCount = 1;
    write(writeFd, &devCount, sizeof(int));
    startBurn<T>(device_id, writeFd, A, B, useDoubles, useTensorCores,
                    useBytes, kernelFile);
    close(writeFd);
    return;
} else {
    clientPids.push_back(myPid);
    close(mainPipe[1]);
    int devCount;
    read(readMain, &devCount, sizeof(int));
    listenClients(clientPipes, clientPids, runLength, sigterm_timeout_threshold_secs);
}
for (size_t i = 0; i < clientPipes.size(); ++i)
    close(clientPipes.at(i));
  • pid_t myPid = fork();
    • 创建子进程
      • 1)在父进程中,fork 返回新创建子进程的进程ID;
      • 2)在子进程中,fork返回0;
      • 3)如果出现错误,fork返回一个负值;
2.2.2.1 子进程流程
  • close(mainPipe0); 关闭读管道
  • initCuda(); 初始化 cuda
  • write(writeFd, &devCount, sizeof(int)); 向管道中写入设备数量 1
  • startBurn<T>(); 调用压测函数【参考2.2.3】
  • close(writeFd); 关闭写管道
2.2.2.2 父进程流程
  • clientPids.push_back(myPid); 保存子进程 ID
  • close(mainPipe1); 关闭写管道
  • read(readMain, &devCount, sizeof(int)); 读取 GPU 设备数量
  • listenClients(): 调用监听函数【参考2.2.4】
  • close(clientPipes.at(i)); 关闭所有的读管道

2.2.3 startBurn()

代码语言:c
代码运行次数:0
复制
template <class T>
void startBurn(int index, int writeFd, T *A, T *B, bool doubles, bool tensors,
               ssize_t useBytes, const char *kernelFile)
  • GPU_Test<T> *our; 首先定义了一个 GPU_Test 类的对象 *our【参考2.2.5】
    • our = new GPU_Test<T>(index, doubles, tensors, kernelFile); 初始化对象参数
    • our->initBuffers(A, B, useBytes); 初始化 GPU 设备内存
代码语言:c
代码运行次数:0
复制
int eventIndex = 0;
const int maxEvents = 2;
CUevent events[maxEvents]; 
for (int i = 0; i < maxEvents; ++i)
    cuEventCreate(events + i, 0);

int nonWorkIters = maxEvents;
  • CUevent eventsmaxEvents; 声明两个 cuda 事件
    • 每次循环迭代两次
  • cuEventCreate(events + i, 0);
    • 创建一个事件
  • while (our->shouldRun()):判断当前是否还需要继续执行
    • g_running:gpu 压测是否需要继续执行
  • our->compute(); 执行 GPU 运算程序
  • our->compare(); 执行 GPU 比较程序
  • cuEventRecord(events[eventIndex], 0)
    • 记录事件
代码语言:c
代码运行次数:0
复制
while (cuEventQuery(events[eventIndex]) != CUDA_SUCCESS)
    usleep(1000); // gpu没有执行没有完成,cpu 休眠 1000us
  • cuEventQuery(events[eventIndex]) != CUDA_SUCCESS
    • 查询事件的状态
  • int ops = our->getIters(); 获取当前迭代次数
    • d_iters(size_t d_iters;)
    • write(writeFd, &ops, sizeof(int)); 迭代信息返回给主进程
  • ops = our->getErrors(); 获取错误的信息
    • d_faultyElemsHost(int *d_faultyElemsHost;)
    • write(writeFd, &ops, sizeof(int)); 错误信息返回给主进程
  • cuEventSynchronize(events[i])
    • 等待事件完成
  • delete our; 执行完成回收对象

2.2.4 GPU_Test

代码语言:c
代码运行次数:0
复制
template <class T> class GPU_Test {}
  • 私有变量
代码语言:c
代码运行次数:0
复制
bool d_doubles; // 是否使用双精度
bool d_tensors; // 是否使用 tensor core
int d_devNumber; // GPU 设备数量
const char *d_kernelFile; // 核函数文件名(ptx 文件名)
size_t d_iters; // 需要运算的次数
size_t d_resultSize; // 矩阵 C 的字节数

long long int d_error; // 错误码

static const int g_blockSize = 16; // block 的大小

CUdevice d_dev;
CUcontext d_ctx;
CUmodule d_module;
CUfunction d_function;

CUdeviceptr d_Cdata;
CUdeviceptr d_Adata;
CUdeviceptr d_Bdata;
CUdeviceptr d_faultyElemData;
int *d_faultyElemsHost;

cublasHandle_t d_cublas;
  • 构造函数 - GPU_Test(int dev, bool doubles, bool tensors, const char *kernelFile) : d_devNumber(dev), d_doubles(doubles), d_tensors(tensors), d_kernelFile(kernelFile) {} - 参数初始化
  • 析构函数
    • ~GPU_Test()
    • 存储释放
  • 方法列表
    • static void termHandler(int signum)
    • unsigned long long int getErrors()
    • size_t getIters()
    • void bind()
    • size_t totalMemory()
    • size_t availMemory()
    • void initBuffers(T A, T B, ssize_t useBytes = 0)
    • void compute()
    • void initCompareKernel()
    • void compare()
    • bool shouldRun()
2.2.4.1 GPU_Test()
代码语言:c
代码运行次数:0
复制
checkError(cuDeviceGet(&d_dev, d_devNumber));
checkError(cuCtxCreate(&d_ctx, 0, d_dev));

bind();

// checkError(cublasInit());
checkError(cublasCreate(&d_cublas), "init");

if (d_tensors)
    checkError(cublasSetMathMode(d_cublas, CUBLAS_TENSOR_OP_MATH));

checkError(cuMemAllocHost((void **)&d_faultyElemsHost, sizeof(int)));
d_error = 0;

g_running = true;

struct sigaction action;
memset(&action, 0, sizeof(struct sigaction));
action.sa_handler = termHandler;
sigaction(SIGTERM, &action, NULL);
  • cuDeviceGet(&d_dev, d_devNumber):根据 d_devNumber 获取 CUdevice d_dev;
  • cuCtxCreate(&d_ctx, 0, d_dev)
    • typedef CUctx_st * CUcontext
      • context 是一种上下文,关联对 GPU 的所有操作,context 与一块显卡关联,一个显卡可以被多个 context 关联;
      • 每个线程都有一个栈结构储存 context,栈顶是当前使用的 context,对应有 push、pop 函数操作 context 的栈,所有 api 都以当前 context 为操作目标;
    • 创建 CUDA 上下文(创建 context 的同时会自动进行压栈的操作)
  • bind(); 将CUDA 上下文绑定到调用 CPU 线程
  • cublasCreate(&d_cublas), "init"
    • cublasStatus_t cublasCreate(cublasHandle_t *handle);
    • 此函数初始化 cuBLAS 库并创建保存 cuBLAS 库上下文的不透明结构的句柄。它在主机和设备上分配硬件资源,必须在进行任何其他 cuBLAS 库调用之前调用。
  • d_tensors:判断是否开启 tensor core
  • cublasSetMathMode(d_cublas, CUBLAS_TENSOR_OP_MATH)
    • cublasStatus_t cublasSetMathMode(cublasHandle_t handle, cublasMath_t mode);
    • 用户可以将计算精度模式设置为它们的逻辑组合 (CUBLAS_TENSOR_OP_MATH 将会被弃用)
  • cuMemAllocHost((void **)&d_faultyElemsHost, sizeof(int))
    • CUresult cuMemAllocHost ( void** pp, size_t bytesize );
    • 分配页锁定主机内存
  • g_running = true; 允许 gpu 运行
  • action.sa_handler = termHandler;
    • 当系统收到 SIGTERM 信号时,设置 g_running = false,终止 gpu 运行
2.2.4.2 ~GPU_Test()
代码语言:c
代码运行次数:0
复制
bind();
checkError(cuMemFree(d_Cdata), "Free A");
checkError(cuMemFree(d_Adata), "Free B");
checkError(cuMemFree(d_Bdata), "Free C");
cuMemFreeHost(d_faultyElemsHost);
printf("Freed memory for dev %d\n", d_devNumber);

cublasDestroy(d_cublas);
printf("Uninitted cublas\n");
  • bind(); 将CUDA 上下文绑定到调用 CPU 线程
  • cuMemFree(d_Cdata())
  • cuMemFree(d_Adata)
  • cuMemFree(d_Bdata)
    • CUresult cuMemFree ( CUdeviceptr dptr );
    • 释放设备内存。
  • cuMemFreeHost(d_faultyElemsHost)
    • CUresult cuMemFreeHost(void* p);
    • 释放页面锁定的主机内存
  • cublasDestroy(d_cublas)
    • 此函数释放 cuBLAS 库使用的硬件资源。
    • 此函数通常是最后一次调用 cuBLAS 库的特定句柄。
2.2.4.3 termHandler()
代码语言:c
代码运行次数:0
复制
static void termHandler(int signum) { g_running = false; }
  • g_running:将 g_running 设置为 false
2.2.4.4 getErrors()
代码语言:c
代码运行次数:0
复制
if (*d_faultyElemsHost) {
    d_error += (long long int)*d_faultyElemsHost;
}
unsigned long long int tempErrs = d_error;
d_error = 0; // 每次计算完,对 d_error 进行清零
return tempErrs;
  • d_faultyElemsHost:如果主机内存中的元素不为空,将错误数量进行相加
2.2.4.5 getIters()
代码语言:c
代码运行次数:0
复制
size_t getIters() { return d_iters; }
  • d_iters 迭代次数
代码语言:c
代码运行次数:0
复制
size_t d_resultSize = sizeof(T) * SIZE * SIZE;
d_iters = (useBytes - 2 * d_resultSize) /
            d_resultSize; // We remove A and B sizes
  • 计算方式
    • d_resultSize: 矩阵 C 对应的内存字节数
    • d_iters 为 存入 C 矩阵大小的倍数(每个 C 都需要迭代一次)
2.2.4.6 bind()
代码语言:c
代码运行次数:0
复制
void bind() { checkError(cuCtxSetCurrent(d_ctx), "Bind CTX"); }
  • cuCtxSetCurrent(d_ctx)
    • CUresult cuCtxSetCurrent ( CUcontext ctx );
    • 将指定的 CUDA 上下文绑定到调用 CPU 线程。
2.2.4.7 totalMemory()/availMemory()
代码语言:c
代码运行次数:0
复制
bind();
size_t freeMem, totalMem;
checkError(cuMemGetInfo(&freeMem, &totalMem));
2.2.4.8 initBuffers()
代码语言:c
代码运行次数:0
复制
bind();

if (useBytes == 0)
    useBytes = (ssize_t)((double)availMemory() * USEMEM);
if (useBytes < 0)
    useBytes = (ssize_t)((double)availMemory() * (-useBytes / 100.0));

size_t d_resultSize = sizeof(T) * SIZE * SIZE;
d_iters = (useBytes - 2 * d_resultSize) /
            d_resultSize; // We remove A and B sizes

if ((size_t)useBytes < 3 * d_resultSize)
    throw std::string("Low mem for result. aborting.\n");
checkError(cuMemAlloc(&d_Cdata, d_iters * d_resultSize), "C alloc");
checkError(cuMemAlloc(&d_Adata, d_resultSize), "A alloc");
checkError(cuMemAlloc(&d_Bdata, d_resultSize), "B alloc");

checkError(cuMemAlloc(&d_faultyElemData, sizeof(int)), "faulty data");

// Populating matrices A and B
checkError(cuMemcpyHtoD(d_Adata, A, d_resultSize), "A -> device");
checkError(cuMemcpyHtoD(d_Bdata, B, d_resultSize), "B -> device");

initCompareKernel();
  • useBytes:使用的内存数量字段校验
    • USEMEM:#define USEMEM 0.9 // Try to allocate 90% of memory
  • d_resultSize:每个矩阵需要的字节数
  • d_iters:迭代次数是,使用的内存数 / 每个矩阵的字节数,需要先减去 A 和 B 矩阵使用的字节数,相当于需要产生多少个 矩阵 C
  • (size_t)useBytes < 3 * d_resultSize
    • 如果需要使用的内存数量不足,抛出异常
  • 分配设备内存
  • 从主机内存拷贝数据到设备内存
  • initCompareKernel(); 初始化核函数 【参考】
2.2.4.9 compute()
代码语言:c
代码运行次数:0
复制
bind();
static const float alpha = 1.0f;
static const float beta = 0.0f;
static const double alphaD = 1.0;
static const double betaD = 0.0;

// C = α A B +β C

for (size_t i = 0; i < d_iters; ++i) {
    if (d_doubles)
        checkError(
            cublasDgemm(d_cublas, CUBLAS_OP_N, CUBLAS_OP_N, SIZE, SIZE,
                        SIZE, &alphaD, (const double *)d_Adata, SIZE,
                        (const double *)d_Bdata, SIZE, &betaD,
                        (double *)d_Cdata + i * SIZE * SIZE, SIZE),
            "DGEMM");
    else
        checkError(
            cublasSgemm(d_cublas, CUBLAS_OP_N, CUBLAS_OP_N, SIZE, SIZE,
                        SIZE, &alpha, (const float *)d_Adata, SIZE,
                        (const float *)d_Bdata, SIZE, &beta,
                        (float *)d_Cdata + i * SIZE * SIZE, SIZE),
            "SGEMM");
}
  • cublasSgemm()
    • 执行矩阵-矩阵运算的 3 级基本线性代数子程序 (BLAS3) 函数
2.2.4.10 initCompareKernel()
代码语言:c
代码运行次数:0
复制
{
    std::ifstream f(d_kernelFile);
    checkError(f.good() ? CUDA_SUCCESS : CUDA_ERROR_NOT_FOUND,
                std::string("couldn't find compare kernel: ") + d_kernelFile);
}
checkError(cuModuleLoad(&d_module, d_kernelFile), "load module");
checkError(cuModuleGetFunction(&d_function, d_module,
                                d_doubles ? "compareD" : "compare"),
            "get func");

checkError(cuFuncSetCacheConfig(d_function, CU_FUNC_CACHE_PREFER_L1),
            "L1 config");
checkError(cuParamSetSize(d_function, __alignof(T *) +
                                            __alignof(int *) +
                                            __alignof(size_t)),
            "set param size");
checkError(cuParamSetv(d_function, 0, &d_Cdata, sizeof(T *)),
            "set param");
checkError(cuParamSetv(d_function, __alignof(T *), &d_faultyElemData,
                        sizeof(T *)),
            "set param");
checkError(cuParamSetv(d_function, __alignof(T *) + __alignof(int *),
                        &d_iters, sizeof(size_t)),
            "set param");

checkError(cuFuncSetBlockShape(d_function, g_blockSize, g_blockSize, 1),
            "set block size");
2.2.4.11 compare()
代码语言:c
代码运行次数:0
复制
checkError(cuMemsetD32Async(d_faultyElemData, 0, 1, 0), "memset");
checkError(cuLaunchGridAsync(d_function, SIZE / g_blockSize,
                                SIZE / g_blockSize, 0),
            "Launch grid");
checkError(cuMemcpyDtoHAsync(d_faultyElemsHost, d_faultyElemData,
                                sizeof(int), 0),
            "Read faultyelemdata");
2.2.4.12 shouldRun()
代码语言:c
代码运行次数:0
复制
bool shouldRun() { return g_running; }
  • g_running:当前 gpu 是否在运行

2.2.5 listenClients()

代码语言:c
代码运行次数:0
复制
void listenClients(std::vector<int> clientFd, std::vector<pid_t> clientPid,
                   int runTime, std::chrono::seconds sigterm_timeout_threshold_secs)
代码语言:c
代码运行次数:0
复制
fd_set waitHandles;

pid_t tempPid;
int tempHandle = pollTemp(&tempPid);
int maxHandle = tempHandle;

FD_ZERO(&waitHandles);
FD_SET(tempHandle, &waitHandles);

for (size_t i = 0; i < clientFd.size(); ++i) {
    if (clientFd.at(i) > maxHandle)
        maxHandle = clientFd.at(i);
    FD_SET(clientFd.at(i), &waitHandles);
}
  • fd_set waitHandles; 定义一个 fd_set 的集合
  • int tempHandle = pollTemp(&tempPid); 【参考】
    • 获取 gpu 运行的温度信息
    • 返回子进程 pid
  • FD_ZERO(&waitHandles); 将 waitHandles 清空
  • FD_SET(tempHandle, &waitHandles); 将读 tempHandle 读管道 fd 保存

FD_SET(int fd, fd_set fdset); //将fd加入set集合 FD_CLR(int fd, fd_set fdset); //将fd从set集合中清除 FD_ISSET(int fd, fd_set fdset); //检测fd是否在set集合中,不在则返回0 FD_ZERO(fd_set fdset); //将set清零使集合中不含任何fd

  • for (size_t i = 0; i < clientFd.size(); ++i)
    • maxHandle = clientFd.at(i); 记录当前 最大的 FD
    • FD_SET(clientFd.at(i), &waitHandles); 将读管道中的 FD 加到 waitHandles 中;
代码语言:c
代码运行次数:0
复制
while (
    (changeCount = select(maxHandle + 1, &waitHandles, NULL, NULL, NULL))) {
    size_t thisTime = time(0);
    struct timespec thisTimeSpec;
    clock_gettime(CLOCK_REALTIME, &thisTimeSpec);

    // Going through all descriptors
    for (size_t i = 0; i < clientFd.size(); ++i)
        if (FD_ISSET(clientFd.at(i), &waitHandles)) {
            // First, reading processed
            int processed, errors;
            int res = read(clientFd.at(i), &processed, sizeof(int));
            if (res < sizeof(int)) {
                fprintf(stderr, "read[%zu] error %d", i, res);
                processed = -1;
            }
            // Then errors
            read(clientFd.at(i), &errors, sizeof(int));

            clientErrors.at(i) += errors;
            if (processed == -1)
                clientCalcs.at(i) = -1;
            else {
                double flops = (double)processed * (double)OPS_PER_MUL;
                struct timespec clientPrevTime = clientUpdateTime.at(i);
                double clientTimeDelta =
                    (double)thisTimeSpec.tv_sec +
                    (double)thisTimeSpec.tv_nsec / 1000000000.0 -
                    ((double)clientPrevTime.tv_sec +
                        (double)clientPrevTime.tv_nsec / 1000000000.0);
                clientUpdateTime.at(i) = thisTimeSpec;

                clientGflops.at(i) =
                    (double)((unsigned long long int)processed *
                                OPS_PER_MUL) /
                    clientTimeDelta / 1000.0 / 1000.0 / 1000.0;
                clientCalcs.at(i) += processed;
            }

            childReport = true;
        }

    if (FD_ISSET(tempHandle, &waitHandles))
        updateTemps(tempHandle, &clientTemp);

    // Resetting the listeners
    FD_ZERO(&waitHandles);
    FD_SET(tempHandle, &waitHandles);
    for (size_t i = 0; i < clientFd.size(); ++i)
        FD_SET(clientFd.at(i), &waitHandles);

    // Printing progress (if a child has initted already)
    if (childReport) {
        float elapsed =
            fminf((float)(thisTime - startTime) / (float)runTime * 100.0f,
                    100.0f);
        printf("\r%.1f%%  ", elapsed);
        printf("proc'd: ");
        for (size_t i = 0; i < clientCalcs.size(); ++i) {
            printf("%d (%.0f Gflop/s) ", clientCalcs.at(i),
                    clientGflops.at(i));
            if (i != clientCalcs.size() - 1)
                printf("- ");
        }
        printf("  errors: ");
        for (size_t i = 0; i < clientErrors.size(); ++i) {
            std::string note = "%d ";
            if (clientCalcs.at(i) == -1)
                note += " (DIED!)";
            else if (clientErrors.at(i))
                note += " (WARNING!)";

            printf(note.c_str(), clientErrors.at(i));
            if (i != clientCalcs.size() - 1)
                printf("- ");
        }
        printf("  temps: ");
        for (size_t i = 0; i < clientTemp.size(); ++i) {
            printf(clientTemp.at(i) != 0 ? "%d C " : "-- ",
                    clientTemp.at(i));
            if (i != clientCalcs.size() - 1)
                printf("- ");
        }

        fflush(stdout);

        for (size_t i = 0; i < clientErrors.size(); ++i)
            if (clientErrors.at(i))
                clientFaulty.at(i) = true;

        if (nextReport < elapsed) {
            nextReport = elapsed + 10.0f;
            printf("\n\tSummary at:   ");
            fflush(stdout);
            system("date"); // Printing a date
            fflush(stdout);
            printf("\n");
            for (size_t i = 0; i < clientErrors.size(); ++i)
                clientErrors.at(i) = 0;
        }
    }

    // Checking whether all clients are dead
    bool oneAlive = false;
    for (size_t i = 0; i < clientCalcs.size(); ++i)
        if (clientCalcs.at(i) != -1)
            oneAlive = true;
    if (!oneAlive) {
        fprintf(stderr, "\n\nNo clients are alive!  Aborting\n");
        exit(ENOMEDIUM);
    }

    if (startTime + runTime < thisTime)
        break;
}
  • changeCount = select(maxHandle + 1, &waitHandles, NULL, NULL, NULL))
    • select 函数属于非阻塞方式,用来检查套接字描述符 waitHandles (sockets descriptors)是否已准备好读/写
  • int res = read(clientFd.at(i), &processed, sizeof(int));
    • 这里读取的 processed 是子进程中写入的 d_iters 迭代次数
  • read(clientFd.at(i), &errors, sizeof(int));
    • 这里读取的是 d_faultyElemsHost 错误数量
  • double flops = (double)processed * (double)OPS_PER_MUL;
    • 计算处理的 flops 数量

//#define OPS_PER_MUL 17188257792ul // Measured for SIZE = 2048 #define OPS_PER_MUL 1100048498688ul // Extrapolated for SIZE = 8192

  • clientGflops.at(i) = (double)((unsigned long long int)processed * OPS_PER_MUL) / clientTimeDelta / 1000.0 / 1000.0 / 1000.0;
  • FD_ISSET(tempHandle, &waitHandles)
    • 如果是读取问题的管道,则更新温度信息

2.2.6 pollTemp()

代码语言:c
代码运行次数:0
复制
int tempPipe[2];
pipe(tempPipe);

pid_t myPid = fork();

if (!myPid) {
close(tempPipe[0]);
dup2(tempPipe[1], STDOUT_FILENO);
#if IS_JETSON
execlp("tegrastats", "tegrastats", "--interval", "5000", NULL);
fprintf(stderr, "Could not invoke tegrastats, no temps available\n");
#else
execlp("nvidia-smi", "nvidia-smi", "-l", "5", "-q", "-d", "TEMPERATURE",
        NULL);
fprintf(stderr, "Could not invoke nvidia-smi, no temps available\n");
#endif

exit(ENODEV);
}

*p = myPid;
close(tempPipe[1]);

return tempPipe[0];

pollTemp() 通过 fork() 创建子进程,然后通过命令读取 GPU 设备的温度信息,再通过管道返回给主进程;

  • Jetson 设备:
    • tegrastats", "tegrastats", "--interval", "5000"
代码语言:txt
复制
11-25-2024 17:51:24 RAM 6633/31003MB (lfb 4855x4MB) SWAP 30/15501MB (cached 0MB) CPU [10%@1190,5%@1190,3%@1190,3%@1191,6%@1190,8%@1189,3%@1190,5%@1190] EMC_FREQ 1%@800 GR3D_FREQ 0%@114 VIC_FREQ 115 APE 150 AUX@32.5C CPU@34C thermal@33.4C Tboard@35C AO@34C GPU@34C Tdiode@36.25C PMIC@50C GPU 0mW/0mW CPU 775mW/775mW SOC 998mW/998mW CV 0mW/0mW VDDRQ 221mW/221mW SYS5V 2985mW/2985mW
  • 非 Jetson 设备
    • nvidia-smi -l 5 -q -d TEMPERATURE
代码语言:txt
复制
==============NVSMI LOG==============

Timestamp                                 : Mon Nov 25 09:59:22 2024
Driver Version                            : 535.183.01
CUDA Version                              : 12.2

Attached GPUs                             : 8
GPU 00000000:18:00.0
    Temperature
        GPU Current Temp                  : 50 C
        GPU T.Limit Temp                  : 37 C
        GPU Shutdown T.Limit Temp         : -8 C
        GPU Slowdown T.Limit Temp         : -2 C
        GPU Max Operating T.Limit Temp    : 0 C
        GPU Target Temperature            : N/A
        Memory Current Temp               : 55 C
        Memory Max Operating T.Limit Temp : 0 C
  • STDOUT_FILENO:输出到标准输出文件中

2.2.7 updateTemps()

代码语言:c
代码运行次数:0
复制
    const int readSize = 10240;
    static int gpuIter = 0;
    char data[readSize + 1];

    int curPos = 0;
    do {
        read(handle, data + curPos, sizeof(char));
    } while (data[curPos++] != '\n');

    data[curPos - 1] = 0;

#if IS_JETSON
    std::string data_str(data);
    std::regex pattern("GPU@([0-9]+)C");
    std::smatch matches;
    if (std::regex_search(data_str, matches, pattern)) {
        if (matches.size() > 1) {
            int tempValue = std::stoi(matches[1]);
            temps->at(gpuIter) = tempValue;
            gpuIter = (gpuIter + 1) % (temps->size());
        }
    }
#else
    // FIXME: The syntax of this print might change in the future..
    int tempValue;
    if (sscanf(data,
               "		GPU Current Temp			: %d C",
               &tempValue) == 1) {
        temps->at(gpuIter) = tempValue;
        gpuIter = (gpuIter + 1) % (temps->size());
    } else if (!strcmp(data, "		Gpu				"
                             "	 : N/A"))
        gpuIter =
            (gpuIter + 1) %
            (temps->size()); // We rotate the iterator for N/A values as well
#endif

updateTemps() 不停的从 pollTemp() 返回的管道中读取 GPU 温度信息,同样区分是 Jetson 设备还是非 jetson 设备;

2.3 compare.cu 源码解析

compare.cu 中主要定义了cuda 运行的核函数;

2.3.1 compare()

代码语言:c
代码运行次数:0
复制
extern "C" __global__ void compare(float *C, int *faultyElems, size_t iters)
代码语言:c
代码运行次数:0
复制
size_t iterStep = blockDim.x*blockDim.y*gridDim.x*gridDim.y;
size_t myIndex = (blockIdx.y*blockDim.y + threadIdx.y)* // Y
    gridDim.x*blockDim.x + // W
    blockIdx.x*blockDim.x + threadIdx.x; // X

int myFaulty = 0;
for (size_t i = 1; i < iters; ++i)
    if (fabsf(C[myIndex] - C[myIndex + i*iterStep]) > EPSILON)
        myFaulty++;

atomicAdd(faultyElems, myFaulty);
  • fabsf(CmyIndex - CmyIndex + i*iterStep) > EPSILON
    • #define EPSILON 0.001f
    • 统计每个迭代计算的 C 矩阵对应坐标上的元素差值,超过 EPSILON 怎认为出现错误
  • atomicAdd(faultyElems, myFaulty);
    • 将错误数量写入到 faultyElems 中

2.3.2 compareD()

代码语言:c
代码运行次数:0
复制
extern "C" __global__ void compareD(double *C, int *faultyElems, size_t iters)
代码语言:c
代码运行次数:0
复制
size_t iterStep = blockDim.x*blockDim.y*gridDim.x*gridDim.y;
size_t myIndex = (blockIdx.y*blockDim.y + threadIdx.y)* // Y
    gridDim.x*blockDim.x + // W
    blockIdx.x*blockDim.x + threadIdx.x; // X

int myFaulty = 0;
for (size_t i = 1; i < iters; ++i)
    if (fabs(C[myIndex] - C[myIndex + i*iterStep]) > EPSILOND)
        myFaulty++;

atomicAdd(faultyElems, myFaulty);
  • fabs(CmyIndex - CmyIndex + i*iterStep) > EPSILOND
    • #define EPSILOND 0.0000001
    • 统计每个迭代计算的 C 矩阵对应坐标上的元素差值,超过 EPSILOND 怎认为出现错误
  • atomicAdd(faultyElems, myFaulty);
    • 将错误数量写入到 faultyElems 中

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1 gpu_burn 简介
  • 2 gpu_burn 代码结构
    • 2.1 总体框图
    • 2.2 gpu_burn-drv.cpp 源码解析
      • 2.2.1 main()
      • 2.2.2 initCuda()
      • 2.2.2 launch()
      • 2.2.3 startBurn()
      • 2.2.4 GPU_Test
      • 2.2.5 listenClients()
      • 2.2.6 pollTemp()
      • 2.2.7 updateTemps()
    • 2.3 compare.cu 源码解析
      • 2.3.1 compare()
      • 2.3.2 compareD()
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档