Kernel中定义变量

版主您好,我又测试了段代码,还有一些问题想请教:
如果使用18行,结果显示为0.00;如果使用19行,结果显示为110.00;
如果使用19行,并把所有float替换为double,结果显示才正确;
这是什么原因呢?
kernel中定义变量应该是没问题的吧?而且每个线程执行的时候,这些变量是不是应该相互不影响;那么是精度的问题吗?

#include <stdio.h>
#include <cuda_runtime.h>
#include "..\..\..\..\..\..\..\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v5.0\include\device_launch_parameters.h"
#include "..\..\..\..\..\..\..\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v5.0\include\common_functions.h"

#define Nx 10
#define Nz 10


__global__ void Mykernel(float *AA)
{
   float r=2000.0;
   float tt=0.001;
   int ix=threadIdx.x+blockDim.x*blockIdx.x;
   int iz=threadIdx.y+blockDim.y*blockIdx.y;

   if(ix<Nx&&iz<Nz){
   //AA[iz*Nx+ix] = 1.0/r*tt+110.0;
   AA[iz*Nx+ix] =1.0/2000.0*0.001+110.0;
   }
}



int main()
{


   float *CPU_V;
   cudaMallocHost((void**)&CPU_V,Nx*Nz*sizeof(float));

   float *GPU_V;
   cudaMalloc((void**)&GPU_V,Nx*Nz*sizeof(float));        cudaMemset(GPU_V,0.0,Nx*Nz*sizeof(float));

   dim3 TheadsPerBlock(32,32);
   dim3 BlockPerGrid((Nx+32-1)/32,(Nz+32-1)/32);
   Mykernel<<<BlockPerGrid,TheadsPerBlock >>>(GPU_V);

   cudaError_t error;
   error=cudaDeviceSynchronize();
   cudaError_t error_cy;
   error_cy=cudaMemcpy(CPU_V,GPU_V,Nx*Nz*sizeof(float),cudaMemcpyDeviceToHost);
   printf("error=%s,error_cy=%s\n",cudaGetErrorString(error),cudaGetErrorString(error_cy));

   for (int iz=0;iz<Nz;iz++)
   {        
   for (int ix=0;ix<Nx;ix++)
   {        
   //if (CPU_V[iz*Nx+ix]!=211.0)
   {        
   printf("%.10f,iz=%d,ix=%d\n",CPU_V[iz*Nx+ix],iz,ix);
   }
   }
   }

   cudaFree(CPU_V);
   //cudaDeviceReset();
   cudaFree(GPU_V);
   printf("hello world!\n");
   return 0;

}

LZ您好:

float精度的浮点数,换算成10进制,只有大概7位有效数字,按照您的写法,1.0/2000.0*0.001+110,需要10位有效数字才能表示,所以您使用double精度的浮点数可以正确表达,而使用float精度的浮点数则会丢失部分尾数。

另,就您18行的写法,经我简单测试,是可以在kernel里面这样计算的,并无问题(和19行一样会丢失尾数)。

大致如此,祝您编码顺利~

斑竹,您好,感谢您的回复
我刚才试了一下,所有的都改成double,然后是使用第18行,屏蔽19行,得到的结果是0.000,不知道为什么呢?

LZ您好:

您本楼的描述和您1#的描述是不一致的,为了避免进一步的混淆,请给出您本楼描述的(“所有都换成double”)情况下的具体的完整的代码,以便分析。

祝您好运~

版主你好,代码如下:

#include <stdio.h>
#include <cuda_runtime.h>
#include "..\..\..\..\..\..\..\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v5.0\include\device_launch_parameters.h"
#include "..\..\..\..\..\..\..\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v5.0\include\common_functions.h"

#define Nx 10
#define Nz 10


__global__ void Mykernel(double *AA)
{
	double r=2000.0;
	double tt=0.001;
	int ix=threadIdx.x+blockDim.x*blockIdx.x;
	int iz=threadIdx.y+blockDim.y*blockIdx.y;

	if(ix<Nx&&iz<Nz){
		AA[iz*Nx+ix] = 1.0/r*tt+110.0;
		//AA[iz*Nx+ix] =1.0/2000.0*0.001+110.0;
	}
}



int main()
{


	double *CPU_V;
	cudaMallocHost((void**)&CPU_V,Nx*Nz*sizeof(double));

	double *GPU_V;
	cudaMalloc((void**)&GPU_V,Nx*Nz*sizeof(double));	cudaMemset(GPU_V,0.0,Nx*Nz*sizeof(double));

	dim3 TheadsPerBlock(32,32);
	dim3 BlockPerGrid((Nx+32-1)/32,(Nz+32-1)/32);
	Mykernel<<<BlockPerGrid,TheadsPerBlock >>>(GPU_V);

	cudaError_t error;
	error=cudaDeviceSynchronize();
	cudaError_t error_cy;
	error_cy=cudaMemcpy(CPU_V,GPU_V,Nx*Nz*sizeof(double),cudaMemcpyDeviceToHost);
	printf("error=%s,error_cy=%s\n",cudaGetErrorString(error),cudaGetErrorString(error_cy));

	for (int iz=0;iz<Nz;iz++)
	{	
		for (int ix=0;ix<Nx;ix++)
		{	
			//if (CPU_V[iz*Nx+ix]!=211.0)
			{	
				printf("CPU_V=%.10lf,iz=%d,ix=%d\n",CPU_V[iz*Nx+ix],iz,ix);
			}
		}
	}

	cudaFree(CPU_V);
	//cudaDeviceReset();
	cudaFree(GPU_V);
	printf("hello world!\n");
	return 0;

}

刚才又试了一下,如果直接定义double r_daoshu=0.0005;
然后将除以r,换成乘以r_daoshu,就可以
是不是GPU太不擅长做除法运算了?
直接给1/2000,他可以做出来,
如果给t=2000, 他计算不出来1/t,是这样吧 ?

LZ您好:

GPU计算除法仅仅是慢点,不是不能计算。

我也觉的应该是这样,可是为什么5#的代码 在我电脑上跑出来就不对呢

LZ您好:

经过仔细调试您的代码,发现问题如下:

1:您的问题在于您的kernel挂掉了。通过在您的kernel后面添加一句
printf(“%s \n”,cudaGetErrorString(cudaGetLastError()));
提示too many resources requested for lanuch

2:如果您将编译类型转换为“release”,可以立即得到正确的结果。

3:考虑到您实际上只计算了1010的数据,但是却分配了3232的线程,90%以上的线程都只是去占用资源但是不干活,所以您如果使用较小的block(比如10*10的block),那么在debug下也可以正确运行。

4:考虑到您的问题出在计算除法的地方,而除法在GPU中是一个有很多中间过程的计算,需要使用较多的寄存器,而您block设置又达到了上限,可能导致每个线程没有足够的寄存器资源。一般这种情况,如果是预先可知的,请设置较小的block尺寸。

如果您必须使用较大的block尺寸,并且不在乎可能在速度方面的降低,那么也可以通过限制Max Used Register来强制GPU使用local memory,从而达到“可用”(但非“好用”)的目的。
经过测试在blocksize选择32*32的情况下,debug编译,通过限制Max Used Register的数目,也可以得到正确的结果。

5:以及为何在release下能直接得到正确的结果,一方面release编译可以按照节省资源的方式编译,无需保留临时信息供调试,另一方面,编译器可能直接识别您是除以一个常数,从而直接优化掉这个除法,编译过后就直接是乘以该数的倒数的形式。

您的问题完整分析如上,祝您编码顺利~

刚才和玫瑰斑竹又研究和测试了一下:

1:我之前在CUDA 5.0上编译的release版的代码编译器并未优化掉除法,而是生成了完整的除法代码。

2:您1#中的代码,如果在2000.0和0.001的地方主动指定为2000.0f和0.001f,那么可能直接解决问题。(此时5.0及更早版本的编译器将使用您指定的单精度版的数值进行计算,而不是按照双精度计算,最后截断,更省资源。)

3:建议您直接升级到CUDA 5.5,在CUDA 5.5中,经观察,上述对常量的除法将直接被编译掉,无论是debug模式还是release模式,速度更快,效果更好。对于赋值给单精度浮点变量的双精度值,也将直接就地在编译的时候转换为单精度的值。

综上,推荐您升级到CUDA 5.5,以直接解决各种困扰。

祝您编码顺利~

感谢ice版主和玫瑰斑细心测试与专业的回答,将block规模改小了后确实可以了,我以后在编程的时候会注意这一点
还有请问版主,cudaGetLastError()是返回最近的一个错误,cudaDeviceSynchronize()函数为什么没能检查出来kernel挂掉的错误呢?在调试程序错误的时候,用这两个哪个好?

LZ您好:

因为实际上kernel除了一般出现的异步错误以外,还可能出现在调用时就返回的同步错误(比较少见),cudaDeviceSynchronize()只能捕获kernel启动的异步错误,而runtime API中kernel是void类型的,无返回值,因此同步错误就只能通过cudaGetLastError()来寻找了。

比如本例,您的双精度的除法被编译成了使用大量寄存器的实现,在编译的时候,编译器并不知道最终会上多少个线程,而您实际上的单一block的线程数量太多,因而在启动kernel的时候立即就会发现无法执行(对于fermi和kepler SM3.5的GPU,一个block上最大数量的线程与每个线程上最大数量的寄存器,这两个限制不能同时满足。)。而如果您手工控制单一线程使用的寄存器数量,使得一个1024线程的线程所使用的寄存器的数量不超过一个SM的寄存器资源的总量的话,kernel是可以成功启动的。

同时,因为这是一个同步Error,所以并没有像异步Error一样导致后续的cudaMemcpy()失效,这样被您cudaMemset()d GPU_V数组的值就被复制回来了。

大致分析如此,亦有玫瑰斑竹的贡献。

总而言之,您需要两者配合使用,才能对付一些比较少见的疑难情况。

欢迎您莅临cudazone@China,祝您编码愉快~

好的 ,又学到不少东西
感谢ice版主和玫瑰版主
话说版主,我把cuda5.5下载好了,是不是直接卸载之前的5.0toolkit然后直接安装这就新的就行了?
我在论坛的某帖子上看到过好像不用修改任何环境变量,直接安装就可以用,是这样吗?

LZ您好:

不客气的,您卸载5.0然后安装一份5.5就可以,或者您也可以直接安装CUDA 5.5,CUDA Toolkit是可以同时存在多个版本的,但是一些环境变量会被后装的那个修改,一般无问题,你要是不放心的话,只保留5.5也是很好的选择。

现在的CUDA Toolkit都是直接安装就可以免配置使用的。

大致如此,祝您好运~

嗯 好的 再次表示感谢

不客气的,欢迎您常来论坛~