GPUという計算機
この章でわかること:
- GPUがどんな問題のために設計された計算機か
- CPUとGPUの設計思想の違い(レイテンシ指向とスループット指向)
- SIMT実行モデル: スレッドの束(ワープ)とダイバージェンス
- GPUがメモリのレイテンシを隠す仕組みと、占有率という考え方
- CUDA/WebGPU/Metalの用語対応
画面描画の専用装置として設計された
GPU(graphics processing unit)は、名前のとおり画面描画の専用装置として 設計されました。画面描画とは「フルHDなら約200万個のピクセルそれぞれに、 ほぼ同じ計算をして色を決める」処理です。これを毎秒60回繰り返します。
ピクセル同士の計算はほぼ独立なので、順番に処理する理由がありません。 GPUは「同じ計算を大量のデータに同時に適用する」ことに特化した 設計へ発展しました。やがて、この構造が行列計算や物理シミュレーションにも そのまま使えることが認識され、GPUを汎用計算に使う GPGPU(general-purpose computing on GPU)が広まりました。 現在の機械学習の計算も、この構造に依存しています。
Part Iで見たCPUの高速化(分岐予測、アウトオブオーダー、キャッシュ)が 「1つの命令列をいかに速く実行するか」の工夫だったのに対し、 GPUの高速化は「同時に実行する命令列の数をいかに増やすか」の工夫です。 この違いを基準に、GPUの主要な仕組みを見ていきます。
設計思想: レイテンシ指向とスループット指向
CPUとGPUの違いは、チップ面積の使い方に現れます。 次の図は面積の配分を模式化したものです。
- CPUはレイテンシ指向(latency-oriented)です。1つの処理を 最短時間で終わらせるために、分岐予測器、アウトオブオーダー実行の 管理機構、大容量キャッシュといった「計算以外の回路」に 面積の大半を割り当てています
- GPUはスループット指向(throughput-oriented)です。 個々の処理は遅くてもよいので、単位時間あたりの総処理量を最大にします。 制御回路を大幅に減らし、その面積に演算ユニットを多数配置します
数値で比べると設計の違いが明確になります(おおよその値です)。
| CPU | GPU | |
|---|---|---|
| 演算の単位 | コア数個〜数十個 | 数千〜数万レーン |
| クロック | 3〜5GHz | 1〜2GHz |
| 1スレッドの速さ | 非常に速い | 遅い |
| 総演算能力(f32) | 数百GFLOP/s | 数TFLOP/s〜 |
| 分岐予測・アウトオブオーダー | あり | 基本なし |
GPUの個々のスレッドは、分岐予測もアウトオブオーダーもない、 Part Iの基準では単純な実行環境です。GPUの速さは、すべて 並列度の規模によるものです。
SIMT: スレッドの束で実行する
ここからの「スレッド」は、5章のOSが管理するスレッドとは別の概念です。 GPUで実行するプログラムをシェーダ(shader)と 呼びます(計算用のシェーダはカーネルとも呼ばれます)。GPUのスレッドとは、 シェーダの関数の1回の実行を指す論理単位で、 WebGPUの用語ではinvocationです。OSは関与しません。
ハードウェアは32本程度のスレッドを1つの束にまとめ、 束の全スレッドに同じ命令を同時に実行させます。GPUのスレッドが 1本ずつ独立に実行されることはありません。この束をNVIDIAは ワープ(warp)と呼びます(WebGPUではサブグループ。束の幅は ハードウェアによって異なり、32が代表的です)。 この実行方式がSIMT(single instruction, multiple threads)です。
4章のSIMDとよく似ています。実際、ハードウェアの実体は 「幅の広いベクトル演算器」でほぼ同じです。違いはプログラミングモデルで、 SIMDは「1本のスレッドがベクトルレジスタを操作する」と書くのに対し、 SIMTは「1レーンを1スレッドとみなして、スカラのコードを書く」形を 取ります。11章で書くWGSLのシェーダがまさにこの形です。
束で実行することの代償がダイバージェンス(divergence、分岐発散)です。
ワープ内のスレッドがifで違う側に進んだ場合、ハードウェアは
両方の側を順番に実行し、各スレッドは自分に該当しない側の実行中は
マスクされて待ちます。真偽が混在する分岐では、
両側の実行時間の合計がかかります。
3章で、CPUは分岐の行き先を予測して、予測した側を先に実行すると 説明しました。GPUは予測せず、両方を実行して不要な側の結果を破棄します。 どちらでも、予測できない(揃わない)分岐はコストが大きいという 原則は共通です。
レイテンシを隠す仕組み
GPUのメモリ(VRAM)のレイテンシは数百サイクルで、CPUのDRAMと 同程度か、それより大きいことが多いです。しかもGPUのキャッシュは CPUより小さく、2章のようにキャッシュで待ち時間をなくす方法は使えません。
GPUの方法は、待っている間に別のワープを実行することです。 実行ユニットに数十のワープを常駐させ、あるワープが メモリ待ちに入ると、別のワープに切り替えます。 CPUのSMT(5章)と同じ方式ですが、規模が違います。切り替えの コストをゼロにするため、GPUは常駐する全スレッドのレジスタを 物理的にすべて保持しています。
実行ユニットに常駐できるワープ数のハードウェア上限に対して、 実際にどれだけ常駐できているかの割合を占有率(occupancy)と呼びます。 1スレッドが大量のレジスタを使うと常駐できるワープ数が減り、 占有率が下がってメモリ待ちを隠しきれなくなります (逆に、占有率が高いほど常に速いわけでもありません。 レイテンシを隠すのに足りているかどうかが判断基準です)。
この仕組みから、GPUの根本的な制約が導けます。 GPUは、メモリ待ちを隠せるだけの大きな並列度がある場合にだけ速くなります。 数千スレッド程度の小さな処理では実行ユニットが使われない時間が多くなり、 CPUより遅くなることも珍しくありません(12章で実測します)。
どんな問題がGPU向きか
ここまでの構造から、GPUに向く問題の条件を整理できます。
- データ並列である: 大量の要素に同じ処理を行い、要素間に依存がありません
- 規模が大きい: 数十万〜数百万要素以上です。レイテンシを隠すために必要です
- 分岐が揃っている: 要素ごとに処理内容がばらつきません
- データ転送に見合う計算量がある: 次章で扱います
行列演算、画像処理、物理シミュレーション、ニューラルネットワークは すべてこの条件を満たします。逆に、逐次依存の強い処理 (パース、逐次的な状態機械)、分岐の多い処理、小さなデータは CPUに向いています。
用語対応表
GPUのAPIは、同じ概念をAPIごとに別の名前で呼びます。 本書は次章からWebGPU(WGSL)の用語を使いますが、 資料を読むときのために対応を示しておきます。
| 概念 | WebGPU/WGSL | CUDA | Metal |
|---|---|---|---|
| 1本の実行の流れ | invocation | thread | thread |
| スレッドの束(32本程度) | subgroup | warp | SIMD-group |
| 共有メモリを持つグループ | workgroup | thread block | threadgroup |
| グループ内共有メモリ | workgroup memory | shared memory | threadgroup memory |
| 実行単位のハードウェア | (compute unit) | SM | GPU core |
| 起動する全体 | dispatch | grid | grid |
実行: 自分のGPUを確認する
この章は概念の導入なのでブラウザ実験はありませんが、 手元にリポジトリがあれば、次のコマンドでwgpu(11章)が認識した GPUの名前を確認できます。
cd examples
cargo run --release -p ch11-vector-add
筆者の環境ではGPU: Apple M4と表示されます。
コードの中身は11章で説明します。
まとめ
- GPUは「大量のデータに同じ計算」を最大スループットで行うために、 制御回路を減らして演算ユニットを多数配置した計算機です
- スレッドは32本程度の束(ワープ)で同じ命令を実行します(SIMT)。 束の中で分岐の行き先が分かれると両側を順に実行するため遅くなります
- メモリのレイテンシは、キャッシュではなく多数のワープの切り替えで 隠します。そのため大きな並列度が前提になります
- 向くのは、大規模・データ並列・分岐が揃った問題です
次章では、GPUの性能を実際に左右する要素であるメモリを扱います。 VRAM、共有メモリ、CPUとGPUの間のデータ転送を説明します。 2章で学んだデータの配置の考え方が、より極端な形で再び必要になります。