由於 Halide Tutorial 中使用了新的寫法, 因此需要了解其中的改進, 儘管 Tutorial 中已經提供基本的 Generator 使用方式, 然而許多進階與注意事項在此文件提到了更多
Generator 這些改進提供了更清晰與洗煉的表示方式, 新的參數讓編譯更有彈性, 以及更強大的混合使用, 新的設計更考量了 AutoScheduler 的搭配, 值得花時間學習
====
在 2016 年 10月下旬 (https://github.com/halide/Halide/pull/1523), Halide Generators 已被增進與改善:
- 改善 Generator 的可讀性與彈性
- 提供機械產生的 Stubs 讓 Generator 能彼此間的使用更簡單
- 使得與 Autoscheduler 間的整合更容易與更可靠
這份文件主要在於提供改變的本質與描述如何"升級" Generator 來使用新的增進功能.
使用 Input<X> 取代 Param<X> (以及用 Input<Func> 取代 ImageParam )
Param
<> 依然存在, 但 Generators 現在能夠使用一個新的類別, Input
<>, 來取代. 對於純量型別, 這些實質上能夠被認為等同於 Param
<>, 但因為一些後續將介紹的程式碼簡明緣故而有著不同的名字.同樣地,
ImageParam
依然存在, 但 Generators 現在能夠使用一個 Input
<Func> 來取代. 這(實質上)類似一個 ImageParam
, 而有著最主要的差異是背後可能不(或者可能)再有著實際的 buffer, 因此沒有事先定義的延伸範圍.
Input<Func> input{"input", Float(32), 2};
類似於下列方式, 這等義於一個背後有著能被一個的 Input<Buffer
<
T
>
> 所建構的
實體 buffer 的 ImageParam
:
ImageParam input{UInt(8), 2, "input"};
轉變為:這允許程式寫作者 (相較於使用Input
<Buffer
<
uint8_t
>
>
input{"input", 2};
Input<Func>
) 透過 input.dim(0).extend() 以及 input.dim(1).extend() 來
取得 buffer 的寬與高.同時宣告
Input
<>
與 Param
<>
或
ImageParam
(i.e.: 如果使用 Input
<>
可能無法使用先前的語法) 對於 Generator 是一種錯誤. 請注意 Input
<> 只能搭配 Generator 使用, 並且無法使用在其他 Halide 程式碼中; 除了在 Generator 中之外, 它並非打算來取代
Param
<>
範例:
class SumColumns : Generator<SumColumns> { ImageParam input{Float(32), 2, "input"}; Func build() { RDom r(0, input.width()); Func f; Var y; f(y) = 0.f; f(y) += input(r.x, y); return f; } };
轉變為
能夠選擇性地不指定 Input<Func> 的型別與/或維度, 在這時數值直接由 Func 傳入的數值推斷. 當然, 若指定了明確的型別與維度, 將要求傳入的 Func 符合, 否則會產生編譯錯誤.class SumColumns : Generator
<
SumColumns> { Input<Func
>
input{"input", Float(32), 2}; Input
<
int32_t
>
width{"width"}; Func build() { RDom r(0, width); Func f; Var y; f(y) = 0.f; f(y) += input(r.x, y); return f; } };
當一個使用了 Input<ltFunc> 的 Generator 被直接編譯時 (e.g. 使用 GenGen), 而型別與(或)維度在程式碼中並沒有被指明, 此時 Input<ltFunc> 必須明確地指明. 能夠透過 GeneratorParams 搭配Input 或 Output 衍生出的名字來指定. (在上述的例子中, input 有著名為 "input.type" 的 GeneratorParam 與名為 "input.dim" 的 GeneratorParam)Input<Func> input{ "input", 3 }; // require 3-dimensional Func, // but leave Type unspecified
明確宣告輸出
所有 Generator 的輸入可以藉由其成員來反推, 但是關於輸出的資訊在先前僅能藉由呼叫 build() 並確認回傳值來確定(可能是個 Func 或是一個 Pipeline)
而這次的改變, Generator 相對地能明確地宣告其輸出為一個成員變數, 並且提供 generator() 介面函式取代 build() (等同於 generate 沒有回傳任何數值)
範例:
轉變為:class SumColumns : Generator<SumColumns> { Input<Func> input{"input", Float(32), 2}; Input<int32_t> width{"width"}; Func build() { RDom r(0, width); Func f; Var y; f(y) = 0.f; f(y) += input(r.x, y); return f; } };
class SumColumns : Generator<SumColumns> { Input<Func> input{"input", Float(32), 2}; Input<int32_t> width{"width"}; Output<Func> sum_cols{"sum_cols", Float(32), 1}; void generate() { RDom r(0, width); Var y; sum_cols(y) = 0.f; sum_cols(y) += input(r, y); } };
當搭配 Output<Func>, 能夠選擇性地
不指定 Output<Func> 的型別與/或維度; 當任何未被指明的型別必須透過
GeneratorParam 指定才能夠使用高階編譯.
請注意
Output<> 僅能搭配 Generator 使用, 並不能在其他 Halide 程式使用.
Generator 的底層實作將會驗證(在呼叫 generate() 之後)所有定義的輸出, 與定義是否符合宣告.
開發者能夠透過 Type 清單來指明一個回傳為 Tuple 的輸出:
一個 Generator 能夠定義多個 Output (相當於悄悄地實作為一個 Pipeline):class Tupler : Generator<Tupler> { Input<Func> input{"input", Int(32), 2}; Output<Func> output{"output", {Float(32), UInt(8)}, 2}; void generate() { Var x, y; output(x, y) = Tuple(cast
(input(x, y)), cast<uint8_t>(input(x, y))); } };
並且允許指定輸出為任意的純量型別(除了 Handle 型別外); 這僅只是在零維度 Func 上的語法糖果, 但相當的方便, 特別是用在多個輸出上:class SumRowsAndColumns : Generator<SumRowsAndColumns> { Input<Func> input{"input", Float(32), 2}; Input
<
int32_t
>
width{"width"}; Input
<
int32_t
>
height{"height"}; Output
<
Func
>
sum_rows{"sum_rows", Float(32), 1}; Output
<
Func
>
sum_cols{"sum_cols", Float(32), 1}; void generate() { RDom rc(0, height); Var x; sum_rows(x) = 0.f; sum_rows(x) += input(x, rc); RDom rr(0, width); Var y; sum_cols(y) = 0.f; sum_cols(y) += input(rr, y); } };
class Sum : Generator<Sum
{ Input
>
Func> input{"input", Float(32), 2}; Input
<
int32_t
<
width{"width"}; Input
>
int32_t
<
height{"height"}; Output
>
Func
<
sum_rows{"sum_rows", Float(32), 1}; Output
>
Func
<
sum_cols{"sum_cols", Float(32), 1}; Output
>
float
<
sum{"sum"}; void generate() { RDom rc(0, height); Var x; sum_rows(x) = 0.f; sum_rows(x) += input(x, rc); RDom rr(0, width); Var y; sum_cols(y) = 0.f; sum_cols(y) += input(rr, y); RDom r(0, width, 0, height); sum() = 0.f; sum() += input(r.x, r.y); } };
>
請注意同時定義 build() 與 generate() 對 Halide 是一個錯誤.
輸入與輸出陣列
藉由使用一個型別陣列來作為型別參數, 亦能夠透過使用新的語法來宣告一個輸入或輸出的陣列:
陣列大小能夠不指定, 但有著注意事項:// Takes exactly 3 images and outputs exactly 3 sums. class SumRowsAndColumns : Generator<SumRowsAndColumns> { Input
<
Func[3]
>
inputs{"inputs", Float(32), 2}; Input
<
int32_t[2]
>
extents{"extents"}; Output
<
Func[3]
>
sums{"sums", Float(32), 1}; void generate() { assert(inputs.size() == sums.size()); // assume all inputs are same extent Expr width = extent[0]; Expr height = extent[1]; for (size_t i = 0; i < inputs.size(); ++i) { RDom r(0, width, 0, height); sums[i]() = 0.f; sums[i]() += inputs[i](r.x, r.y); } } };
- 對於 ahead-of-time 編譯, 輸入必須有著明確的大小, 編譯時藉由一個 GeneratorParam 帶入 (e.g., pyramid.size=3)
- 對於透過一個 Stub 的 JIT 編譯, 輸入陣列大小將自傳入的向量推斷.
- 對於 ahead-of-time 編譯, 輸出可能在編譯時藉由一個 GeneratorParam 指定了具體的大小 (e.g., pyramid.size=3), 或是能夠透過 resize 的方式指定大小.
class Pyramid : public Generator<Pyramid> { public: GeneratorParam
<
int32_t
>
levels{"levels", 10}; Input
<
Func
>
input{ "input", Float(32), 2 }; Output
<
Func[]
>
pyramid{ "pyramid", Float(32), 2 }; void generate() { pyramid.resize(levels); pyramid[0](x, y) = input(x, y); for (int i = 1; i < pyramid.size(); i++) { pyramid[i](x, y) = (pyramid[i-1](2*x, 2*y) + pyramid[i-1](2*x+1, 2*y) + pyramid[i-1](2*x, 2*y+1) + pyramid[i-1](2*x+1, 2*y+1))/4; } } };
一個未指定大小的 Input/Output 陣列必須在高階編譯時有著明確的大小;
現在有著
基於命名的 GeneratorParam 用來設定此數值
, (在上面的範例為"pyramid.size").請注意 Input 與 Output 陣列都支援來自
std::vector<>
有限的介面
:operator[]
size()
begin()
end()
-
resize()
(Output only)
自 Build() 中分開排程
一個 Generator 現在能夠將既存的 build()
分為兩個介面函式:void generate() { ... } void schedule() { ... }
如此一個
Generator 必須將所有對於中介 Func 排程的程式碼移到
schedule()
中
. 請注意這表示可排程的 Func
, Var
, 等等將需要宣告為 Generator 的成員變數. (因為
Output<>
需要宣告為成員變數, 這相當簡單, 但對於中介需要排程 Func
可能需要相當變動.)範例:
轉變為:class Example : Generator<Example> { Output<Func
>
output{"output", Float(32), 2}; void generate() { Var x, y; Func intermediate; intermediate(x, y) = SomeExpr(x, y); output(x, y) = intermediate(x, y); intermediate.compute_at(output, y); } };
請注意輸出的class Example : Generator<Example> { Output<Func> output{"output", Float(32), 2}; void generate() { intermediate(x, y) = SomeExpr(x, y); output(x, y) = intermediate(x, y); } void schedule() { intermediate.compute_at(output, y); } Func intermediate; Var x, y; };
Func
並不需要一個對於
compute_at()
或 store_at()
的排程指令: 要不就是潛在地
compute_root()
(當直接編譯為濾波器), 或是透過呼叫者來排程 (如後面將看到的, 當被用作為子項目元件).即便中介的 Halide 程式碼並沒有必要的排程 (e.g. 全為 inline), 依然應該提供一個空的
schedule()
來使其明確與簡潔.範例:
轉變為class ExampleInline : Generator<ExampleInline> { Output
<
Func
>
output{"output", Float(32), 2}; void generate() { Var x, y; output(x, y) = SomeExpr(x, y); } };
class ExampleInline : Generator<ExampleInline> { Output<Func> output{"output", Float(32), 2}; void generate() { output(x, y) = SomeExpr(x, y); } void schedule() { // empty } Var x, y; };
當需要時將 GeneratorParam 轉為 ScheduleParam
GeneratorParam
現在透過新的 ScheduleParam
型別來改進. 所有被 schedule() 所使用的
generator 參數必須宣告為 ScheduleParam
而非 GeneratorParam
. 這有著兩個目的:- 允許在任意的 Generator 之間陳列與傳遞排程資訊的方式 (之後將會看到).
- 使得哪些 GeneratorParams 是作為排程用途, 這有助於未來的 Autoscheduler 運作.
GeneratorParam
約定早已如同
ScheduleParam
動作(特別是 vectorize
與 parallelize
); 這僅只是型式化先前的慣例.GeneratorParam
與 ScheduleParam
將共存在單一個
namespace (i.e., 將 GeneratorParam
與
ScheduleParam
以相同命名宣告是個錯誤).當一個
GeneratorParam
能用在 Generator 中任何地方使用 (無論 generate()
或者schedule()
), 一個 ScheduleParam
僅能夠限縮在 schedule()
中存取. (在未來, 這將可能會是編譯時期的限制)請注意當
GeneratorParam
必須能夠對字串雙向序列化 (如同 GeneratorParams), 一些 ScheduleParam
數值並非可序列化, 因為可能參照了執行時期的 Halide 結構 (特別是, LoopLevel
, 並不能在一般情況下可靠地以命名指定). 嘗試從 GenGen 設定如此的 ScheduleParam
將產生編譯時期錯誤.範例:
轉變為:class Example : Generator<Example> { GeneratorParam
<
int32_t
>
iters{"iters", 10}; GeneratorParam
<
bool
>
vectorize{"vectorize", true}; Func generate() { Var x, y; vector
<
Func
>
intermediates; for (int i = 0; i < iters; ++i) { Func g; g(x, y) = (i == 0) ? SomeExpr(x, y) : SomeExpr2(g(x, y)); intermediates.push_back(g); } Func f; f(x, y) = intermediates.back()(x, y); // Schedule for (auto fi : intermediates) { fi.compute_at(f, y); if (vectorize) fi.vectorize(x, natural_vector_size()); } return f; } };
請注意class Example : Generator<Example> { GeneratorParam
<
int32_t
>
iters{"iters", 10}; ScheduleParam
<
bool
>
vectorize{"vectorize", true}; Output
<
Func
>
output{"output", Float(32), 2}; void generate() { for (int i = 0; i < iters; ++i) { Func g; g(x, y) = (i == 0) ? SomeExpr(x, y) : SomeExpr2(g(x, y)); intermediates.push_back(g); } output(x, y) = intermediates.back()(x, y); } void schedule() { for (auto fi : intermediates) { fi.compute_at(output, y); if (vectorize) fi.vectorize(x, natural_vector_size()); } } Var x, y; vector<Func> intermediates; };
ScheduleParam
也能夠有著其他有趣的數值, 特別是
LoopLevel
:請注意class Example : Generator<Example> { // Specify a LoopLevel at which we want intermediate Func(s) // to be computed and/or stored. ScheduleParam
<
LoopLevel
>
intermediate_compute_level{"level", "undefined"}; ScheduleParam
<
LoopLevel
>
intermediate_store_level{"level", "root"}; Output
<
Func> output{"output", Float(32), 2}; void generate() { intermediate(x, y) = SomeExpr(x, y); output(x, y) = intermediate(x, y); } void schedule() { intermediate // If intermediate_compute_level is undefined, // default to computing at output's rows .compute_at(intermediate_compute_level.defined() ? intermediate_compute_level : LoopLevel(output, y)) .store_at(intermediate_store_level); } Func intermediate; Var x, y; };
ScheduleParam<LoopLevel>
能夠預設為 "root", "inline", 或
"undefined"; 所有其他的數值 (e.g. Func-and-Var) 必須在實際程式碼中指定. (LoopLevel(Func, Var) 明確地無法藉由像是 "func.var" 的命名來指定; 雖然 Halide 內部使用這慣例, 但目前無法保證在任意集合的 Generator 使用唯一的 Func 命名)請注意使用一個未定義的 LoopLevel 作為排程是一個錯誤
修訂的 RegisterGenerator 語法
在以往, 必須註冊藉由在廣域下明確的實體化一個 RegisterGenerator 來註冊 Generator:
Halide::RegisterGenerator<MyGen> register_my_gen{"my_gen"};
這依然有效, 但導入了更簡單的註冊巨集:
HALIDE_REGISTER_GENERATOR(MyGen, my_gen) // no semicolon at end若想要針對特定 Generator 產生一個 Stub, 必須使用這新的註冊巨集, 並加入該資訊到宣告之中:
若對於第3個參數的 stub 合格命名沒有被適當地宣告, 將產生編譯錯誤. 完全合格的命名必須至少有著一個 namespace (也就是, 一個全域命名是不會被接受的).// We must forward-declare the name we want for the stub, // inside the proper namespace(s). None of the namespace(s) // may be anonymous (if they are, failures will occur at Halide // compilation time). namespace SomeNamespace { class MyGenStub; } HALIDE_REGISTER_GENERATOR(MyGen, "my_gen", SomeNamespace::MyGenStub)
Generator Stubs
先以使用範例做開始, 接著再回頭解釋過程. 若有著一個想重複使用的 RGB-to-YCbCr 元件:
GenGen 現在能夠產生一個類Func 的包覆一個 generator 的 stub 類別, (通常) 以一個副檔名 為 ".stub.h" 檔案發出. 看起來像是:class RgbToYCbCr : public Generator<RgbToYCbCr> { Input<Func> input{"input", Float(32), 3}; Output
<
Func
>
output{"output", Float(32), 3}; void generate() { ... conversion code here ... } void schedule() { ... scheduling code here ... } }; RegisterGeneratorregister_me{"rgb_to_ycbcr"};
請注意這只是一個 "header-only" 類別; 所有的函式都是 inline 方式(或是 template-multilinked, etc) 所以並沒有對應要合入的 .cpp . 並且注意到有一個 "by-value", 基於內部處理的類別, 如同多數其他在 Halide 中的類別(e.g./path/to/rgb_to_rcbcr.stub.h: // MACHINE-GENERATED class RgbToYCbCr : public GeneratorStub { struct Inputs { // All the Input<>s declared in the Generator are listed here, // as either Func or Expr Func input; }; struct GeneratorParams { ... }; struct ScheduleParams { ... }; // ctor, with required inputs, and (optional) GeneratorParams. RgbToYCbCr(GeneratorContext* context, const Inputs& inputs, const GeneratorParams& = {}) { ... } // Output(s) Func output; // Overloads for first output operator Func() const { return output; } Expr operator()(Expr x, Expr y, Expr z) const { return output(x, y, z); } Expr operator()(std::vector<Expr> args) const { return output(args); } Expr operator()(std::vector
<
Var
>
args) const { return output(args); } void schedule(const ScheduleParams ¶ms = {}); };
Func
, Expr
, etc).接著像是如此般地使用:
值得一提的是所有對子元件的輸入必須在子元件建構時明確地提供 (以其 ctor 參數的方式); 呼叫者必須負責提供這些資訊 (並沒有自呼叫者到子元件自動傳遞的概念)#include "/path/to/rgb_to_rcbcr.stub.h" class AwesomeFilter : public Generator<AwesomeFilter> { public: Input
<
Func
>
input{"input", Float(32), 3}; Output
<
Func
>
output{"output", Float(32), 3}; void generate() { // Snap image into buckets while still in RGB. quantized(x, y, c) = Quantize(input(x, y, c)); // Convert to YCbCr. rgb_to_ycbcr = RgbToYCbCr(this, {quantized}); // Do something awesome with it. Note that rgb_to_ycbcr autoconverts to a Func. output(x, y, c) = SomethingAwesome(rgb_to_ycbcr(x, y, c)); } void schedule() { // explicitly schedule the intermediate Funcs we used // (including any reusable Generators). quantized. .vectorize(x, natural_vector_size<float
>
()) .compute_at(rgb_to_ycbcr, y); rgb_to_ycbcr .vectorize(x, natural_vector_size
<
float
>
()) .compute_at(output, y); // *Also* call the schedule method for all reusable Generators we used, // so that they can schedule their own intermediate results as needed. // (Note that we may have to pass them appropriate values for ScheduleParam, // which vary from Generator to Generator; since RgbToYCbCr has none, // we don't need to pass any.) rgb_to_ycbcr.schedule(); } private: Var x, y, c; Func quantized; RgbToYCbCr rgb_to_ycbcr; Expr Quantize(Expr e) { ... } Expr SomethingAwesome(Expr e) { ... } };
若
RgbToYCbCr
有著輸入或輸出陣列會如何? 像是:在這例子中, 所產生的class RgbToYCbCrMulti : public Generator<RgbToYCbCrMulti> { Input
<
Func[3]
>
inputs{"inputs", Float(32), 3}; Input
<
float
>
coefficients{"coefficients", 1.f}; Output
<
Func[3]
>
outputs{"outputs", Float(32), 3}; ... };
RgbToYCbCrMulti
類別對於輸入需要 vector-of-Func (或
vector-of-Expr) , 並且提供 vector-of-Func 作為輸出成員:若class RgbToYCbCrMulti : public GeneratorStub { struct Inputs { std::vector<Func> inputs; std::vector
<
Expr
>
coefficients; }; RgbToYCbCr(GeneratorContext* context, const Inputs& inputs, const GeneratorParams& = {}}) { ... } ... std::vector<Func
>
outputs; };
RgbToYCbCr
有著多個輸出會如何? 像是:在這例子中, 所產生的class RgbToYCbCrMulti : public Generator<RgbToYCbCrMulti> { Input
<
Func> input{"input", Float(32), 3}; Output
<
Func
>
output{"output", Float(32), 3}; Output
<
Func
>
mask{"mask", UInt(8), 2}; Output
<
float
>
score{"score"}; ... };
RgbToYCbCrMulti
類別將所有的輸出搭配對應宣告在 Generator 中的命名作為結構成員:請注意純量輸出為了一致性依然以(零維度)函式方式表現. (並且注意到 "output" 並不是一個特別的命名; 只是發生在 Generator 第一個輸出的命名上)struct RgbToYCbCrMulti { ... Func output; Func mask; Func score; };
亦請注意第一個輸出總是以 "是" 與 "有著" 的關係表示: RgbToYCbCrMulti 複載了必要的運算元, 因此使用其 "output" 就如同使用以一個 Func, 在此例中:
這(的確)是多餘的, 但這是考量過的: 這讓大部分(單一輸出)的情況更為便利, 然而對多輸出的情況沒有影響.struct RgbToYCbCrMulti { ... Func output; operator Func() const { return output; } Expr operator()(Expr x, Expr y, Expr z) const { return output(x, y, z); } Expr operator()(std::vector<Expr> args) const { return output(args); } Expr operator()(std::vector
<
Var
>
args) const { return output(args); } ... };
consumer 可能如此般地使用:
當#include "/path/to/rgb_to_rcbcr_multi.stub.h" class AwesomeFilter : public Generator<AwesomeFilter> { ... void generate() { rgb_to_ycbcr_multi = RgbToYCbCrMulti(this, {input}); output(x, y, c) = SomethingAwesome(rgb_to_ycbcr_multi.output(x, y, c), rgb_to_ycbcr_multi.mask(x, y), rgb_to_ycbcr_multi.score()); } void schedule() { rgb_to_ycbcr_multi.output .vectorize(x, natural_vector_size
<
float>()) .compute_at(output, y); rgb_to_ycbcr_multi.mask .vectorize(x, natural_vector_size
<
float
>
()) .compute_at(output, y); rgb_to_ycbcr_multi.score .compute_root(); // Don't forget to call the schedule() function. rgb_to_ycbcr_multi.schedule(); } };
RgbToYCbCr 之中
有著想要設定來做 code generation 的 GeneratorParam 會如何? 在這情況, 當呼叫建構子時將傳入一個數值到一個
generator_params
的選擇性欄位.這可能會產生一個class RgbToYCbCr : public Generator<RgbToYCbCr> { GeneratorParam
<
Type
>
input_type{"input_type", UInt(8)}; GeneratorParam
<
bool
>
fast_but_less_accurate{"fast_but_less_accurate", false}; ... };
的
不一樣定義的
GeneratorParams, 其
有著每個 GeneratorParam 欄位並以適當預設值作初始
:接著可能手動地填入:struct GeneratorParams { Halide::Type input_type{UInt(8)}; bool fast_but_less_accurate{false}; };
或者, 在 C++ 編譯時期得知其型別時, 能更精練地使用一個範本化建構子(templated constructor):class AwesomeFilter : public Generator<AwesomeFilter> { void generate() { ... GeneratorParams generator_params; generator_params.input_type = Float(32); generator_params.fast_but_less_accurate = true; rgb_to_ycbcr = RgbToYCbCr(this, input, generator_params); ... } }
當class AwesomeFilter : public Generator<AwesomeFilter> { void generate() { ... rgb_to_ycbcr = RgbToYCbCr::make<float, true>(this, input); ... } }
RgbToYCbCr 中有著
ScheduleParam
會如何?
在這情形中, 產生的 stub 程式碼對於class RgbToYCbCr : public Generator<RgbToYCbCr> { ScheduleParam
<
LoopLevel
>
level{"level"}; ScheduleParam
<
bool
>
vectorize{"vectorize"}; void generate() { intermediate(x, y) = SomeExpr(x, y); output(x, y) = intermediate(x, y); } void schedule() { intermediate.compute_at(level); if (vectorize) intermediate.vectorize(x, natural_vector_width<float
>
()); } Var x, y; Func intermediate; };
ScheduleParams
將有著一個不一樣的宣告
:struct ScheduleParams { LoopLevel level{"undefined"}; bool vectorize{false}; };
並且可能如此地呼叫:
class AwesomeFilter : public Generator<AwesomeFilter> { ... void schedule() { rgb_to_ycbcr .vectorize(x, natural_vector_size<float>()) .compute_at(output, y); rgb_to_ycbcr.schedule({ // We want any intermediate products also at compute_at(output, y) LoopLevel(output, y), // vectorization: yes please true }); } ... }
沒有留言:
張貼留言