マルチレイヤコンパイラ基盤による、

エッジ向けディープラーニングの実装と

最適化について

2019/09/28

株式会社フィックスターズ 山田 貴登

Fixstarsってどんな会社?

2

大崎本社
 横浜事業所
Fixstars Solutions, Inc.(米国カリフォルニア州、100%子会社) 

株式会社 Fixstars Autonomous Technologies (株式会社ネクスティエレクトロニクスとの合弁会社)
株式会社 Sleek ( AI によるソフトウェア開発マネジメントSaaSサービスを開発 )
設立 2002年8月

資本金 5億5,176万円 (2019年3月末 現在)

社員数 189名(2019年3月末 現在)

所在地 大崎(本社)、横浜

代表取締役社長 三木 聡

主な取引先 東芝、キヤノン、日立製作所、日立ハイテクノロジーズ、オリンパス、みずほ証券、理化学研究所など 

連結子会社
ソフトウェア

高速化サービス拠点

FixstarsAT拠点

Fixstarsのお仕事

様々な領域のアプリケーションを、様々なハードウェアに対して

高速化している。

3

対象

ハードウェア
技術領域
業界

CPU
GPU
FPGAHPC
量子コンピュータ

機械学習
 数理最適化

画像処理、画像認識

DSP
大規模分散並列、

シミュレーション

IoT
量子コンピューティング

金融

ベンダー専用演算機
クラウド

組込みデバイス

コンパイラ、言語処理
 自然言語処理

ゲノム解析

スパコン

自動運転
 産業機器
 ヘルスケア

Fixstarsのお仕事

様々な領域のアプリケーションを、様々なハードウェアに対して

高速化している。

4

対象

ハードウェア
技術領域
業界

CPU
GPU
FPGAHPC
量子コンピュータ

機械学習
 数理最適化

画像処理、画像認識

DSP
大規模分散並列、

シミュレーション

IoT
量子コンピューティング

金融

ベンダー専用演算機
クラウド

組込みデバイス

コンパイラ、言語処理
 自然言語処理

ゲノム解析

スパコン

自動運転
 産業機器
 ヘルスケア

Deep
Learning
NEDOプロジェクトでのDNNに対する取り組み

5

1. DNNモデルコンパイラ 2. DSLコンパイラ 3. 高位合成コンパイラ
モデル圧縮コンパイラにより計算量・
メモリ使用量を大幅に削減
DSLレベルでCPU/GPUに加えて
データフローアーキテクチャの生成をサポート
汎用のRTLバックエンドにより
FPGAに加えASICとしての実装を可能に
Halide IR
Halide IR
Low Level IR
DSL Compiler
Low Level IR
Model Compiler HLS Compiler
東京工業大学 中原研究室と共同 北海道大学 高前田研究室と共同フィックスターズ
「高効率・高速処理を可能とするAIチップ・次世代コンピューティングの技術開発/
革新的AIエッジコンピューティング技術の開発/
エッジビジョンAIを超軽量化し短TATで実装する技術の研究開発」
FPGAやASICに実装可能なエッジビジョンAIを短期間で容易に開発可能なコンパイラ基盤を構築する
NEDOプロジェクトでのDNNに対する取り組み

6

1. DNNモデルコンパイラ 2. DSLコンパイラ 3. 高位合成コンパイラ
モデル圧縮コンパイラにより計算量・
メモリ使用量を大幅に削減
DSLレベルでCPU/GPUに加えて
データフローアーキテクチャの生成をサポート
汎用のRTLバックエンドにより
FPGAに加えASICとしての実装を可能に
Halide IR
Halide IR
Low Level IR
DSL Compiler
Low Level IR
Model Compiler HLS Compiler
東京工業大学 中原研究室と共同 北海道大学 高前田研究室と共同フィックスターズ
「高効率・高速処理を可能とするAIチップ・次世代コンピューティングの技術開発/
革新的AIエッジコンピューティング技術の開発/
エッジビジョンAIを超軽量化し短TATで実装する技術の研究開発」
FPGAやASICに実装可能なエッジビジョンAIを短期間で容易に開発可能なコンパイラ基盤を構築する
ところで、あなた誰?

名前: 山田貴登 (@tkclimb)



仕事: 

➔ 2019年4月にJoin

➔ DNNグラフコンパイラ開発

➔ DSCの検証をちょっと(Halide, Tiramisu)

趣味: 

➔ コンピュータ科学、工学よりの数学 (趣味でブログやってます)

➔ バドミントン、音楽

7

https://csam.hatenablog.com/
今日話すこと

❏ エッジディープラーニング推論の現状のまとめ

❏ マルチレイヤDNNコンパイラ Noah について

❏ Halideについて

❏ NoahのDNNコンパイラ”開発”アクセラレーション

❏ Halide Xilinx HLSバックエンドについて

❏ まとめと今後の展望

8

エッジディープラーニング推論の現状のまとめ

❏ グラフコンパイラが普及している

TF-Lite, NGraph, TensorRT, OpenVINO, Menoh, TVMなど

❏ ノードレベルの推論実装が普及している

❏ モノリシック計算カーネル: MKL-DNN, cuDNN, ACL, (SNPE?) など

❏ DSLコンパイラベース: TVM, PlaidML, TC, Halide, Tiramisu

❏ ONNXが標準DNNグラフフォーマットとして広まっている

pytorch, onnx-chainer, tensorflow-onnx, caffe2, nnabla など

❏ 様々なプラットフォームで推論が可能に

CPU: x86, arm

GPU: Nvidia GPU, AMD GPU, intel Graphics, arm Mali

その他: TPU, Edge TPU, intel Myriad, 各種DSP, FPGA など

9

エッジディープラーニング推論の現状のまとめ

❏ グラフコンパイラが普及している

TF-Lite, NGraph, TensorRT, OpenVINO, Menoh, TVMなど

❏ ノードレベルの推論実装が普及している

❏ モノリシック計算カーネル: MKL-DNN, cuDNN, ACL, (SNPE?) など

❏ DSLコンパイラベース: TVM, PlaidML, TC, Halide, Tiramisu

❏ ONNXが標準DNNグラフフォーマットとして広まっている

pytorch, onnx-chainer, tensorflow-onnx, caffe2, nnabla など

❏ 様々なプラットフォームで推論が可能に

CPU: x86, arm

GPU: Nvidia GPU, AMD GPU, intel Graphics, arm Mali

その他: TPU, Edge TPU, intel Myriad, 各種DSP, FPGA など

10

おすすめ

グラフコンパイラとベンダのライブラリの組み合わせ

11

ベンダの
ライブラリ
ベンダの
ライブラリ
ベンダの
ライブラリ
グラフ
コンパイラ
訓練済みDNNモデル
メリット:

各ベンダのデバイスに最適化された計算カーネルは高速

“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
グラフコンパイラとベンダのライブラリの組み合わせ

逆に、DSLコンパイラベースのアプローチはどうか?

最近増えてきているのでは?

13

実は世は既に

”IR 戦国時代”

Relay
Glow High-Level IR
XLA HLO
Halide-IR
MLIR
Glow Low-Level IR
LLVM IR
TPU IR
nGraph
IRってなんだっけ?

バックエンドの細かい仕様に依存せずに様々な操作を抽象化

IRを一つに統一すると変換器、最適化機の数を減らせる!

15

around
1990

5. Modern Compiler Implementation in ML, Andrew W. Appel, https://www.cs.princeton.edu/~appel/modern/ml/, p137
IRってなんだっけ?

バックエンドの細かい仕様に依存せずに様々な操作を抽象化

IRを一つに統一すると変換器、最適化機の数を減らせる!

16

around
1990

5. Modern Compiler Implementation in ML, Andrew W. Appel, https://www.cs.princeton.edu/~appel/modern/ml/, p137
手当たり次第にIRを作るのではなく、

適用ドメインをきちんと見定め、適切なIRを定義する

我々の目指すコンパイラ

IR

– データフローアーキテクチャが記述できる

– ループ変形の適用が容易である

– 依存解析がしやすい

– 様々なアーキテクチャの特徴を表現できる

フロントエンド

– 多次元データとその計算の表現力が高く、抽象的な記述が可能

– アルゴリズムと実装を分離できる

– バックエンドの切り替えが容易



DNNをアプリの1サンプルとして、コンパイラ基盤を実現したい!!

17

我々のアプローチ

❏ グラフコンパイラが普及している

OpenVINO, NGraph, TensorRT, TF-Lite, TVM Relay など

❏ ノードレベルの推論実装が普及している

❏ モノリシック計算カーネル: MKL-DNN, cuDNN, ACL, (SNPE?) など

❏ DSLコンパイラベース: TVM, PlaidML, TC, Halide, Tiramisu

❏ ONNXが標準DNNグラフフォーマットとして広まっている

pytorch, onnx-chainer, tensorflow-onnx, caffe2, nnabla など

❏ 様々なプラットフォームで推論が可能に

CPU: x86, arm

GPU: Nvidia GPU, AMD GPU, intel Graphics, arm Mali

その他: TPU, Edge TPU, intel Myriad, 各種DSP, FPGA など

18

組み合わせる

19

訓練済みDNNモデル
フロ
ント
エン
ド
Halideを利用したDNNグラフコンパイラ Noah

Noah
IR
(MLIR)
Noah

Halide
ベンダの
ライブラリ
バック

エンド

専
用
IR
Noahとは

1. 訓練済みDNNグラフ(ONNX)を入力して

2. Noah IRでグラフレベルの最適化して

3. Halide IRで全体最適化を行って

4. 共有ライブラリとヘッダを生成する

20

libmodelA.so
modelA.h
実行バイナリ
modelA.elf
Noah

model A
ユーザプログラム
modelA_wrapper.cpp
ビルド

Noahのアプローチのメリット・デメリット

メリット

個別の計算カーネルを跨いだ全体最適化が可能

新しい計算カーネルを比較的容易かつ短時間で実装可能

計算カーネルの入出力やパラメータ、データの精度に対して特殊化しやすい



デメリット

個別の計算カーネルはベンダの実装より実行速度が遅くなりやすい

システム構築のコストが高い (コンパイルフローが複雑)

デバッグが難しい (デバッガは使えない & printの使用が限定的)

21

Noah IR

MLIRとは

Multi Layer Internal Representationの略

IRを構築、IR同士の変換する仕組みを提供

IRのダンプ、パースなどのIO機能 (シリアライズも?)

CSE, DCEなどの一般的なコンパイラ最適化や独自パス構築用の基盤を提供

LLVM Dialect、Affine Dialect、GPU用Dialectなど多様なIRが実装済み

22

4. MLIR, https://github.com/tensorflow/mlir
MLIRベースの中間表現で、各ノードにおける演算に必要な情報を全て含んで
いる。





%10 = "noah.Conv"(%0, %4) {
dilations = [1, 1], group = 1 : i64, kernel_shape = [5, 5], name = "Convolution28_Output_0",
order = "NCHW", pads = [2, 2, 2, 2], strides = [1, 1]
} : (tensor<1x1x28x28xf32>, tensor<8x1x5x5xf32>) -> tensor<1x8x28x28xf32>
LeNetの例 (ONNX)

23

https://github.com/onnx/models/blob/master/
vision/classification/mnist/README.md
LeNetの例 (Noah IR)

24







module {
func @LeNetFromONNX(%arg0: tensor<1x1x28x28xf32>) -> tensor<1x10xf32> {
%0 = "noah.Placeholder"() {name = "Input3"} : () -> tensor<1x1x28x28xf32>
%1 = "noah.Constant"() {name = "Parameter193", value = dense<[...]> : tensor<16x4x4x10xf32>} : () ->
tensor<16x4x4x10xf32>
...
%8 = "noah.Constant"() {name = "Pooling160_Output_0_reshape0_shape", value = dense<[1, 256]> : tensor<2xi64>} : () ->
tensor<2xi64>
%9 = "noah.Reshape"(%1, %2) {name = "Parameter193_reshape1"} : (tensor<16x4x4x10xf32>, tensor<2xi64>) ->
tensor<256x10xf32>
%10 = "noah.Conv"(%0, %4) {dilations = [1, 1], group = 1 : i64, kernel_shape = [5, 5], name = "Convolution28_Output_0",
order = "NCHW", pads = [2, 2, 2, 2], strides = [1, 1]} : (tensor<1x1x28x28xf32>, tensor<8x1x5x5xf32>) ->
tensor<1x8x28x28xf32>
...
%22 = "noah.Reshape"(%21, %8) {name = "Pooling160_Output_0_reshape0"} : (tensor<1x16x4x4xf32>, tensor<2xi64>) ->
tensor<1x256xf32>
%23 = "noah.MatMul"(%22, %9) {name = "Times212_Output_0"} : (tensor<1x256xf32>, tensor<256x10xf32>) ->
tensor<1x10xf32>
%24 = "noah.Add"(%23, %3) {name = "Plus214_Output_0"} : (tensor<1x10xf32>, tensor<1x10xf32>) -> tensor<1x10xf32>
"noah.Output"(%24) {name = "Plus214_Output_0_output"} : (tensor<1x10xf32>) -> ()
}
}
Tablegenを用いたNoah IRの定義

25







# Noah/Ops.td (TablegenはLLVMのC++のソースコードを生成する独自 DSL)
def NOAH_ConvOp : NOAH_Op<"Conv", [NoSideEffect]> {
let summary = "Comment for Convolution operator";
let arguments = (ins // 実際の引数の定義
StrAttr:$name,
NOAH_4DTensor:$X,
NOAH_4DTensor:$W,
Variadic<NOAH_1DTensor>:$B, // オプショナルな入力
Confined<I64ArrayAttr, [ArrayMinCount<2>]>:$kernel_shape,
Confined<I64ArrayAttr, [ArrayMinCount<2>]>:$strides,
Confined<I64ArrayAttr, [ArrayMinCount<2>]>:$dilations,
Confined<I64ArrayAttr, [ArrayMinCount<4>]>:$pads,
I64Attr:$group,
DefaultValuedAttr<NOAH_DataOrderKind, "NCHW">:$order // デフォルト付きレイアウト
);
let results = (outs NOAH_4DTensor:$Y);
}
Tablegenを用いたIRの成約と検証

26







# f32, f64, i32, i64のみを対象とする成約
def NOAH_ElementType : Type<
Or<[I32.predicate, I64.predicate, F32.predicate, F64.predicate]>,
"noah.element_type"
>;
# MLIRの標準型 “RankedTensorType” を継承して、データ型の制約をつける
def NOAH_Tensor : StaticShapeTensorOf<[NOAH_ElementType]>;
# 4次元であることを保証する成約
def IsRank4D : CPred<"$_self.cast<RankedTensorType>().getRank() == 4">;
# 4次元かつf32, f64, i32, i64であることを保証する成約
def NOAH_4DTensor
: Type<And<[NOAH_Tensor.predicate, Is4DTensor]>>;
MLIRを用いたIRの成約と検証 (ヘッダーファイル)

27







class ConvOp : public Op<ConvOp, OpTrait::OneResult, OpTrait::HasNoSideEffect,
OpTrait::AtLeastNOperands<2>::Impl> {
public:
Value *X(); // オペランド引数を取得する API
Value *W();
Value *Y();
...
StringRef name(); // パラメータを取得する API
ArrayAttr kernel_shape();
ArrayAttr strides();
...
// インスタンス生成用の API (MLIRのOpは通常はConstructorを利用しない)
static void build(Builder *, OperationState *tblgen_state, ArrayRef<Type> resultTypes,
StringAttr name, Value *X, Value *W, ArrayRef<Value *> B,
ArrayAttr kernel_shape, ArrayAttr strides, ArrayAttr dilations, ArrayAttr pads,
IntegerAttr group, StringAttr order);
// 検証実行用のAPI
LogicalResult verify();
};
DNNコンパイラはコンパイルフローが複雑で検証が難しい。

最終的な結果のみの比較では問題箇所の特定が困難。



28

Halide
プログラム
gen_modelA.cpp
生成

C++コンパイラ
ビルド①

ビルド②

libmodelA.so
model A
modelA.h
ユーザプログラム
modelA_wrapper.cpp
実行ファイル (elf)
modelA.elf
gen_modelA.elf
実行

Noah IR
最適化

フロ
ント
エン
ド
Noah IRによる開発アクセラレーション

最終比較リファレンス
(ONNX-Runtimeなど)
HC IR
1. ONNXから適切なIRが生成されているか検証

→ おそらくMLIRのFileCheckが利用できる (未検証)



29

Halide
プログラム
gen_modelA.cpp
生成

C++コンパイラ
ビルド①

ビルド②

libmodelA.so
model A
modelA.h
ユーザプログラム
modelA_wrapper.cpp
実行ファイル (elf)
modelA.elf
gen_modelA.elf
実行

Noah IR
最適化

フロ
ント
エン
ド
Noah IRによる開発アクセラレーション

最終比較リファレンス
(ONNX-Runtimeなど)
Noah IRをダンプして
フロントエンドの検証
HC IR
1. IRの最適化前と最適化後を比較してテスト

→ おそらくMLIRのFileCheckが利用できる





30

Halide
プログラム
gen_modelA.cpp
生成

C++コンパイラ
ビルド①

ビルド②

libmodelA.so
model A
modelA.h
ユーザプログラム
modelA_wrapper.cpp
実行ファイル (elf)
modelA.elf
gen_modelA.elf
実行

Noah IR
最適化

フロ
ント
エン
ド
Noah IRによる開発アクセラレーション

最終比較リファレンス
(ONNX-Runtimeなど)
Noah IRをダンプして
フロントエンドの検証
最適化前と後のNoah IRをダ
ンプして内容を検証
HC IR
各バックエンドに専用のIRがある。アーキテクチャの特性に合わせて

制約をつけることで、実行不可能な変換を検出する。



31

生成

C++コンパイラ
ビルド①

ビルド②

libmodelA.so
model A
modelA.h
ユーザプログラム
modelA_wrapper.cpp
実行ファイル (elf)
modelA.elf
gen_modelA.elf
実行

Noah IR
最適化

フロ
ント
エン
ド
Noah IRによる開発アクセラレーション

最終比較リファレンス
(ONNX-Runtimeなど)
Noah IRをダンプして
フロントエンドの検証
最適化前と後のNoah IRをダ
ンプして内容を検証
バックエンド専用IRがある
特性に合わせた検証が可能
Halide
プログラム
gen_modelA.cpp
HC IR
Halideバックエンドのコード生成 (LeNetの一部)

32

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));
// シンボル: c, x, y, n, 定数: C, X, Y, N, KX, KY, SX, SY, PX, PY
// Halide::Func placeholder_0, constant_0, pad_0, conv_0, add_0, bias_0, maxpool_0
Noah IRの最適化を少し

DNN向けIRの最適化手法に重要な変換に、ステンシル演算に対する

データレイアウトの変換がある。以下にConv2Dの例を示す。



33

34

Convのレイアウトを変
換した後、等価性を保
つためにTransposeを
挿入

Noah IRの最適化を少し

35

入力に対しても
Transposeを挿入

Convのレイアウトを変
換した後、等価性を保
つためにTransposeを
挿入

Noah IRの最適化を少し

36

入力に対しても
Transposeを挿入

Transposeが相殺され
て消去される

Convのレイアウトを変
換した後、等価性を保
つためにTransposeを
挿入

Noah IRの最適化を少し

Halideについて

画像処理の高性能計算に特化したドメイン固有言語(DSL) [3]



特徴

C++の内部DSLとして実装

アルゴリズムとスケジューリングを分離して記述

– 純粋関数型言語による簡素なアルゴリズム記述

– 最適化方法を指定するスケジューリング記述

様々なHWターゲットに対する最適化・コード生成が可能

ターゲット: x86, ARM, POWER, MIPS, NVIDIA GPGPU, Hexagon

出力形式: LLVM-IR, C/C++, OpenCL, OpenGL, Matlab, Metal など





37

3. Halide, https://halide-lang.org/
C++による3x3Boxフィルタの記述例

38

void box_filter_3x3(const Image in, Image &blury) {
Image blurx(in.width(), in.height());
for (int y = 0; y < in.height(); y++)
for (int x = 0; x < in.width(); x++)
blurx(x, y) = (in(x-1, y) + in(x, y) + in(x+1, y))/3;
for (int y = 0; y < in.height(); y++)
for (int x = 0; x < in.width(); x++)
blury(x, y) = (blurx(x, y-1) + blurx(x, y) + blurx(x, y+1))/3;
}
Halideによる3x3Boxフィルタの記述例

39

Func box_filter_3x3(Func in) {
Func blurx, blury;
Var x, y;
blurx(x, y) = (in(x-1, y) + in(x, y) + in(x+1, y))/3;
blury(x, y) = (blurx(x, y-1) + blurx(x, y) + blurx(x, y+1))/3;
return blury;
}
計算の本質的な処理のみを記述

– 出力と入力の関係式を純粋関数として記述

– 汎用ループや条件分岐文は記述できない

– Select式は記述できる

ターゲットハードウェアによらず単一の記述で良い





Halideによる3x3Boxフィルタのスケジューリング

40

アルゴリズム記述と分離して記述できる

再計算とメモリ使用量のトレードオフ決定

タイリング、並列化、ベクトル化などのループ変形

データオーダーの指定





Func box_filter_3x3(Func in) {
Func blurx, blury;
Var x, y;
blurx(x, y) = (in(x-1, y) + in(x, y) + in(x+1, y))/3;
blury(x, y) = (blurx(x, y-1) + blurx(x, y) + blurx(x, y+1))/3;
blury.tile(x, y, xi, yi, 256, 32).vectorize(xi, 8).parallel(y);
blurx.compute_at(blury, x).store_at(blury, x).vectorize(x, 8);
return blury;
} CPU用スケジューリング記述
41

GNESISチームでVivadoHLS向けのバックエンドを開発
→ CPU・GPU・FPGA向けのプログラムをシームレスに開発可能
Halide FPGAバックエンド

Halide
Compiler
AST構築/Lowering
LLVM-IR
LLVM
C.h.a/.o
C/C++ Compiler
CPU / NVIDIA GPU
JIT C/C++ Compiler
CPU
HLS C/C++
Vivado HLS/Vivado
Xilinx FPGA
新規拡張
FPGA
Backend
Halide Program
アルゴリズムに合わせたデータフローアーキテクチャを生成

コンパイラがアクセスパターンを解析し、モジュール間は可能な限り

ストリーム化

モジュール内のループは完全にパイプライン化

42

f

g

h

pipeline

in

p

Halide FPGA Backend 

Stream

Stream

Stream

AXI4-Stream

Func pipeline(ImageParam in, Param p)
{
Func f, g, h;
f(x, y) = in(x, y) * 2;
g(x, y) = in(x, y) + p;
h(x, y) = f(x, y) + g(x, y);
f.compute_root();
g.compute_root();
h.compute_root();
return h;
}
AXI4-Stream

AXI4-Lite

Halide FPGAバックエンドのマイクロアーキテクチャ

ストリーミング&ラインバッファ

による回路を生成

可能な限りII=1の回路を生成

コンパイラが参照範囲を解析

必要ラインバッファサイズを

自動計算し生成



RAMでバッファリングする

回路も生成可能



43

+
in_buffer f_buffer
conv3x3
in_port f_port
■ Shift Regs ■ Block RAM
Halide FPGA Backend
Func conv3x3(ImageParam in) {
Var x, y;
RDom r{-1, 3, -1, 3};
Func f;
f(x, y) = sum(in(x+r.x, y+r.y));
f.compute_root();
return f;
}
Halide FPGAバックエンドのマイクロアーキテクチャ

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

// 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

// 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では致命的!!

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

// 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

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上のメモ
リ使用量を抑えることができる。

50

Noahによる計算量のチューニング

Conv1
BN
Relu
Conv2
時間

51

Noahによる計算量のチューニング

計算量の多い演算をunroll 

時間

conv1.unroll(x, 4)

conv2.unroll(x, 2)
Conv1
Conv2
Conv1
Conv2Unroll 2倍
Unroll 4倍
Conv1
BN
Relu
Conv2
Noahにはノード毎に計算量を計測する機能がある。

解析を元に自動でunroll数を設定可能。

52

Noahによる計算量のチューニング

まとめ

● NoahとはDSLコンパイラベースのDNNグラフコンパイラ

● Halideをバックエンドに採用

→ 新しい計算カーネルに素早く対応できる

● MLIRで最適化パスの実装や検証システムの開発が円滑に

→ 全体の開発速度が向上した

● グラフレベルの最適化と計算量解析をNoah IRで行う

→ Halideでできない最適化を行える

● 全体の最適化をHalideで行う

→ モノリシック計算カーネルでできない全体最適ができる

● Noahは理想のコンパイラを探求するための施策

→ HPC向けの理想の言語ができればHalideの置き換えも可能?

53

課題と今後の展望

● 計算カーネルを増やす (ONNX全サポートが目標)

● Halide以外のバックエンドの追加

● スパースや量子化フォーマットのサポート

● 再学習環境の構築

● レイアウト問題の解消

● ベンダ固有の計算カーネルとの性能差

○ ベンダ固有の計算カーネルを使うこともできる

○ オートチューニングによって、最終的には速度面でも

有利になると予測している

● DNNに限らないループ最適化の活きるドメインへの適用

54

参考

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


マルチレイヤコンパイラ基盤による、エッジ向けディープラーニングの実装と最適化について