我们可以使用 `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 中的项目(线程)之间是否有任何优化的寄存器到寄存器数据交换指令:
例如WARP(CUDA)中的int __shfl_down(int var, unsigned int delta, int width=warpSize);
:https://devblogs.nvidia.com/parallelforall/faster-parallel-reductions-kepler/
或 x86_64 上的 __m128i _mm_shuffle_epi8(__m128i a, __m128i b);
SIMD 通道:https://software.intel.com/en-us/node/524215
这个洗牌指令可以,例如,从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
扭曲投票函数:
__any(predicate)
returns 如果任何谓词为非零
经纱中的线程 returns 非零
__all(predicate)
returns 如果所有谓词都为非零
经纱中的线程 returns 非零
__ballot(predicate)
returns 具有相应位的位掩码
线程集,其中谓词 returns 非零
__shfl(value, thread)
returns 来自请求线程的值
(但前提是该线程还执行了 __shfl()-操作)
结论:
众所周知,在 OpenCL-2.0 中有类似于 WaveFronts 的具有 SIMD 执行模型的子组:
子组有 - 第 160 页:http://amd-dev.wpengine.netdna-cdn.com/wordpress/media/2013/12/AMD_OpenCL_Programming_User_Guide2.pdf
int sub_group_all(int predicate)
与CUDA相同-__all(predicate)
int sub_group_any(int predicate);
与CUDA相同-__any(predicate)
但是在OpenCL中没有类似的函数:
- CUDA-
__ballot(predicate)
- CUDA-
__shfl(value, thread)
2016 年 8 月 28 日最终草案 OpenCL 扩展 #35 版本 4 中只有英特尔指定的内置随机播放功能:intel_sub_group_shuffle
、intel_sub_group_shuffle_down
、intel_sub_group_shuffle_down
、intel_sub_group_shuffle_up
: https://www.khronos.org/registry/OpenCL/extensions/intel/cl_intel_subgroups.txt
OpenCL中也有一些函数,通常是用shuffle-functions实现的,但是并不是所有的函数都可以用shuffle-functions实现:
<gentype> sub_group_broadcast( <gentype> x, uint sub_group_local_id );
<gentype> sub_group_reduce_<op>( <gentype> x );
<gentype> sub_group_scan_exclusive_<op>( <gentype> x );
<gentype> sub_group_scan_inclusive_<op>( <gentype> x );
总结:
shuffle
-functions 保持更灵活的功能,并通过直接寄存器到寄存器数据交换确保线程之间尽可能快的通信。
但是函数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。
中寻找 "device-side enqueue"
AMD APP SDK 支持Sub-Group
众所周知,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 中的项目(线程)之间是否有任何优化的寄存器到寄存器数据交换指令:
例如WARP(CUDA)中的
int __shfl_down(int var, unsigned int delta, int width=warpSize);
:https://devblogs.nvidia.com/parallelforall/faster-parallel-reductions-kepler/或 x86_64 上的
__m128i _mm_shuffle_epi8(__m128i a, __m128i b);
SIMD 通道:https://software.intel.com/en-us/node/524215
这个洗牌指令可以,例如,从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
扭曲投票函数:
__any(predicate)
returns 如果任何谓词为非零 经纱中的线程 returns 非零__all(predicate)
returns 如果所有谓词都为非零 经纱中的线程 returns 非零__ballot(predicate)
returns 具有相应位的位掩码 线程集,其中谓词 returns 非零__shfl(value, thread)
returns 来自请求线程的值 (但前提是该线程还执行了 __shfl()-操作)
结论:
众所周知,在 OpenCL-2.0 中有类似于 WaveFronts 的具有 SIMD 执行模型的子组:
子组有 - 第 160 页:http://amd-dev.wpengine.netdna-cdn.com/wordpress/media/2013/12/AMD_OpenCL_Programming_User_Guide2.pdf
int sub_group_all(int predicate)
与CUDA相同-__all(predicate)
int sub_group_any(int predicate);
与CUDA相同-__any(predicate)
但是在OpenCL中没有类似的函数:
- CUDA-
__ballot(predicate)
- CUDA-
__shfl(value, thread)
2016 年 8 月 28 日最终草案 OpenCL 扩展 #35 版本 4 中只有英特尔指定的内置随机播放功能:intel_sub_group_shuffle
、intel_sub_group_shuffle_down
、intel_sub_group_shuffle_down
、intel_sub_group_shuffle_up
: https://www.khronos.org/registry/OpenCL/extensions/intel/cl_intel_subgroups.txt
OpenCL中也有一些函数,通常是用shuffle-functions实现的,但是并不是所有的函数都可以用shuffle-functions实现:
<gentype> sub_group_broadcast( <gentype> x, uint sub_group_local_id );
<gentype> sub_group_reduce_<op>( <gentype> x );
<gentype> sub_group_scan_exclusive_<op>( <gentype> x );
<gentype> sub_group_scan_inclusive_<op>( <gentype> x );
总结:
shuffle
-functions 保持更灵活的功能,并通过直接寄存器到寄存器数据交换确保线程之间尽可能快的通信。但是函数
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。
AMD APP SDK 支持Sub-Group