繁体   English   中英

__activemask() 与 __ballot_sync()

[英]__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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM