MLIR筆記(6)

5.?方言與操作

5.1.?方言的概念

在MLIR里,通過Dialect類來抽象方言。具體的每種方言都需要從這個基類派生一個類型,并實現重載自己所需的虛函數。?

MLIR文檔里這樣描述方言( MLIR Language Reference - MLIR):

方言是這樣的機制:它融入并擴展MLIR生態系統。它們允許定義新的操作,以及屬性與類型。向每個方言給出唯一的名字空間作為定義的每個屬性/操作/類型的前綴。例如,Affine方言定義了名字空間affine

MLIR允許多個方言共存于一個模塊中,即使是在主干之外的那些。方言由特定的遍生成與消費。對不同的方言之間以及方言內部的轉換,MLIR提供了一個框架。MLIR支持的幾個方言:

  • Affine dialect
  • GPU dialect
  • LLVM dialect
  • SPIR-V dialect
  • Standard dialect
  • Vector dialect

?在教程中,還給出了Toy方言的例子。

5.2.?操作的概念

MLIR引入了一個稱為操作(operation)的統一概念來描述許多不同的抽象與計算層次。在MLIR系統中,從指令到函數再到模塊,一切都塑造為Op。 MLIR沒有固定的Op集合,因此允許并鼓勵用戶自定義擴展Op。編譯器遍會保守地對待未知Op,并且MLIR支持通過特征(traits)、特權操作hook和優化接口等方式向遍描述Op語義。MLIR里的操作是完全可擴展的(沒有固定的操作列表),并具有應用特定的語義。例如,MLIR支持目標無關操作、仿射(affine)操作,以及目標特定機器操作。

操作的內部表示是簡單的:操作由一個唯一字符串標識(如dim、tf.Conv2d、x86.repmovsb、ppc.eieio等),可以返回0或多個結果,接受0或多個操作數,有一個屬性字典,有0或多個后繼者,以及0或多個封閉的區域。通用打印形式包括所有這些元素,加上一個函數類型來表示結果與操作數的類型。

例子:

// An operation that produces two results.
// The results of %result can be accessed via the <name> `#` <opNo> syntax.
%result:2 = "foo_div"() : () -> (f32, i32)

// Pretty form that defines a unique name for each result.
%foo, %bar = "foo_div"() : () -> (f32, i32)

// Invoke a TensorFlow function called tf.scramble with two inputs
// and an attribute "fruit".
%2 = "tf.scramble"(%result#0, %bar) {fruit = "banana"} : (f32, i32) -> f32
5.3.?方言的管理

顯然,管理方言最恰當的地方就是MLIRContext,不過MLIRContext只是上下文的接口,真正的實現是MLIRContextImpl。在MLIRContextImpl中有這樣一些容器:

DenseMap<StringRef, std::unique_ptr<Dialect>> loadedDialects

DialectRegistry dialectsRegistry

llvm::StringMap<AbstractOperation> registeredOperations

llvm::StringMap<PointerUnion<Dialect *, MLIRContext *>, llvm::BumpPtrAllocator &> identifiers

第一個容器保存已載入的方言對象,第二個容器記錄已注冊的方言,第三個容器保存已注冊的抽象操作,第四個容器記錄上下文里可見的標識符。

這里,方言的注冊是指MLIRContext知道這個方言的標識符和構造方法,但方言對象并沒有構造。方言對象的構造發生在載入時,在載入時刻,不僅構造方言對象,相關的接口也會一并準備好。抽象操作與操作相關,參考操作的管理。

5.3.1.?方言的注冊

MLIR提供了一組標準的方言,它們提供了許多有用的功能。為了讓程序能方便地使用標準方言,首先,每個程序在main()的入口都要注冊標準方言,像這樣:

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

? mlir::registerAllDialects();

? …????? // 其他初始化

?registerAllDialects()的定義如下:

67? inline void registerAllDialects() {

68? ??static bool initOnce =

69? ??????([]() { registerAllDialects(getGlobalDialectRegistry()); }(), true);

70? ??(void)initOnce;

71? }

69行的getGlobalDialectRegistry()返回一個類型為llvm::ManagedStatic<DialectRegistry>的對象dialectRegistry,它過可視為DialectRegistry的靜態對象,這個類通過一個類型為std::map<std::string, std::pair<TypeID, DialectAllocatorFunction>>的容器registry來記錄標準方言。因此,同一行上的registerAllDialects()重載函數是:

41? inline void registerAllDialects(DialectRegistry &registry) {

42? ??// clang-format off

43? ??registry.insert<acc::OpenACCDialect,

44? ??????????????????AffineDialect,

45? ??????????????????avx512::AVX512Dialect,

46? ??????????????????gpu::GPUDialect,

47? ??????????????????LLVM::LLVMAVX512Dialect,

48? ??????????????????LLVM::LLVMDialect,

49? ??????????????????linalg::LinalgDialect,

50? ??????????????????scf::SCFDialect,

51? ??????????????????omp::OpenMPDialect,

52? ??????????????????pdl::PDLDialect,

53? ??????????????????pdl_interp::PDLInterpDialect,

54? ??????????????????quant::QuantizationDialect,

55? ??????????????????spirv::SPIRVDialect,

56? ??????????????????StandardOpsDialect,

57? ??????????????????vector::VectorDialect,

58? ??????????????????NVVM::NVVMDialect,

59? ??????????????????ROCDL::ROCDLDialect,

60? ??????????????????SDBMDialect,

61? ??????????????????shape::ShapeDialect>();

62? ??// clang-format on

63? }

這個方法列出了MLIR目前實現的標準方言,DialectRegistry通過一系列insert()方法完成注冊:

252? ??template <typename ConcreteDialect, typename OtherDialect,

253? ??????????????typename... MoreDialects>

254? ????void insert() {

255? ??????insert<ConcreteDialect>();

256? ??????insert<OtherDialect, MoreDialects...>();

257? ????}

241? ??template <typename ConcreteDialect>

242? ??void insert() {

243? ????insert(TypeID::get<ConcreteDialect>(),

244? ???????????ConcreteDialect::getDialectNamespace(),

245? ???????????static_cast<DialectAllocatorFunction>(([](MLIRContext *ctx) {

246? ?????????????// Just allocate the dialect, the context

247? ?????????????// takes ownership of it.

248? ?????????????return ctx->getOrLoadDialect<ConcreteDialect>();

249? ???????????})));

250? ??}

53? void DialectRegistry::insert(TypeID typeID, StringRef name,

54? ?????????????????????????????DialectAllocatorFunction ctor) {

55? ??auto inserted = registry.insert(

56? ??????std::make_pair(std::string(name), std::make_pair(typeID, ctor)));

57? ??if (!inserted.second && inserted.first->second.first != typeID) {

58? ????llvm::report_fatal_error(

59? ????????"Trying to register different dialects for the same namespace: " +

60? ????????name);

61? ??}

62? }

?注意,這里只是注冊了這些方言的構造方法,并沒有把這些方言的Dialect對象構造出來。這是因為Dialect對象的構造需要一個MLIRContext實例,因此要把Dialect對象的構造推遲到MLIRContext對象構造出來后。另外,不是每個程序都需要所有的標準方言,在需要時構造所需的方言才比較合理。所以,DialectRegistry提供了兩個函數:loadByName(),loadAll()。前者構造指定名字的標準方言,后者構造所有的標準方言。

5.3.2.?方言的載入

從上面注冊的構造方法我們看到,實際執行構造的函數是MLIRContext的getOrLoadDialect(),這也是一系列調用:

69? ??template <typename T>

70? ????T *getOrLoadDialect() {

71? ??????return static_cast<T *>(

72? ????????getOrLoadDialect(T::getDialectNamespace(), TypeID::get<T>(), [this]() {

73? ??????????std::unique_ptr<T> dialect(new T(this));

74? ??????????return dialect;

75? ????????}));

76? ??}

模板參數T是具體的方言類型,它是Dialect的派生類,Dialect沒有定義getDialectNamespace(),派生類必須提供自己的定義。在MLIRContext里這個名字將作為這個方言的身份識別。另外,Dialect及其派生類亦是MLIR類型系統中的組成,它們都有TypeIDMLIRContext::getOrLoadDialect()在2021版本里的定義如下:

511 ?Dialect *

512 ?MLIRContext::getOrLoadDialect(StringRef dialectNamespace, TypeID dialectID,

513 ???????????????????????????????function_ref<std::unique_ptr<Dialect>()> ctor) {

514 ???auto &impl = getImpl();

515 ???// Get the correct insertion position sorted by namespace.

516 ???std::unique_ptr<Dialect> &dialect = impl.loadedDialects[dialectNamespace];

517 ?

518 ???if (!dialect) {

519 ?????LLVM_DEBUG(llvm::dbgs()

520 ????????????????<< "Load new dialect in Context " << dialectNamespace << "\n");

521 ?#ifndef NDEBUG

522 ?????if (impl.multiThreadedExecutionContext != 0)

523 ???????llvm::report_fatal_error(

524 ???????????"Loading a dialect (" + dialectNamespace +

525 ???????????") while in a multi-threaded execution context (maybe "

526 ???????????"the PassManager): this can indicate a "

527 ???????????"missing `dependentDialects` in a pass for example.");

528 ?#endif

529 ?????dialect = ctor();

530 ?????assert(dialect && "dialect ctor failed");

531 ?

532 ?????// Refresh all the identifiers dialect field, this catches cases where a

533 ?????// dialect may be loaded after identifier prefixed with this dialect name

534 ?????// were already created.

535 ?????llvm::SmallString<32> dialectPrefix(dialectNamespace);

536 ?????dialectPrefix.push_back('.');

537 ?????for (auto &identifierEntry : impl.identifiers)

538 ???????if (identifierEntry.second.is<MLIRContext *>() &&

539 ???????????identifierEntry.first().startswith(dialectPrefix))

540 ?????????identifierEntry.second = dialect.get();

541 ?

542 ?????// Actually register the interfaces with delayed registration.

543 ?????impl.dialectsRegistry.registerDelayedInterfaces(dialect.get());

544 ?????return dialect.get();

545 ???}

546 ?

547 ???// Abort if dialect with namespace has already been registered.

548 ???if (dialect->getTypeID() != dialectID)

549 ?????llvm::report_fatal_error("a dialect with namespace '" + dialectNamespace +

550 ??????????????????????????????"' has already been registered");

551 ?

552 ???return dialect.get();

553 ?}

從上面可以看到,構造出來的Dialect對象存放在MLIRContextImpl的loadedDialects容器中(類型DenseMap<StringRef, std::unique_ptr<Dialect>>)。同樣,MLIRContext也提供這些函數獲取指定方言的Dialect對象:getOrLoadDialect(),loadDialect()等。比如,Toy例子代碼有這樣的代碼片段來構建自己的方言對象:

int dumpMLIR() {

? mlir::MLIRContext context(/*loadAllDialects=*/false);

? // Load our Dialect in this MLIR Context.

? context.getOrLoadDialect<mlir::toy::ToyDialect>();

identifiers 是MLIRContextImpl里有類型為llvm::StringMap<PointerUnion<Dialect *, MLIRContext *>, llvm::BumpPtrAllocator &>的容器。MLIR里的標識符是帶有上下文前綴或方言前綴的(以“.”分隔),identifiers容器就是關聯標識符與其所在上下文對象或方言對象的,一個操作在創建時首先假設它在一個上下文(參考Identifier::get()),上面537行的for循環檢查是否已經創建了具有這個方言名的上下文對象,如果是把它替換為對應的方言對象。

5.3.2.1.?方言接口

接下來,通過DialectRegistry::registerDelayedInterfaces()向MLIRContextImpl注冊方言的接口。這里“延遲接口”的意思是只在方言載入(或創建)時才注冊接口。

106? void DialectRegistry::registerDelayedInterfaces(Dialect *dialect) const {

107??? auto it = interfaces.find(dialect->getTypeID());

108??? if (it == interfaces.end())

109????? return;

110?

111??? // Add an interface if it is not already present.

112??? for (const auto &kvp : it->getSecond().dialectInterfaces) {

113????? if (dialect->getRegisteredInterface(kvp.first))

114??????? continue;

115????? dialect->addInterface(kvp.second(dialect));

116??? }

117?

118?? ?// Add attribute, operation and type interfaces.

119??? for (const auto &kvp : it->getSecond().objectInterfaces)

120????? kvp.second(dialect->getContext());

121? }

方言可用的接口都保存在DialectRegistry類型為DenseMap<TypeID, DelayedInterfaces>的interfaces容器中,其中DelayedInterfaces是DialectRegistry里這樣的一個嵌套定義:

283 ?struct DelayedInterfaces {

284 ?????/// Dialect interfaces.

285 ?????SmallVector<std::pair<TypeID, DialectInterfaceAllocatorFunction>, 2>

286 ?????????dialectInterfaces;

287 ?????/// Attribute/Operation/Type interfaces.

288 ?????SmallVector<std::pair<TypeID, ObjectInterfaceAllocatorFunction>, 2>

289 ?????????objectInterfaces;

290 ???};

在下面我們會看到,方言除了自己的接口,還支持操作/類型/屬性的外部模式接口,289行的objectInterfaces是存放這些操作接口的地方。這兩個容器用到這兩個定義:

30 ?using DialectInterfaceAllocatorFunction =

31 ?????std::function<std::unique_ptr<DialectInterface>(Dialect *)>;

32 ?using ObjectInterfaceAllocatorFunction = std::function<void(MLIRContext *)>;

顧名思義,這兩個std::function封裝的方法用于創建接口對象,在上面115與120行,它們被調用來創建具體的接口對象。Dialect的addInterface()將生成的接口對象保存在registeredInterfaces容器中(類型DenseMap<TypeID, std::unique_ptr<DialectInterface>>)。而120行處,創建的接口對象實際上保存在對應操作/類型/屬性抽象對象的容器中,下面會看到。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/214736.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/214736.shtml
英文地址,請注明出處:http://en.pswp.cn/news/214736.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

手把手教你玩轉ESP8266(原理+驅動)

在嵌入式開發中&#xff0c;無線通信的方式有很多&#xff0c;其中 WIFI 是繞不開的話題。說到 WIFI 通信&#xff0c;就不得不提 ESP8266了。 ESP8266 是一款高性能的 WIFI 串口模塊&#xff0c;實現透明傳輸。只要有一定的串口知識&#xff0c;不需要知道 WIFI 原理就可以上…

作為一個產品經理帶你了解Axure的安裝和基本使用

1.Axure的簡介 Axure是一種強大的原型設計工具&#xff0c;它允許用戶創建交互式的、高保真度的原型&#xff0c;以及進行用戶體驗設計和界面設計。Axure可以幫助設計師和產品經理快速創建和共享原型&#xff0c;以便團隊成員之間進行溝通和反饋。Axure提供了豐富的交互組件和功…

Spring--10--Spring Bean的生命周期

提示&#xff1a;文章寫完后&#xff0c;目錄可以自動生成&#xff0c;如何生成可參考右邊的幫助文檔 文章目錄 1.Spring Bean1.1 什么是 Bean簡而言之&#xff0c;bean 是由 Spring IoC 容器實例化、組裝和管理的對象。 1.2 Spring框架管理Bean對象的優勢 2.Bean的生命周期實例…

西工大網絡空間安全學院計算機系統基礎實驗二(phase_2下——漫漫深夜過后的黎明!!!)

內存地址內存地址中的數注釋指向這塊內存的寄存器0xffffd0e8函數phase_2的棧幀0xffffd0e40xffffd0f4函數phase_2的棧幀0xffffd0e00x5655b7b0函數phase_2的棧幀0xffffd0dc0x565566ca函數read_six_numbers的返回地址&#xff0c;函數phase_2的棧幀0xffffd0d80x5655af64舊%ebx的值…

SpringIOC之ConditionEvaluator

博主介紹:?全網粉絲5W+,全棧開發工程師,從事多年軟件開發,在大廠呆過。持有軟件中級、六級等證書。可提供微服務項目搭建與畢業項目實戰,博主也曾寫過優秀論文,查重率極低,在這方面有豐富的經驗? 博主作品:《Java項目案例》主要基于SpringBoot+MyBatis/MyBatis-plus+…

Netty性能好的原因是什么

Netty性能好的原因 廢話篇Netty性能好的原因是什么1. 非阻塞IO模型高效的Reactor線程模型零拷貝內存池設計無鎖串行化設計高性能序列化協議 廢話篇 相信有同學會經常被問到這樣的問題&#xff0c;不妨下次被面試官問到這種問題&#xff0c;我們可以這樣回答&#xff01; Nett…

簡單實用的firewalld命令

簡單實用的firewalld命令 一、查看防火墻是否打開二、查詢、開放、關閉端口三、查看已監聽端口四、驗證 一、查看防火墻是否打開 systemctl status firewalld ● firewalld.service - firewalld - dynamic firewall daemonLoaded: loaded (/usr/lib/systemd/system/firewalld.…

map.getOrDefault

map.getOrDefault 是 Java 中的一個方法&#xff0c;用于從 Map 中獲取指定鍵的值&#xff0c;如果鍵不存在&#xff0c;則返回指定的默認值。 方法簽名如下&#xff1a; V getOrDefault(Object key, V defaultValue) 其中&#xff0c;key 是要獲取值的鍵&#xff0c;defaul…

day19_java泛型

泛型 Java 泛型&#xff08;generics&#xff09;是 JDK 5 中引入的一個新特性, 泛型提供了編譯時類型安全檢測機制&#xff0c;該機制允許程序員在編譯時檢測到非法的類型。保證了java的安全性 泛型的本質是參數化類型&#xff0c;也就是說所操作的數據類型被指定為一個參數…

AWS EC2使用 instance profile 訪問S3

AWS EC2 instance可以使用instance profile 配置訪問S3的權限。 然后就可以直接在EC2上執行 python代碼或者AWS CLI去訪問S3了。 唯一需要注意的地方是&#xff0c;申明region。 示例代碼&#xff1a; aws s3 ls xxxx-s3-bucket --region xxx-region import boto3 client …

一文讀懂MySQL基礎知識文集(8)

&#x1f3c6;作者簡介&#xff0c;普修羅雙戰士&#xff0c;一直追求不斷學習和成長&#xff0c;在技術的道路上持續探索和實踐。 &#x1f3c6;多年互聯網行業從業經驗&#xff0c;歷任核心研發工程師&#xff0c;項目技術負責人。 &#x1f389;歡迎 &#x1f44d;點贊?評論…

IDEA 報錯

IDEA 報錯&#xff1a; Cannot resolve symbol&#xff1a;這通常是由于 IDEA 無法識別您正在使用的類或方法導致的。請確保您已經導入了正確的包&#xff0c;并且您的類路徑設置正確。 NullPointerException&#xff1a;這通常是由于您的代碼嘗試訪問空對象或空值導致的。請檢…

JavaScript 函數的返回值

JavaScript 函數的返回值 JavaScript 函數的返回值是函數執行后返回的值&#xff0c;可以是任意類型的值&#xff0c;包括數字、字符串、布爾值、對象等。函數的返回值通過 return 關鍵字來指定&#xff0c;如果函數沒有指定返回值&#xff0c;則默認返回 undefined。例如&…

Qt處理焦點事件(獲得焦點,失去焦點)

背景&#xff1a; 我只是想處理焦點動作&#xff0c;由于懶&#xff0c;上網一搜&#xff0c;排名靠前的一位朋友&#xff0c;使用重寫部件的方式實現。還是因為懶&#xff0c;所以感覺復雜了。于是又花了一分鐘解決了一下。 所以記錄下來&#xff0c;以免以后忘了。 思路&a…

單目相機測距(3米范圍內)二維碼實現方案(python代碼 僅僅依賴opencv)

總體思路:先通過opencv 識別二維碼的的四個像素角位置,然后把二維碼的物理位置設置為 cv::Point3f(-HALF_LENGTH, -HALF_LENGTH, 0), //tl cv::Point3f(HALF_LENGTH, -HALF_LENGTH, 0), //tr cv::Point3f(HALF_LENGTH, HALF_LENGTH, 0), //br cv::P…

四年編程成長總結

文章目錄 計算機計算機基礎知識操作系統計算機網絡 自考學習與備考考試經歷 軟考學習與準備考試成果人生成長自主學習解決問題團隊合作 總結 計算機 計算機是我學習和應用Java編程的基礎&#xff0c;它為我提供了一個強大的工具和平臺。在這四年的學習中&#xff0c;我逐漸深入…

軟件運行原理 - 內存模型 - 棧內存

說明 C/C軟件運行時&#xff0c;內存根據使用方式的不同分為堆內存和棧內存&#xff0c;棧內存使用有以下特征&#xff1a; 棧內存使用&#xff08;申請、釋放&#xff09;由系統自動分配和釋放&#xff0c;程序員不用做任何操作。棧內存重復使用&#xff0c;進入函數時數據入…

什么是特征圖?

在卷積神經網絡&#xff08;CNN&#xff09;中&#xff0c;特征圖是在傳遞給卷積層的圖像上發生卷積操作后卷積層的輸出。 特征圖是如何形成的&#xff1f; 在上面的插圖中&#xff0c;我們可以看到特征圖是如何從提供的輸入圖像中形成的。 要發送到卷積層的圖像是一個包含像…

AutoSAR(基礎入門篇)1.2-AutoSAR的發展史

目錄 一、AutoSAR成員 二、AutoSAR歷史發展 三、未使用AutoSAR前的缺點 1、原始狀態

JavaScript 和 HTML DOM 參考手冊

JavaScript 和 HTML DOM 參考手冊 js的變量類型有字符串,布爾等 在操作這些變量類型的時候,可以將他們看成是對象來操作 因為js 把一切都封裝成對象來看 獲取字符串的長度 var str hello world; console.log(str.length); //11 console.log(str.substr(0,5)); // hello c…