FPGA開発日記

FPGAというより、コンピュータアーキテクチャかもね! カテゴリ別記事インデックス https://sites.google.com/site/fpgadevelopindex/

Rocket ChipのChiselを使ってアクセラレータを作る (より大きな行列に対応したDot Productハードウェア)

Chiselを使って行列積を計算するハードウェアを開発してきたが、これには弱点がある。 小さな行列に焦点を当てたので、大きな行列(16行以上)に対応できないことだ。

msyksphinz.hatenablog.com

msyksphinz.hatenablog.com

しかしこの方法には利点もある。16行に限定したため内部にレジスタキャッシュを持っており、同じアドレスをフェッチする場合はフェッチリクエストを省略し、高速化した。 しかしより大きな行列を計算する場合はそうもいかない。例えば数千行の行列を計算するのにすべて内部レジスタに保持しておくわけにもいかないので、別の方法を考える必要がある。

今回より行列サイズを一般化して、レジスタファイルキャッシュを省略し、どのような行列サイズでも実現できるアクセラレータを作ってみる。

イデアとしては、これまで行と列を順番にフェッチしてきたが、これを編み込む。

f:id:msyksphinz:20170922030906p:plain

これにより、最初にフェッチした行を全て記憶しておく必要が無いので、いくらでも行列サイズを大きくすることが出来る。 この場合L1の局所性が心配になる。改善策としてはある程度の塊で一気に取ってくる方法があるが、まあ列方向だとそれは効きそうにないので、今回は省略。

で、とりあえず今回はアイデア出しと、簡単な実装のみ実現した。

命令としてはカスタム命令に対してFunct=0, Funct=1の2種類を用意した。

  • Funct=0、行列サイズ(行列Aの縦方向 = 行列Bの横方向)を指定する。
  • Funct=1、メモリをフェッチしDot Productを実行する

の2種類を作った。最初にFunct=0はレジスタ戻り値を使わないので適当に返していたところRocket Coreが固まってしまい原因を突き止めるのに相当な時間がかかってしまった。

  when (io.cmd.fire() && setLength) {
    printf("MatrixMulTwoRequester: SetLength Request. %x\n", io.cmd.bits.rs1)
    r_matrix_max := io.cmd.bits.rs1
    r_recv_state := s_recv_finish
  }

  // たとえ使わなくてもきちんと初期化して返すこと!
  when (io.cmd.fire()) {
    r_total      := UInt(0)
    r_resp_rd := io.cmd.bits.inst.rd
  }

  when (io.cmd.fire() && doCalc) {
    printf("MatrixMulTwoRequester: DoCalc Received. %x, %x\n", io.cmd.bits.rs1, io.cmd.bits.rs2)

    r_v_addr     := io.cmd.bits.rs2
    r_h_addr     := io.cmd.bits.rs1

    r_recv_count := UInt(0)
    r_cmd_count  := UInt(0)
    r_tag        := UInt(0)

    r_cmd_state  := s_mem_fetch
    r_recv_state := s_mem_recv
  }

とりあえず今日はここまで。まだ答えはあっていないが、ステートマシンは動作しているみたいだ。きちんとデバッグしていこう。

OpenVX実装についての調査とチュートリアル試行

ザイリンクスのreVISIONスタックは、コンピュータビジョン、機械学習、コネクティビティ & センサー サポート に注力してFPGA上で動作させることのできるハードウェアデザインやソフトウェアスタックを提供している。

このなかでコンピュータビジョンに注目してみると、コンピュータビジョン向けの最も有名なライブラリ群OpenCVをベースにしたザイリンクスのxfOpenCVを提供している。

  • 高い応答性とリコンフィギャラブルなビジョン システム

https://japan.xilinx.com/products/design-tools/embedded-vision-zone.html#computer

この中で、提供しているライブラリは、上記のウェブサイトによると、

  • OpenCV 3.1 ライブラリの数百以上の関数が、Zynq の ARM Cortex™-A9 および Cortex A53 コアで実行するために利用可能
  • 約 45 個の OpenCV 関数 (OpenVX のサブセット) が、ザイリンクス SoC 向けに RTL に最適化された関数ライブラリとして利用可能
  • バイス使用率と性能を示したライブラリ ユーザー ガイド
  • ほとんどの関数は、1 および 8 ピクセルのパラレル バージョンに対応

OpenCVはARM Cortexで動作させ、OpenVXのサブセットはSoCやRTL実装されているということだろうか?興味がある。 そもそもOpenVXというのは画像認識のためのAPIで、現在はバージョン1.2まで定義されているが実装としてはnVIDIAAMDによる実装しか存在していない。

www.khronos.org

VisionWorks | NVIDIA Developer

AMD OpenVX (AMDOVX) - GPUOpen

とりあえずWindows上で手っ取り早く試してみる方法として、他者が提供している仮想マシンをダウンロードして実行してみる方法がある。

GitHub - rgiduthuri/openvx_tutorial: Khronos OpenVX Tutorial Material

VirtualBoxの仮想イメージをダウンロードして、実行してみると、いくつかプロジェクトが立ち上がってOpenVXのサンプルを実行できる。

f:id:msyksphinz:20170920231102p:plain

それ以外にも、日本語のチュートリアルも存在する。これらをいくつか触ってみて、OpenVXの理解を深めていこうかな。

github.com

RoCCアクセラレータを乗せたRocket ChipのFPGA論理合成(4. 自作アクセラレータの論理合成)

RISC-Vの実装、Rocket Chipのアクセラレータ向けインタフェースにハードウェアを実装し、RTLシミュレーションを行った。 次に、Rocket ChipをFPGA向けに論理合成するところまで上手く行ったので、今回開発したRoCCアクセラレータをFPGAに実装してみよう。 これで自作アクセラレータが正しく動作すれば、成功だ。

fpga-zynq で Rocket Chipリポジトリを差し替える

実際、Rocket Chipは結構な頻度で仕様や実装が変更されている。今回は割と最近のRocket Chipのリポジトリをベースにアクセラレータを開発した。 具体的には以下のコミットをベースに、ブランチを作って拡張を行っている。

github.com

しかし、fpga-zynqのリポジトリが現在サブリポジトリとして指定しているのはかなり古いrocket-chipのリポジトリだ。実際、半年前のRocket Chipの実装を指しており、このサブモジュールを最新までアップデートして論理合成を試行してみたのだが、上手く行かなかった。

github.com

そこで、fpga-zynqが指しているRocket Chipのサブモジュールのリビジョンに、今回開発した自作アクセラレータの差分のみ適用し、そのまま論理合成を実行する。 この場合、RoCCアクセラレータのインタフェースとインスタンスの方法が少し変わっているため、そのままアクセラレータを乗せることはできなかったが、僅かな改変で一応、論理合成は上手く行ったようだ。

f:id:msyksphinz:20170920010910p:plain

気になる動作周波数の方だが、そもそもRocket Chipの動作周波数が20MHzとなるようにクロック制約が組まれているため(!)、余裕でMetしている。 きちんと実装した行列演算アクセラレータも実装されているようだ。

あとは、これをFPGAに実装して動作確認してみるだけとなる。いつものテストプログラムをSDカードに読み込んで、FPGALinuxをブートさせた。 そしてRocket Chipのフロントエンドサーバを起動させる。

root@zynq:~# ./fesvr-zynq test-matrixmul.riscv

f:id:msyksphinz:20170920013000p:plain

よし!動いた! FPGAでRoCCアクセラレータの動作に成功したぞ!パフォーマンスも想定通りだ。

「ディジタル回路設計とコンピュータアーキテクチャ 第2版」が出版されていた

ディジタル回路設計とコンピュータアーキテクチャ 第2版

ディジタル回路設計とコンピュータアーキテクチャ 第2版

  • 作者: デイビッド・マネーハリス,サラ・L.ハリス,David Money Harris,Sarah L. Harris,天野英晴,鈴木貢,中條拓伯,永松礼夫
  • 出版社/メーカー: 翔泳社
  • 発売日: 2017/09/11
  • メディア: 単行本
  • この商品を含むブログを見る

いわゆるパタヘネ、ヘネパタと比較して、より実践より(Verilog-HDLでの実装とか)に重きを置いている「ディジタル回路設計とコンピュータアーキテクチャ」の第2版の日本語版が出版されていた。

第1版は、内容をちょっと確認して、基礎的なところで終わりそうだったので購入していなかったのだが、第2版ではどこが更新されたのだろう。

そんな、マイクロプロセッサを作りながら学ぶ『ディジタル回路設計とコンピュータアーキテクチャ』が、System Verilogに対応してバージョンアップしました。

なるほど、実装がSystem Verilogに対応したというところがメインか。System Verilog対応により、実装記述がよりシンプルになっているのならチェックする価値があるのかもしれない。 とりあえずは、本屋で立ち読みしてみて、購入するかどうか考えようかな。 内容はあまり第1版と変わってなさそう。

RISC-VのRoCCアクセラレータ付きRocket-Chip構成をFPGA向けに合成する(3. アプリケーション動作)

前回作業をした、RISC-VのRocket Chipで、FPGAにRoCCアクセラレータ付きの実装をインプリメンテーションする作業、前回までは、 一応ブートまで進んだものの、何故かアプリケーションが動作せず、作業が止まってしまっていた。

そこで、もっとお気楽なビルド方法として、makeだけで実行する方法がある。つまり、fpga-zynqリポジトリで、

cd zedboard
make CONFIG=ZynqRoccConfig

とするだけだ。こうすることで、zedboard_rocketchip_RoccZynqConfig ディレクトリから作成され、fpga-images-zedboard ディレクトリにSDカードにダウンロードするファイルが生成される。 これをSDカードに再度ダウンロードし、ZynqでLinuxを起動した。

sudo mount  /dev/sdb1 /mnt/ZED_ROOT/
sudo cp -r fpga-images-zedboard/* /mnt/ZED_ROOT/
sudo umount /mnt/ZED_ROOT

無事に動作する構成を用意できたようだ。

root@zynq:~# ./fesvr-zynq pk hello
hello!

次に、オリジナルのアプリケーションを動作させる準備をしよう。 RISC-V動作用のSDカードに新しいファイルを追加するためには、先ほどのリポジトリ内でzedboardに移り、make ramdisk-openmake ramdisk-closeを使って、ファイルシステムを編集する。

make ramdisk-open
mkdir ramdisk
dd if=fpga-images-zedboard/uramdisk.image.gz  bs=64 skip=1 | \
gunzip -c | sudo sh -c 'cd ramdisk/ && cpio -i'
[sudo] msyksphinz のパスワード:
127899+1 レコード入力
127899+1 レコード出力
8185554 bytes (8.2 MB, 7.8 MiB) copied, 8.89832 s, 920 kB/s
35892 ブロック

これで、ramdiskディレクトリ内でファイルシステムを編集できるようになった。

$ cd ramdisk/
$ ls -lt
合計 52
drwxr-xr-x  2 root root 4096  918 02:04 bin
drwxr-xr-x  2 root root 4096  918 02:04 boot
drwxr-xr-x  3 root root 4096  918 02:04 home
drwxr-xr-x  2 root root 4096  918 02:04 proc
drwxr-xr-x  2 root root 4096  918 02:04 sbin
drwxr-xr-x 10 root root 4096  918 02:04 media
drwxr-xr-x  3 root root 4096  918 02:04 lib
drwxr-xr-x  2 root root 4096  918 02:04 sys
lrwxrwxrwx  1 root root    8  918 02:04 tmp -> /var/tmp
drwxr-xr-x  8 root root 4096  918 02:04 var
drwxr-xr-x  3 root root 4096  918 02:04 dev
lrwxrwxrwx  1 root root    9  918 02:04 init -> sbin/init
drwxr-xr-x  3 root root 4096  918 02:04 mnt
drwxr-xr-x 11 root root 4096  918 02:04 usr
drwxr-xr-x 16 root root 4096  918 02:04 etc

/home/root/に必要なファイルを移動しよう。今回はベンチマーク測定に使用した、dhrystone.riscvを格納した。

sudo cp /home/msyksphinz/work/rocket-chip-msyksphinz/riscv-tools/riscv-tests/benchmarks/dhrystone.riscv home/root/

ファイルの編集を終えると、make ramdisk-closeを使ってramdiskを再生成する。

$ make ramdisk-close
sh -c 'cd ramdisk/ && sudo find . | sudo cpio -H newc -o' | gzip -9 > uramdisk.cpio.gz
35892 ブロック
mkimage -A arm -O linux -T ramdisk -d uramdisk.cpio.gz fpga-images-zedboard/uramdisk.image.gz
Image Name:
Created:      Mon Sep 18 02:07:26 2017
Image Type:   ARM Linux RAMDisk Image (gzip compressed)
Data Size:    8185517 Bytes = 7993.67 kB = 7.81 MB
Load Address: 00000000
Entry Point:  00000000
rm uramdisk.cpio.gz
Don't forget to remove ramdisk before opening it again (sudo rm -rf ramdisk)

再度SDカードに内容をコピーする。

sudo mount  /dev/sdb1 /mnt/ZED_ROOT/
sudo cp -r fpga-images-zedboard/* /mnt/ZED_ROOT/
sudo umount /mnt/ZED_ROOT

再びSDカードを起動し、早速プログラムを実行してみよう。

root@zynq:~# ./fesvr-zynq pk hello
hello!
root@zynq:~# ./fesvr-zynq dhrystone.riscv
Microseconds for one run through Dhrystone: 688
Dhrystones per Second:                      1451
mcycle = 344442
minstret = 201031

Dhrystoneが動いた!これ、pkを指定するかしないかの違いがまだはっきりとは分かっていないのだけれどもpk(proxy kernel)の部分はDhrystone(つまりベンチマークで使用しているプログラム)はあらかじめすべてバイナリに含んでしまっているものと思われる。だから、pkを指定しなくても良いのかもしれない。

f:id:msyksphinz:20170918021226p:plain

RoCCアクセラレータを動かすプログラムもコンパイルして、アクセラレータ部を動作させた。最新デザイン向けに、物理アドレスへの変換部を除去したら、正しく動作した。 よしよし。

f:id:msyksphinz:20170918031935p:plain

Rocket ChipのChiselを使ってアクセラレータを作る (行列積アクセラレータを高速化する)

前回、RISC-VのRocket Chipに整数のDot Productを計算するアクセラレータを接続した。 これにより、整数行列積の計算において、約2.7倍の高速化を達成した。

ただし、これはまだ高速化できる余地がある。 一度データをアクセラレータ内に格納すると、次に同じアドレスからフェッチをしてくる場合はそのフェッチ処理を省略し、なるべく使いまわしたい。 特に、データキャッシュを縦方向に舐める、乗数行列のフェッチにおいては、なるべくフェッチの回数を短くしたい。

ということで、一度キャッシュした場所を再度フェッチしに行く場合は、既にキャッシュしてあるデータを再利用し、再フェッチをしないという方式を導入してみる。 コヒーレントを取るという観点からはこの方式は若干問題があるが、とりあえず気にせず進めていこう。

f:id:msyksphinz:20170915014654p:plain

この時にソフトウェアの注意点として、行列積を求める順番が重要になる。つまり、なるべく乗数行列(第2オペランド側)が、なるべく連続で同じデータを利用するように工夫してやる必要がある。

  for (int i = 0; i < 16; i++) {
    for (int j = 0; j < 16; j++) {
      matrixmul (output_data[j*16+i], &(input1_data[j*16]), &(input2_data[i]));
      //                                                    ~~~~~~~~~~~~~~~~~ ← ここがなるべく連続で同じアドレスを使用するように。
    }
  }

これで、メモリアクセスの回数を約半分に削減できたはずだ。性能はどうなったかと言うと、

  • キャッシュ付きのハードウェアアクセラレータを使用 : 11418サイクル
  • ハードウェアアクセラレータ使用 : 17166 サイクル
  • ソフトウェアのみで実装 : 47435 サイクル

というわけで、オリジナルのソフトウェアによる実装と比較して、4.15倍の高速化となった。まあまあかな。

逆に、これだけ頑張ったのに何で4倍程度しか速くならないんだろう。これをむしろ調査しなければ。

Rocket ChipのChiselを使ってアクセラレータを作る (行列積アクセラレータのアイデア出し)

Rocket ChipとChiselの勉強を進めてきた。RoCCのインタフェースにChiselで記述したアクセラレータを接続して、その効果を測定した。

まずは簡単化のために、RoCCインタフェースからメモリの値を読み込んで、その値をすべて加算するプログラムを書いてみた。 これは以下の記事にまとまっている。

msyksphinz.hatenablog.com

msyksphinz.hatenablog.com

この記事では、アクセラレータを導入したことによりソフトウェアと比較して大よそ2倍の性能を得ることができた。 今度は、ディープラーニングなどで話題の行列積について、何か高速化のためのアイデアは作れないだろうか。

今回は、まずは整数Dot Productを実行するデザインをChiselで作ってみる。これを組み合わせて、行列積を実行するという方式にしてみよう。 つまり、以下のようなものになる。

ちなみに、浮動小数点数において似たような研究はUCBのRISC-Vチームが研究している。 Vengineerさんも紹介していた以下の論文だ。この論文、絶対性能での評価が書いていないから若干怪しいなあ。。。

blogs.yahoo.co.jp

  • A Hardware Accelerator for Computing an Exact Dot Product

https://aspire.eecs.berkeley.edu/wp/wp-content/uploads/2017/05/exact-dot-product-arith2017.pdf

話を戻して、具体的には、以下の行列の計算の一部分を実行するアクセラレータを作成する。

f:id:msyksphinz:20170915012415p:plain

アクセラレータの実装

f:id:msyksphinz:20170915013559p:plain

アクセラレータの実装詳細の解説は、諸事情でここでは省略するが、ソフトウェア側では、上記の矢印で記述した整数のDot Productを、1命令で実行する。 今回は、まずは  16\times 16 の行列積に限定してアクセラレータを記述した。

  printf ("Hardware Start\n");
  start_cycle = read_csr(mcycle);
  for (int i = 0; i < 16; i++) {
    for (int j = 0; j < 16; j++) {
      matrixmul (output_data[j*16+i], &(input1_data[j*16]), &(input2_data[i]));
    }
  }
  stop_cycle = read_csr(mcycle);

これをソフトウェアで測定すると、サイクル数の比率は以下のようになった。だいたい2.7倍の高速化だ。

  • ハードウェアアクセラレータ使用 : 17166 サイクル
  • ソフトウェアのみで実装 : 47435 サイクル

実は、ハードウェアによる高速化、という観点よりも、Rocket Chip自体がかなり高速だなということに驚いている。 もっと引き離すと思っていたのだが、予想外に2.7倍の高速化にとどまった。

逆に、何故そこまでRoCC経由では高速にならないのだろう?ここを調査する必要がある。