Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

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

4,564 views

Published on

2017/11/16 に開催されました、「画像処理を簡単に高速化してみませんか!?」 Halideによる画像処理プログラミング入門で使用したスライドです。

https://career.levtech.jp/hikalab/event/detail/140/

Published in: Software
  • Be the first to comment

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

  1. 1. Halideによる画像処理プログラミング入門 Fixstars Corp. 丸岡 晃 近村 啓史 0
  2. 2. 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
  3. 3. 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/
  4. 4. 本日の内容  画像認識の高速化・低消費電力化の需要  ムーアの法則の終焉 – ハードウェアが勝手に速くなる時代は終わった – ハードウェアを効率良く使えるソフトウェア開発が必要  画像処理プログラミング言語『Halide』の紹介 – 速く処理出来て – 様々なハードウェアで動かせるプログラムが – 短期間で簡単に開発出来る! 3
  5. 5. アジェンダ Halide概要 アルゴリズム記述 スケジューリング記述 Halideの活用事例の紹介 フィックスターズにおける取り組みの紹介 4
  6. 6. Halide概要 5
  7. 7. Halideとは 画像処理の高性能計算に特化したDSL (DSL: Domain Specific Language) – http://halide-lang.org/ – C++の内部DSLとして提供 特徴 – アルゴリズムとスケジューリングの分離 – マルチプラットフォーム対応 • x86, ARM, PowerPC, NVIDIA GPGPUなど に対するコード生成が可能 6
  8. 8. アルゴリズムとスケジューリングの分離  アルゴリズム – 計算部の本質的な処理のみを記述 – ハードウェアによらず単一の記述でよい  スケジュール – 計算順序やデータの保持の仕方を記述 – 並列化やループ変形などの最適化もここで記述 – ハードウェアごとに異なるスケジュールを指定可能 7 アルゴリズムとスケジュールの分離が可能
  9. 9. 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; }
  10. 10. 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); } } } } }
  11. 11. 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); } } } } }
  12. 12. 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; }
  13. 13. 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; } アルゴリズム記述部 アルゴリズムは本質的な処理の内容だけを記述
  14. 14. 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; } 少ないコード量と期間で簡単に最適化が可能 スケジューリング記述部 アルゴリズムは変更なし
  15. 15. マルチプラットフォーム向けの最適化 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用のスケジューリング記述
  16. 16. 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
  17. 17. 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で記述された ソースコードをコンパイル
  18. 18. 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コンパイラによる コンパイルを実行
  19. 19. 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コンパイル)
  20. 20. 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バックエンドによる コード生成も対応
  21. 21. アルゴリズム記述 20
  22. 22. 関数の定義 アルゴリズムは純粋関数として定義される – 関数: 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で書かれたソースコード
  23. 23. 関数の次元数 最大4次元までの関数を定義可能 22 Func f; Var c, x, y; f(c, x, y) = c + x + y; fは3次元の関数 (e.g. 3D空間, カラー画像)
  24. 24. 関数の次元数 最大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次元の関数
  25. 25. 関数の参照 関数の右辺に定義された関数を記述 右図の定義と同じ意味(合成関数) 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) を値域に持つ 等価
  26. 26. 式 式は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; 等価
  27. 27. 即値 C++の数値リテラルは式の定義に使用可 – 暗黙的型変換によりExpr型にキャストされる 26 意味: 関数 f はどのような定義域に対しても値域 1 を持つ Func f; Var x, y; f(x, y) = 1;
  28. 28. 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++ソースコード
  29. 29. 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++ソースコード
  30. 30. 領域外アクセス時の参照値設定  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 を返す
  31. 31. アルゴリズムの記述例 2次元Convolutionを実装してみよう 30
  32. 32. 2次元Convolution 31 𝐷𝑠𝑡 𝑥, 𝑦 = 𝑘𝑦=0 𝑘ℎ 𝑘𝑥=0 𝑘𝑤 𝑆𝑟𝑐 𝑥 + 𝑘𝑥 − 𝑘𝑤 2 , 𝑦 + 𝑘𝑦 − 𝑘ℎ 2 × 𝐾𝑒𝑟𝑛𝑒𝑙(𝑘𝑥, 𝑘𝑦)  2次元画像の畳み込み処理 – 画像処理において頻繁に用いられる処理 = ∗
  33. 33. 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 ぼかし処理 エッジ検出
  34. 34. 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.
  35. 35. 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; } } }
  36. 36. 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)
  37. 37. 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を登録
  38. 38. スケジューリング記述 37
  39. 39. スケジューリングとは ハードウェアの特性に応じて、 性能を向上させるための様々な指定 – 計算タイミングの指定 – スレッド並列化 – ベクトル化 – ループ変形 – etc… スケジューリングはターゲットハードウェア によって達成される効果が異なります – 今回はCPUの場合について紹介します 38
  40. 40. 計算タイミングの指定  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;
  41. 41. 計算タイミングの指定  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; } }
  42. 42. 計算タイミングの指定  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);
  43. 43. スレッド並列化  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スレッドで並列に処理される
  44. 44. ベクトル化  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要素ずつ演算される
  45. 45. ベクトル化  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
  46. 46. ループアンロール  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;
  47. 47. ループアンロール  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
  48. 48. タイリング  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++ソースコード
  49. 49. スケジューリングの適用例 2次元Convolutionを最適化してみよう 48
  50. 50. 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() { } }; スケジューリングを記述
  51. 51. 性能評価環境  評価環境 – 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
  52. 52. アルゴリズム実装のみの性能 66.4 3.61 0 10 20 30 40 50 60 70 ①: C++ ②: Halide ExecutionTime[ms] 51  Halideで実装するだけで18.4倍の速度向上 – Halide版はLLVMで自動ベクトル化されているのを確認 18.4倍
  53. 53. ループ展開によるレジスタブロッキング 52  通常のConvolution演算 – 出力1画素の計算当たり 入力9画素のロードが必要 dst src unroll前の参照範囲
  54. 54. ループ展開によるレジスタブロッキング 53  通常のConvolution演算 – 出力1画素の計算当たり 入力9画素のロードが必要  Yの次元でループを展開してみると…? – 出力2画素の計算当たり 入力12画素のロードで済む ⇒ロード命令の削減が可能! dst src dst src unroll前の参照範囲 unroll後の参照範囲
  55. 55. ループ展開によるレジスタブロッキング 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展開分のループアンロール
  56. 56. ループアンロール適用後の性能 66.4 3.61 3.17 0 10 20 30 40 50 60 70 ①: C++ ②: Halide ③: ②+unroll ExecutionTime[ms] 55 20.9倍  ロード命令の削減により1.14倍の速度向上
  57. 57. スレッド並列化  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の次元でスレッド並列化
  58. 58. スレッド並列化適用後の性能 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倍の速度向上
  59. 59. 参考情報 公式ページ – http://halide-lang.org/ リファレンス – http://halide-lang.org/docs/index.html チュートリアル – http://halide- lang.org/tutorials/tutorial_introduction.html 58
  60. 60. Halideの活用事例の紹介 59
  61. 61. 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
  62. 62. NNVM/TVM 61  NNVM – 各種DeepLearningフレームワークで記述されたコードを様々な ターゲットハードウェア向けに最適化・コード生成を行うコンパイラ  TVM – テンソル演算用の中間表現スタック – 中間表現としてHalideのIRを拡張し採用 http://www.tvmlang.org/2017/10/06/nnvm-compiler-announcement.html http://tvmlang.org
  63. 63. 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
  64. 64. フィックスターズにおける Halideへの取り組み © 2017 Fixstars Corp. CONFIDENTIAL 63
  65. 65. 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月末 現在) 所在地 大崎(本社)、横浜 代表取締役社長 三木 聡 主な取引先 東芝、キヤノン、デンソー、日立製作所、日立ハイテクノロジーズ、オリンパス、みず ほ証券、 宇宙航空研究開発機構など 連結子会社 本社:品川区大崎
  66. 66. Halide to FPGA(Beta)のサイトオープン © 2017 Fixstars Corp. CONFIDENTIAL 65 10月18日にサイトをオープンしています
  67. 67. Halide to FPGA 概要 GPUCPU FPGA GenesisコンパイラがHalide ベースのプログラムコードを FPGA向けのコードへと変換します Halideアプリケーション プログラム Genesis コンパイラ © 2017 Fixstars Corp. CONFIDENTIAL 66
  68. 68. FPGA? 67 FPGA:Field Programmable Gate Array 製造後に購入者や設計者が構成を設定できる 集積回路 従来ASICと比較して低速でエネルギー効率が 悪いというデメリットがあったが、製造ルー ル微細化の最先端を走り、ギャップは縮まり つつある 高い並列性のあるアルゴリズムの 実装に適している
  69. 69. コンパイルフロー 68 Bitstream Netlist RTL Vivado HLS C/C++ Halide 高位合成ツール Genesisコンパイラ 論理合成/配置配線/ タイミング検証ツール © 2017 Fixstars Corp. CONFIDENTIAL ターゲットハードウェアで 一般的なEDAツールを 使用する 従来は手動で行っていた アルゴリズムから 量産用実装への変換を Genesisコンパイラで置き換える RTL生成部は 高位合成ツールに任せる
  70. 70. ありがとうございました 69

×