首页 > 解决方案 > 减少在时间步进循环中启动内核的时间 - 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 在 GeForce MX150 GPU 上对其进行分析时(单击查看图像),我发现通过时间步进循环的每次迭代都会导致没有计算的大时间间隙正在完成。Driver API 表示在此期间它正在执行“cuLaunchKernel”。如果我只是复制整个循环,以便每个时间步运行两次迭代而不是一次,那么时间步长循环中不存在这种间隙,只有在循环开始时才存在。

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

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

这是我第一次使用 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

标签: cudafortrangpuopenacc

解决方案


当你说“大时间间隔”时,你能更具体一点吗?你需要几秒,微秒,毫秒吗?虽然变化很大,但我预计内核启动开销约为 40 微秒。启动开销通常会在噪音中丢失,但如果内核特别快或者内核启动数百万次,那么它会影响相对性能。使用“异步”子句有助于隐藏启动开销(见下文)。

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

  1. 有没有办法让这些内核在其他内核运行时启动?

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

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

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

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


推荐阅读