この記事はRuby Advent Calendar 2016の6日目です。
rubyでも、GPU使いたいということで、 Ruby FFI を使ってCUDA Cで作ったShared Libraryを実行する方法について試してみました。
前提
- ruby 2.3.1
- ffi v1.9.14
- CUDA 8.0 (Cuda compilation tools, release 8.0, V8.0.44)
かんたんにGPUを使うことだけを考えて、GPUでHello Worldと表示するプログラムを作ってみます。
CUDAのShared Libraryを作る
ヘッダファイルの作成
Shared Libraryとして、実行するメソッドにextern "C"
とつけておく。
#ifndef _HELLO_H
#define _HELLO_H
extern "C" void helloGPU();
#endif // _HELLO_H
本体ファイルの作成
GPU側で"Hello World from GPU!!\n"と10回出力するコードを作成
#include "hello.h"
#include <stdio.h>
__global__ void helloFromGPU()
{
printf("Hello World from GPU!!\n");
}
void helloGPU() {
helloFromGPU<<<1, 10>>>();
cudaDeviceReset();
}
Shared Libraryのコンパイル
ターミナルから、以下のコマンドでコンパイルすると、libhello.soというファイルが作成される。
$ nvcc --ptxas-options=-v --compiler-options '-fPIC' -o libhello.so --shared hello.cu
これで、CUDA側の必要な作業は完了。次に、Ruby FFIを使って、作成したShared Libraryを呼び出すコードを作成する。
Ruby FFI
モジュールを定義
Ruby FFIのExamplesに習って、モジュールを定義します。
module Hello
extend FFI::Library
ffi_lib "libhello.so"
attach_function :helloGPU, [], :void
end
ffi_lib
の引数に、先ほどコンパイルしたShared Libraryファイルを記載。
attach_function
の第一引数に、呼び出す関数の名称をSymbolで記載、第二引数は、呼び出す関数の引数の型を指定しますが、今回は引数がないので空にします。第三引数には、戻り値の型を指定します。今回はvoidなので、:void
としていします。
RubyからFFI経由でCUDAの関数を実行する
用意したHelloモジュールを実行してみます。
require 'ffi'
require_relative './hello.rb'
Hello.helloGPU
先ほどのモジュールのattach_function
で定義したメソッドを実行するコードです。コンソールから、実行してみます。
$ ruby main.rb
Hello World from GPU!!
Hello World from GPU!!
Hello World from GPU!!
Hello World from GPU!!
Hello World from GPU!!
Hello World from GPU!!
Hello World from GPU!!
Hello World from GPU!!
Hello World from GPU!!
Hello World from GPU!!
GPUスレッドが10個生成されて、Hello World from GPU!!
が10回プリントされました。
結論
ニューラルネットワークブームで、かなり注目されているCUDAをRubyから実行できることが確認できました。
CUDAには、オフィシャルのライブラリとして、
- cuDNN
- cuFFT
- cuSPARSE
- cuBLAS
など、CPUで実行すると時間がかかりそうな処理をGPUで実行するのに最適化されたライブラリが用意されているので、必要なケースがあれば使ってみてはどうでしょうか。