前言
前幾天又用到了TVM媚狰,想著是否能夠間接替代TensorRT作為GPU服務(wù)器部署的一個后端。TensorRT在自家的GPU上確實(shí)強(qiáng)大阔拳,也是老黃家重點(diǎn)關(guān)照的”開源項(xiàng)目“崭孤。但TensorRT還是有一點(diǎn)點(diǎn)小缺點(diǎn)的:
- 經(jīng)過infer優(yōu)化后的模型與特定GPU綁定,例如在1080TI上生成的模型在2080TI上無法使用
- 高版本的TensorRT依賴于高版本的CUDA版本糊肠,而高版本的CUDA版本依賴于高版本的驅(qū)動
- TensorRT盡管好用辨宠,但推理優(yōu)化infer還是閉源的,像深度學(xué)習(xí)煉丹一樣货裹,也像個黑盒子嗤形,導(dǎo)入模型我們不確定能否順利轉(zhuǎn)化成功。如果不成功弧圆,不能直接修改源碼赋兵,總感覺缺點(diǎn)什么
以上缺點(diǎn)單純吐槽(輕噴輕噴)笔咽,其實(shí)這些問題都不是大問題,如果是自己搗鼓的話問題不大霹期。但是如果放到生產(chǎn)環(huán)境中叶组,上面的一些情況可能會造成一些小麻煩(害~畢竟生產(chǎn)環(huán)境的條條框框太多了),用起來不是那么得心應(yīng)手历造。
不知道什么是TVM的甩十,可以知乎搜一下,也可以看一下我之前寫的兩篇文章:
不過本文不是討論TVM吭产,除了上述兩文枣氧,也寫了另外一些TVM的分析(算是第三篇第四篇),但自己懶加上時間有時候不夠用就沒有整理垮刹,等等整理好再發(fā)。本文其實(shí)是想介紹下自己之前寫的Halide
一文张弛,與TVM相關(guān)荒典,借此機(jī)會引入一哈。
什么是Halide
Halide是用C++作為宿主語言的一個圖像處理相關(guān)的DSL(Domain Specified Language)語言吞鸭,全程領(lǐng)域?qū)S谜Z言寺董。主要的作用為在軟硬層面上(與算法本身的設(shè)計無關(guān))實(shí)現(xiàn)對算法的底層加速,感覺有必要對其有一定的了解刻剥。因?yàn)椴徽撌?strong>傳統(tǒng)的圖像處理方法亦或是深度學(xué)習(xí)應(yīng)用都或多或少使用到了halide的思想遮咖。
其中,在OpenCV(傳統(tǒng)圖像處理庫)中部分算法使用了Halide后端造虏,而TVM(神經(jīng)網(wǎng)絡(luò)編譯器)也是用了Halide的思想去優(yōu)化神經(jīng)網(wǎng)絡(luò)算子御吞。
那么Halide到底是干嘛用的,看上面那張圖漓藕,同樣的一個算法處理(局部拉普拉斯變換)陶珠,使用直接的C++語言寫出來算法速度很慢,Adobe公司使用3個月對這個算法進(jìn)行了優(yōu)化(手工優(yōu)化)使這個算法的速度快了10倍享钞,但是如果你使用了Halide揍诽,只需要幾行代碼,就可以使這個算法比之前普通直接的算法快上20倍栗竖。
一句話來說暑脆,Halide大大節(jié)省了我們手動優(yōu)化底層算法的時間,讓我們只需要關(guān)注算法的設(shè)計狐肢。
而算法與底層優(yōu)化的的過程添吗,Halide幫咱們做了。
Halide為什么可以優(yōu)化算法
Halide的特點(diǎn)是其圖像算法的計算的實(shí)現(xiàn)(Function和Expression)和這些計算在計算硬件單元上的調(diào)度(Schedule)是分離的处坪,其調(diào)度以Function為單位根资。最終將整個圖像算法轉(zhuǎn)換為高效率的多層for循環(huán)架专,for循環(huán)的分部數(shù)據(jù)范圍劃分和數(shù)據(jù)加載都是由Halide來完成的,而且可以實(shí)現(xiàn)數(shù)據(jù)的加載和算法計算的Overlay玄帕,掩蓋數(shù)據(jù)加載導(dǎo)致的延遲部脚。Halide的Schedule可以由程序員來指定一些策略,指定硬件的buffer大小裤纹,緩沖線的相關(guān)設(shè)置委刘,這樣可以根據(jù)不同的計算硬件的特性來實(shí)現(xiàn)高效率的計算單元的調(diào)度,而圖像算法的計算實(shí)現(xiàn)卻不需要修改鹰椒。
上面這部分截取于知乎:https://www.zhihu.com/question/294625837/answer/496218375
決定算法在某個硬件平臺上執(zhí)行時性能的“三角力量”如下锡移。
其中,算法本身的設(shè)計是一方面漆际,一個好的算法往往效率會高很多淆珊。而另外一個方面就是算法中計算順序的組織,而Halide可以改變的就是我們算法在某個硬件平臺上的計算順序:
其中Halide可以在硬件平臺上為算法實(shí)現(xiàn)并行和良好的緩存一致性:
舉個例子
我們以Halide中的經(jīng)典模糊化(blurred)圖像的例子來演示一下(以下代碼也可以在自己的電腦上測試觀察結(jié)果)奸汇,這里用OpenCV來對圖像進(jìn)行操作進(jìn)行演示:
首先我們設(shè)計一個可以對圖像進(jìn)行模糊的操作函數(shù):
// in為輸入原始圖像 blury為輸出模糊后的圖像
void box_filter_3x3(const Mat &in, Mat &blury)
{
Mat blurx(in.size(), in.type());
for(int x = 1; x < in.cols-1; x ++)
for(int y = 0 ; y < in.rows; y ++)
blurx.at<uint8_t >(y, x) = static_cast<uint8_t>(
(in.at<uint8_t >(y, x-1) + in.at<uint8_t >(y, x) + in.at<uint8_t >(y, x+1)) / 3);
for(int x = 0; x < in.cols; x ++)
for(int y = 1 ; y < in.rows-1; y ++)
blury.at<uint8_t >(y, x) = static_cast<uint8_t>(
(blurx.at<uint8_t >(y-1, x) + blurx.at<uint8_t >(y, x) + blurx.at<uint8_t >(y+1, x)) / 3);
}
對圖像模糊操作很簡單施符,我們首先在x軸上對每個像素點(diǎn)以及周圍的兩個點(diǎn)進(jìn)行求和平均,然后再到y(tǒng)軸上進(jìn)行同樣的操作擂找,這樣相當(dāng)于一個3x3平均卷積核對整個圖像進(jìn)行操作戳吝,這里就不進(jìn)行詳細(xì)描述了。
我們準(zhǔn)備一張(1920,1080)的圖像贯涎,對其進(jìn)行100次上述操作听哭,并記錄時間,發(fā)現(xiàn)Time used:4521.72 ms
塘雳。
然后我們簡單改變一下執(zhí)行次序陆盘,將上述循環(huán)嵌套中的x和y的順序改變一下:
Mat blurx(in.size(), in.type());
// 這里進(jìn)行了嵌套的變換
for(int y = 0 ; y < in.rows; y ++)
for(int x = 1; x < in.cols-1; x ++)
blurx.at<uint8_t >(y, x) = static_cast<uint8_t>(
(in.at<uint8_t >(y, x-1) + in.at<uint8_t >(y, x) + in.at<uint8_t >(y, x+1)) / 3);
// 這里進(jìn)行了嵌套的變換
for(int y = 1 ; y < in.rows-1; y ++)
for(int x = 0; x < in.cols; x ++)
blury.at<uint8_t >(y, x) = static_cast<uint8_t>(
(blurx.at<uint8_t >(y-1, x) + blurx.at<uint8_t >(y, x) + blurx.at<uint8_t >(y+1, x)) / 3);
}
同樣,我們執(zhí)行100次并記錄時間:發(fā)現(xiàn)Time used:3992.35 ms
粉捻,可以發(fā)現(xiàn)下面的模糊操作執(zhí)行的速度比上面的快一些礁遣。當(dāng)然大家可能會想,這也沒快多少啊肩刃。當(dāng)然這只是一副示例圖像祟霍, 如果這張圖像的長寬差距比較大(例如1:10)、亦或是我們要某一個時刻處理幾萬次這樣的操作盈包,一旦量級起來沸呐,那么這兩者的差距就不是一點(diǎn)半點(diǎn)了。
硬件層面的原理
為什么會這樣呢呢燥,上述兩種操作執(zhí)行的算法功能是一樣的崭添,但是速度為什么會有差別。究其原因叛氨,這差別和算法本身沒什么關(guān)系呼渣,而與硬件的設(shè)計是有巨大關(guān)系棘伴,例如并行性和局部性。
我們可以看到下面是Adobe工程師對上述的算法在硬件層面上極致優(yōu)化結(jié)果屁置,比之前的算法快了10倍焊夸,其中用到了SIMD(單指令多數(shù)據(jù)流)、以及平鋪(Tiling)蓝角、展開(Unrolling)和向量化(Vectorization)等常用技術(shù)阱穗。充分利用了硬件的性能,從而不改變算法本身設(shè)計的前提下最大化提升程序執(zhí)行的速度使鹅。
官方示例
Halide作為一個DSL揪阶,我們很容易就可以使用它,這里我們將其源碼下下來并進(jìn)行編譯患朱。完成之后我們就可以使用它了(這里省略編譯步驟鲁僚,可自行在官網(wǎng)查閱):
首先我們引用Halide頭文件以及其他的文件。
#include "Halide.h"
#include <stdio.h>
#include <algorithm>
using namespace Halide;
初次使用Halide之前裁厅,首先需要知道halide中的一些語法:
然后我們利用Halide定義兩個變量蕴茴,這兩個變量單獨(dú)使用時沒有任何意義,同時我們用字符串x
和y
為兩個變量起了名字:
Var x("x"), y("y");
然后利用Func 定義一個待執(zhí)行的function姐直,并起名為gradient
。
Func gradient("gradient");
這時我們定義function中每個點(diǎn)的執(zhí)行邏輯蒋畜,對于(x,y)
這個點(diǎn)執(zhí)行的邏輯為x + y
声畏。
其中x和y都是Var,而x + y
這個操作在賦予給gradient的時候會自動轉(zhuǎn)化為Expr類型姻成,這里可以理解為將x + y
這個代數(shù)表達(dá)式的邏輯賦予了gradient
插龄,最后,通過realize
函數(shù)來執(zhí)行整個邏輯:
gradient(x, y) = x + y;
// realize 即為實(shí)現(xiàn)這個操作 到了這一步才會對上述的操作進(jìn)行編譯并執(zhí)行
Buffer<int> output = gradient.realize(4, 4);
這個邏輯我們用C++來表示即為:
for (int y = 0; y < 4; y++) {
for (int x = 0; x < 4; x++) {
printf("Evaluating at x = %d, y = %d: %d\n", x, y, x + y);
}
}
而上述實(shí)現(xiàn)的Halide偽代碼為:
produce gradient:
for y:
for x:
gradient(...) = ...
Halide默認(rèn)的計算順序是行優(yōu)先的科展,也就是x代表每一行的元素位置均牢,y代表每一列的元素位置:
如果我們將其中y和x的計算順序換一下:
// 將y的順序提到x之前
gradient.reorder(y, x);
最終的計算過程就為列優(yōu)先:
相應(yīng)的偽代碼為:
produce gradient_col_major:
for x:
for y:
gradient_col_major(...) = ...
拆分 Split
我們可以對每個維度進(jìn)行拆分,假如我們依然是行優(yōu)先計算才睹,但是我們對x軸進(jìn)行拆分徘跪,將其拆成一個外循環(huán)一個里循環(huán),y軸不進(jìn)行變動:
Var x_outer, x_inner;
gradient.split(x, x_outer, x_inner, 2);
這時對應(yīng)的C++代碼實(shí)現(xiàn)為:
for (int y = 0; y < 4; y++) {
for (int x_outer = 0; x_outer < 2; x_outer++) {
for (int x_inner = 0; x_inner < 2; x_inner++) {
int x = x_outer * 2 + x_inner;
printf("Evaluating at x = %d, y = %d: %d\n", x, y, x + y);
}
}
}
融合 fuse
或者我們不進(jìn)行拆分琅攘,對x和y兩個軸進(jìn)行融合:
Var fused;
gradient.fuse(x, y, fused);
此時對應(yīng)的C++實(shí)現(xiàn)代碼為:
for (int fused = 0; fused < 4*4; fused++) {
int y = fused / 4;
int x = fused % 4;
printf("Evaluating at x = %d, y = %d: %d\n", x, y, x + y);
}
但是要知道垮庐,上述拆分和融合操作只是對Halide所能進(jìn)行的操作進(jìn)行一下演示而是,這種操作方式并沒有實(shí)際用處坞琴,也就是說實(shí)際中的計算順序并沒有改變哨查。
平鋪 tile
這一步中就要進(jìn)入Halide中比較重要的部分了,這一步中我們將x和y軸以4為因子間隔進(jìn)行劃分剧辐,并且重新對計算的路徑進(jìn)行重排序:
Var x_outer, x_inner, y_outer, y_inner;
gradient.split(x, x_outer, x_inner, 4);
gradient.split(y, y_outer, y_inner, 4);
gradient.reorder(x_inner, y_inner, x_outer, y_outer);
// 上面的步驟其實(shí)可以簡化成
gradient.tile(x, y, x_outer, y_outer, x_inner, y_inner, 4, 4);
對應(yīng)的C++計算代碼為:
for (int y_outer = 0; y_outer < 2; y_outer++) {
for (int x_outer = 0; x_outer < 2; x_outer++) {
for (int y_inner = 0; y_inner < 4; y_inner++) {
for (int x_inner = 0; x_inner < 4; x_inner++) {
int x = x_outer * 4 + x_inner;
int y = y_outer * 4 + y_inner;
printf("Evaluating at x = %d, y = %d: %d\n", x, y, x + y);
}
}
}
}
可視化一下就是這個樣子(注意這里的示例大小為(8,8)):
這種平鋪的好處是可以充分利用相鄰的像素寒亥,例如模糊操作邮府,我們會使用重疊的輸入數(shù)據(jù)(也就是存在一個元素使用兩次的情況),如果采用這種計算方式溉奕,可以大大加快計算性能褂傀。
向量化 vector
向量化即我們使用cpu中的SIMD技術(shù),一次性計算多個數(shù)據(jù)腐宋,充分利用硬件的特點(diǎn)紊服,例如在x86中我們可以利用SSE技術(shù)來實(shí)現(xiàn)這個功能。
在Halide中胸竞,我們首先將x軸的循環(huán)嵌套按照欺嗤,內(nèi)側(cè)循環(huán)因子4的方式,拆分為兩個(也就是內(nèi)側(cè)循環(huán)x執(zhí)行四次卫枝,外側(cè)根據(jù)總數(shù)進(jìn)行計算煎饼,下例是2*4=8),然后將內(nèi)側(cè)的x循環(huán)轉(zhuǎn)化為向量的形式:
Var x_outer, x_inner;
gradient.split(x, x_outer, x_inner, 4);
gradient.vectorize(x_inner);
用C++來表示即為:
for (int y = 0; y < 4; y++) {
for (int x_outer = 0; x_outer < 2; x_outer++) {
// The loop over x_inner has gone away, and has been
// replaced by a vectorized version of the
// expression. On x86 processors, Halide generates SSE
// for all of this.
int x_vec[] = {x_outer * 4 + 0,
x_outer * 4 + 1,
x_outer * 4 + 2,
x_outer * 4 + 3};
int val[] = {x_vec[0] + y,
x_vec[1] + y,
x_vec[2] + y,
x_vec[3] + y};
printf("Evaluating at <%d, %d, %d, %d>, <%d, %d, %d, %d>:"
" <%d, %d, %d, %d>\n",
x_vec[0], x_vec[1], x_vec[2], x_vec[3],
y, y, y, y,
val[0], val[1], val[2], val[3]);
}
}
可視化后就比較明顯了校赤,外部x每一行執(zhí)行兩次吆玖,內(nèi)側(cè)x變?yōu)橄蛄康男问剑粋€指令集就可以執(zhí)行完成:
展開 unrolling
如果在圖像中多個像素同時共享有重疊的數(shù)據(jù)马篮,這個時候我們就可以將循環(huán)展開沾乘,從而使那些可以共享使用的數(shù)據(jù)只計算一次亦或是只加載一次。
在下面中我們將x軸拆分為內(nèi)側(cè)和外側(cè)浑测,因?yàn)槊看蝺?nèi)側(cè)的數(shù)值增長都是從0到1翅阵,如果我們將內(nèi)測循環(huán)的x軸展開,就不需要每次循環(huán)到這里再讀取內(nèi)測循環(huán)的x的值了:
Var x_outer, x_inner;
gradient.split(x, x_outer, x_inner, 2);
gradient.unroll(x_inner);
相應(yīng)的C++代碼為:
printf("Equivalent C:\n");
for (int y = 0; y < 4; y++) {
for (int x_outer = 0; x_outer < 2; x_outer++) {
// Instead of a for loop over x_inner, we get two
// copies of the innermost statement.
{
int x_inner = 0;
int x = x_outer * 2 + x_inner;
printf("Evaluating at x = %d, y = %d: %d\n", x, y, x + y);
}
{
int x_inner = 1;
int x = x_outer * 2 + x_inner;
printf("Evaluating at x = %d, y = %d: %d\n", x, y, x + y);
}
}
}
融合迁央、平鋪掷匠、并行 Fusing, tiling, and parallelizing
這一步中,我們將融合岖圈、平鋪和并行操作都融合到一起讹语,來對一個8x8的圖像進(jìn)行操作。首先蜂科,我們將x軸和y軸都按照4因子進(jìn)行平鋪操作顽决。隨后我們將外側(cè)的y和外側(cè)的x軸循環(huán)進(jìn)行融合(2+2=4),再將這個融合后的操作進(jìn)行并行操作导匣,也就是同時執(zhí)行這四個(2+2=4)操作:
Var x_outer, y_outer, x_inner, y_inner, tile_index;
gradient.tile(x, y, x_outer, y_outer, x_inner, y_inner, 4, 4);
gradient.fuse(x_outer, y_outer, tile_index);
gradient.parallel(tile_index);
相應(yīng)的C++代碼為:
// This outermost loop should be a parallel for loop, but that's hard in C.
for (int tile_index = 0; tile_index < 4; tile_index++) {
int y_outer = tile_index / 2;
int x_outer = tile_index % 2;
for (int y_inner = 0; y_inner < 4; y_inner++) {
for (int x_inner = 0; x_inner < 4; x_inner++) {
int y = y_outer * 4 + y_inner;
int x = x_outer * 4 + x_inner;
printf("Evaluating at x = %d, y = %d: %d\n", x, y, x + y);
}
}
}
可視化后的結(jié)果擎值,可以看到8x8中左上、左下逐抑、右上鸠儿、右下四個區(qū)域是幾乎同時進(jìn)行的(tile_index),而每個區(qū)域和之前tile那一節(jié)的計算方式是一樣的,只不過這次換成了并行計算:
整合
這次來點(diǎn)大點(diǎn)的圖像进每,我們輸入的圖像大小為350 x 250
汹粤,對其進(jìn)行最優(yōu)化的操作:
首先我們將其按照64 x 64
的因子進(jìn)行平鋪,其次融合y軸和x軸外側(cè)的循環(huán)操作數(shù)田晚,最后對其進(jìn)行并行操作
(這里注意下嘱兼,我們可以看到350或者250并不能被64整除,這個不用擔(dān)心贤徒,Halide會自動處理多余或者不夠的部分)芹壕。
Var x_outer, y_outer, x_inner, y_inner, tile_index;
gradient_fast
.tile(x, y, x_outer, y_outer, x_inner, y_inner, 64, 64)
.fuse(x_outer, y_outer, tile_index)
.parallel(tile_index);
// 可以這樣連續(xù)使用.寫,因?yàn)閷ο蠛瘮?shù)返回的是對象本身的引用
這樣還不夠接奈,上面我們已經(jīng)將整個圖像平鋪為6*4個部分踢涌,而這一步中對每個平鋪后的部分再進(jìn)行一次平鋪操作,這次將每個小塊按照4x2的形式平鋪為序宦,其中y_inner_outer
分成兩個(每個為y_pairs
)睁壁,x_inner_outer
分成四個(每個為x_vectors
),然后將每個x_vectors
并行化互捌,將y_pairs
展開潘明。
Var x_inner_outer, y_inner_outer, x_vectors, y_pairs;
gradient_fast
.tile(x_inner, y_inner, x_inner_outer, y_inner_outer, x_vectors, y_pairs, 4, 2)
.vectorize(x_vectors)
.unroll(y_pairs);
以下可視化的結(jié)果為:
對應(yīng)的c++展示代碼為:
for (int tile_index = 0; tile_index < 6 * 4; tile_index++) {
int y_outer = tile_index / 4;
int x_outer = tile_index % 4;
for (int y_inner_outer = 0; y_inner_outer < 64/2; y_inner_outer++) {
for (int x_inner_outer = 0; x_inner_outer < 64/4; x_inner_outer++) {
// We're vectorized across x
int x = std::min(x_outer * 64, 350-64) + x_inner_outer*4;
int x_vec[4] = {x + 0,
x + 1,
x + 2,
x + 3};
// And we unrolled across y
int y_base = std::min(y_outer * 64, 250-64) + y_inner_outer*2;
{
// y_pairs = 0
int y = y_base + 0;
int y_vec[4] = {y, y, y, y};
int val[4] = {x_vec[0] + y_vec[0],
x_vec[1] + y_vec[1],
x_vec[2] + y_vec[2],
x_vec[3] + y_vec[3]};
// Check the result.
for (int i = 0; i < 4; i++) {
if (result(x_vec[i], y_vec[i]) != val[i]) {
printf("There was an error at %d %d!\n",
x_vec[i], y_vec[i]);
return -1;
}
}
}
{
// y_pairs = 1
int y = y_base + 1;
int y_vec[4] = {y, y, y, y};
int val[4] = {x_vec[0] + y_vec[0],
x_vec[1] + y_vec[1],
x_vec[2] + y_vec[2],
x_vec[3] + y_vec[3]};
// Check the result.
for (int i = 0; i < 4; i++) {
if (result(x_vec[i], y_vec[i]) != val[i]) {
printf("There was an error at %d %d!\n",
x_vec[i], y_vec[i]);
return -1;
}
}
}
}
}
}
到這里Halide中的基本操作就介紹完畢了。
還有一點(diǎn)
哦秕噪,對了钳降,如果用Halide來寫文章一開頭描述的模糊(blur)算法的話,會是這個樣子:
Func blur_3x3(Func input) {
Func blur_x, blur_y;
Var x, y, xi, yi;
// The algorithm - no storage or order
blur_x(x, y) = (input(x-1, y) + input(x, y) + input(x+1, y))/3;
blur_y(x, y) = (blur_x(x, y-1) + blur_x(x, y) + blur_x(x, y+1))/3;
// The schedule - defines order, locality; implies storage
blur_y.tile(x, y, xi, yi, 256, 32)
.vectorize(xi, 8).parallel(y);
blur_x.compute_at(blur_y, x).vectorize(x, 8);
return blur_y;
}
這段著名的代碼同時就在官方的主頁上掛著腌巾,算是一個比較好的示例牲阁。
Halide的特點(diǎn)
Halide這個底層優(yōu)化庫有幾個比較亮眼的特點(diǎn):
Explicit programmer control
The compiler does exactly what you say.
Schedules cannot influence correctness.
Exploration is fast and easy.
明確的程序控制,也就是說壤躲,我們?nèi)绾伟凑者@個計算的順序(與算法本身無關(guān))是確定的,一旦我們已經(jīng)設(shè)定好就不會再改變备燃。
Stochastic search (autotuning)
Pick your favorite high-dimensional search.
而自動搜索則是每個具有搜索空間的優(yōu)化器都可以使用的碉克,因?yàn)槊看芜M(jìn)行優(yōu)化操作的時候,優(yōu)化的因子都是不確定的并齐,對于不同的硬件來說漏麦,不同的配置可能導(dǎo)致的執(zhí)行速度也不一樣。因此自動隨機(jī)搜索空間因子是有必要的况褪。
元編程
Halide的思想與元編程有著密切的關(guān)系撕贞,不僅是其設(shè)計思路或者是其執(zhí)行思路,都遵循了元編程的思想测垛,也就是代碼在編譯之前并沒有明確的執(zhí)行邏輯捏膨,只有編譯過后,才會形成執(zhí)行邏輯。
其他相關(guān)
halide既然作為與算法無關(guān)的底層優(yōu)化器号涯,與當(dāng)今大伙的深度學(xué)習(xí)的結(jié)合應(yīng)用肯定也是非常多的目胡。OpenCV庫就使用了halide去優(yōu)化底層的神經(jīng)網(wǎng)絡(luò)算子,相應(yīng)的benchmark結(jié)論在這里链快,但是我們發(fā)現(xiàn)使用了halide的神經(jīng)網(wǎng)絡(luò)運(yùn)行的速度竟然不如普通的C++實(shí)現(xiàn)版誉己。原因不確定是否halide本身的設(shè)計有關(guān),還是與halide優(yōu)化和神經(jīng)網(wǎng)絡(luò)算子的兼容性有關(guān)域蜗,總之巨双,如果想要利用halide真正的實(shí)現(xiàn)加速還是需要等待一段時間(等了好久還是用到TVM上了)。
相關(guān)提問:
https://stackoverflow.com/questions/47202895/why-is-opencv-dnn-slower-if-i-use-halide
另外提一下霉祸,Halide的運(yùn)行有兩種方式筑累,一種是JIT的模式,另一種是AOT的模式脉执。JIT模式使用起來比較方便疼阔,可以直接將算法和Halide的代碼生成generator封裝成一個類,在程序的其他部分調(diào)用這個類半夷,然后再編譯使用婆廊。
而在嵌入式環(huán)境或交叉編譯環(huán)境下一般使用AOT模式,此時需要調(diào)用compiler函數(shù)將算法代碼和Halide的代碼生成generator編譯位目標(biāo)機(jī)器的代碼巫橄,提前生成一個.o目標(biāo)文件和.h頭文件淘邻。然后在獨(dú)立的目標(biāo)機(jī)器的應(yīng)用的工程的源代碼中通過頭文件調(diào)用算法實(shí)現(xiàn)的計算函數(shù),并在build的時候鏈接上.o文件湘换,這樣就得到一個可以在目標(biāo)機(jī)器上運(yùn)行的用Halide實(shí)現(xiàn)算法的程序了宾舅。
后記
本文只是簡單介紹了Halide的基本知識,對于想要深入理解Halide的童鞋可以看官方的教程或者閱讀源碼彩倚,不論我們是設(shè)計算法的算法工程師亦或是在相關(guān)硬件平臺上實(shí)現(xiàn)移植功能的底層工程師筹我,Halide的思想都是值得我們?nèi)ソ梃b和回味的。
Halide的利用范圍很廣帆离,我之所以想要了解下Halide是因?yàn)槭褂昧?strong>TVM庫蔬蕊,TVM借助了Halide的思想去實(shí)現(xiàn)神經(jīng)網(wǎng)絡(luò)算子的優(yōu)化并且取得了不錯的效果。TVM也可以通過這種方式實(shí)現(xiàn)自己的一些算法哥谷,算是兼容了halide而且更好用岸夯,還是建議大家入坑的。
之后還會寫一些TVM的文章...努力復(fù)習(xí)中们妥。如果不對的地方猜扮,大家輕噴呀。
參考資料:
上述所采用的圖像部分來源于此:
- http://stellar.mit.edu/S/course/6/sp15/6.815/courseMaterial/topics/topic2/lectureNotes/14_Halide_print/14_Halide_print.pdf
- Halide的官網(wǎng):https://halide-lang.org/
撩我吧
- 如果你與我志同道合于此监婶,老潘很愿意與你交流旅赢;
- 如果你喜歡老潘的內(nèi)容,歡迎關(guān)注和支持。
- 如果你喜歡我的文章鲜漩,希望點(diǎn)贊?? 收藏 ?? 評論 ?? 三連一下~
想知道老潘是如何學(xué)習(xí)踩坑的源譬,想與我交流問題~請關(guān)注公眾號「oldpan博客」。
老潘也會整理一些自己的私藏孕似,希望能幫助到大家踩娘,點(diǎn)擊神秘傳送門獲取。