关于矩阵相乘GPU

大家好: 我希望写一个在GPU上实现矩阵相乘的程序。之前关于cuda的存储方式,在下面的贴子中我有提问。基本明白了cuda的原理(最基础的)。
我写了一个MN=P,M是一个48的矩阵,N是一个8*2的矩阵的GPU程序。如附件。[attach]3053[/attach]
程序如下。
我得到的结果与想要的不一致。我觉得是调用kernel时没计算好。或是我的程序本身就有问题(但我还不知道)。
结果如图。

   我的问题是:
    1.我有没有可能 通过程序的输出显示在GPU上运行的过程(这样就大体可以找到问题所在)。
    2.我的程序有没有问题,(现在看是一定有的,),问题在哪里。kernel写的不对?

我的机器是quadro K1000M的显卡,用的是vs2010 和cuda4.2

# include "stdio.h"
# include "stdlib.h"

/*
this pro is for the GPU version of the matrix multiple.
use the function
*/

// define the device function
__global__ void fun_MatMul_GPU(float *Md, float *Nd, float *Pd, int M_row, int M_col, int N_col){
	int tx = threadIdx.x;
	int ty = threadIdx.y;

	// init p_tmp
	float p_tmp=0;
	float M_tmp=0;
	float N_tmp=0;
	int k=0;

	// iteration thread
	for (k=0;k<M_col;k++)
	{
		M_tmp = Md[tx*M_col+k ];
		N_tmp = Nd[k *N_col+ty];
		p_tmp = p_tmp+M_tmp*N_tmp;
	}
	// write the Pd
	Pd[tx * N_col + ty] = p_tmp;
}

// main function
int main(void)
{	
	// init and malloc M,N,P **************************************
	printf("----- init matrix--------------------------------------\n");
	int i,j,ind;
	//int	k=0;

	// define the matrix size, M,N which will define P
	int M_row = 4;
	int M_col = 8;
	int N_row = M_col;
	int N_col = 2;

	// matrix memory size
	int M_size = M_row * M_col * sizeof(float);
	int N_size = N_row * N_col * sizeof(float);
	int P_size = M_row * N_col * sizeof(float);
	
	// malloc the matrix memory
	float * M_h =(float *) malloc(M_size);
	float * N_h =(float *) malloc(N_size);
	float * P_h =(float *) malloc(P_size);

	// init M matrix
	printf("M=\n");
	for (i=0;i<M_row;i++)
	{
		for (j=0;j<M_col;j++)
		{
			ind=i*M_col+j;
			M_h[ind]=(i+1)+0.01*(j+1);
			printf("%3.3f, ",M_h[ind]);
		}
		printf("\n");
	}

	// init N matrix	
	printf("N=\n");
	for (i=0;i<N_row;i++)
	{
		for (j=0;j<N_col;j++)
		{
			ind=i*N_col+j;
			N_h[ind]=(i+1)+0.001*(j+1);
			printf("%3.3f, ",N_h[ind]);
		}
		printf("\n");
	}

	// cudamalloc M,N,P**********************************
	printf("----- cudamalloc matrix-----------------------------\n");
	float *M_d, *N_d, *P_d;
	
	cudaMalloc((void **) &M_d, M_size);
	cudaMalloc((void **) &N_d, N_size);
	cudaMalloc((void **) &P_d, P_size);

	// transfor data from host to device
	cudaMemcpy(M_d,M_h,M_size,cudaMemcpyHostToDevice);
	cudaMemcpy(N_d,N_h,N_size,cudaMemcpyHostToDevice);

	// init kernerl
	int block_size=16;
	int grid_size=1;
	dim3 dimBlock(block_size,block_size);
	dim3 dimGrid(grid_size,grid_size);

	// GPU cal
	fun_MatMul_GPU<<<dimGrid,dimBlock>>>(M_d,N_d,P_d,M_row,M_col,N_col);
	
	// transfor data from device to host
	cudaMemcpy(P_h,P_d,P_size,cudaMemcpyDeviceToHost);

	printf("----- GPU end  ----------------------------------\n");
	
	// output multiple result **************************
	printf("----- output multiple result----------------------\n");
   for (i=0;i<M_row;i++)
   {
   	for (j=0;j<N_col;j++)
   	{
   		ind=(i+1)*N_col+(j+1);
   		printf("%4.4f ,",P_h[ind]);
   	}
   	printf("\n");
   }
	// free M,N,P***************************************
	printf("----- free M,N,P---------------------------------\n");
	
	// free the host memory	
	free(M_h);
	free(N_h);
	free(P_h);

	// free the device memory
	cudaFree(M_d);
	cudaFree(N_d);
	cudaFree(P_d);

	return 0;
}
// mod : 11:08 2013/4/13
// debug .
// mod : Sat Apr 13 08:36:18 CST 2013
// cudaMalloc the memory on GPU device.
// the result is wrong!!!!
// mod : Fri Apr 12 18:19:09 CST 2013
// the GPU function
// mod : Fri Apr 12 06:44:14 CST 2013
// build the main structure.

http://cudazone.nvidia.cn/forum/forum.php?mod=viewthread&tid=6566&extra=page%3D1

LZ您好,目测您的kernel逻辑上是没问题的,虽然只能用一个block来算,但是计算您这个例子的规模是足够的。

您的问题在于调用kernel时的参数不正确,根据您的kernel的写法,是一个线程完成P矩阵中一个元素的计算。P矩阵您这里是42的矩阵,所以需要42个线程即可,也就是blockDim.x=4,blockDim.y=2。
但是您使用了16*16的block,并且没有在kernel里面判断去除不用的thread,同时您的kernel里面使用了线程编号来寻址,那么会产生越界,从而造成不正确的结果。

另外需要说明的是,轻微的越界不会使kernel直接挂掉,但是会使结果异常。

目测问题如此,请LZ尝试修正。

祝您编码愉快~

ice版主:
您好。
根据您所说的,重新设置了block的大小 。按如下方式,结果仍然不正确(我觉得很可能是您所说的越界所造成的,)。
关于为什么要调这么大的block,我基本明白了。
问题1.不知道我这样设置是否正确。
问题2.关于这句话,我没怎么看明白。“同时您的kernel里面使用了线程编号来寻址,那么会产生越界,从而造成不正确的结果。”,能否稍稍再解释一下。
非常感谢您的回复和帮助。

        // init kernerl
   int block_size_x=M_row; //mod here, use the row of M to define block size.
   int block_size_y=N_col;
   int grid_size=1;
   dim3 dimBlock(block_size_x,block_size_y);
   dim3 dimGrid(grid_size,grid_size);

   // GPU cal
   fun_MatMul_GPU<<<dimGrid,dimBlock>>>(M_d,N_d,P_d,M_row,M_col,N_col);

LZ您好,您在3#给出的block的设定,我觉得是正确的。

此外,您在113行的代码“ind=(i+1)*N_col+(j+1);”这里计算偏移量ind的时候,为何要用i+1和j+1?您在给M,N赋值的时候,ind并不是这样计算的。

“kernel里面使用了线程编号来寻址”指的是类似于您28行的用法“ Pd[tx * N_col + ty] = p_tmp;”这里,tx和ty都来自于您的线程编号,但是他们参与了计算偏移量表达式,求得偏移量之后,使用Pd首地址+偏移量的方式获得您所需变量的地址。

按照您的代码,P矩阵是42的,但是您顶楼的代码使用了1616的block,那么tx和ty都可以最大取到15,此时偏移量为15152+15(个float变量),这已经超出了您申请的空间,产生了越界。

即便不越界,当tx=2,ty=1时,写入的位置是Pd[2*2+1]即Pd[5],但同时,tx=0,ty=5时,写入位置也为Pd[5],这绝对不是您所需要的行为。

所以您需要在设置block时,选择合适的线程数量并安排好,或者您也可以在kernel里面加以判断,让超出范围的线程直接return。

以上供您参考,祝您编码顺利~

非常感谢ice版主的讲解,我再试着修改下。

不客气的,欢迎您继续尝试和反馈,欢迎您常来论坛~

祝您周末愉快!

在版主的帮助下,程序终于是调试好了。虽然第一个cuda的程序还是很简陋。放在下面。给可能有相同问题的人以借鉴。

# include "stdio.h"
# include "stdlib.h"

/*
this pro is for the GPU version of the matrix multiple.
use the function

the simple one, use the thread index to calculate the P(i,j) value.
*/

// define the device function
__global__ void fun_MatMul_GPU(float *Md, float *Nd, float *Pd, int M_row, int M_col, int N_col){
	// this two variables replace the i,j for loop to calculate the P(i,j) value in GPU device.
	int tx = threadIdx.x; // thread index row,
	int ty = threadIdx.y; // thread index col,

	// init p_tmp
	float p_tmp=0;
	float M_tmp=0;
	float N_tmp=0;
	int k=0;

	// iteration thread, for each  thread, use the dot product to get the p(i,j) value
	for (k=0;k<M_col;k++)
	{
		M_tmp = Md[tx*M_col+k ];     // M(i,:), the tx row of Md
		N_tmp = Nd[k *N_col+ty];     // N(:,j), the ty col of Nd
		p_tmp = p_tmp+M_tmp*N_tmp;   
	}

	// write the Pd
	Pd[tx * N_col + ty] = p_tmp;
}

// main function
int main(void)
{	
	// init and malloc M,N,P **************************************
	printf("----- init matrix--------------------------------------\n");
	int i,j,ind;
	//int	k=0;

	// define the matrix size, M,N which will define P
	int M_row = 4;
	int M_col = 8;
	int N_row = M_col; // for matrix multiple.
	int N_col = 2;

	// matrix memory size
	int M_size = M_row * M_col * sizeof(float); // memory size of M_h and M_d
	int N_size = N_row * N_col * sizeof(float); // memory size of N_h and N_d
	int P_size = M_row * N_col * sizeof(float); // memory size of P_h and P_d
	
	// malloc the matrix memory
	float * M_h =(float *) malloc(M_size);   
	float * N_h =(float *) malloc(N_size);
	float * P_h =(float *) malloc(P_size);

	// init M matrix
	printf("M=\n");
	for (i=0;i<M_row;i++)
	{
		for (j=0;j<M_col;j++)
		{
			ind=i*M_col+j;
			M_h[ind]=(i+1)+0.01*(j+1);
			printf("%3.3f, ",M_h[ind]);
		}
		printf("\n");
	}

	// init N matrix	
	printf("N=\n");
	for (i=0;i<N_row;i++)
	{
		for (j=0;j<N_col;j++)
		{
			ind=i*N_col+j;
			N_h[ind]=(i+1)+0.001*(j+1);
			printf("%3.3f, ",N_h[ind]);
		}
		printf("\n");
	}

	// cudamalloc M,N,P**********************************
	printf("----- cudamalloc matrix-----------------------------\n");
	float *M_d, *N_d, *P_d;
	
	// cudaMalloc to malloc the memory size on GPU device
	cudaMalloc((void **) &M_d, M_size);
	cudaMalloc((void **) &N_d, N_size);
	cudaMalloc((void **) &P_d, P_size);

	// transfor data from host to device
	cudaMemcpy(M_d,M_h,M_size,cudaMemcpyHostToDevice);
	cudaMemcpy(N_d,N_h,N_size,cudaMemcpyHostToDevice);

	// init kernerl,
	int block_size_x=M_row;// mod on : 17:27 2013/4/13. use M_row and N_col to define the blocksize
   int block_size_y=N_col;
	int grid_size=1;
	dim3 dimBlock(block_size_x,block_size_y);
	dim3 dimGrid(grid_size,grid_size);

	// GPU cal
	fun_MatMul_GPU<<<dimGrid,dimBlock>>>(M_d,N_d,P_d,M_row,M_col,N_col);
	
	// transfor data from device to host
	cudaMemcpy(P_h,P_d,P_size,cudaMemcpyDeviceToHost);

	printf("----- GPU end  -------------------------------------\n");

	// output multiple result **************************
	printf("----- output multiple result------------------------\n");
   for (i=0;i<M_row;i++)
   {
   	for (j=0;j<N_col;j++)
   	{
   		ind=(i)*N_col+(j); //mod : the wrong index is : ind=(i+1)*N_col+(j+1); 
   		printf("%4.4f ,",P_h[ind]);
   	}
   	printf("\n");
   }

	// free M,N,P***************************************
	printf("----- free M,N,P-----------------------------------\n");
	
	// free the host memory	
	free(M_h);
	free(N_h);
	free(P_h);

	// free the device memory
	cudaFree(M_d);
	cudaFree(N_d);
	cudaFree(P_d);

	return 0;
}

感谢LZ反馈~

汇江河以成大海,积跬步以至千里。
当LZ他日精通CUDA之时,回首此帖,必有特别的意义。

祝您编码愉快~