なぜGPU移植が必要か?

近年電力効率の良さからスパコンセンターなどでGPUの採用が続いている。以下はTOP500におけるGPU加速器を導入したスパコンのシェアである。2012年から徐々に伸び2025年の段階ではおよそ50%の機関でGPUが利用されている。さらにそのシェアのほとんどはNVIDIA製のGPUである。GPUへの移植は、将来の大規模数値計算を見据えた際に、ほぼ必須の技術となりつつある。

image.png

並列計算の一般論

ここでは説明を簡単にするため計算機は演算をする装置であるコアとメモリとネットワークからなると考えよう。コアを並列に動かし、大量のデータを分割処理することを並列計算と呼ぶが、コアはハードウェアおよびソフトウェアの制約により、全てのメモリにアクセスできるわけではない。**GPU並列化の本質的な難しさは計算の並列化そのものではなく、メモリを意識したデータの取り扱いである。**まずはこの点をより詳しく解説しよう。

本講義では、研究者がコードを書く上で必要となる「ソフトウェアから見た並列計算機の構造」に焦点を当てる。したがって、以下で示すハードウェア構成は概念モデルであり、実際のマイクロアーキテクチャや物理構成とは一致しない場合がある。

コアからみてどの範囲のメモリを参照できるのか?

並列計算を考える際に最も重要なのは「どの計算コアからどのメモリが直接見えるか」という点である。ハードウェアとしては同じメモリが参照できる範囲をノードと呼ぶ。そのデータが必要な際、明示的な通信が必要な場合はノード外である。つまり、以下の図だとnode Aの中のcoreからはnode Bのメモリは読めない。

image.png

それを前提とした上で、ソフトウェア(OpenMP, MPIなど)がどのようにメモリを参照するか見ていこう。

OpenMPの場合(共有メモリ並列)

OpenMP分割基本単位をthread(スレッド)という。以下の図のようにコアをthreadが分割して使っている。各スレッドはメモリのいかなる場所も参照できる。ハードウェアの制約上OpenMPはノード内に限られる。

image.png

これは便利ではあるが、バグに気づきにくいという特性もある。典型的なバグとしてはthread Bでは更新前の値が必要だったのにthread Aが更新してしまった後の値を用いてしまうなどである。この場合は値が似ていたりするので、バグに気づきにくい。

OpenMPではメモリが共有されているだけに、むしろthread ごとに別々にメモリを使用したいときに、明示的にそれを宣言する必要があることに注意する(private)。こう書くと難しく聞こえるが、変数をprivateにするかどうかは簡単に判別できる。つまり、ループのindexのついてない変数で、ループ内で更新されるものをprivateにすれば良い。

以下の例でいうと a,bである。indexの iがついておらず変更されている。

!$omp parallel do private(a,b)
do i=is,ie
	a = x(i) + y(i)
	call func(a,b) ! b is updated
	z(i) = b
enddo
!$end omp parallel

実際のプログラムではこのルールで判別できない場合もでてくる。例えばfuncの内部でindexがついた変数を暗に参照するなどである。あるいはindexはついてないが、意味的にはindexがつくべき変数などを参照するなども問題が起きる。この点については、OpenMPの技法で解決するよりは簡単に判別できるようにコードを書くべきである。簡単にprivateか判別できない場合は変数の設計がよくない。複雑なOpenMPコードはバグの温床になりやすいので注意してほしい。