Halideでつくる
Domain Specific Architectureの世界
Takuro Iizuka
Fixstars Solutions Inc.
名前: 飯塚 拓郎
所属: Fixstars Solutions Inc.(カリフォルニア州アーバイン)
普段はこの辺にいます
ベイエリア、
いわゆるシリコンバレーはここ
名前: 飯塚 拓郎
所属: Fixstars Solutions Inc.(カリフォルニア州アーバイン)
直線距離で600km弱
東京↔大阪くらいある
実際、大体まっすぐに行ける
日本 北米
Fixstars Solutions Inc.
金融
Option pricing
General Batch
Processing
Inspection Machine
Predictive
Maintenance
Imaging Device
Genomics
ADAS
Autopilot System
Imaging Device
Genomics
ADAS
Autopilot System
基盤技術R&D
I/O Acceleration
• 最適化ファームウェア
• NAND Flash, SSD
CPU Acceleration
• 並列コンピューティング
• x86, POWER, ARM, GPU, FPGA
共同開発
フィードバック
マーケティングリサーチ
システム最適化の原則:
いかに優れた部分最適も
全体最適には勝てない
ハードウェア
オペレーティング・システム
ドライバ
ライブラリ
アプリケーション
ハードウェア
オペレーティング・システム
ドライバ
ライブラリ
アプリケーション最適化!
最適化!
最適化!
最適化!
最適化!
ハードウェア
オペレーティング・システム
ドライバ
ライブラリ
アプリケーション最適化!
最適化!
最適化!
最適化!
最適化!
全体として最適か?
ハードウェア
ソフトウェア
モジュール A
最適化!
ソフトウェア
モジュール B
ソフトウェア
モジュール X
最適化! 最適化!
ハードウェア
ソフトウェア
モジュール A
最適化!
ソフトウェア
モジュール B
ソフトウェア
モジュール X
最適化! 最適化!
全体として最適か?
Q: では、レイヤを超えて
システムを再構築すれば良いのか?
A: Yes でもあり No でもある
問題は, 今時の情報システムが複雑すぎること
境界を超えるための技術
設計
ハードウェア
オペレーティング・システム
ドライバ
ライブラリ
アプリケーション
職人プログラマによって再構築、最適化
ドメイン特化言語による
高次アルゴリズム記述
ハードウェア
オペレーティング・システム
ドライバ
ライブラリ
アプリケーション
ドメインに特化したシステムアーキテクチャをコンパイラによって自動生成する
これまで
これから
Domain Specific Architecture
Halide to FPGA
Halideとは
è 画像処理の高性能計算に特化した
オープンソースのDSL
– http://halide-lang.org/
è 特徴
– 簡潔なアルゴリズム記述
– C++の内部DSLとして実装
– マルチプラットフォーム対応
– アルゴリズムとスケジューリングの分離
アルゴリズムとスケジューリングの分離
è アルゴリズム
– 計算部の本質的な処理のみを記述
– ハードウェアによらず単一の記述でよい
è スケジューリング
– 計算順序やデータの保持の仕方を記述
– 並列化やループ変形などの最適化もここで記述
– ハードウェアごとに異なるスケジュールを指定可能
これにより、最適化にまつわる
Try&Errorの時間を劇的に削減できる
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++によるナイーブなコード
C++によるx86向け最適化コード
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++によるx86向け最適化コード
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によるアルゴリズム記述
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;
}
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によるアルゴリズム記述
アルゴリズム記述部
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;
}
HalideによるCPU向け最適化コード
アルゴリズム記述部
スケジューリング記述部
Halideのコンパイルフロー
Halide
Compiler
) .
E B
- /
-
) A .
(C +
Halide FPGA Backendのコンパイルフロー
Halide
Compiler
+ B B
A B
BH
IJ . F B
.. /
.. /
A B
+ C B B
BH(G -
A B
BH
+.
E +. E
B
新規拡張
)
+ F
C
E
+/ C
E
+
E
Halide FPGA Backendの内部
Func filter_A(Func in) {
<...>
return f;
}
F
+
A
A D
アルゴリズム記述方法
関数の定義
è アルゴリズムは関数として定義される
– 関数: Halide::Func
– 誘導変数: Halide::Var
Halideソースコード 等価なC++ソースコード
意味: 関数 f は定義域 x, y に対して x+y を値域に持つ
名前空間Halide::は以下省略
Func f;
Var x, y;
f(x, y) = x + y;
for (int y=0; y<height; y++) {
for (int x=0; x<width; x++) {
f[y][x] = x + y;
}
}
関数の次元数
è 最大4次元までの関数を定義可能
f は3次元の関数(e.g. 3D空間、カラー画像)
Func f;
Var c, x, y;
f(c, x, y) = c + x + y;
関数の次元数
è 最大4次元までの関数を定義可能
f は3次元の関数(e.g. 3D空間、カラー画像)
f は4次元の関数
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;
関数の参照
è 関数の右辺に定義された関数への参照
– 関数合成が可能
意味: 関数 f2 は定義域 x, y に対して f1(x+y) を値域に持つ
Var x, y;
Func f1;
f1(x, y) = x + y;
Func f2;
f2(x, y) = f1(x, y);
Var x, y;
Func f2;
f2(x, y) = x + y;
式
è 式はExpr型の変数として表される
– 関数: Func
– 誘導変数: Var
– 式: Expr
組み立てた計算式を、式としてとっておくことが出来る
Var x, y;
Expr e = x + y;
Func f;
f(x, y) = e;
Var x, y;
Func f;
f(x, y) = x + y;
RDomと畳み込み関数
è 畳み込みを表すためのプリミティブ
– リダクションドメイン: RDom
– RDom(min, extent)
• 指定された次元で[min, min+extent-1]の領域を畳み込み
等価なC++ソースコード
rxは[-1, 1]を動く誘導変数
Var x;
Func f;
f(x) = x;
RDom rx(-1, 3);
Func g;
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;
}
+
FPGA Backend が生成する
ハードウェアアーキテクチャ
FPGAバックエンドのアーキテクチャ生成戦略
è 基本はデータフローアーキテクチャを生成
– モジュール間は可能なかぎりストリーム化
– モジュール内のループは完全にパイプライン化
f
×
g
+
h
+
pipeline
DMA
DMA
in
p
Halide FPGA Backend
Stream
Stream
Stream
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;
}
FPGAバックエンドのアーキテクチャ生成戦略
è 3x3の畳み込みの場合
+
in_buffer f_buffer
in_port f_port
■ Shift Regs
■ Block RAM
等価なC++プログラム
=
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;
}
for(y=0; y<height; y++)
for(x=0; x<width; x++) {
f[y][x]=0;
for(ry=-kh/2; ry<kh/2+1; ry++)
for(rx=-kw/2; rx<kw/2+1; rx++)
f[y][x]+=in[y+ry][x+rx];
}
FPGAバックエンドのアーキテクチャ生成戦略
+
in_buffer f_buffer
in_port f_port
等価なC++プログラム
=
■ 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;
}
for(y=0; y<height; y++)
for(x=0; x<width; x++) {
f[y][x]=0;
for(ry=-kh/2; ry<kh/2+1; ry++)
for(rx=-kw/2; rx<kw/2+1; rx++)
f[y][x]+=in[y+ry][x+rx];
}
FPGAバックエンドのアーキテクチャ生成戦略
+
in_buffer f_buffer
in_port f_port
■ Shift Regs
■ Block RAM
等価なC++プログラム
=
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;
}
for(y=0; y<height; y++)
for(x=0; x<width; x++) {
f[y][x]=0;
for(ry=-kh/2; ry<kh/2+1; ry++)
for(rx=-kw/2; rx<kw/2+1; rx++)
f[y][x]+=in[y+ry][x+rx];
}
FPGAバックエンドのアーキテクチャ生成戦略
+
in_buffer f_buffer
in_port f_port
■ Shift Regs
■ Block RAM
等価なC++プログラム
=
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;
}
for(y=0; y<height; y++)
for(x=0; x<width; x++) {
f[y][x]=0;
for(ry=-kh/2; ry<kh/2+1; ry++)
for(rx=-kw/2; rx<kw/2+1; rx++)
f[y][x]+=in[y+ry][x+rx];
}
疑問
FPGA規模にあわせて
スループットを変えたいんだけど?
ストリームが必ずしも最適なアーキ
じゃない場合もあるけど?
特定のパラメータをレジスタに
マッピングしたいんだけど?
データをAXI4で入れたいんだけど?
レイテンシ削減のために
モジュールを統合したいんだけど?
疑問
スケジューリングによるカスタムアーキテクチャ
è HalideスケジューリングAPIを使用して、計算の意
味を変えずにコードを変形することができる
– 計算タイミングの指定
– ループ変形
– etc…
重要:スケジューリングによる効果はターゲットハードによって異なる
è FPGAバックエンドの場合
– ハードウェアアーキテクチャを
要求に応じてチューニングできる
• 演算器の数
• I/Oバス幅
• I/Oインタフェースの種類
計算タイミングの指定
è Func::compute_inline
– Funcをインライン展開する
• スケジューリング無指定の場合のデフォルトの挙動
C++
Var x, y;
Func blur_x;
blur_x(x, y) = in(x, y) + in(x+1, y);
Func blur_y;
blur_y(x, y) = (blur_x(x, y) +
blur_x(x, y+1)) / 4;
Var x, y;
Func blur_y;
blur_y(x, y) = (in(x, y) +
in(x+1, y) +
in(x, y+1) +
in(x+1, y+1)) / 4;
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][x] +
blur_x[y+1][x] + blur_x[y+1][x]) / 4;
}
}
計算タイミングの指定
è Func::compute_inline
– モジュールの統合
h
×
+
+
pipeline
DMA
DMA
in
p
Halide FPGA Backend
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_inline();
g.compute_inline();
h.compute_root();
return h;
}
計算タイミングの指定
è Func::compute_root
– ルートレベル(最外レベル)で当該Funcの計算を行う
C++
Var x, y;
Func blur_x;
blur_x(x, y) = in(x, y) + in(x+1, y);
Func blur_y;
blur_y(x, y) = (blur_x(x, y) +
blur_x(x, y+1)) / 4;
blur_x.compute_root();
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_root
– モジュールの分割
f
×
g
+
h
+
pipeline
DMA
DMA
in
p
Halide FPGA Backend
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;
}
ループアンロール
è Func::unroll
– 指定した次元に対してループ展開を行う
unroll C++
Var x, y;
Func f;
f(x, y) = x + y;
for (int y=0; y<height; y++)
for (int x=0; x<width; x++)
f[y][x] = x + y;
ループアンロール
è Func::unroll
– 指定した次元に対してループ展開を行う
Unroll
Var x, y;
Func f;
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;
}
Unroll C++
unroll C++
for (int y=0; y<height; y++)
for (int x=0; x<width; x++)
f[y][x] = x + y;
ループアンロール
è Func::unroll
– 演算スループットの向上
h
×
+
+
pipeline
DMA
DMA
in
p
×
+
+
Halide FPGA BackendFunc 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);
h.compute_root()
.unroll(x, 2);
return h;
}
ループアンロール+バースト長の指定
è Func::hls_burst
– I/Oのバースト長を調整する
h
×
+
+
pipeline
DMA
DMA
in
p
×
+
+
Halide FPGA Backend
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);
in.hls_burst(x, 2);
h.compute_root()
.unroll(x, 2)
.hls_burst(x, 2);
return h;
} !
/
CNNに適用してみよう
Convolutional Neural Network
è 画像認識に頻繁に使用されるニューラルネットワーク
– 前段にConvolution層/Pooling層を重ねるのが特徴
– 後段で全結合層による分類や回帰を行う
è 推論時のボトルネック
– Convoluton層: 演算数が膨大
– FC層: 重みの容量が膨大
Y. LeCun, L. Bottou, Y. Bengio, and P. Haffner. Gradient-based learning
applied to document recognition. Proceedings of the IEEE, november 1998.
FPGA向けアルゴリズム最適化: XNOR-Net[1]
è Conv層の入力 Iと重み W を二値化
– ラインバッファ・重みの容量を1/32に削減
– 積和がXNORとpopcountで演算可能
! ∗ # ≈ ( sign(!) ⊛ sign(# ) ○ -.
∗ : Convolution operator
⊛ : XNOR and popcount operator
○ : Element-wise product operator
-.: Scaling factor
+ - -
- + -
+ - +
- + -
+ + -
- + +
0 1 0
1 1 0
0 1 1
1 0 0
0 1 0
1 0 1
× 4.∗ ⊛
! # sign(!) sign(#)
binarize
[1] Rastegari, M. et al., “XNOR-Net: ImageNet Classification Using Binary Convolutional Neural Networks”, https://arxiv.org/abs/1603.05279
FPGA向けアルゴリズム最適化: Log-based Quantization[2]
è初段・最終段のConv/FC層
– 二値化すると精度の低下が激しい
è初段・最終段のConv/FC層の入力と重みを
対数量子化
– ラインバッファ・重みの容量を削減
– 積算がシフト演算で計算可能
• 今回はlog2-baseで量子化
Dataset Network
Accuracy
fixed-32 2-base log2-base
MNIST LeNet-5 98% 95% 98%
CIFAR-10 NIN 89% 66% 82%
[2] Miyashita, D. et al., “Convolutional Neural Networks using Logarithmic Data Representation”, https://arxiv.org/abs/1603.01025
Halide DSLによる量子化MNIST実装
è 各層は適当に融合してモジュール化
– 低レイテンシ化
– 省リソース化
– パフォーマンスとモジュラリティの両立
è モジュール間はコンパイラの最適化によりストリーム化
– シーケンシャルアクセスになるよう
各モジュール内部でウィンドウレジスタ+ラインバッファでバッファリング
Norm&Quant Conv BN ReLU Pooling BN
Conv ReLU Pooling
Scale Active
BN Scale Active FC ReLU FC
M0 M1 M2 M3 M4 M5 M6
Halide DSLによる量子化MNIST実装
è パイプライン全体のスループットは?
– モジュールの演算スループット、モジュール間のI/Oス
ループットのもっとも低い部分に引っ張られる
M0
CU
CU
CU
M1
CU
CU
CU
M2
CU
CU
CU
M3
CU
CU
CU
M4
CU
CU
CU
M5
CU
CU
CU
M6
CU
CU
CU
CU=Computing Unit
Halide DSLによる量子化MNIST実装
è 量子化MNISTの場合(バッチサイズ=1)
– デフォルト(スループット未調整)の状態では、
第2層目のConvolutionの演算(M3)がボトルネック
M0
CU
M1
CU
M2
CU
M3
CU
M4
CU
M5
CU
M6
CU
28x28
=784
5x5x20x28x28
=392000
2x2x20x24x24
=46080
20x5x5x50x12x12
=3600000
2x2x50x8x8
=12800
50x4x4x500
=400000
500x10
=5000
3600000cycle=40FPS(150MHz)
(単位:iteration)
Halide DSLによる量子化MNIST実装
è 量子化MNISTの場合(バッチサイズ=1)
– 畳み込みの入力チャネル層を2unrollすると
全体性能も倍になる
M0
CU
M1
CU
M2
CU
M3 M4
CU
M5
CU
M6
CU
28x28
=784
5x5x20x28x28
=392000
2x2x20x24x24
=46080
10x5x5x50x12x12
=1800000
2x2x50x8x8
=12800
50x4x4x500
=400000
500x10
=5000
CU
CU
conv2_sum.update().unroll(rc, 2);
1800000cycle=80FPS(150MHz)
(単位:iteration)
Halide DSLによる量子化MNIST実装
unroll数 リソース 性能
conv1 conv2 fc3 pool1 pool2 LUT BRAM DSP FPS
#1 0 0 0 0 0 27830 60 100 40
#2 0 10 0 0 0 27491 60 101 400
#3 5 100 5 0 0 28731 70 95 1875
#4 25 500 800 4 2 35403 64 86 9421
スケジューリングにより推論性能を200倍以上に向上
必要に応じて
FC層やPooling層もアンロール
リソース消費も変化 スループット向上
まとめ
HalideのFPGAバックエンドを構築することで…
最小限の努力でハードウェアの構築と動作が可能に
• 簡潔なアルゴリズムの記述により、実装工数を大幅削減
• スケジューリングによりアーキテクチャを一瞬でカスタム
ポータビリティを担保できる
• CPUでアルゴリズム開発・テストののちに
FPGAへ実装、などの柔軟な開発ワークフローが可能
• 今はGPUだが将来的にはハード化したいなどの要求にも対応
ますます加速するヘテロジニアス化への対応
• CPU+GPU+FPGAといったヘテロ構成のシステムを
同一の言語内でデザインできる
適応領域
è 自動運転システム
– Nexty Electronics との合弁会社、
Fixstars Autonomous Technologiesにおける基盤技術
– 低レイテンシ/低消費電力でのセンシング、認識処理、異常検知
è メディカルイメージング
– 北米における産学共同研究プロジェクト
– CT/MRI, ブレインマッピング等
– 超大規模ボリュームデータのストリーミング処理
https://www.halide2fpga.com
Halideで実装されたFPGAアプリケーションのショウケースを公開しています
https://github.com/fixstars/Halide-elements
Halideで実装された様々なアルゴリズムをMIT Licenseの下GitHub上で公開しています
http://www.fixstars.com/recruit/
Fixstarsでは優秀なエンジニアを募集しています(インターンシップも随時募集中)
HalideでつくるDomain Specific Architectureの世界

HalideでつくるDomain Specific Architectureの世界