按照我的设置骨架。这样执行它没有给出正确的结果。这很可能是由于内核使用它们时尚未完成的异步数据传输。我使用预处理器if-else
语句实现了“故障安全”版本。在翻译else
部分时,程序运行正常。我不明白。为什么?
in1
,out1
,...只是占位符。当然,它们指向for循环的每次迭代的不同容器。这样就可以进行异步传输。但是在迭代中,传输使用的out1
和内核的 cudaStream_t streams[2];
cudaEvent_t evCopied;
cudaStreamCreate(&streams[0]); // TRANSFER
cudaStreamCreate(&streams[1]); // KERNEL
cudaEventCreate(&evCopied);
// many iterations
for () {
// Here I want overlapping of transfers with previous kernel
cudaMemcpyAsync( out1, in1, size1, cudaMemcpyDefault, streams[0] );
cudaMemcpyAsync( out2, in2, size2, cudaMemcpyDefault, streams[0] );
cudaMemcpyAsync( out3, in3, size3, cudaMemcpyDefault, streams[0] );
#if 1
// make sure host thread doesn't "run away"
cudaStreamSynchronize( streams[1] );
cudaEventRecord( evCopied , streams[0] );
cudaStreamWaitEvent( streams[1] , evCopied , 0);
#else
// this gives the correct results
cudaStreamSynchronize( streams[0] );
cudaStreamSynchronize( streams[1] );
#endif
kernel<<< grid , sh_mem , streams[1] >>>(out1,out2,out3);
}
是相同的。
{{1}}
请不要发布建议重新安排的答案。比如,将你的内核分成几个,并在不同的流中发布。
答案 0 :(得分:2)
你正在做什么 - 或者至少使用一个事件来同步两个流 - 应该有效。基本上不可能说出为什么你的实际代码不起作用,因为你选择不发布它,并且魔鬼总是在细节中。
但是,这是一个完整的,可运行的示例,我认为它以类似于您尝试的方式使用流API,并且正常工作:
#include <cstdio>
typedef unsigned int uint;
template<uint bsz>
__global__ void kernel(uint * a, uint * b, uint * c, const uint N)
{
__shared__ volatile uint buf[bsz];
uint tid = threadIdx.x + blockIdx.x * blockDim.x;
uint stride = blockDim.x * gridDim.x;
uint val = 0;
for(uint i=tid; i<N; i+=stride) {
val += a[i] + b[i];
}
buf[threadIdx.x] = val; __syncthreads();
#pragma unroll
for(uint i=(threadIdx.x+warpSize); (threadIdx.x<warpSize)&&(i<bsz); i+=warpSize)
buf[threadIdx.x] += buf[i];
if (threadIdx.x < 16) buf[threadIdx.x] += buf[threadIdx.x+16];
if (threadIdx.x < 8) buf[threadIdx.x] += buf[threadIdx.x+8];
if (threadIdx.x < 4) buf[threadIdx.x] += buf[threadIdx.x+4];
if (threadIdx.x < 2) buf[threadIdx.x] += buf[threadIdx.x+2];
if (threadIdx.x == 0) c[blockIdx.x] += buf[0] + buf[1];
}
#define gpuErrchk(ans) { gpuAssert((ans), __FILE__, __LINE__); }
inline void gpuAssert(cudaError_t code, char *file, int line, bool abort=true)
{
if (code != cudaSuccess)
{
fprintf(stderr,"GPUassert: %s %s %d\n", cudaGetErrorString(code), file, line);
if (abort) exit(code);
}
}
int main(void)
{
const int nruns = 16, ntransfers = 3;
const int Nb = 32, Nt = 192, Nr = 3000, N = Nr * Nb * Nt;
const size_t szNb = Nb * sizeof(uint), szN = size_t(N) * sizeof(uint);
size_t sz[4] = { szN, szN, szNb, szNb };
uint * d[ntransfers+1];
for(int i=0; i<ntransfers+1; i++)
gpuErrchk(cudaMallocHost((void **)&d[i], sz[i]));
uint * a = d[0], * b = d[1], * c = d[2], * out = d[3];
for(uint i=0; i<N; i++) {
a[i] = b[i] = 1;
if (i<Nb) c[i] = 0;
}
uint * _d[3];
for(int i=0; i<ntransfers; i++)
gpuErrchk(cudaMalloc((void **)&_d[i], sz[i]));
uint * _a = _d[0], * _b = _d[1], * _c = _d[2];
cudaStream_t stream[2];
for (int i = 0; i < 2; i++)
gpuErrchk(cudaStreamCreate(&stream[i]));
cudaEvent_t sync_event;
gpuErrchk(cudaEventCreate(&sync_event));
uint results[nruns];
for(int j=0; j<nruns; j++) {
for(int i=0; i<ntransfers; i++)
gpuErrchk(cudaMemcpyAsync(_d[i], d[i], sz[i], cudaMemcpyHostToDevice, stream[0]));
gpuErrchk(cudaEventRecord(sync_event, stream[0]));
gpuErrchk(cudaStreamWaitEvent(stream[1], sync_event, 0));
kernel<Nt><<<Nb, Nt, 0, stream[1]>>>(_a, _b, _c, N);
gpuErrchk(cudaPeekAtLastError());
gpuErrchk(cudaMemcpyAsync(out, _c, szNb, cudaMemcpyDeviceToHost, stream[1]));
gpuErrchk(cudaStreamSynchronize(stream[1]));
results[j] = uint(0);
for(int i=0; i<Nb; i++) results[j]+= out[i];
}
for(int j=0; j<nruns; j++)
fprintf(stdout, "%3d: ans = %u\n", j, results[j]);
gpuErrchk(cudaDeviceReset());
return 0;
}
内核是一个“融合向量加法/减少”,只是无意义,但它依赖于三个输入中的最后一个在内核执行之前被归零以产生正确的答案,这应该只是输入数据的两倍点。在您的示例中,内核执行和异步输入数组复制位于不同的流中,因此复制和执行可能会重叠。在这种情况下,没有合理的理由在每次迭代时复制前两个大输入,除了在最后一个副本(这是关键的一个)之前引入延迟,并增加它与内核错误重叠的可能性。这可能是你出错的地方,因为我不相信CUDA内存模型可以保证异步修改正在运行的内核访问的内存是安全的。 如果这是你想要做的,那么期望它失败。但是没有看到真正的代码,就不可能多说了。
如果不这样做,你可以自己看看,如果没有cudaStreamWaitEvent
在内核启动之前同步两个流,内核将不会产生正确的结果。您的伪代码与此示例之间的唯一区别是cudaStreamSynchronize
在执行流上的位置。这里我在内核启动后放置它,以确保内核在传输之前完成,以将结果收集回主机。这可能是关键的区别,但同样,没有真正的代码没有真正的代码分析......
我所能建议的是你玩这个例子来了解它是如何工作的。我知道有可能在不使用Nsight for Windows的最新版本中人为地序列化执行流的情况下分析异步代码。如果您无法通过此示例或您自己的代码解决问题,那么这可能会帮助您诊断问题。