- GPU は演算速度がメモリアクセス速度よりはるかに速いため、メモリ階層構造が性能のボトルネックになる
- 演算集約度(Arithmetic Intensity, AI) によって、処理はメモリバウンドまたは計算バウンドに分類され、A100 GPU のしきい値は約 13 FLOPs/Byte である
- 性能最適化 の主要戦略として演算融合(Fusion)とタイル化(Tiling)があり、Fusion は不要なメモリ往復を減らし、Tiling はデータ再利用を最大化する
- 同期、Coalesced Load、バンク競合の解消 など、GPU ハードウェアの構造的特性を理解することが高性能カーネル作成に重要である
- 占有率(Occupancy)、スレッド分岐の最小化、量子化(Quantization) などの追加要素も、実際の性能に大きく影響する
GPUの計算およびメモリ階層構造
- GPU は一般に、メモリ帯域幅より算術演算の処理速度のほうがはるかに高い
- たとえば NVIDIA A100 は約 19.5 TFLOPS(32ビット浮動小数点)の性能を持つ一方、メモリ帯域幅は約 1.5TB/s 程度である
- 4バイトのデータを読み込む間に数十回の計算を処理できるため、データ移動が性能のボトルネックとなる
- グローバルメモリ(VRAM) はすべてのデータが存在する低速なオフチップメモリであり、Streaming Multiprocessor(SM) が計算を担う
- 各 SM には高速なオンチップ Shared Memory(SRAM) があり、プログラムが直接管理するキャッシュ として利用できる
- スレッド は最小の実行単位であり、各スレッドは 個別のレジスタ の集合を持つ
- 32本のスレッド が Warp を構成し、Block は同一 SM 上で実行されるスレッドグリッドである
性能領域: メモリバウンド vs コンピュートバウンド
- カーネル の性能は、メモリバウンド(データ移動速度によって制限)またはコンピュートバウンド(SM の演算能力によって制限)のいずれかに分類される
- 演算集約度(AI) は Total FLOPs / Total Bytes Accessed と定義され、重要な指標となる
- Roofline モデル: x軸が AI、y軸が FLOPS/s のグラフ上で、カーネルの実現性能を表す
- AI が低くメモリバウンドなら対角線(メモリ帯域幅の上限線)
- AI が高く計算バウンドなら水平線(最大演算性能の上限線)
- A100 の Ridge Point は 19.5 TFLOPS / 1.5 TB/s ≈ 13 FLOPs/Byte である
- AI を高めると性能が向上し、カーネルはコンピュートバウンドに到達できる
演算集約度を高める戦略
- 単純なモデル: 1本のスレッドが C[i,j] を 1つ計算 → AI = 0.25(非常に低く、メモリバウンド)
- スレッドが 2x2 タイルを計算しても AI = 0.5(依然として低い)
- AI を高めるには、複数スレッドがブロック単位で Shared Memory に大きなタイルをロードし、データ再利用を最大化する必要がある
- Block 内のスレッド協調によって AI > 13 まで高め、コンピュートバウンドへ入ることが可能である
オーバーヘッドバウンド状態
- CPU(ホスト)が GPU に処理を割り当てる過程でオーバーヘッドが発生する可能性がある
- GPU カーネルが小さすぎたり多すぎたりすると、GPU は仕事を待って待機する状況が生じる
- 現代のフレームワークは 非同期実行 を導入し、コマンドストリームをあらかじめキューイングすることでオーバーヘッドを最小化している
性能向上のための2つの中核戦略: Fusion と Tiling
Operator Fusion(演算融合)
- 単純な演算チェーン、たとえば
y = relu(x + 1) では、各演算が別々のカーネルで動くとデータがグローバルメモリを往復する
- Fusion は複数の演算を1つのカーネルにまとめ、中間値をグローバルメモリに保存せず、レジスタ内で処理して最終結果のみを書き込む
- 例: Triton、torch.compile Inductor などの JIT コンパイラが自動化する
Tiling(タイル化)
- 行列積 などの複雑な演算では、単一スレッドモデルでは AI が低い
- ブロック単位でタイルを分割し、ブロック内の全スレッドが協力してデータタイルを Shared Memory にロードし、大規模なデータ再利用を実現する
- 演算は "Load(グローバル -> Shared Memory) - Synchronize(同期) - Compute(演算)" の3段階パターンに従う
Coalesced Load とベクトル化
- グローバルメモリから Shared Memory にデータを移す際は、Coalesced Access(Warp の32スレッドが連続した 128 バイト区間にアクセス)が重要である
- ベクトル化(例: float4)によって一度に複数データをロードすると、ハードウェア資源を節約し、メモリ帯域幅の活用を最大化できる
- データ整列(alignment)が必須であり、行列内のバイト数を表す K の値は 4 の倍数である必要がある
Shared Memory のバンクとバンク競合
- Shared Memory は 32 個の独立した バンク で構成されており、Warp の32スレッドがそれぞれ異なるバンクにアクセスすると競合なく動作する
- 行方向アクセス では競合がなく、列方向アクセス では競合(同じバンクへのアクセス)が発生する
- B タイルは「ロードして転置する」戦略で Shared Memory に転置して保存し、演算時は行方向アクセス中心にすることでバンク競合を避ける
高速オンチップ演算パターン
基本戦略 1: 1本のスレッドが1つの output を計算
- BLOCK_DIM=32 の制約下では AI の最大値は 8 であり、コンピュートバウンドには到達できない
戦略 2: 1本のスレッドが複数の output を計算
- BLOCK_DIM=16、TILE_DIM=64 に設定すると、1本のスレッドが 4x4 の出力を計算 → AI=16
- AI>13 であるため、A100 基準では compute-bound の性能を達成できる
- Shared Memory から float4 などのベクトル化ロードを用いて効率的に演算できる
タイル化の実際の限界: タイル量子化
- 行列サイズがタイルサイズの倍数でない場合、境界ブロックは実際より大きい領域を計算することになり(不要な演算)、パディング処理が行われる
- 境界のスレッドは guard 条件によって不要なメモリアクセスは防ぐが、演算ループ自体は同じように回るため、無駄な計算(例:
C += A * 0)が発生する
追加の性能チューニング要素
占有率(Occupancy)とレイテンシ隠蔽
- Warp がメモリ読み出しなどで長時間待機している間、SM は別の Warp に即座に切り替えてアイドル時間を減らす(レイテンシ隠蔽、latency hiding)
- 複数の Thread Block を同時に割り当てると、高い占有率によって待ち時間を最小化できる
- Block やタイルサイズが大きすぎると resident block 数が減り、占有率低下によって性能低下が発生する
スレッド分岐の最小化
- Warp 内で if-else の条件分岐が発生すると、2つの経路を順番に実行するため、実効性能が半分程度まで低下する
- min、max などの branchless なコードで分岐を最小化する必要がある
量子化(Quantization)
- FP32 → FP16/BFP16 などへ精度を落とすと、メモリ移動量と処理可能なデータ数がそれぞれ 2 倍になる
- A100 では FP16 演算は 312 TFLOPS に達し(FP32 の 19.5 TFLOPS と比べて最大 16 倍の性能)、大幅な高速化が可能である
- 量子化によって Roofline 上で AI の右方向(メモリ効率)と上方向(最大演算性能)を同時に実現できる
全体の要約
- GPU 性能の本質的な限界は、メモリ帯域幅 と オンチップ演算能力 の不均衡に起因する
- 性能向上は データ再利用の最大化(タイル化) と 中間メモリトラフィックの最小化(Fusion) によって達成される
- ハードウェア構造(Warp、バンク、コアレスアクセス、同期)の特性を理解することで、高性能カーネルの作成と最適化が可能になる
- 実運用では 占有率、分岐最小化、量子化 などの追加要素が実効速度に直接影響する
- 高性能 GPU 演算の設計には、理論上の AI 向上、ハードウェア特性の活用、実データ配置やサイズへの対応など、複合的な考慮が必要である
1件のコメント
Hacker Newsのコメント
プログラム全体の最適化がコンパイラレベルでどこまでうまく行われているのか気になる。各LLMアーキテクチャを一つずつ最適化していく現在のやり方は、どこか後れを取っているように感じる
同じ4070で
llama.cppとvllmを動かし、より多くのプロンプトをバッチ処理しようとした経験の共有。batch 8あたりからllama.cppは深刻に遅くなり、GPU使用率は問題なさそうに見えても実際にはボトルネックが発生していた状況を説明。vllmははるかにうまく処理できたと実感したvllmは paged KVキャッシュと、GPUが好む fully coalesced なレイアウトを使っているため、バッチ向けに最適化された性能を出せる。一方llama.cppは単一プロンプトに向いた flat レイアウトなので、バッチ時にはL2メモリアクセスパターンが崩れて速度が落ちるllama.cppで KV tensor を[seq, head, dim]から[head, seq, dim]の形に interleave すると、vllmが fused attention kernel にデータを供給する方式に近づき、演算性能がすぐに2倍ほど向上した経験を共有ボトルネックの原因はGPU自体ではなく、shared memory アクセス方式と global read をどう設計するかにある。
vllmはまさにその点をレイアウト変更で攻略しているこうしたボトルネック分析には2日以上かかり、GPU利用グラフを見ただけでは分からず、ほとんどは試行錯誤で分かったことだった
こうした実験をもっと楽に、ホットリロードのような形で反復できる方法があるのか気になる
GPUがボトルネックではないと言っていたが、実際にはメモリレイアウトの非効率が最終的にGPUの演算効率を下げるボトルネックになっていた、という指摘
DeepSeekの社員が昨日公開した
nano-vllmプロジェクトに言及。わずか1200行しかないのに vanillavllmより速い性能を記録したとの共有 https://github.com/GeeeekExplorer/nano-vllmllama.cppで変更したレイアウトを pull request として出したのか質問。2倍の向上は誰にとっても大きな利益になり得るという意見ik_llama.cppというプロジェクトも試してみるよう勧める https://github.com/ikawrakow/ik_llama.cpp有益な情報の詰まった記事で、これはNVIDIAがGPUアーキテクチャを開発する際に選んでいる要素についての話だ、という意見。他社との違いを誤解しないよう強調
例えば AMD Instinct MI300 は FP32 で最大160 TFLOPS、HBM3/3E帯域幅6TB/sにより ridge-point が変わり、A100 の 13 FLOPs/byte の2倍にあたる 27 FLOPs/byte になる。さらに大容量HBMメモリ(128〜256GB)は tiling depth と occupancy の現実的なトレードオフも変える。ただし、こうしたGPUは高価で、CUDA非対応というトレードオフがある
AMDがコンピューティングソフトウェアにもっと力を入れるまでは、NVIDIA GPU だけが存在感を持ち続けるだろうという意見
ネタバレすると、本当に重要なのはGPUの動作原理そのものより、機械学習の計算にどう活用するかだという点を強調
コントラストのある配色を必ず使うべきだという意見。可読性を重視
font-weight: 300を使った経験の共有。大半のMacデザイナーはフォントスムージングのオプションに合わせて開発しているため、通常はnormalに見えるよう設定しているが、Macでは細いフォントもやや太めに表示される。そのためデザイナーは、"普通" の見た目を出すためにより細いフォントを使う傾向があると述べ、関連リンクを共有 https://news.ycombinator.com/item?id=23553486著者はダークモードで編集・整形していたのかもしれないという推測。
edge://flags/#enable-force-darkを適用するとリンクが見やすくなるとも言及投稿者は、リンクとコードブロック内のコメントを読むのに特に余分な努力が必要だったと指摘し、コントラストを上げるよう提案。コンテンツ自体の質は非常に高かったと評価
Webサイトがテキストにアルファ透明度を使って、深刻なコントラスト低下を招いているのは大きなミスだという批判
この文章は実際には「Nvidia GPUについての基本的な事実」に近い題名のほうがよいのではないかという提案。WARP という用語も最新の Nvidia GPU の特徴であり、2003年ごろの Nvidia GPU はビデオゲームのレンダリング専用ハードウェアだったため、今日の汎用計算GPUとは完全に異なるという背景説明。結局のところ、この投稿の内容はすべてのGPUに当てはまる一般論ではない、というまとめ
本当に良い入門資料なので感謝するという意見。AI PCを自作する際にGPUについて何日も調べたが、この記事は必ず知っておくべき核心と高付加価値の応用分野(生成AI)までうまく整理されていて大いに助けられたという後日談。特に A100 GPU のメモリ階層構造の図が非常に有用だったと評価
ASCIIダイアグラムを使っていることへの疑問