【從零開始學深度學習編譯器】十七,MLIR ODS要點總結(jié)下篇
前言
這一節(jié)在【從零開始學深度學習編譯器】十六,MLIR ODS要點總結(jié)上篇 的基礎(chǔ)上補充完整了ODS的要點。約束和屬性的定義都是MLIR中相當重要的元素,至于類型的定義個人認為了解即可,等到我們需要自定義類型的時候再仔細研究。最后MLIR的語法比較晦澀,初學者可以借助mlir-tblgen來輔助debug。
在這兩篇文章里,我跟著MLIR的ODS規(guī)范完整走了一遍并總結(jié)了14個要點,對于每一個要點我都在OneFlow MLIR的Op定義中進行了對照,并給出了一些示例代碼和位置。希望對讀者入門MLIR有幫助。上篇和下篇對應(yīng)的markdown文件我放在:https://github.com/BBuf/tvm_mlir_learn 倉庫了,有需要的自取。
11. 約束(這個很重要)
約束(Constraint)是表驅(qū)動Operation定義中的一個核心概念:Operation驗證和圖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ù),屬性或結(jié)果的約束在實體的聲明位置進行指定,如Operation arguments 和 Operation results 中(在【從零開始學深度學習編譯器】十六,MLIR ODS要點總結(jié)上篇 中總結(jié)了Operation arguments和Operation results需要注意的知識)。
多實體約束。多實體約束在
https://github.com/llvm/llvm-project/blob/main/mlir/include/mlir/IR/OpBase.td中被建模為PredOpTrait類(是OpTrait的一個子類)。查看OpBase.td獲取完整列表。特征。特征是Operation的內(nèi)在屬性,例如是否具有副作用、可交換與否、是否是終止符等。這些約束應(yīng)指定為 Op 類模板參數(shù),如【從零開始學深度學習編譯器】十六,MLIR ODS要點總結(jié)上篇 中第三節(jié)的Op的特征和約束(Operation traits and constraints) 所示。特征在
https://github.com/llvm/llvm-project/blob/main/mlir/include/mlir/IR/OpBase.td中被建模成一個NativeOpTrait類(OpTrait的一個子類)。它們得到支持并將被翻譯成相應(yīng)的 C++mlir::OpTrait類。如何指定新的約束?要寫一個新的約束,我們必須為它提供一個謂詞并指定一個描述名。使用
Pred類建模的謂詞是構(gòu)成約束的核心。約束的謂詞通常以嵌套的方式構(gòu)建,有兩種類型的謂詞:1.CPred:原始的葉子節(jié)點謂詞。2.復合謂詞:由使用謂詞組合器的子謂詞組成的謂詞(conjunction:And, disjunction:Or, negation:Neg, substitution:SubstLeaves, concatenation:Concat)。CPred是構(gòu)成更復雜謂詞的基礎(chǔ)。它是TableGen 視角下的“原子”謂詞,是TableGen 與C++ 之間的“接口”。里面已經(jīng)是 C++ 代碼了,它會被當作不透明的字符串來處理,并帶有特殊的占位符來替換。我們可以將任何返回布爾值的 C++ 代碼放在CPred中,包括計算表達式、調(diào)用函數(shù)、調(diào)用類方法等。
為了幫助與 C++ 環(huán)境交互,提供了一些特殊的占位符來引用使用該謂詞的上下文中的實體。它們充當封閉環(huán)境的“鉤子”。這包括 $_builder、$_op 和 $_self:
$_builder會被替換成一個mlir::Builder實例,以便我們可以訪問常見的構(gòu)建方法。$_op會被當前的Operation替換,以便我們可以訪問當前Operation的信息。$_self會被替換為該謂詞所附加的實體。例如,BoolAttr是一個包含CPred<"$_self.isa的屬性約束。那么對于()"> BoolAttr:$attr,$_self將被$attr替換。對于類型約束,它有點特殊,因為我們希望每個類型定義的約束自然讀取,并且我們希望將類型約束直接附加到操作數(shù)/結(jié)果,$_self將被操作數(shù)/結(jié)果的類型替換。例如,對于F32:$operand中的F32,它的$_self將被擴展為operand(...).getType()。
例如,要寫一個屬性 attr 是一個 IntegerAttr,在 C++ 中我們可以調(diào)用 attr.isa來實現(xiàn)。這行代碼也可以作為 $_self.isa 包裝在 CPred 中,其中 $_self 作為特殊占位符,在擴展時由當前屬性 attr 替換來實現(xiàn)相同的功能(指在Tablegen中)。
對于更復雜的謂詞,我們可以將其包裝在單個 CPred 中,也可以使用謂詞組合器將它們組合起來。例如,要寫出屬性 attr 是 32 位或 64 位整數(shù)的約束,可以將其寫為:
And<[
??CPred<"$_self.isa()" >,
??Or<[
????CPred<"$_self.cast().getType().isInteger(32)" >,
????CPred<"$_self.cast().getType().isInteger(64)" >
??]>
]>
(注意,上面只是用一個熟悉的例子來展示如何使用CPred和謂詞組合器來編寫復雜的謂詞。具體來說,對于整數(shù)屬性,OpBase.td已經(jīng)定義了I32Attr和I64Attr。所以我們實際上可以重用它們來編寫它 Or<[I32Attr.predicate, I64Attr.predicate]>.)
這里再以O(shè)neFlow的一個例子來講解一下,我們定義了一個IsGPU的約束:
def?IsGPU:?Constraint"$0.getValue().equals(\"gpu\")">,?"is?GPU?device">;
然后OneFlow在Transformer部分做了一個定制優(yōu)化,就是將Scale和Tril這兩個連續(xù)的Kernel融合成一個大的Kernel,這樣可以省掉一部分內(nèi)存讀寫的時間。但這個融合的kernel只在GPU的情況下生效,所以這個時候就需要判斷當前計算圖檢測到的Scale和Tril這兩個Operation的device是否是GPU的,就需要這個約束。FusedScaleTrilPattern這個Pass的實現(xiàn)如下,可以看到在最后使用了IsGPU這個約束。
def?FusedScaleTrilPattern?:?Pat<
??(
????OneFlow_TrilOp
????(
??????OneFlow_ScalarMulOp
????????$x,
????????$scale_op_name,
????????$scale_trainable,
????????$scale_device_tag,
????????$scale_device_name,
????????$scale_scope_symbol_id,
????????$scale_hierarchy,
????????$has_int_operand,
????????$has_float_operand,
????????$int_operand,
????????$float_operand
????),
????$tril_op_name,
????$tril_trainable,
????$tril_device_tag,
????$tril_device_name,
????$tril_scope_symbol_id,
????$tril_hierarchy,
????$diagonal,
????$floating_fill_value,
????$integer_fill_value,
????$is_floating_fill_value
??),
??(OneFlow_FusedScaleTrilOp?$x,
????$tril_op_name,
????$tril_trainable,
????$tril_device_tag,
????$tril_device_name,
????$tril_scope_symbol_id,
????$tril_hierarchy,
????$diagonal,
????$floating_fill_value,
????$integer_fill_value,
????$is_floating_fill_value,
????$float_operand,
????$int_operand,
????$has_float_operand
??),
??[
????(IsGPU?$tril_device_tag),
????(IsGPU?$scale_device_tag)
??]
>;
這個Pass的功能就是檢測到連續(xù)的Scale+Tril Operation就將這兩個Operation融合成一個FusedScaleTril Operation。
如果謂詞用 CPred 和謂詞組合器一起編寫非常復雜,我們也可以將其編寫為普通的 C++ 函數(shù),并使用 CPred 作為“調(diào)用”函數(shù)的一種方式。例如,要驗證屬性 attr 是否具有某些屬性,我們可以編寫一個 C++ 函數(shù),如:
bool?HasSomeProperty(Attribute?attr)?{?...?}
然后定義Op如下:
def?HasSomeProperty?:?AttrConstraint"HasSomeProperty($_self)">,
?????????????????????????????????????"has?some?property">;
def?MyOp?:?Op<...>?{
??let?arguments?=?(ins
????...
????HasSomeProperty:$attr
??);
}
至于我們是否應(yīng)該使用單個 CPred 包裝整個表達式、多個帶有謂詞組合器的 CPreds 或單個 CPred “調(diào)用”一個函數(shù)來定義謂詞,沒有明確的標準。使用 CPred 和謂詞組合器進行定義是可取的,因為它將更多信息(而不是隱藏 C++ 函數(shù)背后的所有邏輯)公開到操作定義規(guī)范中,以便它可以潛在地驅(qū)動更多的自動生成案例。但它需要一個很好的通用謂詞庫作為構(gòu)建塊,以避免重復,目前正在研究中。
12. 屬性定義(很重要+1)
屬性是編譯期就知道的Operation的常量。ODS 在 C++ 屬性類上提供屬性包裝器。MLIR 的核心 IR 庫中定義了一些常見的 C++ 屬性類(https://github.com/llvm/llvm-project/blob/main/mlir/include/mlir/IR/Attributes.h)。ODS 允許在 TableGen 中使用這些屬性來定義Operation,可能具有更細粒度的約束。比如StrAttr直接映射到StringAttr;F32Attr/F64Attr 要求 FloatAttr 額外具有一定的位寬。ODS屬性被定義為具有存儲類型(對應(yīng)于存儲屬性的mlir::Attribute類),返回類型(對應(yīng)于生成的getters幫助函數(shù)的C++返回類型)以及在內(nèi)部存儲類型和幫助函數(shù)進行互轉(zhuǎn)的方法。
屬性裝飾器。有一些重要的屬性適配器/裝飾器/修飾符可以應(yīng)用于 ODS 屬性以指定常見的附加屬性,如可選性、默認值等。
DefaultValuedAttr:為一個屬性指定默認值。OptionalAttr:將一個屬性指定為可選的。Confined:Confined作為一種通用機制被提供,以幫助對值類型帶來的屬性約束進行進一步建模??梢酝ㄟ^Confined將較為原始的約束組合成為復雜約束。舉個例子,一個32bit的整型最小值為10,可以被表示為Confined。還有一些其它例子,比如]> IntMinValue:指定一個大于等于N的整型屬性等等。
枚舉屬性 。某些屬性只能從預(yù)定義的enum獲取值,例如,比較op的比較類型。為了定義這些屬性,ODS 提供了幾種機制:StrEnumAttr、IntEnumAttr 和 BitEnumAttr。
StrEnumAttr:每個enum case 都是一個字符串,屬性在op中存儲為StringAttr。IntEnumAttr:每個enum case 都是一個整數(shù),屬性在op中存儲為IntegerType。BitEnumAttr:每個 enum case 都是一個位,屬性在 op 中存儲為IntegerAttr。
所有這些 *EnumAttr 屬性都需要通過其對應(yīng)的 *EnumAttrCase 完全指定所有允許的情況。有了這個,ODS 能夠生成額外的驗證以只接受允許的案例。 為了促進 *EnumAttrs 和它們的 C++ 使用者之間的交互,EnumsGen(https://github.com/llvm/llvm-project/blob/main/mlir/tools/mlir-tblgen/EnumsGen.cpp) TableGen 后端可以生成一些常見的實用程序:C++ 枚舉類、用于枚舉類的 llvm::DenseMapInfo、從/到字符串的轉(zhuǎn)換函數(shù)。這是通過 mlir-tblgen 的 -gen-enum-decls 和 -gen-enum-defs 命令行選項控制的。
例如,給定下面的EnumAttr:
def?Case15:?I32EnumAttrCase<"Case15",?15>;
def?Case20:?I32EnumAttrCase<"Case20",?20>;
def?MyIntEnum:?I32EnumAttr<"MyIntEnum",?"An?example?int?enum",
???????????????????????????[Case15,?Case20]>?{
??let?cppNamespace?=?"Outer::Inner";
??let?stringToSymbolFnName?=?"ConvertToEnum";
??let?symbolToStringFnName?=?"ConvertToString";
}
以下代碼將通過 mlir-tblgen -gen-enum-decls 生成:
namespace?Outer?{
namespace?Inner?{
//?An?example?int?enum
enum?class?MyIntEnum?:?uint32_t?{
??Case15?=?15,
??Case20?=?20,
};
llvm::Optional?symbolizeMyIntEnum(uint32_t) ;
llvm::StringRef?ConvertToString(MyIntEnum);
llvm::Optional?ConvertToEnum(llvm::StringRef) ;
inline?constexpr?unsigned?getMaxEnumValForMyIntEnum()?{
??return?20;
}
}?//?namespace?Inner
}?//?namespace?Outer
namespace?llvm?{
template<>?struct?DenseMapInfo?{
??using?StorageInfo?=?llvm::DenseMapInfo<uint32_t>;
??static?inline?Outer::Inner::MyIntEnum?getEmptyKey()?{
????return?static_cast(StorageInfo::getEmptyKey());
??}
??static?inline?Outer::Inner::MyIntEnum?getTombstoneKey()?{
????return?static_cast(StorageInfo::getTombstoneKey());
??}
??static?unsigned?getHashValue(const?Outer::Inner::MyIntEnum?&val)?{
????return?StorageInfo::getHashValue(static_cast<uint32_t>(val));
??}
??static?bool?isEqual(const?Outer::Inner::MyIntEnum?&lhs,?const?Outer::Inner::MyIntEnum?&rhs)?{
????return?lhs?==?rhs;
??}
};
}
以下代碼將通過 mlir-tblgen -gen-enum-defs 生成:
namespace?Outer?{
namespace?Inner?{
llvm::StringRef?ConvertToString(MyIntEnum?val)?{
??switch?(val)?{
????case?MyIntEnum::Case15:?return?"Case15";
????case?MyIntEnum::Case20:?return?"Case20";
??}
??return?"";
}
llvm::Optional?ConvertToEnum(llvm::StringRef?str)? {
??return?llvm::StringSwitch>(str)
??????.Case("Case15",?MyIntEnum::Case15)
??????.Case("Case20",?MyIntEnum::Case20)
??????.Default(llvm::None);
}
llvm::Optional?symbolizeMyIntEnum(uint32_t?value)? {
??switch?(value)?{
??case?15:?return?MyIntEnum::Case15;
??case?20:?return?MyIntEnum::Case20;
??default:?return?llvm::None;
??}
}
}?//?namespace?Inner
}?//?namespace?Outer
對于以下 BitEnumAttr 定義類似:
def?None:?BitEnumAttrCase<"None",?0x0000>;
def?Bit1:?BitEnumAttrCase<"Bit1",?0x0001>;
def?Bit2:?BitEnumAttrCase<"Bit2",?0x0002>;
def?Bit3:?BitEnumAttrCase<"Bit3",?0x0004>;
def?MyBitEnum:?BitEnumAttr<"MyBitEnum",?"An?example?bit?enum",
???????????????????????????[None,?Bit1,?Bit2,?Bit3]>;
我們得到:
//?An?example?bit?enum
enum?class?MyBitEnum?:?uint32_t?{
??None?=?0,
??Bit1?=?1,
??Bit2?=?2,
??Bit3?=?4,
};
llvm::Optional?symbolizeMyBitEnum(uint32_t) ;
std::string?stringifyMyBitEnum(MyBitEnum);
llvm::Optional?symbolizeMyBitEnum(llvm::StringRef) ;
inline?MyBitEnum?operator|(MyBitEnum?lhs,?MyBitEnum?rhs)?{
??return?static_cast(static_cast<uint32_t>(lhs)?|?static_cast<uint32_t>(rhs));
}
inline?MyBitEnum?operator&(MyBitEnum?lhs,?MyBitEnum?rhs)?{
??return?static_cast(static_cast<uint32_t>(lhs)?&?static_cast<uint32_t>(rhs));
}
inline?bool?bitEnumContains(MyBitEnum?bits,?MyBitEnum?bit)?{
??return?(static_cast<uint32_t>(bits)?&?static_cast<uint32_t>(bit))?!=?0;
}
namespace?llvm?{
template<>?struct?DenseMapInfo<::MyBitEnum>?{
??using?StorageInfo?=?llvm::DenseMapInfo<uint32_t>;
??static?inline?::MyBitEnum?getEmptyKey()?{
????return?static_cast<::MyBitEnum>(StorageInfo::getEmptyKey());
??}
??static?inline?::MyBitEnum?getTombstoneKey()?{
????return?static_cast<::MyBitEnum>(StorageInfo::getTombstoneKey());
??}
??static?unsigned?getHashValue(const?::MyBitEnum?&val)?{
????return?StorageInfo::getHashValue(static_cast<uint32_t>(val));
??}
??static?bool?isEqual(const?::MyBitEnum?&lhs,?const?::MyBitEnum?&rhs)?{
????return?lhs?==?rhs;
??}
};
std::string?stringifyMyBitEnum(MyBitEnum?symbol)?{
??auto?val?=?static_cast<uint32_t>(symbol);
??//?Special?case?for?all?bits?unset.
??if?(val?==?0)?return?"None";
??llvm::SmallVector2>?strs;
??if?(1u?&?val)?{?strs.push_back("Bit1");?val?&=?~1u;?}
??if?(2u?&?val)?{?strs.push_back("Bit2");?val?&=?~2u;?}
??if?(4u?&?val)?{?strs.push_back("Bit3");?val?&=?~4u;?}
??if?(val)?return?"";
??return?llvm::join(strs,?"|");
}
llvm::Optional?symbolizeMyBitEnum(llvm::StringRef?str)? {
??//?Special?case?for?all?bits?unset.
??if?(str?==?"None")?return?MyBitEnum::None;
??llvm::SmallVector2>?symbols;
??str.split(symbols,?"|");
??uint32_t?val?=?0;
??for?(auto?symbol?:?symbols)?{
????auto?bit?=?llvm::StringSwitchuint32_t>>(symbol)
??????.Case("Bit1",?1)
??????.Case("Bit2",?2)
??????.Case("Bit3",?4)
??????.Default(llvm::None);
????if?(bit)?{?val?|=?*bit;?}?else?{?return?llvm::None;?}
??}
??return?static_cast(val);
}
llvm::Optional?symbolizeMyBitEnum(uint32_t?value)? {
??//?Special?case?for?all?bits?unset.
??if?(value?==?0)?return?MyBitEnum::None;
??if?(value?&?~(1u?|?2u?|?4u))?return?llvm::None;
??return?static_cast(value);
}
在OneFlow-MLIR中同樣也有枚舉屬性的定義用來處理OneFlow的各種數(shù)據(jù)類型,代碼如下:
#ifndef?ONEFLOW_ENUMS
#define?ONEFLOW_ENUMS
def?OneFlow_InvalidDataType?:?I32EnumAttrCase<"DT_InvalidDataType",?0>;
def?OneFlow_Char?:?I32EnumAttrCase<"DT_Char",?1>;
def?OneFlow_Float?:?I32EnumAttrCase<"DT_Float",?2>;
def?OneFlow_Double?:?I32EnumAttrCase<"DT_Double",?3>;
def?OneFlow_Int8?:?I32EnumAttrCase<"DT_Int8",?4>;
def?OneFlow_Int32?:?I32EnumAttrCase<"DT_Int32",?5>;
def?OneFlow_Int64?:?I32EnumAttrCase<"DT_Int64",?6>;
def?OneFlow_UInt8?:?I32EnumAttrCase<"DT_UInt8",?7>;
def?OneFlow_OFRecord?:?I32EnumAttrCase<"DT_OFRecord",?8>;
def?OneFlow_Float16?:?I32EnumAttrCase<"DT_Float16",?9>;
def?OneFlow_TensorBuffer:?I32EnumAttrCase<"DT_TensorBuffer",?10>;
def?OneFlow_DataType:?I32EnumAttr<"DataType",?"OneFlow?Data?Type?enum",
??[
????OneFlow_InvalidDataType,
????OneFlow_Char,
????OneFlow_Float,
????OneFlow_Double,
????OneFlow_Int8,
????OneFlow_Int32,
????OneFlow_Int64,
????OneFlow_UInt8,
????OneFlow_OFRecord,
????OneFlow_Float16,
????OneFlow_TensorBuffer,
??]
>?{
??let?cppNamespace?=?"::mlir::oneflow";
??let?stringToSymbolFnName?=?"ConvertToEnum";
??let?symbolToStringFnName?=?"ConvertToString";
}
#endif?//?ONEFLOW_ENUMS
我們可以觀察一下它生成的enum屬性聲明:
/*===-?TableGen'erated?file?-------------------------------------*-?C++?-*-===*\
|*????????????????????????????????????????????????????????????????????????????*|
|*?Enum?Utility?Declarations??????????????????????????????????????????????????*|
|*????????????????????????????????????????????????????????????????????????????*|
|*?Automatically?generated?file,?do?not?edit!?????????????????????????????????*|
|*????????????????????????????????????????????????????????????????????????????*|
\*===----------------------------------------------------------------------===*/
namespace?mlir?{
namespace?oneflow?{
//?OneFlow?Data?Type?enum
enum?class?DataType?:?uint32_t?{
??DT_InvalidDataType?=?0,
??DT_Char?=?1,
??DT_Float?=?2,
??DT_Double?=?3,
??DT_Int8?=?4,
??DT_Int32?=?5,
??DT_Int64?=?6,
??DT_UInt8?=?7,
??DT_OFRecord?=?8,
??DT_Float16?=?9,
??DT_TensorBuffer?=?10,
};
::llvm::Optional?symbolizeDataType(uint32_t) ;
::llvm::StringRef?ConvertToString(DataType);
::llvm::Optional?ConvertToEnum(::llvm::StringRef) ;
inline?constexpr?unsigned?getMaxEnumValForDataType()?{
??return?10;
}
inline?::llvm::StringRef?stringifyEnum(DataType?enumValue)?{
??return?ConvertToString(enumValue);
}
template?<typename?EnumType>
::llvm::Optional?symbolizeEnum(::llvm::StringRef) ;
template?<>
inline?::llvm::Optional?symbolizeEnum(::llvm::StringRef?str)?{
??return?ConvertToEnum(str);
}
class?DataTypeAttr?:?public?::mlir::IntegerAttr?{
public:
??using?ValueType?=?DataType;
??using?::mlir::IntegerAttr::IntegerAttr;
??static?bool?classof(::mlir::Attribute?attr);
??static?DataTypeAttr?get(::mlir::MLIRContext?*context,?DataType?val);
??DataType?getValue()?const;
};
}?//?namespace?oneflow
}?//?namespace?mlir
namespace?llvm?{
template<>?struct?DenseMapInfo<::mlir::oneflow::DataType>?{
??using?StorageInfo?=?::llvm::DenseMapInfo<uint32_t>;
??static?inline?::mlir::oneflow::DataType?getEmptyKey()?{
????return?static_cast<::mlir::oneflow::DataType>(StorageInfo::getEmptyKey());
??}
??static?inline?::mlir::oneflow::DataType?getTombstoneKey()?{
????return?static_cast<::mlir::oneflow::DataType>(StorageInfo::getTombstoneKey());
??}
??static?unsigned?getHashValue(const?::mlir::oneflow::DataType?&val)?{
????return?StorageInfo::getHashValue(static_cast<uint32_t>(val));
??}
??static?bool?isEqual(const?::mlir::oneflow::DataType?&lhs,?const?::mlir::oneflow::DataType?&rhs)?{
????return?lhs?==?rhs;
??}
};
}
實現(xiàn)部分就不貼了,這里貼了過長的代碼了。
13. 類型定義(我只是簡單了解了一下)
MLIR 定義了 TypeDef 類層次結(jié)構(gòu),以支持根據(jù)其規(guī)范生成數(shù)據(jù)類型。類型是通過特化 TypeDef 類來定義的,該類具有它所需的所有字段的具體內(nèi)容。例如,整數(shù)類型可以定義為:
//?All?of?the?types?will?extend?this?class.
class?Test_Type?: ?TypeDef?{?}
//?An?alternate?int?type.
def?IntegerType?:?Test_Type<"TestInteger">?{
??let?mnemonic?=?"int";
??let?summary?=?"An?integer?type?with?special?semantics";
??let?description?=?[{
????An?alternate?integer?type.?This?type?differentiates?itself?from?the
????standard?integer?type?by?not?having?a?SignednessSemantics?parameter,?just
????a?width.
??}];
??let?parameters?=?(ins?"unsigned":$width);
??//?We?define?the?printer?inline.
??let?printer?=?[{
????$_printer?<"int<"?<width?<">";
??}];
??//?The?parser?is?defined?here?also.
??let?parser?=?[{
????if?($_parser.parseLess())
??????return?Type();
????int?width;
????if?($_parser.parseInteger(width))
??????return?Type();
????if?($_parser.parseGreater())
??????return?Type();
????return?get($_ctxt,?width);
??}];
}
Type name : 生成的 C++ 類的名稱默認為
(例如上例中的Type TestIntegerType)。這可以通過cppClassName字段覆蓋。mnemonic是指定解析的asm名稱。它是可選的,不指定將意味著沒有解析器或打印方法附加到此類。Type documentation:存在
summary和description字段,其使用方式與Operation中相同。即,summary應(yīng)該是單行的,而description應(yīng)該是更長的解釋。Type parameters:
parameters字段是類型參數(shù)的列表。如果未指定任何參數(shù)(默認),則此類型被視為單例類型。參數(shù)采用“c++Type”:$paramName格式。要將C++類型用作需要在存儲構(gòu)造函數(shù)中分配的參數(shù),有兩種選擇:1. 設(shè)置hasCustomStorageConstructor以生成帶有剛剛聲明的構(gòu)造函數(shù)的 TypeStorage 類——沒有定義——所以我們可以自己編寫它。2. 使用TypeParametertablegen類而不是"c++Type"字符串。(后半句話我不是很懂,也還沒用過。)TypeParameter tablegen class : 這用于進一步指定有關(guān)每個類型參數(shù)的屬性。它包括文檔(
summary和syntax)、要使用的 C++ 類型、要在存儲構(gòu)造函數(shù)方法中使用的自定義分配器,以及用于確定參數(shù)類型的兩個實例是否相等的自定義比較器。
//?DO?NOT?DO?THIS!
let?parameters?=?(ins?"ArrayRef" :$dims);
默認存儲構(gòu)造函數(shù)盲目地按值復制字段。它對類型一無所知。在這種情況下,ArrayRef 需要使用 dims = allocator.copyInto(dims) 進行分配。
class?ArrayRefIntParam?:
????TypeParameter<"::llvm::ArrayRef" ,?"Array?of?ints">?{
??let?allocator?=?"$_dst?=?$_allocator.copyInto($_self);";
}
...
let?parameters?=?(ins?ArrayRefIntParam:$dims);
allocator代碼塊由$_allocator(是在其中分配對象的 TypeStorageAllocator)和$_dst(是放置已分配數(shù)據(jù)的變量)組成。comparator代碼塊由$_lhs和$_rhs參數(shù)類型實例組成。
自定義Type還有不少內(nèi)容,但目前我沒有這方面的需求,所以就沒有繼續(xù)看了,這里只是簡單了解了一下。感興趣的讀者可以自行查看文檔進行深入研究:https://mlir.llvm.org/docs/OpDefinitions/ 。
14. DEBUG方法
使用mlir-tblgen來看產(chǎn)生的文本。TableGen 語法有時可能很晦澀。閱讀生成的文本對于理解和調(diào)試問題非常有用。要構(gòu)建 mlir-tblgen,可以運行 cmake --build 。--target mlir-tblgen 在我們的構(gòu)建目錄中,并在 bin/ 子目錄中找到 mlir-tblgen 二進制文件。所有支持的生成器都可以通過 mlir-tblgen --help 找到。
要查看生成的代碼,請通過 -I 提供包含路徑,使用 mlir-tblgen 調(diào)用特定生成器。例如:
#?To?see?op?C++?class?declaration
mlir-tblgen?--gen-op-decls?-I?/path/to/mlir/include?/path/to/input/td/file
#?To?see?op?C++?class?definition
mlir-tblgen?--gen-op-defs?-I?/path/to/mlir/include?/path/to/input/td/file
#?To?see?op?documentation
mlir-tblgen?--gen-dialect-doc?-I?/path/to/mlir/include?/path/to/input/td/file
#?To?see?op?interface?C++?class?declaration
mlir-tblgen?--gen-op-interface-decls?-I?/path/to/mlir/include?/path/to/input/td/file
#?To?see?op?interface?C++?class?definition
mlir-tblgen?--gen-op-interface-defs?-I?/path/to/mlir/include?/path/to/input/td/file
#?To?see?op?interface?documentation
mlir-tblgen?--gen-op-interface-doc?-I?/path/to/mlir/include?/path/to/input/td/file
15. 總結(jié)
這一節(jié)在【從零開始學深度學習編譯器】十六,MLIR ODS要點總結(jié)上篇 的基礎(chǔ)上補充完整了ODS的要點。約束和屬性的定義都是MLIR中相當重要的元素,至于類型的定義個人認為了解即可,等到我們需要自定義類型的時候再仔細研究。最后MLIR的語法比較晦澀,初學者可以借助mlir-tblgen來輔助debug。
在這兩篇文章里,我跟著MLIR的ODS規(guī)范完整走了一遍并總結(jié)了14個要點,對于每一個要點我都在OneFlow MLIR的Op定義中進行了對照,并給出了一些示例代碼和位置。希望對讀者入門MLIR有幫助。
