Contenu connexe Similaire à 「FPGA 開発入門:FPGA を用いたエッジ AI の高速化手法を学ぶ」 (20) 「FPGA 開発入門:FPGA を用いたエッジ AI の高速化手法を学ぶ」3. 自己紹介
森高 晃大
エンジニア
✔ 主に FPGA (HLS, Verilog-HDL)、C/C++
略歴
✔ 2013 年 4 月、某電機メーカ入社
サーバ、最先端スパコン向け CPU のメモリコントローラ開発
✔ 2018 年 4 月、フィックスターズ入社
FPGA を使った HFT, 画像処理、ディープラーニングなどの
プロジェクトに参加、開発の中心を担う。
3
4. ウェビナーの目的
Naive に実装されたある CNN の高速化を
通じて、HLS における高速化の原理・手法を習
得する。
✔ 手法自体は基本的なもの
目的達成に向けて
✔ その手法の手軽さと効果、またそのインパクトの大きさを確認
✔ HLS といえどもハードウェア (アーキテクチャ) 設計は必要であることを確
認
4※Xilinx Vivado/Vitis HLS を前提
5. HLS における高速化手法を学ぶ 3 つの理由
1. FPGA 活躍の場面が増加しており、効率的な設計を狙った HLS
の利用ケースが増加
✔ 低消費電力や低レイテンシなどを狙って、ディープラーニングの推論に
FPGA を利用することが注目されている。
2. HLS で性能を出すための手段は今後も重要
3. 自前で作らなくても高速化の原理を知ることは大切
✔ Vitis Libraries でもこのウェビナーで紹介する手法は頻出
5
6. 本ウェビナーの題材
畳み込みニューラルネットワーク (CNN) の 1 つである LeNet を簡略化し
たネットワークモデルで MNIST の手書き文字認識
HLS で FPGA 上に実装し、高速化していく。
✔ 講演内容は ACRi ブログ「FPGA で始めるエッジディープラーニング」(7)~(10) が
ベース
ソースコードは https://github.com/fixstars/dnn-kernel-fpga で公開
6
Netron で可視化したモデル。モデルのソースコードは
https://github.com/fixstars/dnn-kernel-fpga/blob/master/learning/train_mnist.py#L9-L60
8. 畳み込み層 (CONV) と全結合層 (FC)
全結合層
ベクトルと行列の掛け算
畳み込みの出力 (画像の特徴) をラベル
データに変換する場合などに使用
8
畳み込み層
「入力ピクセルとその周辺
ピクセル (X)」に
「カーネル (W)」を掛けて総和を
「出力ピクセル (Y)」とする層
13. プラグマ 3 つを追加するだけ*
で並列化可能に
1. #pragma HLS dataflow
✔ タスク間を FIFO もしくは Ping-pong バッファで接続
■ ハードウェアリソース的なオーバヘッドあり
✔ 各タスクは、自身のペースで処理を実行可能
■ 前後のタスクからは独立
2. #pragma HLS stable
✔ 詳細はブログにて
3. #pragma HLS interface ap_ctrl_chain
✔ 詳細はブログにて
13
* dataflow 適用にはいくつか制限がある。詳細は
詳細は UG902 Exploiting Task Level Parallelism: Dataflow Optimization を参照
14. なぜ Ping-pong バッファ ?
14
Ping-pong バッファ なし
Ping-pong バッファ あり
conv1 は conv2 が 1 つ前のフレームを処
理し切るまで待たなければならない。前フ
レームのデータを壊しかねないため。
conv1 は conv2 が 1 つ前のフレームの処
理を待たなくてよい。conv1 の書き込み
バッファは、conv2 の読み込みバッファと別
であり、前フレームのデータを壊すことはな
いため。
conv1 conv2
…
conv1 conv2
…
Ping-pong buffer
推論回路一部抜粋
16. 高位合成の結果を確認 (続き)
16
t
conv1 conv2 fc1 fc2
conv1 conv2 fc1 fc2
conv1 conv2 fc1 fc2
t
conv1 conv2 fc1 fc2
conv1 conv2 fc1 fc2
conv1 conv2 fc1 fc2
947405 cycles 947405 cycles
947407 cycles
504897
cycles
504897
cycless
タスク並列化前
タスク並列化後
22. 全タスクの処理速度を (できれば) 揃える
22FPGA で始めるエッジディープラーニング(5) https://www.acri.c.titech.ac.jp/wordpress/archives/6757
conv1, conv2 の処理時間を削減できれば
さらに効率的に処理可能
ループ並列化・データ並列化を利用した conv1, conv2 の処理時間短縮
conv2 の実行時間が他と比べて長く conv2 で律速
24. タスク並列とループ並列の違いは並列化の粒度
24
t
conv1 conv2 fc1 fc2
並列化前
conv1 conv2 fc1 fc2
conv1 conv2 fc1 fc2
タスク並列化、並列化対象はタスク
t
conv1 conv2 fc1 fc2
conv1 conv2 fc1 fc2
conv1 conv2 fc1 fc2
ループ並列化、並列化対象はタスク内のループ
t
cv1 cv2 fc1 fc2
cv1 cv2 fc1 fc2
cv1 cv2 fc1 fc2
ループを並列化することで
タスク単体のスループットを向上1
タスクを並列化することで
処理全体のスループットを向上
*1: 後述のデータ並列もタスク単体のスループット向上が目的
25. プラグマ 1 つを追加するだけ*
で並列化可能に
#pragma HLS pipeline II=1
もしくは単に #pragma HLS pipeline だけでも OK
25* ただし、Vivado が並列性を抽出可能なコードになっていることが前提
17 for (int32_t ich = 0; ich < in_channels; ++ich) {
18 for (int32_t kh = 0; kh < ksize; ++kh) {
19 for (int32_t kw = 0; kw < ksize; ++kw) {
...
30 #pragma HLS pipeline II=1
31 sum += x[pix_idx] * weight[weight_idx];
32 }
33 }
34 }
この 1 行の追加だけで
ループ並列化が可能、
II=1 が達成できる…!?
26. II=1 未達の原因は?
II=4 で合成
✔ fadd の レイテンシ 4 サイクルに律速される。
✔ sum にループ間依存関係 (Read After Write) が発生
■ sum += x[pix_idx] * weight[weight_idx];
26
fadd@{i+1} は fadd@{i} の結果を使いたい。
add@{i} は 8 サイクル目までかかる。
よって、fadd@{i+1} は 9 サイクル目まで開始できない。
DEPENDENCE !
28. シフトレジスタを用いて II=1 を達成
28
0
x
w 1 2 3
(総和)
シフトレジスタ
fadd のレイテンシ 4 に合わせて
4 段のシフトレジスタを上図のように追加
✔ 任意の連続する 4 イタレーションで加算結果の依存関係を解消
最後にシフトレジスタの結果 (部分和) を足し合わせて
総和を計算
33. 全タスクの処理速度を (できれば) 揃える (再)
33FPGA で始めるエッジディープラーニング(5) https://www.acri.c.titech.ac.jp/wordpress/archives/6757
conv1, conv2 の処理時間を削減できれば
さらに効率的に処理可能
ループ並列化・データ並列化を利用した conv1, conv2 の処理時間短縮
conv2 の実行時間が他と比べて長く conv2 で律速
37. 処理ピクセル間の並列性
37
入力 x カーネル w 出力 y
for (i=0; i<N; ++i) {
y[i] = f(x[i], w);
}
for (i=0; i<N/2; i+=2) {
y[i] = f(x[i], w);
y[i+1] = f(x[i+1], w);
}
並列化
並列化前 並列化後
互いに依存しない入力ピクセルの計算を同サイクルに実行
畳み込み計算のカーネル (中央黄) は共通
38. 出力チャネル間の並列性
38
入力 x カーネル w 出力 y
for (k=0; k<M; ++k) {
y[k][i] = f(x[i], w[k]);
}
並列化
並列化前 並列化後
互いに依存しない出力チャネルの計算を同サイクルに実行
入力ピクセル (左橙) は共通
for (k=0; k<M/2; k+=2) {
y[k][i] = f(x[i], w[k]);
y[k+1][i] = f(x[i], w[k+1]);
}
39. 両並列性を組み合わせる
39
入力 x カーネル w 出力 y
for (k=0; k<M; k++) {
for (i=0; i<N; ++i) {
y[k][i] = f(x[i], w[k]);
}
} 並列化
並列化前
並列化後
for (k=0; k<M/2; k+=2) {
for (i=0; i<N/2; i+=2) {
y[k][i] = f(x[i], w[k]);
y[k][i+1] = f(x[i+1], w[k]);
y[k+1][i] = f(x[i], w[k+1]);
y[k+1][i+1] = f(x[i+1], w[k+1]);
}
}
2 入力ピクセル
x
2 カーネル
で同時に 4 出力
40. 実装する HW のイメージ
2 ピクセル x 2 カーネルで 4 出力
演算器 (PE) が 2 x 2 の格子状に
配置されている。
メモリアクセスは
✔ ピクセル 2 回読み出し
✔ カーネル 2 回読み出し
40
41. メモリのポート数と DSP 数
1 つの軸のみをデータ並列化
✔ 並列度 n に対し演算器が n 個
✔ FPGA 内のリソース数が
総 DSP 数 > BRAM ポート数であるため
DSP を十分に使い切れない。
2 つの軸をデータ並列化
✔ (ピクセル側の並列度を n, 出力チャネル側の並列度を m)
✔ 総メモリアクセスポート数 n + m に対して
演算器は n * m 個
✔ 十分な DSP を使用可能
41
42. d e f
0
9 a b
c
5 6 7
8
1 2 3
4
d
e
f
0
9
a
b
c
5
6
7
8
1
2
3
4
演算器アレイ動作イメージ
42
d e f
0
9 a b
c
5 6 7
8
1 2 3
4
d e f
0
9 a b
c
5 6 7
8
1 2 3
4
d e f
0
9 a b
c
5 6 7
8
1 2 3
4
d
e
f
0
9
a
b
c
5
6
7
8
1
2
3
4
0 1 0 1 0 1 0 1
0
1
0
1
理論上、2 port
RAM 2x4 個で 32
個の DSP を埋め
られる
43. pragma (とループの書き換え) で実現
#pragma HLS unroll
#pragma HLS array_partition
各プラグマについてはブログにて
並列化したい方向によってはループの書き換えが必要
✔ 今回の畳み込み層では
全 6 重ループ (出力チャネル、y 座標、x 座標、入力チャネル、カーネ
ル y 方向、カーネル x 方向) のうち
出力チャネルと x 軸方向のデータ並列性を抽出、
その 2 ループの回転数を並列化分考慮する必要がある。
43
53. 全体のまとめ
Naive な実装の CNN を対象に 3 つの並列性を抽出し高速化
✔ タスク並列性:dataflow, stable, ap_ctrl_chain
✔ ループ並列性:pipeline
✔ データ並列性:unroll, array_partition
プラグマを挿入するだけである程度の高速化が可能
さらなる高速化を目指すには以下が必要
✔ ハードウェア実装を考慮したコーディング
✔ Vivado/Vitis HLS への優しさ
53