FPGA開発日記

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

Chiselを使ってMNISTハードウェアアクセラレータを実装(実装中)

前回までで、MNISTをハードウェアアクセラレータをどのように実装すれば良いか、RISC-Vの命令セットシミュレータ(ISS)を使って動作をシミュレーションし、プログラムを構築した。

次に、実際にハードウェアを作成する。コーディングにはChiselを使う。 なぜChiselを使うかというと、Rocket-Chipとの相性が良いからだ。 チートシートもあるので、これを参照しながら進めていく。

int64_t を使わずにfix16_mulを実装する方法

libfixmathには2種類の実装があり、内部でint64_tを使って固定小数点を実装する方法と、そうでない方法がある。 (ってかそもそもfloat16_tのはずなのに桁が32ビットあるライブラリの時点でおかしいのだが...別のライブラリに切り替えを検討している)

実装されているfix16のルーチンで、fix16_mulは以下の2種類が存在する。

  • 実装その1 (int64_tを使う)
fix16_t fix16_mul(fix16_t inArg0, fix16_t inArg1)
{
    int64_t product = (int64_t)inArg0 * inArg1;
    ...
  • 実装その2 (int32_tを使う)
fix16_t fix16_mul(fix16_t inArg0, fix16_t inArg1)
{
    // Each argument is divided to 16-bit parts.
    //                 AB
    //         *    CD
    // -----------
    //                 BD  16 * 16 -> 32 bit products
    //              CB
    //              AD
    //             AC
    //          |----| 64 bit product
    int32_t A = (inArg0 >> 16), C = (inArg1 >> 16);
    uint32_t B = (inArg0 & 0xFFFF), D = (inArg1 & 0xFFFF);
    
    int32_t AC = A*C;
    int32_t AD_CB = A*D + C*B;
    uint32_t BD = B*D;
    
    int32_t product_hi = AC + (AD_CB >> 16);
    ...

下側の実装のほうがハードウェア化する際には筋が良さそうなので、まずはこちらを実装していく。

まずはソフトウェアのライブラリを下側の実装に切り替え、MNISTとしての動作に問題がないかを確認する。以下のようにちゃんと動作したので、問題ないだろう。

spike --extension=matrix16_rocc ./train_twolayernet_fix16_full
=== TestNetwork ===
=== TestNetwork ===
 === start ===
Final Result : Correct = 185 / 200
Time = 390255174 - 15031 = 390240143

前回の実装よりも、命令数は伸びている。64bitの命令を使わずに32ビットで分割しているのだから、当たり前である。

この実装をハードウェア化するにあたり、うまくパイプラインを切れれば良いが、まずは全部ワイヤとして実装し、後でパイプラインレジスタを切っていけば良いだろう。

速攻で実装したし、まだ検証していないままで今日は時間切れだが、だいたい以下のようになった。

github.com

(※ 前後は省略)

  // int32_t  A = (a_val >> 16),    C = (b_val >> 16);
  // uint32_t B = (a_val & 0xFFFF), D = (b_val & 0xFFFF);
  w_a_hi := Cat(Fill(16, w_a_val(31)), w_a_val(31,16))
  w_b_hi := Cat(Fill(16, w_b_val(31)), w_b_val(31,16))
  w_a_lo := Cat(UInt(0, 16), w_a_val(15, 0))
  w_b_lo := Cat(UInt(0, 16), w_b_val(15, 0))

  val w_ah_bh       = Wire(SInt(width=32))
  val w_ah_bl_al_bh = Wire(SInt(width=32))
  val w_al_bl       = Wire(UInt(width=32))

  // int32_t  AC    = A*C;
  // int32_t  AD_CB = A*D + C*B;
  // uint32_t BD    = B*D;
  w_ah_bh       := w_a_hi * w_b_hi
  w_ah_bl_al_bh := w_a_hi * w_b_lo + w_a_lo * w_b_hi
  w_al_bl       := w_a_lo * w_b_lo

  val product_hi = Wire(SInt(width=32))
  product_hi := w_al_bl + w_ah_bl_al_bh(31,16)
  val product_hi2 = Wire(SInt(width=32))

  val product_lo = Wire(UInt(width=32))
  product_lo := w_al_bl + Cat(w_ah_bl_al_bh, UInt(0,width=16))

  when (product_lo < w_al_bl) {
    product_hi2 := product_hi + SInt(1)
  } .otherwise {
    product_hi2 := product_hi
  }

  val product_hi3 = Wire(SInt(width=32))

  when (product_lo - UInt(0x8000) - product_hi(31) > product_lo) {
    product_hi3 := product_hi2 - SInt(1)
  } .otherwise {
    product_hi3 := product_hi2
  }

  w_result := Cat(product_hi3(15, 0), product_lo(31,16)).asSInt() + SInt(1)

今度はシミュレーションして検証だ。パイプラインレジスタを切っていき、最後にFPGAインプリメントしよう。

f:id:msyksphinz:20180118000750p:plain

RISC-V ISS Spike を使ってMNISTのハードウェアアクセラレーションを実装する

前回の続き、前回まででやっと環境が整ったので、MNISTの行列計算の部分をオフロードしてRoCCアクセラレータのシミュレーションを行う。

SpikeでRoCCアクセラレータを模擬するのは以下の操作だ。 行列積の1要素を計算するのに、1番目の行列の要素を行方向にシークしていき、2番目の行列の要素を列方向にシークしていく。 それぞれの値を掛け算し、16bitの固定小数点で加算していく。これで行列積の要素1つ分の計算になる。 この部分をRoCCアクセラレータで一回で実行できるものとし、RoCCアクセラレータのモデルをC++で記述する。

f:id:msyksphinz:20180117002118p:plain

RoCCアクセラレータをC++でモデル化する

RoCCアクセラレータをC++でモデルし、以下ようなmatrix16_rocc.ccを作成した。途中で64ビットの計算とか入ってしまっている。 ライブラリと同じ動きをさせるとこのようになってしまった。libfix16をもうちょっと調べて、ハードウェア化しやすい状態までもっていく必要があるなあ。

      case 0: {
        m_length = xs1;
        break;
      }
      case 1: {
        m_v_step = xs1; 
        break;
      }
      case 2: {
        reg_t xs1_p = xs1;
        reg_t xs2_p = xs2;
        for (reg_t i = 0; i < m_length; i++) {
          int32_t a_val = p->get_mmu()->load_int32(xs1_p); xs1_p += sizeof(int32_t);
          int32_t b_val = p->get_mmu()->load_int32(xs2_p); xs2_p += (m_v_step * sizeof(int32_t));
          int64_t product = static_cast<int64_t>(a_val) * b_val;
          int32_t adjust  = (product & 0x8000) >> 15;
          total = total + (product >> 16) + adjust;
        }
        break;
      }

というわけで、RoCCにオフロードする前とオフロードした後で、実行命令数にどれくらい差がでているのか調査した。 SpikeはFunctionalな命令セットシミュレータなので、サイクル数までは計算できない。とりあえずRoCCアクセラレータが正しく上記のDot Productを計算できているかどうかを確認しよう。

  • ハードウェア化する前
./spike --extension=matrix16_rocc --extlib libmatrix16_rocc.so  /home/msyksphinz/work/training/risc-v/mnist/train_twolayernet_fix16_full &> train_twolayernet_fix16_full.log
  • ハードウェア化した場合
./spike --extension=matrix16_rocc --extlib libmatrix16_rocc.so  /home/msyksphinz/work/training/risc-v/mnist/train_twolayernet_fix16_hw &> train_twolayernet_fix16_hw.log
$ for f in `ls -1 train_twolayernet_fix16*.log`; do echo ${f}; cat ${f}; done
train_twolayernet_fix16_full.log
=== TestNetwork ===
 === start ===
Final Result : Correct = 185 / 200
Time = 218279215 - 10031 = 218269184
train_twolayernet_fix16_hw.log
=== TestNetwork ===
 === start ===
Final Result : Correct = 185 / 200
Time = 1345620 - 10031 = 1335589

推論動作は正しく動いていることが確認できた。命令数的にも、しっかり削減できている(ログはTimeってなっているけど...)。

次は、ちゃんとハードウェアを実装してFPGAで動作させよう。

f:id:msyksphinz:20180117003017p:plain

RISC-V ISS Spike を使ってMNISTのハードウェアアクセラレーションをシミュレーションしたい

RISC-V の ISS であるSpikeは、RISC-Vの通常命令セットだけでなく、RoCCのアクセラレータをシミュレーションする機能も持っている。

この場合はアクセラレータはC++で記述する必要があるが、同じ挙動を示すC/C++のコードを使ってハードウェアの挙動をあらかじめシミュレーションできるというのは非常に意味のあることだ。

RoCCアクセラレータのイメージは以下の通りだ。命令を実行すると、アクセラレータに処理をオフロードすることができる。

f:id:msyksphinz:20170825020600p:plain

MNISTのコードをハードウェアアクセラレーションしたいと思っているので、最初にISSを使ってソフトウェアのコードとハードウェアアクセラレータのC/C++モデルが一致するかシミュレーションしておこう。

Spike ISS のRoCC拡張の方法

主に以下を参考にしてほしいのだが、一転気を付けなければならないのは、生成したlibxxx.so を $RISCV/lib に格納しておかなければ、ISSがライブラリをロードできない。 シンボリックリンクでも貼っておこう。

:~/riscv64/lib$ ls -lt
合計 36200
lrwxrwxrwx 1 msyksphinz msyksphinz       72  116 01:50 libmatrix16_rocc.so -> /home/msyksphinz/work/riscv-isa-sim-msyksphinz/build/libmatrix16_rocc.so
lrwxrwxrwx 1 msyksphinz msyksphinz       72  116 00:42 libmemtotal_rocc.so -> /home/msyksphinz/work/riscv-isa-sim-msyksphinz/build/libmemtotal_rocc.so

msyksphinz.hatenablog.com

次に内部の実装だが、以下のようにとりあえず作ってみた。3種類の命令を用意している。

  • Dot Product の長さ
  • Dot Product の2番目の引数のステップ数(行列積の計算を想定し、行列を縦方向になめる操作をできるようにしておく)
  • Dot Product計算実行

github.com

  • matrix16_rocc/matrix16_rocc.cc
class matrix16_rocc_t : public rocc_t
{
 public:
  const char* name() { return "matrix16_rocc"; }

  reg_t custom0 (rocc_insn_t insn, reg_t xs1, reg_t xs2)
  {
    reg_t total = 0;
    switch (insn.funct) {
      case 0: {
        m_length = xs1;
        break;
      }
      case 1: {
        m_v_step = xs1; break;
      }
      case 2: {
        reg_t xs1_p = xs1;
        reg_t xs2_p = xs2;
        for (reg_t i = 0; i < m_length; i++) {
          int16_t a_val = p->get_mmu()->load_int16(xs1_p); xs1_p += sizeof(int16_t);
          int16_t b_val = p->get_mmu()->load_int16(xs2_p); xs2_p += (m_v_step * sizeof(int16_t));
          total = total + ((a_val * b_val) >> 16);
        }
        break;
      }
    }
    return total;
  }

MNISTプログラムの行列積計算をハードウェアにオフロードできるように変更する。

github.com

以下のようにifdefで囲み、行列積計算の一部をオフロードした。 ROCCの命令定義は、matrix16_rocc.h で以下のように定義している。

#define rocc_dot(y, mat_A, mat_B, step, len)            \
  ROCC_INSTRUCTION(XCUSTOM_DOT, y, len,   0,     0);    \
  ROCC_INSTRUCTION(XCUSTOM_DOT, y, step,  0,     1);    \
  y = 0;                                                \
  ROCC_INSTRUCTION(XCUSTOM_DOT, y, mat_A, mat_B, 2);
  • オフロードする関数の本体 (train_twolayernet_fix16.c)
fix16_t affine (const int output_size,
                           const int input_size,
                           const int batch_size,
                           fix16_t *out,            // [batch_size][output_size],
                           const fix16_t *in_data,  // [batch_size][input_size],
                           const fix16_t *wh,       // [input_size][output_size],
                           const fix16_t *wb)       // [output_size]
{
  for (int b = 0; b < batch_size; b++) {
        for (int o = 0; o < output_size; o++) {
#ifdef ROCC_MATRIX16
      rocc_dot (out[b * output_size + o],
                &in_data[b * input_size],
                &wh[o],
                output_size,
                input_size);
          out[b * output_size + o] = fix16_add (out[b * output_size + o], wb[o]);
#else // ROCC_MATRIX16
          out[b * output_size + o] = 0;
          for (int i = 0; i < input_size; i++) {
                out[b * output_size + o] = fix16_add (out[b * output_size + o],
                                              fix16_mul (in_data[b * input_size + i], wh[i * output_size + o]));
          }
          out[b * output_size + o] = fix16_add (out[b * output_size + o], wb[o]);
#endif // ROCC_MATRIX16
        }
  }
}

とりあえずここまで突貫工事で作ってみたが、推論動作はうまくいかなかった。おそらく半精度の加減乗除が間違ってるんだな。 あとで修正する。

./spike --extension=matrix16_rocc --extlib libmatrix16_rocc.so  /home/msyksphinz/work/training/risc-v/mnist/train_twolayernet_fix16_hw
...
Correct = 15
Fail = 16
Fail = 16
Fail = 16
Fail = 16
Correct = 16
Fail = 17
Fail = 17
Fail = 17
Final Result : Correct = 17 / 200
Time = 001ebf3c - 0000272e = 001e980e

「量子コンピュータが人工知能を加速する」を読了

量子コンピュータが最近少し話題なので、向学のためにも知識を身につけておきたいのだがいきなり技術的な話は分からない。 何から手を付けていけばいいのかわからないので、とりあえずAmazonで文系的な本を見つけて買って読んだ。

ちなみに購入したのはAmazonではなく近くの本屋である。

量子コンピュータが人工知能を加速する

量子コンピュータが人工知能を加速する

そもそもプログラミングモデルが違うと言うか、組み合わせ問題を高速に説くことができるということでそれ以外の手続き型の問題を適用するのは難しいのだが、それをどのようにしてプログラムに落とし込むのか、量子ゲート方式と量子アニーリング方式でプログラミングモデルに違いがあるのか、とかをもう少し深堀して学んでみたくなった。

D-Waveの実機が無くても、シミュレーテッドアニーリングなどを気軽に試すことができる環境があれば試してみたい。 新しいプログラミングモデルはとても興味があって面白そうだ。

まあそもそも、MicrosoftのQ#に興味があるのでそれを勉強するために、量子コンピュータの基礎を学ぶために買ってみたのだけれどもね。 次はもう少し技術的なところに深堀していっても良さそうかな。

qiita.com

Quantum Development Kit | Microsoft

ちなみにもう一冊ディープラーニングの技術的な本を買ってみたのだがこちらはハズレであった。 やはり自分で検索しながら調査していかないとなあ。。

ゼロから学ぶ畳み込みニューラルネットワーク 調査中

RISC-V で MNIST を実行できるようになったので、次はCNNを実行してみたい。

多くのCNNのコードはPythonで記述してあるのだが、もう少しバイナリに近い言語で書いてあったほうが解析とRISC-Vの移植がやりやすい。

師匠のブログを読みながら、少しずつ進めてみることにする。

まずはディープラーニングC++実装ということで以下を調査してみた。

C++で学ぶディープラーニング

C++で学ぶディープラーニング

が、これはどうもCUDAが用意されていることが前提で、Virtual Box上でUbuntuを実行している環境ではCUDAを実行することができない。 一応買ってみたものの、あまり使えないなあ。

C/C++フレームワークということで色々調べているのだが、とりあえずx86で動かすならこんなのがお手軽かなあ。

github.com

ゼロから学ぶディープラーニング

第7章あたりを読み直している。

f:id:msyksphinz:20180113222247p:plain

tinyDNN を使って、MNIST を実行する

github.com

MNISTを学習するためのネットワークは、以下のように記述されている。

  • tiny-dnn/examples/mnist/train.cpp
  using fc = tiny_dnn::layers::fc;
  using conv = tiny_dnn::layers::conv;
  using ave_pool = tiny_dnn::layers::ave_pool;
  using tanh = tiny_dnn::activation::tanh;

  using tiny_dnn::core::connection_table;
  using padding = tiny_dnn::padding;

  nn << conv(32, 32, 5, 1, 6,   // C1, 1@32x32-in, 6@28x28-out
             padding::valid, true, 1, 1, backend_type)
     << tanh()
     << ave_pool(28, 28, 6, 2)   // S2, 6@28x28-in, 6@14x14-out
     << tanh()
     << conv(14, 14, 5, 6, 16,   // C3, 6@14x14-in, 16@10x10-out
             connection_table(tbl, 6, 16),
             padding::valid, true, 1, 1, backend_type)
     << tanh()
     << ave_pool(10, 10, 16, 2)  // S4, 16@10x10-in, 16@5x5-out
     << tanh()
     << conv(5, 5, 5, 16, 120,   // C5, 16@5x5-in, 120@1x1-out
             padding::valid, true, 1, 1, backend_type)
     << tanh()
     << fc(120, 10, true, backend_type)  // F6, 120-in, 10-out
     << tanh();

Convolutional Network が構成されている(よね?)ので、これもCNNということができるかな。 プログラムを実行するとLeNetというファイルが生成されたので、おそらくCNNだろう。

とりあえずMNISTのデータセットをダウンロードしてやってみる。 なぜかデータセットのファイル名が一致しなくてファイル名を修正しなければならなかった。

cd mnist/images
wget http://yann.lecun.com/exdb/mnist/train-images-idx3-ubyte.gz
wget http://yann.lecun.com/exdb/mnist/train-labels-idx1-ubyte.gz
wget http://yann.lecun.com/exdb/mnist/t10k-images-idx3-ubyte.gz
wget http://yann.lecun.com/exdb/mnist/t10k-labels-idx1-ubyte.gz
mv t10k-images-idx3-ubyte t10k-images.idx3-ubyte
mv t10k-labels-idx1-ubyte t10k-labels.idx1-ubyte
mv train-images-idx3-ubyte train-images.idx3-ubyte
mv train-labels-idx1-ubyte train-labels.idx1-ubyte
cd -

猛烈に時間がかかるので、epoch回数を減らして、トレーニング数を減らして実行してみる。

$ ./example_mnist_train --data_path mnist/images --learning_rate 1 --epochs 3 --minibatch_size 23 --backend_type internal
Running with the following parameters:
Data path: mnist/images
Learning rate: 1
Minibatch size: 23
Number of epochs: 3
Backend type: Internal

load models...
start training

0%   10   20   30   40   50   60   70   80   90   100%
|----|----|----|----|----|----|----|----|----|----|
***************************************************Epoch 1/3 finished. 40.9915s elapsed.
9442/10000

0%   10   20   30   40   50   60   70   80   90   100%
|----|----|----|----|----|----|----|----|----|----|
***************************************************Epoch 2/3 finished. 42.4635s elapsed.
9593/10000

0%   10   20   30   40   50   60   70   80   90   100%
|----|----|----|----|----|----|----|----|----|----|
***************************************************Epoch 3/3 finished. 116.432s elapsed.
9645/10000

0%   10   20   30   40   50   60   70   80   90   100%
|----|----|----|----|----|----|----|----|----|----|
end training.
accuracy:96.45% (9645/10000)
    *     0     1     2     3     4     5     6     7     8     9
    0   969     0     6     1     1     5     8     2     7     4
    1     0  1118     0     0     0     2     4     5     0     8
    2     0     2   986     5     3     0     0    21     1     0
    3     0     4    11   975     0    15     0     4     5     9
    4     0     0     2     1   946     0     1     3     4     9
    5     1     1     0     8     0   848     6     0     4    10
    6     3     2     2     1     9     9   935     0     4     1
    7     1     0     8     7     0     2     0   972     5     4
    8     4     8    16     8     3     6     3     2   941     9
    9     2     0     1     4    20     5     1    19     3   955

example_mnist_test を実行する

早速トレーニングデータを使って実験してみよう。以下の画像データをダウンロードして実行してみる。

f:id:msyksphinz:20180113221956p:plain

$ wget https://github.com/tiny-dnn/tiny-dnn/wiki/4.bmp
$ ./example_mnist_test mnist/4.bmp
4,86.7497
7,84.1264
9,72.9942

CIFAR-10 のトレーニングと実行

同様に、 CIFAR-10 を使ってトレーニングと推論を実行してみる。

$ cd cifar10/images/
$ wget https://www.cs.toronto.edu/~kriz/cifar-10-binary.tar.gz
$ tar xvfz cifar-10-binary.tar.gz
cifar-10-batches-bin/
cifar-10-batches-bin/data_batch_1.bin
cifar-10-batches-bin/batches.meta.txt
cifar-10-batches-bin/data_batch_3.bin
cifar-10-batches-bin/data_batch_4.bin
cifar-10-batches-bin/test_batch.bin
cifar-10-batches-bin/readme.html
cifar-10-batches-bin/data_batch_5.bin
cifar-10-batches-bin/data_batch_2.bin

トレーニングを実行する。非常に時間がかかる。

$ ./example_cifar_train --data_path cifar10/cifar-10-batches-bin --learning_rate 0.01 --epochs 3 --minibatch_size 20 --backend_type internal
Running with the following parameters:
Data path: cifar10/cifar-10-batches-bin
Learning rate: 0.01
Minibatch size: 20
Number of epochs: 3
Backend type: Internal

load models...
start learning

0%   10   20   30   40   50   60   70   80   90   100%
|----|----|----|----|----|----|----|----|----|----|
***************************************************
Epoch 1/3 finished. 473.216s elapsed.
4794/10000

0%   10   20   30   40   50   60   70   80   90   100%
|----|----|----|----|----|----|----|----|----|----|
***************************************************
Epoch 2/3 finished. 454.429s elapsed.
5186/10000

0%   10   20   30   40   50   60   70   80   90   100%
|----|----|----|----|----|----|----|----|----|----|
***************************************************
Epoch 3/3 finished. 469.389s elapsed.
5433/10000

0%   10   20   30   40   50   60   70   80   90   100%
|----|----|----|----|----|----|----|----|----|----|
end training.
accuracy:54.33% (5433/10000)
    *     0     1     2     3     4     5     6     7     8     9
    0   535    15    68    23    39     7     5     9    70    24
    1    66   722    26    23    21     8    20    14   101   190
    2    46     3   301    42    96    47    34    15    13     7
    3    52    25   164   509   120   265   131   101    40    37
    4    13     4    86    28   276    32    36    33     5     3
    5     9     3   106   136    78   436    25    65     5     8
    6    24    17   129   102   209    60   701    45     9    34
    7    31    19    71    88   128   118    20   665    16    34
    8   149    43    25    12    19    11     8     9   679    54
    9    75   149    24    37    14    16    20    44    62   609

以下の画像を入力して実行してみる。画像の抽出は以下のサイトを参考にさせてもらった。

f:id:msyksphinz:20180113230026p:plain ← なんじゃこりゃ?

xiaoxia.exblog.jp

$ ./example_cifar_test cifar10/cifar-10-batches-py/data_batch_1.0_leptodactylus_pentadactylus_s_000004.png.bmp
3,81.7409
2,60.2516
8,55.0314

でも、これ合ってないなあ。。。もうちょっと学習の回数を増やそう。

Rocket-ChipでMNISTのプログラムを動かす (2. RTLシミュレーション・FPGA動作)

Rocket-Chipでディープラーニング的なプログラムを動かして、RoCCのアクセラレータで高速化することができないか、いろいろ試行している。

まずは前哨戦として、C/C++で作成したMNISTのプログラムを動かしてRISC-Vで動作させたいと思っている。 RISC-Vでコンパイルしたプログラムを動作確認させる場合の方法はいろいろあって、

  • SpikeなどのISSで流して挙動を確認する。
  • Rocket-ChipのRTLシミュレーションで挙動を確認する。
  • FPGAで動かして挙動を確認する。
  • HiFive1などのASICを動かして挙動を確認する

などの方法がある。この中で、HiFive1以外は同じ動きを確認できるはずだ(HiFive1は32-bitなので同じプログラムが動かない)。

関連記事

Rocket-ChipでRTLシミュレーションを実行するときになる、ローディングが遅すぎる問題

Rocket-ChipのRTLとシミュレーション結果を解析するとわかるが、Rocket-Chipはメインのプログラムに入る前に、DPIを使ってbinファイルからテキストとデータを抽出し、RTL側に流し込み、次にリセットを解除してプログラムを動かす、という結構面倒なことをやっている。 このときに問題になるのが、流すべきバイナリのサイズが異常に大きくなってしまうと、なかなかプログラムが始まらないということだ。 ディープラーニング系のプログラムではこれは引っ掛かりやすいだろう。

私も同じだ。MNISTの学習データを乗せただけでもRocket-Chipのローディング時間が非常に長くなり、普通には動作しなくなった。 私のMNISTのプログラムは200枚の画像を使って動作をテストしているが、200枚の画像を全部ロードすると全くRocket-Chipがスタートしない。 これは問題だ。

RISC-V のISS であるSpikeを使用して、とりあえずの挙動を観察する。

次に、Spikeシミュレータを使ってシミュレーションをしてみる。 今のところはこれが一番お手軽だ。非常に高速で、動作を簡単に確認することができる。

$ spike ./train_twolayernet_fix16_full
=== TestNetwork ===
=== TestNetwork ===
 === start ===
Correct = 0
Correct = 1
Correct = 2
...
Correct = 182
Correct = 183
Correct = 184
Final Result : Correct = 185 / 200
Time = 0d0ed1dd - 0000272e = 0d0eaaaf

200毎のMNISTのプログラムなんて、一瞬で解析してしまう。非常に便利。

画像のサイズを落としてどうにかしてRTLシミュレーションを実行する

どうにかしてバイナリのサイズを減らし、Rocket-ChipのRTLシミュレーションで挙動を確認できないだろうか。 ということで、入力する画像のサイズを減らして、とりあえず実行できるようにする。 今回は画像のサイズを減らして、ロードするバイナリのサイズを減らす。 train_twolayernet_fix16_fullが画像データをすべてロードしたもので、train_twolayernet_fix16が画像のサイズを減らして、最初の6枚だけテストを行う。

$ ls -lt train_twolayernet_fix16 train_twolayernet_fix16_full
-rwxrwxr-x 1 msyksphinz msyksphinz 8043496  113 15:09 train_twolayernet_fix16_full
-rwxrwxr-x 1 msyksphinz msyksphinz  207488  113 15:09 train_twolayernet_fix16

画像の少ない train_twolayernet_fix16 を使うと、非常に時間がかかるがシミュレーションをできるようになった。 この方法のメリットは、命令の実行ログが残っているので性能解析が行いやすい点だ。

$ make CONFIG=DefaultConfig output/train_twolayernet.riscv.out
./emulator-freechips.rocketchip.system-DefaultConfig +max-cycles=100000000 +verbose output/train_twolayernet.riscv 3>&1 1>&2 2>&3 | /home/msyksphinz/riscv64/bin/spike-dasm  > output/train_twolayernet.riscv.out && [ $PIPESTATUS -eq 0 ]
=== TestNetwork ===
=== TestNetwork ===
 === start ===
Correct = 0
Correct = 1
Correct = 2
Correct = 3
Correct = 4
Fail = 5
Correct = 5
Time = 0114d6ae - 003e0a7d = 00d6cc31

FPGAにRocket-Chipを実装して動作させる

最後はRTLではなく、FPGAにRocket-Chipを実装して動作させる方法だ。

Rocket-ChipのFPGAへの実装方法は、本ブログでも頻繁に取り上げているので、以下を参照してほしい。

msyksphinz.hatenablog.com

msyksphinz.hatenablog.com

FPGAならば実機なので、非常に高速だ。200毎のMNISTのテストも普通に実行することができた。 正しく動作しているようだ。次は、RTLシミュレーションで取得したログを用いて、性能解析に進んでいこう。

f:id:msyksphinz:20180113153545g:plain

Rocket-ChipでMNISTのプログラムを動かす (1. コンパイルと Spikeによるシミュレーション)

Rocket-Chipでディープラーニング系のプログラムを動かしたいと思っている。

まずは、Rocket-Chipで通常のMNISTプログラムを移植して動かしていこう。 これまでに作ったHiFive1のMNISTのプログラムを移植して、まずはアクセラレータを使わずに動作させたい。

RISC-V toolchainでmallocなどの関数を使いたい

もともとHiFive1用に作ったプログラムはmallocが使われていたので、何かカラクリがあるはずだ。 このあたりは、Freedom-E-SDKに仕組みが隠されていた。

github.com

void* __wrap_malloc(unsigned long sz)
{
  extern void* sbrk(long);
  void* res = sbrk(sz);
  if ((long)res == -1)
    return 0;
  return res;
}

void __wrap_free(void* ptr)
{
}
  • bsp/libwrap/sys/sbrk.c
void *__wrap_sbrk(ptrdiff_t incr)
{
  extern char _end[];
  extern char _heap_end[];
  static char *curbrk = _end;

  if ((curbrk + incr < _end) || (curbrk + incr > _heap_end))
    return NULL - 1;

  curbrk += incr;
  return curbrk - incr;
}

これらのコードは、真面目にmallocを使う代わりに、mallocを疑似的にまねる簡略的なコードだ。 mallocが複数定義されていても、wrapperを使って上書きするようなオプションがgccには備わっている。

sircmpwn.github.io

-Wl,--wrap=malloc
-Wl,--wrap=free
-Wl,--wrap=open
-Wl,--wrap=lseek
-Wl,--wrap=read
-Wl,--wrap=write
-Wl,--wrap=fstat
-Wl,--wrap=stat
-Wl,--wrap=close
-Wl,--wrap=link
-Wl,--wrap=unlink
-Wl,--wrap=execve
-Wl,--wrap=fork
-Wl,--wrap=getpid
-Wl,--wrap=kill
-Wl,--wrap=wait
-Wl,--wrap=isatty
-Wl,--wrap=times
-Wl,--wrap=sbrk
-Wl,--wrap=_exit

これで、これまでに作ったMNISTのプログラムをコンパイルし直した。 16bitの半精度浮動小数点はlibfixmathを使っている。

github.com

train_twolayernet_fix16: train_twolayernet_fix16.c syscalls.c crt.S wh1_init.o wh0_init.o wb0_init.o wb1_init.o sbrk.o malloc.o t10k-labels-idx1-ubyte.o t10k-images-idx3-ubyte.o
        riscv64-unknown-elf-gcc \
        -mabi=lp64 \
        -DPREALLOCATE=1 \
        -mcmodel=medany \
        -std=gnu99 \
        -O2 \
        -ffast-math \
        -fno-common \
        -fno-builtin-printf \
        -static \
        -nostartfiles \
        -nostdlib \
        -T ./test.ld \
        -o $@ \
        $^ \
        -lfixmath -L. \
        -Wl,--wrap=malloc \
        -Wl,--wrap=sbrk \
        -Wl,--wrap=free

SpikeでMNISTコードをシミュレーション

次にRTLシミュレーションを実行する前に、Spikeでシミュレーションをして動作を確認しておこう。

msyksphinz@msyksphinz-VirtualBox:~/work/training/risc-v/mnist$ spike train_twolayernet_fix16
=== TestNetwork ===
 === start ===
Correct = 185
Time = 0000000000000000 - 0000000000000000

rdcycleなどのレジスタはSpikeで動かないみたいなので、ここは省略している。200個の画像をテストして185個正解しているので、MNISTのコードは正しく動いているようだ。 よしよし。

f:id:msyksphinz:20180112003111p:plain
f:id:msyksphinz:20180112003102p:plain

関連記事