【從零開始學(xué)深度學(xué)習(xí)編譯器】十六,MLIR ODS要點(diǎn)總結(jié)上篇
前言
在【從零開始學(xué)深度學(xué)習(xí)編譯器】十二,MLIR Toy Tutorials學(xué)習(xí)筆記一 中提到MLIR是通過Dialect來統(tǒng)一各種不同級別的IR,即負(fù)責(zé)定義各種Operation(算子)。然后對Dialect和Operation的定義又是通過TabelGen規(guī)范構(gòu)造的,通過TableGen驅(qū)動MLIR的Operation定義也被稱作ODS( Operation Definition Specification) 。我們目前只是簡單認(rèn)識了Toy Tutorials的Dialect和Operation是如何通過ODS定義的,但對ODS本身的語法以及一些限制都沒有太多了解,這就導(dǎo)致在看一些相關(guān)工程的Operation定義時時常陷入迷惑,不知道某個字段是什么含義,或者說自定義Op的時候的應(yīng)當(dāng)如何聲明操作數(shù)和Attr(舉個例子,要將卷積的groups參數(shù)設(shè)置為可選的屬性,應(yīng)該怎么做)。
因此這篇文章(這是上篇,還有下篇)將基于MLIR的ODS文檔來講解ODS中的一些要點(diǎn),幫助我們更好的了解和上手MLIR。我會把官方文檔中需要注意的點(diǎn)拆成一些小的要點(diǎn)。下面文章中提到的TableGen和ODS不做特別區(qū)分,ODS中的語法也就是TableGen語法。這里介紹的要點(diǎn)在OneFlow對接MLIR時都或多或少用到了,感興趣的可以對照著看看OneFlow的這部分源碼。https://github.com/Oneflow-Inc/oneflow/blob/master/oneflow/ir/include/OneFlow/OneFlowOps.td。另外MLIR和TVM學(xué)習(xí)的所有的文章都匯總到了這個倉庫:https://github.com/BBuf/tvm_mlir_learn ,目前已經(jīng)獲得了170左右star,歡迎點(diǎn)點(diǎn)關(guān)注,謝謝。
由于篇幅原因以及我最近時間比較緊,這篇文章主要總結(jié)10個要點(diǎn)。剩下的一些要點(diǎn),在下篇文章給出。歡迎大家交流指正。
1. 為什么要使用ODS來定義Operation
在MLIR中要定義Operation支持用C++直接定義以及基于ODS框架定義兩種方法。使用C++直接定義要求我們繼承基類Op的一些構(gòu)造方法并重寫,對于每一個Op都要寫一段C++代碼??梢韵氲竭@樣做整個系統(tǒng)的Op定義部分會非常冗余,產(chǎn)生大量可重復(fù)代碼并且可讀性也會比較差。如果基于ODS來定義Operation,我們只需要將Op定義按照ODS的規(guī)范統(tǒng)一寫到一個td文件中,然后使用MLIR提供的代碼生成工具自動生成Operation的C++定義,這種完全auto codegen的方式非常優(yōu)雅的實(shí)現(xiàn)了Operation定義并且需要用戶操心的東西(也就是ODS的語法規(guī)范)更加直觀。
ODS是MLIR定義Operation的不二選擇,因此我們有必要學(xué)習(xí)ODS的語法規(guī)范。
2. TableGen語法
一個TableGen文件(以.td結(jié)尾)包含以下一些語法:
TableGen class類似于C++的class,可以作為模板或者基類去派生子類。TableGen def類似于C++的對象。以用一個TableGenclass的特化來聲明,例如,def MyDef: MyClass<...>;,也可以單獨(dú)使用def MyDef;。它不能用作模板,也不能作為基類去派生子類。TableGen dag是一種專門用于有向無環(huán)圖元素的類型。一個dag類型帶有一個操作符和零個或者多個參數(shù)。語法形如(operator arg0, arg1, argN.),其中operator可以是任意的TableGendef。參數(shù)可以是任何東西,包括dag本身。我們可以將名稱附加到操作符和參數(shù)上,如(MyOp:$op_name MyArg:$arg_name)。
想了解更多TableGen支持的類型和表達(dá)式可以點(diǎn)這個鏈接:https://llvm.org/docs/TableGen/ProgRef.html。
3. Operation定義
MLIR定義了幾個公共的結(jié)構(gòu)用于幫助定義Operation,并通過TableGen backend : OpDefinitionsGen提供它們的語義。這些公共結(jié)構(gòu)在文件OpBase.td中定義。主要包括:
Op類:這是定義Operation時使用的主要結(jié)構(gòu)。在特化該類時,通過下述結(jié)構(gòu)的幫助,指定與Operation有關(guān)的所有事實(shí)。Dialect類:歸屬于同一個邏輯組的Operation會被放置在同一個Dialect下。Dialect包含了方言等級信息。OpTrait類及其子類:它們用于指定Operation的特殊屬性和約束,包括Operation是否具有副作用、Op的輸出是否與輸入具有相同的形狀等。ins/outs標(biāo)記:這是OpDefinitionsGen后端內(nèi)置的兩個特殊標(biāo)記,分別引導(dǎo)操作數(shù)(operands)/屬性(attributes)、結(jié)果(results)的定義。TypeConstraint類及其子類:它們用于指定對操作數(shù)(operands)或結(jié)果(results)的約束。一個值得注意的子類是Type,它代表通用C++類型的約束。AttrConstraint類及其子類:它們用于指定對屬性(attributes)的約束。一個值得注意的子類是Attr,它代表值為通用類型的屬性的約束。
一個Operation是通過特化Op類定義的,特化后的Op類包含它需要的所有字段的具體內(nèi)容。舉個例子,tf.AvgPool定義如下:
def?TF_AvgPoolOp?:?TF_Op<"AvgPool",?[NoSideEffect]>?{
??let?summary?=?"Performs?average?pooling?on?the?input.";
??let?description?=?[{
Each?entry?in?`output`?is?the?mean?of?the?corresponding?size?`ksize`
window?in?`value`.
??}];
??let?arguments?=?(ins
????TF_FpTensor:$value,
????Confined4>]>:$ksize,
????Confined4>]>:$strides,
????TF_AnyStrAttrOf<["SAME",?"VALID"]>:$padding,
????DefaultValuedAttr"NHWC">:$data_format
??);
??let?results?=?(outs
????TF_FpTensor:$output
??);
??TF_DerivedOperandTypeAttr?T?=?TF_DerivedOperandTypeAttr<0>;
}
下面描述一下定義一個Operation所需的所有字段。有關(guān)支持的字段的完整列表,請參閱Op類的定義(就是OpBase.td)。
「Operation name」 : 就是Operation的名字,比如TensorFlow Dialect中的 tf.Add。「Operation documentation」 : Operation的文檔描述,包含 summary和description兩種,大家看下就懂,不多說。「Operation arguments」 :Operation的參數(shù),一個Operation有兩種參數(shù)一種是 operands即操作數(shù),一種是attributes屬性參數(shù)。其中屬性參數(shù)又分為Natural attributes和Derived attributes兩種,前者為自然屬性必須指定比如卷積的輸出通道數(shù),后者為派生屬性比如輸出Tensor的形狀。
操作數(shù)和屬性都在dag類型的arguments中被指定,以ins引導(dǎo):
let?arguments?=?(ins
??:$,
??...
??:$,
??...
);
這里是一個來自TypeConstraint類層次的TableGen def。與此類似的,是一個來自AttrConstraint類層次的TableGen def。在Constraints章節(jié)有更多詳細(xì)內(nèi)容。
「可變操作數(shù)」。定義一個可變操作數(shù),需要用 Variadic<...>把TypeConstraint包起來。通常,Operation是沒有可變操作數(shù)或者只有一個可變操作數(shù)。對于后一種情況,可以通過靜態(tài)可變操作數(shù)的定義很容易的推導(dǎo)出動態(tài)可變操作數(shù)。但是,如果一個Operation有多個可變長度操作數(shù)(可選的或可變長度的),在沒有來自該操作的進(jìn)一步信息的情況下,就不可能將動態(tài)操作數(shù)歸因于相應(yīng)的靜態(tài)可變長度操作數(shù)定義。因此,需要用SameVariadicOperandSize或AttrSizedOperandSegments特征來表明所有的可變長度操作數(shù)都有與之對應(yīng)的動態(tài)值。「可選操作數(shù)」。定義一個可選操作數(shù),需要用 Optional<...>把TypeConstraint包起來。解釋和可變操作數(shù)一樣。「可選屬性」。定義一個可選屬性,需要使用 OptionalAttr<...>把AttrConstraint包起來。「帶默認(rèn)值的可選屬性」。使用 DefaultValuedAttr<..., "...">把AttrConstraint包起來。DefaultValuedAttr的第二個參數(shù)應(yīng)該是包含C++默認(rèn)值的字符串。舉個例子,一個單精度浮點(diǎn)默認(rèn)值需要被指定為“0.5f”,一個整型數(shù)組的默認(rèn)值需要被指定為"{1, 2, 3}"。「限制屬性(Confining attributes)」。 Confined作為一種通用機(jī)制被提供,以幫助對值類型帶來的屬性約束進(jìn)行進(jìn)一步建模??梢酝ㄟ^Confined將較為原始的約束組合成為復(fù)雜約束。舉個例子,一個32bit的整型最小值為10,可以被表示為Confined。還有一些其它例子,比如]> IntMinValue:指定一個大于等于N的整型屬性等等。「Operation results」:類似操作數(shù),結(jié)果使用 tag類型的results聲明,使用outs引導(dǎo)。
let?results?=?(outs
??:$,
??...
);
還有「Operation regions」和「Operation successors」目前我還沒用過,暫時不清楚應(yīng)用場景。
「Op的特征和約束(Operation traits and constraints)」:特征是影響語法或語義的Operation屬性。MLIR C++的各種特征在
mlir::OpTrait命名空間中。Operation的特征、接口或者約束涉及多個操作數(shù)/屬性/結(jié)果時,要作為Op類的第二個模板參數(shù)傳入。它們都需要繼承于OpTrait類。詳見Constraints章節(jié)。
4. Operation自動生成的默認(rèn)構(gòu)建方法
定義了Operation之后,我們怎么構(gòu)建呢??每一個Operation,都會基于Operation的參數(shù)和Operation的返回值自動生成一些builers。舉個例子,給出如下的Operation定義:
def?MyOp?:?...?{
??let?arguments?=?(ins
????I32:$i32_operand,
????F32:$f32_operand,
????...,
????I32Attr:$i32_attr,
????F32Attr:$f32_attr,
????...
??);
??let?results?=?(outs
????I32:$i32_result,
????F32:$f32_result,
????...
??);
}
下面的builders被產(chǎn)生:
//?All?result-types/operands/attributes?have?one?aggregate?parameter.
//?所有?結(jié)果類型/操作數(shù)/屬性都集合為一個聚合參數(shù)。
static?void?build(OpBuilder?&odsBuilder,?OperationState?&odsState,
??????????????????ArrayRef?resultTypes,
??????????????????ValueRange?operands,
??????????????????ArrayRef?attributes) ;
//?Each?result-type/operand/attribute?has?a?separate?parameter.?The?parameters
//?for?attributes?are?of?mlir::Attribute?types.
//?每一個?結(jié)果類型/操作數(shù)/屬性?都是一個獨(dú)立的參數(shù)。屬性參數(shù)為 mlir::Attribute 類型
static?void?build(OpBuilder?&odsBuilder,?OperationState?&odsState,
??????????????????Type?i32_result,?Type?f32_result,?...,
??????????????????Value?i32_operand,?Value?f32_operand,?...,
??????????????????IntegerAttr?i32_attr,?FloatAttr?f32_attr,?...);
//?Each?result-type/operand/attribute?has?a?separate?parameter.?The?parameters
//?for?attributes?are?raw?values?unwrapped?with?mlir::Attribute?instances.
//?(Note?that?this?builder?will?not?always?be?generated.?See?the?following
//?explanation?for?more?details.)
//?每一個?結(jié)果類型/操作數(shù)/屬性?都是一個獨(dú)立的參數(shù)。
//?屬性參數(shù)是未經(jīng) mlir::Attribute 實(shí)例包裝的原始值。
//?(注意,該構(gòu)建器并不總是生成。詳見下列解釋獲得更多細(xì)節(jié)。)
static?void?build(OpBuilder?&odsBuilder,?OperationState?&odsState,
??????????????????Type?i32_result,?Type?f32_result,?...,
??????????????????Value?i32_operand,?Value?f32_operand,?...,
??????????????????APInt?i32_attr,?StringRef?f32_attr,?...);
//?Each?operand/attribute?has?a?separate?parameter?but?result?type?is?aggregate.
//?每一個?操作數(shù)/屬性?都是一個獨(dú)立的參數(shù)。但是結(jié)果全部集合為了一個聚合類型。
static?void?build(OpBuilder?&odsBuilder,?OperationState?&odsState,
??????????????????ArrayRef?resultTypes,
??????????????????Value?i32_operand,?Value?f32_operand,?...,
??????????????????IntegerAttr?i32_attr,?FloatAttr?f32_attr,?...) ;
//?All?operands/attributes?have?aggregate?parameters.
//?Generated?if?return?type?can?be?inferred.
//?這個構(gòu)建器只有在返回值類型能夠被推斷出的情況下,才會生成。
static?void?build(OpBuilder?&odsBuilder,?OperationState?&odsState,
??????????????????ValueRange?operands,?ArrayRef?attributes) ;
//?(And?manually?specified?builders?depending?on?the?specific?op.)
上面的代碼注釋翻譯已經(jīng)解釋了這些builder的不同之處。并且可能還存在一些其它的builder,請參考https://mlir.llvm.org/docs/OpDefinitions/#run-mlir-tblgen-to-see-the-generated-content 這里的文檔進(jìn)行查看。
5. 自定義builder方法
假設(shè)上面生成的C++代碼中構(gòu)造方法沒有我們所期待的,這個時候我們就需要自定義builder方法。比如:
def?MyOp?:?Op<"my_op",?[]>?{
??let?arguments?=?(ins?F32Attr:$attr);
??let?builders?=?[
????OpBuilder<(ins?"float":$val)>
??];
}
builders字段是添加到Op類的自定義構(gòu)建器列表。在這個例子中,我們提供了一個方便的builer,它接受浮點(diǎn)值而不是屬性。在使用TableGen dag的ODS中,許多函數(shù)聲明都使用ins前綴。緊隨其后的是用逗號分隔的列表,列表的每一項(xiàng)都是類型與帶$前綴的名字的組合。上述定義將會轉(zhuǎn)換成如下格式的builder:
class?MyOp?:?/*...*/?{
??/*...*/
??static?void?build(::mlir::OpBuilder?&builder,?::mlir::OperationState?&state,
????????????????????float?val);
};
注意,這個builder有兩個額外的前置參數(shù)。這些參數(shù)對于構(gòu)建Operation很有用。特別地,為了能夠通過該方法構(gòu)建Operation,必須向state填充該Operation的屬性,操作數(shù),域和返回值類型。builder可以用于構(gòu)建屬于Op的任意IR對象,例如類型或嵌套操作。當(dāng)類型與名字轉(zhuǎn)換為C++代碼時,它們應(yīng)該是有效的C++結(jié)構(gòu),一個類型(在Op的命名空間中)與一個標(biāo)識符(例如,class不是一個有效標(biāo)識符)。可以在ODS中直接提供builder的實(shí)現(xiàn),使用如下TableGen的代碼塊:
def?MyOp?:?Op<"my_op",?[]>?{
??let?arguments?=?(ins?F32Attr:$attr);
??let?builders?=?[
????OpBuilder<(ins?"float":$val),?[{
??????$_state.addAttribute("attr",?$_builder.getF32FloatAttr(val));
????}]>
??];
}
$_builder和$_state這兩個特殊參數(shù)等效于builder和state。ins部分中的參數(shù)可以被直接使用,比如val。builer的c++代碼實(shí)現(xiàn)會通過替換ODS中的特殊變量來完成,要保證builder ODS實(shí)現(xiàn)的其他部分是有效的C++結(jié)構(gòu)。雖然對代碼大小沒有限制,但我們鼓勵只在ODS中內(nèi)聯(lián)較短定義的builder,而將定義較長的builder的定義放在C++文件中。最后,如果某些參數(shù)需要默認(rèn)值,可以使用 CArg 定義它們以包裝類型和此值,如下所示:
def?MyOp?:?Op<"my_op",?[]>?{
??let?arguments?=?(ins?F32Attr:$attr);
??let?builders?=?[
????OpBuilder<(ins?CArg<"float",?"0.5f">:$val),?[{
??????$_state.addAttribute("attr",?$_builder.getF32FloatAttr(val));
????}]>
??];
}
轉(zhuǎn)換后的C++代碼中,默認(rèn)參數(shù)只在聲明中出現(xiàn),而不會在定義中出現(xiàn),這符合C++要求。
///?Header?file.
class?MyOp?:?/*...*/?{
??/*...*/
??static?void?build(::mlir::OpBuilder?&builder,?::mlir::OperationState?&state,
????????????????????float?val?=?0.5f);
};
///?Source?file.
MyOp::build(::mlir::OpBuilder?&builder,?::mlir::OperationState?&state,
????????????float?val)?{
??state.addAttribute("attr",?builder.getF32FloatAttr(val));
}
6. 聲明指令格式(Declarative Assembly Format)
Operation的聲明指令格式可以在與Operation操作數(shù)、屬性等匹配的聲明性字符串中指定。具有表達(dá)需要解析以構(gòu)建Operation的附加信息能力:
def?CallOp?:?Std_Op<"call",?...>?{
??let?arguments?=?(ins?FlatSymbolRefAttr:$callee,?Variadic:$args);
??let?results?=?(outs?Variadic);
??let?assemblyFormat?=?[{
????$callee?`(`?$args?`)`?attr-dict?`:`?functional-type($args,?results)
??}];
}
主要由三部分組成:
「Directives:指令」。指令是一種帶有可選參數(shù)的內(nèi)置函數(shù)??捎玫闹噶钣?code style="font-size: 14px;word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;background-color: rgba(27,31,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: #595959;">attr-dict, attr-dict-with-keyword,operands,ref等等。「字面值(Literals)」 。字面值是用``包裹起來的鍵值或者標(biāo)點(diǎn)符號。下列是有效的標(biāo)點(diǎn)符號集合: :, ,, =, <, >, (, ), {, }, [, ], ->, ?, +, *。\n標(biāo)點(diǎn)符號有另起一行的效果。如下:
let?assemblyFormat?=?[{
??`{`?`\n`?`?`?`?`?`this_is_on_a_newline`?`\n`?`}`?attr-dict
}];
%results?=?my.operation?{
??this_is_on_a_newline
}
內(nèi)容為空的字面量可用于刪除隱式插入某些字面量元素后的空格。例如)或者]等等。舉個例子,]可能出現(xiàn)在輸出output的末尾,但它并不是格式中的最后一個元素,在這個例子里可以使用 "]``"刪除掉后續(xù)的空格。
「Variables(變量)」。變量是注冊在Operation上的實(shí)體,例如Operation的參數(shù)(屬性或操作數(shù)),域,結(jié)果,后繼者,等等。在 CallOp中,變量代表$callee和$args。屬性變量將顯示其各自的值類型。除非其值的類型可以構(gòu)造,在這種情況下,屬性變量的值類型可以省略。
7. 自定義指令(Custom Directives) & 可選組(Optional Groups)
聲明指令格式規(guī)范在格式化一個Operation的時候能夠處理大部分的普通場景。對于那些想要在格式中指定Operations的某一部分的Op,聲明式語法是不支持的,這個時候可以嘗試使用自定義指令。
在某些情況下,Operations可能具有“可選”信息,例如 屬性或一組空的可變參數(shù)操作數(shù)。在這些情況下,可以根據(jù)此信息的存在將匯編格式的一部分標(biāo)記為可選。
這兩部分比較復(fù)雜,我還沒用到,所以這里不展開,感興趣請看官方文檔。
8. 類型推斷
格式的一項(xiàng)要求是操作數(shù)和結(jié)果的類型必須始終存在。在某些情況下,可以通過類型約束或其他可用信息來推斷變量的類型。在這些情況下,可以從格式中省略該變量的類型。
「Buildable Types??蓸?gòu)建類型」 。一些類型約束可能只有一種表示,允許它們直接構(gòu)建;例如 I32或Index類型。ODS 中的類型可以通過設(shè)置builderCall字段或從BuildableType類繼承來將自己標(biāo)記為可構(gòu)建。「Trait Equality Constraints。特征等價約束」。有許多Operations具有在Operations上注冊為已知類型相等特征的約束;例如, selectOperation的真、假和結(jié)果值通常具有相同的類型。匯編格式可以檢查這些等價的約束以辨別缺失變量的類型。當(dāng)前支持的特征有:AllTypesMatch、TypesMatchWith、SameTypeOperands和SameOperandsAndResultType。「InferTypeOpInterface」 。實(shí)現(xiàn) InferTypeOpInterface的Operations可以在其匯編格式中省略其結(jié)果類型,因?yàn)榭梢詮牟僮鲾?shù)中推斷出結(jié)果類型。「hasCanonicalizer」。此布爾字段指示是否已為此Operation定義規(guī)范化模式。如果它是 1,那么::getCanonicalizationPatterns()應(yīng)該被定義。「hasCanonicalizeMethod」。當(dāng)此布爾字段設(shè)置為 true時,表示操作為簡單的“matchAndRewrite”樣式規(guī)范化模式實(shí)現(xiàn)了canonicalize方法。如果hasCanonicalizer為 0,則實(shí)現(xiàn)::getCanonicalizationPatterns()的實(shí)現(xiàn)來調(diào)用此函數(shù)。「hasFolder」。此布爾字段指示是否已為此操作定義了通用折疊規(guī)則。如果它是 1,那么::fold()應(yīng)該被定義。
9. 額外聲明
表驅(qū)動操作定義的目標(biāo)之一是為每個操作自動生成盡可能多的邏輯和方法。話雖如此,總會有無法涵蓋的長尾案例。對于這種情況,您可以使用 extraClassDeclaration。extraClassDeclaration 中的代碼將逐字復(fù)制到生成的 C++ op 類。
請注意,extraClassDeclaration 是一種針對高級用戶的長尾案例的機(jī)制;對于尚未實(shí)施的廣泛適用的情況,改善基礎(chǔ)設(shè)施是可取的。
10. 生成C++代碼
OpDefinitionsGen (https://github.com/llvm/llvm-project/blob/main/mlir/tools/mlir-tblgen/OpDefinitionsGen.cpp)處理Operation定義規(guī)范文件(.td文件)并生成兩個包含相應(yīng) C++ 代碼的文件:一個用于聲明,另一個用于定義。前者通過 -gen-op-decls 命令行選項(xiàng)生成,而后者通過 -gen-op-defs 選項(xiàng)生成。
定義文件包含所有的op方法定義,可以通過定義GET_OP_CLASSES來包含和啟用。對于每個操作,OpDefinitionsGen 生成一個操作類和一個操作數(shù)適配器類(operand adaptor class)。此外,它還包含一個以逗號分隔的所有已定義Operations的列表,可以通過定義 GET_OP_LIST 來包含和啟用這些操作。
「Class name and namespaces」 。
對于每個Operation,其生成的C++類名是使用TableGen def為前綴的名字,并刪除了Dialect前綴。第一個_用作分隔符。例如,對于def TF_AddOp,C++類名會是AddOp。我們移除了TF前綴,因?yàn)樗嵌鄠€Operation作用域。其它Dialect也可以定義自己的AddOps。
生成的C++類的namespace將來自Dialect的cppNamespace字段。舉個例子,如果一個Dialect的Namespace是A::B,那么該Dialect的Op將被放置在namespace A { namespace B { ... } }。如果Dialect沒有指定cppNamespace,我們就使用方言的名稱作為命名空間。
這意味著生成的 C++ 類的名稱不一定與Operation 名稱中的操作名稱完全匹配。這是為了允許靈活命名以滿足編碼風(fēng)格要求。
「Operand adaptors」
對于每個Operation,MLIR會自動生成一個操作數(shù)適配器。這個類解決了訪問作為列表值提供的操作數(shù)而不使用“魔術(shù)“”常量的問題。操作數(shù)適配器引用一個 Value 數(shù)組,并提供與Operation類中名稱相同的方法來訪問它們。例如,對于二元算術(shù)運(yùn)算,它可以提供 .lhs() 來訪問第一個操作數(shù)和 .rhs() 來訪問第二個操作數(shù)。操作數(shù)適配器類與Operation類位于同一命名空間中,類的名稱由Operation類的名稱后面接一個Adaptor組成。
操作數(shù)適配器也可以用于處理Operation的函數(shù)模板:
template?<typename?BinaryOpTy>
std::pair?zip(BinaryOpTy?&&op)? {
??return?std::make_pair(op.lhs(),?op.rhs());;
}
void?process(AddOp?op,?ArrayRef?newOperands) ?{
??zip(op);
??zip(Adaptor(newOperands));
??/*...*/
}
在OneFlow中,我們可以看到生成的UserOpAdaptor代碼。里面提供了一系列接口可以訪問Operation的操作數(shù)以及相關(guān)屬性。
//===----------------------------------------------------------------------===//
//?::mlir::oneflow::UserOp?declarations
//===----------------------------------------------------------------------===//
class?UserOpAdaptor?{
public:
??UserOpAdaptor(::mlir::ValueRange?values,?::mlir::DictionaryAttr?attrs,?::mlir::RegionRange?regions?=?{});
??UserOpAdaptor(UserOp?&op);
??::mlir::ValueRange?getOperands();
??std::pair<unsigned,?unsigned>?getODSOperandIndexAndLength(unsigned?index);
??::mlir::ValueRange?getODSOperands(unsigned?index);
??::mlir::ValueRange?data_input();
??::mlir::ValueRange?ctrl_inputs();
??::mlir::DictionaryAttr?getAttributes();
??::mlir::StringAttr?op_name();
??::mlir::BoolAttr?trainable();
??::mlir::StringAttr?device_tag();
??::mlir::ArrayAttr?device_name();
??::mlir::IntegerAttr?scope_symbol_id();
??::mlir::ArrayAttr?hierarchy();
??::mlir::DenseIntElementsAttr?operand_segment_sizes();
??::mlir::DenseIntElementsAttr?result_segment_sizes();
??::mlir::StringAttr?op_type_name();
??::mlir::ArrayAttr?input_lbn_segment_keys();
??::mlir::ArrayAttr?input_lbn_segment_sizes();
??::mlir::ArrayAttr?output_lbn_segment_keys();
??::mlir::ArrayAttr?output_lbn_segment_sizes();
??::mlir::ArrayAttr?output_lbns();
??::mlir::LogicalResult?verify(::mlir::Location?loc);
private:
??::mlir::ValueRange?odsOperands;
??::mlir::DictionaryAttr?odsAttrs;
??::mlir::RegionRange?odsRegions;
};
11. 約束
約束(Constraint)是表驅(qū)動Operation定義中的一個核心概念:Operation驗(yàn)證和圖Operation匹配都是基于滿足約束。因此,Operation定義和重寫規(guī)則都直接涉及寫入約束。MLIR在OpBase.td(https://github.com/llvm/llvm-project/blob/main/mlir/include/mlir/IR/OpBase.td)中定義了Constraint基類。一個Operation的約束可以覆蓋不同的范圍,可能是:
僅關(guān)注單個屬性(例如大于 5 的 32 位整數(shù)) 多個操作數(shù)和結(jié)果(例如,第一個結(jié)果的形狀必須與第一個操作數(shù)(可理解為Tensor)相同) 操作本身固有的。(例如沒有副作用,參考Transpose Op消除那個案例)
我們將它們分別稱為單實(shí)體約束、多實(shí)體約束和特征。
?這里就不寫小結(jié)了,下篇文章總結(jié)完再寫。
?
