FPGAでDeep Learningしてみる

# はじめに
Xilinxが[BNN-PYNQ](https://github.com/Xilinx/BNN-PYNQ)というプロジェクトを公開したことにより、FPGA初心者でも簡単にDeep LearningをFPGA実行することができるようになりました。早速ボードを購入してデモ実行まで試してみました。

# 事前説明
## PYNQ
Xilinxのオープンソースプロジェクトで、XilinxのZynqに実装したFPGAロジックを、Pythonから簡単に使えるようにするためのもののようです。

![pynq-logo.png](https://qiita-image-store.s3.amazonaws.com/0/129013/cfd749b5-9d51-8f86-2143-7c5bcf2a6be1.png)

通常、Zynqでプログラムを実行する際は、CPUで実行するPS(Processing System)と、FPGAで実行するPL(Programmable Logic)に分かれています。Deep Learningでは、Deep Learningを利用するアプリケーションをPSに実装し、並列化による高速化が見込める畳み込み処理やニューラルネットワークの各層の計算処理などをPLに実装するイメージですかね。PYNQでは、PSをPythonで記述することができます。

さらに、大きな特徴として、Overlayという考え方を持っています。Overlayでは、ソフトウェアライブラリのようにPLを扱うことで、Pythonから動的にPL部分を変更することができます。例えば、MNISTを実行するときは、MNISTのOverlayをPythonでロードするだけで、MNISTのNetworkがPLに展開されるイメージです。

PYNQの詳細は下記が参考になると思います。

* [Introduction to Overlays](https://pynq.readthedocs.io/en/latest/6_overlays.html)
* [PYNQ-Z1のOverlay読み込みとPythonからのFPGA PLの制御](http://todotani.cocolog-nifty.com/blog/2017/01/pynq-z1overlayp.html)
* [FPGAエクストリーム・コンピューティング 番外編：PYNQ祭り](https://fpgax.connpass.com/event/48848/)

## PYNQ-Z1 Board
PYNQプロジェクトを正式にサポートしているボードが、PYNQ-Z1 Boardです。Dual-Core ARM® Cortex®-A9が搭載されています。また、HDMIのIN/OUTも持っているため、画像やビデオ処理などにも活躍しそうです。

![pynq-z1-board.png](https://qiita-image-store.s3.amazonaws.com/0/129013/79d75b79-db47-65c5-1f66-6ed539369c74.png)
[PYNQ: PYTHON PRODUCTIVITY ON ZYNQ](http://www.pynq.io/board.html)

現状、日本での購入はできなさそうなため、[Digilentサイト](http://store.digilentinc.com/pynq-z1-python-productivity-for-zynq/)から購入しました。
金額は送料なども含めて3万円ちょっとくらいで、注文から1週間程度で届きました。配送はFedExだったのですが、商品とは別で、税金の請求書（1,500円くらい）が届きました。請求書はコンビニで支払いができました。

## BNN-PYNQ
[BNN-PYNQ](https://github.com/Xilinx/BNN-PYNQ)とは、Binarized Neural Network (BNN)をPYNQ上で実行することができるプロジェクトです。Deep Learningは、推論と学習で構成されますが、BNN-PYNQで公開されているのは、推論のみです。

### アルゴリズム
FPGAでは、計算資源の制約から、2値化したアルゴリズムを用いることが主流のようです。また、2値化することで、XNOR計算となり高速化が見込めるみたいです。BNN-PYNQでは、論文[[FINN: A Framework for Fast, Scalable Binarized Neural Network Inference](https://arxiv.org/abs/1612.07119)]で紹介されているCNVとLFCが、PYNQのOverlayとして公開されています。

BNNの詳細は、論文を読むのがもっともいいと思います。また、2値化アルゴリズムに関しては、下記が参考になると思います。

* [[Survey]Binarized Neural Networks: Training Deep Neural Networks with Weights and Activations Constrained to +1 or -1](http://qiita.com/supersaiakujin/items/81719e49a50a3fb653e8)
* [２値化CNN on FPGAでGPUとガチンコバトル（公開版）](https://www.slideshare.net/HirokiNakahara1/cnn-on-fpgagpu)
* [BinaryNetとBinarized Deep Neural Network](https://tkng.org/b/2016/02/12/binarynet-and-binarized-neural-network/)

### 実装
BNN-PYNQでは、Deep Learningを[xilinx-tiny-cnn](https://github.com/Xilinx/xilinx-tiny-cnn)というライブラリを使って実装しています。xilinx-tiny-cnnは、[tiny-dnn](https://github.com/tiny-dnn/tiny-dnn)を基にしており、次の点が変更されているとのことです。
[BNN-PYNQは、tiny-dnnを利用しています。](http://blogs.yahoo.co.jp/verification_engineer/71047369.html)

* added batchnorm layer (currently feedforward only, no training)
* support for offloaded layer
* interleave layer
* binarized layers

tiny-dnnの開発者は日本人のようです。すごいですね。。。
[C++ヘッダだけでDeep Learning、tiny-dnnの紹介](http://qiita.com/nyanp/items/11c6bb6fb539486c5069)


# デモの実行
## 環境
BNN-PYNQを実行するにあたり、下記を準備しました。

* PYNQ-Z1 Board
* Micro-SDカード（8GB以上を推奨）
* LANケーブル
* USBケーブル  
 電源用に使います。ACアダプタの方がいいと思います。
* Mac mini  
 PYNQのイメージをSDカードに焼くためと、PYNQのJupyterを開くために使います。

## PYNQの初期設定
ドキュメントの[Getting Started](https://pynq.readthedocs.io/en/latest/1_getting_started.html)に沿って進めます。

### イメージの作成
まずは、イメージのダウンロードです。上記ドキュメントの手順内にある`Download and the PYNQ-Z1 image`からダウンロードします。また、ダウンロードは、[Digilentのサイト](https://reference.digilentinc.com/reference/programmable-logic/pynq-z1/start)からもできるようです。（違いはわかりません）
記事を書いている時点では、`pynq_z1_image_2017_02_10.zip`がダウンロードされました。

ダウンロードした`PYNQ-Z1 Image`のzipファイルを解凍します。

```shell-session
$ tar zxvf pynq_z1_image_2017_02_10.zip
x pynq_z1_image_2017_02_10.img
```

解凍したイメージをSDカードにインストールします。まずは、イメージをインストールするSDカードを確認します。

```shell-session
$ df -ah
Filesystem      Size   Used  Avail Capacity   iused    ifree %iused  Mounted on
/dev/disk1s1    30Gi  2.5Mi   30Gi     1%         0        0  100%   /Volumes/UNTITLED
```

SDカードをFAT32でフォーマットします。

```shell-session
$ diskutil eraseDisk FAT32 PYNQ /dev/disk1
Started erase on disk1
Unmounting disk
Creating the partition map
Waiting for the disks to reappear
Formatting disk1s2 as MS-DOS (FAT32) with name PYNQ
512 bytes per physical sector
/dev/rdisk1s2: 62501024 sectors in 1953157 FAT32 clusters (16384 bytes/cluster)
bps=512 spc=32 res=32 nft=2 mid=0xf8 spt=32 hds=255 hid=411648 drv=0x80 bsec=62531584 bspf=15260 rdcl=2 infs=1 bkbs=6
Mounting disk
Finished erase on disk1
```

SDカードをアンマウントします。
※SDカードは抜きません。

```shell-session
$ diskutil unmountDisk /dev/disk1
Unmount of all volumes on disk1 was successful
```

先ほどのイメージをSDカードに書き込みます。

```shell-session
$ sudo dd bs=1024m if=pynq_z1_image_2017_02_10.img of=/dev/rdisk1
Password:
```

これでSDカードの準備は完了です！

### 起動

PYNQ Boardを下記のイメージ通りに設定します。

![pynqz1_setup.jpg](https://qiita-image-store.s3.amazonaws.com/0/129013/f9fb2220-356f-c86d-6df8-d1a8656c6c7d.jpeg)

⓪ 電源スイッチがOFFであることを確認します
① JP4（USB HOSTの隣）をSDにします
② 先ほど作成したSDカードを挿入します
③ USBケーブル（電源ケーブル）を接続します
④ LANケーブルを接続します

私の場合は、電源をUSBからとるため、上記に加えてJP5（電源スイッチの隣）をUSBに変更しました。すべて設定した後...

⑤ PYNQに電源を入れます

## Jupyter Notebook / SSH
PYNQでは、Jupyter Notebookが起動しています。そのため、下記のリンクにアクセスすることで、Jupyter Notebook上でPythonのプログラムが可能です。

 http://[PYNQのIPアドレス]:9090

最初にアクセスすると下のようなログインページになります。パスワードは「xilinx」です。

![jupyter-login.png](https://qiita-image-store.s3.amazonaws.com/0/129013/93bce371-d968-445f-67da-60046dadfa95.png)

使い方は通常のJupyterと同じです。

また、PYNQに、SSHでアクセスすることもできます。アカウント名「xilinx」、パスワード「xilinx」です。

PYNQを最新化するために下記のコマンドを実行します。

```shell-session
xilinx@pynq:~$ sudo /home/xilinx/scripts/update_pynq.sh
[sudo] password for xilinx: 
Info: This operation will overwrite all the example notebooks
Press any key to continue...

Github Repo Detected. Pulling latest changes from upstream..
fatal: A branch named 'master' already exists.
Already on 'master'
Your branch is up-to-date with 'origin/master'.
remote: Counting objects: 13, done.
remote: Compressing objects: 100% (13/13), done.
remote: Total 13 (delta 2), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (13/13), done.
From https://github.com/Xilinx/PYNQ
   3ed304a..0309566  master     -> origin/master
Updating 3ed304a..0309566
Fast-forward
 python/pynq/gpio.py            | 210 ++++++++++++++++++++++++++++++++++++++----------------
 python/pynq/iop/iop.py         |  22 +++---
 python/pynq/tests/test_gpio.py |   1 +
 3 files changed, 161 insertions(+), 72 deletions(-)

checking out v1.4

Verifying current SDCard image supports this pynq release.
Completed

Build libsds_lib

cd /home/xilinx/pynq_git/scripts/xlnkutils && make && make install
make[1]: Entering directory '/home/xilinx/pynq_git/scripts/xlnkutils'
gcc wrapper.c -fPIC -shared -rdynamic -o libsds_lib.so -Wl,--whole-archive libsds_lib.a -l pthread  -Wl,--no-whole-archive
make[1]: Leaving directory '/home/xilinx/pynq_git/scripts/xlnkutils'
make[1]: Entering directory '/home/xilinx/pynq_git/scripts/xlnkutils'
cp -avf libsds_lib.so /usr/lib/
‘libsds_lib.so’ -> ‘/usr/lib/libsds_lib.so’
cp -arvf libxlnk_cma.h /usr/include/
‘libxlnk_cma.h’ -> ‘/usr/include/libxlnk_cma.h’
make[1]: Leaving directory '/home/xilinx/pynq_git/scripts/xlnkutils'

Pip install latest pynq python package

python3.6 /home/xilinx/scripts/stop_pl_server.py
rm -rf /opt/python3.6/lib/python3.6/site-packages/pynq/*
cp -rf /home/xilinx/pynq_git/Pynq-Z1/sdk/bin/*.bin /home/xilinx/pynq_git/python/pynq/iop/
cp -rf /home/xilinx/pynq_git/Pynq-Z1/bitstream /home/xilinx/pynq_git/python/pynq/
cd /home/xilinx/pynq_git/python ; sudo -H python3.6 -m pip install --upgrade .
Processing /home/xilinx/pynq_git/python
Installing collected packages: pynq
  Found existing installation: pynq 1.4
    Uninstalling pynq-1.4:
      Successfully uninstalled pynq-1.4
  Running setup.py install for pynq ... done
Successfully installed pynq-1.4
python3.6 /home/xilinx/scripts/start_pl_server.py &

Update scripts and notebooks

cp -arf /home/xilinx/pynq_git/Pynq-Z1/notebooks/* /home/xilinx/jupyter_notebooks
cp -f /home/xilinx/pynq_git/scripts/linux/rc.local /etc/
mkdir -p /home/xilinx/jupyter_notebooks/getting_started
mkdir -p /home/xilinx/jupyter_notebooks/getting_started/images
cp /home/xilinx/pynq_git/docs/source/3_jupyter_notebook.ipynb \
/home/xilinx/jupyter_notebooks/getting_started/1_jupyter_notebook.ipynb
cp /home/xilinx/pynq_git/docs/source/4_programming_python.ipynb \
/home/xilinx/jupyter_notebooks/getting_started/2_programming_python.ipynb
cp /home/xilinx/pynq_git/docs/source/5_programming_onboard.ipynb \
/home/xilinx/jupyter_notebooks/getting_started/3_programming_onboard.ipynb
cp /home/xilinx/pynq_git/docs/source/8_base_overlay_iop.ipynb \
/home/xilinx/jupyter_notebooks/getting_started/4_base_overlay_iop.ipynb
cp /home/xilinx/pynq_git/docs/source/9_base_overlay_video.ipynb \
/home/xilinx/jupyter_notebooks/getting_started/5_base_overlay_video.ipynb
cp /home/xilinx/pynq_git/docs/source/10_base_overlay_audio.ipynb \
/home/xilinx/jupyter_notebooks/getting_started/6_base_overlay_audio.ipynb
chown -R xilinx:xilinx /opt/python3.6/lib/python3.6/site-packages/pynq/*
chmod -R a+rw /home/xilinx/jupyter_notebooks /opt/python3.6/lib/python3.6/site-packages/pynq
chmod -R a+x /home/xilinx/scripts/*
chmod a+x /root/*.sh
chmod a+x /etc/rc.local
chown -R xilinx:xilinx /home/xilinx/jupyter_notebooks /home/xilinx/scripts /opt/python3.6/lib/python3.6/site-packages/pynq
Notebooks     folder is at: /home/xilinx/jupyter_notebooks
Scripts       folder is at: /home/xilinx/scripts

Completed PYNQ update.

xilinx@pynq:~$ 
```

PYNQ 1.4になりました。

## BNN-PYNQのインストール
BNN-PYNQをインストールします。
BNN-PYNQの[Quick Start](https://github.com/Xilinx/BNN-PYNQ#quick-start)に記載されていますが、下記のコマンドでインストールできます。

```shell-session
xilinx@pynq:~$ sudo pip3.6 install git+https://github.com/Xilinx/BNN-PYNQ.git
[sudo] password for xilinx: 
The directory '/home/xilinx/.cache/pip/http' or its parent directory is not owned by the current user and the cache has been disabled. Please check the permissions and owner of that directory. If executing pip with sudo, you may want sudo's -H flag.
The directory '/home/xilinx/.cache/pip' or its parent directory is not owned by the current user and caching wheels has been disabled. check the permissions and owner of that directory. If executing pip with sudo, you may want sudo's -H flag.
Collecting git+https://github.com/Xilinx/BNN-PYNQ.git
  Cloning https://github.com/Xilinx/BNN-PYNQ.git to /tmp/pip-7pt0wn6t-build
Installing collected packages: bnn-pynq
  Running setup.py install for bnn-pynq ... done
Successfully installed bnn-pynq-0.1
```

## BNN-PYNQの実行

インストールが完了すると、Jupyter上でbnnフォルダができているかと思います。
このフォルダの中には、いくつかのサンプルが準備されています。
![jupyter-bnn-sample.png](https://qiita-image-store.s3.amazonaws.com/0/129013/8a37d460-0761-7fc6-479a-7c9b06c74bff.png)

この中から、Cifar10を実行してみたいと思います。[Cifar10](https://www.cs.toronto.edu/~kriz/cifar.html)は、画像を10種類に分類するサンプルです。
とりあえず、[Run All]してみます。

<img src="https://qiita-image-store.s3.amazonaws.com/0/129013/4eb684b6-3531-3a60-7b17-97b57dd69336.png" width="100px">

すべて実行されました。
ソースコードを見てみるとわかりますが、Pythonでは、推論対象画像の読み込みと、classify_imageの呼び出しを行っているだけですね。

CPUとFPGAによる速度の比較結果は、下記の箇所で表示されています。

![pynq-cifar10-hw-sw.png](https://qiita-image-store.s3.amazonaws.com/0/129013/c97c1573-29d9-83fa-143e-7392475f4afb.png)

PYNQのCPUが遅いこともありますが、360倍以上の差が出ていますね。

```
 4. Launching BNN in hardware → FPGAを使用
    2223.00 microseconds
 5. Launching BNN in software → CPUのみを使用
    817744.00 microseconds
```

## ソースコード
PIPでインストールした、BNN-PYNQの[bnn.py](https://github.com/Xilinx/BNN-PYNQ/blob/master/bnn/bnn.py)を簡単に見てみます。

まず、`CnvClassifierクラス`の初期化です。

```py3
class CnvClassifier:
    def __init__(self, params, runtime=RUNTIME_HW):
        self.bnn = PynqBNN(runtime, network=NETWORK_CNV)
        self.bnn.load_parameters(params)
```

ここでは、PynqBNNのインスタンス生成と、ネットワークの学習パラメーターをロードしています。

次に、`PynqBNNクラス`の初期化です。

```py3
class PynqBNN:
    
    def __init__(self, runtime=RUNTIME_HW, network=NETWORK_CNV, load_overlay=True):
        self.bitstream_name = None
        if runtime == RUNTIME_HW:
            self.bitstream_name="{0}-pynq.bit".format(network)
            self.bitstream_path=os.path.join(BNN_BIT_DIR, self.bitstream_name)
            if PL.bitfile_name != self.bitstream_path:
                if load_overlay:
                    Overlay(self.bitstream_path).download()
                else:
                    raise RuntimeError("Incorrect Overlay loaded")
        dllname = "{0}-{1}.so".format(runtime, network)
        if dllname not in _libraries:
            _libraries[dllname] = _ffi.dlopen(
		os.path.join(BNN_LIB_DIR, dllname))
        self.interface = _libraries[dllname]
        self.num_classes = 0
```

ここでは、指定されたネットワークに応じて、Overlayがロードされています。
ちなみに、`Overlay(self.bitstream_path).download()`の中では、bitstreamファイルを読み込み、`/dev/xdevcfg`というデバイスファイルに書き出しているらしいです。
[PYNQ-Z1のOverlay読み込みとPythonからのFPGA PLの制御](http://todotani.cocolog-nifty.com/blog/2017/01/pynq-z1overlayp.html)

また、FPGAにアクセスするための共有ライブラリの読み込みを行っています。BNN-PYNQでは、`cffi.FFI.dlopen`で共有ライブラリにアクセスすることで、共有ライブラリを経由してFPGAを利用しているんですね。

最後に、`PynqBNNクラス`の推論です。

```py3
    def inference(self, path):
        usecperimage = _ffi.new("float *") 
        result_ptr = self.interface.inference(path.encode(), _ffi.NULL, len(self.classes), usecperimage)
        print("Inference took %.2f microseconds" % (usecperimage[0]))
        print("Classification rate: %.2f images per second" % (1000000.0/usecperimage[0]))
        return result_ptr
```

推論するために、`self.interface.inference`を呼び出しています。
先ほど、表示されていた推論時間はここで`print`されたもののようですね。

# おわりに

現状、推論だけですが、FPGAでDeep Learningを実行することができました。
CPUと比較してかなり早かったです。
また、Python(Jupyter Notebook)で利用できるので、アプリケーションに埋め込むことや、テストすることが簡単にできそうだと思いました。

ただ、今回はPLには手を出していないため、FPGAを使った実感はあまりなかったです。
共有ライブラリ以降の[ソースコード](https://github.com/Xilinx/BNN-PYNQ/tree/master/bnn/src/network)も公開されており、[リビルド方法](https://github.com/Xilinx/BNN-PYNQ#hardware-design-rebuilt)も記載されているため、見ていきたいです。

PYNQが紹介されており、とても興味深い記事でした。
[機械学習／Deep Learningの仕事が増える2017年、ソフトウェアエンジニアがFPGAを学ぶべき理由](http://www.atmarkit.co.jp/ait/articles/1701/30/news007.html)