GPUという計算機
この章でわかること:
- GPUがどんな問題のために設計された計算機か
- CPUとGPUの設計思想の違い(レイテンシ指向とスループット指向)
- SIMT実行モデル: スレッドの束(ワープ)とダイバージェンス
- GPUがメモリの遅さを「隠す」仕組みと、占有率という考え方
- CUDA/WebGPU/Metalの用語対応
画面を描く仕事から生まれた
Section titled “画面を描く仕事から生まれた”GPU(graphics processing unit)は、名前のとおり画面描画の専用装置として 生まれました。画面描画とは「フルHDなら約200万個のピクセルそれぞれに、 ほぼ同じ計算をして色を決める」仕事です。これを毎秒60回繰り返します。
ピクセル同士の計算はほぼ独立なので、順番にやる理由がありません。 GPUは「同じ計算を大量のデータに同時に適用する」ことに特化した 設計へ進化しました。やがて、この構造が行列計算や物理シミュレーションにも そのまま使えることが認識され、GPUを汎用計算に使う GPGPU(general-purpose computing on GPU)が広まりました。 現在の機械学習ブームを支えているのもこの構造です。
Part Iで見たCPUの高速化(分岐予測、アウトオブオーダー、キャッシュ)が 「1つの流れをいかに速く進めるか」の工夫だったのに対し、 GPUの高速化は「流れの数をいかに増やすか」の工夫です。 この違いを軸に、GPUの主要な仕組みを見ていきます。
設計思想 — レイテンシ指向とスループット指向
Section titled “設計思想 — レイテンシ指向とスループット指向”CPUと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 — スレッドの束で動く
Section titled “SIMT — スレッドの束で動く”先に言葉の注意を1つ。ここからの「スレッド」は、5章のOSが管理する スレッドとは別物です。GPUで実行するプログラムをシェーダ(shader)と 呼びます(計算用のシェーダはカーネルとも呼ばれます)。GPUのスレッドとは 「シェーダの関数を1回実行する流れ」を指す軽量な論理単位で、 WebGPUの用語ではinvocationです。OSは関与しません。
GPUのスレッドは1本ずつ自由に動くわけではありません。 ハードウェアは32本程度のスレッドを束にして、 全員に同じ命令を同時に実行させます。この束をNVIDIAは ワープ(warp)と呼びます(WebGPUではサブグループ。束の幅は ハードウェアによって異なり、32が代表的です)。 この実行方式がSIMT(single instruction, multiple threads)です。
4章のSIMDとよく似ています。実際、ハードウェアの実体は 「幅の広いベクトル演算器」でほぼ同じです。違いはプログラミングモデルで、 SIMDは「1本のスレッドがベクトルレジスタを操作する」と書くのに対し、 SIMTは「1レーンを1スレッドとみなして、スカラのコードを書く」形を 取ります。11章で書くWGSLのシェーダがまさにこの形です。
束で動くことの代償がダイバージェンス(divergence、分岐発散)です。
ワープ内のスレッドがifで違う側に進んだ場合、ハードウェアは
両方の側を順番に実行し、各スレッドは自分に関係ない側の間
マスクされて待ちます。真偽が混在する分岐では、
両側の実行時間を合計で支払うことになります。
3章でCPUは「分岐を予測して先回りする」と学びました。 GPUは予測せず「両方やって、いらない方を捨てる」を選びました。 どちらの世界でも、予測できない(揃わない)分岐は高くつくという 原則は共通です。
レイテンシは隠すもの
Section titled “レイテンシは隠すもの”GPUのメモリ(VRAM)のレイテンシは数百サイクルと、CPUのDRAM同様に 遅い——むしろ悪いことが多いです。しかもGPUのキャッシュは CPUより小さく、2章のような「キャッシュで待ちを消す」戦略は取れません。
GPUの答えは、待っている間、別のワープを実行することです。 実行ユニットのそばに何十ものワープを常駐させ、あるワープが メモリ待ちに入った瞬間、別のワープに切り替えます。 CPUのSMT(5章)と同じ発想ですが、規模が違います。切り替えを ゼロコストにするため、GPUは常駐する全スレッドのレジスタを 物理的に全部持っています。
実行ユニットに常駐できるワープ数のハードウェア上限に対して、 実際にどれだけ常駐できているかの割合を占有率(occupancy)と呼びます。 1スレッドが大量のレジスタを使うと常駐できるワープ数が減り、 占有率が下がってメモリ待ちが表に出てきます (逆に、占有率が高いほど常に速いわけでもありません。 レイテンシを隠すのに足りているかどうかが本質です)。
この仕組みから、GPUの根本的な制約が導けます。 GPUは、メモリ待ちを隠せるだけの膨大な並列度がある場合にだけ速い。 数千スレッド程度の小さな仕事では実行ユニットが遊んでしまい、 CPUに負けることも普通にあります(12章で実測します)。
どんな問題がGPU向きか
Section titled “どんな問題がGPU向きか”ここまでの構造から、GPUに向く問題の条件を整理できます。
- データ並列である — 大量の要素に同じ処理。要素間の依存がない
- 規模が大きい — 数十万〜数百万要素以上。レイテンシを隠すため
- 分岐が揃っている — 要素ごとに処理内容がばらつかない
- (次章で扱う)データ転送に見合う計算量がある
行列演算、画像処理、物理シミュレーション、ニューラルネットワークは すべてこの条件を満たします。逆に、逐次依存の強い処理 (パース、逐次的な状態機械)、分岐だらけの処理、小さなデータは CPUの領分です。
GPUの世界は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を確認する
Section titled “手を動かす: 自分のGPUを確認する”この章は概念の導入なのでブラウザ実験はありませんが、 手元にリポジトリがあれば、次のコマンドでwgpu(11章)が認識した GPUの名前を確認できます。
cd examplescargo run --release -p ch11-vector-add筆者の環境ではGPU: Apple M4と表示されます。
コードの中身は11章で説明します。
- GPUは「大量のデータに同じ計算」を最大スループットで行うために、 制御回路を削って演算ユニットを敷き詰めた計算機です
- スレッドは32本程度の束(ワープ)で同じ命令を実行します(SIMT)。 束の中で分岐が割れると両側を順に実行するため遅くなります
- メモリの遅さは、キャッシュではなく「大量のワープの切り替え」で 隠します。だから膨大な並列度が前提になります
- 向くのは、大規模・データ並列・分岐が揃った問題です
次章では、GPUの性能を実際に左右する主役——メモリ——を扱います。 VRAM、共有メモリ、そしてCPUとGPUの間のデータ転送。 2章で学んだ「データの置き方」の考え方が、より極端な形で再登場します。