使用多个GPU OpenCL

使用多个GPU OpenCL,opencl,gpgpu,Opencl,Gpgpu,我有一个循环,在这个循环中,我在一个GPU上启动多个内核。以下是片段: for (int idx = start; idx <= end ;idx ++) { ret = clEnqueueNDRangeKernel(command_queue, memset_kernel, 1, NULL, &global_item_size_memset, &local_i

我有一个循环,在这个循环中,我在一个GPU上启动多个内核。以下是片段:

for (int idx = start; idx <= end ;idx ++) {

            ret = clEnqueueNDRangeKernel(command_queue, memset_kernel, 1, NULL,
                                            &global_item_size_memset, &local_item_size, 0, NULL, NULL);
            ASSERT_CL(ret, "Error after launching 1st memset_kernel !");


            ret = clEnqueueNDRangeKernel(command_queue, cholesky_kernel, 1, NULL,
                                                    &global_item_size_cholesky, &local_item_size, 0, NULL, NULL);
            ASSERT_CL(ret, "Error after launching 1st cholesky_kernel !");


            ret = clEnqueueNDRangeKernel(command_queue, ckf_kernel1, 1, NULL,
                                            &global_item_size_kernel1, &local_item_size, 0, NULL,  NULL);
            ASSERT_CL(ret, "Error after launching ckf_kernel1[i] !");



            clFinish(command_queue);
            ret = clEnqueueNDRangeKernel(command_queue, memset_kernel, 1, NULL,
                                            &global_item_size_memset, &local_item_size, 0, NULL, NULL);
            ASSERT_CL(ret, "Error after launching 2nd memset_kernel !");


            ret = clEnqueueNDRangeKernel(command_queue, cholesky_kernel, 1, NULL,
                                                    &global_item_size_cholesky, &local_item_size, 0, NULL, NULL);
            ASSERT_CL(ret, "Error after launching 2nd cholesky_kernel !");


            ret = clSetKernelArg(ckf_kernel2, 4, sizeof(idx), (void *)&idx);

            ret = clEnqueueNDRangeKernel(command_queue, ckf_kernel2, 1, NULL,
                                            &global_item_size_kernel2, &local_item_size, 0, NULL, NULL);
            ASSERT_CL(ret, "Error after launching ckf_kernel2 !");

现在,我不知道如何在循环中的不同设备上启动内核。如何让它们并行执行?请注意,上面的循环中有一个clFinish命令


另一个问题:在主机上使用多个线程/进程,其中每个线程/进程负责在单个GPU上启动内核,这是标准做法吗

您不需要为所有设备创建单独的上下文。只有当他们来自不同的平台时,你才需要这样做。 您也不需要创建单独的内核。您可以同时为多个设备编译内核,clBuildProgram支持多设备编译,如果您在设备上启动内核,运行时将知道内核实体保存的设备二进制文件是否对给定设备有效。 最简单的事情是:创建一个上下文,获取您需要的所有设备,然后将其放入一个数组中,并使用该数组构建内核,然后为其中的每个设备创建一个命令队列。 ClenqueEndRange内核是非阻塞的。for循环没有快速通过的唯一原因是因为clFinish状态,很可能是因为您使用的是顺序队列,这意味着如果没有clFinish,单个设备案例也可以正常工作。 OpenCL中最佳使用多GPU的总体思路是按照我提到的方式创建上下文内核队列,并使队列无序。这样,命令就可以并行执行,如果它们没有未满足的依赖项,例如command2的输入不是command1的输出,那么就可以自由地开始与command1并行执行。但是,如果使用此方法,则必须使用clEnqueueNDRangeKernels的最后几个参数,因为必须使用cl_事件构建此依赖链。每个clEnqueueWhatever都可以等待一系列事件,这些事件源自其他命令。队列中的命令只有在满足所有依赖项后才会开始执行

有一个问题你还没有提到,那就是缓冲区的概念。如果你想让多GPU运行,你需要显式地分别为你的设备创建缓冲区,并对你的数据进行分区。在两个设备上设置相同的缓冲区作为参数是无效的,而这两个设备都试图写入缓冲区。在最好的情况下,运行时将序列化您的工作,并且这两个设备不会并行工作。这是因为缓冲区是内存的句柄,运行时负责将缓冲区的内容移动到需要它的设备。这可能隐式地发生惰性内存移动,或者显式地发生在调用clenqueuemiddebuffer时。运行时禁止同时向2个设备提供具有CL_MEM_READ_WRITE或CL_MEM_WRITE_ONLY标志的相同缓冲区。即使作为程序员,您知道这两个设备可能没有写入缓冲区的相同部分,但运行时没有。你必须说出来。优雅的方法是创建2个子缓冲区,它们是较大/原始缓冲区的一部分;不那么优雅的方法是简单地创建2个缓冲区。第一种方法更好,因为它更容易将数据从多个设备收集回主机,因为您只需要获取大的缓冲区,运行时将知道哪些设备上的哪些子缓冲区已被修改,并且它将负责收集数据

如果我看到了您的clSetKernelArgument调用,以及您正在使用的缓冲区,我可以看到内核的依赖关系,并写出您需要做的事情,但我认为这对于您运行多设备来说是一个相当好的开始。归根结底,这一切都与数据有关。并开始使用无序队列,因为它有可能更快,而且它迫使您开始使用事件,这使您和任何阅读代码的人都能清楚地看到,允许并行运行哪些内核

您不需要为所有设备创建单独的上下文。只有当他们来自不同的平台时,你才需要这样做。 您也不需要创建单独的内核。您可以同时为多个设备编译内核,clBuildProgram支持多设备编译,如果您在设备上启动内核,运行时将知道内核实体保存的设备二进制文件是否对给定设备有效。 最简单的事情是:创建一个上下文,获取您需要的所有设备,然后将其放入一个数组中,并使用该数组构建内核,然后为其中的每个设备创建一个命令队列。 ClenqueEndRange内核是非阻塞的。for循环没有快速通过的唯一原因是因为clFinish状态,很可能是因为您使用的是顺序队列,这意味着如果没有clFinish,单个设备案例也可以正常工作。 在OpenCL中最佳使用多GPU的总体思路是按照我提到的方式创建上下文内核队列,并生成队列- 秩序井然。这样,命令就可以并行执行,如果它们没有未满足的依赖项,例如command2的输入不是command1的输出,那么就可以自由地开始与command1并行执行。但是,如果使用此方法,则必须使用clEnqueueNDRangeKernels的最后几个参数,因为必须使用cl_事件构建此依赖链。每个clEnqueueWhatever都可以等待一系列事件,这些事件源自其他命令。队列中的命令只有在满足所有依赖项后才会开始执行

有一个问题你还没有提到,那就是缓冲区的概念。如果你想让多GPU运行,你需要显式地分别为你的设备创建缓冲区,并对你的数据进行分区。在两个设备上设置相同的缓冲区作为参数是无效的,而这两个设备都试图写入缓冲区。在最好的情况下,运行时将序列化您的工作,并且这两个设备不会并行工作。这是因为缓冲区是内存的句柄,运行时负责将缓冲区的内容移动到需要它的设备。这可能隐式地发生惰性内存移动,或者显式地发生在调用clenqueuemiddebuffer时。运行时禁止同时向2个设备提供具有CL_MEM_READ_WRITE或CL_MEM_WRITE_ONLY标志的相同缓冲区。即使作为程序员,您知道这两个设备可能没有写入缓冲区的相同部分,但运行时没有。你必须说出来。优雅的方法是创建2个子缓冲区,它们是较大/原始缓冲区的一部分;不那么优雅的方法是简单地创建2个缓冲区。第一种方法更好,因为它更容易将数据从多个设备收集回主机,因为您只需要获取大的缓冲区,运行时将知道哪些设备上的哪些子缓冲区已被修改,并且它将负责收集数据


如果我看到了您的clSetKernelArgument调用,以及您正在使用的缓冲区,我可以看到内核的依赖关系,并写出您需要做的事情,但我认为这对于您运行多设备来说是一个相当好的开始。归根结底,这一切都与数据有关。并开始使用无序队列,因为它有可能更快,它迫使您开始使用事件,这使您和任何阅读代码的人都能清楚地看到,哪些内核可以并行运行。

您是将通信与计算重叠,还是同时执行相同的共享任务?请您详细说明一下。我不确定你的问题。在主机上使用多个线程/进程,每个线程/进程负责在单个GPU上启动内核,这是标准做法吗不典型。现在,我不知道如何在循环中的不同设备上启动内核。如何让它们并行执行?请注意,上面的循环中有一个clFinish命令。-内核启动是异步的。如果你为每个设备做一个,那么它们应该并行计算。那么你是说ClenqueueEndRangeKernel是非阻塞的吗?你是说通信与计算重叠还是它们同时做相同的事情?请你详细说明一下。我不确定你的问题。在主机上使用多个线程/进程,每个线程/进程负责在单个GPU上启动内核,这是标准做法吗不典型。现在,我不知道如何在循环中的不同设备上启动内核。如何让它们并行执行?请注意,上面的循环中有一个clFinish命令。-内核启动是异步的。如果你为每个设备做一个,那么它们应该并行计算。那么你是说ClenqueueEndRangeKernel是非阻塞的吗?我不同意第2点。为了简化setArgs调用,这可能不是必需的,但通常是一种很好的做法。我同意其余的观点。@Meteohead:我在这里展示了我的内核参数:如果你能帮我举个例子,那就太棒了。这就是我的想法:它不会编译,但你明白了。从你的setKernelArgs判断,我觉得你的内核是完全相互依赖的,所以没有人能在它之前接管内核。不过,以后学习使用事件会很有用。而且,我觉得你过于随意地使用CL_MEM_READ_WRITE。如果可能的话,将缓冲区限制为只读/只写。temp_var必须是一个缓冲区,但还没有看到它被声明。我不同意第2点。为了简化setArgs调用,这可能不是必需的,但通常是一种很好的做法。我同意其余的观点。@Meteohead:我在这里展示了我的内核参数:如果你能帮我举个例子,那就太棒了。这就是我的想法:它不会编译,但你明白了。从你的setKernelArgs判断,我觉得你的内核是完全相互依赖的,所以没有任何 n在它之前接管内核。不过,以后学习使用事件会很有用。而且,我觉得你过于随意地使用CL_MEM_READ_WRITE。如果可能的话,将缓冲区限制为只读/只写。temp_var必须是缓冲区,但尚未看到它被声明。
cl_kernel ckf_kernel1[2];
cl_kernel ckf_kernel2[2];
cl_kernel cholesky_kernel[2];
cl_kernel memset_kernel[2];

// read get kernel.
ckf_kernel1[0] = clCreateKernel(program, "ckf_kernel1", &ret);
ASSERT_CL(ret, "Cannot load ckf_kernel1[i]!");
ckf_kernel2[0] = clCreateKernel(program, "ckf_kernel2", &ret);
ASSERT_CL(ret, "Cannot load ckf_kernel2!");
memset_kernel[0] = clCreateKernel(program, "memset_zero", &ret);
ASSERT_CL(ret, "Cannot load memset_kernel!");
cholesky_kernel[0] = clCreateKernel(program, "cholesky_kernel", &ret);
ASSERT_CL(ret, "Cannot load cholesky_kernel!");

ckf_kernel1[1] = clCreateKernel(program, "ckf_kernel1", &ret);
ASSERT_CL(ret, "Cannot load ckf_kernel1[i]!");
ckf_kernel2[1] = clCreateKernel(program, "ckf_kernel2", &ret);
ASSERT_CL(ret, "Cannot load ckf_kernel2!");
memset_kernel[1] = clCreateKernel(program, "memset_zero", &ret);
ASSERT_CL(ret, "Cannot load memset_kernel!");
cholesky_kernel[1] = clCreateKernel(program, "cholesky_kernel", &ret);
ASSERT_CL(ret, "Cannot load cholesky_kernel!");