こんにちは、データベースマーケティング研究所技術開発ユニットの三浦です。画像解析機能をもつアプリケーションを開発する際、実行環境要件として必ずといっていいほど出てくる"GPU"。しかし、その実態についてはなんとなく理解している程度のレベルです。
GPU周りについて理解し整理しておき、サーバやエッジといった様々な実行環境の制約に柔軟に対応できるようになりたいと考えています。少しずつGPU周りの技術について調べてその内容について記事に残していきたいと思います。
今回はCPUとGPUの構造の違い、NVIDIA社が提供しているプラットフォームCUDAについて調べた内容を紹介します。
CPU vs GPU
CPUとGPUはどちらもコンピュータの中で演算を行う装置ですが、それぞれの構造や目的(得意領域)が異なります。 NVIDIA社のCUDA Programming Guideによると、CPUは1つのスレッドを可能な限り高速に処理できるようにデザインされており、一方でGPUは多数のスレッドを並列で処理できるようにデザインされているとのことです。
行列同士の足し算をイメージするといいのかもしれません。行列同士を足し算する場合は、行列の各要素を足し合わせることになりますが、CPUではそれを0行0列の足し算、1行0列の足し算・・・といったように1つ1つの処理を高速に順々にこなしていき、一方GPUではそれを同時に並列でこなすようにデザインされている、ということになるのだと思います。
CUDA
多数のスレッドを並列で実行するために、どのスレッドを並列で実行するのかなどの制御が必要になります。GPU上でのプログラミングをC++で記述可能にし、科学演算向けのライブラリ、コンパイラなどが用意されたプラグラミングプラットフォームがNVIDIA社が提供しているCUDAです。GPUは主にゲーミンググラフィックスといった画像処理の領域で活用されていたのに対し、CUDAでは科学演算等のもっと汎用的な用途でGPUを利用することを可能にしています。汎用コンピューティング向けのGPU活用技術はGPGPU (General-Purpose computing on Graphics Processing Units) と呼ばれています。
CUDA Programmingの例
GPUで並列化させたい処理は、CUDAではCUDA kernelというC++の関数で記述することが出来ます。
CUDA Programmingの具体例として、サイズNのベクトルAとBを足してベクトルCを生成する例をCUDA Programming Guideにから転載します。N個の要素間の足し算をGPU上でN個のスレッドで並列処理する、ということを行っています。
// Kernel definition __global__ void VecAdd(float* A, float* B, float* C) { int i = threadIdx.x; C[i] = A[i] + B[i]; } int main() { ... // Kernel invocation with N threads VecAdd<<<1, N>>>(A, B, C); ... }
通常のC++の関数の記述に__global__
の宣言を付与している点、自身が実行されているGPUのスレッド情報をthreadIdx
で取得している点、<<<...>>>
で実行するスレッド数を指定している点など、短いコードでありながらCUDAで行っていることのイメージを掴むことが出来ました。より高度な計算を実現したい場合は、用意されているライブラリを利用することで実現することが出来るのだと思います。
まとめ
tensorflow-gpuで深層学習の処理を書いて実行する際に、裏側では今回調べたようなCUDA Programmingが実行されているのだと理解しました。一方で気になるのは、こういったGPU上でのプログラミングを実現する方法としてCUDA以外の選択肢はあるのだろうか、という点です。この辺りを引き続き調査してみて、CUDAとの対比など、また別の機会にご紹介出来たらいいなと思います。