【问题标题】:How can I check the progress of matrix multiplication?如何检查矩阵乘法的进度?
【发布时间】:2013-12-19 04:31:28
【问题描述】:

我现在只需要展示一个矩阵乘法的中间过程。

for(unsigned int col=0; col<mtxSize; col++) {
         unsigned tmp = 0;
         for(unsigned int row=0; row<mtxSize; row++) {
             for(unsigned int idx=0; idx<mtxSize; idx++) {
                 tmp += h_A[col*mtxSize+idx] * h_B[idx*mtxSize+row];
            }
             h_Rs[col*mtxSize+row] = tmp;
             tmp = 0;
             int rate_tmp = (col*mtxSize + (row+1))*100;
             // Maybe like this...
             fprintf(stdout, "Progress : %d.%d %%\r", rate_tmp/actMtxSize, rate_tmp%actMtxSize);
             fflush(stdout);
         }
}

如果是主机代码(使用CPU),很容易,因为它是顺序处理的,所以我们很容易检查。

但是在GPU并行处理的情况下,我该怎么办呢?

内核一旦运行,直到内核执行完成才返回。

所以我无法在内核执行期间检查中间数据。

我想我需要使用异步内核调用,但我不太了解。

即使使用异步内核调用,要通过处理器将所有数据查看到多个块中,我是否必须编写 atomicAdd()(换句话说,全局内存访问)函数,其中包括一些开销?

给我一​​些建议或提示。

我想知道 CUDA 的情况。

【问题讨论】:

  • 如果您只想查看数据,您仍然可以使用 nsight visual studio 或 nsight eclipse 版本调试代码(针对 cuda)。对于 opencl 也有调试器 - 不知道确切,因为我为 cuda 开发。
  • hubs // 我的意思是数据是完全计算的元素数量。使用这些数据,我可以计算进度。
  • 是的,但在提供的代码中,您只输出速率。所以我认为你只需要它来进行调试。您是否也需要它来进行以下计算?
  • 是的。其实我打算用GUI做进度条。
  • 所以对于 CUDA,您只能将其拆分为多个内核。例如,内核正在计算一行或几行,因为您无法影响调度程序的工作方式以及计算 theadblocks 的顺序。

标签: cuda


【解决方案1】:

这是一个演示如何从矩阵乘法内核检查进度的代码:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define TIME_INC 100000000
#define INCS 10
#define USE_PROGRESS 1
#define MAT_DIMX 4000
#define MAT_DIMY MAT_DIMX

#define cudaCheckErrors(msg) \
    do { \
        cudaError_t __err = cudaGetLastError(); \
        if (__err != cudaSuccess) { \
            fprintf(stderr, "Fatal error: %s (%s at %s:%d)\n", \
                msg, cudaGetErrorString(__err), \
                __FILE__, __LINE__); \
            fprintf(stderr, "*** FAILED - ABORTING\n"); \
            exit(1); \
        } \
    } while (0)

__global__ void mykernel(volatile int *data){

  unsigned long time;
  for (int i = 0; i < INCS; i++){
    atomicAdd((int *)data,1);
    __threadfence_system();
    time = clock64();
    while((clock64() - time)<TIME_INC) {};
    }
  printf("progress check finished\n");
}

__global__ void matmult(float *a, float *b, float *c, unsigned int rowA, unsigned int colA, unsigned int colB, volatile int *progress){
  unsigned int row = threadIdx.x+blockDim.x*blockIdx.x;
  unsigned int col = threadIdx.y+blockDim.y*blockIdx.y;
  if ((row < rowA) && (col < colB)){
    float temp = 0.0f;
    for (unsigned int k = 0; k < colA; k++)
      temp += a[(row*colA)+k] * b[(k*colB) + col];
    c[(row*colB)+col] = temp;
#if USE_PROGRESS
    if (!(threadIdx.x || threadIdx.y)){
      atomicAdd((int *)progress, 1);
      __threadfence_system();
      }
#endif
  }
}

int main(){
// simple test to demonstrate reading progress data from kernel
  volatile int *d_data, *h_data;
  cudaSetDeviceFlags(cudaDeviceMapHost);
  cudaCheckErrors("cudaSetDeviceFlags error");
  cudaHostAlloc((void **)&h_data, sizeof(int), cudaHostAllocMapped);
  cudaCheckErrors("cudaHostAlloc error");
  cudaHostGetDevicePointer((int **)&d_data, (int *)h_data, 0);
  cudaCheckErrors("cudaHostGetDevicePointer error");
  *h_data = 0;
  printf("kernel starting\n");
  mykernel<<<1,1>>>(d_data);
  cudaCheckErrors("kernel fail");
  int value = 0;
  do{
    int value1 = *h_data;
    if (value1 > value){
       printf("h_data = %d\n", value1);
       value = value1;}}
    while (value < (INCS-1));
  cudaDeviceSynchronize();
  cudaCheckErrors("kernel fail 2");

// now try matrix multiply with progress

  float *h_c, *d_a, *d_b, *d_c;
  h_c = (float *)malloc(MAT_DIMX*MAT_DIMY*sizeof(float));
  if (h_c == NULL) {printf("malloc fail\n"); return 1;}
  cudaMalloc((void **)&d_a, MAT_DIMX*MAT_DIMY*sizeof(float));
  cudaCheckErrors("cudaMalloc a fail");
  cudaMalloc((void **)&d_b, MAT_DIMX*MAT_DIMY*sizeof(float));
  cudaCheckErrors("cudaMalloc b fail");
  cudaMalloc((void **)&d_c, MAT_DIMX*MAT_DIMY*sizeof(float));
  cudaCheckErrors("cudaMalloc c fail");

  for (int i = 0; i < MAT_DIMX*MAT_DIMY; i++) h_c[i] = rand()/(float)RAND_MAX;
  cudaMemcpy(d_a, h_c, MAT_DIMX*MAT_DIMY*sizeof(float), cudaMemcpyHostToDevice);
  cudaCheckErrors("cudaMemcpy a fail");
  cudaMemcpy(d_b, h_c, MAT_DIMX*MAT_DIMY*sizeof(float), cudaMemcpyHostToDevice);
  cudaCheckErrors("cudaMemcpy b fail");

  cudaEvent_t start, stop;
  cudaEventCreate(&start); cudaEventCreate(&stop);
  *h_data=0;
  dim3 block(16,16);
  dim3 grid(((MAT_DIMX+block.x-1)/block.x), ((MAT_DIMY+block.y-1)/block.y));
  printf("matrix multiply kernel starting\n");
  cudaEventRecord(start);
  matmult<<<grid,block>>>(d_a, d_b, d_c, MAT_DIMY, MAT_DIMX, MAT_DIMX, d_data);
  cudaEventRecord(stop);
#if USE_PROGRESS
  unsigned int num_blocks = grid.x*grid.y;
  float my_progress = 0.0f;
  value = 0;
  printf("Progress:\n");
  do{
    cudaEventQuery(stop);  // may help WDDM scenario
    int value1 = *h_data;
    float kern_progress = (float)value1/(float)num_blocks;
    if ((kern_progress - my_progress)> 0.1f) {
      printf("percent complete = %2.1f\n", (kern_progress*100));
      my_progress = kern_progress;}}
    while (my_progress < 0.9f);
  printf("\n");
#endif
  cudaEventSynchronize(stop);
  cudaCheckErrors("event sync fail");
  float et;
  cudaEventElapsedTime(&et, start, stop);
  cudaCheckErrors("event elapsed time fail");
  cudaDeviceSynchronize();
  cudaCheckErrors("mat mult kernel fail");
  printf("matrix multiply finished.  elapsed time = %f milliseconds\n", et);


  return 0;
}

与第一个内核调用相关的代码只是为了演示让内核报告它的进度的基本思想。

代码的第二部分显示了 GPU 上的简单矩阵乘法示例,GPU 报告它的进度。我已经包含了通过预处理器宏删除进度检查代码的能力,以及对矩阵乘法内核计时的能力。对于我在这里的情况,有或没有进度代码的时间没有明显的差异。因此,虽然进度报告代码可能确实增加了一些开销,但与合理大小的矩阵乘法内核的范围相比,它并没有增加我可以看到的大量时间。

【讨论】:

  • 谢谢罗伯特。我会以这种方式测试我的程序。而且我觉得我一直在使用我熟悉的 API,因为我从未使用过这两个 API(cudaHostAllocMapped、cudaHostGetDevicePointer)。看来我只需要学习你的代码。谢谢!
  • *h_data 在我的机器上永远不会改变。使用 Nsight,我可以看到 atomicAdd 随着内核中值的增长而工作,但在读取主机代码时并没有反映出来。这需要TCC吗?我希望这适用于我的 GTX 980,但它永远不会离开 do-while 循环。
  • 我认为它在 linux 上可以正常工作(刚刚再次测试),我认为它应该在 windows TCC 上类似地工作。 Windows WDDM 可能会带来一些我在编写它时没有预料到的挑战。有时在 WDDM 模式下需要特殊步骤,例如 here 中所述。我并不是说这是对 WDDM 中此代码的任何困难的解释,我只是指出它可能需要一些额外的要求才能在 WDDM 下工作。我没有 WDDM 机器方便的 ATM 来测试,但如果时间允许,我会看看。
  • 好吧,它适用于cudaEventQuery。我觉得这样做很脏。 :) 谢谢你的提示。这是一个 bug/bugworthy 的问题,还是只是在非 TCC 或 Geforce 设备上预期的问题。?
  • 欢迎您提交错误或改进请求。我不清楚这绝对是一个错误。虽然我可能喜欢 WDDM 中的不同行为,但发生的事情对我来说是有意义的。当我在 WDDM 下简要查看它时,它似乎只需要队列刷新,但这似乎与您在 nsight 中报告的观察结果不一致。但我没有仔细看。
猜你喜欢
  • 2012-03-05
  • 1970-01-01
  • 1970-01-01
  • 2012-09-25
  • 1970-01-01
  • 1970-01-01
  • 2018-06-16
  • 2017-11-28
  • 1970-01-01
相关资源
最近更新 更多