GPUとそのプログラム
天野
アクセラレータとは?
AI用DSA(Domain Specific Architecture)
開発 | 名称 | 構成方式 | 用途 |
TPU(Tensor Processing Unit) | シストリックアレイ | ver.3は推論、学習共に可能。 | |
Intel | Spring Crest/Lake Crest | 専用プロセッサのMIMD/NORMA | 学習に特化 |
Intel | Spring Hill | 専用プロセッサのCC-NUMA | 推論に特化 |
Microsoft | BrainWave | Intel社のFPGA(7.6参照) | FPGA内のロジックによりどちらも可能 |
Amazon | AWS Inferentia | 不明 | 推論中心だが学習も可能 |
Baidu | Kunlun(崑崙) | 不明 | 推論用と学習用 |
アリババ | HanGuang(含光) | 不明 | 推論用 |
開発 | 製品名 | 構成 | 用途 |
Intel | NCS2 | VLIW型のプロセッサのUMA+画像処理用アレイ | 推論 |
Edge TPU | TPUの下方展開ならばシストリックアレイ | 推論 | |
Xilinx | VERSAL | MIMD/NORMAのAI用アレイ+FPGA | 推論を中心に広い範囲をカバー |
Gyrfalcon | Lightspeeur 5801/2803 | ロジックインメモリ | 推論 |
Mythic | IPU | フローティングゲートを用いたアナログ計算アレイ | 推論 |
クラウド用
エッジ用
巨大IT企業が直接DSAを作る時代になった!
①GPGPU:General Purpose Computing with GPUグラフィックプロセッサをアクセラレータとして使う
※()内は開発環境
PBSM
PBSM
Thread Processors
PBSM
PBSM
Thread Processors
PBSM
PBSM
Thread Processors
PBSM
PBSM
Thread Processors
PBSM
PBSM
Thread Processors
…
Thread Execution Manager
Input Assembler
Host
Load/Store
Global Memory
GeForce
GTX280
240 cores
GPU (NVIDIA’s GTX580)
512 GPU cores ( 128 X 4 )
768 KB L2 cache
40nm CMOS 550 mm^2
128 Cores
128 Cores
128 Cores
128 Cores
L2 Cache
128個のコアは
SIMD動作をする
4つのグループは
独立動作をする
もちろん、このチップを
たくさん使う
NVIDIAのGPUの名前が訳が分からん問題
CoolChips 2019のKeynoteのスライドより引用
CoolChips 2019のKeynoteのスライドより引用
CUDA/OpenCL
なんといっても本家を見よう
アクセラレータのプログラム
…
ホストのプログラム
Device
…
ホストのプログラム
Device
CPU:Serial Code
Parallel Kernel
KernelA(args);
アクセラレータ
CPU:Serial Code
Parallel Kernel
KernelB(args);
アクセラレータ
ホストのプログラムが準備してアクセラレータのプログラムにデータを渡す
処理が終わったら回収
CUDA、OpenCLはこの考え方を取る
スレッドとスレッドブロック
0
1
2
3
4
5
6
7
Thread Block 0
threadID
…
float x =
input[threadID];
float y=func(x);
output[threadID]=y;
…
0
1
2
3
4
5
6
7
Thread Block 1
…
float x =
input[threadID];
float y=func(x);
output[threadID]=y;
…
0
1
2
3
4
5
6
7
Thread Block N-1
…
float x =
input[threadID];
float y=func(x);
output[threadID]=y;
…
…
各スレッドは同じコードを実行
同一スレッドブロック内のスレッドはバリア同期
_syncthreads();
スレッドブロック間では同期されない。
CUDA threadはスレッドIDを使って各データへ割り付ける
メモリ階層
Thread
Per-thread
Local memory
Block
Per-block
Shared
Memory
…
…
Per-device
Global
Memory
Kernel 0
Kernel 1
Kernelは
順番に実行
ホストのメモリとの間では
cudaMemcpy();
を用いて転送
ログインとサンプルプログラムの実行
まずITCにログイン
https://keio.box.com/s/zd3i2rhx0254bkqms7ky5lq5jbojy2yu
次にGPUが使えるマシンにログイン
今回使うGPU:NVIDIA Pascal
サンプルプログラム (sample1.cu, sample_kernel1.cu)
#include <stdio.h>
#include <stdlib.h>
#include "header.h" // Library files
int main(int argc, char **argv) {
float *h_A, *h_B, *h_C; // variables in the host
float *d_A, *d_B, *d_C; // variables in the device
float result = 0.0f; // results
dim3 dim_grid(LENGTH/BLOCK_SIZE, 1); // For kernel call
dim3 dim_block(BLOCK_SIZE, 1, 1); //
// Allocation in the host memory and Generation of array
h_A = (float *)malloc(sizeof(float) * LENGTH);
h_B = (float *)malloc(sizeof(float) * LENGTH);
h_C = (float *)malloc(sizeof(float) * LENGTH);
for (int i = 0; i < LENGTH; ++i) {
h_A[i] = 1.0f; h_B[i] = 2.0f; h_C[i] = 0.0f; }
host: sample.cu
ホストでの初期化
dim3 dim_grid(LENGTH/BLOCK_SIZE, 1); // For kernel call
ブロックによるグリッドの次元 (2次元(3次元の定義もOK))
ブロック数 dim_grid.x * dim_grid.y
dim3 dim_block(BLOCK_SIZE, 1, 1); //
スレッドによるブロックの次元 (3次元)
スレッド数 dim_block.x * dim_block.y* dim_block.z
…..
Sample1Kernel<<<dim_grid, dim_block>>>(d_A, d_B, d_C);
dim3: 組み込みデバイス変数
<<<… >>> がCUDA独特の記法
Kernel 呼び出し (host: sample1.cu)
//デバイスのメモリ割り当て
cudaMalloc((void **)&d_A, sizeof(float) * LENGTH);
cudaMalloc((void **)&d_B, sizeof(float) * LENGTH);
cudaMalloc((void **)&d_C, sizeof(float) * LENGTH);
// デバイスへのデータコピー
cudaMemcpy(d_A, h_A, sizeof(float) * LENGTH,
cudaMemcpyHostToDevice);
cudaMemcpy(d_B, h_B, sizeof(float) * LENGTH,
cudaMemcpyHostToDevice);
Sample1Kernel<<<dim_grid, dim_block>>>(d_A, d_B, d_C);
メモリ割り当てとデータのコピー
カーネル呼び出しの例
blockIdx.x=0
blockDim.x=4
threadIdx.x=0,1,2,3
idx=0,1,2,3
blockIdx.x=1
blockDim.x=4
threadIdx.x=0,1,2,3
idx=4,5,6,7
blockIdx.x=2
blockDim.x=4
threadIdx.x=0,1,2,3
idx=8,9,10,11
blockIdx.x=3
blockDim.x=4
threadIdx.x=0,1,2,3
idx=12,13,14,15
LENGTH=16, BLOCK_SIZE=4の場合
int idx = blockDim.x * blockId.x + threadldx.x;
により、ローカルindexであるthreadldxをグローバルなidxにマップしている
blockDim は実際のコードでは 32 以上でないとまずい
多い分はメモリアクセス遅延の隠蔽に使われ、有効な場合もある
実行モデル
Kernel
1
Block
(0,0)
Block
(1,0)
Block
(2,0)
Block
(0,1)
Block
(1,1)
Block
(2,1)
Grid1
Device
Host
Kernel
2
Block
(0,0)
Block
(1,0)
Block
(2,0)
Block
(0,1)
Block
(1,1)
Block
(2,1)
Grid2
Thread
(0,0)
…
Thread
(31,0)
Thread
(32,0)
…
Thread
(63,0)
Warp 0
Warp 1
Thread
(0,1)
…
Thread
(31,1)
Thread
(32,1)
…
Thread
(63,1)
Warp 2
Warp 3
Thread
(0,2)
…
Thread
(31,2)
Thread
(32,2)
…
Thread
(63,2)
Warp 4
Warp 5
Block (1,1)
Block内の
32スレッドはWarpという単位で
並列実行される
Kernel: sample1_kernel.cu
__global__ void Sample1Kernel(float *d_A, float *d_B,
float *d_C) {
// Getting its thread id
int thread_id = blockDim.x * blockIdx.x + threadIdx.x;
// Compute sum of array
d_C[thread_id] = d_A[thread_id] + d_B[thread_id];
}
thread_idを使うことで、一重分ループを並列実行することができる
// 結果のホストへのコピー
cudaMemcpy(h_C, d_C, sizeof(float) * LENGTH, cudaMemcpyDeviceToHost);
// デバイスメモリの解放
cudaFree(d_A); cudaFree(d_B); cudaFree(d_C);
// 結果のプリント
for (int i = 0; i < LENGTH; ++i) result += h_C[i];
result /= (float)LENGTH;
printf("result = %f\n", result);
// 終了
free(h_A); free(h_B); free(h_C);
return 0;
}
Post processing (host: sample1.cu)
演習 ex1
ex1_kernel.cu を記述せよ。
for (i=0; i<LENGTH; i++)
C[i] = 0.0;
for(j=0; j<LENGTH; j++)
C[i] += (A[i]-B[j])*(A[i]-B[j]);
ex1.cuのex1Kernelのコメントをはずして実行
CPUとGPUの答が一致するはず
提出:ex1_kernel.cuをkeio.jpに提出
最終レポートと授業の成績