CUDA IPC Memcpy+;MPI在Theano中失败,在pycuda中工作

CUDA IPC Memcpy+;MPI在Theano中失败,在pycuda中工作,mpi,ipc,theano,pycuda,Mpi,Ipc,Theano,Pycuda,为了便于学习,我编写了一个小型的C Python模块,该模块应该执行IPC cuda memcopy来在进程之间传输数据。为了进行测试,我编写了等效的程序:一个使用theano的CudaNdarray,另一个使用pycuda。问题是,即使测试程序几乎相同,pycuda版本仍然可以工作,而theano版本则不能。它不会崩溃:它只会产生错误的结果 下面是C模块中的相关功能。它的作用是:每个进程都有两个缓冲区:一个源缓冲区和一个目标缓冲区。调用_sillycopy(source,dest,n)将n个元

为了便于学习,我编写了一个小型的C Python模块,该模块应该执行IPC cuda memcopy来在进程之间传输数据。为了进行测试,我编写了等效的程序:一个使用theano的CudaNdarray,另一个使用pycuda。问题是,即使测试程序几乎相同,pycuda版本仍然可以工作,而theano版本则不能。它不会崩溃:它只会产生错误的结果

下面是C模块中的相关功能。它的作用是:每个进程都有两个缓冲区:一个源缓冲区和一个目标缓冲区。调用_sillycopy(source,dest,n)将n个元素从每个进程的源缓冲区复制到相邻进程的dest数组。所以,如果我有两个进程,0和1,进程0将以进程1的源缓冲区结束,进程1将以进程0的源缓冲区结束

请注意,为了在进程之间传输cudaIpcMemHandle\t值,我使用了MPI(这是使用MPI的大型项目的一小部分)_sillycopy由另一个函数“sillycopy”调用,该函数在Python中通过标准的Python C API方法公开

void\u sillycopy(浮点*源、浮点*目的、整数n、MPI\u通信){
int localRank;
int localSize;
MPI_通信等级(通信和本地等级);
MPI_Comm_大小(Comm和localSize);
//找出哪个进程在“左边”。
//m()执行mod并处理负数
//恰当地
int neighbor=m(localRank-1,localSize);
//为*源创建内存句柄并执行以下操作:
//浪费的Allgather分配给其他流程
//(可以只使用MPI_Sendrecv,但现在不相关)
cudaIpcMemHandle_t*memHandles=new-cudaIpcMemHandle_t[localSize];
cudaipgetmemhandle(memHandles+localRank,source);
MPI_Allgather(
memHandles+localRank、sizeof(cudaIpcMemHandle)、MPI\u字节、,
memHandles,sizeof(cudaIpcMemHandle_t),MPI_字节,
通信);
//打开邻居的内存句柄,这样我们就可以进行cudaMemcpy了
浮点*源PTR;
cudaIpcOpenMemHandle((void**)和sourcePtr,memHandles[neighbor],cudaIpcMemLazyEnablePeerAccess);
//收到!
cudaMemcpy(dest、sourcePtr、n*sizeof(float)、cudaMemcpyDefault);
CUDAIPCloseMemHandle(源PTR);
删除[]个memHandles;
}
下面是pycuda示例。作为参考,在a_gpu和b_gpu上使用int()返回指向设备上底层缓冲区内存地址的指针

导入sillymodule#sillycopy就住在这里
将simplempi导入为mpi
将pycuda.driver作为drv导入
将numpy作为np导入
进口退欧
导入时间
mpi.init()
drv.init()
#确保每个进程使用不同的GPU
dev=drv.Device(mpi.rank())
ctx=开发人员创建上下文()
atexit.寄存器(ctx.pop)
形状=(2**26,)
#分配主机内存
a=np.ones(形状,np.32)
b=np.0(形状,np.32)
#分配设备内存
a_gpu=drv.mem_alloc(a.n字节)
b_gpu=drv.mem_alloc(b.N字节)
#将主机复制到设备
drv.memcpy_htod(a_gpu,a)
drv.memcpy_htod(b_gpu,b)
#多几个主机缓冲区
a_p=np.zero(形状,np.float32)
b_p=np.0(形状,np.32)
#健全性检查:这应该用1填充一个p
drv.memcpy_dtoh(a_p,a_gpu)
#核实
打印(a_p[0:10])
sillymodule.sillycopy(
int(一个gpu),
int(b_gpu),
形状[0])
#在这之后,b_p应该拥有自己的全部
drv.memcpy_dtoh(b_p,b_gpu)
打印(c_p[0:10])
现在是上述代码的theano版本。与使用int()获取缓冲区地址不同,CudaNdarray访问缓冲区地址的方法是通过gpudata属性

导入操作系统
将simplempi导入为mpi
mpi.init()
#选择每个进程一个gpu
os.environ['THEANO_FLAGS']=“device=gpu{}”.format(mpi.rank())
将theano.sandbox.cuda作为cuda导入
导入时间
将numpy作为np导入
导入时间
导入sillymodule
形状=(2**24,)
#分配主机数据
a=np.ones(形状,np.32)
b=np.0(形状,np.32)
#分配设备数据
agpu=cuda.CudaNdarray.zero(形状)
b_gpu=cuda.CudaNdarray.zero(形状)
#从主机复制到设备
AGPU[:]=a[:]
BGPU[:]=b[:]
#应该打印1作为一个健康检查
打印(np.asarray(a_gpu[0:10]))
sillymodule.sillycopy(
agpu.gpudata,
b_gpu.gpudata,
形状[0])
#应该打印1的
打印(np.asarray(b_gpu[0:10]))
同样,pycuda代码工作正常,theano版本运行正常,但给出了错误的结果。确切地说,在theano代码的末尾,b_gpu充满了垃圾:既不是1也不是0,只是随机数,就好像它从内存中的错误位置复制一样

我最初关于这一失败原因的理论与CUDA环境有关。我想知道theano是否可能对他们做了一些事情,这意味着sillycopy中的cuda调用是在不同的cuda上下文下运行的,而不是用于创建gpu阵列。我认为情况并非如此,因为:

  • 我花了很多时间深入研究theano的代码,没有看到任何有趣的事情在上下文中进行
  • 我希望这样的问题会导致严重的崩溃,而不是错误的结果,而事实并非如此
  • 第二个问题是,即使使用cuda后端,theano也会产生多个线程,这一点可以通过运行“ps-huhp”来验证。我不知道线程可能会影响什么,但是我已经没有明显的事情要考虑了。 如果您对此有任何想法,我们将不胜感激

    供参考:进程以正常OpenMPI方式启动:

    mpirun--np2 python测试_pycuda.py