洋食の日記

「だ・である」調ではなく「です・ます」調で書きはじめれば良かったなと後悔してる人のブログです

インストール時にBLISをビルドしてNumo::Linalgのバックグラウンドライブラリに使用するGemを作成した

はじめに

Numo::OpenBLASのBLIS版を作成した。Numo::OpenBLASを作ったのが、だいたい1年前だった。

インストール時にOpenBLASをビルドしてNumo::Linalgのバックグラウンドライブラリに使用するGemを作成した - 洋食の日記

BLISは、最適化BLASの一種で、線形代数計算に対するモダンなAPIも提供している。条件によっては、OpenBLASよりも高速に計算できる様だ。

GitHub - flame/blis: BLAS-like Library Instantiation Software Framework

blis/Performance.md at master · flame/blis · GitHub

作ったきっかけは、Numo::OpenBLASに寄せられた「BLISをつかうオプションがあると良いんじゃない?」というIssueで、同じ層のNumo::OpenBLASにBLISを混ぜるのは文脈的にややこしいので、別のGemにした。最初は、Numo::Linalg::LoaderでBLISを読み込めるようにするPull Requestを作ろうと思ったが、各OSのパッケージでBLISのビルドのオプションにバラつきがあったので、Numo::OpenBLASのように、Gemのインストール時にダウンロードとビルドを行う専用のモノを用意することにした。

使い方

普通にgemコマンドでインストールできる。インストール時に、BLISとLAPACKをダウンロードしてビルドするので、ちょっと時間が必要になる。

$ gem install numo-blis

使用方法はNumo::BLISをrequireするのみで、内部的にNumo::NArrayとNumo::Linalgがrequireされて、ビルドしたBLISとLAPACKがバックグラウンドライブラリに使用される。

require 'numo/blis'

x = Numo::DFloat.new(5, 2).rand
c = x.transpose.dot(x)
eig_val, eig_vec = Numo::Linalg.eigh(c)

Numo::BLISとしては、これだけである。

Numo::BLISが行っていること

Numo::BLISがインストール時に行っていることを、手動でやってみる。BLIS・LAPACKのビルドと、Numo::Linalgでの読み込みである。

まず、BLISとLAPACKGithubからダウンロードする。

$ wget https://github.com/flame/blis/archive/refs/tags/0.8.1.tar.gz
$ tar xvzf 0.8.1.tar.gz
$ wget https://github.com/Reference-LAPACK/lapack/archive/refs/tags/v3.10.0.tar.gz
$ tar xvzf v3.10.0.tar.gz

BLISではconfigureでビルドオプションを指定する。helpオプションでどういうものがあるか確認する。

$ cd blis-0.8.1
$ ./configure --help
...
   -t MODEL, --enable-threading[=MODEL], --disable-threading

                 Enable threading in the library, using threading model
                 MODEL={openmp,pthreads,no}. If MODEL=no or
                 --disable-threading is specified, threading will be
                 disabled. The default is 'no'.
...
   --enable-cblas, --disable-cblas

                 Enable (disabled by default) building the CBLAS
                 compatibility layer. This automatically enables the
                 BLAS compatibility layer as well.
...

重要なのが、マルチスレッドとCBLAS互換に関するオプションがデフォルトで無効になっている点である。特に、CBLAS互換がないとNumo::Linalgでうまく動作してくれないので、これは有効にする必要がある。そんなわけで、configureは次のようになる。後ろについている「auto」は、CPUアーキテクチャを自動で検出してね、というモノである。

$ mkdir build; cd build
$ ../configure --enable-cblas --enable-threading=pthreads --prefix=インストール先ディレクトリ auto
...
$ make
$ make install

BLISのビルドができたので、次はLAPACKをビルドする。LAPACKではcmakeを使う。

$ cd lapack-3.10.0
$ mkdir build; cd build

BLASに先程ビルドしたBLISを指定する必要がある。CMakeのFindBLASを使っている様なので、BLAS_LIBRARIESでパスを指定すればよい。 その他のオプションは、CMakeLists.txtを見て確認した。Numo::LinalgはCインターフェースのLAPACKEを必要とするので、LAPACKEオプションをONにする。

$ cmake -DLAPACKE=ON -DBUILD_SHARED_LIBS=ON -DCMAKE_INSTALL_PREFIX='インストール先ディレクトリ' -DBLAS_LIBRARIES='先程ビルドしたBLISをlibblas.soとかまで指定する' ../
...
$ make
$ make install

あとはこれらをNumo::Linalgで読み込むと、使えるようになる。

require 'fiddle'
require 'numo/linalg/linalg'

Fiddle.dlopen('/hoge/fuga/lib/libblis.so')
Fiddle.dlopen('/hoge/fuga/lib/liblapacke.so')
Numo::Linalg::Blas.dlopen('/hoge/fuga/lib/libblis.so')
Numo::Linalg::Lapack.dlopen('/hoge/fuga/lib/liblapacke.so') 

ここで、注意が必要なのが、BLISは環境変数でスレッド数を指定しないといけないことである。指定しないとシングルスレッドになってしまう。

BLIS_NUM_THREADS=8 ruby hoge.rb

Numo::BLISでは、ENV['BLIS_NUM_THREADS']で確認していて、これがnilであれば、Etc.nprocessorsをセットしている。

おわりに

Numo::OpenBLASをベースにすれば良いので、BLISのビルド方法を調べて、一日でガッと作った。未だ腱鞘炎に悩まされていて、大した開発ができてないのだが、久しぶりにgemを作って、良いストレス解消になった。