并行归约假设相应的操作是关联的。浮点数的加法违反了这个假设。你可能会问我为什么关心这个。好吧,它使结果的可重复性降低。当模拟退火用于优化(或拟合参数)子程序时,情况会变得更糟,从而产生这种不可重现的结果。
处理这个问题的常用方法是什么?关于以下策略可以说些什么?
- 不要关心不可重复性。
- 不要对浮点数和加法使用并行归约。
- 以可重复的方式创建适当大小的工作包,并手动进行最终缩减。
- 使用更高的精度进行加法(但并非所有编译器都提供更高精度的浮点类型)。
并行归约假设相应的操作是关联的。浮点数的加法违反了这个假设。你可能会问我为什么关心这个。好吧,它使结果的可重复性降低。当模拟退火用于优化(或拟合参数)子程序时,情况会变得更糟,从而产生这种不可重现的结果。
处理这个问题的常用方法是什么?关于以下策略可以说些什么?
MPI_Allreduce()
只要您使用相同数量的处理器,使用实现的缩减是可重现的,前提是该实现遵守 MPI-2.2 标准第 5.9.1 节中出现的以下注释。
给实施者的建议。强烈建议
MPI_REDUCE
实现,以便每当函数应用于相同的参数时获得相同的结果,以相同的顺序出现。请注意,这可能会阻止利用处理器物理位置的优化。(对实施者的建议结束。)
如果您需要不惜一切代价保证可重复性,您可以遵循下一段中的指南:
给用户的建议。一些应用程序可能无法忽略浮点运算的非关联性质,或者可能使用需要特殊归约顺序且不能被视为关联的用户定义操作(参见第 5.9.5 节)。此类应用程序应明确强制执行评估顺序。例如,对于需要严格的从左到右(或从右到左)求值顺序的操作,这可以通过在单个进程中收集所有操作数(例如,使用
MPI_GATHER
),应用归约操作来完成以所需的顺序(例如,使用MPI_REDUCE_LOCAL
),并且如果需要,将结果广播或分散到其他进程(例如,使用MPI_BCAST
)。(对用户的建议结束。)
在更广泛的方案中,大多数应用程序的有效算法都利用了局部性。由于算法在不同数量的进程上运行时确实不同,因此在不同数量的进程上运行时准确再现结果是不切实际的。一个可能的例外是带有阻尼雅可比或多项式(例如切比雪夫)平滑器的多重网格,这种简单的方法可以很好地执行。
在进程数量相同的情况下,按照消息接收的顺序(例如使用 )处理消息通常对性能有益MPI_Waitany()
,这会引入非确定性。在这种情况下,您可以实现两种变体,一种以任何顺序接收的快速变体,另一种以静态顺序接收的“调试”变体。这要求还编写所有底层库以提供此行为。
对于某些情况下的调试,您可以隔离不提供这种可重现行为的部分计算并冗余执行。根据组件的设计方式,该更改可能是少量代码或非常具有侵入性。
在大多数情况下,我同意 Jed 的回答。但是,有一个不同的出路:给定普通浮点数的大小,您可以将每个数字存储在一个 4000 位左右的定点数中。因此,如果您减少嵌入的浮点数,无论关联性如何,您都会得到精确的计算。(对不起,我没有提到这个想法是谁提出的。)
您可以在 MPI 中实现数值稳定的归约算法,就像在串行中实现的一样。当然,性能可能会受到影响。如果您负担得起复制向量,只需使用 MPI_Gather 并在根上进行数字稳定的串行减少。在某些情况下,您可能会发现性能损失并不是什么大问题。
另一种解决方案是使用这里描述的宽累加器。您可以使用 MPI 作为用户定义的缩减来执行此操作,尽管它会使用更多带宽。
上述的折衷方案是使用补偿求和。有关详细信息,请参阅参考资料“Kahan summation”。Higham 的“数值算法的准确性和稳定性”是关于这个主题的极好资源。
为了解决共享内存系统上线程上下文中的问题,我写了这个页面来解释我们在 deal.II 中所做的事情:http: //dealii.org/developer/doxygen/deal.II/group__threads.html #MTWorkStream