1カ月集中講座

骨まで理解するPCアーキテクチャ(GPU編) 第2回

〜GPGPUへの最適化や電力効率向上へ進んだNVIDIA GPUの歩み

 今週はNVIDIAのアーキテクチャの変遷を、後藤弘茂氏の記事をフルに引用させていただきつつ、ご紹介したいと思う。

 GPUに本格的なシェーダが始めて実装されたのは、繰り返しになるがDirectX 8の世代である(厳密に言えばDirectX 7の「NV20」でShader Model 1.0には対応しているが)。最初にこれに対応したのは、NVIDIAの場合は「NV25」(GeForce 4)の世代になる。この世代は、まだあまりシェーダのプログラマビリティは高くなく、また、DirectX 7世代と大きく構造も変わらなかった。

 後藤氏の記事にあるDavid B. Kirk氏へのインタビュー記事(前編後編)を読めばこれは明らかで、インタビューの冒頭にNV25はNV20(GeForce 3)の発展型で、主にピクセルシェーダの強化を行なったと説明していることからも分かる。ダイサイズの制約から、ピクセルパイプラインそのものは4本のままとしながら、スループットを上げることで描画性能を引き上げたのがこの世代である。

 ところでこの世代のシェーダとはどのような構造なのか。直接的な回答にはならないが、間接的な答えは示されている。写真1はMicrosoftがMeltdown 2001で示した、Shader 1.x世代のリファレンス構成である。ハードウェアの物理的な実装そのものは別にして、アプリケーション(シェーダプログラム)からはハードウェアがどう見えるかをまとめたものだ。まず頂点シェーダがベクトルデータを基に、「どのピクセルはどんな色でどんなテクスチャが貼られるか」という頂点コンポーネントを算出。次いでピクセルシェーダがこれを元にテクスチャを張り込んでピクセルデータを生成して出力する、という流れが分かる。このShader 1.x世代はいくつかバージョンがあり、DirectX 8.1ではPS(Pixel Shader) 1.2/1.3/1.4が用意されているが(写真2)、実はそう大きな違いはないことが分かる。

【写真1】Shader 1.x世代のリファレンス構成
【写真2】DirectX 8.1のピクセルシェーダ

 ちなみにNVIDIAはGeForce 4でPS 1.2/1.3に対応しており、一方PS 1.4はATI(現AMD)のRADEON 9000シリーズでのみ実装された機能である。さて、このピクセルシェーダの中身がソフトウェアからどう見えるか、というのが写真3で、2つの入力を色とαチャネルに分けた上で、積和計算を行なって出力、という形になる。繰り返しになるが、実際のハードウェアの実装がこの通り、というのではなく、あくまでもソフトウェアからはこう見える、という話であることに注意されたい。

【写真3】ピクセルシェーダのソフトウェアでの実装

劇的な変化となったが“失敗作”となった「GeForce FX」

 さて、NVIDIAは次のDirectX 9世代で内部構造を大きく変える。それが「NV30」こと「GeForce FX」である。下記の記事にある後藤氏作成の「GeForce FXの推定ブロック図」を参照して欲しい。ラフに言えば、この世代でNVIDIAは「CineFX」と呼ばれる大幅な内部拡張を実装しており、これもあって内部構成が劇的に変わった。

 これもラフに言えば、従来は図1の左側のように命令発行部から複数のピクセルパイプラインに対して命令を出している構図だったのが、NV30では広義に言うところのピクセルパイプラインは1つで、その中に複数のシェーダがぶら下がる構図と言える。例えるならば、従来型のGPUはあたかもマルチコアのAtomプロセッサで、1個1個のコア(≒シェーダ)の性能はそう高くないが、数を並べることで性能を出す方式。一方のNV30はHaswellやItaniumのように、ひたすらIPCを引き上げる方向性である。この場合シェーダはCPUコアというよりは、コア内部の実行ユニットに相当するわけだ。

【図1】従来のGPUとNV30の違い

 このような複雑な構成にした理由の一端は、先にも示した後藤氏によるDavid B. Kirk氏へのインタビュー記事(前編後編)の中で明かされている。端的に言えば、DirectXが想定したものよりもずっと高い機能をNV30に入れたかったから、という事になる。

 こちらの記事の後半で多少触れられているが、頂点シェーダ(バーテックスシェーダ)は最大64K命令(DirectX 9では最大1K命令)サポート、三角関数のサポート、ピクセルシェーダもDirectXがTexture 32/Color 64命令のみのサポートなのが最大1K命令までサポート、など、大幅に機能が拡張された。また16/32bit浮動小数点演算もサポートされた。あるいはこちらの記事にもあるように、より高いデータ精度を求めたというのも理由の1つだろう。

 問題は、この当時はやっと0.13μmプロセスが利用できるようになったばかりで、まだトランジスタ数の制約が厳しかったことだ。このため、高機能なシェーダを従来方式(図1の左側)で実装するのは物理的に不可能で、NV30のような方式しかなかったとも言える。もっとも、これだけではまだ十分ではなく、例えば128bitに関しては制約があったり、実際には32bit演算ユニットではなく16bit演算ユニットのみで構成されたりといったトレードオフが行なわれた。NV30ではなく、これの改良版であるNV35世代の話ではあるが、David B. Kirk氏への後藤氏のインタビュー記事の中でも、これに関して触れている。

 ただ、結果としてこの世代は失敗扱いされているのは仕方ないというか、理想に向かって先走りしすぎていた感は否めない。

NV40世代の「GeForce 6800 Ultra」

 これもあってか、NVIDIAは次の「GeForce 6」シリーズ、つまり「NV40」で、いきなり保守的な構成に戻した。具体的な内部構造は下記記事の「NV40 3D Pipeline Block図」に詳しい。まとめれば頂点/ピクセルシェーダともに完全に32bit化され、かつこれが図1左側の方式で並列に並ぶ構造となっている。

 ある意味工夫がないというか、ストレートな実装ではあるのだが、NV30/35世代に凝ったアーキテクチャを投入して、ストレートな実装のATI R300/350世代に負けてしまった以上、仕切り直しの意味でもストレートな構造が必要、と判断したのかもしれない。ただこの世代ですでにダイサイズの肥大化というペナルティもある。また、そろそろシェーダの数が増えてきて効率性を考える必要が出てきた時期でもあった。

 不幸にもと言うべきか、幸いにもと言うべきか分からないが、NV40の後はしばらく停滞を迎える。これはNVIDIAがPlayStation 3(PS3)、ATIがXbox 360の開発に忙殺され、新アーキテクチャを投入する余地がなかったからだ。

 2005年になって、NVIDIAは「G70」コアを投入するが、内部構造はNV40世代のままで、プロセスの微細化(0.13μm→0.11μm)とシェーダ数の強化のみが行なわれた程度である。

 ただ水面下で、次世代のGPUアーキテクチャに関する話は色々出ていた。その代表的な話がこちらの記事にある通りである。ここで出てくる話は、まだGPGPU向けではなくあくまでGPU、つまり描画処理に関しての話である。シェーダが高速化するに従い、オンボードのDRAMにアクセスするまでのレイテンシ(遅延)が大きくなってきたため、マルチスレッディングを使ってこのレイテンシを隠蔽しよう、という方法論だ。

 Pentium 4のHyper-Threadingとも少し似ているが、異なるのはHyper-Threadingの主要な目的はむしろ実行ユニットの効率利用で、レイテンシの隠蔽は副次的な目的だったのが、GPUではそちらがメインなことである。また、Hyper-Threadingでは2スレッドの同時実行だが、こちらはもっと多数のスレッドの実行を考慮している。こちらの記事にもある通り、すでにNV40の世代でマルチスレッディングが実装されている事は明らかにされており、具体的には“Fine-Grained Multithreading”(細粒度マルチスレッディング)であることが分かっている。

 さて、話を戻すとNVIDIAとAMD(ATI)がそれぞれPS3やXbox 360に忙殺されている間にも、ゆっくりとDirectXのアーキテクチャの開発が進んでいった。最終的にDirectX 10としてリリースされた新アーキテクチャは、これまでと異なり、頂点/ピクセルシェーダの区別がない、統合型シェーダとなることが決まった。後藤氏のこの記事の最後に、David E. Orton氏へのインタビューを引用しながら、GPUが単なる描画用のものから、より汎用の方向に向けて舵を切ったことが示されているが、これはその次に書かれた記事からも明確である。シェーダ単体の性能はともかくとして、重要なのはロードバランシングがこのDirectX 10の世代から理論上可能になることで、両社ともにこれを見据えて、DirectX 10世代のアーキテクチャを構築してゆくことになる。

DirectX 10世代の統合型シェーダ

G80世代の「GeForce 8800 GTX」

 その統合型シェーダの第1世代としてNVIDIAが発表したのが「G80」こと「GeForce 8000」シリーズである。実はNVIDIA、G80の発表前はあえて統合型シェーダに否定的な意見を述べるなど、色々撹乱工作を行なっていたが、実際に蓋を開けてみると恐ろしく統合型シェーダに最適化された構成になっていた。詳細は下記のレポートに詳しい。汎用的なGPGPUに向けた構成を採ったのがこのG80である。このG80はまた、CUDAを導入した世代でもあり、これでいよいよGPGPUとしての最低限の環境が揃ったとも言える。

 ちなみにこのG80世代では、ハイエンドのG80では128のシェーダ(Streaming Processor:SP)から構成されるが、これを冒頭の例えで説明すると128コアのマルチプロセッサということになる。さすがに128ものコアをそのまま独立して管理するのは大変である。そこでG80では、まずこのSPを8個単位でまとめたStreaming Multiprocessor(SM)という単位を作り、さらにSMを2つでTexture Processor Cluster(TPC)というユニットを構成している。TPCの方は、そこにテクスチャユニットやL1/L2キャッシュ、ロード/ストアユニットなどが付属する。言ってみればハードウェアとしての管理単位である。

 先ほどのG80の内部構成で、SPとテクスチャユニット、L1キャッシュを固めたものが8つ並んでいるかと思うが、これがそれぞれTPCということになる。G80世代はハードウェア的にはこのTPCの数を増減させてバリエーションを作っている。

 その一方でソフトウェア的にはSMが最小単位である。SPは基本的にはスカラ演算ユニット(つまり1度に1つのデータしか処理ができない)が、8個のSPが一塊となっている関係で概念的には8-wayのSIMDと見ることができる。

 またこのSMは、内部で「Warp」という概念(管理単位とも言える)を利用している。Warpというのは「32スレッドの塊」と解釈すればいいが、要するに32個のスレッドを同時に保持しておき、実行可能なスレッドをSP上で走らせるための仕組みである。先にも書いたが、GPUにおけるマルチスレッディングの主要な目的はレイテンシの隠蔽であり、Warp(に含まれる32スレッド)が実行中に、メモリアクセス待ちなどの理由で実行を継続できなくなった際には、別のWarp(に属する32スレッド)を実行するという形で動作を行なう。

 このWarpは、CUDAのプログラムに直接出てくるわけではないが、これを考慮してプログラミングをしないと性能が上がらない、というちょっと厄介な存在である。また、このWarpに属するスレッドは全て同じ命令を実行する必要がある、という縛りもある。つまり、32のスレッドで並列処理を行なっているというイメージを考えてもらえればいい。

GT200世代の「GeForce GTX 280」

 続いてNVIDIAは、G80世代に続き、2007年末に「G92」コアをリリースしたが、プロセスとか構成はともかく、シェーダの構造はほぼ同じままだ。これがもう少し変更されるのは、2008年に投入された「GT200」コアである。あるいは「Tesla」と言った方が分かりやすいかも知れない。内部構造などは下記の記事を参照されたい。

 TPC/SM/SPという構成は変わらないし、内部的にWarpという概念を使うことも変更はない(よって、プログラミングという観点からは大きな違いはない)のだが、1つのTPCに含まれるSMの数が2→3に増えている。プロセスの微細化に伴いSPの数をずっと増やすことが可能になったが、G80世代と同じ仕組みにするとTPCの数が15にもなってしまい、内部の配線が大変になることを嫌ったと後藤氏は説明している。実際これは妥当な説明だと思う。もう1つ理由を挙げれば、GPGPU的な使い方が次第に増えたことで、I/Oと演算の比率がやや演算寄りになってきたため、TPCの性能を引き上げる方が(TPCの数を増やすよりも)効率的という判断もあったのではないかと思う。このGPGPU的な使い方が、この時期でも次第に増えつつあるという手応えは、同時期のこちらの記事でも分かるかと思う。

 またシェーダ内部で言えば、このGT200の世代で初めて倍精度浮動小数点演算にも対応したことは特筆すべきだろう。倍精度演算は、GPU的な用途では考えにくい(現在でもそうした高精度な演算は、GPU用途では使われていない)から純粋にGPGPUのものである。もっとも、さすがに既存のSPを倍精度対応にすると回路規模が洒落にならないので、既存のSPとは別に倍精度演算専用ユニットをSMに追加するという構成になっている。

Fermi世代の「GeForce GTX 480」

 ただ、この後、NVIDIAはしばらく停滞期に入る。もちろん停滞とは言ってもプロセスの微細化と、それを採用した新製品の投入は行なっていたが、DirectX 11への対応はAMDよりやや遅れていた。このDirectX 11に対応させ、内部構造を大幅に変更してよりGPGPU向けに強化を図ったのが次の「Fermi」世代である。主要な変更点などは下記の記事を参照していただきたい。

 SPについて言えば名称がSPから「CUDAコア」に変わった以外にも、倍精度浮動小数点演算が可能になった(ただしスループットは半分)。1つのSMは8 SPから32 CUDAコアと4倍になっている。さらに複数のWarpを同時に発行できるような構成になり、より演算の粒度が上がっている。また、これを取り巻く周辺ユニットを大幅に強化した結果として、Tesla世代に比べて格段に命令発行効率を引き上げたのが特徴となっている。ただその一方で、メモリレイテンシの隠蔽をマルチスレッディングに頼りにくい構成になったのも特徴で、この結果として旧来のCUDAプログラムでは性能が出にくくなるケースも出てきた。要するに、L1/L2キャッシュをより利用するようメモリ階層をTesla世代より増やしたので、これを上手く使うのが性能改善の鍵になるという、保守的なCPUにやや近づいた形だ。

 そのFermiの第1世代の製品である「GF100」こと「GeForce GTX 480」は、物理設計の問題で性能が上がらないわ、歩留まりは悪いわ、消費電力が多いわ、という困難な状態だったのはご存知の通り。ハードウェア的には「マルチコア」構成であり、またGPUとしても強力な製品になるはずだっただけに可哀想な話ではあるのだが、同社はこの経験を無駄にはしなかった。

 後追いで登場した「GF104」はSM内の構成をGPU向けに再変更して、より高い性能が出せるようにチューニングしたし、一方でメインストリーム向けには論理設計を変えずに物理設計をやり直したことで、GF100が本来狙っていた性能を引き出すことに成功した。GF104の設計は下記の記事に詳しい。

性能/電力比や実効効率を重視した設計へ

 ただこの頃から、NVIDIAの戦略は微妙に変わってゆく。2010年秋に開催されたGTCの基調講演で、同社はFermiに続く製品として「Kepler」、「Maxwell」の存在を明らかにするが、純粋に性能を追求する方向ではなく、性能/消費電力比や、実効効率性能を重視する方向にシフトしてきた。特にMaxwellに関しては、この時点での展望はかなり野心的と言っても良い。

 そのKepler、発表は2012年にずれ込んだが、マイクロアーキテクチャを一新したことを明らかにした。

Kepler採用の「GeForce GTX 680」

 CUDAコアそのものは引き続きスカラ演算という意味では違いはないが、消費電力を下げるためにパイプライン段数を減らしている。またスケジューリングの機能をソフトウェア(CUDAコンパイラだったり、ディスプレイドライバだったり)側に移すことで、スケジューラを簡素化した。

 スケジューラを簡素化すると何が可能になるか、というと「よりワイドな並列実行」が可能になるのである。これまではハードウェアスケジューラがボトルネックになって、あまり並列度を上げても利用効率が上がりにくいという問題があったが、これがソフトウェア側で事前にスケジューリングされるなら、もっと並列度を上げても効率を高められる可能性があるわけだ。その結果、SMはSMXに拡張され、1つのSMXには192ものCUDAコアが搭載されることになった。この結果として投入された「GK110」が、ブッチギリの性能を発揮して、現在もシングルGPUとしては最高速の性能を発揮している(シングルカードという観点ではRadeon R9 295X2という“心臓の弱い人にはお勧めできない”らしい製品があるが、これは置いておく)のはご存知の通りである。

 この設計思想は次のMaxwellにも継承されたが、Maxwellではさらに実効効率を引き上げるための工夫がなされたのは、下記記事にもある通りだ。


 ということで、駆け足でここ10年ほどのNVIDIAのGPUアーキテクチャの変遷を紹介した。次週は同じように後藤氏の記事をフルに参照しつつ、ATI/AMDのアーキテクチャをご紹介したい。

(大原 雄介)