大型矩阵的 CUDA 矩阵乘法中断

Posted

技术标签:

【中文标题】大型矩阵的 CUDA 矩阵乘法中断【英文标题】:CUDA Matrix multiplication breaks for large matrices 【发布时间】:2011-05-02 20:50:03 【问题描述】:

我有以下矩阵乘法代码,使用 CUDA 3.2 和 VS 2008 实现。我在 Windows server 2008 r2 企业版上运行。我正在运行 Nvidia GTX 480。以下代码适用于“宽度”(矩阵宽度)的值高达 2500 左右。

int size = Width*Width*sizeof(float);
float* Md, *Nd, *Pd;
cudaError_t err = cudaSuccess;

//Allocate Device Memory for M, N and P
err = cudaMalloc((void**)&Md, size);
err = cudaMalloc((void**)&Nd, size);
err = cudaMalloc((void**)&Pd, size);

//Copy Matrix from Host Memory to Device Memory
err = cudaMemcpy(Md, M, size, cudaMemcpyHostToDevice);
err = cudaMemcpy(Nd, N, size, cudaMemcpyHostToDevice);

//Setup the execution configuration
dim3 dimBlock(TileWidth, TileWidth, 1);
dim3 dimGrid(ceil((float)(Width)/TileWidth), ceil((float)(Width)/TileWidth), 1);

MatrixMultiplicationMultiBlock_Kernel<<<dimGrid, dimBlock>>>(Md, Nd, Pd, Width);

err = cudaMemcpy(P, Pd, size, cudaMemcpyDeviceToHost);

//Free Device Memory
cudaFree(Md);
cudaFree(Nd);
cudaFree(Pd);

当我将“宽度”设置为 3000 或更大时,黑屏后出现以下错误:

我在网上看了一下,看到有人出现这个问题,因为看门狗在内核挂起超过 5 秒后正在杀死内核。我尝试在注册表中编辑“TdrDelay”,这延迟了黑屏和出现相同错误之前的时间。所以我得出结论,这不是我的问题。

我调试了我的代码,发现这行是罪魁祸首:

err = cudaMemcpy(P, Pd, size, cudaMemcpyDeviceToHost);

这是我在调用矩阵乘法内核函数后用于从设备返回结果集的方法。到目前为止,一切似乎都运行良好。我相信我正在正确分配内存并且无法弄清楚为什么会发生这种情况。我想也许我的卡上没有足够的内存,但是 cudaMalloc 不应该返回错误吗? (我在调试时确认它没有)。

任何想法/帮助将不胜感激!...非常感谢大家!!

内核代码:

//Matrix Multiplication Kernel - Multi-Block Implementation
__global__ void MatrixMultiplicationMultiBlock_Kernel (float* Md, float* Nd, float* Pd, int Width) 

int TileWidth = blockDim.x;

//Get row and column from block and thread ids
int Row = (TileWidth*blockIdx.y) + threadIdx.y;
int Column = (TileWidth*blockIdx.x) + threadIdx.x;

//Pvalue store the Pd element that is computed by the thread
float Pvalue = 0;

for (int i = 0; i < Width; ++i)

    float Mdelement = Md[Row * Width + i];
    float Ndelement = Nd[i * Width + Column];
    Pvalue += Mdelement * Ndelement;


//Write the matrix to device memory each thread writes one element
Pd[Row * Width + Column] = Pvalue;

我也有这个使用共享内存的其他功能,它也给出了同样的错误:

呼叫:

            MatrixMultiplicationSharedMemory_Kernel<<<dimGrid, dimBlock, sizeof(float)*TileWidth*TileWidth*2>>>(Md, Nd, Pd, Width);

内核代码:

 //Matrix Multiplication Kernel - Shared Memory Implementation
 __global__ void MatrixMultiplicationSharedMemory_Kernel (float* Md, float* Nd, float* Pd, int Width) 
 
int TileWidth = blockDim.x;

//Initialize shared memory
extern __shared__ float sharedArrays[];
float* Mds = (float*) &sharedArrays;
float* Nds = (float*) &Mds[TileWidth*TileWidth];

int tx = threadIdx.x;
int ty = threadIdx.y;

//Get row and column from block and thread ids
int Row = (TileWidth*blockIdx.y) + ty;
int Column = (TileWidth*blockIdx.x) + tx;
float Pvalue = 0;

//For each tile, load the element into shared memory
for( int i = 0; i < ceil((float)Width/TileWidth); ++i)

    Mds[ty*TileWidth+tx] = Md[Row*Width + (i*TileWidth + tx)];
    Nds[ty*TileWidth+tx] = Nd[(ty + (i * TileWidth))*Width + Column]; 

    __syncthreads();

    for( int j = 0; j < TileWidth; ++j)
    
        Pvalue += Mds[ty*TileWidth+j] * Nds[j*TileWidth+tx];
    

    __syncthreads();


//Write the matrix to device memory each thread writes one element
Pd[Row * Width + Column] = Pvalue;

【问题讨论】:

能发一下内核代码吗? 编辑:添加了两个内核代码函数 为了完整起见,您能否发布 dimGrid/dimBlock 分配?我假设dimGrid.x = dimGrid.y 和dimBlock.x = dimBlock.y,而那个Width 是dimBlock.x 的倍数? @Tom,我添加了我的 dimGrid 和 dimBlock 定义并在那里发现了问题.. 我没有使用多个 dimBlock... 我的内核代码没有正确处理.. 谢谢你很多!!!!,请将此评论添加到下面的答案中,以便我选择它。再次感谢!!! 不客气。我应该建议早点运行cuda-memcheck,对此感到抱歉。它应该立即检测到越界访问。 【参考方案1】:

控制 WDDM 超时

问题实际上是内核而不是cudaMemcpy()。当您启动内核时,GPU 会关闭并与 CPU 异步执行工作,因此只有当您与 GPU 同步时,您才必须等待工作完成。 cudaMemcpy() 涉及隐式同步,因此您会看到问题所在。

您可以通过在内核之后调用cudaThreadSynchronize() 来仔细检查这一点,问题似乎出在cudaThreadSynchronize() 而不是cudaMemcpy()

更改 TDR 超时后,您是否重新启动了计算机?不幸的是,需要重新启动 Windows 才能更改 TDR 设置。 This Microsoft document 对可用的完整设置有相当好的描述。

内核问题

在这种情况下,问题实际上不是 WDDM 超时。您需要解决内核中的错误(例如,您应该能够在每次迭代中将i 增加一个以上)并且检查 SDK 中的matrixMul 示例可能很有用。顺便说一句,我希望这是一个学习练习,因为实际上您最好(就性能而言)使用 CUBLAS 来执行矩阵乘法。

代码中最关键的问题是您使用共享内存而没有实际分配任何内存。在您的内核中,您有:

//Initialize shared memory
extern __shared__ float sharedArrays[];

但是当你启动内核时,你并没有指定为每个块分配多少共享内存:

MatrixMultiplicationMultiBlock_Kernel<<<dimGrid, dimBlock>>>(Md, Nd, Pd, Width);

>> 语法实际上有四个参数,其中第三个和第四个是可选的。第四个是流索引,用于获取计算和数据传输(以及并发内核执行)之间的重叠,但 third 参数指定每个块的共享内存量。在这种情况下,我假设您想将 TileWidth * TileWidth 浮点数存储在共享内存中,因此您可以使用:

MatrixMultiplicationMultiBlock_Kernel<<<dimGrid, dimBlock, dimBlock.x * dimBlock.x * sizeof(float)>>>(Md, Nd, Pd, Width);

主要问题

正如您在评论中提到的那样,实际的问题是您的矩阵宽度不是块宽度的倍数(和高度,因为它是方形的,这意味着超出末端的线程将访问超出数组的末端。代码应该处理非多重情况,或者应该确保宽度是块大小的倍数。

我应该早些提出这个建议,但运行cuda-memcheck 来检查内存访问违规通常很有用。

【讨论】:

我确实重新启动并且更改生效,因为黑屏出现的时间更长..但是它仍然崩溃.... 我回家后会尝试 cudaThreadSynchronize 并重新发布! 好吧,你是对的。我刚刚这样做了,我得到了同样的错误......我将 TdrDelay 作为 REG_DWORD 添加到 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Contol\GraphicsDrivers。我重新启动了我的机器,我注意到屏幕变黑并出现错误需要更长的时间..只要我设置了延迟..但它仍然不起作用。我不完全相信这是一个延迟,因为它处理 2500 的宽度就好了,但除此之外,它崩溃了.. 甚至 2800 ......我错过了什么吗? 我现在还注意到,当我添加 cudaThreadSynchronize() 时,它会破坏它以前用于工作的大小矩阵... 2500 会在添加该行时破坏它。 (我不知道这是否给你们任何线索) IIUC 高达 ~2500 它可以快速完成(比如不到几秒钟),但超过此时间,无论您将 TDR 设置多高,它都不会完成。您可以通过将 TDR 设置得很高来检查这一点,请耐心等待!既然如此,下一步就是看内核了。【参考方案2】:

您必须更改驱动程序超时设置,是 Windows 功能,以防止有故障的驱动程序使系统无响应。 查看描述如何执行此操作的 Microsoft Page 。

【讨论】:

我应该尝试除 TdrDelay 之外的其他方法吗?【参考方案3】:

您还应该检查 GPU 设备上的“超时”标志设置。如果您安装了 CUDA SDK,我相信“deviceQuery”应用会报告此属性。

【讨论】:

感谢您的回复!我该去哪里修改这个属性? 我不确定如何修改它 - 这是驱动程序处理的事情。这可能与您是否将显示器连接到设备有关。

以上是关于大型矩阵的 CUDA 矩阵乘法中断的主要内容,如果未能解决你的问题,请参考以下文章

CUDA做矩阵乘法如何计算所需显存

CUDA 矩阵乘法优化

在 CUDA 的平铺矩阵乘法中访问矩阵作为其转置

CUDA矩阵乘法的性能

使用 CUDA 进行矩阵乘法:2D 块与 1D 块

CUDA 平铺矩阵乘法解释