12. メリット:
各ベンダのデバイスに最適化された計算カーネルは高速
“monolithic kernel approach”と呼ばれる [1][2]
ONNXなど主要なフォーマットの変換もサポートされている
デバッグがしやすい
デメリット:
新しい計算カーネルの開発に時間がかかる
パラメータや入出力の精度のパターンが膨大で特殊化には限界がある[2]
個別の計算カーネルを跨いだ全体最適化が難しい
12
1. Machine Learning Systems are Stuck in a Rut, Paul Barham & Michael Isard, 2019, https://dl.acm.org/citation.cfm?id=3321441
2. VTA: A Hardware-Software Blueprint for Flexible Deep Learning Specialization, Thierry Moreau et al, 2018, https://arxiv.org/abs/1807.04188
グラフコンパイラとベンダのライブラリの組み合わせ
44. Halide FPGAバックエンドによるコードの生成
44
placeholder_0(c, x, y, n) = runtime_argument(c, x, y, n); // runtime_argumentは実行時に渡される
constant_0(c, x, y, n) = buffer(c, x, y, n);
pad_0 = BoundaryConditions::constant_exterior(placeholder_0, 0, {{0, C}, {0, X}, {0, Y}, {0, N}});
RDom rc_0(0, C), rx_0(0, KX), ry_0(0, KY); // convは縮約領域内の重み付き和で表現
conv_0(c, x, y, n) += pad_0(rc_0, x * SX + rx_0 - PX, y * SY + ry_0 - PY, n) * constant_0(rc, rx, ry, c);
add_0(c, x, y, n) += conv_0 + biased_0(n); // バイアスは入力とバイアス値の加算で表現
relu_0(c, x, y, n) = max(add_0(c, x, y, n), 0); // reluは入力と0のmaxで表現
pad_1 = BoundaryConditions::constant_exterior(add_0, 0, {{0, C}, {0, X}, {0, Y}, {0, N}});
RDom rx_1(0, KX), ry_1(0, KY); // maxpoolingは縮約領域内のmaxで表現
maxpool_0(c, x, y, n) = maximum(r, pad_1(c, x * SX + rx_1 - P, y * SY + ry_1 - P, n));
この計算パイプラインをモノリシック計算カーネルで構築すると...
(全ての関数を compute_root でスケジュールすることで再現可能)
45. 45
// conv_0の初期化
for conv_0_n:
for conv_0_y:
for conv_0_x:
for conv_0_c:
conv_0(...) = 0
// conv_0の計算
for conv_0_n:
for conv_0_y:
for conv_0_x:
for conv_0_c:
for ry_0:
for rx_0:
for rc_0:
conv_0(...) = …
// add_0の計算 (バイアス)
for add_0_n:
for add_0_y:
for add_0_x:
for add_0_c:
add_0(...) = ...
// relu_0の計算
for relu_0_3:
for relu_0_2:
for relu_0_1:
for relu_0_0:
relu_0(...) = …
// maxpool_0の計算
for maxpool_0_n:
for maxpool_0_y:
for maxpool_0_x:
for maxpool_0_c:
// maxpoolのリダクション
for ry_1:
for rx_1:
maximum(...) = ...
consume maximum
maxpool_0(...) = ...
モノリシック計算カーネルでのループのイメージ
46. 46
// conv_0の初期化
for conv_0_n:
for conv_0_y:
for conv_0_x:
for conv_0_c:
conv_0(...) = 0
// conv_0の計算
for conv_0_n:
for conv_0_y:
for conv_0_x:
for conv_0_c:
for ry_0:
for rx_0:
for rc_0:
conv_0(...) = …
// add_0の計算 (バイアス)
for add_0_n:
for add_0_y:
for add_0_x:
for add_0_c:
add_0(...) = ...
// relu_0の計算
for relu_0_3:
for relu_0_2:
for relu_0_1:
for relu_0_0:
relu_0(...) = …
// maxpool_0の計算
for maxpool_0_n:
for maxpool_0_y:
for maxpool_0_x:
for maxpool_0_c:
// maxpoolのリダクション
for ry_1:
for rx_1:
maximum(...) = ...
consume maximum
maxpool_0(...) = ...
モノリシック計算カーネルでのループのイメージ
計算カーネル間のバッファにムダが発生
これはFPGAでは致命的!!
47. Halide FPGAバックエンドによるコードの生成
47
placeholder_0(c, x, y, n) = runtime_argument(c, x, y, n); // runtime_argumentは実行時に渡される
constant_0(c, x, y, n) = buffer(c, x, y, n);
pad_0 = BoundaryConditions::constant_exterior(placeholder_0, 0, {{0, C}, {0, X}, {0, Y}, {0, N}});
RDom rc_0(0, C), rx_0(0, KX), ry_0(0, KY); // convは縮約領域内の重み付き和で表現
conv_0(c, x, y, n) += pad_0(rc_0, x * SX + rx_0 - PX, y * SY + ry_0 - PY, n) * constant_0(rc, rx, ry, c);
add_0(c, x, y, n) += conv_0 + biased_0(n); // バイアスは入力とバイアス値の加算で表現
relu_0(c, x, y, n) = max(add_0(c, x, y, n), 0); // reluは入力と0のmaxで表現
pad_1 = BoundaryConditions::constant_exterior(add_0, 0, {{0, C}, {0, X}, {0, Y}, {0, N}});
RDom rx_1(0, KX), ry_1(0, KY); // maxpoolingは縮約領域内のmaxで表現
maxpool_0(c, x, y, n) = maximum(r, pad_1(c, x * SX + rx_1 - P, y * SY + ry_1 - P, n));
ステンシル計算(conv_0, maxpool_0)にcompute_inline,
ポイントワイズ計算(add_0, relu_0) にcompute_root でスケジュール
48. 48
// conv_0の初期化 (Halideだと初期化は別ループになる)
for conv_0_n:
for conv_0_y:
for conv_0_x:
for conv_0_c:
conv_0(...) = 0
// conv_0の計算
for conv_0_n:
for conv_0_y:
for conv_0_x:
for conv_0_c:
for ry_0:
for rx_0:
for rc_0:
conv_0(...) = …
// ポイントワイス演算のループを融合した
add_0(...) = …
relu_0(...) = …
// maxpool_0の計算はconv_0とは別のループになる
for maxpool_0_n:
for maxpool_0_y:
for maxpool_0_x:
for maxpool_0_c:
// maxpoolのリダクション
for ry_1:
for rx_1:
maximum(...) = ...
consume maximum
maxpool_0(...) = ...
FPGA向けに最適化したループのイメージ
49. 49
Halide FPGAバックエンドによるHWの生成
conv_0 add_0
■ Shift Regs ■ Block RAMHalide FPGA Backend
+
relu_0 maxpool_0
+
biased_0
max
0 constant
max
input
port
output
port
compute_inline は各計算カーネルの中間バッファが最小になるようにするた
め、計算に必要な領域のみをレジスタとして確保する。よって、FPGA上のメモ
リ使用量を抑えることができる。
55. 参考
1. Paul Barham and Michael Isard. 2019. Machine Learning Systems are Stuck in a Rut. In Proceedings of the Workshop on Hot Topics in
Operating Systems (HotOS '19). ACM, New York, NY, USA, 177-183. DOI: https://doi.org/10.1145/3317550.3321441
2. Thierry Moreau and Tianqi Chen and Luis Vega and Jared Roesch and Eddie Yan and Lianmin Zheng and Josh Fromm and Ziheng Jiang
and Luis Ceze and Carlos Guestrin and Arvind Krishnamurthy, 2018. A Hardware-Software Blueprint for Flexible Deep Learning
Specialization, https://arxiv.org/abs/1807.04188
3. J.Ragan-Kelley, et al. Halide: A Language and Compiler for Optimizing Parallelism, Locality, and Recomputation in Image Processing
Pipelines. In Proceedings of the 34th, ACM SIGPLAN Conference on Programming Language Design and Implementation, 519–530,
2013.
4. MLIR, 2019, https://github.com/tensorflow/mlir
5. Andrew W. Appel, Modern Compiler Implementation in ML, 1998, Published by Cambridge University Press, ISBN 0-521-60764-7,
https://www.cs.princeton.edu/~appel/modern/ml/
55