Ruby FFIとCUDA CでRubyからGPUを使う

  • 6
    いいね
  • 0
    コメント

この記事は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"とつけておく。

hello.h
#ifndef _HELLO_H
#define _HELLO_H

extern "C" void helloGPU();

#endif // _HELLO_H

本体ファイルの作成

GPU側で"Hello World from GPU!!\n"と10回出力するコードを作成

hello.cu
#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に習って、モジュールを定義します。

hello.rb
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モジュールを実行してみます。

main.rb
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で実行するのに最適化されたライブラリが用意されているので、必要なケースがあれば使ってみてはどうでしょうか。