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