MLIR Python绑定
MLIR Python綁定
當前狀態:正在開發中,默認情況下未啟用
build
前提條件
? 相對較新的Python3安裝
? pybind11 必須已安裝,可被CMake定位(如果通過進行安裝,則會自動檢測到 python -m pip install pybind11)。注意:所需的最低版本::2.6.0。
CMake的變量
? MLIR_BINDINGS_PYTHON_ENABLED:BOOL
啟用構建Python綁定的功能。默認為OFF。
? Python3_EXECUTABLE:STRING
指定python用于LLVM構建的可執行文件,包括用于確定Python綁定的header/link flags標志的可執行文件。在具有多個Python實現的系統上,python3強烈建議將其顯式設置為可執行文件首選。
? MLIR_PYTHON_BINDINGS_VERSION_LOCKED:BOOL
將本機擴展鏈接到Python運行時庫,該庫在某些平臺上是可選的。雖然將此設置為OFF可以帶來更大的部署靈活性,但通過這種方式的鏈接,linker可以報告所有平臺上未解析符號的編譯時錯誤,從而使開發工作流程更加順暢。默認為ON。
推薦的開發實踐
建議使用python虛擬環境。存在許多方法,但是以下是最簡單的方法:
Make sure your ‘python’ is what you expect. Note that on multi-python
systems, this may have a version suffix, and on many Linuxes and MacOS where
python2 and python3 co-exist, you may also want to use python3.
which python
python -m venv ~/.venv/mlirdev
source ~/.venv/mlirdev/bin/activate
Now the python command will resolve to your virtual environment and
packages will be installed there.
python -m pip install pybind11 numpy
Now run cmake, ninja, et al.
為了進行交互使用,將python目錄中的 build/目錄添加到中就足夠了PYTHONPATH。通常:
export PYTHONPATH=$(cd build && pwd)/python
設計
用例
MLIR python綁定可能有兩個主要用例:
- 支持用戶,期望LLVM / MLIR的已安裝版本,能夠以import mlir純開箱即用的方式提供使用API的能力。
- 下游集成可能希望將API的某些部分,包含在其私有名稱空間或特制的庫中,可能將其與其它python本地位混合。
組合模塊
為了支持用例2,Python綁定被組織成可組合的模塊,下游集成商可以包括這些模塊,并在需要時將其重新導出到自己的名稱空間中。這迫使幾個設計要點:
? 將a的構造/填充py::module與PYBIND11_MODULE 全局構造器分開。
? 為僅C ++的包裝器類引入 headers,因為其它相關的C ++模塊將需要與之互操作。
? 將所有依賴于可選組件的初始化例程,分成自己的模塊/依賴關系(當前,諸如此類之registerAllDialects類)。
共享庫鏈接,issues問題等許多與之相關的問題都會影響到這些因素。將代碼組織到可組合的模塊中(相對于整體cpp文件),可以靈活地隨時間推移解決其中的許多問題。同樣,pybind標尺中所有模板元編程的編譯時間,與在翻譯單元中定義的內容數量成比例。分成多個翻譯單元可以極大地幫助大表面積API的編譯時間。
子模塊
通常,C ++代碼庫將大多數內容都mlir命名為命名空間。但是,為了模塊化并使Python綁定更易于理解,定義了sub-packages,這些sub-packages大致映射到MLIR中功能單元的目錄結構。
例子:
? mlir.ir
? mlir.passes(pass是保留字:()
? mlir.dialect
? mlir.execution_engine (除了命名空間外,重要的是將這樣的“龐大” /可選部分隔離開來)
另外,暗示可選依賴項的初始化函數應放在帶下劃線的(概念上是私有的)模塊中,例如_init和分別鏈接在一起。這使下游集成商可以完全自定義“盒子”中包含的內容,并涵蓋 dialect注冊,通行證注冊等內容。
load
LLVM / MLIR是一個非平凡的python本機項目,很可能與其它非平凡的本機擴展共存。這樣,本機擴展(即 .so/ .pyd/ .dylib)將作為概念上專用的頂級符號(mlir)導出,同時在mlir/cext_loader.py和同級中,提供了一小套Python代碼,以加載和重新導出它。這種拆分為在共享庫加載到Python運行時之前準備環境所需的代碼提供了一個放置場所,并且還提供了一個一次性的初始化代碼可以與模塊構造函數一起調用的地方。
建議避免使用__init.py文件,直到到達represents 離散組件的葉包為止。要記住的規則是,init.py文件的存在,會阻止將名稱空間中該級別或更低級別的任何內容拆分為不同的目錄,部署程序包,驅動程序等的功能。
請參閱文檔以獲取更多信息和建議:https: //packaging.python.org/guides/packaging-namespace-packages/
使用C-API
Python API應該盡可能地在C-API之上分層。特別是對于核心的,與 dialect無關的部分,這種綁定可以實現跨越C ++ ABI邊界的困難或不可能的打包決策。此外,以這種方式進行分解,可以避免將基于RTTI的模塊(pybind派生的東西)與非RTTI多態C ++代碼(LLVM的默認編譯模式)結合在一起時,出現的一些非常棘手的問題。
核心IR中的所有權Ownership
核心IR中有幾種頂級類型,python-side引用對此具有很強的所有權:
? PyContext(mlir.ir.Context)
? PyModule(mlir.ir.Module)
? PyOperation(mlir.ir.Operation)-但有警告
所有其它對象都是相關的。所有對象都對其最接近的頂級對象保持反向引用(保持活動狀態)。此外,從屬對象分為兩類:a)唯一的(在上下文的生存期內生存)和b)可變的??勺儗ο笮枰渌鼨C制,跟蹤支持其Python對象的C ++實例,何時不再有效(通常是由于IR,刪除或批量操作的某些特定突變)。
核心IR中的可選性和參數排序
以下類型支持作為上下文管理器綁定到當前線程:
? PyLocation(loc: mlir.ir.Location = None)
? PyInsertionPoint(ip: mlir.ir.InsertionPoint = None)
? PyMlirContext(context: mlir.ir.Context = None)
為了支持函數自變量的可組合性,當這些類型作為自變量出現時,應始終排在最后,并以上述順序和給定名稱顯示(通常這是在特殊情況下,需要明確表示順序)。每個都應帶有默認值,py::none()并使用手動或自動轉換,使用顯式值或線程上下文管理器中的值(即DefaultingPyMlirContext或 DefaultingPyLocation)進行解析。
這樣做的理由是,在Python中,最右邊的關鍵字參數是可組合的,從而啟用了各種策略,例如kwarg傳遞,默認值等。使功能簽名保持可組合性增加了有趣的DSL和更高級別的API可以提供的機會。不需要很多 exotic boilerplate.的樣板就可以建造。
始終使用,可以實現一種IR構造樣式,該樣式很少需要使用顯式上下文,位置或插入點,但是在需要額外控制時可以隨意使用。
操作層次
如上所述,PyOperation它是特殊的,因為它可以存在于頂層狀態或從屬狀態。生命周期是單向的:可以分離創建操作(頂層),然后將其添加到另一個操作中,然后在整個生命周期中依賴。當考慮將操作添加到仍然分離的可傳遞父級的構造場景時,情況更加復雜,需要在此類過渡點處進行進一步核算(即,最初將所有此類添加的子級與最外層的父級一起添加到IR中分離操作,一旦將其添加到附加操作中,則需要將重新父級化到包含模塊。
由于有效性和parenting accounting的需要,PyOperation是區域和塊的所有者,并且需要是可以不依賴別名的頂級類型。這讓做一些事情,例如在發生突變時有選擇地使實例無效,而不必擔心層次結構中的同一操作具有某些別名。操作也是唯一一個允許處于分離狀態的實體,并且在上下文級別進行檢查,因此mlir.ir.Operation唯一的Python對象永遠不會超過一個MlirOperation,無論如何獲取它。
C / C ++ API允許還分離Region / Block,大大簡化了所有權模型,從而消除了該API中的這種可能性,從而使Region / Block完全依賴于其自己的記帳操作。Python Region/Block實例對基礎 MlirRegion/的別名MlirBlock被認為是良性的,并且這些對象不會在上下文中被插入(與操作不同)。
如果想重新引入分離的區域/塊,則可以使用新的“ DetachedRegion”類或類似的類來這樣做,并且還可以避免accounting的復雜性。通過現在的方式,可以避免擁有區域和街區的全局實時列表。可能最終需要在待定的某個時間點獲得一個操作本地的操作,具體取決于保證突變如何與其Python對等對象進行交互的難度。當到達那座橋時,可以輕松地越過那座橋。
純粹從Python API使用模塊時,無論如何都不能使用別名,因此可以將其用作頂級引用類型,而無需使用活動列表進行內部訪問。如果API曾經更改過以至于無法保證(例如,通過封送本機定義的模塊),那么也將需要一個活動表。
樣式
通常,對于MLIR的核心部分,Python綁定應與底層C ++結構在很大程度上是同構的。但是,出于實用性或為了使生成的庫具有適當的“ Pythonic”風格而做出讓步。
屬性vs get *()方法
通常有利于轉換瑣碎的方法,如getContext(),getName(), isEntryBlock()等,以只讀的Python性質(即context)。這主要是在綁定代碼中調用def_property_readonlyvs的問題,這def使Python方面感覺更好。
例如,prefer:
m.def_property_readonly(“context”, …)
Over:
m.def(“getContext”, …)
repr methods
具有良好打印表示的東西真的很棒:)如果有合理的打印形式,將其連接到__repr__方法(并使用doctest進行驗證 )可以大大提高生產率。
CamelCase vs snake_case
在中命名函數/方法/屬性snake_case和中的類CamelCase。作為對Python風格的機械讓步,這可以使API感覺很適合與Python環境中的同等對象相去甚遠。
如有疑問,請選擇可與其它PEP 8樣式名稱正確配合使用的名稱 。
Prefer pseudo-containers
許多核心IR構造直接在實例上提供方法來查詢計數和開始/結束迭代器。最好將其吊起使用專用的偽容器。
例如,可以通過以下方式完成區域內塊的直接映射:
region = …
for block in region:
pass
但是,首選這種方式:
region = …
for block in region.blocks:
pass
print(len(region.blocks))
print(region.blocks[0])
print(region.blocks[-1])
不要泄漏STL派生的標識符(front,back等),而是將轉換__dunder__為綁定中的適當方法和迭代器包裝器。
注意,這可能會做得太遠,使用良好的判斷力。例如,塊參數可能看起來像容器,具有用于查找和突變的定義方法,如果不使語義復雜化,將很難正確地對其進行建模。如果遇到這些問題,只需鏡像C / C ++ API。
為常見問題提供一站式幫助器helpers
在多個低層實體上聚集的一站式幫助者會非常有用,并且在合理范圍內會受到鼓勵。例如,使 Context具有parse_asm或避免顯式構造SourceMgr的等效項可能很好。一站式助手不必與支持結構的更完整映射相互排斥。
測試
測試應該添加到test/Bindings/Python目錄中,并且通常應該是.py運行狀態良好的文件。
使用lit和FileCheck基礎的測試:
? 對于生成測試(產生IR的測試),請定義一個Python模塊,該模塊構造/打印IR并將其通過管道傳遞FileCheck。
? 通過使用原始常量和適當的parse_asm調用,解析應保持在被測模塊內是自包含的。
? 與依賴測試模塊外部的文件工件/路徑相比,任何文件I / O代碼都應通過tempfile進行暫存。
? 為了方便起見,還使用相同的機制(CHECK根據需要打印和記錄)來測試非生成式API交互。
樣本FileCheck測試
RUN: %PYTHON %s | mlir-opt -split-input-file | FileCheck
TODO: Move to a test utility class once any of this actually exists.
def print_module(f):
m = f()
print("// -----")
print("// TEST_FUNCTION:", f.name)
print(m.to_asm())
return f
CHECK-LABEL: TEST_FUNCTION: create_my_op
@print_module
def create_my_op():
m = mlir.ir.Module()
builder = m.new_op_builder()
CHECK: mydialect.my_operation …
builder.my_op()
return m
與ODS集成
MLIR Python綁定與基于tablegen的ODS系統集成在一起,可為MLIR的 dialect和操作提供用戶友好的包裝器。集成有多個部分,概述如下。大部分細節都被刪除了:有關mlir.dialects 使用此工具的規范方法,請參考下面的構建規則和python源代碼。
用戶負責提供一個{DIALECT_NAMESPACE}.py(或與__init__.py文件等效的目錄)作為入口點。
生成_{DIALECT_NAMESPACE}ops_gen.py包裝器模塊
每個與python映射的 dialect都需要{DIALECT_NAMESPACE}_ops_gen.py創建一個適當的 包裝器模塊。這是通過調用mlir-tblgen特定于python綁定的tablegen包裝器來完成的,該包裝器包括樣板td文件和實際的 dialect特定文件。的示例StandardOps(std為特例分配了名稱空間):
#ifndef PYTHON_BINDINGS_STANDARD_OPS
#define PYTHON_BINDINGS_STANDARD_OPS
include “mlir/Bindings/Python/Attributes.td”
include “mlir/Dialect/StandardOps/IR/Ops.td”
#endif
在主存儲庫中,構建包裝器是通過CMake函數完成的,該函數 add_mlir_dialect_python_bindings調用:
mlir-tblgen -gen-python-op-bindings -bind-dialect={DIALECT_NAMESPACE}
{PYTHON_BINDING_TD_FILE}
必須以{DIALECT_NAMESPACE}.py類似于C ++生成的代碼包含生成的 headers的方式將生成的op類包含在文件中:
from ._my_dialect_ops_gen import *
擴展包裝模塊的搜索路徑
當python綁定需要定位包裝器模塊時,會參考 dialect_search_path并使用它來查找適當命名的模塊。對于主存儲庫,此搜索路徑經過硬編碼以包含mlir.dialects模塊,而該 模塊是abobe構建規則在其中發出包裝器的位置。樹外 dialect,并通過調用以下命令將其模塊添加到搜索路徑中:
mlir.cext.append_dialect_search_prefix(“myproject.mlir.dialects”)
包裝器模塊代碼組織
包裝器模塊tablegen發射器輸出:
? 甲_Dialect類(延伸mlir.ir.Dialect)與DIALECT_NAMESPACE 屬性。
? {OpName}每個操作的類(擴展mlir.ir.OpView)。
? 以上每個裝飾器均要在系統中注冊。
注意:為了避免命名沖突,包裝模塊使用的所有內部名稱均以前綴_ods。
每個具體的OpView子類進一步定義了幾個公共屬性:
? OPERATION_NAME屬性具有str完全限定的操作名稱(即std.absf)。
? __init__用于缺省構建器的方法(如果為該操作定義或推斷了一個方法)。
? @property 每個操作數或結果的getter(使用自動生成的名稱表示每個操作數的未命名)。
? @property 每個聲明的屬性的getter,setter和Deleter。
它還會發出用于子類化和自定義的其它專用屬性(默認情況下,省略這些屬性,而使用on的默認值OpView):
? _ODS_REGIONS:有關區域數量和類型的規范。當前的元組為(min_region_count,has_no_variadic_regions)。請注意,API對此進行了一些簡單的驗證,但主要目的是捕獲足夠的信息以執行其它默認的構建和區域訪問器生成。
? _ODS_OPERAND_SEGMENTS和_ODS_RESULT_SEGMENTS:黑盒值,指示關于變量的操作數或結果的結構。用于OpView._ods_build_default對包含列表的操作數和結果列表進行解碼。
builders
當前,只有一個默認的構建器映射到該__init__方法。目的是該__init__方法represents 通常為C ++生成的最具體的構建器。但是目前它只是下面的通用形式。
? 每個聲明的結果有一個參數:
o 對于單值結果:每個將接受一個mlir.ir.Type。
o 對于可變結果:每個將接受一個List[mlir.ir.Type]。
? 每個聲明的操作數或屬性有一個參數:
o 對于單值操作數:每個將接受一個mlir.ir.Value。
o 對于可變參數操作數:每個將接受一個List[mlir.ir.Value]。
o 對于屬性,它將接受mlir.ir.Attribute。
? 尾隨用法特定的,可選的關鍵字參數:
o loc:明確mlir.ir.Location使用。默認為綁定到線程的位置(即with Location.unknown():),如果未綁定也未指定,則返回錯誤。
o ip:明確mlir.ir.InsertionPoint使用。默認為綁定到線程的插入點(即with InsertionPoint(…):)。
此外,每個方法都OpView繼承了一種build_generic方法,該方法允許通過results和的 序列(在可變參數情況下為嵌套)進行構造operands。這可用于獲取Python否則不支持的操作的一些默認構造語義,以犧牲獲得非常通用的簽名為代價。
總結
以上是生活随笔為你收集整理的MLIR Python绑定的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Rust和C / C ++的跨语言链接时
- 下一篇: MLIR(Multi-Level Int