Skip to content

Instantly share code, notes, and snippets.

@kou
Last active May 27, 2019 01:47
Show Gist options
  • Save kou/5f3cc0e00fa99f5eab0ebb73ec9bd007 to your computer and use it in GitHub Desktop.
Save kou/5f3cc0e00fa99f5eab0ebb73ec9bd007 to your computer and use it in GitHub Desktop.
Buffer protocol for Ruby

Ruby版バッファープロトコルの実現

PythonのPEP 3118 -- Revising the buffer protocol相当の仕組みをRubyでも実現したい。

須藤功平 - 2019-05

背景

大きなデータ(多次元配列や画像データ)を相互に交換するための統一的な仕組みが欲しい。統一的な仕組みがあると実装が単純になるからである。

私はApache Arrow(多次元配列のためのデータ構造がある)やgdk-pixbuf(画像処理ライブラリー)やOpenCV(コンピュータービジョンライブラリー)のRubyバインディングを開発している。これらの各バインディングがなにもかも必要な処理をすべてできるわけではない。たとえば、gdk-pixbufは代表的な高レベルな画像処理(読み書き変換)はできるが、ピクセルレベルの処理を高速に実現することはできない。そのため、必要に応じて対象の処理が得意なライブラリーを活用したい。たとえば、ピクセルレベルの処理は多次元配列ライブラリーを使うことで高速に処理できる。

そのため、私はいくつか相互にデータを交換する補助ライブラリーを作った。たとえば、Apache Arrowの多次元配列データとgdk-pixbufの画像データを相互に変換するred-arrow-gdk-pixbufである。これで各ライブラリーの得意な処理を組み合わせることができる。(今のApache Arrowのテンソルにはピクセルレベルの処理を高速に実現する機能はないので、例として微妙。)

ただ、このように個別のライブラリーごとにデータ交換処理を実現していると組み合わせの数が一気に増えてしまう。たとえば、gdk-pixbufとOpenCV間、Apache ArrowとOpenCV間でのデータ交換処理も実装しなければいけない。(使う必要がなければ別に実装する必要はないので例として微妙。)しかし、統一的な仕組みがあれば、各ライブラリーが統一的な仕組み1つに対応するだけで各種ライブラリー間で相互に交換できる。このためデータ交換の実装が単純になる。

PEP 3118 -- Revising the buffer protocolとの違い

基本的にPEP 3118をベースに考えるのでよさそうだがいくつか改良したい。

  • 中身の表現の仕方を↓のように暗号的な感じではなくもっとヒューマンリーダブルな感じにできないか。

  • 単語の区切りには_を入れたい。

    • ndimn_dimとかn_dimensionsとか。
  • CPU上のメモリーだけではなくGPU上のメモリーも扱えるような仕組みがよさそう。

どうして__array_interface__プロトコルベースではないのか

The Array Interfaceの先頭に書いているが、PEP 3188は__array_interface__プロトコルのスーパセットである。せっかく今から整備するならPEP 3188の方がよいのではないか。(せっかくってなんだ?)

NumBufferを使うのはどうか

NumBuffer__array_interface__プロトコル相当のもので、数値の多次元配列のための実装である。せっかくなので数値以外もサポートするのはどうだろう。(せっかくってなんだ?)

Apache Arrowを使うのはどうか

Apache Arrowは言語中立なデータ交換のためのフォーマットを定義しようとしているのでよさそうである。ただ、数値の多次元配列のための実装である。せっかくなので数値以外もサポートするのはどうだろう。(せっかくってなんだ?)

Apache ArrowにPEP 3188相当の拡張性を提案するのもありかもしれない。

XNDを使うのはどうか

ndtypesもろもろの型を表現できそう(RGBも含めて)だし、PEP-3118の構文から変換できそうだし悪くないかもしれない。

対象外の話題

__array_function__プロトコル

Pythonでは異なる多次元配列の実装(たとえばNumPyとCuPy)でも同じAPIで動かせる__array_function__プロトコルというプロトコルも検討している。これも有用そうだが今回は対象外とする。

参考までにどんなことがうれしくなるかというのをざっくりと説明する。

Red ChainerではCPU上で処理するときはNumoを使い、GPU上で処理するときにはCumoを使っている。違う多次元配列の実装を使っていてもできるだけコードの重複をなくし、メンテナンス性を高めるために次のようにxmという変数(中身はNumoCumoモジュール)を経由して名前解決をしている。

https://github.com/red-data-tools/red-chainer/blob/master/lib/chainer/optimizers/adam.rb#L32-L33

xm = Chainer.get_array_module(grad)
param.data -= lr * @state[:m] / (xm::NMath.sqrt(@state[:v]) + hp.eps)

__array_function__プロトコル相当のことが実現できると、たとえば次のように書いただけでNumoでもCumoでも動く。

param.data -= lr * @state[:m] / (Numo::NMath.sqrt(@state[:v]) + hp.eps)

今後の流れ

微妙だね。どうしようね。私が本当に必要な気になったらがんばるのでいいかもしれない。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment