如今,在GPU上运行计算工作负载主要有两种方式。一个是CUDA,它有一个非常棒的生态系统,包括高度调优的库,但(实际上)与NVIDIA硬件捆绑在一起。另一种是主要用于游戏的图形API,它们在各种各样的硬件上运行,但在历史上提供的能力比CUDA小得多。此外,那个空间的计算工具也很糟糕。从历史上看,使用OpenCL也做了很多计算,但是它的未来是阴云密布的,因为它已经被正式.。
Vulkan在其原始功能方面一直在快速追赶,最近的扩展支持更高级的GPU计算功能,如子组、指针和内存模型。它是否已经到了可以运行严重计算工作负载的地步?
在这篇博客文章中,我们对在最近的Vulkan上实现前缀SUM进行了一些初步的探索。我有一个粗略的实现初稿,它表明Vulkan对于一个足够坚持不懈的实现者来说可能是一个可行的平台。
正如Hacker News用户Flffything在我的GPU计算演讲品味HN线程中指出的那样,前缀总和是评估GPU计算语言和运行时的一个很好的基准。
首先,它本身是有用的。我在font-rs中使用它来集成精确面积计算的片段,以得出字体呈现的总面积覆盖率。它还被用作更多操作中的原语,包括GPU端动态分配和压缩。
第二,这很简单。顺序版本可以仅用几行代码来表示:
def prefix_sum(A):a:s+=x结果中x的def prefix_sum(A):s=0 result=[]。追加返回结果。
对于三个人来说,在GPU上高效实现是具有挑战性的,但也是可能的。上面的代码具有严格的顺序依赖关系,但是因为加法是关联的,所以可以利用大量的并行性,这方面的文献可以追溯到几十年前。即便如此,在GPU上高效地利用这种并行性需要调用之间的通信(更常见的GPU术语中的“线程”),并且需要仔细注意内存层次结构。
前缀SUM的泛化称为“扫描”,适用于任何关联操作,而不仅仅是加法。它甚至不必是可交换的;示例包括正则表达式和IIR过滤。更准确地说,可以使用任何么半群、具有标识元素的结构以及关联操作进行扫描;标识元素是扫描的“独占”变体所必需的,因为它是输出的第一个元素。
最先进的技术是解耦的回顾。我不打算在这里总结算法,但建议您阅读这篇论文。结果令人印象深刻-对于大型数据集,他们报告达到了memcpy的速度,这意味着不可能再有进一步的加速。
这项工作是对NVIDIA的GPU Gems3中的CUDA并行前缀求和(SCAN)的改进。产品级的开源实现是CUB。另一个设计为更易于访问但并未优化的实现是现代GPU扫描。
我自己的实现在很大程度上是一种研究质量的概念证明。它作为Piet-gpupository的前缀分支存在。基本上,我想确定是否有可能使用Vulkan计算内核来实现与memcpy性能近在咫尺的性能。它是解耦回顾论文的一个相当简单的实现,并没有实现所有的技巧。例如,回顾是完全按顺序进行的;我没有按照建议将回顾并行化。
这个实现已经够粗糙的了,我还不想做仔细的性能评估,但初步结果是令人鼓舞的:在GTX 1080上计算64-32位无符号整数的前缀和需要2.05ms的GPU时间,速度为312亿个元素/秒。由于每个元素涉及读取和写入4个字节,这对应于大约262GiB/s的原始内存带宽。理论内存带宽列出为320 GB/s,因此很明显代码能够.。
“现代C++”的成就之一是C++11内存模型。在过去,C和C++中的无锁编程模式的机制是Volatil限定符和各种非标准的障碍内部函数。人们从操作上对此进行了推论--Volatile的主要功能是禁用某些优化,而屏障内部函数会编译成内存围栏指令,这通常会导致硬件刷新缓存。
今天,大多数无锁爱好者认为那个时代是野蛮的。为了多线程的目的,从来没有清楚地定义易失性的语义(尽管人们还是使用它,因为它看起来很有用),并且屏障指令具有特定于硬件的令人不安的属性。因为x86有“总存储顺序”,所以发布安全通常不需要屏障指令。然而,同样的代码在,