使用CUDA streams和memCpyAsync的错误结果,添加cudaDeviceSynchronize后变为正确
我正在开发CUDA矩阵乘法,但我做了一些修改,以观察它们如何影响性能 我试图观察一个简单的矩阵乘法内核的行为(并测量GPU事件时间的变化)。但我在两种不同的条件下测试它:使用CUDA streams和memCpyAsync的错误结果,添加cudaDeviceSynchronize后变为正确,cuda,cuda-streams,Cuda,Cuda Streams,我正在开发CUDA矩阵乘法,但我做了一些修改,以观察它们如何影响性能 我试图观察一个简单的矩阵乘法内核的行为(并测量GPU事件时间的变化)。但我在两种不同的条件下测试它: 对于A、B和C,我有大量的矩阵(比如说matN),然后我转移(H2D)一个矩阵给A,一次转移一个矩阵给B,然后将它们叠加,以转移回(D2H)一个C 对于A、B和C,我都有matN,但对于A和B,我每次传输>1(比如chunk)矩阵,精确执行chunk乘法,并传回chunk结果矩阵C 在第一种情况下(chunk=1),所有功
- 对于A、B和C,我有大量的矩阵(比如说
),然后我转移(H2D)一个矩阵给A,一次转移一个矩阵给B,然后将它们叠加,以转移回(D2H)一个CmatN
- 对于A、B和C,我都有
,但对于A和B,我每次传输>1(比如matN
)矩阵,精确执行chunk
乘法,并传回chunk
结果矩阵Cchunk
chunk=1
),所有功能都按预期工作,但在第二种情况下(chunk>1
),我发现一些Cs是正确的,而另一些是错误的
但是如果我在cudaMemcpyAsync
之后加上cudaDeviceSynchronize()
,我得到的所有结果都是正确的
下面是我刚才描述的代码部分:
/****main.cpp****/
int chunk=matN/iters;
#ifdef LOWPAR
GRIDx=1;
GRIDy=1;
label=“低”;
#否则
int-sizeX=M;
int-sizeY=N;
GRIDx=天花板((sizeX)/砌块);
GRIDy=celi((sizeY)/块);
标签=”;
#恩迪夫
常量int字节=M*K*sizeof(浮点);
常量int bytesB=K*N*sizeof(浮点);
常量int字节=M*N*sizeof(浮点);
//设备内存分配
浮动*Ad、*Bd、*Cd;
gpuErrchk(cudaMalloc((void**)和Ad,bytesA*chunk));
gpuErrchk(cudaMalloc((void**)和Bd,bytesB*chunk));
gpuErrchk(cudaMalloc((void**)和Cd,bytesC*chunk));
//主机固定内存分配
浮动*A、*B、*C;
gpuErrchk(cudamalochost((void**)和A,bytesA*matN));
gpuErrchk(cudamalochost((void**)和B,bytesB*matN));
gpuerchk(cudamalochost((void**)和C,bytesC*matN));
//主机数据初始化
对于(int i=0;i如果使用多个流,则可以在使用它们之前覆盖Ad
和Bd
具有iters=2
和nStream=2
的示例:
for (int i = 0; i < iters; ++i) {
int j = i%nStream;
int idx = i*size*chunk;
newSquareMatMulKer(A+idx, B+idx, C+idx, Ad, Bd, Cd, N, chunk, stream[j]);
}
由于您在设备上使用相同的内存区域进行两次呼叫,您可能会遇到几个同步问题:
调用1
在调用0:squareMatMulKernel
结束之前开始在设备上复制A
和B
,因此您可能会使用不正确的A
和/或B
值来计算第一次迭代
call 1:squareMatMulKernel
在从调用0检索C
的值之前启动,因此您可以使用call 1
中的值覆盖C
要解决此问题,我看到两种方法:
- 使用同步,如您的示例中的
cudaDeviceSynchronize();
- 例如,您可以在两个设备端(每个流一个工作区)分配更多内存
''
//设备内存分配
浮动*Ad、*Bd、*Cd;
gpuErrchk(cudaMalloc((void**)和Ad,bytesA*chunk*nStream));
gpuErrchk(cudaMalloc((void**)和Bd,bytesB*chunk*nStream));
gpuErrchk(cudaMalloc((void**)和Cd,bytesC*chunk*nStream));
/*代码在这里*/
对于(int i=0;i
在这种情况下,在循环结束之前不需要同步。如果D2H副本有cudaMemcpyAsync,那么在检查主机上的结果之前,当然需要某种同步。这是预期的行为。@RobertCrovella是的,我完全同意。但是在所有内核/memcpy之后,在打印结果之前,我需要调用msTot=endEvent(&starteEvent,&stopEvent)
内部有一个cudaEventSynchronize
。我原以为这是一个足够的同步,但事实并非如此,我也不明白为什么。另一件事我不明白:是否真的有必要在每个D2H之后放置一个cudaevicesynchronize
?这会大大降低性能,重叠又如何ing?有人能解释一下为什么我的问题得a-1吗?我想知道可能的原因来纠正我的问题并提高我的问题技能。你非常清楚地解释了我的同步问题,现在我深刻地理解了我的代码做了什么以及为什么它不起作用。顺便说一句,我尝试了你建议的第二个解决方案。我选择了那个解决方案来避免问题d一个同步,只是时间问题。对于每个尝试类似事情的人,如果内存是主要因素,我建议使用第一种解决方案。第二种解决方案使我的内存对于大矩阵大小和/或高块数满。
newSquareMatMulKer(A, B, C, Ad, Bd, Cd, N, chunk, stream[0]); // call 0
newSquareMatMulKer(A+idx, B+idx, C+idx, Ad, Bd, Cd, N, chunk, stream[1]); // call 1
//device mem allocation
float *Ad, *Bd, *Cd;
gpuErrchk( cudaMalloc((void **)&Ad, bytesA*chunk*nStream) );
gpuErrchk( cudaMalloc((void **)&Bd, bytesB*chunk*nStream) );
gpuErrchk( cudaMalloc((void **)&Cd, bytesC*chunk*nStream) );
/* code here */
for (int i = 0; i < iters; ++i) {
int j = i%nStream;
int idx = i*size*chunk;
int offset_stream = j*size*chunk;
newSquareMatMulKer(A+idx, B+idx, C+idx,
Ad + offset_stream ,
Bd + offset_stream ,
Cd + offset_stream , N, chunk, stream[j]);
}