Cuda 在单个流中使用对等GPU
在我目前的项目中,我使用GPU进行信号处理和可视化。我已经在使用流来允许异步操作。信号在帧中处理,对于每个帧,流中的处理步骤如下所示Cuda 在单个流中使用对等GPU,cuda,multi-gpu,Cuda,Multi Gpu,在我目前的项目中,我使用GPU进行信号处理和可视化。我已经在使用流来允许异步操作。信号在帧中处理,对于每个帧,流中的处理步骤如下所示 memcpy到设备 信号调节 图像处理 形象化 现在这些步骤都是在单个GPU上进行的,但是我的机器有一个多GPU卡(GeForce GTX 690),我想在两个设备之间分配操作。基本上,我希望在设备A上执行步骤1和2,在设备B上执行步骤3和4,而操作1、2、3和4仍然作为单个异步流执行。理想的结果是流式布局,如下所示 Device A Stream a 1 2
Device A Stream a 1 2 1 2 ...
Stream b 1 2 ...
Device B Stream a 3 4 3 4 ...
Stream b 3 4 ...
如何执行此操作?我以前的尝试不正确,因为流与创建它的设备相关联。因此,我认为对你在标题中提出的问题最直接的回答是“这是不可能做到的”。您不能创建单个流并从中向多个GPU发出命令。发件人: 然而,在研究过程中,我注意到事件是在两个不同设备上同步两个流的建议方法:
cudaStreamWaitEvent()
将成功,即使输入流和
事件关联到不同的设备。cudaStreamWaitEvent()可以
因此,可用于使多个设备相互同步
因此,我创建了以下代码来说明这一点:
#include <stdio.h>
#define SIZE 32
#define K1VAL 5
#define K3VAL 3
#define cudaCheckErrors(msg) \
do { \
cudaError_t __err = cudaGetLastError(); \
if (__err != cudaSuccess) { \
fprintf(stderr, "Fatal error: %s (%s at %s:%d)\n", \
msg, cudaGetErrorString(__err), \
__FILE__, __LINE__); \
fprintf(stderr, "*** FAILED - ABORTING\n"); \
exit(1); \
} \
} while (0)
__global__ void kernel1(int *frame, int size){
int idx = threadIdx.x + (blockDim.x * blockIdx.x);
if (idx == 0){
int *a = new int[10000]; // just to make this kernel take a while
for (int i = 0; i<10000; i++)
a[i] = 0;
for (int i = 0; i < size; i++)
frame[i] += K1VAL;
}
}
__global__ void kernel3(int *frame, int size){
int idx = threadIdx.x + (blockDim.x * blockIdx.x);
if (idx == 0)
for (int i = 0; i < size; i++)
frame[i] -= K3VAL;
}
void set_device(int dev){
int ldev;
cudaSetDevice(dev);
cudaGetDevice(&ldev);
cudaCheckErrors("set device error");
if (ldev != dev){
printf("set device mismatch error\n");
exit(1);
}
}
int main(){
int A=0;
int B=1;
int framesize = SIZE*sizeof(int);
int *h_frame;
int *d_frame_aA, *d_frame_bB;
int numdev = 0;
cudaGetDeviceCount(&numdev);
cudaCheckErrors("can't determine number of devices");
if (numdev < 2){
printf("not enough devices!\n");
return 1;
}
set_device(A);
cudaMalloc((void **) &d_frame_aA, framesize); // stream_a
cudaMemset(d_frame_aA, 0, framesize);
set_device(B);
cudaMalloc((void **) &d_frame_bB, framesize); // stream_b
cudaMemset(d_frame_bB, 0, framesize);
cudaHostAlloc((void **) &h_frame, framesize, cudaHostAllocDefault);
cudaCheckErrors("allocations failure");
set_device(A);
cudaStream_t stream_a, stream_b;
cudaStreamCreate(&stream_a);
cudaEvent_t absync;
cudaEventCreate(&absync);
set_device(B);
cudaStreamCreate(&stream_b);
cudaCheckErrors("stream creation failure");
for (int i = 0; i < SIZE; i++)
h_frame[i] = 0;
set_device(A);
cudaDeviceEnablePeerAccess(B, 0);
set_device(B);
cudaDeviceEnablePeerAccess(A, 0);
cudaCheckErrors("enable peer access fail");
set_device(A);
cudaMemcpyAsync(d_frame_aA, h_frame, framesize, cudaMemcpyHostToDevice, stream_a);
kernel1<<<1,1,0, stream_a>>>(d_frame_aA, SIZE);
cudaCheckErrors("kernel1 fail");
cudaMemcpyPeerAsync(d_frame_bB, B, d_frame_aA, A, framesize, stream_a );
cudaCheckErrors("memcpypeer fail");
cudaEventRecord(absync, stream_a);
set_device(B);
// comment out the next line to see the failure
cudaStreamWaitEvent(stream_b, absync, 0);
kernel3<<<1,1,0, stream_b>>>(d_frame_bB, SIZE);
cudaCheckErrors("main sequence fail");
// cudaCheckErrors("main sequence failure");
cudaMemcpy(h_frame, d_frame_bB, framesize, cudaMemcpyDeviceToHost);
cudaCheckErrors("results_a memcpy fail");
for (int i = 0; i < SIZE; i++)
if (h_frame[i] != (K1VAL - K3VAL)) {
printf("results error\n");
return 1;
}
printf("success\n");
return 0;
}
#包括
#定义大小32
#定义k1值5
#定义k3val3
#定义cudaCheckErrors(msg)\
做{\
cudaError\u t\u err=cudaGetLastError()\
如果(_err!=cudaSuccess){\
fprintf(标准,“致命错误:%s(%s位于%s:%d)\n”\
msg,cudaGetErrorString(_err)\
__文件(行)\
fprintf(stderr,“***失败-中止\n”)\
出口(1)\
} \
}而(0)
__全局无效内核1(整数*帧,整数大小){
int idx=threadIdx.x+(blockDim.x*blockIdx.x);
如果(idx==0){
int*a=newint[10000];//只是为了让这个内核花费一些时间
for(int i=0;icudaStreamWaitEvent()
启用GPU间同步,因为您可以在属于另一个设备的CUDA事件上插入等待
因此,生产者和消费者之间的GPU间同步需要为2个GPU中的每一个分配几个事件(至少2个),然后让生产者cudaEventRecord()
和消费者cudaStreamWaitEvent()
在同一事件上。cudaStreamWaitEvent()
将命令插入当前设备的命令缓冲区,使其暂停执行,直到记录给定事件为止
下面查看一个代码片段,其中使用cudaStreamWaitEvent()
以这种方式实现对等memcpy。泵启动后,生产者和消费者都应同时进行PCIe传输,每个传输到两个暂存缓冲区(分配在便携式固定内存中)中的一个
这实际上与我已经尝试过的非常接近,唯一的区别是我没有使用cudaMemcpyPeerAsync
在GPU之间传输数据。我的印象是,通过使用cudaEnablePeerAccess
设备可以直接存储其对等设备,而无需通过副本。不管怎样结果是,根据nvvp,所有操作都是在设备A上执行的;在探查器中运行时,可视化部分被禁用,我只收集有关结果的统计信息。您正确地设置了内核和函数,但可能会执行第五次内核执行,这是一个cudaMemcpy设备,用于宿主以检索步骤/k的结果ernel 3,这样就可以存储起来进行离线分析和可视化。对等访问不是仅在特斯拉系列上可用吗?正如您链接的文本“特斯拉系列2.0及更高计算能力的设备可以相互寻址内存”中所述@ks6g10在回答了这个问题后,我让一位同事轶事般地告诉我,高速对等访问似乎在GTX690上起作用。即使高速对等访问不起作用,cudamemcypeerasync()
调用仍能正常工作,只会以稍慢的速度进行,因为它是通过系统内存进行的。您可能希望阅读我在该答案中链接的相关部分。@ks6g10:根据文档,开普勒也可以使用它。考虑到我是PCI-E带宽,我不介意额外的复制操作无论如何绑定。我下面的答案是错误的,不起作用。您不能让一个流向两个不同的设备发出内核或memcopies。我将很快删除下面的错误答案。@RobertCrovella:谢谢提醒。同一文档还声明,cudaStreamWaitEvent
可用于设备间同步因此,通过使用2*n个流,应该可以异步同步每个设备的流。现在我正在重构有问题的代码,但我会在再次运行时尝试。
#include <stdio.h>
#define SIZE 32
#define K1VAL 5
#define K3VAL 3
#define cudaCheckErrors(msg) \
do { \
cudaError_t __err = cudaGetLastError(); \
if (__err != cudaSuccess) { \
fprintf(stderr, "Fatal error: %s (%s at %s:%d)\n", \
msg, cudaGetErrorString(__err), \
__FILE__, __LINE__); \
fprintf(stderr, "*** FAILED - ABORTING\n"); \
exit(1); \
} \
} while (0)
__global__ void kernel1(int *frame, int size){
int idx = threadIdx.x + (blockDim.x * blockIdx.x);
if (idx == 0){
int *a = new int[10000]; // just to make this kernel take a while
for (int i = 0; i<10000; i++)
a[i] = 0;
for (int i = 0; i < size; i++)
frame[i] += K1VAL;
}
}
__global__ void kernel3(int *frame, int size){
int idx = threadIdx.x + (blockDim.x * blockIdx.x);
if (idx == 0)
for (int i = 0; i < size; i++)
frame[i] -= K3VAL;
}
void set_device(int dev){
int ldev;
cudaSetDevice(dev);
cudaGetDevice(&ldev);
cudaCheckErrors("set device error");
if (ldev != dev){
printf("set device mismatch error\n");
exit(1);
}
}
int main(){
int A=0;
int B=1;
int framesize = SIZE*sizeof(int);
int *h_frame;
int *d_frame_aA, *d_frame_bB;
int numdev = 0;
cudaGetDeviceCount(&numdev);
cudaCheckErrors("can't determine number of devices");
if (numdev < 2){
printf("not enough devices!\n");
return 1;
}
set_device(A);
cudaMalloc((void **) &d_frame_aA, framesize); // stream_a
cudaMemset(d_frame_aA, 0, framesize);
set_device(B);
cudaMalloc((void **) &d_frame_bB, framesize); // stream_b
cudaMemset(d_frame_bB, 0, framesize);
cudaHostAlloc((void **) &h_frame, framesize, cudaHostAllocDefault);
cudaCheckErrors("allocations failure");
set_device(A);
cudaStream_t stream_a, stream_b;
cudaStreamCreate(&stream_a);
cudaEvent_t absync;
cudaEventCreate(&absync);
set_device(B);
cudaStreamCreate(&stream_b);
cudaCheckErrors("stream creation failure");
for (int i = 0; i < SIZE; i++)
h_frame[i] = 0;
set_device(A);
cudaDeviceEnablePeerAccess(B, 0);
set_device(B);
cudaDeviceEnablePeerAccess(A, 0);
cudaCheckErrors("enable peer access fail");
set_device(A);
cudaMemcpyAsync(d_frame_aA, h_frame, framesize, cudaMemcpyHostToDevice, stream_a);
kernel1<<<1,1,0, stream_a>>>(d_frame_aA, SIZE);
cudaCheckErrors("kernel1 fail");
cudaMemcpyPeerAsync(d_frame_bB, B, d_frame_aA, A, framesize, stream_a );
cudaCheckErrors("memcpypeer fail");
cudaEventRecord(absync, stream_a);
set_device(B);
// comment out the next line to see the failure
cudaStreamWaitEvent(stream_b, absync, 0);
kernel3<<<1,1,0, stream_b>>>(d_frame_bB, SIZE);
cudaCheckErrors("main sequence fail");
// cudaCheckErrors("main sequence failure");
cudaMemcpy(h_frame, d_frame_bB, framesize, cudaMemcpyDeviceToHost);
cudaCheckErrors("results_a memcpy fail");
for (int i = 0; i < SIZE; i++)
if (h_frame[i] != (K1VAL - K3VAL)) {
printf("results error\n");
return 1;
}
printf("success\n");
return 0;
}
cudaError_t
chMemcpyPeerToPeer(
void *_dst, int dstDevice,
const void *_src, int srcDevice,
size_t N )
{
cudaError_t status;
char *dst = (char *) _dst;
const char *src = (const char *) _src;
int stagingIndex = 0;
while ( N ) {
size_t thisCopySize = min( N, STAGING_BUFFER_SIZE );
CUDART_CHECK( cudaSetDevice( srcDevice ) );
CUDART_CHECK( cudaStreamWaitEvent( NULL, g_events[dstDevice][stagingIndex], 0 ) );
CUDART_CHECK( cudaMemcpyAsync( g_hostBuffers[stagingIndex], src, thisCopySize,
cudaMemcpyDeviceToHost, NULL ) );
CUDART_CHECK( cudaEventRecord( g_events[srcDevice][stagingIndex] ) );
CUDART_CHECK( cudaSetDevice( dstDevice ) );
CUDART_CHECK( cudaStreamWaitEvent( NULL, g_events[srcDevice][stagingIndex], 0 ) );
CUDART_CHECK( cudaMemcpyAsync( dst, g_hostBuffers[stagingIndex], thisCopySize,
cudaMemcpyHostToDevice, NULL ) );
CUDART_CHECK( cudaEventRecord( g_events[dstDevice][stagingIndex] ) );
dst += thisCopySize;
src += thisCopySize;
N -= thisCopySize;
stagingIndex = 1 - stagingIndex;
}
// Wait until both devices are done
CUDART_CHECK( cudaSetDevice( srcDevice ) );
CUDART_CHECK( cudaDeviceSynchronize() );
CUDART_CHECK( cudaSetDevice( dstDevice ) );
CUDART_CHECK( cudaDeviceSynchronize() );
Error:
return status;
}