OpenCL 2.0设备命令队列不断被填满并停止执行。

问题描述 投票:0回答:1

我正在利用OpenCL的enqueue_kernel()函数从GPU动态地enqueue内核,以减少不必要的主机交互。下面是一个简化的例子,说明我在内核方面的尝试。

kernel void kernelA(args)
{
    //This kernel is the one that is enqueued from the host, with only one work item. This kernel
    //could be considered the "master" kernel that controls the logic of when to enqueue tasks
    //First, it checks if a condition is met, then it enqueues kernelB

    if (some condition)
    {
        enqueue_kernel(get_default_queue(), CLK_ENQUEUE_FLAGS_WAIT_KERNEL, ndrange_1D(some amount, 256), ^{kernelB(args);});
    }
    else
    {
        //do other things
    }
}

kernel void kernelB(args)
{
    //Do some stuff

    //Only enqueue the next kernel with the first work item. I do this because the things
    //occurring in kernelC rely on the things that kernelB does, so it must take place after kernelB is completed,
    //hence, the CLK_ENQUEUE_FLAGS_WAIT_KERNEL
    if (get_global_id(0) == 0)
    {
        enqueue_kernel(get_default_queue(), CLK_ENQUEUE_FLAGS_WAIT_KERNEL, ndrange_1D(some amount, 256), ^{kernelC(args);});
    }
}

kernel void kernelC(args)
{
    //Do some stuff. This one in particular is one step in a sorting algorithm

    //This kernel will enqueue kernelD if a condition is met, otherwise it will
    //return to kernelA 
    if (get_global_id(0) == 0 && other requirements)
    {
        enqueue_kernel(get_default_queue(), CLK_ENQUEUE_FLAGS_WAIT_KERNEL, ndrange_1D(1, 1), ^{kernelD(args);});
    }
    else if (get_global_id(0) == 0)
    {
        enqueue_kernel(get_default_queue(), CLK_ENQUEUE_FLAGS_WAIT_KERNEL, ndrange_1D(1, 1), ^{kernelA(args);});
    }
}

kernel void kernelD(args)
{
    //Do some stuff

    //Finally, if some condition is met, enqueue kernelC again. What this will do is it will
    //bounce back and forth between kernelC and kernelD until the condition is
    //no longer met. If it isn't met, go back to kernelA
    if (some condition)
    {
        enqueue_kernel(get_default_queue(), CLK_ENQUEUE_FLAGS_WAIT_KERNEL, ndrange_1D(some amount, 256), ^{kernelC(args);});
    }
    else
    {
        enqueue_kernel(get_default_queue(), CLK_ENQUEUE_FLAGS_WAIT_KERNEL, ndrange_1D(1, 1), ^{kernelA(args);});
    }
}

这就是程序的一般流程 它的工作非常完美,完全按照我的意图来做 除了一个问题之外 在某些情况下,当工作负荷非常大时,随机的一个enqueue_kernel()会无法enqueue而停止程序。发生这种情况是因为设备队列已经满了,它无法将另一个任务放入其中。但是,即使我做了大量的研究,我也无法理解为什么会这样。

我以为一旦队列中的一个任务(例如内核)完成,就会释放队列中的那个位置。所以我的队列一次最多应该只能达到1到2个任务的样子。但是这个程序会把整个262,144字节大小的设备命令队列填满,然后停止运行。

如果有人有任何想法,我将非常感谢一些潜在的洞察力,为什么会发生这种情况。我被卡住了,在解决这个问题之前,我无法继续工作。

先谢谢你

顺便说一下,我在Radeon RX 590卡上运行,并使用AMD APP SDK 3.0与OpenCL 2.0一起使用。

opencl gpgpu gpu-programming opencl-c
1个回答
2
投票

我不知道到底是哪里出了问题,但我注意到你发布的代码中的一些东西,这些反馈在评论中读起来太过冗长,所以这里就说说吧--这不是一个明确的答案,但却是一个接近的尝试。

代码并不像评论里说的那样做

kernelD,你有。

//Finally, if some condition is met, enqueue kernelC again.

if (get_global_id(0) == 0)
{
    enqueue_kernel(get_default_queue(), CLK_ENQUEUE_FLAGS_WAIT_KERNEL, ndrange_1D(some amount, 256), ^{kernelD(args);});
}

这实际上是在查询 kernelD 又是自己,而不是 kernelC 正如评论所言。另一个条件分支查询 kernelA.

这可能是你的代码缩减版中的一个排版错误。

潜在的任务爆炸

这可能又是由于你删减代码的方式造成的,但我不太明白为什么

所以我的队列应该真的是一次最多只能达到1到2个任务的样子。

可以是真的。根据我的解读,所有工作项目的 kernelCkernelD 会产生新的任务;而由于每种情况下似乎都有不止1个工作项目,这似乎很容易产生非常多的任务。

例如,在 kernelC:

if (get_global_id(0) == 0 && other requirements)
{
    enqueue_kernel(get_default_queue(), CLK_ENQUEUE_FLAGS_WAIT_KERNEL, ndrange_1D(some amount, 256), ^{kernelD(args);});
}
else
{
    enqueue_kernel(get_default_queue(), CLK_ENQUEUE_FLAGS_WAIT_KERNEL, ndrange_1D(1, 1), ^{kernelA(args);});
}

kernelB 将创建至少256个工作项目,运行 kernelC. 这里,工作项目0将(如果 other requirements met)产生1个任务,至少有256个以上的工作项目,255个以上的任务有1个工作项目运行 kernelA. kernelD 行为类似。

因此,通过几次迭代,你可以很容易地最终获得几千个任务,用于运行 kernelA 排队。我真的不知道你的代码是怎么做的,但似乎是个好主意,检查一下是否减少了这数百个 kernelA 任务改善情况,以及是否可以或许修改 kernelA 这样你只需用一个范围来enqueue它一次,而不是从每个工作项目中enqueue一个1的工作大小。(或者类似于这样的思路--如果这样做更合理的话,也许每个组都可以enqueue一次。基本上,减少 enqueue_kernel 被调用。)

enqueue_kernel() 返回值

你有没有实际检查过 enqueue_kernel? 它能准确地告诉你失败的原因,所以即使我上面的建议不可行,也许你可以设置一些全局状态,让 kernelA 如果中断了更多的任务,在更多任务耗尽后重新开始计算?

© www.soinside.com 2019 - 2024. All rights reserved.