
导致每次计算结果不同的原因.docx
17页1 前言 曾经有很多人问,诸如为什么明明输入文件一样,但是动力学轨迹每次跑出来的都不一样、为什么几何优化结果相差甚远等等,甚至怀疑计算科学是否遵循决定论 实际上结果的差异是由于运算开始或运算过程中各种形式的数值误差、随机性引起的这个是很重要却不被多数人重视的问题,它和理论本身、算法、软件环境、硬 件环境等都有密切关联本文将对这个问题做一些分析探讨首先先讨论数值算法、软件、硬件因素是如何导致误差(错误)和随机性的然后再看这些问题对计算化学会产生何等的影响2 数值误差(错误) 、随机性的根源2.1 内存因为不少人对内存有误解,所以下面说得多一些内存在数据读、写的过程中不可避免地会发生一些错误,轻则数据出现异常(比如一个变量为 1 却成了 0) ,重则死机、重启和内存错误(包括各种类型错误)相关的主要有以下几个方面:(1)供电质量差如电压偏离标准值过多、电流不稳这不仅取决于电源质量,还取决于主板好坏(内存供电电路) 2)过热随着温度升高内存出错的几率总是增加的,但只有高温下增加的幅度才最明显遥想笔者用 i860+RDRAM 的日子,不装个小风扇吹着内存的话高负载下 20 分钟左右就死机。
烫手的话应该装上散热片乃至小风扇3)内存质量一方面是内存设计,即布线、PCB 板数、贴片元件等另一方面取决于内存颗粒制造商的品质,也有 RP 的关系,比如颗粒来自晶圆的边角还是中心(杂质含量不同) 4)制程一般来说制程越精细可靠性越低,比如 22nm 制程的比起0.35 微米制程的更容易出问题但不要忽视工艺质量也在逐步改进,新的精细制程的产品出错几率不一定比以往的粗糙制程的要大5)宇宙辐射这看起来比较微妙,但是当严重的时候,如太阳风暴剧烈时,对电子设备的影响就明显了这也是为什么在一些宇航领域(接受的辐射更严重) 的芯片仍然用着设计过时、制程古旧的产品,没准儿出现一个小错大家就没命了机箱内电磁辐射也对内存稳定性有影响6)内存参数所设的工作频率越高、延迟越低出问题的几率越高,当然也要看内存类型7)内存容量、读写次数:内存容量越大、读写越频繁,相同时间内出错的几率越大8)老化在越恶劣的环境使用越长时间,出错的可能性越大要注意以上的因素不是分立、绝对的,而是互相关联的、相对的,不能单独拿出来对比可见,与内存数据出错相关的因素很多,也很难衡量内存平均多长时间出一次错一个令我比较认可的、由大规模 统计得到的说法是一般的使用环境下(外界环境正常,计算机运行稳定) ,平均每年每条内存有 3%几率出错(包括各种形式的错误) 。
实际上这个几率是很小的, 换句话说,等到机子被淘汰了也未必能赶上一次内存出错所以不要把内存问题过分夸大,比如持续几天的计算,计算结果无法重现由内存错误导致的可能性微乎其 微但是,对于数据中心,内存出错几率乘上相应内存数目,再考虑数据出错的损失,这就是大问题了内存的 Error Checking and Correcting (ECC)显得十分必要,这是一种数据错误检测和校正的技术,每次在写入数据时同时写入额外的数据位用作校验,在读出数据时也同时把校验位读出,由此根据 一定算法分析读出的数据是否和写入的数据相同,若不同则进行修复这使得内存数据出错几率大大降低,但也并非能解决所有问题有些人误认为 ECC内存性能 比普通同类型内存要低很多,其实相差甚微现今服务器内存几乎都带 ECC,它比普通内存略贵,多了额外内存颗粒用于储存校验位,而现今服务器主板也都支持 ECC(有些主板则只支持 ECC 内存) 个人计算没有绝对的必要使用 ECC 内存导致内存出错的原因对于 CPU/GPU 运算出错也基本是一样的,就不累述了2.2 处理器类型对于不同架构的 CPU、拥有不同指令集的 CPU、乃至 GPU,不能期望它们能完全重现同样的结果。
尤其是浮点运算的标准可能不同,比如末位的舍入、极小值 的处理有些指令在不同处理器中执行方式不同,比如先乘一个数再加上一个数,这两条指令在有的处理器中会被融合为结果更精确的“乘加”一条指令也有一些很特殊的情况,数值错误是由 CPU 设计 bug 引起的老玩家们应该还记得,较早一批的 Pentium 60/66Mhz 的浮点除法指令有错误 2.3 网络数据传输对于集群,尤其是分布式计算,需要考虑网络连接引入的错误网络传输都会有一定误码率,即在多少位数据中出现一位差错,其大小取决于网络规范、传输 介质、传输距离、外界干扰、网络适配器质量等因素主流的以太网、infiniband 等网络标准本身都带校验帧以保证数据完整、准确性,已很大程度上减 轻了网络传输引发的数据错误2.4 随机数生成器在一些程序中要用到随机数生成器,比如 Monte Carlo 过程、动力学的初始速度设定通常随机数是以不同的随机数算法产生的,常用的比如乘同余法这些算法都需要一个称为“种子” 的数,不同的种子将 产生出不同的随机数序列也就是说,如果程序中利用了随机数生成器,想重现结果就必须用相同的种子但种子往往不允许由用户设定,也不是固定不变的,而是 根据一些规则产生来保证每次运行结果的随机化,比如利用当前系统的时间,对这种情况用户不要指望结果会重现。
2.5 库文件、操作系统、程序版本不同的数学库中相同的功能在内部会使用不同的算法或者不同的编写方式,即便是同一个库,在哪怕相近版本中,也可能由于对代码进行的优化、除虫,导致不同 结果有些库函数的运行结果甚至取决于运行期的情况,比如快速傅里叶变换库 FFTW 会在启动时检测哪种算法在当前系统下效率最高,然后会在之后一直使用, 这就可能在检测的时候由于正在运行的其它任务对系统 CPU 资源占用不同,使检测结论不同,影响后续算法有些程序是通过静态链接产生的,库文件会被合并在程序可执行文件当中,或者有些程序虽然使用动态链接,但是 自带了动态库文件,这样不同系统下结果的重现性还好说而有些程序需要依赖于当前系统中的库文件,不同系统自带版本不同,差异就容易出现了若平台不同, 如一个程序的 Linux 版本与 Windows 版本,差异会更明显不要指望不同版本号的程序能获得相同结果,不仅所用的库往往不同,默认参数、算法也经常不同,比如 Gaussian03 的几何优化默认用的berny 方法,而 09 版后来默认用 GEDIIS,同一个输入文件有时前者能优化成功而后者却不收敛即便算法和 参数都不变,代码细节也可能改变。
2.6 编译器与编译选项编译器类型及具体版本号、编译参数对结果影响明显不同编译器会对代码进行不同形式的优化,有些优化是以牺牲精度为代价的如果调用了程序语言标准中定义 的标准数学函数,比如开方、求三角函数、求对数等,不同编译器会用不同的内建数学库,显然也会带来差异不同编译器对于语言标准中没有定义的情况的处理也 不同,比如未初始化的变量,有些编译器一律将之初始化为 0,而有些编译器则不这么做,相应内存地址位置目前是什么数值就是什么数值,由于这个数值无法预 测,每次运行时可能造成结果不同来看一个简单的比较,用 Multiwfn 2.02 对一个水分子全空间积分电子密度拉普拉斯值的结果Intel visual fortran 与 CVF 结果有所不同,在不同优化参数下结果也有所不同IVF12 0d/O1 -2.603320357104659E-005IVF12 O3 -2.603320356662977E-005CVF6.5 debug/release -2.603320356424300E-0052.7 并行执行时的运算顺序虽然诸如 a+b+c=a+(b+c)在数学上是成立的,但是在计算机执行中未必成立。
比如在 CVF6.5 里执行这样的语句a=sin(4D0)b=sin(99D0)c=sin(2D0)write(*,*) a+b+c-(a+(b+c))得 到的结果不是 0.000000000000000E+000,而是1.110223024625157E-016,如果结果被乘上一个比较大的数,那么这 个差异就不能被忽略了,可见计算机运算未必满足结合律在并行运算中,经常要把一部分工作拆成几个线程以分配给多个处理器并行执行,然后将算出来的结果累 加到一起通常哪个线程先算完,哪个结果就先被累加上然而实际计算中各个线程完成的先后顺序是不确定的,每次程序执行时都可能不同,因此数据累加的顺序 不一样,这就造成结果出现一定不确定性另外,有的程序中使用动态均衡负载以获得更高的效率,这导致每个线程执行的内容也是有随机性的,使结果的重现更加 困难 举个实例,在 Gaussian09 中用 4 核计算一个体系单点能,执行三次,最后一次迭代的能量分别为-234.579551993147-234.579551993143-234.579551993144结果在末位有所差异如果只用 nproc=1 来串行计算,则结果每次都能重现。
尽管并行时每次的结果差异看似微不足道,以 a.u.为单位一般精确到小数 后 5、 6 位足矣,然而这个差异在其它一些任务中会被逐渐放大,甚至可能达到影响定性结论的地步有些人以为通过增加积分精度、收敛精度之类能减轻结果的随 机性,实际上从原理上讲根本于事无补,根本不是一码事(仅在极个别情况下能有改善) 2.8 文件格式不同文件格式的精度,以及格式转换过程中的信息丢失是需要注意的浮点数据在计算机中通过二进制形式记录,而在磁盘上记录的文件往往是文本格式,这便于 人类阅读和第三方程序处理在内存中占8 字节的双精度变量若想完整以文本方式记录下来起码要用 22 字节,这不仅太占空间,阅读也不方便,因此往往故意减少 有效数字位数比如 Gaussian 的 chk 文件是二进制文件,浮点变量以二进制形式精确保存,有效数字位数约 16而转换成 fch 后,浮点数有效数字就 被截为 9 位,若用 unfchk 再将之转化回 chk,则此时的 chk 的数据精度比起之前的 chk 已经损失了即便是二进制文件也并非都是完整精度形式记录数 据,比如 Gromacs 的 xtc 文件也是二进制形式,而变量记录精度可控(xtc_precision 参数) ,默认的记录精度低于单精度浮点变量(这也是 为什么 Gromacs 轨迹文件比较小的原因) ,因此不要指望rerun 的过程,即重新算轨迹中每一帧的势能的值会与之前动力学过程中输出的一致。
有些程序 在导出/导入的过程中还可能会对结构、导数等等进行坐标变换,对单位进行转换等操作,也会引入数值误差2.9 操作过程一些操作过程的细节差异往往不被注意,对最终结果的影响可能却不小例如对晶体测得的结构加氢,很多程序加氢结果表面看似都一样,位置没有区别,但是查看 文件中记录的坐标,却总有细微差别再比如模拟一个 NMR 测得的体系,pdb 文件通常会有很多帧,往往每一帧差异很小,似乎取哪帧是随意的,但实际上用于 模拟会得到不同结果还比如 Gaussian 中设定不同内存大小,看似和计算结果无关,但实际上 Gaussian 会自动根据分配的内存容量选择不同的积分 计算、储存方式,也会引入差异总结一下,上面的讨论中,数值差异可以归纳为两部分,一种是可重现性差异,也可以叫静态差异,包括硬件配置、编译过程、库文件、文件操作、操作过程、程 序版本,只要运行条件完全一致就可以彻底避免;另一种是不可重现性差异,或者叫动态差异,它取决于运行期的实际条件,有的可以通过一定对策避免,比如把并 行运算改为串行,但有的很难完全消除,尤其是硬件偶发错误 3 数值误差、随机性对计算化学结果的影响从前面简单例子看到,误差或者随机性对结果的影响甚微,在默认的输出精度下甚至看不出任何差异。












