一、说明
在前面将束内原语的Vote和Shuffle进行了分析和说明,基本明白了二者的功能和用途。这时候可能就会想到,会不会还有其它的束内函数呢?那自然是有的。下面将对其它的几个束内函数进行分析和说明,不过,重点只是进行功能的分析说明。更多的细节需要思考和查找相关资料(特别是官网上的文档说明)。
二、Wrap Reduce、Warp Match和Warp Matrix
在CUDA中,还提供了Wrap Reduce、Warp Match和Warp Matrix函数即线程束归约函数、线程束匹配函数和线程束矩阵运算操作函数。
Wrap Reduce用于在线程束内计算所有线程数据的单一聚合值,如极大极小值等。主要应用场景为并行归约、求和和极值运算等
Warp Match用于在线程束内查找与当前线程拥有相同值的其它线程,如进行快速的数据查找和比较,应用场景为数据的过滤、去重及构建索引等。
Warp Matrix则是利用GPU的Tensor Core硬件加速单元,在线程束内进行小规模的矩阵乘累加操作(D = A * B + C),核心应用就是硬件加速矩阵运算。应用场景为深度学习、科学计算中的矩阵运算等。
需要注意的是,后二者都需要GPU架构的支持,在实际应用中需要先确定硬件和CUDA版本的具体支持情况,发现问题要首先确认是否是硬件或软件版本支持的问题。
三、相关的函数接口
这三个束内函数包括:
c
//Warp 匹配函数
unsigned __match_any_sync(unsigned mask, T value);
unsigned __match_all_sync(unsigned mask, T value, int *pred);
//Warp 归约函数
T __reduce_add_sync(unsigned mask, T value);
T __reduce_min_sync(unsigned mask, T value);
T __reduce_max_sync(unsigned mask, T value);
unsigned __reduce_and_sync(unsigned mask, unsigned value);
unsigned __reduce_or_sync (unsigned mask, unsigned value);
unsigned __reduce_xor_sync(unsigned mask, unsigned value);
//Warp矩阵运算处理函数
template<typename Use, int m, int n, int k, typename T, typename Layout=void> class fragment;
void load_matrix_sync(fragment<...> &a, const T* mptr, unsigned ldm);
void load_matrix_sync(fragment<...> &a, const T* mptr, unsigned ldm, layout_t layout);
void store_matrix_sync(T* mptr, const fragment<...> &a, unsigned ldm, layout_t layout);
void fill_fragment(fragment<...> &a, const T& v);
void mma_sync(fragment<...> &d, const fragment<...> &a, const fragment<...> &b, const fragment<...> &c, bool satf=false);
上面的接口相对来说看函数名称即可明白相关的操作。后面的矩阵操作进行一下简单的说明:
矩阵的运算处理只针对算力在7.0以上的设备上,它们都定义在nvcuda::wmma命名空间中。fragment是一个重载的类,包含分布在warp所有线程中的矩阵片段。矩阵元素到fragment内部存储的映射关系未指定,且在未来的架构中可能发生变化。它仅允许特定的模板参数组合。
load_matrix_sync和store_matrix_sync接口类似于C++中的原子操作中的相关操作,前者等待所有warp线程到达 load_matrix_sync,然后从内存加载矩阵片段;而后者等待所有warp线程到达 store_matrix_sync,然后将矩阵片段 a 存储到内存。
fill_fragment用常量值v填充矩阵片段。由于矩阵元素到每个片段的映射未指定,此函数通常由warp中所有线程以共同的v值调用。
mma_sync等待所有warp线程到达mma_sync,然后执行warp同步的矩阵乘加操作D = A * B + C。
四、Warp Active Mask
在开始分析束内函数时,就在官方文档中看到相关活动掩码控制的函数接口即:
c
unsigned __activemask();
其主要的功能是动态获取当前活动线程的掩码,配合前面的束内原语函数进行工作。但对它的使用要非常谨慎,因为新的GPU(如Volta架构)后,由于引入了独立线程调度,即线程的活跃状态可能随时发生变化 ,因为由此函数获取的掩码可能不再准确。通常还是建议直接在代码逻辑中指定掩码。
五、接口更新
在最新的NVIDIA13.1的官方文档中,在这些函数进行了不同程度的更新和警告说明。比如对于上面的Shuffle和匹配函数,推荐使用libcu++中的相应接口。如"libcu++中的cuda::device::warp_match_all()函数作为__match_all_sync函数"的安全且通用的替代方案。又比如推荐使用CUB库中的归约库函数等等。
对于矩阵的处理,则会不断的发展变化,所以在官方文档中说明了"Sub-byte operations are considered preview, i.e. the data structures and APIs for them are subject to change and may not be compatible with future releases"意思说对子字节(低于一字节精度如int4,int1等)的矩阵运算在本阶段为预览版,在未来有可能发生变化并不保证兼容。
这些在新版本的开发中,需要明白这些最新的变化。
六、例程
下面看一个例程:
c
#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include <stdio.h>
__global__ void WarpReduceDemo() {
int tid = threadIdx.x;
int retAdd = __reduce_add_sync(0xffffffff, tid);
int retMin = __reduce_min_sync(0xffffffff, tid);
int retMax = __reduce_max_sync(0xffffffff, tid);
unsigned int opTid = tid & 1;
unsigned int retAnd = __reduce_and_sync(0xffffffff, opTid);
unsigned int retOr = __reduce_or_sync(0xffffffff, opTid);
unsigned int retXor = __reduce_xor_sync(0xffffffff, opTid);
printf("threadId: %d reduce add: %d reduce min: %d reduce max: %d reduce and: %x reduce or: %x reduce xor: %x\n",
threadIdx.x, retAdd, retMin, retMax, retAnd, retOr, retXor);
}
int main() {
WarpReduceDemo << <1, 32 >> > ();
return 0;
}
由于环境中支持的架构算力太低,GTX960是5.2,所以默认的编译是无法通过的。利用在前面的CDP动态并行性中的方法将compute_52,sm_52修改为compute_85,sm85编译报不支持此架构,再次修改为compute_80,sm_80,编译通过,但运行无结果。这也符合前面提到的,如果硬件支持不够,则行为未定义。这也意味着,编译与实际运行是两个范畴。开发者可以自行指定编译的算力大小,从而决定运行的环境。
大家可以根据自己情况进行处理。
七、总结
AI的技术进步和更新的速度是有目共睹的,作为其基础建设的重要一环,CUDA的技术发展必然会与时俱进。技术进步迭加硬件更新,导致学习成本也在不断提高。而AI反过来又导致开发者的需求量减少,两者相制约,给开发者带来很大的压力!