スキップしてメイン コンテンツに移動

GPUを支える技術読み始めた 第4章 [後半]

新年明けましておめでとう。
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スレッド分あり?、毎回切り替えて計算を行うことができる。

enter image description here
出展: 「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バッファやステンシルの処理を行うユニット

enter image description here
出展: 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のページにアクセスした場合も同様に動作する。

enter image description here
出展: 「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}} \]

出展: Wikipedia ハミング符号の例

右端の単位行列のような部分は、左端のデータビットを用いた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です。皆で議論を深めて行けるような場にしていきましょー。

コメント

このブログの人気の投稿

GPUを支える技術読み始めた 第5章[前半]

GPUを支える技術読み始めた 第5章[前半] 新年の休みもとうに終わり、皆さんどうお過ごしだろうか。 ちなみに主は年末読む予定であった本や論文を全く消化できずに今に至ってしまった。 年末年始は時間があったので、ひたすら読んでいたはずなのになぜだ。。_| ̄|○ 読むのが遅いのがいけないのか?翻訳するのが遅いのか??それともそれをまとめるのに時間がかかっているのか。 おそらく全て当てはまるが、時間がかかるものは仕方がないので、少しずつ慣れていくしかないなー。。(-_-;) 余談だが、年末にブログ執筆環境を再構築した。 今まではwebエディタの Classeur を利用していたが、やはり純粋なWebアプリケーションなのでレンダリングやアップデートに問題があった。そこで、今までも利用していたAtomを少しカスタマイズして試してみたが、どうにも動作が重くかつ、vim-modeがいい感じにならずに残念に思っていたところ、vscodeのことを思い出した。どちらもエレクトロンベースだが、vscodeはAtomより全然軽く、markdownプラグインも豊富にあるので、すぐにmarkdown+mathjax環境を構築することができた。いやはや、世の中は便利になったものだ(^ω^) せっかくなので、下に執筆環境のスクリーンショットを自慢げに貼ってみようと思う。(markdownは頻繁に見る必要はないので、普段はプレビューは別のタブで開いている) もしかして、こういうことしてるから時間がかかるのかな??/(^o^)\ それでは、本題に戻ろう。 今回はGPU支える技術の第5章だ。4章と同じく楽しみにしていた章なのでじっくり読んでいきたい。 なお今回も長い章なので、前半と後半に分けてまとめと感想を書いていく。 第5章 GPUプログラミングの基本[前半] GPUの超並列プロセッサでプログラムを実行するには、超並列で実行でき...

GPUを支える技術読み始めた 第4章 [前半]

今年もあと少しになってきたが、なんとか目標だったもう一本を投稿することができて良かった。 私情だが、先日の社内年末パーティでは年間MVPに選出していただいた。\(^o^)/ 非常に嬉しく思うと同時に、いろんな面でサポートをしてくれたHWチームメンバやバックオフィスに感謝したい。 また、来年は社内だけでなくて社外にも影響を与えられるよう頑張っていきたい。 少し気が早いが、来年度の本ブログの方針として「基礎と応用」というコンセプトで書いていきたと考えている。古典的名著と最新の論文の要約などができたら上出来だろうか。(^ω^) 直近だと、DeepLearning × HWに関するの新しめの論文や並列処理技法系の本のまとめを計画している。もしかすると、年内にまだいけるかもしれない。 良い報告もできたところで、早速続きを初めていこうと思う。 4章は個人的には一番楽しみな章でじっくり読んでいる。内容が多いので前半と後半に分割して投稿していく。 4章 GPUの超並列処理 [前半] GPUの並列処理方式 先の章で並列処理方式について以下のように説明した。 SIMD: 1つの計算を幾つかのデータに対して並列に実行する SIMT: 1つの計算を別々の演算機で並列に実行する 4章では上記2つについてもう少し詳しく解説している。 SIMD方式 以下2つのベクトルXと行列Aがあるとする。 \[ \begin{align} \bf{X} &= (a, b, c) \\ \bf{A} &= \left( \begin{array}{ccc} a00 & a01 \\ a10 & a12 \\ a21 & a21 \end{array} \right) \\ \bf{Y} &= \bf{X} \cdot \bf{A} \end{align} \ \] Yを計算する時、SIMDでは先にXの列要素(a)を各演算機にブロードキャストし、Aの行要素(a00, a10, a20)と計算する。この動作をXの列要素分繰り返すことで計算を完了する。 仮に、Xの要素がシェアードメモリ(後述)など、レイテンシのあるメモリに格納されている場合、各ブロードキャストでサイク...

GPUを支える技術読み始めた 第2章

2日連続で第2回目の投稿である。 そもそも書き溜めをしてあったり、本ブログを初めたのが土日で比較的時間をとれたのは大きい。 前回でBloggerのエディタではMarkdownやLatexが使えず不便だったので、どうにかならないかと探していたところ、 このブログ を見つけ Classeur というWebエディタを使い始めた。 普段はメモ用として使っているQuiverやAtomと言ったオープンソースの高性能エディタを使うこともできたが、やはり変更のたびに投稿を行わなければいけないのはめんどくさい。ClasseurはMarkdownが使え、かつBloggerと連携して1クリックで更新ができる。いつもながら有益な情報を残してくれた先人に感謝である。 物事を継続するのコツとして、いかにメイン作業以外の手間を減らすが重要だと感じているので、ここでもそれに従うことにする。 もちろん環境の導入やそもそもの事前調査に時間は使ったが、長く続ければ十分にもとがとれるので、初めのうちにやっておいた( ・´ー・`) 上記以外にもLatexを使えるようにするためにMathjaxをBloggerのテンプレートに導入した。 準備は万端、早速書き始めよう。 2章 計算処理の変革 ゲームと画面描画の内容が多く、少し前回と同様に流し読みぎみ。 初期グラフィックボードである1983 年に Intel が発表した iSBX 275というボードは、256 x 256ピクセル解像度で8種類の色を利用可能だった。 続く1996年にha3dfx Interactive社がVoodooシリーズを、1999年にはNVIDIAはGeForde 256を発表した。 GeForce 256を含む初期GPUの主な目的は、3Dグラフィックスに必要なT & L(Transpose & Lighting)処理を高速に行うためであったが、当初の性能はハイエンドCPUに負ける程度であった。その後、ムーアの法則に従って搭載できるトランジスタの量が増えるにつれて、CPUの10倍以上の性能を出せるようになっていく。 当初はTransposeとLightingの処理は別々のパイプラインとなっていたが、 リソースを柔軟に使いまわしすことで、効率を上げるUnified shaderが用いられるよう...