• 検索結果がありません。

GPGPUによる高速画像処理

N/A
N/A
Protected

Academic year: 2021

シェア "GPGPUによる高速画像処理"

Copied!
137
0
0

読み込み中.... (全文を見る)

全文

(1)

~リアルタイム画像処理への挑戦~

名古屋大学大学院情報科学研究科

出口 大輔

(2)
(3)

発表の流れ

GPGPUを始める前に

GPGPUの基礎知識

CUDAの使い方

CUDAを使う前に

プログラミングの予備知識

CUDAを使って“Hello World”

GPGPUにチャレンジ

行列積の計算

テンプレートマッチング

ガウシアンフィルタ

SIFT特徴量の計算

(4)
(5)

GPGPUって?

GPGPUは何の略?

G

eneral-

P

urpose computation on

GPU

s

GPUを汎用計算に利用しようという試み

現在は「GPUコンピューティング」とも呼ばれる

(6)

0 200 400 600 800 1000 1200 1400 1600 1900 1900 1900 1900 1900 1900 1900 1900 1900

CPUとGPUの性能比較

2003

2004

2005

2006

2008

※NVIDIA CUDA Programming Guide 4.0より引用

[GFLOP/s]

NVIDIA GPU

Intel

CPU

Geforece GTX280

Geforece 8800GTX

Quad Core

Xeon 3.2GHz

Core2 Duo

3.0GHz

2007

2009

2010

Geforece GTX480

(7)

GPGPUって?

GPGPUは何の略?

G

eneral-

P

urpose computation on

GPU

s

GPUを汎用計算に利用しようという試み

現在は「GPUコンピューティング」とも呼ばれる

どうしてGPGPUが注目されているのか?

GPUの計算性能が飛躍的に向上

最新のGPUは 1.5 TFLOPS 以上の演算性能(CPUの約10倍)

GPUはCPUと比較して並列計算に優れている

GeForce GTX580 では 512 コアによる並列計算が可能

手頃な価格で入手可能

(8)

GPGPUの活用例

動画像処理

CyberLink PowerDirector 7

ビデオエフェクトのレンダリングを高速化

TMPGEnc 4.0 XPress

フィルター処理・デコード処理の高速化

画像処理

Adobe Photoshop CS4 / CS5,Adobe Premire CS5

各種フィルタ処理の高速化

OpenCV

各種画像処理の高速化

数値計算

MATLAB

FFTの高速化

(9)

GPUの歴史: 1999年以前

1970年 ~ 1990年

初期開発の時代

ソフトウェアによるグラフィックス処理

プログラム可能なGPUに関する初期研究

Ikonas System [1], Pixel Machine [2]

1990年 ~ 1999年

GPU技術の黎明期

3Dグラフィックス・アクセラレータの開発

グラフィックス向けライブラリの開発

OpenGL(1992年~), DirectX(1995年~)

[1] J. N. England, “A system for interactive modeling of physical curved surface

objects,” Proceedings of SIGGRAPH 78, pp.336-340. 1978

[2] M. Potmesil and E. M. Hoffert, “The Pixel Machine: A Parallel Image

Computer,” Proceedings of SIGGRAPH89, pp.69-78, 1989

(10)

GPUの歴史: 1999年~

“GPU”の誕生

NVIDIA社の GeForce 256 の登場

ハードウェア T&L

をサポート

CPU負荷を大幅に削減

グラフィックスパイプライン

ハードウェア固定の処理

自由表現な表現は不可

頂点処理

(ハードウェア固定)

ジオメトリ処理

(クリッピング等)

ラスタライズ処理

画面出力

※Transform & Lighting

ピクセル処理

(11)

GPUの歴史: 2003年~

プログラマブルシェーダの登場

Vertex Shader

頂点座標の変換処理

Pixel Shader

画素の輝度計算処理

シェーダ言語の進化

アセンブラから高級言語へ

Cg, HLSL, GLSL

柔軟な映像表現が可能に

グラフィックス以外への応用

GPGPUへの関心が高まる

Vertex Shader

(プログラマブル)

ジオメトリ処理

(クリッピング等)

ラスタライズ処理

画面出力

Pixel Shader

(プログラマブル)

(12)

GPUの歴史: 2007年~

GPGPUの開発環境の整備

CUDA (NVIDIA)

ATI Stream (AMD)

OpenCL

DirectCompute

GPGPU時代の到来

物理シミュレーション

数値計算

信号解析

画像処理・認識

・・・

Vertex Shader

(プログラマブル)

Geometry Shader

(プログラマブル)

ラスタライズ処理

画面出力

Pixel Shader

(プログラマブル)

統合型シェーダ

(13)

GPUの歴史: 2009年~

Fermiアーキテクチャの登場

汎用計算向けのアーキテクチャ

L1/L2キャッシュの搭載

複数カーネルの同時実行のサポート

倍精度浮動小数点演算の高速化

ECCメモリのサポート

アトミックなメモリ操作の高速化

C++のフルサポート

GPGPU関連のライブラリ&ツールの充実

CUBLAS, CUFFT, Thrust, NPP

Parallel Nsight, Visual Profiler

(14)
(15)

CUDAって何?

CUDA(Compute Unified Device Architecture)

発音は「クーダ」もしくは「キューダ」

NVIDIA社が提供するGPUを利用する

ための統合開発環境

GeForce

8以降のハードウェアで利用可能

グラフィックス処理APIの知識は不要

CPUでプログラムを実行する感覚

C/C++を用いてプログラムの開発が可能

既存のアルゴリズムの移植も比較的容易

(16)

CUDAが利用可能な環境

CUDA対応のグラフィックスカード

OSの対応状況(CUDA 4.0)

Windows XP(32bit, 64bit)

Windows Vista(32bit, 64bit)

Windows Server 2008(32bit, 64bit)

Linux(32bit, 64bit)

Mac OS

GeForce

GTX 580, GTX 480, GTX 260, 8800シリーズ,他

Quadro

Plex 2200 D2, FX 5800, FX 5600, 5000, 6000, 他

Tesla

S2050, C2070, S1070, C1060, S870, D870, C870

※TeslaはHPCに特化した製品であり映像出力を持たない

(17)

CUDAを使うための準備(Windows編)

「CUDA ZONE」から次をダウンロード

NVIDIA Driver

CUDA対応のビデオドライバー

CUDA Toolkit 4.0

コンパイラ(nvcc)

CUBLASやCUFFT

ライブラリ

ドキュメント

CUDA SDK 4.0

サンプル

※ 2011年5月25日にリリース

http://www.nvidia.com/object/cuda_home.html

(18)

CUDAを動かしてみよう!!

Volumetric Particle Shadows

※「CUDA SDK」内のサンプルの実行結果

Image Denoising

(19)
(20)

CUDA対応のGPU(G80, GT200)

ストリーミング・マルチプロセッサ(SM)

8 個のスカラープロセッサ(SP)

16KBの共有メモリ

スレッド間の同期機構

マルチプロセッサ内でのみ可能

マルチプロセッサ間での同期には

CPU処理が必要

GeForce GTX280 の場合

30 基のマルチプロセッサを搭載

1GByte以上のグローバルメモリ

SP

SP

SP

SP

SP

SP

SP

SP

マルチプロセッサ #1

SP

SP

SP

SP

SP

SP

SP

SP

マルチプロセッサ #2

(21)

CUDA対応のGPU(Fermi)

ストリーミング・マルチプロセッサ(SM)

32

個のスカラープロセッサ(SP)

48

KBの共有メモリ

スレッド間の同期機構

マルチプロセッサ内でのみ可能

マルチプロセッサ間での同期には

CPU処理が必要

GeForce GTX480 の場合

15

基のマルチプロセッサを搭載

1GByte以上のグローバルメモリ

SM #1

SP SP

SP

SP

SP

SP

SP SP

SP SP

SP

SP

SP

SP

SP SP

SP SP

SP

SP

SP

SP

SP SP

SP SP

SP

SP

SP

SP

SP SP

SM #2

SP SP

SP

SP

SP

SP

SP SP

SP SP

SP

SP

SP

SP

SP SP

SP SP

SP

SP

SP

SP

SP SP

SP SP

SP

SP

SP

SP

SP SP

(22)

CUDAのプログラミングモデル

GPUの特徴

多数のスレッドが高い並列性をもって処理を実行

GPU のみでプログラムを実行できない

CPUとの連携が不可欠

CUDAにおける計算の流れ

GPUを並列演算可能なデバイスとして扱う

複数のスレッドを同時に実行できる外部CPU

階層的にスレッドを管理

スレッドのまとまり = ブロック

ブロックのまとまり = グリッド

問題を分割して計算する際に便利

(23)

スレッドを3次元的に配置

各スレッドの ID を X, Y, Z の3要素で表現

スレッドのまとまりをブロックとして管理

グリッド

ブロック(0,1,1)

ブロック(0,0,1)

スレッド

(0,0,1)

スレッド

(0,1,1)

スレッド

(0,2,1)

スレッド

(1,0,1)

スレッド

(1,1,1)

スレッド

(1,2,1)

ブロック(1,0,1)

スレッド

(0,0,1)

スレッド

(0,1,1)

スレッド

(0,2,1)

スレッド

(1,0,1)

スレッド

(1,1,1)

スレッド

(1,2,1)

ブロック(1,1,1)

ブロック(0,1,0)

ブロック(1,1,0)

ブロック(1,0,0)

スレッド

(0,0,0)

スレッド

(0,1,0)

スレッド

(0,2,0)

スレッド

(1,0,0)

スレッド

(1,1,0)

スレッド

(1,2,0)

スレッド

(0,0,0)

スレッド

(1,0,0)

スレッド

(2,0,0)

スレッド

(0,1,0)

スレッド

(1,1,0)

スレッド

(2,1,0)

CUDAにおけるスレッド管理

ブロック(0,0,0)

スレッド

(0,0,0)

スレッド

(0,1,0)

スレッド

(0,2,0)

スレッド

(1,0,0)

スレッド

(1,1,0)

スレッド

(1,2,0)

スレッド

(0,0,0)

スレッド

(1,0,0)

スレッド

(2,0,0)

スレッド

(0,1,0)

スレッド

(1,1,0)

スレッド

(2,1,0)

(24)

階層的スレッドの利用方法

一般的な画像処理で利用する場合

ブロック内のスレッド数を決定

16×16 = 256 スレッド

画像内にブロックを配置

ブロック内のスレッド

が各画素を処理

計算範囲の求め方

ブロックID

スレッドID

ブロック内スレッド数

T

00

T

10

T

N0

T

01

ブロック#1

T

0M

T

NM T00 T10 TN0 T01 T0M TNM

(25)

スレッド

(0,0,0)

スレッド

(1,0,0)

スレッド

(2,0,0)

スレッド

(0,1,0)

スレッド

(1,1,0)

スレッド

(2,1,0)

ブロック(0,0,0)

CUDAにおける計算の流れ

ブロック(1,0,0)

ブロック(0,1,0)

ブロック(1,1,0)

処理1

グリッド 1

ブロック (0,0,0)

ブロック (1,0,0)

グリッド 2

処理2

スレッド

(0,0,0)

スレッド

(1,0,0)

スレッド

(2,0,0)

スレッド

(0,1,0)

スレッド

(1,1,0)

スレッド

(2,1,0)

(26)

CUDAのメモリモデル

共有メモリ

スレッド (0,0,0)

ローカルメモリ

レジスタ

スレッド(1,0,0)

ブロック (0,0,0)

共有メモリ

スレッド(0,0,0)

スレッド(1,0,0)

ブロック(1,0,0)

コンスタントメモリ

テクスチャメモリ

グローバルメモリ

ローカルメモリ

レジスタ

ローカルメモリ

レジスタ

ローカルメモリ

レジスタ

(27)

CUDAで利用可能なメモリ

グローバルメモリ

大量のメモリを利用可能

低速なメモリアクセス(400~600クロック必要)

Fermiアーキテクチャではキャッシュ機構(L1/L2)を搭載

共有メモリ

レジスタと同じ速度でアクセス可能

マルチプロセッサ1基あたり16KB(Fermiは最大48KB)

Fermiの場合は一部をL1キャッシュとして利用可能

テクスチャメモリ

キャッシュ機構による高速なアクセスが可能

ハードウェア線形補間や正規化テクスチャ座標が利用可能

コンスタントメモリ

キャッシュ機構による高速なアクセスが可能

マルチプロセッサ1基あたり64KB

(28)

各GPUで使用できる機能の違い

使用できる機能を Compute Capability で区別

現在 1.0 ~ 2.1 のGPUが存在

Compute Capability による機能の違い

1.0:初期リリース

GeForce 8800GTX, Quadro FX 5600, 他

1.1:アトミックなメモリ操作のサポート

GeForce 9800 GTX, Quadro FX 3700, 他

1.3:倍精度浮動小数点演算のサポート

GeForce GTX 280, Quadro FX 5800, 他

2.x :グローバルメモリのキャッシュをサポート

GeForce GTX480,Quadro 5000,他

(29)

Compute Capabilityによる機能の違い

Compute Capability

1.0

1.1

1.2

1.3

2.x

3次元グリッド

×

倍精度浮動小数

×

32ビット整数の

アトミック演算

×

64ビット整数の

アトミック演算

×

単精度浮動小数の

アトミック演算

×

(30)

CUDAにおける制限事項

Compute Capability

1.0

1.1

1.2

1.3

2.x

1ブロックあたりの

スレッド数

512

1024

1マルチプロセッサ

あたりのスレッド数

768

1024

1536

1マルチプロセッサ

あたりのレジスタ数

8192

16384

32768

コンスタントメモリ

64KB

共有メモリ

16KB

48KB

2Dテクスチャ

2

16

×2

15

2

16

×2

16

(31)
(32)

CUDAを使って“Hello World!!”

Hello World!! を表示するサンプル(main.cu)

__global__ void hello( char *data ) {

char *text = "Hello World!!¥n";

data[ threadIdx.x ] = text[ threadIdx.x ]; }

int main( int argc, char *argv[] ) {

char *dData, hData[ 15 ];

cudaMalloc( ( void ** )&dData, sizeof( char ) * 15 ); dim3 nThreads( 15, 1 );

dim3 nBlocks( 1, 1 );

hello<<< nBlocks, nThreads >>>( dData );

cudaMemcpy( hData, dData, sizeof( char ) * 15, cudaMemcpyDeviceToHost ); printf( "%s", hData );

cudaFree( dData ); return( 0 );

(33)

CUDAを使って“Hello World!!”

Hello World!! を表示するサンプル(main.cu)

__global__ void hello( char *data ) {

char *text = "Hello World!!¥n";

data[ threadIdx.x ] = text[ threadIdx.x ]; }

int main( int argc, char *argv[] ) {

char *dData, hData[ 15 ];

cudaMalloc( ( void ** )&dData, sizeof( char ) * 15 ); dim3 nThreads( 15, 1 );

dim3 nBlocks( 1, 1 );

hello<<< nBlocks, nThreads >>>( dData );

cudaMemcpy( hData, dData, sizeof( char ) * 15, cudaMemcpyDeviceToHost ); printf( "%s", hData ); cudaFree( dData ); return( 0 ); }

_

_global_

_, threadIdx

dim3, <<<…>>>

CUDAで拡張された部分

(34)

CUDAを使って“Hello World!!”

Hello World!! を表示するサンプル(main.cu)

__global__ void hello( char *data ) {

char *text = "Hello World!!¥n";

data[ threadIdx.x ] = text[ threadIdx.x ]; }

int main( int argc, char *argv[] ) {

char *dData, hData[ 15 ];

cudaMalloc( ( void ** )&dData, sizeof( char ) * 15 ); dim3 nThreads( 15, 1 );

dim3 nBlocks( 1, 1 );

hello<<< nBlocks, nThreads >>>( dData );

cudaMemcpy( hData, dData, sizeof( char ) * 15, cudaMemcpyDeviceToHost ); printf( "%s", hData );

cudaFree( dData ); return( 0 );

(35)

CUDAの言語拡張(1)

言語拡張により追加された修飾子

関数に対する修飾子

変数に対する修飾子

_ _global_ _

CPU から呼び出され,GPU で実行される関数

_ _device_ _

GPU から呼び出され,GPU で実行される関数

_ _host_ _

CPU から呼び出され,CPU で実行される関数

_ _constant_ _ GPU 上のコンスタントメモリに存在する変数

_ _shared_ _

GPU 上の共有メモリに存在する変数

_ _device_ _

GPU 上のメモリに存在する変数

(36)

CUDAを使って“Hello World!!”

Hello World!! を表示するサンプル(main.cu)

__global__ void hello( char *data ) {

char *text = "Hello World!!¥n";

data[ threadIdx.x ] = text[ threadIdx.x ]; }

int main( int argc, char *argv[] ) {

char *dData, hData[ 15 ];

cudaMalloc( ( void ** )&dData, sizeof( char ) * 15 ); dim3 nThreads( 15, 1 );

dim3 nBlocks( 1, 1 );

hello<<< nBlocks, nThreads >>>( dData );

cudaMemcpy( hData, dData, sizeof( char ) * 15, cudaMemcpyDeviceToHost ); printf( "%s", hData );

cudaFree( dData ); return( 0 );

(37)

CUDAの言語拡張(2)

CUDAで利用可能な組み込み型

GPU内のスレッドを識別する組み込み変数

dim3

整数 x, y, z からなる 3 次元ベクトル

(スレッド数やブロック数の指定に利用)

uchar2, int2, float2, …

x, y からなる 2 次元ベクトル

uchar3, int3, float3, …

x, y, z からなる 3 次元ベクトル

uchar4, int4, float4, …

x, y, z, w からなる 4 次元ベクトル

gridDim

グリッドの次数

blockIdx

スレッドが属するブロックのインデックス

blockDim

スレッドが属するブロックの次数

(38)

gridDim

= 2×2×2

スレッドを識別する組み込み変数

グリッド

ブロック(0,1,1)

ブロック(1,0,1)

スレッド

(0,0,1)

スレッド

(0,1,1)

スレッド

(0,2,1)

スレッド

(1,0,1)

スレッド

(1,1,1)

スレッド

(1,2,1)

ブロック(1,1,1)

ブロック(0,1,0)

ブロック(1,1,0)

ブロック(1,0,0)

スレッド

(0,0,0)

スレッド

(0,1,0)

スレッド

(0,2,0)

スレッド

(1,0,0)

スレッド

(1,1,0)

スレッド

(1,2,0)

スレッド

(0,0,0)

スレッド

(1,0,0)

スレッド

(2,0,0)

スレッド

(0,1,0)

スレッド

(1,1,0)

スレッド

(2,1,0)

ブロック(0,0,1)

スレッド

(0,0,1)

スレッド

(0,1,1)

スレッド

(0,2,1)

スレッド

(1,0,1)

スレッド

(1,1,1)

スレッド

(1,2,1)

ブロック

(0,0,0)

blockIdx

blockDim

= 3×2×2

スレッド

(0,0,0)

スレッド

(0,1,0)

スレッド

(0,2,0)

スレッド

(1,0,0)

スレッド

(1,1,0)

スレッド

(1,2,0)

スレッド

(1,0,0)

スレッド

(2,0,0)

スレッド

(0,1,0)

スレッド

(1,1,0)

スレッド

(2,1,0)

スレッド

(0,0,0)

threadIdx

(39)

C/C++プログラミングとの相違点

CPUとGPUで実行するコードを明確に区別

CPUで実行する場合は “_ _host_ _”(省略可)を付与

GPUで実行する場合は “_ _global_ _” を付与

CPUとGPUで処理可能なメモリ空間の違い

CPUとGPU間でメモリ転送が必要

スレッド数を指定してGPU上の関数を呼び出し

関数名<<< ブロック数, スレッド数 >>>( … );

ブロック数とスレッド数は実行時に指定可能

問題サイズに合わせて調整可能

(40)

CUDAを使って“Hello World!!”

Hello World!! を表示するサンプル(main.cu)

__global__ void hello( char *data ) {

char *text = "Hello World!!¥n";

data[ threadIdx.x ] = text[ threadIdx.x ]; }

int main( int argc, char *argv[] ) {

char *dData, hData[ 15 ];

cudaMalloc( ( void ** )&dData, sizeof( char ) * 15 ); dim3 nThreads( 15, 1 );

dim3 nBlocks( 1, 1 );

hello<<< nBlocks, nThreads >>>( dData );

cudaMemcpy( hData, dData, sizeof( char ) * 15, cudaMemcpyDeviceToHost ); printf( "%s", hData ); cudaFree( dData ); return( 0 ); }

GPU上で実行される関数

GPU上のメモリを解放

CPUからGPUへメモリ転送

GPU上にメモリを確保

ブロック内に配置する

スレッド数 15×1 = 15

総ブロック数 1×1 = 1

(41)

スレッドのレジスタ数を調査

Visual Studio 2008 コマンドプロンプトを起動

コマンドプロンプト上で main.cu をコンパイル

合計レジスタ数をチェック

C:¥Your¥Path>nvcc main.cu --ptxas-options=-v --compile

main.cu

ptxas info : Compiling entry function '_Z5helloPc' for 'sm_10'

ptxas info :

Used 2 registers

, 8+16 bytes smem, 15 bytes cmem[0]

レジスタ数

2 × 15 = 30

(42)
(43)

Thrustライブラリって?

CUDAで利用できるテンプレートライブラリ

CUDAとOpenMPに対応したC++ STLの並列版

URL: http://thrust.googlecode.com/

Thrustライブラリの特徴

コンテナ

CPUとGPUのデータをコンテナとして管理

アルゴリズム

コンテナに対してアルゴリズムを適用可能

直感的なインターフェース

CUDAの複雑なAPIに関する知識は不要

メモリの確保/解放/転送がとても簡単

(44)

Thrustライブラリの使い方

GPU上で「1,2,3,…,100」の総和を計算

#include <thrust/host_vector.h> #include <thrust/device_vector.h> #include <thrust/sequence.h> #include <thrust/reduce.h>

int main( int argc, char *argv[] ) {

// CPU上のメモリを確保

thrust::host_vector< int > hvec( 100 );

// データを初期化 1, 2, 3, ...

thrust::sequence( hvec.begin( ), hvec.end( ), 1 );

// GPU上のメモリを確保

thrust::device_vector< int > dvec( 100 );

// CPU -> GPU のメモリ転送

dvec = hvec;

int val = thrust::reduce( dvec.begin( ), dvec.end( ), 0, thrust::plus< int >( ) );

printf( "%d¥n", val );

return( 0 ); }

(45)

コンテナの使い方(1)

メモリの確保

thrust::host_vector< int > hvec( 20 );

CPU上のメモリに要素数20の「int」配列を確保

thrust::device_vector< float > dvec( 100 );

GPU上のメモリに要素数100の「float」配列を確保

メモリの解放

コンテナオブジェクトの消滅時に自動解放

CPUの場合:free( )

GPUの場合:cudaFree( )

明示的なメモリ解放:dvec.clear( )

(46)

コンテナの使い方(2)

メモリの転送

代入操作でメモリ転送が可能

コンテナの各要素へのアクセス

配列と同様にアクセス可能

thrust::host_vector< float >

hvec( 20 );

thrust::device_vector< float > dvec( 20 );

dvec = hvec;

hvec[ 5 ] = 100.0f;

dvec[ 1 ] = 23.4f;

(47)

イテレータ(1)

コンテナの要素を指すポインタ

STLのイテレータと同じように使用可能

thrust::device_vector< int > dvec( 4 );

thrust::device_vector< int >::iterator ite = dvec.begin( );

0

1

2

3

dvec.begin( )

dvec.end( )

dvec.begin( ) + 3

(48)

イテレータ(2)

コンテナの各要素へのアクセス

thrust::device_vector< int > dvec( 4 );

thrust::device_vector< int >::iterator ite = dvec.begin( );

*ite = 10;

++ite;

*ite = 25;

dvec[ 0 ] = 10 と等価

(49)

アルゴリズムと使用方法

CPUとGPUのデータに対して同じ様に適用可能

GPUの場合は

GPUを使用して並列処理

される

アルゴリズムの使用例

コンテナのデータすべてに1を代入する

thrust::fill( dvec.begin( ), dvec.end( ), 1 );

コンテナのデータに 1, 2, 3, … の値で初期化する

thrust::sequence( dvec.begin( ), dvec.end( ) );

1 1 1 1

1 1 1 1

(50)

Parallel Reduction(1)

コンテナ内の要素を1つの値に集約する処理

GPUのスレッドが並列に処理を実行

例:総和計算の場合

36

1 2 3 4 5 6 7 8

GPU上のデータ

3

7

11

15

10

26

36

CUDAスレッドの同期

CUDAスレッドの同期

(51)

Parallel Reduction(2)

Thrustライブラリを用いた集約処理

総和の計算

int val = thrust::reduce( dvec.begin( ), dvec.end( ) );

最大値の計算

int val = thrust::reduce( dvec.begin( ), dvec.end( ),

0, thrust::maximum< int >( ) );

1 2 3 4 5 6 7

10

55

(52)

CUDAとThrustを組み合わせる

Hello World!! を表示するサンプル

#include <thrust/host_vector.h> #include <thrust/device_vector.h> __global__ void hello( char *data ) {

char *text = "Hello World!!¥n";

data[ threadIdx.x ] = text[ threadIdx.x ]; }

int main( int argc, char *argv[] ) {

thrust::host_vector< char > hvec( 15 ); thrust::device_vector< char > dvec( 15 ); dim3 nThreads( 15, 1 );

dim3 nBlocks( 1, 1 );

hello<<< nBlocks, nThreads >>>( thrust::raw_pointer_cast( &dvec[ 0 ] ) ); hvec = dvec; printf( "%s", &hvec[ 0 ] ); return( 0 ); }

CPUからGPUへメモリ転送

GPU上のメモリを指す

ポインタを取得

(53)
(54)

GPGPUにチャレンジ

行列積の計算

CUDAにおける基本的な実装方法

共有メモリ

テンプレートマッチング

2次元テクスチャメモリ

ハードウェア線形補間

正規化テクスチャ座標

Parallel Reduction アルゴリズム

ガウシアンフィルタ

1次元テクスチャメモリ

コンスタントメモリ

SIFT特徴量の計算

1・2次元テクスチャメモリ

コンスタントメモリ

(55)
(56)

GPUによる行列積の計算

行列積計算の特徴

各要素はすべて独立に計算可能

GPUによる並列計算が可能

同じメモリ領域へ頻繁にアクセス

メモリアクセス速度がボトルネック

GPU上での実装方法

1.

行列積の計算式に忠実な実装(GPU#1)

2.

共有メモリを利用した高速化(GPU#2)

ブロック単位(部分行列)で行列積を計算

共有メモリをキャッシュとして利用

(57)

行列積C=A×Bの流れ

1.

行列A, B, CのメモリをGPU上に確保

2.

行列A, BのデータをGPUに転送

3.

行列Cの各要素c

mn

を計算

N 次正方行列を仮定

ただし,N は16の倍数

4.

計算結果をCPUに転送

N

k

kn

mk

mn

a

b

c

1

行列A

行列C

行列B

mn

c

mk

a

kn

b

m行

n列

N

N

(58)

処理の流れ

CPU側の処理

行列の初期化

GPU側の処理

行列Cの各要素を計算

CPU

GPU

GPU

CPU GPU

GPU CPU

(59)

int main( int argc, char *argv[] )

{

int N = 512; // 行列A, B, Cのサイズ

float *hA, *hB, *hC; // CPU(host)側で利用するメモリへのポインタ

float *dA, *dB, *dC; // GPU(device)側で利用するメモリへのポインタ

/* CPU側のメモリを確保*/

/* GPU側のメモリを確保*/

/* CPU側のメモリをGPU側へ転送 */

/* 実行するGPUのスレッド数,ブロック数を設定 */

/* GPUのカーネルを実行し,C=A×B の結果を dC に格納 */

/* GPUの計算結果をCPU側へ転送 */

/* CPUとGPUそれぞれのメモリを解放 */

return( 0 );

}

行列積計算のCPU処理(ソース)

(60)

行列積計算のCPU処理(1)

行列積の計算で使用する変数

CPUとGPUでメモリを確保

CPU:malloc, new, cudaMallocHost でメモリを確保

GPU:cudaMalloc によりグローバルメモリを確保

int N = 512; // 行列A, B, Cのサイズ

float *hA, *hB, *hC; // CPU(host)側で利用するメモリへのポインタ float *dA, *dB, *dC; // GPU(device)側で利用するメモリへのポインタ

/* CPU側のメモリを確保 */

hA = ( float * )malloc( N * N * sizeof( float ) ); hB = ( float * )malloc( N * N * sizeof( float ) ); hC = ( float * )malloc( N * N * sizeof( float ) ); /* GPU側のメモリを確保 */

cudaMalloc( ( void ** )&dA, N * N * sizeof( float ) ); cudaMalloc( ( void ** )&dB, N * N * sizeof( float ) ); cudaMalloc( ( void ** )&dC, N * N * sizeof( float ) );

(61)

行列積計算のCPU処理(2)

CPUとGPU間のメモリ転送

CPU→GPU

cudaMemcpy( dst, src, size, cudaMemcpyHostToDevice );

GPU→CPU

cudaMemcpy( dst, src, size, cudaMemcpyDeviceToHost );

CPUとGPUで確保したメモリの解放

CPU:free, delete, cudaFreeHost でメモリを解放

GPU:cudaFree でグローバルメモリを解放

転送先

転送バイト数

転送元

転送方向

(62)

行列積計算のCPU処理(3)

GPU上で処理を実行

計算範囲の設定

N 次正方行列を仮定

ただし N は16の倍数

行列Cをサイズ16×16のブロックに分割

ブロック内の各要素を各スレッドが計算

合計M×M 個のブロックを配置

各スレッドが c

mn

を計算

n = threadIdx.x + blockIdx.x × blockDim.x;

m = threadIdx.y + blockIdx.y × blockDim.y;

GPU上で関数を実行

multiply<<< ブロック数, スレッド数 >>>( … );

行列C

16

16

(threadIdx.y, threadIdx.x)

(blockIdx.y, blockIdx.x)

行方向のブロック数 (=16)

(63)

int main( int argc, char *argv[] )

{

int N = 512; // 行列A, B, Cのサイズ

float *hA, *hB, *hC; // CPU(host)側で利用するメモリへのポインタ

float *dA, *dB, *dC; // GPU(device)側で利用するメモリへのポインタ

/* CPU側のメモリを確保*/

/* GPU側のメモリを確保*/

/* CPU側のメモリをGPU側へ転送 */

/* 実行するGPUのスレッド数,ブロック数を設定 */

dim3 nThreads( 16, 16 );

dim3 nBlocks( N / nThreads.x, N / nThreads.y );

/* GPUのカーネルを実行し,C=A×B の結果を dC に格納 */

multiply<<< nBlocks, nThreads >>>( dA, dB, dC, N );

/* GPUの計算結果をCPU側へ転送 */

/* CPUとGPUそれぞれのメモリを解放 */

return( 0 );

}

行列積計算のCPU処理(まとめ)

GPU上で処理を実行

GPU上のメモリを指定

(64)

実装方法(GPU#1)

行列積C=A×Bの各要素c

mn

を計算

要素c

mn

をGPU上で並列計算

__global__ void multiply1( float *dA, float *dB, float *dC, int N )

{

int n = threadIdx.x + blockIdx.x * blockDim.x;

int m = threadIdx.y + blockIdx.y * blockDim.y;

float sum = 0.0f;

for( int k = 0 ; k < N ; k++ )

{

sum += dA[ m + k * N ] * dB[ k + n * N ];

}

dC[ m + n * N ] = sum;

}

N

k

kn

mk

mn

a

b

c

1

行列A

行列C

行列B

mn

c

mk

a

kn

b

m行

N

N

(65)

各要素c

mn

を部分行列(ブロック)の積で計算

ブロックサイズが16×16の場合

ブロック内で同じメモリを参照

高速な共有メモリを利用

グローバルメモリへの

アクセスを削減

2×N×16

2

2×N×16

実装方法(GPU#2)

 

16

1

)

1

(

16

16

N

t

t

t

k

kn

mk

mn

a

b

c

行列A

行列C

行列B

mn

c

mk

a

kn

b

N

N

16

16

16

16

16分の1

(66)

実装方法(GPU#2)

__global__ void multiply2( float *dA, float *dB, float *dC, int N )

{

int n = threadIdx.x + blockIdx.x * blockDim.x;

int m = threadIdx.y + blockIdx.y * blockDim.y;

float sum = 0.0f;

for( int k = 0 ; k < N ; k += 16 )

{

__shared__ float tA[ 16 ][ 16 ];

__shared__ float tB[ 16 ][ 16 ];

tA[ threadIdx.y ][ threadIdx.x ] = dA[ m + ( k + threadIdx.x ) * N ];

tB[ threadIdx.x ][ threadIdx.y ] = dB[ ( k + threadIdx.y ) + n * N ];

__syncthreads( );

for( int t = 0 ; t < 16 ; t++ )

{

sum += tA[ threadIdx.y ][ t ] * tB[ threadIdx.x ][ t ];

}

__syncthreads( );

}

dC[ m + n * N ] = sum;

}

共有メモリの宣言

ブロック内のスレッドを同期

ブロック内のスレッドを同期

各スレッドが独立

して行列積を計算

各スレッドが独立して

メモリアクセス

行列A 行列C 行列B mn

c

mk

a

kn

b

N

N

16 16 16 16

(67)

計算時間の比較(1)

CPU, GPU#1, GPU#2 の計算時間を比較

使用計算機

CPU: Intel Core i7-X980(3.33GHz)

OpenMPを使用して6スレッドで並列計算

GPU: NVIDIA Geforce GTX480

マルチプロセッサ数: 15(SP数 = 15×32 = 480)

Memory:1.5 GB

(68)

0 50 100 150 200 250 300 350 400 450 500 16 32 48 64 80 96 11 2 12 8 14 4 16 0 17 6 19 2 20 8 22 4 24 0 27 2 28 8 30 4 32 0 33 6 35 2 36 8 40 0 41 6 43 2 44 8 46 4 48 0 49 6 52 8 54 4 56 0 59 2 60 8 62 4 65 6 67 2 68 8 72 0 73 6 75 2 76 8 78 4 80 0

計算時間の比較(2)

行列のサイズ (N

次正方行列)

[msec.]

CPU

GPU#1

GPU#2

N = 560

計算時間(N

= 560 の場合)

・CPU

#1

: 91.7 msec.

・GPU#1: 18.3 msec. (×5.0)

・GPU#2:

0

6.7 msec. (

×13.7

)

(69)

まとめ(行列積)

GPU上での実装方法

1.

行列積の計算式に忠実な実装(GPU#1)

2.

共有メモリを利用した高速化(GPU#2)

大きさ16×16の部分行列積に分割

共有メモリをキャッシュとして利用

CPUとGPUそれぞれでの計算速度を比較

CPU < GPU#1 < GPU#2

メモリアクセスの工夫により約34倍の高速化

(70)
(71)

テンプレートマッチング(1)

画像処理の分野で広く用いられている手法

基板の品質検査

画像中の特定物体(人物など)の検出

入力画像中からテンプレートに類似する部分を探索

入力画像中に窓を設定

窓を移動させながら

類似度を評価

テンプレート

比較

(72)

テンプレートマッチング(2)

基本的なテンプレートマッチングの戦略

入力画像中の部分画像とテンプレートの類似度を評価

窓を移動させながら類似度計算

位置(X軸方向, Y軸方向)

拡大/縮小(スケール変化)

回転

類似度の例

SAD(Sum of Absolute Difference)

SSD(Sum of Squared Difference)

NCC(Normalized Cross Correlation)

窓の大きさに比例して計算コストが増加

膨大な回数の類似度評価が必要

(73)
(74)

テンプレートマッチングの実装方法

テンプレートマッチングの特徴

類似度は各位置で独立に計算可能

GPUによる並列計算が可能

頻繁に画像メモリへアクセス

テクスチャメモリのキャッシュ機能を利用

複数スケールでテンプレートと入力画像を比較

正規化テクスチャ座標とハードウェア線形補間の利用

解像度に依存しないメモリアクセス

・・・

スケール

(75)

処理の流れ

CPU側の処理

入力画像とテンプレートの読み込み

GPUスレッドの同期

GPU側の処理

類似度計算

スケールの変更

CPU

GPU

GPU

CPU

CPU GPU

GPU CPU

(76)

テクスチャメモリとは?

GPU上の読み取り専用の特殊なメモリ領域

キャッシュを利用した高速なアクセスが可能

2次元アクセスに対して効率的

ハードウェアを利用した高速な線形補間が可能

テクスチャメモリの定義

texture<DataType, Type, ReadMode> texRef;

DataType: データの型(int,float 等)

Type: テクスチャの種類(1次元テクスチャなど)

cudaTextureType1D,cudaTextureType2D,…

ReadMode: 値の範囲

cudaReadModeElementType

: 各データ型の値を使用

cudaReadModeNormalizedFloat

: 0 ~ 1に正規化(符号付きは-1~1)

(77)

メモリ確保と転送(1)

入力画像のメモリをGPU上に確保

テクスチャの定義(画素表現:RGBA)

cudaArrayを利用して2次元テクスチャを確保

CPUからGPUへメモリ転送

テクスチャメモリへの対応付け

cudaArray *imgArray;

cudaChannelFormatDesc c1 = cudaCreateChannelDesc< uchar4 >( );

cudaMallocArray( &imgArray, &c1, width, height );

cudaBindTextureToArray( imgTex, imgArray, c1 );

画素表現の指定

cudaMemcpyToArray( imgArray, 0, 0, pSrc, nBytes, cudaMemcpyHostToDevice );

転送データ量

転送元ポインタ

CPUからGPUへ転送

(78)

メモリ確保と転送(2)

テンプレートのメモリをGPU上に確保

テクスチャの定義

ハードウェア線形補間の有効化

正規化テクスチャ座標の有効化

ハードウェア線形補間の利用(1)

texture< uchar4, cudaTextureType2D, cudaReadModeNormalizedFloat > refTex;

refTex.normalized = 1;

refTex.filterMode = cudaFilterModeLinear;

ハードウェア線形補間の利用(2)

(0, 0)

(W-1, 0)

(0, H-1)

(W-1, H-1)

(0, 0)

(1, 0)

(0, 1)

(1, 1)

(0, 0)

(1, 0)

(0, 1)

(0, 0)

(1, 0)

(0, 1)

(79)

GPU上で類似度計算

__global__ void kernel( float *error, float *scale, int imgW, int imgH,

int areaW, int areaH, int maskW, int maskH, float s ) {

int i = threadIdx.x + blockDim.x * blockIdx.x; int j = threadIdx.y + blockDim.y * blockIdx.y; float err = 0.0f;

float _1_w = 1.0f / maskW; // テンプレートの幅に対するスケーリング係数 float _1_h = 1.0f / maskH; // テンプレートの高さに対するスケーリング係数 for( int n = 0 ; n < maskH ; n++ )

{

for( int m = 0 ; m < maskW ; m++ ) {

uchar4 p1 = tex2D( imgTex, i + m, j + n );

float4 p2 = tex2D( refTex, _1_w * m, _1_h * n ) * 255.0f; err += ( p1.x - p2.x ) * ( p1.x - p2.x );

err += ( p1.y - p2.y ) * ( p1.y - p2.y ); err += ( p1.z - p2.z ) * ( p1.z - p2.z ); }

}

err *= _1_w * _1_h;

if( error[ i + j * imgW ] > err ) {

error[ i + j * imgW ] = err; scale[ i + j * imgW ] = s; } }

テクスチャからデータを読み取り

SSDを計算

SSDが最小のも

のを記録

(80)

CPUによる類似度計算の制御

// スケール&誤差を保持するGPU側のメモリ領域

thrust::device_vector< float > derror( img.size( ), 1.0e10f ); thrust::device_vector< float > dscale( img.size( ), 0 );

float s = MIN_SCALE; while( s <= MSCALE ) {

// 実行するGPUのスレッド数を指定

int W = ( int )img.width( ), H = ( int )img.height( );

int w = ( int )( ref.width( ) * s ), h = ( int )( ref.height( ) * s ); int threadNumX = 16, threadNumY = 16;

int blockNumX = ( W - w ) / threadNumX + ( ( ( W - w ) % threadNumX ) == 0 ? 0 : 1 ); int blockNumY = ( H - h ) / threadNumY + ( ( ( H - h ) % threadNumY ) == 0 ? 0 : 1 ); dim3 nThreads( threadNumX, threadNumY, 1 );

dim3 nBlocks( blockNumX, blockNumY, 1 ); // GPU側で類似度を計算する

kernel<<< nBlocks, nThreads >>>( thrust::raw_pointer_cast( &derror[ 0 ] ), thrust::raw_pointer_cast( &dscale[ 0 ] ), W, H, W - w, H - h, w, h, s ); cudaDeviceSynchronize( ); // 処理が完了するまで待機する s *= SCALE_FACTOR; // スケールを変更する } // GPUの処理結果を転送する

thrust::host_vector< float > herror = derror; thrust::host_vector< float > hscale = dscale;

GPUを用いて類似度を計算

スケールを変更して再探索

(81)

計算時間の比較(1)

CPUとGPUで計算時間を評価

使用計算機

CPU: Intel Core i7-X980(3.33GHz)

OpenMPを使用して6スレッドで並列計算

GPU: NVIDIA GeForce GTX480

マルチプロセッサ数: 15(SP数= 15×32 = 480)

Memory:1.5 GB

(82)

計算時間の比較(2)

実験パラメータ

入力画像 :800×600画素

テンプレート:105×135画素

スケール :0.3~1.9倍(拡大率1.2)

実験結果

CPU: 77.3 sec.

GPU: 2.2 sec.

約 35 倍の高速化

テンプレート

結果画像

(83)

まとめ(テンプレートマッチング)

テクスチャメモリを利用した高速化

キャッシュを利用した効率的なメモリアクセス

解像度に依存しないメモリアクセス

ハードウェア線形補間

正規化テクスチャ座標

CPUとGPUで計算時間を比較

CPU: 77.3 sec.

GPU: 2.2 sec.

約 35 倍の高速化を実現

(84)
(85)

類似度計算の打ち切り処理を導入

各スケールで計算した類似度の最大値をGPUで計算

Parallel Reduction アルゴリズムを利用

スケールの変更

CPU

GPU

GPU

CPU

CPU GPU

GPU CPU

GPU

処理の流れ

(86)

GPU上で類似度計算

__global__ void kernel( float *error, float *scale, int imgW, int imgH,

int areaW, int areaH, int maskW, int maskH, float s, float maxerr ) {

int i = threadIdx.x + blockDim.x * blockIdx.x; int j = threadIdx.y + blockDim.y * blockIdx.y; float err = 0.0f;

float _1_w = 1.0f / maskW; // テンプレートの幅に対するスケーリング係数 float _1_h = 1.0f / maskH; // テンプレートの高さに対するスケーリング係数 for( int n = 0 ; n < maskH ; n++ )

{

for( int m = 0 ; m < maskW ; m++ ) {

uchar4 p1 = tex2D( imgTex, i + m, j + n );

float4 p2 = tex2D( refTex, _1_w * m, _1_h * n ) * 255.0f; err += ( p1.x - p2.x ) * ( p1.x - p2.x );

err += ( p1.y - p2.y ) * ( p1.y - p2.y ); err += ( p1.z - p2.z ) * ( p1.z - p2.z ); }

if( maxerr < err *_1_w * _1_h ) { break; } } ... 以下同じ ... }

前スケールの探索時における

SSDが最小値より大きい場合

は計算を打ち切り

(87)

Parallel Reductionによる最小値探索

GPU上のメモリから最小値を計算

GPUのスレッドが並列に値の比較処理を実行

2

GPU上のデータ

同期

11 4 27 25 5 13 6 9 20 12 14 7 2 19 3 15

4

25

5

6

12

7

2

3

4

5

7

2

4

2

2

同期

同期

(88)

Thrustライブラリの reduce アルゴリズムを利用

thrust::reduce( 先頭, 末尾, 初期値, thrust::minimum<T>( ) );

Parallel Reductionによる最小値探索

GPU上のメモリから最小値を計算

GPUのスレッドが並列に値の比較処理を実行

2

GPU上のデータ

同期

11 4 27 25 5 13 6 9 20 12 14 7 2 19 3 15

4

25

5

6

12

7

2

3

4

5

7

2

4

2

2

同期

同期

先頭を指すイテレータ

末尾を指すイテレータ

(89)

CPUによる類似度計算の制御

// スケール&誤差を保持するGPU側のメモリ領域

thrust::device_vector< float > derror( img.size( ), 1.0e10f ); thrust::device_vector< float > dscale( img.size( ), 0 );

float s = MIN_SCALE, maxerr = 1.0e10f; while( s <= MSCALE )

{

// 実行するGPUのスレッド数を指定

int W = ( int )img.width( ), H = ( int )img.height( );

int w = ( int )( ref.width( ) * s ), h = ( int )( ref.height( ) * s ); int threadNumX = 16, threadNumY = 16;

int blockNumX = ( W - w ) / threadNumX + ( ( ( W - w ) % threadNumX ) == 0 ? 0 : 1 ); int blockNumY = ( H - h ) / threadNumY + ( ( ( H - h ) % threadNumY ) == 0 ? 0 : 1 ); dim3 nThreads( threadNumX, threadNumY, 1 );

dim3 nBlocks( blockNumX, blockNumY, 1 ); // GPU側で類似度を計算する

kernel<<< nBlocks, nThreads >>>( thrust::raw_pointer_cast( &derror[ 0 ] ), thrust::raw_pointer_cast( &dscale[ 0 ] ),

W, H, W - w, H - h, w, h, s, maxerr );

cudaDeviceSynchronize( ); // 処理が完了するまで待機する // 誤差の最大値を取得する

maxerr = thrust::reduce( derror.begin( ), derror.end( ), 1.0e10f, thrust::minimum< float >( ) ); s *= SCALE_FACTOR; // スケールを変更する } // GPUの処理結果を転送する ...

Trustライブラリを用いてGPUを

活用して誤差の最小値を計算

(90)

計算時間の比較(1)

CPUとGPUで計算時間を評価

使用計算機

CPU: Intel Core i7-X980(3.33GHz)

OpenMPを使用して6スレッドで並列計算

GPU: NVIDIA GeForce GTX480

マルチプロセッサ数: 15(SP数= 15×32 = 480)

Memory:1.5 GB

(91)

計算時間の比較(2)

実験パラメータ

入力画像 :800×600画素

テンプレート:105×135画素

スケール :0.3~1.9倍(拡大率1.2)

実験結果

CPU: 8.700 sec.

GPU: 0.258 sec.

約 33.7 倍の高速化

テンプレート

結果画像

(92)

まとめ(テンプレートマッチング)

類似度計算の打ち切り処理により高速化

Parallel Reductionアルゴリズムを利用

CPUとGPUで計算時間を比較

類似度の打ち切り

なし

あり

CPU

77.3 秒

8.7 秒

GPU

2.2 秒

0.258 秒

(93)
(94)
(95)

線形フィルタとは?

画像処理の基本的な処理

ガウシアンフィルタ

LoGフィルタ

ガボールフィルタ

空間フィルタリング(線形フィルタ)

 

 

  

  

h h y w w x

y

j

x

i

f

y

x

h

j

i

g

,

,

,

入力画像

フィルタ

出力画像

注目画素 f(i,j)

フィルタh(i,j)

積和

周辺領域

入力画像

出力g(i,j)

(96)

ガウシアンフィルタ

フィルタ係数に2次元ガウス分布を利用

問題点

フィルタ半径が大きくなるにつれ計算コスト大

フィルタを1次元ガウス分布の積に分解

 





2 2 2

2

exp

2

1

,



y

x

y

x

h

 













2 2 2 22 22

2

exp

2

exp

2

1

2

exp

2

1

,





y

x

y

x

y

x

h

 

  









h h y w w x

y

j

x

i

f

x

y

j

i

g

,

2

exp

2

exp

2

1

,

2 2 2 2



X軸方向への1次元ガウシアンフィルタ

Y軸方向への1次元ガウシアンフィルタ

(97)

ガウシアンフィルタの特徴

フィルタ出力は各画素で独立に計算可能

GPUによる並列計算が可能

フィルタ係数の算出

フィルタ適用前に事前計算が可能

フィルタ適用中は常に同じ値を参照

コンスタントメモリを利用

注目画素の周辺領域(1次元)にアクセス

1次元テクスチャのキャッシュ機能を利用

参照

Outline

関連したドキュメント

Then the strongly mixed variational-hemivariational inequality SMVHVI is strongly (resp., weakly) well posed in the generalized sense if and only if the corresponding inclusion

We see that simple ordered graphs without isolated vertices, with the ordered subgraph relation and with size being measured by the number of edges, form a binary class of

The Mumford–Tate conjecture is a precise way of saying that the Hodge structure on singular cohomology conveys the same information as the Galois representation on ℓ-adic

Taking care of all above mentioned dates we want to create a discrete model of the evolution in time of the forest.. We denote by x 0 1 , x 0 2 and x 0 3 the initial number of

Henson, “Global dynamics of some periodically forced, monotone difference equations,” Journal of Di ff erence Equations and Applications, vol. Henson, “A periodically

Fig.5 The number of pulses of time series for 77 hours in each season in summer, spring and winter finally obtained by using the present image analysis... Fig.6 The number of pulses

Amount of Remuneration, etc. The Company does not pay to Directors who concurrently serve as Executive Officer the remuneration paid to Directors. Therefore, “Number of Persons”

撮影画像(4月12日18時頃撮影) 画像処理後画像 モックアップ試験による映像 CRDレール