LoginSignup
10
8

More than 3 years have passed since last update.

AIエッジコンテスト(実装コンテスト)のチュートリアル【10: HWをPythonで制御する・・が、しかし、、】

Posted at

ようやく長い道のりを経て準備が終わりました。Ultra96V2ボードで設計した畳み込み回路を動かしてみましょう!

必要なファイルをUltra96V2に転送

前回(AIエッジコンテスト(実装コンテスト)のチュートリアル【9: HW合成してビットストリームを生成するまで】)で以下のファイルを作成したはずです。

  • pynq_ultra96_conv_l0_r1.bit
  • pynq_ultra96_conv_l0_r1.tcl
  • pynq_ultra96_conv_l0_r1.hdf
  • pynq_ultra96_conv_l0_r1.hwh

第3回の(AIエッジコンテスト(実装コンテスト)のチュートリアル【3: Ultra96ボードのCPUで推論実行】)を参考にしてこれらのファイルをUltra96V2ボード上の/home/xilinx/pynq/overlays/baseに置いてください。

また、これまで使ってきたテストベンチ

  • testbench_input.txt
  • testbench_output.txt

/home/xilinx/dataに置いてください。

ハードウェアを制御するノートブックをチュートリアルのリポジトリ(https://github.com/HirokiNakahara/FPGA_AI_Edge_Contest_2019/blob/master/Inference_PYNQ_1)に置いています。クローンして、Ultra96V2のホーム/home/xilinxに置いてください。あとからUltra96V2のJupyter Notebookで読み込みます。

いよいよ推論をハードウェアで実行

Ultra96V2のJupyter Notebookにブラウザから接続します。第3回を参考にしてください。
スクリーンショット 2020-03-20 11.21.39.png
Uploadをクリックしてノートブック(ultra96v2_pynq_convolution_layer0.ipynb)を読み込んで実行します。あとは上から実行していくと準備、推論実行(ただし遅い)、比較のためのCPU上での推論がそれぞれ行われます。

以下、要点を絞って解説します。

ultra96v2_pynq_convolution_layer0.ipynb
from pynq import Overlay
import pynq

overlay = Overlay('/home/xilinx/pynq/overlays/base/pynq_ultra96_conv_l0_r1.bit')
dir(overlay)

PYNQはoverlayという概念でハードウェアを抽象化します。表示してみるとわかりますが、前回のIP接続で使ったコアの名前(kernel_0とかaxi_dma_0)がいくつか出ていると思います。これにアクセスして操作を行います。

ultra96v2_pynq_convolution_layer0.ipynb
registers = overlay.kernel_0.register_map

例えば、ユーザのIPコアにPythonで制御するにはregister_mapにアクセスすれば可能です。今回はプラグマでAXIストリームを指定していますので、その操作が簡単に可能です!これはすごい(AXIのバスをRTLで書いたことがある人にとっては)。

DMAの設定ですが、

ultra96v2_pynq_convolution_layer0.ipynb
import pynq.lib.dma

dma = overlay.axi_dma_0

とオーバーレイにアクセスして

ultra96v2_pynq_convolution_layer0.ipynb
from pynq import Xlnk

inimg_size = 416*11*3
outfmap_size = 102*64+1

xlnk = Xlnk()

send_buf   = xlnk.cma_array(shape=(inimg_size),dtype=np.int32)
recv_buf = xlnk.cma_array(shape=(outfmap_size),dtype=np.int32)

Xlnk()(Xilinx社が設計したDMA制御ミドルウェアのラッパ)を読んで、配列を確保しておしまいです。楽勝。

ハードウェアのデータ転送と受信ですが

ultra96v2_pynq_convolution_layer0.ipynb
%%time
for line in range(102):
    # load input image
    for i in range(11):
        inimg_buf[i] = inimg[i+line*4]

    tmp = inimg_buf.copy().transpose((2,0,1)).reshape(-1,) # CH,Y,X
    send_buf[0:inimg_size] = tmp[0:inimg_size]

    # activate DMA
    registers.CTRL.AP_START = 1

    # DMA access
    dma.sendchannel.transfer(send_buf)
    dma.recvchannel.transfer(recv_buf)

    # wait DMA
    dma.sendchannel.wait()
    dma.recvchannel.wait()

    # store output buffer
    tmp2 = recv_buf[0:outfmap_size - 1]
    tmp2 = tmp2.reshape((64,102)) # CH, X
    outfmap_buf[line] = tmp2

numpyの配列(ここではinimg)を渡してあげて転送開始のレジスタをONに設定(AP_START)します。あとはtransferにバッファを渡して転送(すなわち畳み込み演算の処理)が終わるまで待ちます(wait)。その後、該当するデータをnumpyの配列に渡してあげれば終了。これを出力のライン分繰り返します。

時間を%%timeで計測しました。Jupyter Notebookで外部コマンドを呼ぶ方法ですね。で、どれどれ

CPU times: user 22.5 s, sys: 6.85 ms, total: 22.5 s
Wall time: 22.5 s

おそい。。。やっぱり22秒とHLSの見積もりは正確でした。。。
この後に検証もしています。一応動かして正しくHWが動いていることも確認してください。

おまけ。CPU上の推論時間は?

ついでにPytorchをインストールしているはずなので、CPU推論時間を確認してみましょう。

ultra96v2_pynq_convolution_layer0.ipynb
import torch
x = torch.randn(1,3,416,416)
conv = torch.nn.Conv2d(in_channels=3, out_channels=64, kernel_size=11,stride=4,bias=False)
y = conv(x)
CPU times: user 259 ms, sys: 7.96 ms, total: 267 ms
Wall time: 93.2 ms

え、約100倍速い。。。どーすんの、これ。
(ということが割とよくおきます>FPGA設計)

どうするんだよ

ということで一通りPytorchの学習→ソフトウェア設計→ハードウェア設計→FPGAで実際に動作、までを一通りやってみましたが、結果は散々でした。。いかにハードウェア設計のハードルが高いか、ましてはディープラーニングだったら、、ということが理解できたと思います。

このままだと流石にまずいので、とりあえずもうちょっと頑張って速くしてみましょうかね。
ということでもうちょっと続くのじゃ。

10
8
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
8