减少在时间步进循环中启动内核的时间 - OpenACC

Reducing time to launch kernels in time stepping loop - OpenACC

我正在尝试在我拥有的一些 Fortran 代码上实施 OpenACC。该代码由一个外部时间步进循环(无法并行化)组成,循环内有许多嵌套循环。这些嵌套循环可以并行化,但需要按顺序 运行(即 A 后跟 B 后跟 C)。

我想将整个过程卸载到 GPU,因为 GPU 和 CPU 之间的多个时间步长的数据传输变得令人望而却步。下面的伪代码说明了我当前的方法:

!$acc data copy(ALL THE DATA THAT I NEED)
DO iter = 1, NT
    value = array_of_values(iter)
    !$ acc kernels
!PART A
    !$acc loop independent, private(j)
        DO J=0,ymax
    !$acc loop independent, private(i)
        DO I=0,xmaxput
    !$acc loop independent, private(l)
        DO L=0,zmax
            if(value == 0) then 
                (DO SOME COMPUTATIONS...)
            elseif(value < 0) then 
                (DO SOME OTHER COMPUTATIONS...)
            elseif(value > 0) then 
                (DO SOME OTHER COMPUTATIONS...)
            endif
        ENDDO
        ENDDO
        ENDDO

        !NOW GO DO OTHER STUFF
!PART B
    !$acc loop independent, private(j)
        DO J=0,ymax
    !$acc loop independent, private(i)
        DO I=0,xmax
    !$acc loop independent, private(l)
        DO L=0,zmax
            (DO SOME EVEN MORE COMPUTATIONS...)
        ENDDO
        ENDDO
        ENDDO

!PART C
!etc...

    !$acc end kernels 
ENDDO
!$acc end data

我有使用这种方法的工作代码,但是,当我使用 NVIDIA 的 Visual Profiler (click for image) 在 GeForce MX150 GPU 上对其进行分析时,我发现时间步长循环中的每次迭代都会导致较大的时间间隔没有计算的地方。驱动程序 API 说它正在执行 "cuLaunchKernel"。如果我简单地复制整个循环,以便每个时间步进行两次迭代 运行 而不是一次迭代,那么这个间隙在时间步循环中不存在,只有在循环开始时才存在。

我有几个(相互关联的)问题:
1. 有没有办法在其他内核 运行ning 时启动这些内核?
2. 我读到 here and here WDDM 驱动程序批处理内核启动,这似乎发生在这里。这是否意味着如果我在 Linux 上 运行 我不应该期待这种行为?

cuStreamSynchronize 似乎也在阻止 GPU 运行ning,导致额外的空时间。这似乎与如何在时间步进循环结束之前启动其他内核的问题有关。

这是我第一次使用OpenACC。我已经到处寻找答案,但可能使用了错误的关键字,因为我找不到任何东西。

编辑 - 解决方案

根据 Mat 的建议,我添加了 Async 解决了这个问题。有趣的是,内核启动仍然是同时完成的,但现在每个将在迭代时间步长循环时启动的内核都会在程序开始时立即启动。下面是更新的伪代码以及一些其他调整,如果它对其他人有帮助的话:

!$acc data copy(ALL THE DATA THAT I NEED)
!$acc wait
DO iter = 1, NT
    value = array_of_values(iter)
    !$acc update device(value, iter), async(1)    !allows loop to run on cpu and sends value to GPU

!PART A
    !$acc kernels, async(1)
    !$acc loop independent, private(j)
        DO J=0,ymax
    !$acc loop independent, private(i)
        DO I=0,xmaxput
    !$acc loop independent, private(l)
        DO L=0,zmax
            if(value == 0) then 
                (DO SOME COMPUTATIONS...)
            elseif(value < 0) then 
                (DO SOME OTHER COMPUTATIONS...)
            elseif(value > 0) then 
                (DO SOME OTHER COMPUTATIONS...)
            endif
        ENDDO
        ENDDO
        ENDDO
    !$acc end kernels   

!NOW GO DO OTHER STUFF
!PART B
    !$acc kernels, async(1) 
    !$acc loop independent, private(j)
        DO J=0,ymax
    !$acc loop independent, private(i)
        DO I=0,xmax
    !$acc loop independent, private(l)
        DO L=0,zmax
            (DO SOME EVEN MORE COMPUTATIONS...)
        ENDDO
        ENDDO
        ENDDO
    !$acc end kernels   

!PART C
    !$acc kernels, async(1)
        !for loops, etc...
    !$acc end kernels 
ENDDO
!$acc wait
!$acc end data

您可以估计 WDDM 使您的 GPU 运行 不带显示器有什么不同。如果没有显示器连接到它,它会消除 运行 WDDM 的一些问题。

尝试将显示器连接到主板,如果它支持集成显卡和 GPU 的同步驱动程序(检查您的 BIOS)。

否则,您可以将另一个 GPU 添加到您的 PC 中(也许您是闲置的旧 GPU)并将其用于您的显示器。

当你说"large time gap"的时候,你能具体点吗?你用的是秒、微秒还是毫秒?虽然变化很大,但我预计内核启动开销约为 40 微秒。通常启动开销会在噪音中消失,但如果内核特别快或者如果内核启动数百万次,那么它会影响相对性能。使用 "async" 子句有助于隐藏启动开销(见下文)。

虽然如果差距大得多,那么可能会有其他事情发生。例如,如果循环中存在缩减,则缩减变量可能会被复制回主机。请注意,如果您使用的是 PGI,请查看编译器反馈消息 (-Minfo=accel)。这可能会提供一些线索。

  1. Is there a way to get these kernels to be launched while other kernels are running?

是的。使用三个独立的 "kernels" 区域,每个区域一个。然后将 "async(1)" 子句添加到每个计算区域。异步将使主机在启动内核后继续运行,并且由于它们使用相同的队列,在这种情况下为 1 但您可以使用任何正整数,它将创建一个依赖关系,因此 B 不会 运行 直到 A 完成,并且 C将在 B 之后开始。您需要在希望主机与设备同步的位置添加“!$acc wait”。

请注意,异步队列映射到 CUDA 流。

cuStreamSynchronize appears to also be blocking the GPU from running, leading to additional null time. This seems related with the question of how to get other kernels to launch prior to the end of the time stepping loop.

这是主机阻塞等待 GPU 计算完成的时间。它应该与您的内核 运行 时间大致相同(如果不使用异步)。