[英]__activemask() vs __ballot_sync()
在阅读 CUDA 开发人员博客上的 这篇文章后,我很难理解何时安全\\正确使用__activemask()
代替__ballot_sync()
。
在Active Mask Query部分,作者写道:
这是不正确的,因为它会导致部分总和而不是总和。
之后,在Opportunistic Warp-level Programming 一节中,他们使用了__activemask()
函数,因为:
如果您想在库函数中使用扭曲级编程,但又无法更改函数接口,这可能会很困难。
CUDA 中没有__active_mask()
。 这是一个错字(在博客文章中)。 它应该是__activemask()
。
__activemask()
只是一个查询。 它提出了一个问题“在这个周期中,warp 中的哪些线程当前正在执行这条指令?” 这相当于询问“当前经纱中的哪些线程正在收敛?”
它对收敛没有影响。 它不会导致线程收敛。 它没有扭曲同步行为。
__ballot_sync()
具有收敛行为(根据提供的mask
)。
应根据 Volta 扭曲执行模型考虑此处的主要区别。 Volta 及其他版本,由于扭曲执行引擎中的硬件变化,可以支持扭曲中的线程在更多场景中发散,并且比以前的体系结构发散的时间更长。
我们在这里所指的背离是由于先前的条件执行而导致的偶然背离。 由于显式编码导致的强制发散在 Volta 之前或之后是相同的。
让我们考虑一个例子:
if (threadIdx.x < 1){
statement_A();}
statement_B();
假设线程块 X 维度大于 1,则statement_A()
处于强制发散区域。 执行statement_A()
时,warp 将处于发散状态。
那么statement_B()
呢? CUDA 执行模型没有特别说明在执行statement_B()
时经纱是否处于发散状态。 在 Volta 之前的执行环境中,程序员通常会期望在前一个if
语句的右花括号处有某种扭曲重新收敛(尽管 CUDA 不保证这一点)。 因此,一般的期望是statement_B()
将在非发散状态下执行。
然而,在 Volta 执行模型中,不仅 CUDA 没有提供任何保证,而且在实践中我们可能会在statement_B()
处观察到扭曲处于发散状态。 发散在statement_B()
不需要代码的正确性(而它需要在statement_A()
也不是在收敛statement_B()
由CUDA执行模型需要。 如果在 Volta 执行模型中可能出现statement_B()
处出现分歧,我将其称为偶然分歧。 发散不是出于代码的某些要求,而是由于某种先前的条件执行行为。
如果我们在statement_B()
处没有分歧,那么这两个表达式(如果它们在statement_B()
)应该返回相同的结果:
int mask = __activemask();
和
int mask = __ballot_sync(0xFFFFFFFF, 1);
所以在 pre-volta 的情况下,当我们通常不期望在statement_B()
出现分歧时,这两个表达式返回相同的值。
在 Volta 执行模型中,我们可以在statement_B()
处偶然发散。 因此这两个表达式可能不会返回相同的结果。 为什么?
__ballot_sync()
指令与所有其他具有掩码参数的 CUDA 9+ 扭曲级内部函数一样,具有同步效果。 如果我们有代码强制发散,如果掩码参数指示的同步“请求”无法满足(就像上面我们请求完全收敛的情况一样),那将代表非法代码。
但是,如果我们有偶然的分歧(仅在此示例中),则__ballot_sync()
语义将首先重新收敛扭曲,至少达到掩码参数请求的程度,然后执行请求的投票操作。
__activemask()
操作没有这种重新收敛行为。 它只是报告当前收敛的线程。 如果某些线程发散,无论出于何种原因,它们都不会在返回值中报告。
如果您随后创建了执行某些扭曲级别操作的代码(例如博客文章中建议的扭曲级别总和减少)并根据__activemask()
与__ballot_sync(0xFFFFFFFF, 1)
选择要参与的线程,则您可以可以想象得到不同的结果,在存在偶然分歧的情况下。 __activemask()
实现,在存在偶然发散的情况下,将计算不包括所有线程的结果(即,它将计算“部分”总和)。 另一方面, __ballot_sync(0xFFFFFFFF, 1)
实现,因为它会首先消除偶然的分歧,将强制所有线程参与(计算“总”和)。
博客文章中的清单 10 给出了与我在此处给出的内容类似的示例和描述。
关于“机会主义扭曲级编程”的博客文章中给出了使用__activemask
正确示例,此处:
int mask = __match_all_sync(__activemask(), ptr, &pred);
这个语句是说“告诉我哪些线程收敛了”(即__activemask()
请求),然后“使用(至少)这些线程来执行__match_all
操作。这是完全合法的,将使用任何碰巧收敛的线程在这一点上。随着清单 9 示例的继续,在上述步骤中计算的mask
用于唯一的其他扭曲合作原语:
res = __shfl_sync(mask, res, leader);
(恰好在一段条件代码之后)。 这决定了哪些线程可用,然后强制使用这些线程,不管可能存在什么偶然的分歧,以产生可预测的结果。
作为对mask
参数用法的额外说明,请注意PTX 指南中的用法说明。 特别是, mask
参数不是一种排除方法。 如果您希望从 shuffle 操作中排除线程,则必须使用条件代码来执行此操作。 鉴于 PTX 指南中的以下声明,这很重要:
如果正在执行的线程不在成员掩码中,则 shfl.sync 的行为是未定义的。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.