hsfzxjy
2022-07-23 19:36:20 +08:00
我来说一个很多人不会注意到的随机性来源:并行化计算和浮点运算造成的不确定性。
首先要知道,IEEE754 浮点数的加法和乘法是不符合结合律的。也就是说在某些情况下 (a+b)+c!=a+(b+c),一个反例是在 64-bit 情况下
1e40+((-1e40)+1e10) == 0.0
(1e40+(-1e40))+1e10 == 1000000000.0
因此,对于同一批浮点数相加,它们加的顺序会影响最终的结果。
其次要知道,GPU 算法通常会将一个问题分为几个子问题,对子问题并行计算,再将子问题的结果汇总为最终的结果。
比如你要用 GPU 计算 3 个浮点数的和,你可以开 3 个线程使用 atomicAdd 把这些数加在结果数上。
atomicAdd 能保证没有 race condition ,但是不能保证加的顺序,你这次跑可能是 (a+b)+c ,下一次就可能是 (b+c)+a 。而根据前一条,浮点数的运算顺序会影响运算结果。因此,你每次跑都有可能得到不一样的结果。
只要你的运算涉及将多路并行的浮点数结果汇总,这种随机性就无可避免,不管你如何固定应用层的随机种子。这种随机性的影响可能很小,但在漫长的训练过程中会不断放大,直到产生肉眼可见的差异。
为了克服这种随机性以得到确定性的结果,有时你需要和运算效率做 trade-off ,即以更慢的但是顺序确定的方式汇总浮点数。这也是 torch.backends.cudnn.deterministic 这个开关的由来。
但需要注意的是,torch.backends.cudnn.deterministic 不能保证完全消除这种不确定性。因为这涉及算法的重新设计,而不像固定一个种子这么简单。如果 cudnn 等库的作者考虑不周,那还是会有这种随机性。