我们可以使用 `shuffle()` 指令在 WaveFront 中的项目(线程)之间进行 reg-to-reg 数据交换吗?

Can we use `shuffle()` instruction for reg-to-reg data-exchange between items (threads) in WaveFront?

众所周知,WaveFront (AMD OpenCL) 与 WARP (CUDA) 非常相似:http://research.cs.wisc.edu/multifacet/papers/isca14-channels.pdf

GPGPU languages, like OpenCL™ and CUDA, are called SIMT because they map the programmer’s view of a thread to a SIMD lane. Threads executing on the same SIMD unit in lockstep are called a wavefront (warp in CUDA).

众所周知,AMD 建议我们使用本地内存(减少)数字相加。为了加速加法(Reduce)建议使用向量类型:http://amd-dev.wpengine.netdna-cdn.com/wordpress/media/2013/01/AMD_OpenCL_Tutorial_SAAHPC2010.pdf


但是 WaveFront 中的项目(线程)之间是否有任何优化的寄存器到寄存器数据交换指令:

这个洗牌指令可以,例如,从8个threads/lanes,执行3个循环的8个元素的Reduce(将数字相加)而无需任何同步,并且不使用任何 cache/local/shared-memory(每次访问有大约 3 个周期的延迟)。

即线程将其值直接发送到其他线程的寄存器:https://devblogs.nvidia.com/parallelforall/faster-parallel-reductions-kepler/

或者在 OpenCL 中我们只能使用指令 gentypen shuffle( gentypem x, ugentypen mask ),它只能用于矢量类型,例如 float16/uint16 into each item (thread), but not between items (threads) in WaveFront: https://www.khronos.org/registry/OpenCL/sdk/1.1/docs/man/xhtml/shuffle.html

我们可以使用看起来像 shuffle() 的东西在 WaveFront 中的项目(线程)之间进行 reg-to-reg 数据交换,这比通过本地内存进行数据交换更快吗?


AMD OpenCL 中是否有用于 WaveFront 内寄存器到寄存器数据交换的指令,例如指令 __any()__all()__ballot()__shfl() WARP 内(CUDA):http://on-demand.gputechconf.com/gtc/2015/presentation/S5151-Elmar-Westphal.pdf

扭曲投票函数:


结论:

众所周知,在 OpenCL-2.0 中有类似于 WaveFronts 的具有 SIMD 执行模型的子组:

子组有 - 第 160 页:http://amd-dev.wpengine.netdna-cdn.com/wordpress/media/2013/12/AMD_OpenCL_Programming_User_Guide2.pdf

但是在OpenCL中没有类似的函数:

2016 年 8 月 28 日最终草案 OpenCL 扩展 #35 版本 4 中只有英特尔指定的内置随机播放功能:intel_sub_group_shuffleintel_sub_group_shuffle_downintel_sub_group_shuffle_downintel_sub_group_shuffle_up: https://www.khronos.org/registry/OpenCL/extensions/intel/cl_intel_subgroups.txt

OpenCL中也有一些函数,通常是用shuffle-functions实现的,但是并不是所有的函数都可以用shuffle-functions实现:

总结:

  1. shuffle-functions 保持更灵活的功能,并通过直接寄存器到寄存器数据交换确保线程之间尽可能快的通信。

  2. 但是函数sub_group_broadcast/_reduce/_scan不保证寄存器到寄存器的直接数据交换,这些子组函数不太灵活。

gentype work_group_reduce<op> ( gentype  x)

版本 >=2.0

但它的定义并没有说明任何关于使用本地内存或寄存器的内容。这只是将每个协作者的 x 值减少为所有协作者的单个总和。此函数必须被所有 workgroup-items 击中,因此它不是波前级方法。 floating-point 操作的顺序也无法保证。

可能有些供应商使用注册方式,而有些供应商使用本地内存。我假设 Nvidia 使用寄存器。但是旧的主流 Amd gpu 具有 3.7 TB/s 的本地内存带宽,这仍然是一个不错的数量。 (编辑:它不是 22 TB/s)对于 2k 内核,这意味着每个内核每个周期近 1.5 个字节或每个缓存行快得多。

对于%100 register(如果不溢出到全局内存)版本,如果元素的数量只有8或16,你可以减少线程的数量,并在线程本身进行向量化减少,而不需要与其他元素进行通信。如

v.s0123 += v.s4567
v.s01 += v.s23
v.s0 += v.s1

应该类似于 __m128i _mm_shuffle_epi8 并且在 CPU 和 non-scalar 实现上编译时其总和版本将在 GPU 上使用相同的 SIMD 来执行这3个操作。

此外,使用这些向量类型往往会使用高效的内存事务,即使是全局和局部,而不仅仅是寄存器。

一个 SIMD 一次只能处理一个波前,但一个波前可能由多个 SIMD 处理,因此,这种矢量运算并不意味着正在使用整个波前。甚至整个波阵面都可能在一个周期内计算所有向量的第一个元素。但是对于 CPU,最合乎逻辑的选择是 SIMD 逐个计算工作项 (avx,sse),而不是通过相同的索引元素并行计算它们。


如果主要工作组不符合要求,则可以生成子内核并使用动态宽度内核进行此类操作。子内核同时在另一个名为 sub-group 的组上工作。这是在 device-side 队列中完成的,需要 OpenCl 版本至少为 2.0。

http://amd-dev.wpengine.netdna-cdn.com/wordpress/media/2013/12/AMD_OpenCL_Programming_User_Guide2.pdf

中寻找 "device-side enqueue"

AMD APP SDK 支持Sub-Group