FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

MLIRの勉強 (5. MLIRGenにおけるスコープ)

MLIRについて勉強している。

ちょっとTypeについて詰まってしまったので、ドキュメントを読み直している。


mlir.llvm.org

MLIRは完全に拡張可能なインフラストラクチャとして設計されている; 属性(定数メタデータなど)、演算、型などについての閉じた集合が存在しない。MLIRはこの拡張性をDialect(方言)という考え方によってサポートしている。Dialectはユニークなnamespaceによって抽象化グループである。

MLIRでは、Operationsが抽象化と計算のコアユニットであり、多くのLLVM命令と似ている。Operationsはアプリケーション特有のセマンティックを持つコタができ、LLVMのコアIR: 命令、グローバル(関数やモジュールなど)のすべてを表現することができる。

以下はToyのtranspose操作のMLIRアセンブリである。

%t_tensor = "toy.transpose"(%tensor) {inplace = true} : (tensor<2x3xf64>) -> tensor<3x2xf64> loc("example/file/path":12:1)
  • %t_tensor
    • このOperationによって定義された結果に与えられる名前 (衝突を避けるためにプレフィックス付きのシギル(%, #, @のようなもの)を含む)。Operationは0個以上の結果を定義することができ(Toyの文脈では、単一結果の操作に限定する)、それらはSSA値である。名前は解析中に使用されるが、永続的ではない(たとえば、SSA 値のメモリ内表現で追跡されない)
  • “toy.transpose”
    • Operationの名前。これは一意な文字列であることが期待され、"."の前にDialectの名前空間が付く。これは、toy方言での転置操作と読むことができる
  • (%tensor)
    • 0個以上の入力オペランド(または引数)のリスト。これは、他の操作によって定義されたSSA値、またはブロック引数を参照するものである
  • { inplace = true }
    • 0個以上の属性からなる辞書で、常に一定の値を持つ特別なオペランドである。ここでは、'inplace'という名前のブール型属性を定義しており、一定の値としてtrueを持つ
  • (tensor<2x3xf64>) -> (tensor<3x2xf64)
    • これは、関数の形で操作の型を指し、括弧の中に引数の型を、その後に戻り値の型を示す
  • loc(”example/file/path”:12:1)

Operationはいくつかのコンセプトに基づいてモデル化されている。

  • Operationの名前
  • SSAオペランドの値
  • 属性のリスト (属性、そのOperationに対する固定の値、cmpiの比較の種類など)
  • 結果の値の型のリスト
  • デバッグ目的のためのソースの位置
  • 後続ブロックのリスト(殆どの場合分岐命令のため)
  • リージョンのリスト(関数のような構造的な操作のため)

MLIRでは,すべてのOperationにソース位置の指定が必須である。LLVMでは、デバッグ情報の場所はメタデータであり、削除することも可能だが、MLIRではlocationはコア要件であり、APIはそれに依存し操作される。ロケーション情報を削除するという選択は、誤りにより発生することのない明示的な選択であると言える。

例として もし変換がある操作を別の操作に置き換えるなら、その新しい操作には新しいロケーションが必要である。これによって、その操作がどこから来たのか追跡することが可能になる。

コンパイラのパスをテストするためのツールであるmlir-optツールは、デフォルトでは出力にロケーションを含めないことに注意すること。mlir-print-debuginfo フラグを指定すると、ロケーションを含める。(その他のオプションは mlir-opt --help を実行すること)。

不透明なAPI

MLIRはOpreation, 属性、型などすべてのIRの要素がカスタマイズ可能になっている。IRの要素は基本的なコンセプトを削減できるようになっている。例えば、Toy言語ではmlir-optによりToyに関連するDialectを登録せずに

func.func @toy_func(%tensor: tensor<2x3xf64>) -> tensor<3x2xf64> {
  %t_tensor = "toy.transpose"(%tensor) { inplace = true } : (tensor<2x3xf64>) -> tensor<3x2xf64>
  return %t_tensor : tensor<3x2xf64>
}

未登録の属性,Operation,型の場合,MLIRは構造的な制約(例えばdominanceなど)を強制するが、それ以外は完全に不透明となる。例えば,未登録の操作は、特定のデータ型に対して操作可能か,いくつのオペランドを取ることができるか,いくつの結果を生成するかなど,MLIRはほとんど情報を持たない。このような柔軟性は,ブートストラップには有用だが,成熟したシステムでは一般に避けるべきである。未登録の操作は、変換や分析において保守的に扱わなければならず、構築や操作が非常に難しくなる。

この処理は、Toyのために無効なIRを作成し、Verfierを引っかけることなくラウンドトリップすることで確認することができる。

func.func @main() {
  %0 = "toy.print"() : () -> tensor<2x3xf64>
}

上記のプログラムにはいくつかの問題が存在する: toy.printはターミネータではないし,オペランドを取るべきだし, 値を返すべきでもない。次節では、MLIRに方言と操作を登録し、Verifierと接続し、操作を操作するためのより良いAPIを追加する予定である。