FPGAでDeep Learningしてみる

  • 111
    いいね
  • 2
    コメント

はじめに

XilinxがBNN-PYNQというプロジェクトを公開したことにより、FPGA初心者でも簡単にDeep LearningをFPGA実行することができるようになりました。早速ボードを購入してデモ実行まで試してみました。

事前説明

PYNQ

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

pynq-logo.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の詳細は下記が参考になると思います。

PYNQ-Z1 Board

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

pynq-z1-board.png
PYNQ: PYTHON PRODUCTIVITY ON ZYNQ

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

BNN-PYNQ

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]で紹介されているCNVとLFCが、PYNQのOverlayとして公開されています。

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

実装

BNN-PYNQでは、Deep Learningをxilinx-tiny-cnnというライブラリを使って実装しています。xilinx-tiny-cnnは、tiny-dnnを基にしており、次の点が変更されているとのことです。
BNN-PYNQは、tiny-dnnを利用しています。

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

tiny-dnnの開発者は日本人のようです。すごいですね。。。
C++ヘッダだけでDeep Learning、tiny-dnnの紹介

デモの実行

環境

BNN-PYNQを実行するにあたり、下記を準備しました。

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

PYNQの初期設定

ドキュメントのGetting Startedに沿って進めます。

イメージの作成

まずは、イメージのダウンロードです。上記ドキュメントの手順内にあるDownload and the PYNQ-Z1 imageからダウンロードします。また、ダウンロードは、Digilentのサイトからもできるようです。(違いはわかりません)
記事を書いている時点では、pynq_z1_image_2017_02_10.zipがダウンロードされました。

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

$ tar zxvf pynq_z1_image_2017_02_10.zip
x pynq_z1_image_2017_02_10.img

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

$ 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でフォーマットします。

$ 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カードは抜きません。

$ diskutil unmountDisk /dev/disk1
Unmount of all volumes on disk1 was successful

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

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

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

起動

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

pynqz1_setup.jpg

⓪ 電源スイッチが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

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

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

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

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に記載されていますが、下記のコマンドでインストールできます。

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

この中から、Cifar10を実行してみたいと思います。Cifar10は、画像を10種類に分類するサンプルです。
とりあえず、[Run All]してみます。

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

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

pynq-cifar10-hw-sw.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を簡単に見てみます。

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

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

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

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

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の制御

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

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

    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を使った実感はあまりなかったです。
共有ライブラリ以降のソースコードも公開されており、リビルド方法も記載されているため、見ていきたいです。

PYNQが紹介されており、とても興味深い記事でした。
機械学習/Deep Learningの仕事が増える2017年、ソフトウェアエンジニアがFPGAを学ぶべき理由