Halide チュートリアル


Halide チュートリアル

Halideの公式チュートリアルを日本語訳した

Halide チュートリアル 02: 画像処理

画像を読み込んで,輝度値を1.5倍にする

processing_images.cpp
#include "Halide.h"
#include "halide_image_io.h"
using namespace Halide::Tools;

int main(int argc, char **argv) {

    Halide::Buffer<uint8_t> input = load_image("images/rgb.png"); //画像の読み込み

    Halide::Func brighter;
    Halide::Var x, y, c;
    Halide::Expr value = input(x, y, c);
    value = Halide::cast<float>(value);
    value = value * 1.5f;
    value = Halide::min(value, 255.0f);
    value = Halide::cast<uint8_t>(value);
    brighter(x, y, c) = value;

    Halide::Buffer<uint8_t> output = brighter.realize(input.width(), input.height(), input.channels());

    save_image(output, "brighter.png"); //画像の保存

    printf("Success!\n");
    return 0;
}

数値の最後に'f'をつけることでfloatとして扱われる.
value = value * 1.5f;

uint8に戻したどきにオーバーフローしないように,255以下になるように制限する.
value = Halide::min(value, 255.0f);

関数brighter()を1行で書くと次のようになる.
brighter(x, y, c) = Halide::cast<uint8_t>(min(input(x, y, c) * 1.5f, 255));
uint8で読み込んだ画像は,1.5fの乗算で自動的にfloatにキャストされるので,floatへのキャストを書く必要はない.
同様に,関数min()の第2引数255も,第1引数との比較で自動的にfloatにキャストされる.
また, 関数min()の呼び出しでHalide::は省略できる.これは,C++では,関数呼出時に与えられた引数の型に依存して,呼び出す関数を探索するためである.

ここまでは、メモリ内にHalideプログラムの表現を構築しただけで,実際にはまだピクセルを処理していない.Halideプログラムはまだコンパイルされていない.

realizeメソッドを呼び出しFuncを実現する.
出力画像のサイズは、入力画像のサイズと一致させる必要がある.より大きなサイズを要求すると,Halideは実行時にエラー表示し,入力画像の範囲外を読み取ろうとしていることを通知する.
Halide::Buffer<uint8_t> output = brighter.realize(input.width(), input.height(), input.channels());

Halide チュートリアル 03: 生成されたコードの確認

inspect_generated_code.cpp
#include "Halide.h"
#include <stdio.h>

int main(int argc, char **argv) {

    Func gradient("gradient");
    Var x("x"), y("y");
    gradient(x, y) = x + y;

    Buffer<int> output = gradient.realize(8, 8);
    gradient.compile_to_lowered_stmt("gradient.html", {}, HTML);

    printf("Success!\n");
    return 0;
}

文字列をFuncおよびVarコンストラクターに渡して,デバッグ用の名前を付ける.
Func gradient("gradient");
Var x("x"), y("y");
gradient(x, y) = x + y;

環境変数HL_DEBUG_CODEGENを1に設定してコンパイルすると,コンパイルのさまざまな段階と,最終的なパイプラインの疑似コード表現が出力される.
HL_DEBUG_CODEGENを2に設定すると、コンパイルの各段階でのHalideコードや最後に生成するllvmビットコードなどより詳細な出力が表示される.

HTML形式でも出力できる.
構文の強調表示とコードの折りたたみをサポートしているため,大規模なパイプラインの場合は読みやすくなる.
gradient.compile_to_lowered_stmt("gradient.html", {}, HTML);

Halide チュートリアル 04:トレース,print,print_whenによるデバッグ

debugg.cpp
#include "Halide.h"
#include <stdio.h>
using namespace Halide;

int main(int argc, char **argv) {

    Var x("x"), y("y")

    // Printing out the value of Funcs as they are computed.
    {
        Func gradient("gradient");
        gradient(x, y) = x + y;

        gradient.trace_stores();

        printf("Evaluating gradient\n");
        Buffer<int> output = gradient.realize(8, 8);

        Func parallel_gradient("parallel_gradient");
        parallel_gradient(x, y) = x + y;

        parallel_gradient.trace_stores();

        parallel_gradient.parallel(y);

        printf("\nEvaluating parallel_gradient\n");
        parallel_gradient.realize(8, 8);
    }

    // Printing individual Exprs.
    {
        Func f;
        f(x, y) = sin(x) + cos(y);

        Func g;
        g(x, y) = sin(x) + print(cos(y));

        printf("\nEvaluating sin(x) + cos(y), and just printing cos(y)\n");
        g.realize(4, 4);
    }

    // Printing additional context.
    {
        Func f;
        f(x, y) = sin(x) + print(cos(y), "<- this is cos(", y, ") when x =", x);

        printf("\nEvaluating sin(x) + cos(y), and printing cos(y) with more context\n");
        f.realize(4, 4);

        Expr e = cos(y);
        Func g;
        g(x, y) = sin(x) + e;
        g.realize(4, 4);
    }

    // Conditional printing
    {
        Func f;
        Expr e = cos(y);
        e = print_when(x == 37 && y == 42, e, "<- this is cos(y) at x, y == (37, 42)");
        f(x, y) = sin(x) + e;
        printf("\nEvaluating sin(x) + cos(y), and printing cos(y) at a single pixel\n");
        f.realize(640, 480);

        Func g;
        e = cos(y);
        e = print_when(e < 0, e, "cos(y) < 0 at y ==", y);
        g(x, y) = sin(x) + e;
        printf("\nEvaluating sin(x) + cos(y), and printing whenever cos(y) < 0\n");
        g.realize(4, 4);
    }

    // Printing expressions at compile-time.
    {
        Var fizz("fizz"), buzz("buzz");
        Expr e = 1;
        for (int i = 2; i < 100; i++) {
            if (i % 3 == 0 && i % 5 == 0) e += fizz*buzz;
            else if (i % 3 == 0) e += fizz;
            else if (i % 5 == 0) e += buzz;
            else e += i;
        }
        std::cout << "Printing a complex Expr: " << e << "\n";
    }

    printf("Success!\n");
    return 0;
}

trace_scores

gradient(x、y)が計算されるたびにその結果を出力する.
gradient.trace_stores();

各スキャンラインを並行して処理する新しいバージョンのgradiationを作成する.
Func parallel_gradient("parallel_gradient");
parallel_gradient(x, y) = x + y;

この関数もトレースできるようにしておく.
parallel_gradient.trace_stores();

y座標に対して並列forループで計算するようにする.
parallel_gradient.parallel(y);

今回は,各スキャンラインが異なるスレッドで処理されている可能性があるため,printfの順序が違うはずである.
printf("\nEvaluating parallel_gradient\n");
parallel_gradient.realize(8, 8);

print

trace_stores()はFuncの値しか出力できないため,Func全体ではなく、部分式の値を調べたい場合には,組み込み関数「print」を用いる.print()は,任意のExprをラップして,評価されるたびにそのExprの値を出力できる.

例えば、2つの項の合計であるFuncがあるとする.
Func f;
f(x, y) = sin(x) + cos(y);

1つの項だけprint()したい場合は,次のようにする.
Func g;
g(x, y) = sin(x) + print(cos(y));

printは複数の引数を取ることができる.引数はExprsまたは文字列である.
Func f;
f(x, y) = sin(x) + print(cos(y), "<- this is cos(", y, ") when x =", x);

上記のような式を複数行に分割する,デバッグ中に特定の値の出力のオンとオフを簡単に切り替えることができる.
Expr e = cos(y);
次の行のコメントを外せば,cos(y)の値を出力できる.すなわち,コメントイン/アウトで値の出力のオン/オフが切り替えられる.
//e = print(e, "<- this is cos(", y, ") when x =", x);

Func g;
g(x, y) = sin(x) + e;
g.realize(4, 4);

print_when

printとtrace_storesはどちらも大量の出力を表示するが,低頻度で発生するイベントや単一のピクセルで何が発生しているのかを確認したい場合は,関数print_whenを使用して、条件付きでExprを出力した方が良い.print_whenの最初の引数はBool式であり, Exprがtrueと評価された場合,2番目の引数を返し,すべての引数を出力する.Exprがfalseと評価された場合,2番目の引数を返すだけで出力はされない.
Func f;
Expr e = cos(y);
e = print_when(x == 37 && y == 42, e, "<- this is cos(y) at x, y == (37, 42)");
f(x, y) = sin(x) + e;
printf("\nEvaluating sin(x) + cos(y), and printing cos(y) at a single pixel\n");
f.realize(640, 480);

出力

```

-0.399985 <- this is cos(y) at x, y == (37, 42)
```

また,print_whenを使用して、予期しない値を確認することもできる.
Func g;
e = cos(y);
e = print_when(e < 0, e, "cos(y) < 0 at y ==", y);
g(x, y) = sin(x) + e;
printf("\nEvaluating sin(x) + cos(y), and printing whenever cos(y) < 0\n");
g.realize(4, 4);

出力

```

-0.416147 cos(y) < 0 at y == 2
-0.416147 cos(y) < 0 at y == 2
-0.416147 cos(y) < 0 at y == 2
-0.416147 cos(y) < 0 at y == 2
-0.989992 cos(y) < 0 at y == 3
-0.989992 cos(y) < 0 at y == 3
-0.989992 cos(y) < 0 at y == 3
-0.989992 cos(y) < 0 at y == 3
```

式自体の評価

上記のコードは,数行のコードにわたってExprを構築している.プログラムで複雑な式を作成していて,作成したExprが想定どおりであることを確認したい場合は,C++ストリームを使用して式自体を出力することもできる.
Var fizz("fizz"), buzz("buzz");
Expr e = 1;
for (int i = 2; i < 100; i++) {
   if (i % 3 == 0 && i % 5 == 0) e += fizz*buzz;
   else if (i % 3 == 0) e += fizz;
   else if (i % 5 == 0) e += buzz;
   else e += i;
}
std::cout << "Printing a complex Expr: " << e << "\n";

出力

```

Printing a complex Expr: ((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((1 + 2) + fizz) + 4) + buzz) + fizz) + 7) + 8) + fizz) + buzz) + 11) + fizz) + 13) + 14) + (fizz*buzz)) + 16) + 17) + fizz) + 19) + buzz) + fizz) + 22) + 23) + fizz) + buzz) + 26) + fizz) + 28) + 29) + (fizz*buzz)) + 31) + 32) + fizz) + 34) + buzz) + fizz) + 37) + 38) + fizz) + buzz) + 41) + fizz) + 43) + 44) + (fizz*buzz)) + 46) + 47) + fizz) + 49) + buzz) + fizz) + 52) + 53) + fizz) + buzz) + 56) + fizz) + 58) + 59) + (fizz*buzz)) + 61) + 62) + fizz) + 64) + buzz) + fizz) + 67) + 68) + fizz) + buzz) + 71) + fizz) + 73) + 74) + (fizz*buzz)) + 76) + 77) + fizz) + 79) + buzz) + fizz) + 82) + 83) + fizz) + buzz) + 86) + fizz) + 88) + 89) + (fizz*buzz)) + 91) + 92) + fizz) + 94) + buzz) + fizz) + 97) + 98) + fizz)
```