2022年9月29日に開催した「CUDA高速化セミナーvol.5 / 画像処理アルゴリズムの高速化2」の当日資料です。
Copyright© Fixstars GroupCUDA高速化セミナー vol.5画像処理アルゴリズムの高速化2
View Slide
Copyright© Fixstars Group本日のAgenda● はじめに● フィックスターズの紹介● 画像処理アルゴリズムの高速化1 のおさらい● バイラテラルフィルタ高速化● 転置● リダクション● まとめ2
Copyright© Fixstars Groupはじめに
Copyright© Fixstars Group本講演の位置づけ● CUDAに関連する様々な技術情報を、CUDA高速化セミナーとして発信しています● 今回は、vol.1で解説したデータ転送・カーネルを書く上での基本を踏まえ、CUDA 特有の計算方法を使った、バイラテラルフィルタ、転置、リダクションの実装例を紹介します● こんな方に向いています○ これから CUDA を使った画像処理をしてみたい○ CUDA カーネルを高速化したい4● vol.1 画像処理アルゴリズムの高速化● vol.2 CUDAアーキテクチャの進化● vol.3 ソフトウェア高速化と深層学習● vol.4 TensorRT化のワークフロー事例紹介
Copyright© Fixstars Group発表者紹介冨田 明彦ソリューションカンパニー執行役員2008年に入社。金融、医療業界において、ソフトウェア高速化業務に携わる。その後、新規事業企画、半導体業界の事業を担当し、現職。5上野 晃司ソリューション第一事業部エグゼクティブエンジニア2016年に入社。スパコンのベンチマークGraph500を「京」「富岳」向けに最適化し世界1位を達成。CUDAやOpenCLを使った画像処理高速化を担当。
Copyright© Fixstars Groupフィックスターズのご紹介
Copyright© Fixstars Groupフィックスターズの強みコンピュータの性能を最大限に引き出す、ソフトウェア高速化のエキスパート集団ハードウェアの知見 アルゴリズム実装力 各産業・研究分野の知見7目的の製品に最適なハードウェアを見抜き、その性能をフル活用するソフトウェアを開発します。ハードウェアの特徴と製品要求仕様に合わせて、アルゴリズムを改良して高速化を実現します。開発したい製品に使える技術を見抜き、実際に動作する実装までトータルにサポートします。
Copyright© Fixstars Groupサービス概要お客様専任のエンジニアが直接ヒアリングを行い、高速化を実現するために乗り越えるべき課題や問題を明確にしていきます。高速化のワークフローコンサルティング先行技術調査性能評価・ボトルネックの特定高速化アルゴリズムの改良・開発ハードウェアへの最適化レポート作成サポートレポートやコードへのQ&A実製品への組込み支援8
Copyright© Fixstars Groupサービス提供分野9半導体自動車産業機器生命科学金融● NAND型フラッシュメモリ向けファームウェア開発● 次世代AIチップの開発環境基盤● 自動運転の高性能化、実用化● 次世代パーソナルモビリティの研究開発● Smart Factory実現への支援● マシンビジョンシステムの高速化● ゲノム解析の高速化● 医用画像処理の高速化● AI画像診断システムの研究開発● デリバティブシステムの高速化● HFT(アルゴリズムトレード)の高速化
Copyright© Fixstars Groupサービス領域様々な領域でソフトウェア高速化サービスを提供しています。大量データの高速処理は、お客様の製品競争力の源泉となっています。10組込み高速化画像処理・アルゴリズム開発分散並列システム開発GPU向け高速化FPGAを活用したシステム開発量子コンピューティングAI・深層学習自動車向けソフトウェア開発フラッシュメモリ向けファームウェア開発
Copyright© Fixstars Group画像処理アルゴリズム開発高速な画像処理需要に対して、経験豊富なエンジニアが責任を持って製品開発をご支援します。お客様の課題高度な画像処理や深層学習等のアルゴリズムを開発できる人材が社内に限られている機能要件は満たせそうだが、ターゲット機器上で性能要件までクリアできるか不安製品化に結びつくような研究ができていないご支援内容深層学習ネットワーク精度の改善様々な手法を駆使して深層学習ネットワークの精度を改善論文調査・改善活動論文調査から最先端の手法の探索性能向上に向けた改善活動を継続アルゴリズム調査・改変課題に合ったアルゴリズム・実装手法を調査製品実装に向けて適切な改変を実施11
Copyright© Fixstars GroupGPU向け高速化高性能なGPUの本来の性能を十分に引き出し、ソフトウェアの高速化を実現します。お客様の課題GPUで計算してみたが期待した性能が出ないGPU/CPUを組み合わせた全体として最適な設計がしたいご支援内容GPU高速化に関するコンサルティングCPU・GPU混在環境でのシステム設計アルゴリズムのGPU向け移植GPUプログラム高速化継続的な精度向上原価を維持したまま機能を追加するため、もう少し処理を速くしたい品質確保のため、精度を上げたく演算量は増えるが性能は維持したい12
Copyright© Fixstars GroupAI・深層学習向け技術支援AIを使うためのハードウェア選定や、高速な計算を実現するソフトウェア開発技術で、お客様の製品開発を支援します。お客様の課題推論精度を維持したまま計算時間を短縮したい組込みデバイス向けにAIモデルを軽量化したいご支援内容AIモデル設計データの前処理・後処理推論精度の改善分散処理による学習高速化モデル圧縮・推論の高速化学習計算を高速化して研究開発を効率化したい精度と計算時間を両立するAIモデルを開発したい13
Copyright© Fixstars Group前回の復習
Copyright© Fixstars GroupNVIDIA Nsight Compute• カーネルプロファイラをサポート15
Copyright© Fixstars Groupガウシアンフィルタ⊗カーネル16
Copyright© Fixstars GroupガウシアンフィルタCUDA化スレッド割り当てブロック(0,0)ブロック(0,1)ブロック(0,2)ブロック(0,3)ブロック(0,4)ブロック(1,0)ブロック(1,1)ブロック(1,2)ブロック(1,3)ブロック(1,4)ブロック(2,0)ブロック(2,1)ブロック(2,2)ブロック(2,3)ブロック(2,4)ブロック(3,0)ブロック(3,1)ブロック(3,2)ブロック(3,3)ブロック(3,4)3232• 1スレッドが出力1ピクセルを担当• ブロックの最大スレッド数は1024なので、1ブロック32x32(=1024スレッド)に設定• 画像全体を覆うようにブロックを起動する17
Copyright© Fixstars Group本日説明するコード● ↓ここにあります● https://github.com/fixstars/CudaOptimizeSample/blob/master/CudaOptimizeSample/kernel.cu● (前回と同じファイルです)18
Copyright© Fixstars Groupバイラテラルフィルタ高速化
Copyright© Fixstars Groupバイラテラルフィルタ高速化• ガウシアンフィルタと同じように、画像と重みを掛け合わせるが、重みが画素値の差によって変わるようにしたもの20
Copyright© Fixstars Group__global__ void BilateralKernelSimple( const uint8_t *src, uint8_t *dst, int width, intheight, int step, float sigma){int x = blockIdx.x * blockDim.x + threadIdx.x;int y = blockIdx.y * blockDim.y + threadIdx.y;if (x < width && y < height) {float coef = 1.0 / sqrtf(2 * 3.1415926f * sigma * sigma);float coef2 = -1.0 / (2 * sigma * sigma);float c_sum = 0;float f_sum = 0;int val0 = src[x + y * step];for (int dy = 0; dy < 3; ++dy) {for (int dx = 0; dx < 3; ++dx) {int val = src[(x + dx) + (y + dy) * step];int diff = val - val0;float w = filter3[dy][dx] * coef * expf(diff * diff * coef2);f_sum += w;c_sum += w * val;}}dst[x + y * step] = (int)(c_sum / f_sum + 0.5f);}}必ず”f”を付ける。付けないと doubleの演算になって、Tesla以外では、かなり遅くなるので注意重みの計算21
Copyright© Fixstars Groupバイラテラルフィルタ高速化カーネルプロファイリング• かなり演算ネック22
Copyright© Fixstars Group__global__ void BilateralKernelSimple( const uint8_t *src, uint8_t *dst, int width, intheight, int step, float sigma){int x = blockIdx.x * blockDim.x + threadIdx.x;int y = blockIdx.y * blockDim.y + threadIdx.y;if (x < width && y < height) {float coef = 1.0 / sqrtf(2 * 3.1415926f * sigma * sigma);float coef2 = -1.0 / (2 * sigma * sigma);float c_sum = 0;float f_sum = 0;int val0 = src[x + y * step];for (int dy = 0; dy < 3; ++dy) {for (int dx = 0; dx < 3; ++dx) {int val = src[(x + dx) + (y + dy) * step];int diff = val - val0;float w = filter3[dy][dx] * coef * expf(diff * diff * coef2);f_sum += w;c_sum += w * val;}}dst[x + y * step] = (int)(c_sum / f_sum + 0.5f);}}割り算、sqrt割り算exp割り算重い演算が多い23
Copyright© Fixstars Group__global__ void BilateralKernelFast( const uint8_t *src, uint8_t *dst, int width, int height,int step, float sigma){int x = blockIdx.x * blockDim.x + threadIdx.x;int y = blockIdx.y * blockDim.y + threadIdx.y;if (x < width && y < height) {float coef = __frsqrt_rn(2 * 3.1415926f * sigma * sigma);float coef2 = __frcp_rn(-2 * sigma * sigma);float c_sum = 0;float f_sum = 0;int val0 = src[x + y * step];for (int dy = 0; dy < 3; ++dy) {for (int dx = 0; dx < 3; ++dx) {int val = src[(x + dx) + (y + dy) * step];int diff = val - val0;float w = filter3[dy][dx] * coef * __expf(diff * diff * coef2);f_sum += w;c_sum += w * val;}}dst[x + y * step] = (int)(__fdividef(c_sum, f_sum) + 0.5f);}}1/sqrtf(x) のintrinsic1/x のintrinsicexpf(x) の高速版x/y の高速版24
Copyright© Fixstars Groupバイラテラルフィルタ高速化高速版の誤差https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#intrinsic-functionsulp = Unit in the last place25
Copyright© Fixstars Groupバイラテラルフィルタ高速化 バイラテラルフィルタ計算時間 (ms) 最適化前 2.48 最適化後 1.63 最適化前 最適化後 26
Copyright© Fixstars Group転置
Copyright© Fixstars Group転置28
Copyright© Fixstars Group転置単純に書いてみる__global__ void TransposeKernelSimple( const uint8_t *src, uint8_t *dst, intwidth, int height){int x = blockIdx.x * blockDim.x + threadIdx.x;int y = blockIdx.y * blockDim.y + threadIdx.y;if (x < width && y < height)dst[y + x * height] = src[x + y * width];}xとyを逆にするだけ29
Copyright© Fixstars Group転置カーネルプロファイルLoadに比べてStoreのL2⇔DRAM Transactionがかなり多いメモリがネック30
Copyright© Fixstars Group転置単純に書いてみる__global__ void TransposeKernelSimple( const uint8_t *src, uint8_t *dst, intwidth, int height){int x = blockIdx.x * blockDim.x + threadIdx.x;int y = blockIdx.y * blockDim.y + threadIdx.y;if (x < width && y < height)dst[y + x * height] = src[x + y * width];}書き込みが全くコアレスアクセスになっていない31
Copyright© Fixstars Group転置単純に書いてみるメモリ連続方向メモリ連続方向Warpのアクセスするメモリコアレスアクセスになっている 完全に飛び飛びのアクセスLoadStoreNG!32
Copyright© Fixstars Group転置Shared Memoryを使うWarpのアクセスするメモリShared Memory Shared Memory同じメモリ書き込みもコアレスアクセスでできるようにするメモリ連続方向メモリ連続方向33
Copyright© Fixstars Group__global__ void TransposeKernelShared( const uint8_t *src,uint8_t *dst, int width, int height) {int tx = threadIdx.x;int ty = threadIdx.y;int xbase = blockIdx.x * blockDim.x;int ybase = blockIdx.y * blockDim.y;__shared__ uint8_t sbuf[16][16];{ int x = xbase + tx;int y = ybase + ty;if (x < width && y < height)sbuf[ty][tx] = src[x + y * width];}__syncthreads();{ int x = xbase + ty;int y = ybase + tx;if (x < width && y < height)dst[y + x * height] = sbuf[tx][ty];}}転置Shared Memoryを使う書き込みもコアレスアクセスでできるようにする一旦Shared Memoryに格納34
Copyright© Fixstars Group転置Shared Memoryを使うLoadとStoreでL2⇔DRAM Transactionが同じになったメモリネックでなくなった35
Copyright© Fixstars Group転置Shared Memoryを使う計測環境CPU: Core i7-8700 3.2GHz (6コア 12スレッド)GPU: GeForce RTX 2060計測条件6720x4480の画像(グレースケール)を処理計算時間のみで、データ転送やメモリ確保などの時間を含めず36
Copyright© Fixstars Group転置Shared Memoryを使う• ただし、Shared Memory のバンクコンフリクトが発生している37
Copyright© Fixstars Group__global__ void TransposeKernelFast( const uint8_t *src,uint8_t *dst, int width, int height){int tx = threadIdx.x;int ty = threadIdx.y;int xbase = blockIdx.x * blockDim.x;int ybase = blockIdx.y * blockDim.y;__shared__ uint8_t sbuf[16][16+4];{ int x = xbase + tx;int y = ybase + ty;if (x < width && y < height)sbuf[ty][tx] = src[x + y * width];}__syncthreads();{ int x = xbase + ty;int y = ybase + tx;if (x < width && y < height)dst[y + x * height] = sbuf[tx][ty];}}転置バンクコンフリクト回避パディングを追加Shared Memoryのバンクは4バイトインターリーブされているので、4バイトパディングを追加する38
Copyright© Fixstars Group転置バンクコンフリクト回避• バンクコンフリクトがゼロになった39
Copyright© Fixstars Group転置バンクコンフリクト回避 計測環境CPU: Core i7-8700 3.2GHz (6コア 12スレッド)GPU: GeForce RTX 2060計測条件6720x4480の画像(グレースケール)を処理計算時間のみで、データ転送やメモリ確保などの時間を含めず40
Copyright© Fixstars Group転置1スレッドあたり処理量を増やす カーネルのアセンブラ• 1スレッドあたりの処理量が少なすぎる○ カーネルが32命令しかない• 1スレッドが処理する要素数を増やす
Copyright© Fixstars Group__global__ void TransposeKernelFast2( const uint8_t *src, uint8_t *dst, int width, intheight){int tx = threadIdx.x;int ty = threadIdx.y;int xbase = blockIdx.x * 32;int ybase = blockIdx.y * 32;__shared__ uint8_t sbuf[32][32+4];{ int x = xbase + tx;if (x < width) {int yend = min(ybase + 32, height);for (int tyy = ty, y = ybase + ty; y < yend; tyy += 8, y += 8) {sbuf[tyy][tx] = src[x + y * width];}}}__syncthreads();{ int y = ybase + tx;if (y < height) {int xend = min(xbase + 32, width);for (int tyy = ty, x = xbase + ty; x < xend; tyy += 8, x += 8) {dst[y + x * height] = sbuf[tx][tyy];}}}}転置1スレッドあたり処理量を増やす1スレッドが4要素処理するように修正42
Copyright© Fixstars Group転置1スレッドあたり処理量を増やす計測環境CPU: Core i7-8700 3.2GHz (6コア 12スレッド)GPU: GeForce RTX 2060計測条件6720x4480の画像(グレースケール)を処理計算時間のみで、データ転送やメモリ確保などの時間を含めず43
Copyright© Fixstars Groupリダクション
Copyright© Fixstars Groupリダクション• 2次元データのリダクション• 2つの軸のリダクションをそれぞれ実装してみるメモリ連続方向xyY軸リダクション X軸リダクションメモリ連続方向45
Copyright© Fixstars GroupリダクションY軸リダクション• 1スレッド1列担当• コアレスアクセスになっていることに注意__global__ void ReduceHKernelSimple( const uint8_t *src,float *dst, int width, int height){int x = blockIdx.x * blockDim.x + threadIdx.x;if (x < width) {float sum = 0;for (int y = 0; y < height; ++y) {sum += src[x + y * width];}dst[x] = sum;}}Y軸リダクション46
Copyright© Fixstars GroupリダクションY軸リダクション• 6720x4480だとブロック数が7個しかない○ 1スレッド1列担当なので ceil(6720/1024)=747
Copyright© Fixstars GroupリダクションY軸リダクション• 列を分割して並列数を増やす○ 1列1スレッド→ceil(行数/128)スレッド__global__ void ReduceHKernelFast( const uint8_t *src, float *dst, intwidth, int height){int x = blockIdx.x * blockDim.x + threadIdx.x;int y = blockIdx.y * 128;if (x < width) {float sum = 0;for (int yend = min(y + 128, height); y < yend; ++y) {sum += src[x + y * width];}atomicAdd(&dst[x], sum);}}128行ごとに分割して処理するdstはこのカーネルを呼び出す前にゼロ初期化しておく48
Copyright© Fixstars GroupリダクションY軸リダクション計測環境CPU: Core i7-8700 3.2GHz (6コア 12スレッド)GPU: GeForce RTX 2060計測条件6720x4480の画像(グレースケール)を処理計算時間のみで、データ転送やメモリ確保などの時間を含めず49
Copyright© Fixstars GroupリダクションX軸リダクション• Y軸リダクションと同じように実装X軸リダクション__global__ void ReduceWKernelSimple( const uint8_t *src,float *dst, int width, int height){int y = blockIdx.x * blockDim.x + threadIdx.x;int x = blockIdx.y * 128;if (y < height) {float sum = 0;for (int xend = min(x + 128, width); x < xend;++x) {sum += src[x + y * width];}atomicAdd(&dst[y], sum);}}50
Copyright© Fixstars GroupリダクションX軸リダクション• Y軸リダクションと同じように実装• 2.39ms …遅いX軸リダクション X軸リダクション時間 (ms) Y軸と同じ方法 2.39 Y軸リダクション時間 (ms) 1列1スレッド 0.28 列も分割 0.115 参考51
Copyright© Fixstars GroupリダクションX軸リダクション__global__ void ReduceWKernelSimple( const uint8_t *src, float *dst, intwidth, int height){int y = blockIdx.x * blockDim.x + threadIdx.x;int x = blockIdx.y * 128;if (y < height) {float sum = 0;for (int xend = min(x + 128, width); x < xend; ++x) {sum += src[x + y * width];}atomicAdd(&dst[y], sum);}}このアクセスが全くコアレスアクセスでない52
Copyright© Fixstars GroupリダクションX軸リダクションX軸リダクションこのアクセスはダメWarpのアクセスするメモリこうアクセスしたい!水平方向のリダクションが必要53
Copyright© Fixstars Groupリダクション水平方向のリダクションが必要パラレルリダクション54
Copyright© Fixstars Groupリダクションパラレルリダクション• 1行を1ブロックが担当__global__ void ReduceWKernelFast( const uint8_t *src, float *dst, intwidth, int height){int tid = threadIdx.x;int y = blockIdx.y;__shared__ float sbuf[512];float sum = 0;for (int x = tid; x < width; x += 512) {sum += src[x + y * width];}sbuf[tid] = sum;__syncthreads();sum = ReduceFunc(tid, sbuf);if (tid == 0)dst[y] = sum;}512要素までのリダクションは普通にスレッドごとに計算1ブロック512スレッドでコードを書いた場合Shared Memoryに書いてパラレルリダクションを呼び出す55
Copyright© Fixstars Groupリダクションパラレルリダクション__device__ float ReduceFunc(int tid, float* buf){if (tid < 256) { buf[tid] += buf[tid + 256]; } __syncthreads();if (tid < 128) { buf[tid] += buf[tid + 128]; } __syncthreads();if (tid < 64) { buf[tid] += buf[tid + 64]; } __syncthreads();float sum;if (tid < 32) {sum = buf[tid] + buf[tid + 32];sum += __shfl_down_sync(0xffffffff, sum, 16);sum += __shfl_down_sync(0xffffffff, sum, 8);sum += __shfl_down_sync(0xffffffff, sum, 4);sum += __shfl_down_sync(0xffffffff, sum, 2);sum += __shfl_down_sync(0xffffffff, sum, 1);}return sum;}32スレッドまでは__syncthreads()を使って計算32スレッドになったら、WarpShuffleで計算56
Copyright© Fixstars Groupリダクションパラレルリダクション57
Copyright© Fixstars Group本セミナーのまとめ● バイラテラルフィルタ高速化○ CUDA組み込み関数を使って演算を軽量化● 転置○ Shared Memoryを使ったメモリアクセス最適化○ バンクコンフリクト回避● リダクション○ X軸、Y軸方向のリダクション○ メモリのアクセス方向を意識した計算○ 水平方向のリダクションを高速に行うパラレルリダクション58
Copyright © Fixstars GroupThank you!お問い合わせ窓口 : [email protected]