FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

NVDLAの内部構成調査(5. NVDLA VPによるアプリケーション実行)

前回はCaffeをインストールして、LeNetを実行した結果をnvdla_compilerに食わせて、NVDLAのバイナリファイルを作成した。

これを実行するためには、NVDLA Virtual Platformを実行してLinuxを立ち上げ、そこでnvdlaを動作させるランタイムプログラムを実行しなければならない。

大まかな手順はNVDLAのウェブサイトに掲載されているが、いろいろとテクニックが必要だ。

NVDLA Virtual Platformの立ち上げ方はこちら。

msyksphinz.hatenablog.com

f:id:msyksphinz:20180904234315p:plain
図. NVDLA Virtual Platformの概要図 (http://nvdla.org/vp.html より抜粋)

Caffeのニューラルネットワークのモデルをロードして実行

QEMUにログインすると、まずはホストのディレクトリをマウントしてCaffeのモデルとランタイムプログラムを参照できるようにする。

# QEMUログインして実行
$ mount -t 9p -o trans=virtio r /mnt

QEMUを立ち上げた場所(おそらく高確率でvp)がマウントされるので、そこにファイルを集める。 Caffe で作成したbasic.nvdlaswリポジトリなどに格納されている画像ファイル(sw/regression/images/*.pgm)などをマウント領域に見えるようにコピーしてくる。

ドライバのロード

まずはドライバをロードする必要がある。sw/prebuilt/linux/をマウントしている場所に移って、以下を実行する。

$ insmod drm.ko
$ insmod opendla.ko

nvdla_runtimeを実行する

NVDLAを動かすためのフロントエンドはnvdla_runtimeだ。いろいろオプションを探して、lenetを実行した。

以下ではLeNetのモデルを入力し、入力画像として1の画像(one.pgm)を入力している。

# ./nvdla_runtime --loadable ../lenet/basic.nvdla --image ../lenet/digits/one.pgm
creating new runtime context...
Emulator starting
ppgminfo 1 28 28
pgm2dimg 1 28 28 1 32 896 896
submitting tasks...
[ 1429.133975] Enter:dla_read_network_config
[ 1429.134275] Exit:dla_read_network_config status=0
[ 1429.134504] Enter: dla_initiate_processors
[ 1429.134729] Enter: dla_submit_operation
[ 1429.134918] Prepare Convolution operation index 0 ROI 0 dep_count 1
[ 1429.135157] Enter: dla_prepare_operation
[ 1429.135409] processor:Convolution group:0, rdma_group:0 available
[ 1429.135706] Enter: dla_read_config
[ 1429.135976] Exit: dla_read_config
[ 1429.136147] Exit: dla_prepare_operation status=0
[ 1429.136336] Enter: dla_program_operation
[ 1429.136521] Program Convolution operation index 0 ROI 0 Group[0]
...
[ 1651.413376] Enter: dla_free_op_desc op desc index 10 ROI 0
[ 1651.413632] Exit: dla_free_op_desc
[ 1651.413793] Exit:dla_op_completion processor SDP group0 status=0
[ 1651.414039] Exit:dla_handle_events, ret:0
[ 1651.414210] Enter:dla_handle_events, processor:PDP
[ 1651.414417] Exit:dla_handle_events, ret:0
[ 1651.414603] Enter:dla_handle_events, processor:CDP
[ 1651.414791] Exit:dla_handle_events, ret:0
[ 1651.414971] Enter:dla_handle_events, processor:RUBIK
[ 1651.415184] Exit:dla_handle_events, ret:0
[ 1651.418539] reset engine done
Work Found!
Work Done
Shutdown signal received, exiting
Test pass

一応実行できたぞ。ただしメッセージも出てこないし、正しく推論できたかもわからない。 もう少しプログラムを改造して解析したいのと、NVDLAがどのように動いているのかを見ていきたい。

Design Solution Forum 2018にて発表します

去年よりも開催が1か月早くて焦っています。

2018/09/12(水)に開催されるDesign Solution Forumにて、RISC-V/IoTのトラックの1つとして発表させて頂くことになりました。

13:30~14:10 で、タイトルは "試しながら学ぶオープンソースプロセッサRISC-V の世界" としました。 なんか去年とタイトルが似てますね!つまりあまりしゃべることが無いんです。

仕事としてやってきたわけでもなく、仕事から帰ってきてクタクタになりながら更新していたブログなので、情報量としてはいまいちになるかもしれません。興味のある皆様よろしくお願いします。

f:id:msyksphinz:20180901131835p:plain

あと一週間なのにまだスライド全然できていなくて内容変わるかもしれないんですが、

  • RISC-Vの最新動向をまとめて解説。発表者がブログを通じて感じたRISC-Vのよもやま話
    • RISC-V Foundationは、どこへ向かおうとしているのか
    • どこで使われている?徐々に世界に浸透する”RISC-V”
    • RISC-Vの仕様を読みながら実装を作って、RISC-Vの光と闇を覗いてみる
    • RISC-Vを通じて変わる「ハードウェア設計の常識」

っていうか、お隣のセッションでポスト京やってるので集客は期待しておりません。。。静かに気づかれないよう発表したいところ。

という訳で、「ポスト京なんかどうでもいいからRISC-Vの話を聞きたい」とか、「ポスト京についての情報も気になるけどRISC-Vの冷やかしをしたい」っていう方は聴講お待ちしております。

お手柔らかにお願いします。

RISC-Vを使ったEnclaveプロジェクトKeystone

f:id:msyksphinz:20180904021652p:plain

限りなく内容の無い、メモのような日記になってしまったが。。。

RISC-Vアーキテクチャを使ったセキュリティ技術の開発で、Keystoneというプロジェクトがあるらしい。 Hisa Ando氏の記事で知った。

    1. KeystoneでRISC-Vをセキュア化

20180901

  • Keystone Open-source Secure Hardware Enclave

keystone-enclave.org

ちょうどIntel SGXのEnclaveについて知識もついてきたところだし、論文も公開されているので読んでみようかな。

NVDLAの内部構成調査(4. NVDLA_compiler と Caffeデータの変換)

NVDLAの動かし方についてもう少しプログラミングモデルを解き明かしていきたいが、いかんせん内部レジスタの仕様を読み進めながらでは効率が悪くて良くない。

それに実際のニューラルネットワークを使いながらインタフェースを見ていかないと、意味が分からないことになってしまう。 sanityテストと、わずかなConvolutionテストだけを使って内部構成を把握するのはかなり厳しい気がしてきた。

そこで、テストパタンやC言語で書かれたプログラムを用いて、NVDLAを実際に動かして推論を行うコードを見ながら調査を進めていくことにした。 真っ向から解析していっても全く埒が明かないので、少し視点を変えてみようという訳だ。

NVDLAにはハードウェアのデザインだけでなく、ソフトウェアのリポジトリも用意されている。

  • NVDLA Open source Hardware 1.0

github.com

  • NVDLA Open source softwea

github.com

  • NVDLA Open source Virtual Platform

github.com

ソフトウェアのリポジトリの使い方をいろいろと調べていた。nvdla_compilernvdla_runtimeというのが気になっている。

  • NVDLA - Test applications

Test applications — NVDLA Documentation

nvdla_compilerというコマンドを使用すれば、NVDLAに食わすことができるバイナリを生成できそうな気がする。 その時に必要な入力は、prototxtcaffemodelというものらしい。どうやら、Caffeで生成されるもののようだ。

CaffeどころかTensorFlowもろくに使いこなせていないが、バイナリだけ作ってCaffeのモデルを入力するだけでバイナリが作られるのならば、やりようがあるのかもしれない。

./nvdla_compiler [-options] –prototxt <prototxt_file> –caffemodel <caffemodel_file> -o <outputpath>

Caffeの構築

まずはCaffeをダウンロードして手元の環境でモデルを作ることができる環境を整えた。

環境の構築は、 https://gist.github.com/nikitametha/c54e1abecff7ab53896270509da80215 を参考にした。OpenCVやら各種ライブラリのインストールに時間がかかってしまった。

make all -j8

CaffeのLenetサンプルプログラムのビルド

Caffeのサンプルプログラムである、MNISTをLeNetで動かすプログラムは以下でコンパイルできるらしい。

./data/mnist/get_mnist.sh
./examples/mnist/create_mnist.sh
./examples/mnist/train_lenet.sh

すると、examples/mnistに目的のファイルが生成される。これをnvdla_compilerに食わせてみた。

f:id:msyksphinz:20180902235659p:plain
nvdla_compiler --caffemodel lenet_iter_10000.caffemodel --prototxt lenet.prototxt
creating new wisdom context...
opening wisdom context...
parsing caffe network...
libnvdla<3> mark prob
Marking total 1 outputs
attaching parsed network to the wisdom...
compiling profile "basic"...
libnvdla<3> compile network
libnvdla<3> compile349
libnvdla<2> Prototxt input batch size (N = 64) != Profile batch size (N = 1). Preferring N from profile for compiling.
profile compiled "basic"...
closing wisdom context...

output.protobufbasic.nvdlaが生成された。 ProtoBufってことは、Protocol Bufferが使われているのかなあ。ということはバイナリを見ただけでは分からないな。

実際にこの生成されたファイルを使用するためには、NVDLAのVirtual Platformの環境を使用しなければならないらしい。 これにはnvdla_vpとnvdla_runtimeを使用しなければならないのだが、これの解析は後日とする。

Freedom-U-SDKで生成したLinuxバイナリを自作RISC-Vシミュレータで実行 (4. 命令セットシミュレータの高速化検討)

RISC-Vの自作シミュレータでLinuxを立ち上げることに成功したが、まだかなりシミュレーション速度が遅いことが心配だ。

原因はすでに解析済み。メモリアクセスが何度も発生しているためにそこが最適化できていない。 以下は前回Google Perftoolsで解析したシミュレータのボトルネック部分。 LoadMemoryと言う、メモリアクセスをつかさどっている部分にかなりのアクセスが集中している。 WalkPageTableと言うこちらも仮想アドレスから物理アドレスへの変換を行う機構もやはりかなりのアクセスが必要になっている。

この辺りを改良するためには、TLBを実装するとよい。 という訳で、非常に簡単なTLBを実装してメモリアクセスの高速化の検討を行った。

(pprof) top
Total: 19415 samples
    3750  19.3%  19.3%     5083  26.2% Memory::LoadMemory
    3088  15.9%  35.2%     6920  35.6% RiscvPeThread::WalkPageTable
    1457   7.5%  42.7%    19198  98.9% RiscvPeThread::StepExec
    1384   7.1%  49.9%     9034  46.5% RiscvPeThread::FetchMemory
    1185   6.1%  56.0%     1185   6.1% CsrEnv::Riscv_Read_CSR
    1173   6.0%  62.0%     1173   6.0% RiscvDec::DecodeInst
     872   4.5%  66.5%      872   4.5% __memmove_avx_unaligned_erms
     768   4.0%  70.4%      768   4.0% TraceInfo::RecordTrace
     543   2.8%  73.2%     2056  10.6% RiscvPeThread::CheckInterrupt
     397   2.0%  75.3%     3561  18.3% EnvBase::LoadMemoryDebug

RISC-VにTLBはある?

誤解を招きそうなのだが、RISC-VにTLBの仕様は明記されていない。 それはRISC-VがISAの仕様だからであり、わざわざTLBの構成まで規定する必要はないからだ。 なので、Spikeを含めTLBの実装方法はかなりバリエーションがある。 ここでは、Spikeの実装を参考にしながらTLBを実装した。

  static const uint32_t tlb_width = 1 << 8;
  bool   m_tlb_en[tlb_width];
  Addr_t m_tlb_tag[tlb_width];
  Addr_t m_tlb_addr[tlb_width];

ここで、さらにTLBの参照及びアップデートの機能を追加していく。 基本的には、上記で実装しているページWalkをつかさどる関数 WalkPageTable() に追加してけば良い。

  //===================
  // Simple TLB Search
  //===================
  Addr_t  vaddr_vpn = (vaddr >> 12);
  uint8_t vaddr_tag = vaddr_vpn & (tlb_width-1);
  if (m_tlb_en[vaddr_tag] && m_tlb_tag[vaddr_tag] == vaddr_vpn) {

TLBにヒットしなかったときはTLBをアップデートする。

  //==========================
  // Update Simple TLB Search
  //==========================
  DebugPrint("<Info: TLB[%d] <= 0x%016lx(0x%016lx)>\n", vaddr_tag, vaddr_vpn, *paddr & ~0x0fff);
  m_tlb_en  [vaddr_tag] = true;
  m_tlb_tag [vaddr_tag] = vaddr_vpn;
  m_tlb_addr[vaddr_tag] = (*paddr & ~0x0fff) | (pte_val & 0x0ff);

Spikeの実装はどのようになっているのか

Spikeも似たような実装になっている。 Spikeの方は少し複雑で、命令フェッチ、Load、Storeで別々のタグが用意されている。

  • riscv-isa-sim/riscv/mmu.h
  // If a TLB tag has TLB_CHECK_TRIGGERS set, then the MMU must check for a
  // trigger match before completing an access.
  static const reg_t TLB_CHECK_TRIGGERS = reg_t(1) << 63;
  tlb_entry_t tlb_data[TLB_ENTRIES];
  reg_t tlb_insn_tag[TLB_ENTRIES];
  reg_t tlb_load_tag[TLB_ENTRIES];
  reg_t tlb_store_tag[TLB_ENTRIES];

測定結果

Linuxのブートプロセスで、どれくらい高速化されるか観測した。まあまあ速くなった。

1000000000命令実行時間 実行時間
TLB実装前 195.823
TLB実装後(256-entries) 128.412
TLB実装後(512-entries) 128.101
f:id:msyksphinz:20180902183620p:plain

NVDLAの内部構成調査(3. プログラミングシーケンス, Register Description Language)

NVDLAの内部構成調査の続き。前回はNVDLAのBDMAについて調べたが、次は実際にConvolutionの操作を見ていかなければならない。

sanity3のテストパタンを見ると、大量のレジスタを設定してるのだが、それをいちいち調べていくのは大変だ。

まずは、NVDLAの基本的な実行方法について調べていく。

f:id:msyksphinz:20180901013918p:plain
図. NVDLAコアの内部構成 (http://nvdla.org/primer.html より抜粋)

NVDLAのプログラミングシーケンス

以下を参考にした。 Hardware Architectural Specification — NVDLA Documentation

NVDLAサブモジュールをプログラムするための基本的なシーケンスは以下のとおりである。 NVDLAサブモジュールは、すべて同じレジスタを持っており、このシーケンスではCDMAサブモジュールを使ってプログラムを動作させるための手法を説明している。

  1. リセット後、Group0とGroup1はどちらともIDLE状態である。CPUはCDMA_POINTERレジスタを読み出し、PRODUCERレジスタに対してCONSUMERレジスタの値を設定する必要がある(リセット後、CONSUMERレジスタは0に設定されている)。
  2. レジスタグループ0に、1番目のハードウェアレイヤのパラメータを設定する。設定が完了すると、D_OP_ENABLEレジスタenableビットフィールドに1を設定する。
  3. ハードウェアが最初のハードウェアレイヤの処理を始める。
  4. レジスタグループ1がIDLE状態であることを確認するためにS_STATUSレジスタを確認する。
  5. CPUはPRODUCERに1を設定し、グループレイヤ1に対して2番目のハードウェア寧屋のパラメータを設定し始める。これのレジスタに対するプログラムが完了すると、グループ1のD_OP_ENABLEレジスタのenableビットを設定する。
  6. CPUはS_STATUSレジスタを参照して、レジスタグループ0の状態を確認するまだ実行中であれば、CPUは割り込みの発生を待つ。
  7. 現在のハードウェアレイヤについて、ハードウェアが処理を完了する。S_STATUSレジスタ内のアクティブだったグループの値をIDLEに設定し、D_OP_ENABLEレジスタENABLEに設定する。
  8. ハードウェアはCONSUMERフィールドを次のレジスタグループ(今回の場合は1)に設定する。CONSUMERフィールドを進めると、新しいグループ内のEnableビットの位置が決まる。そして次のハードウェアレイヤが即時に開始される。そうでなければ、Enableビットが設定されるまでハードウェアは待ち状態になる。
  9. 実行が完了したハードウェアに対して"done"割り込みが発生する。CPUがdone割り込みの発生をブロックしているならば、上記の処理が継続される。
  10. 必要に応じて上記が繰り返される。

NVDLAのRegister Description Language

NVDLAには大量のレジスタが定義されており、どのレジスタのどのビットフィールドに何が設定できるのかが良く分からない。 一応レジスタ一覧表はあるが、ざっくりと解説してあるだけで具体的な値については説明されていない。

ここで役に立つのが、Register Description Language(=RDL)という気泡で記述されたレジスタの一覧表だ。 このRDLで記述されたレジスタ定義から、VerilogPythonC言語などのシステムレジスタの実装が生成されている。

Register Definition Languageは統一された規格のようだ。以下に仕様が掲載されている。

  • SystemRDL 2.0 Register Description Language

http://www.eda.org/images/downloads/standards/systemrdl/SystemRDL_2.0_Jan2018.pdf

例えば、NVDLAのCDMAで定義されているレジスタを見てみよう。masterブランチ(NVDLA v2)でRDLが定義されている。

  • spec/manual/NVDLA_CDMA.rdl
...
    reg {
        name = "D_MISC_CFG";
        enum D_MISC_CFG_CONV_MODE_enum {
            DIRECT = 1'd0;
            WINOGRAD = 1'd1;
        };
        field {
            encode          = D_MISC_CFG_CONV_MODE_enum;
            sw              = rw;
            hw              = r;
            spec_access     = rw;
            reset           = 0x0;
            reset_mask      = 0x1;
            spec_sw_default = 0x0;
            sw_default_mask = 0x0;
        } CONV_MODE[0:0];
        enum D_MISC_CFG_IN_PRECISION_enum {
            INT8 = 2'd0;
            INT16 = 2'd1;
            FP16 = 2'd2;
        };
        field {
            encode          = D_MISC_CFG_IN_PRECISION_enum;
            sw              = rw;
            hw              = r;
...

これをコンパイルする。NVDLAのルートディレクトリに移って、makeでtree.makeを作成してからの./tools/bin/tmakespec/manualの中身がコンパイルされる。

$ make  # nv_largeを指定する。
$ ./tools/bin/tmake -build vmod
[TMAKE]: building nv_large in spec/defs
[TMAKE]: building nv_large in spec/manual
[TMAKE]: building nv_large in spec/odif
[TMAKE]: building nv_large in vmod/vlibs
[TMAKE]: building nv_large in vmod/include
...
[TMAKE]: building nv_large in vmod/nvdla/retiming
[TMAKE]: building nv_large in vmod/nvdla/top
[TMAKE]: Done nv_large
[TMAKE]: nv_large: PASS

outdir/spec/manualに移動してみる。NVDLA_CDMA.*に関するファイルだけでも以下が生成されていた。

$ ls -1 NVDLA_CDMA*
NVDLA_CDMA.h
NVDLA_CDMA.py
NVDLA_CDMA.rdl
NVDLA_CDMA_reg.sv
NVDLA_CDMA_reg.v
NVDLA_CDMA.vh
NVDLA_CDMA.xml

NVDLA_CDMA_reg_c:
ordt_pio_common.cpp
ordt_pio_common.hpp
ordt_pio.cpp
ordt_pio.hpp

例えば、NVDLA_CDMA.vhには以下のようなレジスタフィールドが定義されている。 S_STATUS_0レジスタはアドレス0x3000に定義されており、以下の2つのフィールドが定義されていることが分かりますね。 (ちなみに同じようなレジスタが2つ生成されているのが、各パイプラインでグループが指定されており、Foreground / Backgroundの指定ができるものと思われる)

  • STATUS_0[1:0] : IDLE=0, RUNNING=1, PENDING=2
  • STATUS_1[17:16] : IDLE=0, RUNNING=0, PENDING=2

  • NVDLA_CDMA.vh

// Register NVDLA_CDMA_S_STATUS_0
#define NVDLA_CDMA_S_STATUS_0                                   32'h3000
#define NVDLA_CDMA_S_STATUS_0_STATUS_0_RANGE                    1:0
#define NVDLA_CDMA_S_STATUS_0_STATUS_0_SIZE                             2
#define NVDLA_CDMA_S_STATUS_0_STATUS_0_IDLE                     2'h0
#define NVDLA_CDMA_S_STATUS_0_STATUS_0_RUNNING                  2'h1
#define NVDLA_CDMA_S_STATUS_0_STATUS_0_PENDING                  2'h2
#define NVDLA_CDMA_S_STATUS_0_STATUS_1_RANGE                    17:16
#define NVDLA_CDMA_S_STATUS_0_STATUS_1_SIZE                             2
#define NVDLA_CDMA_S_STATUS_0_STATUS_1_IDLE                     2'h0
#define NVDLA_CDMA_S_STATUS_0_STATUS_1_RUNNING                  2'h1
#define NVDLA_CDMA_S_STATUS_0_STATUS_1_PENDING                  2'h2

NVDLAの内部構成調査(2. BDMAの使用方法について)

RISC-VのNVDLAがコラボしたり、NVDLAの調査も再度実施しなければならないと思ってきた。

riscv.org

テストパタンの中で、sanity1 / sanity2 BDMAのテストパタンだ。 BDMAはいわゆるDMAのことなのだが、ディープラーニング向けのDMAよろしく、いろんな設定ができるようになっている。

まずは、sanity2のテストパタンのテストの内容をチェックしていこう。 Feature Dataという入力データを外部から取り込んで、別のメモリ場所に格納するというDMAのテストである。 Feature Dataはsample_surf.datというデータに格納されている。これはreadmemhでcsb_master_seqというモジュール経由であらかじめ外部メモリに格納されているものとする。

load_mem 0x80000000 0x1000 sample_surf.dat

テストパタンの内容としては、0x80000000から格納されているsample_surf.datのデータを0x81000000にDMA転送するというものである。 この時に幾つかパラメータを設定するのだが、その前にFeature Dataの構造について理解しておく必要がある。

パラメータには大きく分けて"LINE"向け、"SURFACE"向けのパラメータが存在している。 マニュアルを読んだ感じだと、

  • LINE : Feature DataのW(Width)×H(Height)に相当するデータ群
  • SURF : Feature DataのC(Channel)に相当するデータ群

であると理解した。これらについて、BDMAでは転送ストライドをそれぞれ設定しながらデータを流すことができるというわけだ。

f:id:msyksphinz:20180830230204p:plain:w400
図. Feature DataとLINE / SURFACEの関係(想像)

転送パラメータについて確認しよう。

  • SURF向けの設定
    • CFG_SRC_SURF_0 : SURFのSource Addressのストライド数。この1SURF(=Channel)は、ここで設定したバイト数の間隔で配置されているものとする。
      • テストパタン : 0x800 (つまりSURFは0x800バイト毎に配置されている)
    • CFG_DST_SURF_0 : SURFのDestination Addressのストライド数。
      • テストパタン : 0x800 (つまりSURFは0x800バイト毎に転送する。)
    • CFG_SURF_REPEAT_0 : SURFを何ブロック分転送するかを示している。ここでは0が設定されているので1回分転送する。
  • LINE向けの設定
    • CFG_SRC_LINE_0 : LINEのSource Addressのストライド数。この1LINE(=W×H)は、ここで設定したバイト数の間隔で配置されているものとする。
      • テストパタン : 0x100 (つまりLINEは0x100バイト毎に配置されている)
    • CFG_DST_LINE_0: LINEのDestination Addressのストライド数。
      • テストパタン : 0x100 (つまりLINEは0x100バイト毎の場所に転送される)
    • CFG_LINE_0 : 1LINEのサイズを指定する。ここでは7が設定されているので8(=7+1)×32-byte(256-bit)が転送される。
    • CFG_LINE_REPEAT_0 : LINEを何ブロック転送するかを示している。ここでは7が設定されているので8(=7+1)回LINEが転送される。

それぞれの関係図を示すと以下のようになる。3次元の入力データに対して、このようにして自由に転送できる領域を変えている。

f:id:msyksphinz:20180830225447p:plain:w1000
図. NVDLAのBDMA機能の各種パラメータと転送データの関係