Halideによる画像処理プログラミング入門
Fixstars Corp.
丸岡 晃
近村 啓史
0
DeepLearningの台頭
28.2
25.8
16.4
11.7
6.7
3.57
0
5
10
15
20
25
30
2010
NEC America
2011
Xerox
2012
AlexNet
2013
Clarifai
2014
GoogLeNet
2015
ResNet
Top-5Error[%]
ILSVRC ImageNet Classification
1
Deep Neural Networkによる
劇的な精度向上
http://image-net.org/challenges/talks/ilsvrc2015_deep_residual_learning_kaiminghe.pdf
DeepLearningを使った画像認識
 運転支援・自動運転
 工場での不良品検査
2
http://www.gapsis.jp/2015/01/nvidia-drive.html
http://www.itmedia.co.jp/enterprise/articles/1706/20/news049.html
https://gigazine.net/news/20160105-nvidia-drive-px2-demo/
本日の内容
 画像認識の高速化・低消費電力化の需要
 ムーアの法則の終焉
– ハードウェアが勝手に速くなる時代は終わった
– ハードウェアを効率良く使えるソフトウェア開発が必要
 画像処理プログラミング言語『Halide』の紹介
– 速く処理出来て
– 様々なハードウェアで動かせるプログラムが
– 短期間で簡単に開発出来る!
3
アジェンダ
Halide概要
アルゴリズム記述
スケジューリング記述
Halideの活用事例の紹介
フィックスターズにおける取り組みの紹介
4
Halide概要
5
Halideとは
画像処理の高性能計算に特化したDSL
(DSL: Domain Specific Language)
– http://halide-lang.org/
– C++の内部DSLとして提供
特徴
– アルゴリズムとスケジューリングの分離
– マルチプラットフォーム対応
• x86, ARM, PowerPC, NVIDIA GPGPUなど
に対するコード生成が可能
6
アルゴリズムとスケジューリングの分離
 アルゴリズム
– 計算部の本質的な処理のみを記述
– ハードウェアによらず単一の記述でよい
 スケジュール
– 計算順序やデータの保持の仕方を記述
– 並列化やループ変形などの最適化もここで記述
– ハードウェアごとに異なるスケジュールを指定可能
7
アルゴリズムとスケジュールの分離が可能
C++でのナイーブな実装コード
3x3Blurフィルターの実装例
8
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;
}
C++でのIntel向け手動最適化コード
9
void box_filter_3x3(const Image &in, Image &blury) {
__m128i one_third = _mm_set1_epi16(21846);
#pragma omp parallel for
for (int yTile = 0; yTile < in.height(); yTile += 32) {
__m128i a, b, c, sum, avg;
__m128i blurx[(256/8)*(32+2)]; // allocate tile blurx array
for (int xTile = 0; xTile < in.width(); xTile += 256) {
__m128i *blurxPtr = blurx;
for (int y = -1; y < 32+1; y++) {
const uint16_t *inPtr = &(in[yTile+y][xTile]);
for (int x = 0; x < 256; x += 8) {
a = _mm_loadu_si128((__m128i*)(inPtr-1));
b = _mm_loadu_si128((__m128i*)(inPtr+1));
c = _mm_load_si128((__m128i*)(inPtr));
sum = _mm_add_epi16(_mm_add_epi16(a, b), c);
avg = _mm_mulhi_epi16(sum, one_third);
_mm_store_si128(blurxPtr++, avg);
inPtr += 8;
}
}
blurxPtr = blurx;
for (int y = 0; y < 32; y++) {
__m128i *outPtr = (__m128i *)(&(blury[yTile+y][xTile]));
for (int x = 0; x < 256; x += 8) {
a = _mm_load_si128(blurxPtr+(2*256)/8);
b = _mm_load_si128(blurxPtr+256/8);
c = _mm_load_si128(blurxPtr++);
sum = _mm_add_epi16(_mm_add_epi16(a, b), c);
avg = _mm_mulhi_epi16(sum, one_third);
_mm_store_si128(outPtr++, avg);
}
}
}
}
}
C++でのIntel向け手動最適化コード
10
void box_filter_3x3(const Image &in, Image &blury) {
__m128i one_third = _mm_set1_epi16(21846);
#pragma omp parallel for
for (int yTile = 0; yTile < in.height(); yTile += 32) {
__m128i a, b, c, sum, avg;
__m128i blurx[(256/8)*(32+2)]; // allocate tile blurx array
for (int xTile = 0; xTile < in.width(); xTile += 256) {
__m128i *blurxPtr = blurx;
for (int y = -1; y < 32+1; y++) {
const uint16_t *inPtr = &(in[yTile+y][xTile]);
for (int x = 0; x < 256; x += 8) {
a = _mm_loadu_si128((__m128i*)(inPtr-1));
b = _mm_loadu_si128((__m128i*)(inPtr+1));
c = _mm_load_si128((__m128i*)(inPtr));
sum = _mm_add_epi16(_mm_add_epi16(a, b), c);
avg = _mm_mulhi_epi16(sum, one_third);
_mm_store_si128(blurxPtr++, avg);
inPtr += 8;
}
}
blurxPtr = blurx;
for (int y = 0; y < 32; y++) {
__m128i *outPtr = (__m128i *)(&(blury[yTile+y][xTile]));
for (int x = 0; x < 256; x += 8) {
a = _mm_load_si128(blurxPtr+(2*256)/8);
b = _mm_load_si128(blurxPtr+256/8);
c = _mm_load_si128(blurxPtr++);
sum = _mm_add_epi16(_mm_add_epi16(a, b), c);
avg = _mm_mulhi_epi16(sum, one_third);
_mm_store_si128(outPtr++, avg);
}
}
}
}
}
Halideでのアルゴリズム実装
11
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;
}
Halideでのアルゴリズム実装
12
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;
}
アルゴリズム記述部
アルゴリズムは本質的な処理の内容だけを記述
HalideでのIntel向け最適化実装
13
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;
}
少ないコード量と期間で簡単に最適化が可能
スケジューリング記述部
アルゴリズムは変更なし
マルチプラットフォーム向けの最適化
14
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;
if (target.has_gpu_feature()) {
Var tx, ty;
blury.gpu_tile(x, y, tx, ty, 32, 8);
} else {
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;
}
複数のハードウェアに対しても簡単に最適化が可能
GPU用のスケジューリング記述
Halideのコンパイルフロー
15
Halide
Compiler
Halide Program
C++ Compiler
Binary
Exec
LLVM-IR
LLVM
C.h.a/.o
C/C++ Compiler
Host Program
BinaryExec(JIT)
C/C++ Compiler
Binary
Halideのコンパイルフロー
16
Halide
Compiler
Halide Program
C++ Compiler
Binary
Exec
LLVM-IR
LLVM
C.h.a/.o
C/C++ Compiler
Host Program
BinaryExec(JIT)
C/C++ Compiler
Binary
Halideで記述された
ソースコードをコンパイル
Halideのコンパイルフロー
17
Halide
Compiler
Halide Program
C++ Compiler
Binary
Exec
LLVM-IR
LLVM
C.h.a/.o
C/C++ Compiler
Host Program
BinaryExec(JIT)
C/C++ Compiler
Binary
Halideコンパイラによる
コンパイルを実行
Halideのコンパイルフロー
18
Halide
Compiler
Halide Program
C++ Compiler
Binary
Exec
LLVM-IR
LLVM
C.h.a/.o
C/C++ Compiler
Host Program
BinaryExec(JIT)
C/C++ Compiler
Binary
LLVMバックエンド
によるコード生成
コンパイル時実行可能
(JITコンパイル)
実行前にコンパイルも可能
(AOTコンパイル)
Halideのコンパイルフロー
19
Halide
Compiler
Halide Program
C++ Compiler
Binary
Exec
LLVM-IR
LLVM
C.h.a/.o
C/C++ Compiler
Host Program
BinaryExec(JIT)
C/C++ Compiler
Binary
Cバックエンドによる
コード生成も対応
アルゴリズム記述
20
関数の定義
アルゴリズムは純粋関数として定義される
– 関数: Halide::Func
– 次元変数: Halide::Var
21
Func f;
Var x, y;
f(x, y) = x + y;
意味: 関数f は定義域 x, yに対して x + y を値域に持つ
名前空間 Halide:: は以下省略
for (int y=0; y<height; y++) {
for (int x=0; x<width; x++) {
f[y][x] = x + y;
}
}
等価なC++ソースコードHalideで書かれたソースコード
関数の次元数
最大4次元までの関数を定義可能
22
Func f;
Var c, x, y;
f(c, x, y) = c + x + y;
fは3次元の関数
(e.g. 3D空間, カラー画像)
関数の次元数
最大4次元までの関数を定義可能
23
Func f;
Var c, x, y;
f(c, x, y) = c + x + y;
Func f;
Var c, x, y, z;
f(c, x, y, z) = c + x + y + z;
fは3次元の関数
(e.g. 3D空間, カラー画像)
fは4次元の関数
関数の参照
関数の右辺に定義された関数を記述
右図の定義と同じ意味(合成関数)
24
Func f1, f2;
Var x, y;
f1(x, y) = x + y;
f2(x, y) = f1(x, y);
Func f2;
Var x, y;
f2(x, y) = x + y;
意味: 関数 f2 は定義域 x, yに対して f1(x, y) を値域に持つ
等価
式
式はExprクラスの変数として表される
– 関数: Func
– 次元変数: Var
– 式: Expr
25
組み立てた計算式を、式として取っておく事ができる
Func f;
Var x, y;
Expr e = x + y;
f(x, y) = e;
Func f;
Var x, y;
f(x, y) = x + y;
等価
即値
C++の数値リテラルは式の定義に使用可
– 暗黙的型変換によりExpr型にキャストされる
26
意味: 関数 f はどのような定義域に対しても値域 1 を持つ
Func f;
Var x, y;
f(x, y) = 1;
RDomと畳み込み関数
 リダクションドメイン: Rdom
– RDom(min, extent)
– 指定された次元で[min, min+extent-1]の領域を動く
ループ変数
27
rx は[-1, 1] を動くループ変数
Func f, g;
Var x;
RDom rx(-1, 3)
f(x) = x;
g(x) = sum(f(x + rx));
for (int x=0; x<width; x++) {
T sum = 0;
for (int rx=-1; rx<2; rx++) {
sum += x + rx;
}
g[x] = sum;
}
等価なC++ソースコード
RDomと畳み込み関数
 畳み込み関数:
– RDomで指定した領域を畳み込んで演算を行う関数
• 例: sum/product/maximum/minimum/argmin/argmax
28
Func f, g;
Var x;
RDom rx(-1, 3)
f(x) = x;
g(x) = sum(f(x + rx));
関数 g は各xにおいて、x-1, x+0, x+1の総和を値域に持つ
for (int x=0; x<width; x++) {
T sum = 0;
for (int rx=-1; rx<2; rx++) {
sum += x + rx;
}
g[x] = sum;
}
等価なC++ソースコード
領域外アクセス時の参照値設定
 BoundaryConditions
– constant_exterior: 指定した定数値を参照
– repeat_edge: 袖領域を拡張して参照
– mirror_image: 画像を反転して複製するように参照
– etc…
29
Func f, f_, g;
Var x;
RDom rx(-1, 3)
f(x) = x;
f_ = BoundaryConditions::constant_exterior(f, 0)
g(x) = sum(f_(x + rx));
関数 f は定義域 x<0 または x>=width のとき、値 0 を返す
アルゴリズムの記述例
2次元Convolutionを実装してみよう
30
2次元Convolution
31
𝐷𝑠𝑡 𝑥, 𝑦 =
𝑘𝑦=0
𝑘ℎ
𝑘𝑥=0
𝑘𝑤
𝑆𝑟𝑐 𝑥 + 𝑘𝑥 −
𝑘𝑤
2
, 𝑦 + 𝑘𝑦 −
𝑘ℎ
2
× 𝐾𝑒𝑟𝑛𝑒𝑙(𝑘𝑥, 𝑘𝑦)
 2次元画像の畳み込み処理
– 画像処理において頻繁に用いられる処理
= ∗
2次元Convolution
32
 例: 画像処理フィルタ
– ぼかしやエッジ検出、ノイズ除去などの様々な
画像処理に使用されている
1
16
1
8
1
16
1
8
1
4
1
8
1
16
1
8
1
16
−1 −1 −1
−1 8 −1
−1 −1 −1
=
∗
=
Gaussian Kernel
Laplacian Kernel
ぼかし処理
エッジ検出
DeepLearningにおけるConvolution
 Convolutional Neural Network
– 画像認識に使用されるニューラルネットワーク
– 推論時はConvolution層が全体の処理時間の50%以上を占める
• 最近のネットワークではConvolution層の多段化により
さらに処理時間が増加
 Convolution層の高速化はとても重要!
33
Convolution層
LeNetのネットワーク図
Y. LeCun, L. Bottou, Y. Bengio, and P. Haffner. Gradient-based learning
applied to document recognition. Proceedings of the IEEE, november 1998.
2次元Convolutionのアルゴリズム記述
 C++での実装例
34
void conv3x3(const uint8_t* src, const float* kernel, uint8_t* dst,
int height, int width) {
for (int y=0; y<height; y++) {
for (int x=0; x<width; x++) {
float tmp = .0f;
for (int ky=0; ky<kh; ky++) {
for (int kx=0; kx<kw; kx++) {
tmp += src[y+ky-kh/2][x+kx-kw/2] * kernel[ky][kx];
}
}
dst[y][x] = tmp;
}
}
}
2次元Convolutionのアルゴリズム記述
 Generator インタフェースに従った記述例
– ターゲットや出力形式、コンパイル時パラメータを
コンパイル時に指定可能
35
class conv3x3 : public Generator<conv3x3>{
Var x{"x"}, y{"y"};
public:
Input<Buffer<uint8_t>> src{“src", 2};
Input<Buffer<float>> kernel{"kernel", 2};
Output<Buffer<uint8_t>> dst{“dst", 2};
void generate() {
RDom r(-1, 3, -1, 3, "r");
Func src_ = BoundaryConditions::repeat_edge(in);
dst(x, y) = cast<uint8_t>(sum(cast<float>(src_(x+r.x, y+r.y)) * kernel(r.x+1, r.y+1)));
}
};
HALIDE_REGISTER_GENERATOR(conv3x3, conv3x3)
2次元Convolutionのアルゴリズム記述
 Generator インタフェースに従った記述例
– ターゲットや出力形式、コンパイル時パラメータを
コンパイル時に指定可能
36
class conv3x3 : public Generator<conv3x3>{
Var x{"x"}, y{"y"};
public:
Input<Buffer<uint8_t>> src{“src", 2};
Input<Buffer<float>> kernel{"kernel", 2};
Output<Buffer<uint8_t>> dst{“dst", 2};
void generate() {
RDom r(-1, 3, -1, 3, "r");
Func src_ = BoundaryConditions::repeat_edge(in);
dst(x, y) = cast<uint8_t>(sum(cast<float>(src_(x+r.x, y+r.y)) * kernel(r.x+1, r.y+1)));
}
};
HALIDE_REGISTER_GENERATOR(conv3x3, conv3x3)
入出力パラメータ
アルゴリズムの記述部
Generatorを登録
スケジューリング記述
37
スケジューリングとは
ハードウェアの特性に応じて、
性能を向上させるための様々な指定
– 計算タイミングの指定
– スレッド並列化
– ベクトル化
– ループ変形
– etc…
スケジューリングはターゲットハードウェア
によって達成される効果が異なります
– 今回はCPUの場合について紹介します
38
計算タイミングの指定
 Func::compute_inline (デフォルト)
– 出力のFunc以外はインライン展開される
• 〇必要メモリ量は少なくなる
• ×計算量が増える可能性あり
39
等価
Func blur_x, blur_y;
Var x, y;
blur_x(x, y) =
in(x, y) + in(x+1, y);
blur_y(x, y) =
(blur_x(x, y) + blur_x(x, y+1)) / 4;
Func blur_y;
Var x, y;
blur_y(x, y) =
(in(x, y) + in(x+1, y) +
in(x, y+1) + in(x+1, y+1)) / 4;
計算タイミングの指定
 Func::compute_root
– 関数の全領域の計算結果がメモリに保持される
• 〇計算量が削減できる可能性あり
• ×必要メモリ量は多くなる
40
Func blur_x, blur_y;
Var x, y;
blur_x(x, y) =
in(x, y) + in(x+1, y);
blur_y(x, y) =
(blur_x(x, y) + blur_x(x, y+1)) / 4;
blur_x.compute_root();
blur_xはblur_yを評価する前に全領域が計算される
for (int y=0; y<height; y++) {
for (int x=0; x<width; x++) {
blur_x[y][x] = in[y][x] + in[y][x+1];
}
}
for (int y=0; y<height; y++) {
for (int x=0; x<width; x++) {
blur_y[y][x] =
(blur_x[y][x] + blur_x[y+1][x]) / 4;
}
}
計算タイミングの指定
 Func::compute_at
– 指定した関数の次元のループ内で必要な領域のみの
計算結果がメモリに保持される
• 計算量・メモリ使用量共に
compute_inlineとcompute_rootの中間となる
41
blur_xはblur_yのyループ内で必要な領域が計算される
for (int y=0; y<height; y++) {
for (int x=0; x<width; x++) {
blur_x[0][x] = in[y][x] + in[y][x+1];
blur_x[1][x] =
in[y+1][x] + in[y+1][x+1];
}
for (int x=0; x<width; x++) {
blur_y[y][x] =
(blur_x[0][x] + blur_x[1][x]) / 4;
}
}
Func blur_x, blur_y;
Var x, y;
blur_x(x, y) =
in(x, y) + in(x+1, y);
blur_y(x, y) =
(blur_x(x, y) + blur_x(x, y+1)) / 4;
blur_x.compute_at(blue_y, y);
スレッド並列化
 Func::parallel
– 指定した次元でループをスレッド並列化
– 各スレッドを異なるコアで実行することで高速化
42
Func f;
Var x, y;
f(x, y) = x + y;
f.parallel(y);
Thread 0
Thread 1
x
y
f
Parallelize
fの計算が2スレッドで並列に処理される
ベクトル化
 Func::vectorize
– 指定した次元とベクトル幅でループをベクトル化
 ベクトル化
– SIMD命令を使用するように最適化すること
• 1命令で複数のデータを同時に演算可能
• 例: Intel SSE/AVX, ARM NEON, Power AltiVec など
43
Func f, g, h;
Var x;
f(x) = g(x) + h(x);
+
g
h
f
1命令あたり1要素ずつ演算される
ベクトル化
 Func::vectorize
– 指定した次元とベクトル幅でループをベクトル化
 ベクトル化
– SIMD命令を使用するように最適化すること
• 1命令で複数のデータを同時に演算可能
• 例: Intel SSE/AVX, ARM NEON, Power AltiVec など
44
Func f, g, h;
Var x;
f(x) = g(x) + h(x);
f.vectorize(x, 4);
+
g
h
f
1命令で4要素が同時に演算される
Vectorize
ループアンロール
 Func::unroll
– 指定した次元に対してループ展開を行う
• 分岐命令の削減
• ソフトウェアパイプライニング
• レジスタの再利用(レジスタブロッキング)
45
for (int y=0; y<height; y++)
for (int x=0; x<width; x++)
f[y][x] = x + y;
unroll前の等価なC++ソースコード
Func f;
Var x, y;
f(x, y) = x + y;
ループアンロール
 Func::unroll
– 指定した次元に対してループ展開を行う
• 分岐命令の削減
• ソフトウェアパイプライニング
• レジスタの再利用(レジスタブロッキング)
46
Func f;
Var x, y;
f(x, y) = x + y;
f.unroll(x, 2);
for (int y=0; y<height; y++)
for (int x=0; x<width; x+=2) {
f[y][x] = x + y;
f[x+1][y] = x+1 + y;
}
for (int y=0; y<height; y++)
for (int x=0; x<width; x++)
f[y][x] = x + y;
unroll前の等価なC++ソースコード
Unroll後の等価なC++ソースコード
Unroll
タイリング
 Func::tile
– 指定した次元とタイルサイズでループをタイル化する
• データの再利用性を高める
⇒キャッシュやローカルメモリなどを有効活用できる
47
Func f;
Var x, y;
f(x, y) = in(y, x);
f.tile(x, y, xi, yi, 16, 16);
for (int y=0; y<height; y+=16) {
for (int x=0; x<width; x+=16) {
for(int yi = 0; yi < 16; yi++) {
for(int xi = 0; xi < 16; xi++) {
f[y+yi][x+xi] = in[x+xi][y+yi];
}
}
}
}
Tile後の等価なC++ソースコード
スケジューリングの適用例
2次元Convolutionを最適化してみよう
48
2次元Convolutionのスケジュール記述
 Generator インタフェースに従った記述例
– schedule関数内にスケジューリングを記述
49
class conv3x3 : public Generator<conv3x3>{
Var x{"x"}, y{"y"};
public:
Input<Buffer<uint8_t>> src{“src", 2};
Input<Buffer<float>> kernel{"kernel", 2};
Output<Buffer<uint8_t>> dst{"dst", 2};
void generate() {
RDom r(-1, 3, -1, 3, "r");
Func src_ = BoundaryConditions::repeat_edge(src);
dst(x, y) = cast<uint8_t>(sum(cast<float>(src_(x+r.x, y+r.y)) * kernel(r.x+1, r.y+1)));
}
void schedule() {
}
};
スケジューリングを記述
性能評価環境
 評価環境
– CPU: Intel Core i7-5930K
• 周波数: 3.5GHz
• 6コア12スレッド
• AVX2/FMA3搭載(1命令で256-bit幅の積和演算が可能)
– コンパイラ
• GCC-6.3.0 (C++実装用のコンパイラとして使用)
• Halide: Release-2017_10_31 & LLVM-5.0.0
• コンパイルオプション: -O3 –march=native
 2次元Convolution
– 入出力: 2048x2048 8-bitグレースケール画像
– カーネルサイズ: 3x3
– カーネル: Gaussianカーネル
50
アルゴリズム実装のみの性能
66.4
3.61
0
10
20
30
40
50
60
70
①: C++ ②: Halide
ExecutionTime[ms]
51
 Halideで実装するだけで18.4倍の速度向上
– Halide版はLLVMで自動ベクトル化されているのを確認
18.4倍
ループ展開によるレジスタブロッキング
52
 通常のConvolution演算
– 出力1画素の計算当たり
入力9画素のロードが必要
dst src
unroll前の参照範囲
ループ展開によるレジスタブロッキング
53
 通常のConvolution演算
– 出力1画素の計算当たり
入力9画素のロードが必要
 Yの次元でループを展開してみると…?
– 出力2画素の計算当たり
入力12画素のロードで済む
⇒ロード命令の削減が可能!
dst src
dst src
unroll前の参照範囲
unroll後の参照範囲
ループ展開によるレジスタブロッキング
54
スケジューリングの記述
void generate() {
RDom r(-1, 3, -1, 3, "r");
Func src_ = BoundaryConditions::repeat_edge(src);
dst(x, y) =
cast<uint8_t>(sum(cast<float>(src_(x+r.x, y+r.y)) * kernel(r.x+1, r.y+1)));
}
void schedule() {
out.split(y, yo, yi, 4);
out.reorder(yi, x, yo);
out.unroll(yi);
}
Y次元で4展開分のループアンロール
ループアンロール適用後の性能
66.4
3.61 3.17
0
10
20
30
40
50
60
70
①: C++ ②: Halide ③: ②+unroll
ExecutionTime[ms]
55
20.9倍
 ロード命令の削減により1.14倍の速度向上
スレッド並列化
 Yの次元でスレッド並列化を適用
56
void generate() {
RDom r(-1, 3, -1, 3, "r");
Func src_ = BoundaryConditions::repeat_edge(src);
dst(x, y) =
cast<uint8_t>(sum(cast<float>(src_(x+r.x, y+r.y)) * kernel(r.x+1, r.y+1)));
}
void schedule() {
out.split(y, yo, yi, 4);
out.reorder(yi, x, yo);
out.unroll(yi);
out.parallel(yo);
}
Yの次元でスレッド並列化
スレッド並列化適用後の性能
66.4
3.61 3.17 0.52
0
10
20
30
40
50
60
70
①: C++ ②: Halide ③: ②+unroll ④: ③+parallel
ExecutionTime[ms]
57
127.7倍
 スレッド並列化により6.10倍の速度向上
– スケジューリングを4行追加するだけで127.7倍の速度向上
参考情報
公式ページ
– http://halide-lang.org/
リファレンス
– http://halide-lang.org/docs/index.html
チュートリアル
– http://halide-
lang.org/tutorials/tutorial_introduction.html
58
Halideの活用事例の紹介
59
OpenCVのDNNモジュール
60
 DNNモジュールに
Halide実装が追加
– ネットワークごとに各層の
スケジューリングを定義可能
– IntelCPU上でMKLを使用した
場合と同等程度の性能
https://docs.opencv.org/3.3.0/de/d37/tutorial_dnn_halide.html
https://www.slideshare.net/embeddedvision/making-opencv-
code-run-fast-a-presentation-from-intel
NNVM/TVM
61
 NNVM
– 各種DeepLearningフレームワークで記述されたコードを様々な
ターゲットハードウェア向けに最適化・コード生成を行うコンパイラ
 TVM
– テンソル演算用の中間表現スタック
– 中間表現としてHalideのIRを拡張し採用
http://www.tvmlang.org/2017/10/06/nnvm-compiler-announcement.html
http://tvmlang.org
Google Pixel2
62
 Googleが発表したスマートフォン
 画像処理・機械学習用のカスタムSoC
「Pixel Visual Core」を搭載
– 8個のIPU(Image Processing Unit)を搭載
– IPUはTensorFlowとHalideによる開発をサポート
https://techcrunch.com/2017/10/17/googles-first-custom-consumer-chip-is-the-secret-behind-the-pixel-2s-camera-performance
フィックスターズにおける
Halideへの取り組み
© 2017 Fixstars Corp. CONFIDENTIAL
63
Fixstars Solutions, Inc.(米国カリフォルニア州、100%子会社)
株式会社アイ・イー・テック(株式会社SHIFTとの合弁会社、当社出資比率:66%)
Who am I?
© 2017 Fixstars Corp. CONFIDENTIAL
64
フィックスターズとは?
マルチコアプロセッサ/フラッシュメモリを駆使して
大量計算や大量データI/Oの高速処理を実現するソフトウェア・パートナーです
事業内容 マルチコアプロセッサ関連事業
設立 2002年8月
資本金 5億4,996万円 (2016年12月末 現在)
社員数 150名(2017年9月末 現在)
所在地 大崎(本社)、横浜
代表取締役社長 三木 聡
主な取引先 東芝、キヤノン、デンソー、日立製作所、日立ハイテクノロジーズ、オリンパス、みず
ほ証券、 宇宙航空研究開発機構など
連結子会社
本社:品川区大崎
Halide to FPGA(Beta)のサイトオープン
© 2017 Fixstars Corp. CONFIDENTIAL
65
10月18日にサイトをオープンしています
Halide to FPGA 概要
GPUCPU FPGA
GenesisコンパイラがHalide ベースのプログラムコードを
FPGA向けのコードへと変換します
Halideアプリケーション
プログラム
Genesis コンパイラ
© 2017 Fixstars Corp. CONFIDENTIAL
66
FPGA?
67
FPGA:Field Programmable Gate Array
製造後に購入者や設計者が構成を設定できる
集積回路
従来ASICと比較して低速でエネルギー効率が
悪いというデメリットがあったが、製造ルー
ル微細化の最先端を走り、ギャップは縮まり
つつある
高い並列性のあるアルゴリズムの
実装に適している
コンパイルフロー
68
Bitstream
Netlist
RTL
Vivado HLS C/C++
Halide
高位合成ツール
Genesisコンパイラ
論理合成/配置配線/
タイミング検証ツール
© 2017 Fixstars Corp. CONFIDENTIAL
ターゲットハードウェアで
一般的なEDAツールを
使用する
従来は手動で行っていた
アルゴリズムから
量産用実装への変換を
Genesisコンパイラで置き換える
RTL生成部は
高位合成ツールに任せる
ありがとうございました
69

Halide による画像処理プログラミング入門