(深度學(xué)習(xí)優(yōu)化大師)TVM的前輩,介紹下什么是HALIDE吧~

前言

前幾天又用到了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對算法的加速效果

那么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í)行時性能的“三角力量”如下锡移。

TIM截圖20190417101516

其中,算法本身的設(shè)計是一方面漆际,一個好的算法往往效率會高很多淆珊。而另外一個方面就是算法中計算順序的組織,而Halide可以改變的就是我們算法在某個硬件平臺上的計算順序:

TIM截圖20190417102405

其中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í)行的速度使鹅。

TIM截圖20190417151611

官方示例

Halide作為一個DSL揪阶,我們很容易就可以使用它,這里我們將其源碼下下來并進(jìn)行編譯患朱。完成之后我們就可以使用它了(這里省略編譯步驟鲁僚,可自行在官網(wǎng)查閱):

首先我們引用Halide頭文件以及其他的文件。

#include "Halide.h"
#include <stdio.h>
#include <algorithm>

using namespace Halide;

初次使用Halide之前裁厅,首先需要知道halide中的一些語法:

TIM截圖20190417103330

然后我們利用Halide定義兩個變量蕴茴,這兩個變量單獨(dú)使用時沒有任何意義,同時我們用字符串xy為兩個變量起了名字:

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代表每一列的元素位置:

lesson_05_row_major

如果我們將其中y和x的計算順序換一下:

// 將y的順序提到x之前
gradient.reorder(y, x);

最終的計算過程就為列優(yōu)先:

lesson_05_col_major

相應(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)):

lesson_05_tiled

這種平鋪的好處是可以充分利用相鄰的像素寒亥,例如模糊操作邮府,我們會使用重疊的輸入數(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í)行完成:

lesson_05_vectors

展開 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é)的計算方式是一樣的,只不過這次換成了并行計算:

lesson_05_parallel_tiles

整合

這次來點(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é)果為:

lesson-05-full

對應(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í)行邏輯。

TIM截圖20190417103037

其他相關(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上了)。

TIM截圖20190417101052

相關(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í)中们妥。如果不對的地方猜扮,大家輕噴呀。

參考資料:

上述所采用的圖像部分來源于此:

撩我吧

  • 如果你與我志同道合于此监婶,老潘很愿意與你交流旅赢;
  • 如果你喜歡老潘的內(nèi)容,歡迎關(guān)注和支持。
  • 如果你喜歡我的文章鲜漩,希望點(diǎn)贊?? 收藏 ?? 評論 ?? 三連一下~

想知道老潘是如何學(xué)習(xí)踩坑的源譬,想與我交流問題~請關(guān)注公眾號「oldpan博客」。
老潘也會整理一些自己的私藏孕似,希望能幫助到大家踩娘,點(diǎn)擊神秘傳送門獲取。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末喉祭,一起剝皮案震驚了整個濱河市养渴,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌泛烙,老刑警劉巖般卑,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件困后,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)憔涉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進(jìn)店門宦棺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來复旬,“玉大人诸蚕,你說我怎么就攤上這事∽耘猓” “怎么了妈嘹?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長绍妨。 經(jīng)常有香客問我润脸,道長,這世上最難降的妖魔是什么他去? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任毙驯,我火速辦了婚禮,結(jié)果婚禮上灾测,老公的妹妹穿的比我還像新娘爆价。我一直安慰自己,他們只是感情好行施,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著魂那,像睡著了一般蛾号。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上涯雅,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天鲜结,我揣著相機(jī)與錄音,去河邊找鬼。 笑死精刷,一個胖子當(dāng)著我的面吹牛拗胜,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播怒允,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼埂软,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了纫事?” 一聲冷哼從身側(cè)響起勘畔,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎丽惶,沒想到半個月后炫七,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡钾唬,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年万哪,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片抡秆。...
    茶點(diǎn)故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡奕巍,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出琅轧,到底是詐尸還是另有隱情伍绳,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布乍桂,位于F島的核電站冲杀,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏睹酌。R本人自食惡果不足惜权谁,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望憋沿。 院中可真熱鬧旺芽,春花似錦、人聲如沸辐啄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽壶辜。三九已至悯舟,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間砸民,已是汗流浹背抵怎。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工奋救, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人反惕。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓尝艘,卻偏偏與公主長得像,于是被迫代替她去往敵國和親姿染。 傳聞我的和親對象是個殘疾皇子背亥,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內(nèi)容