Vivado-HLSで行列積演算回路を作成したが、この性能はあまり良いものでは無かった。
その後いろいろ調査を行った結果、以下の資料を発見した。 以下の資料は、その名の通り浮動小数点行列積をVivado-HLSで実装したときの最適化ガイドだ。これを見れば、何らかの有益な情報が掴めるかもしれない。
http://japan.xilinx.com/support/documentation/application_notes/j_xapp1170-zynq-hls.pdf
最適化ガイドのソースコードには、パイプライン挿入のディレクティブに加え、見慣れないディレクティブが挿入されていた。これは何だろう?
set_directive_array_partition -type block -factor 16 -dim 2 "mmult_hw" A set_directive_array_partition -type block -factor 16 -dim 1 "mmult_hw" B set_directive_pipeline -II 1 "matrix_mul/L2"
調査してみると、このディレクティブは該当するポートに対して並列にアクセスすることを許可するらしい。なるほど、これで入出力ポートの並列化を行っていくわけか。
- Vivado Design Suiteユーザー ガイド 高位合成
配列をより小さな配列または個々のエレメントに分割します。結果として RTLには、1つの大きなメモリではなく、複数の小型メモリまたは複数のレジスタがインプリメントされます。ストレージの読み出しおよび書き込みポートの数が増加し、デザインのスループットが改善される可能性がありますが、より多くのメモリインスタンスやレジスタが必要となります。
なるほど。また、-dim
は分割対象の次元を指定するものらしい。上記の例だと、1次元目と2次元目の両方を分解、Bは1次元目を分解するという訳か?
行列積は、赤矢印の方向に演算を進めていくのだが、これを上記の通りに分解すると、
上記は、それぞれ接続されている要素がまとまっており、離れている要素は同時にアクセスできる構成になっているとする。そうすると、2番目の行列の演算が全て並列に行なわれるということなのか。
複数種類の構成を作って性能を検証する
そこで、以下の行列演算プログラムに対していくつかの最適化を実施し、どのようになるのかを実験した。
void matrix_mul (float matrix_a[MATRIX_LENGTH][MATRIX_LENGTH], float matrix_b[MATRIX_LENGTH][MATRIX_LENGTH], float matrix_c[MATRIX_LENGTH][MATRIX_LENGTH]) { L2:for (int j = 0; j < MATRIX_LENGTH; j++) { L1:for (int i = 0; i < MATRIX_LENGTH; i++) { matrix_c[j][i] = 0; L0:for (int k = 0; k < MATRIX_LENGTH; k++) { matrix_c[j][i] += matrix_a[j][k] * matrix_b[k][i]; } } } return; }
最適化無し (matrix_hls_normal)
最内ループの最適化 + 並列アクセス挿入 (matrix_hls_L0)
set_directive_pipeline -II 1 "matrix_mul/L0"
- 2番目のループの最適化 + 並列アクセス挿入 (matrix_hls_L1)
set_directive_pipeline -II 1 "matrix_mul/L1"
- 3番目のループの最適化 + 並列アクセス挿入 (matrix_hls_L2)
set_directive_pipeline -II 1 "matrix_mul/L1"
それぞれの速度および面積比較を行うと、以下のようになった。
normal | pipeline_L0 | pipeline_L1 | pipeline_L2 | |
---|---|---|---|---|
Clock Latency | 8.41 | 8.09 | 8.09 | 9.80 |
Speed | ||||
Exec Latency | 1110625 | 573697 | 7156 | 7218 |
Area | ||||
BRAM_18K | 0 | 24 | 72 | 72 |
DSP48E | 5 | 5 | 80 | 80 |
FF | 488 | 821 | 11467 | 126153 |
LUT | 846 | 1215 | 17882 | 59039 |
おお、昨日の結果よりもずいぶんと高速化されたな。
しかし、L2までパイプライン化すれば良いというものでは無さそうだ。性能は出るが、パイプライン化され過ぎていて、面積がかなり大きくなってしまっている。ここは、pipeline_L1のデザインが最適と言えるだろう。
VivadoにIPとしてインポートし、Zynq ZedBoardで動作させる
早速pipeline_L1デザインをVivadoにインポートし、プロジェクトを作って合成した。
新しい行列演算回路を挿入した図。まああまり見た目は変わっていないけど。
早速昨日と同じプログラムを実行して、サイクル数を測定した。
サイクル数 | |
---|---|
Vivado-HLSで生成した回路 | 23059 |
ARM Cortex-A9で実行したプログラム | 179650 |
おお、随分と速くなった!