FPGA開発日記

FPGAというより、コンピュータアーキテクチャかもね! カテゴリ別記事インデックス <a href="https://msyksphinz.github.io/github_pages/">

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

関連記事