ONNXを試す

前回はONNXとは何なのか、インストール方法を書いたので、 今回は実際に使ってみた、という記事です。

ONNXデータのインポート

まずはデータを取得するところからです。 サンプルとしてONNXのモデルのレポジトリに記載されているリンクから好きなモデルのファイルを取得します。ここではinception v1モデルのonnxファイルをダウンロードしたいと思います。一応以下のコマンドからも取得できます。

$ wget https://s3.amazonaws.com/download.onnx/models/opset_9/inception_v1.tar.gz

これを解凍すると、

inception_v1/
  |-model.onnx
  |-test_data_0.npz
  |-test_data_1.npz
  |-test_data_2.npz
  |-test_data_set_0/
  |-test_data_set_1/
  |-test_data_set_2/

このようなディレクトリ構成になります。 とりあえずモデルの情報を見てみましょう。

import onnx
model_path = "./model.onnx"
model = onnx.load(model_path)
for node in model.graph.node:
   print(node)

これだけです。実行結果は

input: "inception_4c/output_1"
input: "inception_4d/1x1_w_0"
input: "inception_4d/1x1_b_0"
output: "inception_4d/1x1_1"
name: ""
op_type: "Conv"
attribute {
  name: "strides"
  ints: 1
  ints: 1
  type: INTS
}
attribute {
  name: "pads"
  ints: 0
  ints: 0
  ints: 0
  ints: 0
  type: INTS
}
attribute {
  name: "kernel_shape"
  ints: 1
  ints: 1
  type: INTS
}

こういう感じの構造がいっぱい出てきます。入出力の指定、演算の指定、ストライドやパディング、カーネルのサイズなどの情報を得られるのでこのonnxファイルをもとにどのような構造のモデルなのかを解析することができますね。

Netronを試す

ONNXのモデルを上記のようにインポートできたら次はNetronというVisualizerを起動させてみます。webとGUIアプリのどちらからも選べます。今回はwebアプリを使ってみます。 https://lutzroeder.github.io/netron/ にアクセスして、"Open Model"でダウンロードしてきたonnxファイルを選択します。 そうするとしばらく待つと次のような図が出てきて、きちんとモデルに関する情報が抽出できていることがわかります。 f:id:kiizuka:20190220091409p:plain それぞれの層のブロックをクリックすると計算に用いるカーネルサイズやパディング、重みの値なども見ることができます。 f:id:kiizuka:20190220091418p:plain

ONNXへモデルをエクスポート

onnxファイルからモデルを取得することはできたので、次は自分で作成したモデルをエクスポートしてみます。

import onnx                                                                                                         
import onnx.helper as helper

export_onnx_path = "sample_model.onnx"

# define input
input_tensor = []
input_tensor.append(helper.make_tensor_value_info("input_1", onnx.TensorProto.FLOAT, [24, 24, 3]))
input_tensor.append(helper.make_tensor_value_info("input_2", onnx.TensorProto.FLOAT, [24, 24, 3]))

# define output
output_tensor = []
output_tensor.append(helper.make_tensor_value_info("output", onnx.TensorProto.FLOAT, [24, 24, 3]))

# define computational graph
nodes = []
nodes.append(helper.make_node("Add", ["input_1", "input_2"], ["output"]))

graph = helper.make_graph(nodes, "Sample graph", input_tensor, output_tensor)

model = helper.make_model(graph)

with open(export_onnx_path, "wb") as f:
    f.write(model.SerializeToString())

割とシンプルな書き方で何がしたいかがわかりやすいでしょうか? どのような計算グラフを作成したかを想像してもらった上で、 このスクリプトで作成したonnxファイルを改めて先程のNetronで見てみると、 こんな感じです。 f:id:kiizuka:20190220091449p:plain こんな小さなグラフでも可視化すると達成感があります。 実際には自分で計算グラフを構成するようなことは少なくて、ある機械学習フレームワークで作成した学習モデルを他のフレームワークに移植するというために使うことが多いです。 そのためのチュートリアルのレポジトリもあるので見てみたいと思います。

ONNXの紹介とインストール方法

ONNXとは

ONNXはOpenNeuralNetworkEXchange formatの略称で機械学習フレームワーク間でモデルの構造や学習したパラメータを交換するためのデータフォーマットです。ONNXをサポートしているツールはここで紹介されているのですが、Caffeのモデルや学習データをPyTorchで利用したりcognitive-toolkitからchainerに移植したりということが可能になります。TVMなどONNXをサポートするディープラーニングコンパイラもあります。 ONNXからモデルの構造と計算グラフを抽出することで専用ハードウェアへの実装などにも応用できます(私の研究でもそれを目論んでいます)。

ONNXのインストール

ONNXのGitHubレポジトリのREADMEにすべて書いてあります。で終わってしまうのですが、備忘録も兼ねてインストール手順をまとめておきます。 想定環境は以下のとおりです。 - Ubuntu 18.04 - Python 3.6.7

READMEにはcondaを使ったインストール方法がまず紹介されているのですが、 なんとなく使いたくないのでソースコードからビルドしたいと思います。

$ git clone https://github.com/onnx/onnx.git
$ cd onnx
$ git submodule update --init --recursive
$ python setup.py install

...
長いビルドのログ
...
Using /home/kiizuka/.venv/onnx/lib/python3.6/site-packages
Finished processing dependencies for onnx==1.4.1

ここまで終わったら、最後に

$ sudo apt-get install protobuf-compiler libprotoc-dev

で必要なライブラリを追加して終了です。 このままこのディレクトリで

$ python -c "import onnx"

を実行すると

ModuleNotFoundError: No module named 'onnx.onnx_cpp2py_export.defs'

が出ます。別のディレクリでは正常に動くよとREADMEにも書いてあるので放置でもいいと思います。

$ python setup.py develop

を実行するととりあえずエラーが消えます。

(参考: https://github.com/onnx/onnx/issues/1302#issuecomment-413922657)

次は実際に動かしてみたいと思います。

Xilinx White Paper "Accelerating DNNs with Xilinx Alveo Accelerator Cards"を読んで

Xilinxが10月に新しいクラウド向けのFPGAとそのエコシステムについてのホワイトペーパを公開しました。 ここにPDFがあります。 実は日本語版も出ていたので英語頑張って読まなくても大丈夫です。

自分はそもそもホワイトペーパーの意味をよくわからなったのですが、https://ferret-plus.com/5845 によると、

ホワイトペーパーは、もともとは政府や公的機関による年次報告書つまり「白書」を意味しました。しかし近年では マーケティング用語としても用いられており、特定の技術や商品について売り込む目的で、調査と関連付けて利点や長所をアピールする記載がなされることが特徴です。

なぜ読んだのか

FPGAMicrosoftのデータセンターで動いたりAWS上でF1インスタンスとして提供されるなどGPUGoogleのTPUなどの専用チップと並んで注目されているハードウェアです。自分で回路設計が可能なことや作り直すことができる柔軟性や専用チップに比べると開発期間が短くてすむなどの長所を持っていますが、FPGAを使うにはハードウェアに関する専門知識、開発ツールの使い方を知らないといけないなど、GPUに比べると使い勝手が悪いという欠点があります。 この文書ではユーザーがREST APIPython機械学習フレームワークを利用してモデルを構成するだけでクラウドの計算資源としてFPGAを使えるようにしたソフトウェアプラットフォームとFPGAを組み合わせたものが発表されています。 これはXilinxクラウド向けのFPGAに注力することの宣言でもあります(現在使われているAWSだけでなく、Microsoftのデータセンターにも新たにXilinx製のFPGAが搭載されるようです。) 自分の研究の良い参考(裏を返すと競合相手ですが...)になると思ってまとめました。

概要

クラウド向けFPGAである新製品Alveoカードは電力コストを抑えつつ深層学習における推論処理の高速化、低遅延化を実現するアクセラレータである。 xDNNと呼ばれるFPGA上のハードウェアアーキテクチャPython機械学習フレームワークFPGAで実行可能にするコンパイラなどのソフトウェアスタックxfDNNを利用することで、 性能向上だけでなく実装コストを気にすることなくクラウド上の計算資源としてFPGAが利用できるようになった。

アウトライン

に沿って、紹介していきたいと思います。

深層学習がどのようなアプリケーションとしてクラウド上で動くのか

機械学習が様々なサービスに利用されているのはすでによく知られていることです。その中でもCNNは動画像処理において非常に強力なツールとなります。Youtube, Instagramの普及でインターネット上では無数の動画像がユーザーに閲覧されています。それにともなって例えば不正なコンテンツはアクセスできないようにする、何が映っているのかを識別するといった処理が重要となります。CNNを利用するとこれらの処理を高精度で実行できますが、ストリーミングサービスでは低遅延で行う必要性もあります。GPUは深層学習の学習のアクセラレータとして用いられることが多いですがデータをある程度集約して計算を行うバッチ処理を実行しないとその性能を活かすことはできません。しかしバッチ処理を行うと遅延が大きくなってしまう(画像処理では何枚か画像を集めて一気に計算を行うイメージなので最初の画像を送ってからしばらくしないと最初の画像の推論結果は出てこない)という欠点から前述のようなニーズを満たすことはできません。今回紹介するxDNN推論エンジンの載ったFPGAを利用するとバッチ処理を行わなくてもGoogLeNet v1モデルで4000枚/秒のスループットを出すことができるそうです。

xDNN(ハードウェアアーキテクチャについて)

Alveoカード上のハードウェアアーキテクチャの5つの特徴について説明します。

  1. Dual Mode: Throughput-Optimized or Latency-Optimized

  2. Command-Level Parallel Execution

  3. HW-Assisted Image Tiling

  4. Custom Layer Support(Heterogeneous Execution)

  5. Systolic Array Architecture

という構成で進めていきます。それぞれの特徴について説明する前にxDNNアーキテクチャの見取り図を引用しておきます。 ※図は全て紹介しているPDFのものを引用しています。

f:id:kiizuka:20190116233804p:plain

1. Dual Mode: Throughput-Optimized or Latency-Optimized

スループット重視で演算するか、レイテンシ重視で演算するかの2つモードがあるというのが1つ目の特徴です。 GoogLeNet v1の最初の層はRGB画像を入力として受け取ります。しかしこれが全体の10%くらいの実行時間を占めるのに対して、 xDNNのベースとなっている行列積の演算器Systolic Arrayだと効率よく実行できないようです。サイズが大きく3枚のRGBが必要になるからでしょうか? ここでスループット重視のモードにしておくと、3入力のためのSystolic Arrayによって演算を行うことができるようです。 レイテンシ重視モードを選択するとパイプライン化してレイテンシを下げるように処理を行うようです。

2. Command-Level Parallel Execution

xDNNアーキテクチャでは畳み込み演算やプーリング処理などいくつかの命令を実行できるのですが、 Systolic Arrayを使って畳み込み演算を行いながらプーリング処理も並列処理できるような設計になっています。 そのため下図に示すような構造のGoogLeNet v1のInception層の処理を並列してスケジューリングすることが可能です。 f:id:kiizuka:20190116234057p:plain スケジューリングした結果、下図のように畳み込み(Conv 3x3)とプーリング処理(MaxPool 3x3)を並列実行できます。 f:id:kiizuka:20190116234045p:plain

3. HW-Assisted Image Tiling

大きな入力画像や入力特徴量を持つCNNのモデルを動かすためには特徴量をタイリング(分割)する必要があります。 これをxDNNではハードウェアレベルでサポートしています。畳み込み演算の高速化とパイプライン化のためにデータを分割して演算するというのは 他のFPGA研究でも行われています。

4. Custom Layer Support(Heterogeneous Execution)

xDNNでサポートされていないような演算を行うようなCNNのモデルではそのような演算を行うときはCPUで実行するということが可能です。 前処理や途中で一度CPUでサポートされていない演算を行ってまたFPGAで演算させるみたいなこともできるようです。 当然通信による遅延などは生じますが、典型的なCNNではなくユーザーが開発したモデルも部分的にでもFPGAで高速化できるというのは プラットフォームとして売り出すときには強みですね。

5. Systolic Array Architecture

深層学習のアクセラレータではGoogleのTPUが同じSystolic Arrayを採用していますが、このアイディア自体は行列積演算器としてかなり昔に提唱されているようです。ムーアの法則に沿った成長ができなくなってきて、DSA(Domain Specific Architecture)=特定のアプリケーションを効率よく実行するプロセッサという考え方が広まっています。そこで昔提案されたアーキテクチャなどが再び注目されるルネッサンス(=文芸復興)な時代になってきています。

xfDNNソフトウェアスタック(ソフトウェアの処理のフロー)

さて、xDNNのアーキテクチャについて説明した上で、このFPGAをどうやって開発するんだ、というところにフォーカスします。 通常のFPGA開発のプロセスだとXilinxが提供するVivadoなどを用いてVHDL,Verilogなどハードウェア記述言語で設計する or C, C++, OpenCLなどで高位合成(高位といいつつだいぶ低水準な気が...)を行いますが、これはなかなか大変です… そこで、XilinxはTensorFlowやMxNet,CaffeなどのPython機械学習フレームワークで記述したCNNのモデルを計算グラフを介してコンパイルして(もし必要なら重みの量子化もするようです)、 CPU, FPGAで実行可能なランタイムを生成するようなソフトウェアスタックをつくりました。 クラウド上のFPGAでCNNの推論をやりたいと思ったユーザーがわざわざFPGAの使い方などを勉強しなくてもいいようなプラットフォームが生まれたのです。 NVIDIAGPUとCUDAという開発環境も抱き合わせて製品をリリースして覇権を獲ったように、 ハードウェアだけ作ってリリースするのではなく開発環境も抱き合わせるというのは今後必要なんだろうなと思いました(TPUが最初からTensorFlowに対応してGCPで使えるのも同じ例ですね)。

性能評価

これもXilinxのホワイトペーパーから引用になりますが下のグラフを見るのが何よりもわかりやすいと思います。 f:id:kiizuka:20190116234040p:plain 比較する対象は

で対象ベンチマークはバッチサイズが1(= 1枚ずつ画像を処理する)のGoogLeNet v1でスループットとレイテンシを計算しています。 比較結果はグラフを見ると一目瞭然で、Xilinxの新製品Alveoの大勝利です! バッチサイズを1にするとGPUはどうしても実力を発揮できないのですが、実際にストリーム配信の動画像に対して 推論処理を行うなどのケースを考えるとバッチサイズ1で演算をする可能性は大いにあります。 Alveoを用いた実装では演算の精度をINTの8bitまで量子化しているのも性能向上のミソです(比較対象も演算精度を同じにしたらもう少し結果が良くなりそうです)。 電力効率も次のグラフを見ると比較対象に勝利していますね。電力効率が良いのもFPGAの強みなのでそれがしっかり活かされています。 f:id:kiizuka:20190116234013p:plain クラウドやデータセンタでは演算性能だけでなく、消費電力などのランニングコストも機材導入の大切な判断材料です。

まとめ

ここまででXilinxの新製品の優位性が示されました。 実際に使いたい場合にはFPGA実機を購入するか、AWSやNimbixというクラウドサービスのインスタンスを使えるようです。