いまさらきけないCUDA高速化(2024/12/19)

10.4K Views

December 24, 24

スライド概要

生成AIをはじめ、近年では様々な分野でGPUの活用が進んでいます。

開発環境やライブラリも充実してきており、GPUによる並列計算の恩恵を受けやすくなってきています。 しかしながら、GPU性能をさらに引き出すためには、その動作原理を深く理解する必要があります。

本ウェビナーでは、CUDAプログラミングモデルとハードウェアアーキテクチャについて基礎から解説を行い、高速化の実践例を紹介します。

profile-image

フィックスターズは、コンピュータの性能を最大限に引き出すソフトウェア開発のスペシャリストです。車載、産業機器、金融、医療など、幅広い分野での開発経験があります。また、ディープラーニングや機械学習などの最先端技術にも力を入れています。 並列化や最適化技術を駆使して、マルチコアCPU、GPU、FPGA、量子アニーリングマシンなど、さまざまなハードウェアでソフトウェアを高速化するサービスを提供しています。さらに、長年の経験から培ったハードウェアの知識と最適化ノウハウを活かし、高精度で高性能なアルゴリズムの開発も行っています。       ・開催セミナー一覧:https://www.fixstars.com/ja/seminar   ・技術ブログ :https://proc-cpuinfo.fixstars.com/

シェア

またはPlayer版

埋め込む »CMSなどでJSが使えない場合

(ダウンロード不可)

関連スライド

各ページのテキスト
1.

いまさら聞けない!CUDA高速化入門 Copyright © Fixstars Group

2.

本日のAgenda ⚫ 会社紹介 ⚫ CUDA高速化入門 ○ なぜGPUなのか? ○ CUDAプログラミングモデル ○ ハードウェアアーキテクチャ ○ 高速化実践例 ⚫ Q&A / 告知 Copyright © Fixstars Group 1

3.

発表者紹介 冨田 明彦 平櫛 貴章 ソリューションカンパニー 営業企画 ソリューション第三事業部 エグゼクティブエンジニア 2008年に入社。金融、医療業界において、ソ フトウェア高速化業務に携わる。その後、新規 事業企画、半導体業界の事業を担当し、現職。 2015年に新卒で入社。幅広い産業領域でCPU / GPU を用いたパフォーマンスチューニング業務 に携わる。 Copyright © Fixstars Group 2

4.

フィックスターズの ご紹介 Copyright © Fixstars Group 3

5.

フィックスターズの強み コンピュータの性能を最大限に引き出す、ソフトウェア高速化のエキスパート集団 ハードウェアの知見 アルゴリズム実装力 各産業・研究分野の知見 目的の製品に最適なハードウェアを見抜き、 その性能をフル活用するソフトウェアを開 発します。 ハードウェアの特徴と製品要求仕様に合わ せて、アルゴリズムを改良して高速化を実 現します。 開発したい製品に使える技術を見抜き、実 際に動作する実装までトータルにサポート します。 Copyright © Fixstars Group 4

6.

サービス概要 お客様専任のエンジニアが直接ヒアリングを行い、高速化を実現するために乗り越えるべき 課題や問題を明確にしていきます。 高速化のワークフロー お客様 オリジナルソースコードのご提供 高速化したコード コンサルティング 高速化 サポート 先行技術調査 アルゴリズムの改良・開発 レポートやコードへのQ&A 性能評価・ボトルネックの特定 ハードウェアへの最適化 実製品への組込み支援 レポート作成 Copyright © Fixstars Group 5

7.

サービス提供分野 半導体 産業機器 金融 自動車 ● NAND型フラッシュメモリ向け ファームウェア開発 ● 次世代AIチップの開発環境基盤 生命科学 ● Smart Factory実現への支援 ● マシンビジョンシステムの高速化 ● 自動運転の高性能化、実用化 ● ゲノム解析の高速化 ● 次世代パーソナルモビリティの 研究開発 ● 医用画像処理の高速化 Copyright © Fixstars Group ● デリバティブシステムの高速化 ● HFT(アルゴリズムトレード)の高速化 ● AI画像診断システムの研究開発 6

8.

サービス領域 様々な領域でソフトウェア高速化サービスを提供しています。大量データの高速処理は、 お客様の製品競争力の源泉となっています。 組込み高速化 GPU向け高速化 AI・深層学習 画像処理・ アルゴリズム開発 FPGAを活用した システム開発 分散並列システム開発 量子コンピューティング 自動車向け フラッシュメモリ向け ソフトウェア開発 ファームウェア開発 Copyright © Fixstars Group 7

9.

画像処理アルゴリズム開発 高速な画像処理需要に対して、経験豊富なエンジニアが 責任を持って製品開発をご支援します。 お客様の課題 ご支援内容 高度な画像処理や深層学習等のアルゴリズム を開発できる人材が社内に限られている アルゴリズム調査・改変 課題に合ったアルゴリズム・実装手法を調査 製品実装に向けて適切な改変を実施 機能要件は満たせそうだが、ターゲット機器 上で性能要件までクリアできるか不安 深層学習ネットワーク精度の改善 様々な手法を駆使して深層学習ネットワークの精度を改善 製品化に結びつくような研究ができていない 論文調査・改善活動 論文調査から最先端の手法の探索 性能向上に向けた改善活動を継続 Copyright © Fixstars Group 8

10.

GPU向け高速化 高性能なGPUの本来の性能を十分に引き出し、 ソフトウェアの高速化を実現します。 お客様の課題 ご支援内容 GPUで計算してみたが期待した性能が出ない GPU高速化に関するコンサルティング GPU/CPUを組み合わせた全体として最適な設 CPU・GPU混在環境でのシステム設計 計がしたい アルゴリズムのGPU向け移植 原価を維持したまま機能を追加するため、も う少し処理を速くしたい GPUプログラム高速化 品質確保のため、精度を上げたく演算量は増 えるが性能は維持したい Copyright © Fixstars Group 継続的な精度向上 9

11.

CUDA高速化入門 Copyright © Fixstars Group 10

12.

なぜGPUを使うのか • CPUと比べて • • ピーク性能の高さ 電力効率の良さ 浮動小数点数演算性能 メモリバンド幅 CPU: AMD Ryzen 9 9950X 5.84 [TFLOPS] GPU: NVIDIA GeForce RTX 4070 SUPER 35.48 [TFLOPS] • TDP 価格 87.5 [GB/s] 170 [W] ¥109,000~ 504.2 [GB/s] 220 [W] ¥95,000~ その他のアクセラレータと比べて • • 入手性・価格性能比の良さ プログラミングの容易さ Copyright © Fixstars Group 11

13.

なぜGPUが速いのか • 並列計算に特化した構成 • 大量のコア・演算器 • • • • CPU: AMD EPYC 7763: 64 Cores, 32 FLOPs/Core/cycle GPU: NVIDIA A100: 108 SMs, 128 FLOPs/SM/cycle バス幅の広い広帯域メモリ もちろん弱点もある • • 並列に処理できない問題には弱い 最大メモリ容量が小さい Copyright © Fixstars Group 12

14.

CUDAプログラミングモデル Copyright © Fixstars Group Copyright © Fixstars Corporation

15.
[beta]
例題: saxpy
•

Single-precision ax plus y

•

y←a×x+y

•

CPU向けの実装例:
void saxpy(float *y, const float *x, float a, int n){
for(int i = 0; i < n; ++i){
y[i] = a * x[i] + y[i];
}
}

Copyright © Fixstars Group

14

16.

CUDAを用いたプログラムの流れ • ホストメモリからデバイスメモリへデータを転送 • GPU上でカーネル(プログラム)を実行 • デバイスメモリからホストメモリへデータを転送 Copyright © Fixstars Group 15

17.

ホストメモリとデバイスメモリ • CPUとGPUはそれぞれがメモリを持っている • • 目的に応じて適切なほうを利用する 必要に応じて片方から他方へデータをコピーする ~600 GB/s CPU ホストメモリ (DDR) ~64 GB/s ~3.4 TB/s GPU デバイスメモリ (GDDR/HBM) Copyright © Fixstars Group 16

18.

ホストメモリからデバイスメモリへデータを転送 • cudaMalloc • • • デバイスメモリ上の領域を確保 標準Cにおけるmallocに対応 cudaMemcpy • • デバイスメモリに関係するメモリコピー 第4引数で転送の方向を指定 (HostToDevice, DeviceToHost など) float *d_y, *d_x; // デバイスメモリの確保 cudaMalloc(&d_x, sizeof(float) * n); cudaMalloc(&d_y, sizeof(float) * n); // ホストメモリ (h_x, h_y) から sizeof(float) * n バイト転送 cudaMemcpy(d_x, h_x, sizeof(float) * n, cudaMemcpyHostToDevice); cudaMemcpy(d_y, h_y, sizeof(float) * n, cudaMemcpyHostToDevice); Copyright © Fixstars Group 17

19.

GPU上でカーネル(プログラム)を実行 • カーネルの呼び出し • • • スレッド数を指定する スレッドブロック数×ブロックあたりのスレッド数で表現 ここではループ1回を1スレッドで処理する const int bdim = 128; const int gdim = (n + bdim – 1) / bdim; kernel<<<gdim, bdim>>>(d_y, d_x, a, n); Copyright © Fixstars Group // 切り上げ 18

20.
[beta]
GPU上で動くカーネルの実装
•

__global__ 修飾された関数として定義

•

定義済み変数から自身のインデックスを取得
•
•
•

blockDim: 現在のカーネル実行におけるブロックサイズ
blockIdx: 自身の属するスレッドブロックのインデックス
threadIdx: 自身のスレッドブロック内におけるインデックス
__global__ void kernel(float *y, const float *x, float a, int n){
const int i = threadIdx.x + blockIdx.x * blockDim.x;
if(i < n)
y[i] = a * x[i] + y[i];
}

Copyright © Fixstars Group

19

21.

デバイスメモリからホストメモリへデータを転送 • cudaMemcpyで逆方向にコピー // デバイスメモリ (d_y) から sizeof(float) * n バイト転送 cudaMemcpy(h_y, d_y, sizeof(float) * n, cudaMemcpyDeviceToHost); // デバイスメモリの解放 cudaFree(d_x); cudaFree(d_y); Copyright © Fixstars Group 20

22.

スレッドの階層構造 • CUDAではスレッド間に階層構造がある • 近いスレッド同士はより密に通信・同期を行うことができる Grid Thread Block (~1024T) Warp (32T) … … Copyright © Fixstars Group … 21

23.

スレッドの階層構造 • CUDAではスレッド間に階層構造がある • Warp: 同時に命令が発行されるスレッドをまとめたもの • • Thread Block: いくつかのスレッドをまとめたもの • • • 現行アーキテクチャでは1ブロックあたり最大1024スレッド 同一ワープに属するスレッドは必ず同一スレッドブロックに属する Grid: いくつかのスレッドブロックをまとめたもの • • 現行アーキテクチャでは32スレッド カーネル呼び出しは1つのグリッドで処理される 階層構造上で近いスレッド同士はより密に同期や通信を行うことができる Copyright © Fixstars Group 22

24.

同期・通信: 同一グリッド内 スレッド間の同期 • cooperative_group::sync(grid_group) による同期 • 制約に注意 • • • グリッド中のすべてのスレッドが並行実行されている必要がある カーネル起動時に Cooperative Launch API を使用する必要がある コストも大きいためグリッド単位の同期は避けたほうが良いことが多い スレッド間のデータ交換 • グローバルメモリを用いたデータ共有 Copyright © Fixstars Group 24

25.

同期・通信: 同一スレッドブロック内 スレッド間の同期 • cooperative_group::sync(grid_group) による同期 • __syncthreads() による同期 スレッド間のデータ交換 • グローバルメモリを用いたデータ共有 • シェアードメモリを使ったデータ共有 • グローバルメモリよりはかなり高速にやり取りができる (詳細は後述) Copyright © Fixstars Group 25

26.

同期・通信: 同一ワープ内 スレッド間の同期 • cooperative_group::sync(grid_group) による同期 • __syncthreads() による同期 • __syncwarp() による同期 スレッド間のデータ交換 • グローバルメモリを用いたデータ共有 • シェアードメモリを使ったデータ共有 • Warp Shuffle を用いたデータ交換 • • レジスタからレジスタに直接値を渡す 上の2つよりさらに低コスト Copyright © Fixstars Group 26

27.

メモリの階層構造 • メモリにも階層構造がある • おおむねスレッドの階層構造と対応 Grid Thread Block Thread Registers Local Memory Shared Memory Global Memory Copyright © Fixstars Group Constant Memory 27

28.

メモリの階層構造: レジスタ • プログラム中の自動変数に対応 • 各種演算命令に直接渡すことができる • 他のスレッドとは共有されない Grid Thread Block Thread Registers Local Memory Shared Memory Global Memory Copyright © Fixstars Group Constant Memory 28

29.

メモリの階層構造: ローカルメモリ • プログラム中の自動変数に対応 • 何らかの理由でレジスタに乗せられないときに使用される • 演算命令に渡す際はいったんレジスタにロードする必要がある • 他のスレッドとは共有されない Grid Thread Block Thread Registers Local Memory Shared Memory Global Memory Copyright © Fixstars Group Constant Memory 29

30.

メモリの階層構造: シェアードメモリ • __shared__ 修飾された変数に対応 • 同一スレッドブロック内の全スレッドで共有される Grid Thread Block Thread Registers Local Memory Shared Memory Global Memory Copyright © Fixstars Group Constant Memory 30

31.

メモリの階層構造: グローバルメモリ • cudaMalloc などで確保された領域に対応 • デバイス全体で共有される • カーネル停止後も値が保持される Grid Thread Block Thread Registers Local Memory Shared Memory Global Memory Copyright © Fixstars Group Constant Memory 31

32.

メモリの階層構造: コンスタントメモリ • __constant__ 修飾された変数に対応 • デバイス全体で共有される • カーネルから値を書き換えることができない Grid Thread Block Thread Registers Local Memory Shared Memory Global Memory Copyright © Fixstars Group Constant Memory 32

33.

Unified Memory • グローバルメモリとしてもホストメモリとしても使える領域 • • CPUとGPUどちらからも同じアドレスでアクセスできる 実際にはアクセス時に必要であればコピーされるような動作になる Copyright © Fixstars Group 33

34.

ホストとデバイス間の同期 • カーネル呼び出しやデータ転送は基本的に非同期実行 • 明示的もしくは暗黙的に同期を挿入する必要がある • cudaMemcpy など一部のAPIは自動的に同期を挿入する kernel<<<1, 1>>>(); // この時点では kernel() はまだ実行されていないかもしれない foo(); cudaDeviceSynchronize(); // この時点では kernel() の処理は確実に完了している CPU GPU foo() cudaDeviceSynchronize() kernel() Copyright © Fixstars Group 34

35.

ストリーム • デバイスで実行される処理のキュー • • • 投入した順に処理される 同じストリームに投入された処理同士はオーバーラップしない 指定されなかった場合はデフォルトストリームが使用される kernel1<<<1, 1>>>(); kernel2<<<1, 1>>>(); cudaDeviceSynchronize(); CPU cudaDeviceSynchronize() GPU kernel1() Copyright © Fixstars Group kernel2() 35

36.

ストリーム • ストリームは複数作成することができる • 別ストリームに投入された処理同士は並行するかもしれない kernel1<<<1, 1, 0, stream1>>>(); kernel2<<<1, 1, 0, stream2>>>(); cudaDeviceSynchronize(); CPU Synchronize GPU kernel1() kernel2() Copyright © Fixstars Group 36

37.

ストリーム • ストリームとホスト間で同期をとることもできる • ストリームごとに別のタイミングで同期をとることができる • デバイス全体での同期よりこちらを使うほうが便利なことが多い kernel1<<<1, 1, 0, stream1>>>(); kernel2<<<1, 1, 0, stream2>>>(); cudaStreamSynchronize(stream1); foo(); CPU Synchronize GPU kernel1() foo() kernel2() Copyright © Fixstars Group 37

38.

プログラミングモデルまとめ • 大量のスレッドの間には階層関係がある • • • メモリにも階層関係がある • • • ワープ・スレッドブロック・グリッド 距離に応じて同期や通信の制約が変化する レジスタ・ローカルメモリ・シェアードメモリ・グローバルメモリ 速度や共有する必要があるスレッド数など要求に応じて適切な領域を使い分ける デバイス上で動く処理は基本的に非同期実行となる • 細かい同期周りの制御にはストリームを活用する Copyright © Fixstars Group 38

39.

ハードウェア Copyright © Fixstars Group Copyright © Fixstars Corporation

40.

カーネルが遅い原因と対応策 要求されている演算量が多すぎる • アルゴリズムを改善して演算量を減らす メモリトラフィックが多すぎる • アルゴリズムを改善してメモリアクセスを減らす • キャッシュなどのハードウェア機能を活用する リソースを有効活用できていない • ハードウェアの制約を理解して演算器やバスなどの稼働率を上げる Copyright © Fixstars Group 40

41.

カーネルが遅い原因と対応策 要求されている演算量が多すぎる • アルゴリズムを改善して演算量を減らす メモリトラフィックが多すぎる • アルゴリズムを改善してメモリアクセスを減らす • キャッシュなどのハードウェア機能を活用する リソースを有効活用できていない • ハードウェアの制約を理解して演算器やバスなどの稼働率を上げる Copyright © Fixstars Group 41

42.

Compute Capability (CC) • デバイスの仕様を表す値 • • • • おおむね大きいほど新しい 新しいものがそれ以前の機能をすべて含むとは限らない CCが同じであればチップの規模が違うのみ(コア数・メモリ帯域など) デバイスと対応するCCの例: CC アーキテクチャ デバイスの例 7.0 Volta Tesla V100 など 7.5 Turing GeForce RTX 20xx など 8.0 Ampere NVIDIA A100 など 8.6 Ampere GeForce RTX 30xx など 8.9 Ada Lovelace GeForce RTX 40xx など 9.0 Hopper NVIDIA H100 など Copyright © Fixstars Group 42

43.

NVIDIA A100 Block Diagram • CC 8.0 • 108 SMs/Chip • 6912 FP32 CUDA Cores • コアを活用できるだけの並行実行可能なタスク (=スレッド) を投入する必要がある • スレッド数が足りないならタスクを分割することも視野に入れる https://images.nvidia.com/aem-dam/en-zz/Solutions/data-center/nvidia-ampere-architecture-whitepaper.pdf Copyright © Fixstars Group 43

44.

Streaming Multiprocessor (SM) • スレッドブロックに対応する • • いくつかのスレッドブロックを並行して処理 以下の要素を束ねたもの • • • • • • • CUDA Core Tensor Core LD/ST Unit SFU Register File Cache/Shared Memory Scheduler, Dispatcher Copyright © Fixstars Group 44

45.

Streaming Multiprocessor (SM) • スレッドブロックに対応する • 以下の要素を束ねたもの • • • • • • • CUDA Core Tensor Core LD/ST Unit SFU Register File Cache/Shared Memory Scheduler, Dispatcher 演算器 メモリ Copyright © Fixstars Group 45

46.

Processing Block • ワープに対応する • • いくつかのワープを並行して処理 SMからワープをまたがない要素を分割したもの • • • 各種演算器 レジスタファイル スケジューラ・ディスパッチャ Copyright © Fixstars Group 46

47.

CUDA Core • スレッドに対応する • 何らかの演算を行う • • • レジスタファイルから値を読んで 演算を行って レジスタファイルに書き出す FP32/INT32 • Volta以降でINTコアが分離された • 整数演算と浮動小数点数演算を同時に実行できる Copyright © Fixstars Group 47

48.

Tensor Core • 深層学習向けのアクセラレータ • ワープ単位で協調して小さい行列積を効率よく行う • 世代によって対応する精度・サイズが異なる Copyright © Fixstars Group 48

49.

その他のユニット • LD/ST (Load/Store) • • メモリアクセスを行う SFU (Special Function Unit) • • 特殊関数 (指数関数・三角関数など) の処理を行う 演算器が少ない分スループットも落ちる Copyright © Fixstars Group 49

50.

SIMTとWarp • ディスパッチャはワープに対して一つの命令を一度に発行する • • • SIMT: Single Instruction, Multiple Threads スレッドごとに異なる命令を発行することはできない 条件分岐の取り扱い • • 分岐によって実行の必要がなくなった命令も発行されうる そのような場合はその命令が無視される Copyright © Fixstars Group 51

51.

Warp Divergence • 条件分岐によって有効な演算を行わないスレッド (=コア) が発生する • ワープ内での異なる方向への分岐は性能劣化につながる • • Warp Divergence と呼ぶ 下の例では B(), C() の処理中にコアが半分遊んでいる A() A(); if(threadIdx.x % 2 == 0){ B(); }else{ C(); } B() C() Warp 0 Warp 1 Copyright © Fixstars Group 52

52.

Warp Divergence • できるだけ同じワープのスレッドが同じように動くことで効率を改善できる • • 連続するスレッドが同じ方向に分岐するようにする 下の例では B(), C() におけるコア稼働率が改善している A() A(); if(threadIdx.x < 4){ B(); }else{ C(); } B() C() Warp 0 Warp 1 Copyright © Fixstars Group 53

53.

ワープ内の同期 • ワープ内の各スレッドはそれぞれプログラムカウンタ (PC) を持つ • 分岐した後は明示的に合流させないといけないことがある Copyright © Fixstars Group 54

54.

レイテンシの隠蔽 • 命令のパイプライニングはGPUでも有効 • • 依存性のない命令同士を並列実行する 依存性のない命令の組をどう見つけるか • • 近くにある命令との依存性を解析する 別のスレッドの命令と組み合わせる Copyright © Fixstars Group 55

55.

ワープスケジューリング • Processing Block はいくつかの実行中ワープの状態を保持している • • サイクルごとに実行可能なワープをその中から選択して命令を発行する • • 可能であれば物理コア数より多くのスレッドの状態を保持する 実行可能: 次に発行される命令が依存している処理がすべて完了している 実行可能なワープを絶やさないことが効率改善につながる • • 命令のレイテンシを考慮したプログラムを記述する 実行可能なワープの候補 (=状態を保持しているスレッド数) を増やす Copyright © Fixstars Group 56

56.

レイテンシ隠蔽の例 • 依存性のある加算を4回行うプログラム • • FADDのレイテンシは4とする 並行実行しているワープ数が1の場合: 4 ops / 16 cycles 0x00: FADD R1, R2 0x01: FADD R1, R3 0x02: FADD R1, R4 0x03: FADD R1, R5 Warp 0 R1 += R2 R1 += R3 Copyright © Fixstars Group R1 += R4 R1 += R5 57

57.

レイテンシ隠蔽の例 • 依存性のある加算を4回行うプログラム • FADDのレイテンシは4とする • 並行実行しているワープ数が1の場合: 4 ops / 16 cycles • 並行実行しているワープ数が4の場合: 16 ops / 19 cycles 0x00: FADD R1, R2 0x01: FADD R1, R3 0x02: FADD R1, R4 0x03: FADD R1, R5 Warp 0 Warp 1 Warp 2 Warp 3 R1 += R2 R1 += R3 R1 += R2 R1 += R4 R1 += R3 R1 += R2 R1 += R4 R1 += R3 R1 += R2 Copyright © Fixstars Group R1 += R5 R1 += R3 R1 += R5 R1 += R4 R1 += R4 R1 += R5 R1 += R5 58

58.

Occupancy • SMがいくつのワープを並行実行できるかを表す指標 • • ブロックサイズ・消費レジスタ数・シェアードメモリサイズから求める • • • • 高ければ高いほどレイテンシを隠蔽しやすい ブロックサイズ: SMあたりの並行実行可能なブロック数 消費レジスタ数: SMあたりのレジスタファイル数 シェアードメモリサイズ: SMあたりのシェアードメモリサイズ プロファイラ・CUDA Toolkit 付属のExcelシートなどで求められる Copyright © Fixstars Group 59

59.

メモリ階層 • 上に行くほど高速な代わりに共有される範囲が狭まる SM Processing Block Register File L1 Cache Shared Memory L2 Cache Device Memory Copyright © Fixstars Group 60

60.

デバイスメモリ • グローバルメモリ・ローカルメモリに対応 • GPUのスペックに書かれている容量はこの領域のもの • アクセスパターンによって大きく性能が変わる Copyright © Fixstars Group 61

61.

Coalesce Access • 同一ワープに属するスレッド群のメモリアクセスはまとめて処理される • 範囲が狭ければ1トランザクションにまとめられる 1トランザクション / 1アクセス スレッド メモリ 0 1 2 3 4 5 … 31 … Copyright © Fixstars Group 62

62.

Coalesce Access • 同一ワープに属するスレッド群のメモリアクセスはまとめて処理される • 範囲が狭ければ1トランザクションにまとめられる 2トランザクション / 1アクセス スレッド メモリ 0 1 2 3 4 5 … 31 … Copyright © Fixstars Group 63

63.

Coalesce Access • 同一ワープに属するスレッド群のメモリアクセスはまとめて処理される • 範囲が狭ければ1トランザクションにまとめられる 32トランザクション / 1アクセス スレッド メモリ 0 1 … 2 3 … 4 … … 5 … 31 … … Copyright © Fixstars Group … 64

64.

L2 キャッシュ • デバイス中の全SMで共有されている • デバイスメモリへのアクセス時にはほぼ常に使用される Copyright © Fixstars Group 65

65.

L1キャッシュ • SMごとに用意されている • 明示的に指定したものか読み取り専用のデータへのアクセスに対して使用される 読み取り専用かどうかの判定 • コンパイラが判定する • ポインタを const __restrict__ 修飾すると読み取り専用であることを明示できる 明示的なL1キャッシュの利用 • 組み込み関数 __ldg() を使用する • *ptr → __ldg(ptr) Copyright © Fixstars Group 66

66.

シェアードメモリ • SMごとに用意された領域 • L1キャッシュとシェアードメモリの割合は設定で変更可能 • • シェアードメモリとして使えるのは 16-96 [KB/SM] 程度 残りはL1キャッシュとして使用される Copyright © Fixstars Group 67

67.

メモリバンク • シェアードメモリはバンクを用いてに管理されている • • バンクは4バイトごとに切り替わる 同じバンクの異なる領域へのアクセスはまとめて処理できない: バンクコ ンフリクト Bank 31 Bank 30 Bank 29 Bank 28 Bank 27 Bank 4 Bank 3 Bank 2 Bank 1 Bank 0 0x00000000 0x00000080 … 0x00000100 Copyright © Fixstars Group 68

68.

メモリバンク • まとめて処理できるアクセスの例 • 素直なシーケンシャルアクセス Bank 27 Bank 28 Bank 29 Bank 30 Bank 31 Bank 4 Bank 3 Bank 2 Bank 1 Bank 0 27 28 29 30 31 0x00000000 0x00000080 … 0x00000100 スレッド 0 1 2 3 4 Copyright © Fixstars Group 69

69.

メモリバンク • まとめて処理できるアクセスの例 • バンクが重複しないランダムアクセス Bank 27 Bank 28 Bank 29 Bank 30 Bank 31 Bank 4 Bank 3 Bank 2 Bank 1 Bank 0 27 28 29 30 31 0x00000000 0x00000080 … 0x00000100 スレッド 0 1 2 3 4 Copyright © Fixstars Group 70

70.

メモリバンク • まとめて処理できるアクセスの例 • ブロードキャスト: バンクが重なっても同じアドレスなら問題ない Bank 27 Bank 28 Bank 29 Bank 30 Bank 31 Bank 4 Bank 3 Bank 2 Bank 1 Bank 0 27 28 29 30 31 0x00000000 0x00000080 … 0x00000100 スレッド 0 1 2 3 4 Copyright © Fixstars Group 71

71.

メモリバンク • まとめて処理できないアクセスの例 • ストライドアクセス: この場合は2回に分割される Bank 27 Bank 28 Bank 29 Bank 30 Bank 31 Bank 4 Bank 3 Bank 2 Bank 1 Bank 0 27 28 29 30 31 0x00000000 0x00000080 … 0x00000100 スレッド 0 1 2 3 4 Copyright © Fixstars Group 72

72.

レジスタファイル • プロセッシングブロックごとに用意された領域 • レジスタ幅は1要素あたり32bit • long, double, ポインタなどの64bit型には2つ使われる 自動変数に対する領域割り当て • 自動変数は可能ならレジスタに割り当てられる • 特定のケースで低速なローカルメモリに割り当てられる • • 自動変数がレジスタに収まりきらない場合(レジスタスピル) インデックスアクセスが必要な場合 Copyright © Fixstars Group 73

73.

ハードウェアまとめ 演算器 • 演算器を使い切るためには注意が必要なことがある • • 分岐によって何もしないコアが発生することがある レイテンシを埋めるだけの命令供給が必要 メモリ • アクセスパターン次第で効率が落ちることがある • • • グローバルメモリ: Coalescing, キャッシュ利用 シェアードメモリ: バンクコンフリクト ローカルメモリの利用にも注意する • コンパイラの出力を確認すると確実 Copyright © Fixstars Group 74

74.

実践例 Copyright © Fixstars Group Copyright © Fixstars Corporation

75.

問題の概要 • 画像のステレオマッチング: Semi-Global Matching (SGM) • ステレオ画像の視差を計算するアルゴリズム • • • 視差: 片方の画像のある画素が他方の画像で何ピクセルずれたところにあるか 近くの物体ほど視差が大きくなること利用して距離を計算できる ターゲット環境: Pascal 世代のGPU (GeForce GTX 10xx など) Copyright © Fixstars Group 76

76.

チューニングする部分 • 動的計画法 (DP) である画素における視差が d [px] としたときのスコアを求める • • 対応する画素同士の特徴ベクトルの距離が大きいとコストが大きい 隣接画素に対して急激な視差の変化があるとコストが大きい Copyright © Fixstars Group 77

77.

チューニングする部分 • 動的計画法である画素における視差が d [px] としたときのスコアを求める • • • 対応する画素同士の特徴ベクトルの距離が大きいとコストが大きい 隣接画素に対して急激な視差の変化があるとコストが大きい これを縦横斜めの8方向それぞれについてラインごとに計算する Copyright © Fixstars Group 78

78.

アルゴリズムの概略 左から右方向のスキャン for(int y = 0; y < H; ++y){ int prev_min = 0; for(int x = 0; x < W; ++x){ int cur_min = INT_MAX; for(int d = 0; d < D; ++d){ X方向のループは依存性がある int cost = min({ P2, scost[y][x-1][d-1] - prev_min + P1, 主要な計算は O(HWD) 回行われる scost[y][x-1][d+1] - prev_min + P1, 計算処理はかなり軽い scost[y][x-1][d] - prev_min }); scost[y][x][d] = キャッシュヒットが期待できない cost + dist(left[y][x], right[y][x-d]); メモリアクセスも O(HWD) 回 cur_min = min(prev_min, cost); } prev_min = cur_min; } Y方向のループは完全に独立している } • Copyright © Fixstars Group 79

79.

並列化方針の検討 y方向ループ1回を1スレッドで担当する • 並列度が足りない: 数百スレッド程度しか利用できない y方向ループ1回を複数スレッドで担当する • x方向のループは分割できない: 前のループに対する依存性があるため • d方向のループは分割可能 • ただしx方向のループ1回ごとに同期が必要になる Copyright © Fixstars Group 80

80.

並列化方針の検討: d方向ループの分割 • X方向のループを進めるたびに同期と通信が必要になる • • d方向の最小値を求める 端の値を隣のスレッドに渡す scostの計算 Thread 1 Thread 0 Copyright © Fixstars Group 81

81.

並列化方針の検討: d方向ループの分割 • X方向のループを進めるたびに同期と通信が必要になる • • d方向の最小値を求める 端の値を隣のスレッドに渡す 最小値の計算・共有 Thread 1 Thread 0 Copyright © Fixstars Group 82

82.

並列化方針の検討: d方向ループの分割 • X方向のループを進めるたびに同期と通信が必要になる • • d方向の最小値を求める 端の値を隣のスレッドに渡す 端の値の共有 Thread 1 Thread 0 Copyright © Fixstars Group 83

83.

並列化方針の検討: d方向ループの分割 細かく分割する場合のメリット • スレッド数を増やすことによる Occupancy の向上 • スレッドあたりのレジスタ量の削減 粗く分割する場合のメリット • スレッド間通信などの非本質的な処理の占める割合の減少 • • 1ワープ以内になるとより軽量な通信が利用できる グローバルメモリへのアクセス効率の向上 • • スレッドあたりのメモリアクセス量が多くなる 1回のアクセスで4要素までアクセスできる Copyright © Fixstars Group 84

84.

並列化方針の検討: d方向ループの分割 細かく分割する場合のメリット • スレッド数を増やすことによる Occupancy の向上 • スレッドあたりのレジスタ量の削減 粗く分割する場合のメリット • スレッド間通信などの非本質的な処理の占める割合の減少 • • 1ワープ以内になるとより軽量な通信が利用できる グローバルメモリへのアクセス効率の向上 1ワープを境に実装が大きく変化する • • ⇒ 1ワープ以下の範囲で値を変えつつ試せるように実装する スレッドあたりのメモリアクセス量が多くなる 1回のアクセスで4要素までアクセスできる Copyright © Fixstars Group 85

85.

ハイエンドGPU対策 • ハイエンドGPUだとd方向の分割を入れてもスレッド数が足りない • 8方向それぞれが独立なことを利用して複数カーネルを並行させる Copyright © Fixstars Group 86

86.

アルゴリズム:局所特徴同士の距離の計算 • 局所特徴についての情報 • • • 局所特徴の表現: 64 bit のビット列 局所特徴の距離: 互いに異なるビットの数 特徴ベクトルの距離は同じ組の距離が何度も使われる • • 8方向すべての処理で同じ計算を行う 既存実装では事前計算してテーブル化されていた • table[y][x][d] = distance(left[y][x], right[y][x - d]) Copyright © Fixstars Group 87

87.

理論性能で比べる • テーブル引きと計算どちらが速い? • • • • popcounts/s: 763 [Gops/s] • • • 32 [ops/s/SM] × 28 [SM/s] × 1.481 [GHz] = 1326 [Gops/s] 1要素あたり2回必要なのでその半分 Bytes/s: 484.4 [GB/s] • • テーブル化した場合1要素当たり 1 [byte] 特徴同士の距離は popcount 命令2回で求められる GeForce GTX 1080 Ti (sm_61) を例に試算してみる 実測値だとおよそ 340 [GB/s] くらい 毎回計算するほうが速そう!! Copyright © Fixstars Group 88

88.

プロファイル結果 (1) • 横方向の処理のプロファイル結果 • 演算器の稼働率80%弱: うまくリソースを活用できてそう Copyright © Fixstars Group 89

89.

プロファイル結果 (2) • 本当に距離をテーブル化しないほうが速かったのか? • • 実効メモリ帯域で評価する テーブル引きする場合はメモリトラフィックが Reads = Writes になる • • テーブルサイズが結果バッファのサイズと等しいため 52.049×2 = 104.098 [GB/s] 出せなければテーブル化のほうが遅い • • bandwidthTest での帯域が 92.7 [GB/s] 程度 テーブル化する方針では勝てないだろうと考えられる Copyright © Fixstars Group 90

90.

プロファイル結果 (3) • 縦方向・斜め方向でも同様の傾向 Copyright © Fixstars Group 91

91.

全体の評価 • 既存実装との性能比較 • • • 比較対象: Embedded real-time stereo estimation via Semi-Global Matching on the GPU, D. Hernandez-Juarez et al, ICCS 2016. https://github.com/dhernandez0/sgm 実際にはもう一つ大きいカーネルがあるのですがそちらの詳細は省略して います Copyright © Fixstars Group 92

92.

評価結果 • 2.3-12.5% 程度の高速化 • 演算性能に対してメモリ帯域の細いチップで特に強い フレームレート 300 261 232 200 今回の実装 69.768.1 100 50.245.8 Hernandez+ 0 GTX 1080 Ti GTX 1050 Ti Copyright © Fixstars Group DRIVE PX2 93

93.

まとめ • 演算とメモリアクセスどちらが重要か見極める • • • 理論性能から見積もり 実測で裏付け コアあたりの効率とチップあたりの効率 • • 並列度を下げると演算量は減らしやすい 一方でリソースが余りやすくなるのでうまくバランスをとる Copyright © Fixstars Group 94

94.

全体のまとめ • パフォーマンスチューニングにおいてはハードウェアの知識も重要 • • • カーネルのチューニングにおいては特に演算器とメモリに気を配る • • • 使い方を誤ると数倍の性能劣化なども起こりうる もちろんアルゴリズムも重要で両方からのアプローチが必要 演算器を余らせない 不得意なアクセスパターンによる性能劣化を防ぐ 理論をもとに仮説を立てて実装したものを評価する • • プロファイラによる評価 理論ピークと実性能の差を読み取る Copyright © Fixstars Group 95

95.

9 6 Thank you! お問い合わせ窓口 : [email protected] Copyright © Fixstars Group