新年明けましておめでとう。
2017年を振り返ると自分が社会人1年目だったということもあり、四苦八苦しながらも成長できた1年だった。まだまだ卵がヒナになったくらいだが、2018年は去年やりたかったけど出来なかったことにチャレンジしてみたい。(^ω^)
新年一発目は去年の末から読み進めている「GPUを支える技術」の続きだ。
とても面白い本なのでじっくりと読んでいるが、他に読みたい本も積んでいるので少し急ぎ足になってしまうかもしれない。(´Д`)
4章 GPUの超並列処理 [前半]
4.3 AMDとARMのSIMT方式のGPU
ここではAMD GCNアーキテクチャとARM Bifrost GPUについて取り上げている。
AMDやARMのGPUについて扱っている他の本を見たことがないので、とても貴重なのではないかな?(^ω^)
AMD GPU GCNアーキテクチャ
AMDは2012年にGCN(Graphics Core Next)という、NVIDIAと同じSIMTを採用したアーキテクチャを導入した。(それまではVLIWというISAだったらしい。)
AMD GCNアーキテクチャはコンピュートユニット(CU)とローカルデータシェア、グローバルデータシェアというメモリを持つ。グローバルデータシェアをつかうと、全ての異なるCUの全てのスレッド間でデータの共有を行うことができる。さらに、それはGPUチップ内臓のSRAMでできているため、デバイスメモリと比べて高速である。
また前章で述べたように、AMDのGPUは64つのスレッドを同時に実行する。CUは64個の32-bitの浮動小数点演算機(FP32)と24-bitの整数演算器を持っているが、DPはなく、代わりにFP32で複数サイクルかけて計算できるようになっている。
以下にNVIDIAとの各要素の比較を表にして示す。(マークダウンで書いたテーブルがBloggerでは何故か正しくレンダリングされない。。(;^ω^))
項目 | NVIDIA | AMD |
---|---|---|
計算コア | SM | CU |
スレッド間共有メモリ | シェアードメモリ | ローカルデータシェア |
高速グローバルメモリ | なし | グローバルデータシェア |
グローバルメモリ | デバイスメモリ | デバイスメモリ |
スレッド同時実行数 | 32(ワープ) | 64(ウェーブフロント) |
倍精度の計算 | 専用DPコアを持つ | FP32を複数サイクル使う |
CU: 4つのSIMDと16の計算コア
1つのCUはGCNアーキテクチャは4つのSIMDで構成されている。内部に16個のプレディケートビット付きの演算器を持つ。これらは全て同じ命令を実行する。各SIMDはそれぞれ独立したプログラムカウンタと命令バッファを持っており、異なる命令を実行できるようになっている。
本書では、SIMD内の16個の演算器でそれぞれ4回連続して実行することで、64スレッドを処理しているのではないかと予測している。1つのSIMDが処理すべてスレッドは16であるのに対してレジスタは64スレッド分あり?、毎回切り替えて計算を行うことができる。
出展: 「GPUを支える技術」,p147,Hisa Ando,2017
AMD GPUのシリーズの名前
AMD GPUの各要素の名前は少々ややこしいのでここにまとめておく。
-
命令アーキテクチャ
初代のGCN Generation1を始めとして、2と3を経て現在Generation4である。 -
GPUの開発コードネーム
Southern, Islands, Volcanic Islandsなどのアイランドシリーズの名前がつけられている。2016年発売のPolarisアーキテクチャは正式にはGCN Generation4であり、Arctic Islandsとも呼ばれる。 -
個別チップの開発コードネーム
PolarisやVegaなど構成の名前がつけられている。
CUは1つのSIMDで1つの命令を計4回実行して64スレッド全てを処理しつつ、
次の命令は違うSIMDで計算するということだろうか。
64つのデータを計算するのを4分割しているから、同一スレッドにおけるレジスタアクセスのレイテンシを軽減できるが、1スレッドの命令数が4の倍数でないと効率が落ちるとか?(゜.゜)
スマートフォン用SoC
スマートフォンのバッテリー容量は大きいものでも3,000mAH程度なので、電圧を3.7Vとすると11.1WHのエネルギーしかない。10時間の利用を想定すると消費電力は1.11Wに抑える必要がある。
\[
\begin{align}
3000 [mA/h] \times 3.7 [V] &= 11.1 [W/h] \\
11.1 [W/h] \div 10 [h] &= 1.11 [W]
\end{align}
\]
本書では短い電池寿命を想定しても、一般的なモバイル端末上でGPUが利用できる電力は3 〜 4Wと見積もられている。
QualcommのSnapdragon 820に用いられているAdreno 530 GUPは16-bitの半精度浮動小数点で256ops/cycleであるため、NVIDIA GP100と比較すると約1/50のハードウェア量となり、動作周波数も1/3の500MHzである。さらに、製造プロセスが28nmなので16nmより2倍ほど消費電力が多いと仮定すると、GP100との比較から約4W程度の消費電力になると考えられる。
\[
\begin{align}
&W_{GP100} = 300 [W], \,\,\,\, \alpha_{\frac{28nm}{16nm}} \approx 2 \\
&W_{adreno530} = \frac{1}{3} \times \frac{1}{50} \times \alpha_{\frac{28nm}{16nm}}\times W_{GP100} \\
&W_{adreno530} \approx 4 [W]
\end{align}
\]
スマートフォン用のSoCは消費電力が公式に発表されてないため、上記ではわざわざ概算している。
ARM Bifrost GPU
2016年8月に発表されたARMの新GPUアーキテクチャ。SIMT方式を採用している。
GPU内部には複数個のシェダーコアがあり、GPUのファブリックを経由してL2キャッシュセグメントに接続され、更にその先のメモリにつながっている。他には後述するタイリング方針期の描画を行うユニットやドライバソフトウェア、ジョブマネージャ等がある。
ちなみに、ARMはCPUやGPUのライセンス提供のみを行っているので、搭載するシェダーコアの数などは製造元が自身の製品に合わせて判断する必要がある。
シェダーコア
シェダーッコアには3つの実行エンジンがあり、それぞれがFP32の積和演算器を持っている。各シェダーコアはCUDAコア12個分に相当し、最大搭載可能数である32個を搭載すると、384CUDAコア相当になる。演算エンジン以外も以下ようなのユニットが搭載されている。以前述べたように同時実行スレッド単位数は4である。
- メモリロード/ストアユニット
- 属性ユニット
- フラグメントシェーディングユニット
- タイル方式の描画ユニット
- Zバッファやステンシルの処理を行うユニット
出展: https://www.anandtech.com/show/10375/arm-unveils-bifrost-and-mali-g71/4
上記のCUDAコア数はGeForce GTX 1050Ti(768CUDAコア)のピッタリ半分だが、
1050Tiは75Wを消費するので単純計算だと32Wの消費電力になってしまう。
また↑はJetson TX2搭載モジュール(256CUDAコア)よりも多い。
32個搭載のBifrostがどれくらいの消費電力を消費するのか気になるなー(゜.゜)
タイリング
モバイル向けGPUは利用可能メモリの小ささとバンド幅の不足から、タイリングという描画方法がよく用いられる。タイリングは1つのイメージを32 × 32ほどの大きさで切り出して内部メモリに保存し、その部分だけに計算処理を行って描画する方法である。これは、大きなサイズのバッファが使える場合には効率がおちる。
GPUの使い勝手を改善する最新の技術
GPUの最も扱いづらい点の1つにCPUとGPUのメモリが別れていることがあり、このセクションではその問題の軽減するための技術について述べている。なお、CPU内蔵GPUやSoCの場合はこの問題は発生しない。
ユニファイドメモリ
一般的にCPUとGPUのメモリへの要求は異なり、CPUは容量の拡張性、GPUの場合は高いバンド幅を必要とする。しかし、この2つを同時に満たすメモリは存在しないので、それぞれ異なるメモリを使用しているというのが現状だ。この場合、双方のメモリ間でデータのコピーや同期が必要だが、その過程で以下ような問題があり、GPUの扱いを難しくしている。
- 該当データが含まれたページがスワップへ退避されるのを防ぐ必要がある
- ディープコピーの問題(1章で述べた)
- 計算時間をオーバラップさせて転送時間の隠蔽する必要がある(ダブルバッファなど)
上記の解決策として、NVIDIAはユニファイドメモリという手法を開発した。これは統一したアドレス空間の中でCPUとGPUに同じ仮想ページテーブルを割り当て、Vビットというフラグによってそのページエントリの処理を切り替える手法である。Vビットはそのデバイス(CPU or GPU)のメモリにあるデータが最新であるかどうかを示しており、CPUもしくはGPU上の片方は必ずValidでもう片方はInvalidになっている(最新は常に1つしかないため)。この仕様により、GPUがInvalidのページエントリにアクセスしようとした場合、割り込みを行って処理を中断する。次に、CPU側のページテーブル側のページエントリのVビットをInvalidに設定して、先頭アドレスをDMAに設定してGPUのデバイスメモリに送信する。最後にGPU側の該当するページエントリのVビットをValidにすることで、割り込みから復帰したGPUはデバイスメモリから該当ページエントリを読んで処理を続行できる。これらはCPU側からInvalidのページにアクセスした場合も同様に動作する。
出展: 「GPUを支える技術」,p154,Hisa Ando,2017
この手法はCPUとGPUが同じページテーブルを持つため、ポインタへの対処を簡単にする(ディープコピー問題も解決)。ただし、一度に転送されるページエントリのサイズは4KiBなので、計算に利用するデータ量が少ない場合は効率が落ちる。また、コピーは要求の都度行われるので、明示的にダブルバッファを用いなければ転送時間を隠蔽することができない。
SSG
AMD Polaris GPUはSSG(Solid-State Graphics)という機能を持っている。これは、GPUボードに1TのSSDを搭載することでデバイスメモリを格段に増やすというものだ。
SSDの速度はGDDR5の100倍以上遅いが、大量のデータをCPU側のメモリから毎回コピーする必要が無いため、利用用途によっては高速化が期待される。
細粒度プリエンプション
割り込みを受けた命令が、その終了後に再度処理を開始することをプリエンプションと呼ぶ。このプリエンプションはCPUではあまり問題にならないが、GPUの場合は保持しているアーキテクチャ状態の多さから、その退避/復元が困難になるという問題がある。現にPascal GPU以前では、1つのスレッドブロックに含まれる全てのスレッドが終了した時点でしか割り込み受け付けることができなかった。しかし、3DゲームやVR等によりリアリティが必要とされるにつれて、より粒度の高いプリエンプションの要求が出てきた。これらを踏まえ、Pascal GPUではスレッドの各命令の切れ目で割り込みを受け付けられるようになっている。これは処理の切り替えを速くする以外に、デバックを格段に楽するという利点もある。
エラー検出と訂正
3章で説明したように、フリップフロップやメモリセルなどの記憶回路にはエラーが起こることがある。これはだいたい\(10^{-13}\)-bit/時間程度であり、1MBのキャッシュを持つプロセッサだと100万時間に1回程度で発生する。これらのプロセッサを組み合わせたスーパーコンピュータのようなシステムだと、約1時間に1回エラーが起こるようなケースもあるので、エラーの検出は必須である。
パリティチェック
1ビットのエラーを検出する最も簡単な方法はパリティチェックである。これはデータ内の1のビットの数を数えて、それが偶数の場合は0, 奇数の場合は1のビットを送信側で元の値に付加する。受信側では再度1の数を数えることで、伝送時に1ビットの反転が起こったかどうかが分かる。もちろんだが、この手法の問題は2ビット以上のエラー検出ができないことである。
ECC
エラーの検出だけでなく、訂正ができるコードのことをECC(Error Correction Code)と呼ぶ。数あるECCの中から、その代表的存在であるハミングコードを紹介している。
ハミングコード
以下の図のように複数のビットを持つベクトル(行)がいくつか与えられ、それらのハミング距離(この呼名も出ていない)がどれも3以上の時、1ビットのエラーをする修正できる。このような符号化法のことをハミングコード(または(7,4)ハミングコード)と呼ぶ。
\[ H={\begin{bmatrix} 1&0&1&1&1&0&0\\ 1&1&0&1&0&1&0\\ 0&1&1&1&0&0&1 \\\end{bmatrix}} \]
右端の単位行列のような部分は、左端のデータビットを用いたXOR演算で計算することができ、その結果から反転したビットの位置を知ることができる。ビットの位置がわかれば、後はそのビットを再度反転させれば元の符号と同じになるため、これを復元と呼んでいる。本書では簡略化のため厳密な定義は割愛している。
SECDEDコード
ハミングコードは1ビットより大きいエラーが起こった際に、訂正を誤ってしまうという問題がある。そこで、上記に1-bitの情報を追加して2-bitまで誤りを検出できるようにする方法にSECDEDがある。SECDEDは64-bitデータの場合、8-bitのチェックビットが必要なのでその符号長は72-bitになる。NVIDIAの科学技術計算用のGPUもこの72-bitのSECDECコードを用いている。
CRC
n-bitのデータを\(a_{n-1}, a_{n_2}, \ldots , a_0\)の\(a\)が0または1の値を取る係数とする多項式を\(X\)は以下の形で表される。
\[
X = a_{n-1}x^{n-1} + a_{n_2}x^{n-2} + \cdots + a_{2}x^{2} + a_{1}x^{n1} + a_0
\]
この時、\(X\)を他の多項式で割り、その余りをチェックビットとするような符号化法のことをCRC(Cyclic Redundancy Check)と呼ぶ。
CRCは、m-bitのチェックビットをつけた場合、(m-1)-biti以下の連続したエラーを検出することができる。通信路でのビット誤りは連続して起こることが多く(バーストエラー)、CRCはそのような範囲集中のエラー検出に有効である。
Bluetooth等では \(x^{16} + x^{12} + x^{5} + 1\)という16-bitのチェックビットが用いられる。また、PCI Express 3.0では32-bitの\(x^{32} + x^{26} + x^{23} + x^{22} + x^{16} + x{11} + x{10} + x^8 + x^7 + x^5 + x^4 + x^2 + x +1\)の32-bitが利用されている。PCI ExpressのCRCの場合は3-bit以下のエラーと31-bit以下のバーストエラーを検出できる。CRCは高いエラー検出能力を持っているが訂正を行うことはできないので、ACK信号などと併用して利用される。
GPUデバイスメモリのECC
GDDR5 DRAMには上記で説明したチェックビット用の追加ビットがない。GDDR5は一度の通信で、32ビットのデータ幅で8バースト転送するので合計が256-bit/cycleになる。従って普通にチェックビット8-bitを64-bitに付加とすると、\((32 \times 8) \mod (64 + 8) = 40[bit]\)が余ってしまう。そこでNVIDIAは9サイクル連続でデータを読みだし、余りのビットが出ないようにして上記の計算を行っている。AMDは同様の手法において、128-bitに9-bitのチェックビットを付けて対応している。
ちなみに、HBMではチェック用ビットを内蔵しており、上記のようなハードウェアは必要ない。
おそらく、↑の9は72と256の最小公倍数なので、最も短いデータ長で効率が最大になる数字だということなんだろなー(゜.゜)
ただ、検出の最小単位が72-bitに対して転送単位は256-bitだし、幾つかの72-bitは複数の転送にまたがっているから、誤りを検出した際の訂正の効率は落ちる気がする。256-bit × 9のバッファを全て再送するのか、該当の72-bitを含む部分だけを再送するのかわからないけれど、後者は該当箇所のアドレスなどを計算するハードが必要で少し複雑になりそう。
この誤り検出と復元に関しては本書だけでは少し不足だと感じたので、どこかの機会できちんした定義、計算方法、効率的なハードウェア実装を含んだまとめをつくりたい。
なお、この部分の補足は情報理論などの理論書でも良いが、「コンピュータの構成と設計 第5版 下巻」でも説明があった気がするので(おそらく)、コンピュータ的に理解したい人はそちらを参照すると良いと思う。
まとめ
4章ではGPUのハードウェアが如何にして多数の演算機を配置し、それらを並列で動かすことで高い演算性能を出しているのかを説明していた。近年の殆どのGPUはSIMT方式を採用しており(ARMもこちらにシフトした)、この方式が現在の主流といえる。
なお、GPUとCPUのメモリはその要求の違いから分離しており、問題となっている。NVIDIAはこの問題に対して、別々のメモリをあたかも同一の空間として扱えるようにしたユニファイドメモリという手法を開発した。
最後に、エラー検出と訂正について少しだけ触れた。
楽しみにしていただ章だけあって、とても良い内容だった!
特に各社GPUの設計思想の違いや計算性能、消費電力が分かったのは大きい。
また、ユニファイドメモリは今まで名前だけしか知らなかったので、簡単にだがその内容を知ることができてよかった。
次は5章だが、4章と同様に楽しみにしていた章なので速く読みたい。( ^ω^)
本ブログに対するご意見や間違いの指摘などがありましたら、ぜひコメントください。TwitterでもOKです。皆で議論を深めて行けるような場にしていきましょー。
コメント
コメントを投稿