使用CUDA streams和memCpyAsync的错误结果,添加cudaDeviceSynchronize后变为正确

使用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),所有功

我正在开发CUDA矩阵乘法,但我做了一些修改,以观察它们如何影响性能

我试图观察一个简单的矩阵乘法内核的行为(并测量GPU事件时间的变化)。但我在两种不同的条件下测试它:

  • 对于A、B和C,我有大量的矩阵(比如说
    matN
    ),然后我转移(H2D)一个矩阵给A,一次转移一个矩阵给B,然后将它们叠加,以转移回(D2H)一个C

  • 对于A、B和C,我都有
    matN
    ,但对于A和B,我每次传输>1(比如
    chunk
    )矩阵,精确执行
    chunk
    乘法,并传回
    chunk
    结果矩阵C

在第一种情况下(
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]); 
}