正在阅读:

清华大学副教授都志辉:如何使用GPU加速实时空间天气预报

扫一扫下载界面新闻APP

清华大学副教授都志辉:如何使用GPU加速实时空间天气预报

清华大学计算机系副教授都志辉团队采用PPMLR-MHD方法并利用GPU加速运算处理有效解决了实时空间天气预报问题。

3月23日起,智东西联合NVIDIA推出「实战营」第一季,共计四期。第四期于4月20日晚8点在智东西「高性能计算」系列社群开讲,由清华大学计算机系副教授都志辉、NVIDIA高级系统架构师易成二位讲师先后主讲,主题分别为《GPU加速的实时空间天气预报》和《NVIDIA GPU加速高性能计算》。

大型的科学应用程序往往由于其代码量和通信量巨大,对计算力和数据传输性能要求非常高,如何利用高性能计算技术,设计开发高效的并行计算模型与算法,充分利用不断提高的硬件系统的计算能力来解决科学与工程计算问题具有重大意义,清华大学计算机系副教授都志辉团队采用PPMLR-MHD方法并利用GPU加速运算处理有效解决了实时空间天气预报问题。

本文为都志辉教授的主讲实录,共计7721字,预计14分钟读完。在浏览主讲正文之前,可以思考以下三个问题:

-如何有效的将大量代码从CPU移植到GPU上,并保证运算结果的一致性?

-如何准确找到对性能影响特别关健部分的代码并进行优化?

-如何优化GPU上代码的性能以提高计算资源利用率?

主讲环节

都志辉:大家好,我叫都志辉。很高兴能够在这里跟大家一块交流我们在GPU加速空间天气预报方面的一些工作,我今天分享的主题是《GPU加速的实时空间天气预报》。

今天的内容分为三个方面:

1,空间天气预报的概念和意义;

2,空间天气预报的计算难点及挑战,这里会介绍一些我们做大型科学工程计算时在GPU移植上获得的一些经验,以及如何在GPU上进行优化;

3,利用GPU实现加速空间天气预报采取的方法

空间天气预的概念和意义

首先介绍一下空间天气预报的概念,空间天气预报可以类比地面的天气预报。比如刮风下雨对我们有什么影响,只不过空间天气预报是在更远的地方,主要研究太阳与地球的相互作用对地球外部环境的影响。

在地球上,主要是刮风、下雨对我们有影响;而在空间是太阳风,即太阳发出很强劲的带电粒子,会对地球造成非常大的影响。

对地球的影响有很多方面,对通信、雷达信号、发射卫星和电视转播等都会造成影响。根本原因是这些带电粒子干扰了地球的外层空间环境,从而对地球上的各种通信网络、电力网,甚至是臭氧层都造成影响。

如果没有地球的磁场把太阳给推开,那么地球上就不可能有人类,太阳粒子会直接把人类杀死。进入地区的太阳例子就像一个很强的X射线一样进入了人体,相当于人体接触了大量的辐射,会对人体的健康造成非常有害的影响。

空间天气预报的计算的难点以及挑战

如果把问题抽象起来,就是一个磁场和一个带电的离子流之间的相互作用,在数学上可以用磁流体动力学方程来进行描述。

这个方程建立起来之后是非常复杂的,有很多不同的解法。其中我们的合作方(空间科学中心)提出了一种叫PPMLR的方法来解磁流体动力学方程。

他们用了十几年的时间来发展这个方法,而且也已经形成了一个比较稳定的程序。这个方法的好处在于可以得到比较高精度的仿真计算结果,但计算量、通信量也非常大。

因此目前空间的空间天气预报面临的主要挑战跟很多大规模科学工程计算问题一样,都是如何才能算得更快,如何才能够完成实时计算的问题。对于实时预报问题,空间科学中心告诉我们理想的情况是在40到50分钟之内把全部的结果仿真出来。用原来的程序完成这个计算需要2-3个小时,所以我们面对的问题是如何把2-3小时的计算时间压缩到1小时之内。

他们的方法非常好,所有的科学工程计算问题都首先得有数值计算方法,然后才是实现。这里就不进行仔细地介绍了,当然一个好的数值计算方法也是非常重要的,这里我们重点讲一下如何在GPU上进行程序代码的实现和优化。

这是SCALE CHALLENGE AWARD证书,是我们把空间科学中心的大型应用程序放在GPU集群上进行性能优化,实现了实时的计算,我们把这个成果参加了一个比赛,在IEEE的可扩展计算委员会经过答辩评审后授予了这个证书。

如果大家也做了相关的事情,比如做了科学工程计算,解决了一个很实际的问题,效果也非常好,也可以试着去参加一下这个竞赛,在CCGRID的会议投稿期间,可以把你的工作写成类似于论文的东西投过去,同时需要指明你是要参加这个竞赛。他们首先要进行评审,评审之后还要去进行答辩,答辩完了之后再评价,最后挑选出他们认为做得比较好的项目,并授予相应的奖项。

这是空间科学中心已有程序的大概计算流程。这个程序分为两部分,一部分是计算太阳风和地球磁层之间的相互作用;另一部分是计算地球的电离层。将这两部分相互计算的结果最后综合到一起形成总的仿真结果。

计算量比较大的是太阳风和磁层的相互作用这一部分,而右边相对来说计算量很小,所以在他们的程序里面就出现了跟一般的并行不太一样的地方,就是用一个MPI进程来专门计算电离层这部分的任务,而其他所有的进程来计算太阳风和磁层相互作用这一部分。

我们在GPU上加速以及原来在MPI并行的也主要是左边这一部分。大家可以看到有sweepx、y、z三个过程,同时最主要的计算量也是在这三个过程里面,而我们做的事情就是对这三个过程分别在GPU上又进一步进行加速。

在具体介绍我们怎么优化这个程序之前,先大概讲一下我们在优化这个程序时获得的一些体会。在优化这个程序的同时,我们还做了一个大型双黑洞仿真的MPI并行程序,将其放在GPU集群上进行了性能优化,这些大型应用程序的代码比较多,一般有几十万行,而这个程序只有几万行。

通过把这个大型的科学工程应用程序进行性能优化,在GPU上进行移植等,完成这件事情得到的体会是它和一般的小程序Benchmark有非常大的不同:

1,源程序的代码量不一样,一般大型程序都在几万行以上,而一般Benchmark只有几百行,最多是一两千行。对于Benchmark这样的小程序来说,由于代码量很少,因此在优化的过程中,你就可以非常仔细地对每一条语句进行研究。而对于十几万行代码的大型程序,这样操作的工作量就非常大。这也是大型程序性能优化在GPU上进行移植面临的一个非常大的问题;

2,控制逻辑和数据结构不同,由于大型应用程序代码量大,必然导致其控制逻辑和数据结构与benchmark非常不同,对于大型程序来说不是那么简单就可以搞得明白,而Benchmark可以很容易把整个代码做的事情搞得一清二楚。

我们在将空间天气预报程序在GPU集群上进行移植以及将更大的双黑洞仿真程序在GPU上的移植时,发现一个相同的规律,就是代码移植本身并不是太难,对于一个对CUDA编程非常熟练的人来说,只要用一个月的时间就可以把上万行甚至几十万行的代码移植到GPU上,而难的是将代码移植到GPU上之后,一开始运行就会出现这样那样的意外与错误,就算你把这些问题都解决了之后,也会发现一个非常难解决的问题,就是在GPU和CPU上运行的结果不一样。

我们的学生,他们之前用GPU用得很熟,算是CUDA编程高手,所以一开始对这么大的程序代码是非常有信心的,觉得移植到GPU上没有问题,事实上一开始的代码移植也的确是这样,但问题是将代码移植到GPU上之后,不能保证这些代码是正确的。

这两个大型程序的移植都出现了相同的情况,我们调试错误所花的时间要远远超过代码移植所花的时间,甚至有些学生调试到最后都已经完全崩溃了,感到非常绝望,并且不知道该从哪下手了,虽然他们一开始都觉得自己是GPU编程的高手,但是对于大型程序中出现的各种莫名其妙的错误都感到束手无策。

利用GPU实现加速空间天气预报采取的方法

下面我来讲一下我们是如何在GPU集群上移植代码的。

首先这个代码不是串行代码,前面已经介绍过,它是空间科学中心花了多年的时间,用MPI在CPU集群上可以稳定并行运行的并行代码。

这个代码有些特殊的地方,就是实际应用程序和Benchmark不同,用Benchmark实现的某些方法和手段,在实际应用程序中,经常发现是不能解决问题的,原因就是大型应用程序总有很多特殊的情况,而这些特殊的情况会让那些从Benchmark里面总结的经验变得不可行。

比如上图这个程序,它对网格的划分比较特殊,它不是均匀的划分,大概意思是,越靠近地球的地方相对来说越稠密,而离地球越远的地方相对稀疏一些,这也是所有的网格划分中基本的指导思想。

这个算法的实现还有一个特点,在对空间网格划分以后,对各个坐标轴的约束也不一样。它需要专门拿出一个进程来仿真电离层的计算,而其他的进程用来仿真太阳风与地球的相互作用,为了让地球都处在特定的位置,就要求它坐标轴的y和z轴的进程所划分的块数必须是奇数。

如上图所示,以地球为中心进行156*150*150的空间网格划分,在这个基础上可以并行的进程数并不能随便选,而只能是某些特定的值,比如4,28,37等这些值。

这里我跟大家介绍的这些特别细节的内容,我觉得大家可能没必要去掌握。我想从中说明一件事情,就是对大型应用程序来说,往往会有很多特殊的要求,而这些特殊的要求可能会对你的性能优化或者以前在别的地方积累起来的一些经验造成很大的障碍,在这样的场景下已有的经验可能并不适用或者说效果并不好。

同时这也说明一件事情,我们经常说GPU的性能非常高,有成百甚至上千倍的加速。这个加速往往是在Benchmark理想情况下得到的,而在真实的大型应用程序上,如果想得到非常高的性能加速,一般来说是特别难的,难的原因就是大型应用程序本身不管是CPU还是GPU并行都不是特别理想,与理想情况不太吻合,因此给并行和优化加速制造了非常多的困难和障碍。

我们把程序在GPU集群上进行移植和优化的过程分为两步:

第一步,是一个摸索的过程,在MPI并行程序的基础上,对代码进行处理,其中代码分两部分,一部分是计算量比较少的,用一个MPI进程在CPU上计算就可以完成。另一部分是计算量比较大的,就是太阳风与地球磁层相互作用的那一部分,需要将它全部移植到GPU上进行计算。

我们之所以把这么多的代码全都移植到GPU上,而不是将一部分放在CPU上一部分核心的sweepX、Y、Z三个过程放在GPU上执行,主要是因为如果你把一部分代码放在CPU上,一部分放在GPU上,计算是可以大大的加速的,但是部分代码放在CPU上会导致CPU和GPU之间存在大量的数据交换,如此一来,之前得到的加速计算的效果都会被这些数据交换的开销所淹没。

正是因为这个原因,所以我们才不得不把这么多的代码放在GPU上进行移植,尽管有些代码并不是计算密集部分的代码或者也不是一些瓶颈的代码,这个问题在很多GPU性能优化上都会存在的,其根本原因是因为目前在GPU上执行代码,得先把它需要的数据放上去,放数据是需要很大的开销的。因此为了避免反反复复地在CPU和GPU之间来回拷贝数据,我们才把更多的代码放到GPU上来执行。

对于这个过程,像刚才说的,一开始看了这个代码之后很有信心,认为移植上去不会有问题。然后不到一个月,就把原来在MPI在 CPU上并行的代码全都移植到GPU上去了,但是到后来一测试,就发现会出现各种各样的错误。开始我们还是比较有信心的,也很快地把很多错误都搞定了。但是后面不断测试,就不断会有新的错误出来,一直到最后,就发现有些错误根本就不知道是什么原因导致的。

在这种情况下,我们不得不反思,移植上去的代码可能是一个失败的尝试,换句话说我们根本没有办法保证这些代码是正确的,也不可能通过调试把每一条的错误都找出来。在经过思考以后,我们就提出了第二版移植方案,其中的基本想法就是分步走,不要想着一步就把原来的代码直接移植到GPU上,这也是从CPU程序向GPU移植经常会犯的一个错误,一般将代码直接直截了当地移植到GPU上往往都是有问题的。

因为CPU程序的执行流程和计算方式与GPU有很大的不同,包括它们的数据结构和流程都不一样。如果将代码直接移植到GPU上,它的性能是会受影响的,或者你要改动非常大才能满足性能要求,所以这种Straight forward移植是有很大问题的。而我们认识到这个错误以后,我们的方案是不把代码直接移植到GPU上,而是先分析原来的代码,如果要准备移植到GPU上,什么样的一个代码结构与数据结构更有利于移植。

第二步,先把数据结构和代码结构分别进行重组,但都还是用C或者用Fortran在CPU上进行编程,比如循环结构和数据结构,CPU上的哪些数据结构跟GPU的特性有关联,相关联的结构重组后,将代码移植到GPU上后才不会造成混乱的局面。

把这些代码捋顺以后,看上去这些代码看上去还是CPU上的代码,但是它是按照GPU上的逻辑,为了能在GPU上执行的逻辑和数据组织方式去重写的。通过调试,确保这部分代码是正确的。

在这样的基础上,我们才将代码往GPU上进行移植。刚才也说到,代码量太大,为了避免CPU和GPU之间频繁的数据交换,需要将很多代码都转移到GPU上,很明显这工作量太大。但是如果特别影响性能的那部分核心代码又不是特别多,我们可以采取引入Open ACC的方法。

引入Open ACC的目的,是对于那些我们不得不移植到GPU上的、但即使这部分代码经过优化以后也不可能对性能有非常大的提升的不太核心的代码来说,我们可以通过Open ACC加一些简单的注释语句,将它变成能在GPU上执行的代码。而对于那小部分对性能影响非常大的代码,可以专门用CUDA来进行重写。

通过这种方式,就达到了以下的效果:

第一,通过使用Open ACC的方式,使得工作量大大降低,而且不容易出错,降低了出错的概率;

第二,通过手写对性能影响特别关健的部分的代码,用CUDA来重新修改、优化,可以大幅度提高最终代码的性能。

以上说的就是我们在整个移植过程中总结出来的一些经验,就是通过这种方式,既避免了CPU上大量的代码移植到GPU上重写的繁琐过程,又能保证对性能没有造成很大的影响。

因此大型应用程序在GPU或者GPU集群上的移植和性能优化,它的第一个难点并不是性能的提升,而是如何有效地控制代码移植的复杂性。

我们采取的办法就是,首先经过代码的重组,构造一个中间版本,而这个中间版本跟原来的程序一样,都是用C或者用Fortran语言来写的,但是它比原来的程序更容易移植到GPU上去,因为它是按照GPU的逻辑来组织、重写的,但它又是CPU的代码,这样的中间代码就容易符合GPU上的逻辑;第二步就是采用Open ACC方法,避免了手工重写大量与性能无关的代码。

还有一个难点,对大型程序来说,要找到真正影响性能的地方往往也是很难的。有时候可能自己感觉用shared memory能提高性能,但是用了以后效果可能不一定真的好。我们在做用GPU来加速引力波数据处理的时候,就发现如果用shared memory来进行优化,还不如用atomic操作最终得到的效果要好。

NVIDIA的编程指导中讲了很多通用的GPU性能优化的方法,比如提高occupancy,使用shared memory等这些手段,但是在真实的应用场景下,这些优化手段并不是用了就一定能够得到好的结果,有的时候用了可能还会带来另外的负面影响,从而导致整体性能没有提高,反而可能有下降的情况。

这就是我想说的另外一个难点,如何才能找到真正可以影响性能的优化点,这个是非常难的,因为大型程序它存在很多会影响性能优化的因素,可能计算性能提高了,但是通信的开销大了,性能也不一定好,或者是并行计算提高了,但数据访问的次数提高了,开销大了,也有可能会影响性能。

下面具体讲讲我们对大型应用程序,再深入到具体的GPU代码编程大概用哪些优化手段和方法。

第一个比较简单,对每一个MPI进程都调用一个CUDA核心来并行实现,就是把原来CPU上一个进程的执行代码变成一个CUDA的并行执行(代码)。

另外一个方面是如何提高访存的效率。由于我们在具体应用中,在解方程的过程中,几乎没有数据的重用,因此我们没必要再用shared memory,并且在这时候用,并不能带来性能的提升。而能够提高性能的,就是想办法重新调整数据的结构,让数据访问时局部性高一点,一次可以读入更多的数据,以这种方式来提高数据访问的效率。

这里我想再强调一下,根据我们在GPU上进行性能优化的工作体会,在大部分情况下,将CPU的代码移植到GPU上的时候,数据结构都需要进行重组,如果不进行数据结构的重组,往往对性能会带来负面的影响,而且GPU上的性能优化有很大的程度是在优化对数据的访问,就是如何才能更有效、更快地得到一些数据。

还有一个优化的方面就是使用Stream(流),我们编写了很多不同的核心,通过这些核心把它们形成Stream,由于GPU的计算能力很强,通过Stream这种方式,让它们可以共享GPU资源,提高GPU资源的利用率。

这也是一种比较通用的提高GPU上代码性能的方式。由于现在GPU的能力越来越强,单个的Kernel不可能充分地利用GPU的资源,因此在很多情况下,Kernel都不能把GPU上的资源用满,这时利用Stream的优化方法去提高GPU资源的利用率也是非常重要的。

上图是对通信优化的结果,我们对大型应用程序进行通信优化,最主要的一点,也最有效的一点就是用GPU NVLink的特点进行优化,就是GPU和GPU之间可以不通过CPU直接进行数据交换。我们在测试以后发现这种方式对性能的提高非常大。

这个应该是目前世界上最大的GPU集群,它是由美国橡树岭国家重点实验室在Titan GPU集群上测试的结果。总的来说与在CPU上MPI并行版本相比,性能提高了2到3倍。

以上就是我要介绍的内容。谢谢大家。

Q&A环节

问题

李敏-中科院软件所-高性能计算与并行计算方向博士

1,对一个大型应用程序,在GPU上并行加速的时候,一般的步骤都有哪些?可否分享一下您的经验?

2,对应用进行并行加速这个工作,比较偏工程一些,如果对于博士从事这个方向的同学来说,如果想发论文,一般从哪些方面着手,找一些创新点?因为一边是具体的应用领域,一边是计算机领域的并行优化,其实是一个交叉的方向,如果权衡这两方面?

都志辉:

1,其实我在前面讲的时候也大概说了一下,对一个大型应用程序在GPU上并行加速的时候,不要直接就往上移植,最好是先好好分析一下这个程序,先把问题、结构和流程搞清楚。然后再把你往GPU移植所要采取的手段和方法也想清楚,之后再动手。我们一般说性能优化,包括三个方面:应用、算法和硬件体系结构。那么移植也差不多,先把应用搞明白,再去移植,然后你再考虑如何用算法来实现,最后再结合GPU和硬件来做这件事情。

2,这个问题比较通用,干我们这一行的很多人都有同感。我要给你一点建议是:其实对于我们一开始做优化的人来说,可能上来的想法是我如何把性能提高就行了,如果你想在博士期间做这件事情,想发论文,想找创新点,我觉得不是一上来就想着怎么去优化的问题,而是要先多想想问题的本质在哪里,别人是怎么做的,为什么不行,你打算怎么做,这个方法为什么可以或者你觉得他为什么有可能不行,在优化之前或者是优化过程中,如果你把这些问题都弄明白了,那么你动手有了优化结果后,也就意味着你写的文章或者是你的创新点也比较容易找到了。

我们之前也犯过毛病,那时候我们觉得反正有这么一个任务,想着快点完成就行了,然后这样弄完之后,你只能说我把这性能提高了多少倍,但是有很多问题都没有想清楚,比如你这么做比别人做有什么好处,为什么不用别的方法,有没有更好的方法,你这个方法的极限和瓶颈在哪等,可能都没有想清楚,所以如果想发论文或者是找创新点,那就在动手之前先多考虑考虑,多问些问题。这就是我的一点建议。

对于权衡这方面,首先你说的非常对,如何去权衡目前确实没有特别好的办法,只能硬着头皮去做,在做应用领域时就得先把应用问题搞清楚,如果搞不清楚就不可能把这个优化做好,这是我的一点体会。

本文为转载内容,授权事宜请联系原著作权人。

评论

暂无评论哦,快来评价一下吧!

下载界面新闻

微信公众号

微博

清华大学副教授都志辉:如何使用GPU加速实时空间天气预报

清华大学计算机系副教授都志辉团队采用PPMLR-MHD方法并利用GPU加速运算处理有效解决了实时空间天气预报问题。

3月23日起,智东西联合NVIDIA推出「实战营」第一季,共计四期。第四期于4月20日晚8点在智东西「高性能计算」系列社群开讲,由清华大学计算机系副教授都志辉、NVIDIA高级系统架构师易成二位讲师先后主讲,主题分别为《GPU加速的实时空间天气预报》和《NVIDIA GPU加速高性能计算》。

大型的科学应用程序往往由于其代码量和通信量巨大,对计算力和数据传输性能要求非常高,如何利用高性能计算技术,设计开发高效的并行计算模型与算法,充分利用不断提高的硬件系统的计算能力来解决科学与工程计算问题具有重大意义,清华大学计算机系副教授都志辉团队采用PPMLR-MHD方法并利用GPU加速运算处理有效解决了实时空间天气预报问题。

本文为都志辉教授的主讲实录,共计7721字,预计14分钟读完。在浏览主讲正文之前,可以思考以下三个问题:

-如何有效的将大量代码从CPU移植到GPU上,并保证运算结果的一致性?

-如何准确找到对性能影响特别关健部分的代码并进行优化?

-如何优化GPU上代码的性能以提高计算资源利用率?

主讲环节

都志辉:大家好,我叫都志辉。很高兴能够在这里跟大家一块交流我们在GPU加速空间天气预报方面的一些工作,我今天分享的主题是《GPU加速的实时空间天气预报》。

今天的内容分为三个方面:

1,空间天气预报的概念和意义;

2,空间天气预报的计算难点及挑战,这里会介绍一些我们做大型科学工程计算时在GPU移植上获得的一些经验,以及如何在GPU上进行优化;

3,利用GPU实现加速空间天气预报采取的方法

空间天气预的概念和意义

首先介绍一下空间天气预报的概念,空间天气预报可以类比地面的天气预报。比如刮风下雨对我们有什么影响,只不过空间天气预报是在更远的地方,主要研究太阳与地球的相互作用对地球外部环境的影响。

在地球上,主要是刮风、下雨对我们有影响;而在空间是太阳风,即太阳发出很强劲的带电粒子,会对地球造成非常大的影响。

对地球的影响有很多方面,对通信、雷达信号、发射卫星和电视转播等都会造成影响。根本原因是这些带电粒子干扰了地球的外层空间环境,从而对地球上的各种通信网络、电力网,甚至是臭氧层都造成影响。

如果没有地球的磁场把太阳给推开,那么地球上就不可能有人类,太阳粒子会直接把人类杀死。进入地区的太阳例子就像一个很强的X射线一样进入了人体,相当于人体接触了大量的辐射,会对人体的健康造成非常有害的影响。

空间天气预报的计算的难点以及挑战

如果把问题抽象起来,就是一个磁场和一个带电的离子流之间的相互作用,在数学上可以用磁流体动力学方程来进行描述。

这个方程建立起来之后是非常复杂的,有很多不同的解法。其中我们的合作方(空间科学中心)提出了一种叫PPMLR的方法来解磁流体动力学方程。

他们用了十几年的时间来发展这个方法,而且也已经形成了一个比较稳定的程序。这个方法的好处在于可以得到比较高精度的仿真计算结果,但计算量、通信量也非常大。

因此目前空间的空间天气预报面临的主要挑战跟很多大规模科学工程计算问题一样,都是如何才能算得更快,如何才能够完成实时计算的问题。对于实时预报问题,空间科学中心告诉我们理想的情况是在40到50分钟之内把全部的结果仿真出来。用原来的程序完成这个计算需要2-3个小时,所以我们面对的问题是如何把2-3小时的计算时间压缩到1小时之内。

他们的方法非常好,所有的科学工程计算问题都首先得有数值计算方法,然后才是实现。这里就不进行仔细地介绍了,当然一个好的数值计算方法也是非常重要的,这里我们重点讲一下如何在GPU上进行程序代码的实现和优化。

这是SCALE CHALLENGE AWARD证书,是我们把空间科学中心的大型应用程序放在GPU集群上进行性能优化,实现了实时的计算,我们把这个成果参加了一个比赛,在IEEE的可扩展计算委员会经过答辩评审后授予了这个证书。

如果大家也做了相关的事情,比如做了科学工程计算,解决了一个很实际的问题,效果也非常好,也可以试着去参加一下这个竞赛,在CCGRID的会议投稿期间,可以把你的工作写成类似于论文的东西投过去,同时需要指明你是要参加这个竞赛。他们首先要进行评审,评审之后还要去进行答辩,答辩完了之后再评价,最后挑选出他们认为做得比较好的项目,并授予相应的奖项。

这是空间科学中心已有程序的大概计算流程。这个程序分为两部分,一部分是计算太阳风和地球磁层之间的相互作用;另一部分是计算地球的电离层。将这两部分相互计算的结果最后综合到一起形成总的仿真结果。

计算量比较大的是太阳风和磁层的相互作用这一部分,而右边相对来说计算量很小,所以在他们的程序里面就出现了跟一般的并行不太一样的地方,就是用一个MPI进程来专门计算电离层这部分的任务,而其他所有的进程来计算太阳风和磁层相互作用这一部分。

我们在GPU上加速以及原来在MPI并行的也主要是左边这一部分。大家可以看到有sweepx、y、z三个过程,同时最主要的计算量也是在这三个过程里面,而我们做的事情就是对这三个过程分别在GPU上又进一步进行加速。

在具体介绍我们怎么优化这个程序之前,先大概讲一下我们在优化这个程序时获得的一些体会。在优化这个程序的同时,我们还做了一个大型双黑洞仿真的MPI并行程序,将其放在GPU集群上进行了性能优化,这些大型应用程序的代码比较多,一般有几十万行,而这个程序只有几万行。

通过把这个大型的科学工程应用程序进行性能优化,在GPU上进行移植等,完成这件事情得到的体会是它和一般的小程序Benchmark有非常大的不同:

1,源程序的代码量不一样,一般大型程序都在几万行以上,而一般Benchmark只有几百行,最多是一两千行。对于Benchmark这样的小程序来说,由于代码量很少,因此在优化的过程中,你就可以非常仔细地对每一条语句进行研究。而对于十几万行代码的大型程序,这样操作的工作量就非常大。这也是大型程序性能优化在GPU上进行移植面临的一个非常大的问题;

2,控制逻辑和数据结构不同,由于大型应用程序代码量大,必然导致其控制逻辑和数据结构与benchmark非常不同,对于大型程序来说不是那么简单就可以搞得明白,而Benchmark可以很容易把整个代码做的事情搞得一清二楚。

我们在将空间天气预报程序在GPU集群上进行移植以及将更大的双黑洞仿真程序在GPU上的移植时,发现一个相同的规律,就是代码移植本身并不是太难,对于一个对CUDA编程非常熟练的人来说,只要用一个月的时间就可以把上万行甚至几十万行的代码移植到GPU上,而难的是将代码移植到GPU上之后,一开始运行就会出现这样那样的意外与错误,就算你把这些问题都解决了之后,也会发现一个非常难解决的问题,就是在GPU和CPU上运行的结果不一样。

我们的学生,他们之前用GPU用得很熟,算是CUDA编程高手,所以一开始对这么大的程序代码是非常有信心的,觉得移植到GPU上没有问题,事实上一开始的代码移植也的确是这样,但问题是将代码移植到GPU上之后,不能保证这些代码是正确的。

这两个大型程序的移植都出现了相同的情况,我们调试错误所花的时间要远远超过代码移植所花的时间,甚至有些学生调试到最后都已经完全崩溃了,感到非常绝望,并且不知道该从哪下手了,虽然他们一开始都觉得自己是GPU编程的高手,但是对于大型程序中出现的各种莫名其妙的错误都感到束手无策。

利用GPU实现加速空间天气预报采取的方法

下面我来讲一下我们是如何在GPU集群上移植代码的。

首先这个代码不是串行代码,前面已经介绍过,它是空间科学中心花了多年的时间,用MPI在CPU集群上可以稳定并行运行的并行代码。

这个代码有些特殊的地方,就是实际应用程序和Benchmark不同,用Benchmark实现的某些方法和手段,在实际应用程序中,经常发现是不能解决问题的,原因就是大型应用程序总有很多特殊的情况,而这些特殊的情况会让那些从Benchmark里面总结的经验变得不可行。

比如上图这个程序,它对网格的划分比较特殊,它不是均匀的划分,大概意思是,越靠近地球的地方相对来说越稠密,而离地球越远的地方相对稀疏一些,这也是所有的网格划分中基本的指导思想。

这个算法的实现还有一个特点,在对空间网格划分以后,对各个坐标轴的约束也不一样。它需要专门拿出一个进程来仿真电离层的计算,而其他的进程用来仿真太阳风与地球的相互作用,为了让地球都处在特定的位置,就要求它坐标轴的y和z轴的进程所划分的块数必须是奇数。

如上图所示,以地球为中心进行156*150*150的空间网格划分,在这个基础上可以并行的进程数并不能随便选,而只能是某些特定的值,比如4,28,37等这些值。

这里我跟大家介绍的这些特别细节的内容,我觉得大家可能没必要去掌握。我想从中说明一件事情,就是对大型应用程序来说,往往会有很多特殊的要求,而这些特殊的要求可能会对你的性能优化或者以前在别的地方积累起来的一些经验造成很大的障碍,在这样的场景下已有的经验可能并不适用或者说效果并不好。

同时这也说明一件事情,我们经常说GPU的性能非常高,有成百甚至上千倍的加速。这个加速往往是在Benchmark理想情况下得到的,而在真实的大型应用程序上,如果想得到非常高的性能加速,一般来说是特别难的,难的原因就是大型应用程序本身不管是CPU还是GPU并行都不是特别理想,与理想情况不太吻合,因此给并行和优化加速制造了非常多的困难和障碍。

我们把程序在GPU集群上进行移植和优化的过程分为两步:

第一步,是一个摸索的过程,在MPI并行程序的基础上,对代码进行处理,其中代码分两部分,一部分是计算量比较少的,用一个MPI进程在CPU上计算就可以完成。另一部分是计算量比较大的,就是太阳风与地球磁层相互作用的那一部分,需要将它全部移植到GPU上进行计算。

我们之所以把这么多的代码全都移植到GPU上,而不是将一部分放在CPU上一部分核心的sweepX、Y、Z三个过程放在GPU上执行,主要是因为如果你把一部分代码放在CPU上,一部分放在GPU上,计算是可以大大的加速的,但是部分代码放在CPU上会导致CPU和GPU之间存在大量的数据交换,如此一来,之前得到的加速计算的效果都会被这些数据交换的开销所淹没。

正是因为这个原因,所以我们才不得不把这么多的代码放在GPU上进行移植,尽管有些代码并不是计算密集部分的代码或者也不是一些瓶颈的代码,这个问题在很多GPU性能优化上都会存在的,其根本原因是因为目前在GPU上执行代码,得先把它需要的数据放上去,放数据是需要很大的开销的。因此为了避免反反复复地在CPU和GPU之间来回拷贝数据,我们才把更多的代码放到GPU上来执行。

对于这个过程,像刚才说的,一开始看了这个代码之后很有信心,认为移植上去不会有问题。然后不到一个月,就把原来在MPI在 CPU上并行的代码全都移植到GPU上去了,但是到后来一测试,就发现会出现各种各样的错误。开始我们还是比较有信心的,也很快地把很多错误都搞定了。但是后面不断测试,就不断会有新的错误出来,一直到最后,就发现有些错误根本就不知道是什么原因导致的。

在这种情况下,我们不得不反思,移植上去的代码可能是一个失败的尝试,换句话说我们根本没有办法保证这些代码是正确的,也不可能通过调试把每一条的错误都找出来。在经过思考以后,我们就提出了第二版移植方案,其中的基本想法就是分步走,不要想着一步就把原来的代码直接移植到GPU上,这也是从CPU程序向GPU移植经常会犯的一个错误,一般将代码直接直截了当地移植到GPU上往往都是有问题的。

因为CPU程序的执行流程和计算方式与GPU有很大的不同,包括它们的数据结构和流程都不一样。如果将代码直接移植到GPU上,它的性能是会受影响的,或者你要改动非常大才能满足性能要求,所以这种Straight forward移植是有很大问题的。而我们认识到这个错误以后,我们的方案是不把代码直接移植到GPU上,而是先分析原来的代码,如果要准备移植到GPU上,什么样的一个代码结构与数据结构更有利于移植。

第二步,先把数据结构和代码结构分别进行重组,但都还是用C或者用Fortran在CPU上进行编程,比如循环结构和数据结构,CPU上的哪些数据结构跟GPU的特性有关联,相关联的结构重组后,将代码移植到GPU上后才不会造成混乱的局面。

把这些代码捋顺以后,看上去这些代码看上去还是CPU上的代码,但是它是按照GPU上的逻辑,为了能在GPU上执行的逻辑和数据组织方式去重写的。通过调试,确保这部分代码是正确的。

在这样的基础上,我们才将代码往GPU上进行移植。刚才也说到,代码量太大,为了避免CPU和GPU之间频繁的数据交换,需要将很多代码都转移到GPU上,很明显这工作量太大。但是如果特别影响性能的那部分核心代码又不是特别多,我们可以采取引入Open ACC的方法。

引入Open ACC的目的,是对于那些我们不得不移植到GPU上的、但即使这部分代码经过优化以后也不可能对性能有非常大的提升的不太核心的代码来说,我们可以通过Open ACC加一些简单的注释语句,将它变成能在GPU上执行的代码。而对于那小部分对性能影响非常大的代码,可以专门用CUDA来进行重写。

通过这种方式,就达到了以下的效果:

第一,通过使用Open ACC的方式,使得工作量大大降低,而且不容易出错,降低了出错的概率;

第二,通过手写对性能影响特别关健的部分的代码,用CUDA来重新修改、优化,可以大幅度提高最终代码的性能。

以上说的就是我们在整个移植过程中总结出来的一些经验,就是通过这种方式,既避免了CPU上大量的代码移植到GPU上重写的繁琐过程,又能保证对性能没有造成很大的影响。

因此大型应用程序在GPU或者GPU集群上的移植和性能优化,它的第一个难点并不是性能的提升,而是如何有效地控制代码移植的复杂性。

我们采取的办法就是,首先经过代码的重组,构造一个中间版本,而这个中间版本跟原来的程序一样,都是用C或者用Fortran语言来写的,但是它比原来的程序更容易移植到GPU上去,因为它是按照GPU的逻辑来组织、重写的,但它又是CPU的代码,这样的中间代码就容易符合GPU上的逻辑;第二步就是采用Open ACC方法,避免了手工重写大量与性能无关的代码。

还有一个难点,对大型程序来说,要找到真正影响性能的地方往往也是很难的。有时候可能自己感觉用shared memory能提高性能,但是用了以后效果可能不一定真的好。我们在做用GPU来加速引力波数据处理的时候,就发现如果用shared memory来进行优化,还不如用atomic操作最终得到的效果要好。

NVIDIA的编程指导中讲了很多通用的GPU性能优化的方法,比如提高occupancy,使用shared memory等这些手段,但是在真实的应用场景下,这些优化手段并不是用了就一定能够得到好的结果,有的时候用了可能还会带来另外的负面影响,从而导致整体性能没有提高,反而可能有下降的情况。

这就是我想说的另外一个难点,如何才能找到真正可以影响性能的优化点,这个是非常难的,因为大型程序它存在很多会影响性能优化的因素,可能计算性能提高了,但是通信的开销大了,性能也不一定好,或者是并行计算提高了,但数据访问的次数提高了,开销大了,也有可能会影响性能。

下面具体讲讲我们对大型应用程序,再深入到具体的GPU代码编程大概用哪些优化手段和方法。

第一个比较简单,对每一个MPI进程都调用一个CUDA核心来并行实现,就是把原来CPU上一个进程的执行代码变成一个CUDA的并行执行(代码)。

另外一个方面是如何提高访存的效率。由于我们在具体应用中,在解方程的过程中,几乎没有数据的重用,因此我们没必要再用shared memory,并且在这时候用,并不能带来性能的提升。而能够提高性能的,就是想办法重新调整数据的结构,让数据访问时局部性高一点,一次可以读入更多的数据,以这种方式来提高数据访问的效率。

这里我想再强调一下,根据我们在GPU上进行性能优化的工作体会,在大部分情况下,将CPU的代码移植到GPU上的时候,数据结构都需要进行重组,如果不进行数据结构的重组,往往对性能会带来负面的影响,而且GPU上的性能优化有很大的程度是在优化对数据的访问,就是如何才能更有效、更快地得到一些数据。

还有一个优化的方面就是使用Stream(流),我们编写了很多不同的核心,通过这些核心把它们形成Stream,由于GPU的计算能力很强,通过Stream这种方式,让它们可以共享GPU资源,提高GPU资源的利用率。

这也是一种比较通用的提高GPU上代码性能的方式。由于现在GPU的能力越来越强,单个的Kernel不可能充分地利用GPU的资源,因此在很多情况下,Kernel都不能把GPU上的资源用满,这时利用Stream的优化方法去提高GPU资源的利用率也是非常重要的。

上图是对通信优化的结果,我们对大型应用程序进行通信优化,最主要的一点,也最有效的一点就是用GPU NVLink的特点进行优化,就是GPU和GPU之间可以不通过CPU直接进行数据交换。我们在测试以后发现这种方式对性能的提高非常大。

这个应该是目前世界上最大的GPU集群,它是由美国橡树岭国家重点实验室在Titan GPU集群上测试的结果。总的来说与在CPU上MPI并行版本相比,性能提高了2到3倍。

以上就是我要介绍的内容。谢谢大家。

Q&A环节

问题

李敏-中科院软件所-高性能计算与并行计算方向博士

1,对一个大型应用程序,在GPU上并行加速的时候,一般的步骤都有哪些?可否分享一下您的经验?

2,对应用进行并行加速这个工作,比较偏工程一些,如果对于博士从事这个方向的同学来说,如果想发论文,一般从哪些方面着手,找一些创新点?因为一边是具体的应用领域,一边是计算机领域的并行优化,其实是一个交叉的方向,如果权衡这两方面?

都志辉:

1,其实我在前面讲的时候也大概说了一下,对一个大型应用程序在GPU上并行加速的时候,不要直接就往上移植,最好是先好好分析一下这个程序,先把问题、结构和流程搞清楚。然后再把你往GPU移植所要采取的手段和方法也想清楚,之后再动手。我们一般说性能优化,包括三个方面:应用、算法和硬件体系结构。那么移植也差不多,先把应用搞明白,再去移植,然后你再考虑如何用算法来实现,最后再结合GPU和硬件来做这件事情。

2,这个问题比较通用,干我们这一行的很多人都有同感。我要给你一点建议是:其实对于我们一开始做优化的人来说,可能上来的想法是我如何把性能提高就行了,如果你想在博士期间做这件事情,想发论文,想找创新点,我觉得不是一上来就想着怎么去优化的问题,而是要先多想想问题的本质在哪里,别人是怎么做的,为什么不行,你打算怎么做,这个方法为什么可以或者你觉得他为什么有可能不行,在优化之前或者是优化过程中,如果你把这些问题都弄明白了,那么你动手有了优化结果后,也就意味着你写的文章或者是你的创新点也比较容易找到了。

我们之前也犯过毛病,那时候我们觉得反正有这么一个任务,想着快点完成就行了,然后这样弄完之后,你只能说我把这性能提高了多少倍,但是有很多问题都没有想清楚,比如你这么做比别人做有什么好处,为什么不用别的方法,有没有更好的方法,你这个方法的极限和瓶颈在哪等,可能都没有想清楚,所以如果想发论文或者是找创新点,那就在动手之前先多考虑考虑,多问些问题。这就是我的一点建议。

对于权衡这方面,首先你说的非常对,如何去权衡目前确实没有特别好的办法,只能硬着头皮去做,在做应用领域时就得先把应用问题搞清楚,如果搞不清楚就不可能把这个优化做好,这是我的一点体会。

本文为转载内容,授权事宜请联系原著作权人。