RISC-V の ISS であるSpikeは、RISC-Vの通常命令セットだけでなく、RoCCのアクセラレータをシミュレーションする機能も持っている。
この場合はアクセラレータはC++で記述する必要があるが、同じ挙動を示すC/C++のコードを使ってハードウェアの挙動をあらかじめシミュレーションできるというのは非常に意味のあることだ。
RoCCアクセラレータのイメージは以下の通りだ。命令を実行すると、アクセラレータに処理をオフロードすることができる。
MNISTのコードをハードウェアアクセラレーションしたいと思っているので、最初にISSを使ってソフトウェアのコードとハードウェアアクセラレータのC/C++モデルが一致するかシミュレーションしておこう。
Spike ISS のRoCC拡張の方法
主に以下を参考にしてほしいのだが、一転気を付けなければならないのは、生成したlibxxx.so を $RISCV/lib
に格納しておかなければ、ISSがライブラリをロードできない。
シンボリックリンクでも貼っておこう。
:~/riscv64/lib$ ls -lt 合計 36200 lrwxrwxrwx 1 msyksphinz msyksphinz 72 1月 16 01:50 libmatrix16_rocc.so -> /home/msyksphinz/work/riscv-isa-sim-msyksphinz/build/libmatrix16_rocc.so lrwxrwxrwx 1 msyksphinz msyksphinz 72 1月 16 00:42 libmemtotal_rocc.so -> /home/msyksphinz/work/riscv-isa-sim-msyksphinz/build/libmemtotal_rocc.so
次に内部の実装だが、以下のようにとりあえず作ってみた。3種類の命令を用意している。
- Dot Product の長さ
- Dot Product の2番目の引数のステップ数(行列積の計算を想定し、行列を縦方向になめる操作をできるようにしておく)
- Dot Product計算実行
- 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プログラムの行列積計算をハードウェアにオフロードできるように変更する。
以下のように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