NVIDIA Japan Seminar 2012

1,646 views
1,547 views

Published on

Published in: Technology
0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total views
1,646
On SlideShare
0
From Embeds
0
Number of Embeds
15
Actions
Shares
0
Downloads
0
Comments
0
Likes
0
Embeds 0
No embeds

No notes for slide
  • \n
  • それでは、本日のメニューをご紹介します。\n\n前菜は、CUDA環境構築です。CUDAプログラミングを行うに当たって、何が必要なのかを簡単にご説明します。メイン1皿目には、配列をxxするプログラム、というものをゼロから実装します。メイン2皿目には、C++で実装されたリアルタイムレイトレーシングプログラムをCUDAに移植します。そしてデザートには、簡単にパフォーマンス測定を行います。\n
  • それでは、本日のメニューをご紹介します。\n\n前菜は、CUDA環境構築です。CUDAプログラミングを行うに当たって、何が必要なのかを簡単にご説明します。メイン1皿目には、配列をxxするプログラム、というものをゼロから実装します。メイン2皿目には、C++で実装されたリアルタイムレイトレーシングプログラムをCUDAに移植します。そしてデザートには、簡単にパフォーマンス測定を行います。\n
  • \n
  • さて、CUDAスレッドとカーネルという言葉が出てきましたね。これについて説明します。\n\nGPUは、数万というCUDAスレッドを数百のCUDAコアで並列処理するという実行モデルを持っています。CUDAカーネルは、CUDAスレッドの処理内容を記述するためのもので、__global__という識別子を関数の先頭に付けると、それがCUDAカーネルになります。\n\n= Live =\nCUDAカーネルのスタブを書く\n
  • さて、CUDAスレッドとカーネルという言葉が出てきましたね。これについて説明します。\n\nGPUは、数万というCUDAスレッドを数百のCUDAコアで並列処理するという実行モデルを持っています。CUDAカーネルは、CUDAスレッドの処理内容を記述するためのもので、__global__という識別子を関数の先頭に付けると、それがCUDAカーネルになります。\n\n= Live =\nCUDAカーネルのスタブを書く\n
  • さて、CUDAスレッドとカーネルという言葉が出てきましたね。これについて説明します。\n\nGPUは、数万というCUDAスレッドを数百のCUDAコアで並列処理するという実行モデルを持っています。CUDAカーネルは、CUDAスレッドの処理内容を記述するためのもので、__global__という識別子を関数の先頭に付けると、それがCUDAカーネルになります。\n\n= Live =\nCUDAカーネルのスタブを書く\n
  • さて、CUDAスレッドとカーネルという言葉が出てきましたね。これについて説明します。\n\nGPUは、数万というCUDAスレッドを数百のCUDAコアで並列処理するという実行モデルを持っています。CUDAカーネルは、CUDAスレッドの処理内容を記述するためのもので、__global__という識別子を関数の先頭に付けると、それがCUDAカーネルになります。\n\n= Live =\nCUDAカーネルのスタブを書く\n
  • さて、CUDAスレッドとカーネルという言葉が出てきましたね。これについて説明します。\n\nGPUは、数万というCUDAスレッドを数百のCUDAコアで並列処理するという実行モデルを持っています。CUDAカーネルは、CUDAスレッドの処理内容を記述するためのもので、__global__という識別子を関数の先頭に付けると、それがCUDAカーネルになります。\n\n= Live =\nCUDAカーネルのスタブを書く\n
  • さて、CUDA APIとCUDAの拡張構文についてですが、必要なのは全部でたった4つです。\nGPUは専用のメモリ空間を持っており、このメモリ空間のアロケーションに使用するのがcudaMallocとcudaFreeです。\nまた、CPU側のメモリとの間のデータのコピーはcudaMemcpyというAPIを使用します。\n\nそして、カーネルと呼ばれるGPUプログラムの呼び出しには、CUDAの拡張文法を使用します。\nこのようにアングルブラケットをカーネル関数の前に付け、nvccでコンパイルすると、\nカーネルの呼び出しとして解釈されます。\n\n= Live =\nCUDA APIを使用したホストコードを作成する\n
  • CUDAカーネル内部では、いくつかの特殊な組み込み関数や変数が使用できます。今回は、threadIdxという組み込み変数のみを使用します。この変数は、そのカーネルを実行するCUDAスレッドのIDを格納しており、CUDAスレッドごとに違う変数を取得できます。\n\n= Live =\nカーネルを実装\n関数オブジェクトを実装し、関数に__device__識別子を付ける\n
  • さて、これでCUDAプログラミング環境は整いました。次はいよいよプログラミングの時間です。\n
  • さて、これでCUDAプログラミング環境は整いました。次はいよいよプログラミングの時間です。\n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • さて、これでCUDAプログラミング環境は整いました。次はいよいよプログラミングの時間です。\n
  • さて、これでCUDAプログラミング環境は整いました。次はいよいよプログラミングの時間です。\n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • それでは、メイン2皿目、リアルタイムレイトレーシングについて。\n\n
  • さて、これでCUDAプログラミング環境は整いました。次はいよいよプログラミングの時間です。\n
  • \n
  • \n
  • \n
  • さて、これでCUDAプログラミング環境は整いました。次はいよいよプログラミングの時間です。\n
  • \n
  • それではみなさん、よいプログラミングを!\n
  • NVIDIA Japan Seminar 2012

    1. 1. CUDAプログラミング入門株式会社フィックスターズリードエンジニア 飯塚拓郎
    2. 2. たったひとつでない、 GPUプログラミングの冴えたやり方 ready-to-useなライブラリを使う CUBLAS, CUFFT, CURAND, NPP, etc...ディレクティブ+半自動並列化コンパイラを使う PGI Fortran, OpenACC CUDAプログラミングする
    3. 3. たったひとつでない、 GPUプログラミングの冴えたやり方 ready-to-useなライブラリを使う✓ CUDAプログラミングのメリット CUBLAS, CUFFT, CURAND, NPP, etc... - 柔軟:GPUのロジックを自由に記述でき、細かい制御もできる - 高速:ロジックを命令レベルで最適化可能、データ転送のタイミングも自由ディレクティブ+半自動並列化コンパイラを使う - 最新:GPUの最新のFeatureはまずCUDAに反映される PGI Fortran, OpenACC CUDAプログラミングしよう!
    4. 4. CUDA入門 基礎編
    5. 5. CPU vs GPU CPU GPU ∼数十コア ∼数百コア複雑なコントロール部 単純なコントロール部
    6. 6. CUDAプログラミングモデル Host & Device CUDAアプリケーション CUDA CUDA API C Language 通信 CPU GPU
    7. 7. CUDAプログラミングモデル Kernel → Thread → CUDA Core CUDAカーネルには 各スレッドの処理を記述 CUDAスレッド群は CUDAカーネルを実行論理層 CUDAコアは物理層 CUDAスレッドを順次実行∼数十万ものスレッドを数百のコアで効率良く実行
    8. 8. CUDAプログラミングモデル Thread Hierarchy✓ CUDAのスレッド空間は階層化されている Grid - 全体を構成するのが Grid Block - Grid の中に複数の Block Thread - Thread の中に複数の Thread✓ なぜ? - どんな構成のGPUでも 同等スケールの性能を達成するため - 同期機構を提供しつつ、 スケーラビリティを担保できる
    9. 9. CUDAプログラミング Basic Workflow CPU GPU1.GPUメモリ確保2.入力データ転送3.カーネル呼び出し 4.カーネル実行5.出力データ転送6.GPUメモリ破棄
    10. 10. CUDAプログラミング Basic CUDA API✓ GPUメモリ確保/破棄 - cudaMalloc(void** devPtr, size_t size) - cudaFree(void* devPtr)✓ データ転送 - cudaMemcpy(void* dst, void* src, size_t size, esize cudaMemcpyKind kind)✓ カーネル呼び出し - kernel_function<<<grid_size, block_size>>>(...)
    11. 11. CUDA プログラミング Basic CUDA C Language✓ CUDA C Languageとは? - CUDAのカーネルを書くための言語 - 文法はほぼC/C++、ただし標準Cライブラリ等は使用できない✓ 文法要素 - __global__ void func(...)でfuncがカーネルとしてコンパイルされる - __device__ void func(...)でfuncがデバイス関数 (カーネルから呼び出せる関数)としてコンパイルされる
    12. 12. 配列の足し算
    13. 13. ----左から続く----#include <iostream>#include <vector> // 2. 入力データ転送// 4. カーネル実行 cudaMemcpy(d_a, &a[0], size*sizeof(float), cudaMemcpyHostToDevice);__global__ cudaMemcpy(d_b, &b[0], size*sizeof(float),void vecadd(float *a, float *b, float *c) cudaMemcpyHostToDevice);{ c[threadIdx.x] = a[threadIdx.x] dim3 grid_size = dim3(1, 1, 1); + b[threadIdx.x]; dim3 block_size = dim3(size, 1, 1);} // 3. カーネル呼び出しint main(int argc, char *argv[]){ vecadd<<<grid_size, const int size = 16; block_size>>>(d_a, d_b, d_c); std::vector<float> a(size, 1); std::vector<float> b(size, 1); // 5. 出力データ転送 std::vector<float> c(size, 0); cudaMemcpy(&c[0], d_c, size*sizeof(float), cudaMemcpyDeviceToHost); float *d_a, *d_b, *d_c; // 6. GPUメモリ破棄 // 1. GPUメモリ確保 cudaFree(d_a); cudaMalloc(&d_a, size*sizeof(float)); cudaFree(d_b); cudaMalloc(&d_b, size*sizeof(float)); cudaFree(d_c); cudaMalloc(&d_c, size*sizeof(float)); for (int i=0; i<size; ++i) { ----右へ続く---- std::cout << c[i] << std::endl; }
    14. 14. コンパイル&実行✓ コンパイル環境 - NVIDIA CUDA Driver、 NVIDIA CUDA Toolkitをインストール - CUDAコンパイラ nvccを使う - CUDAヘッダファイルのインクルードや必要なライブラリのリンクは 自動的にやってくれる✓ 実行環境 - *nix環境:cudart.so/dylibへのパスを環境変数LD_LIBRARY_PATHに追加
    15. 15. CUDA入門 応用編
    16. 16. GPUのアーキテクチャは進化するPrev : GT200 Now : Fermi Next : Kepler
    17. 17. その時CUDAプログラマに 何が起こったか?✓ プログラミングモデルが変わった - メモリ空間の統合によって メモリコピー操作が不要になった✓ プログラミングの難易度が変わった - キャッシュによってメモリ局所性を 意識しなくてよくなった✓ 最適化方法が変わった
    18. 18. つまり?✓GPUアーキテクチャと共に、CUDAプログラミングも進化する✓CUDAでそこそこの性能を出すことは、どんどん簡単になってゆく
    19. 19. つまり?✓GPUアーキテクチャと共に、CUDAプログラミングも進化する✓CUDAでそこそこの性能を出すことは、どんどん簡単になってゆく
    20. 20. Unified Virtual Address Space✓ Fermiアーキテクチャ+CUDA4.0ではCPUとGPUのメモリ空間が統合された これにより・・・✓ CPUとGPUのメモリ転送をプログラム中に書かなくて良くなった!✓ 複数GPUを使用する際のメモリ転送をプログラム中に書かなくて良くなった! まさに いいことづくめ
    21. 21. 配列の足し算(簡単編)
    22. 22. #include <iostream>__global__void vecadd(float *a, float *b, float *c){ c[threadIdx.x] = a[threadIdx.x] + b[threadIdx.x];}int main(int argc, char *argv[]){ const int size = 16; float *a, *b, *c; cudaMallocHost(&a, size*sizeof(float)); cudaMallocHost(&b, size*sizeof(float)); for (int i=0; i<size; ++i) { a[i] = b[i] = 1; c[i] = 0; } cudaMallocHost(&c, size*sizeof(float)); dim3 grid_size = dim3(1, 1, 1); dim3 block_size = dim3(size, 1, 1); vecadd<<<grid_size, block_size>>>(a, b, c); for (int i=0; i<size; ++i) std::cout << c[i] << std::endl; 注意:GT200アーキテクチャ すごく簡単
    23. 23. でも、例えば・・・ CPU GPUMemory Kernel たくさん 明らかに遅い ループ
    24. 24. こうするべき CPU GPU Memory Memory Kernel明示的にDevice Memoryにキャッシュ
    25. 25. アーキテクチャを知ることが なぜ重要か?✓今時のCUDAはデータフローが隠 されていて簡単にプログラミングできる Great! But...✓遅いプログラムも簡単にかけてしまう
    26. 26. つまり?✓GPUアーキテクチャと共に、CUDAプログラミングも進化する✓CUDAでそこそこの性能を出すことは、どんどん簡単になってゆく というよりも・・・
    27. 27. つまり?✓GPUアーキテクチャと共に、CUDAプログラミングも進化する✓CUDAでそこそこの性能を出すことは、どんどん簡単になってゆく
    28. 28. FermiアーキテクチャStreaming Multiprocessor Streaming Multiprocessor CUDA Cores CUDA CoresL1 Cache Shared L1 Cache Shared Memory Memory L2 Cache L2 Cache Device Memory PCI Express
    29. 29. I/O性能✓ PCI Express 低速 Streaming Multiprocessor Streaming Multiprocessor - レイテンシ:∼10us, スループット:∼8GB/s - CPU、チップセット、メインメモリ、PCIバスに性能が左右される CUDA Cores CUDA Cores✓ Device Memory 中速 Shared Shared - GDDR5、オンボード/オフチップ L1 Cache Memory L1 Cache Memory - レイテンシ:∼500cycle, スループット:100~200GB/s L2 Cache L2 Cache✓ L2 Cache 中高速 Device Memory (on board, off chip) - 768KB - レイテンシ:∼200cycle PCI Express I/O
    30. 30. CacheとShared Memory✓ 違い - L1/L2キャッシュによるキャッシュは暗黙的に行われる - Shared Memoryはカーネル中で明示的に使う✓ Shared Memoryの使い方 - 変数修飾子__shared__をつける __global__ void kernel(float *ptr) { __shared__ float buf[16]; Shared Memoryの宣言とロード buf[16] = ptr[threadIdx.x]; ... __syncthreads(); 同期命令 ...
    31. 31. 行列の掛け算
    32. 32. 愚直な一手 __global__ void matmul_naive(float *a, float *b, float *c, int matrix_size) { const unsigned int xidx = blockIdx.x * blockDim.x + threadIdx.x; const unsigned int yidx = blockIdx.y * blockDim.y + threadIdx.y; float accumulator = 0.0; for (int i=0; i<matrix_size; ++i) { accumulator += a[yidx*matrix_size+i] * b[i*matrix_size+xidx]; } c[yidx*matrix_size+xidx] = accumulator;1. 計算結果の行列Cの要素ごとに1スレッドを割り当てる - 16x16のスレッドからなる、(matrix_size/16)x(matrix_size/16)のブロッ ク2. xidx, yidxは行列Cの要素の添字になる
    33. 33. 2x2ブロックの4x4行列の計算に 単純化してみる A B C D × =A : & C : &B : & D : & ブロック内でデータを共有できれば 計算に必要な領域は少なくてすむ
    34. 34. Shared Memoryでキャッシュ (Step1) 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 ×1.0 1.0 1.0 1.0 2.0 2.0 ×1.0 1.0 1.0 1.0 2.0 2.0 Shared Memory
    35. 35. Shared Memoryでキャッシュ (Step2) 1.0 1.0 1.0 1.0 × 1.0 1.0 1.0 1.01.0 1.0 1.0 1.0 4.0 4.0 ×1.0 1.0 1.0 1.0 4.0 4.0 Shared Memory
    36. 36. スマートな一手__global__void matmul_shared(float *a, float *b, float *c, int matrix_size){ const unsigned int xidx = blockIdx.x * blockDim.x + threadIdx.x; const unsigned int yidx = blockIdx.y * blockDim.y + threadIdx.y; float accumulator = 0.0; for (int i=0; i<matrix_size; i+=16) { Shared Memoryの宣言とロー __shared__ float sub_a[16][16]; __shared__ float sub_b[16][16]; sub_a[threadIdx.y][threadIdx.x] = a[yidx*matrix_size+(i+threadIdx.x)]; sub_b[threadIdx.y][threadIdx.x] = b[(i+threadIdx.y)*matrix_size+xidx]; Shared Memoryへの __syncthreads(); for (int j=0; j<16; ++j) { accumulator += sub_a[threadIdx.y][j] * sub_b[j][threadIdx.x]; } Shared Memoryからの __syncthreads(); }
    37. 37. まとめ✓CUDAプログラミングは簡単です - プログラミング言語的にはC + α程度、単純なプログラムなら 使用するAPIも10個以内ですむ - 「既存のCのコードをとりあえず動かだけ」すなら移植も楽✓最適化方法はアプリ(問題の性質)に依存する、まずGPUアーキテクチャを理解しよう - メモリI/Oのコストは一見隠されてはいるものの、
    38. 38. Thank you !

    ×