第2章 作者・誕生の背景・MLIR
2.1 この章で学ぶこと
この章で学ぶ内容は以下のとおりです。
学ぶこと |
ポイント |
|---|---|
AI インフラのスタック断層問題 |
なぜ新しい言語が必要になったか |
MLIR とは何か |
複数レベルの中間表現を扱うコンパイラ基盤 |
lowering とは何か |
高水準表現から機械語へ段階的に変換する仕組み |
アセンブリの中の MLIR の痕跡 |
第1章の |
2.2 AI インフラのスタック断層問題
Python は AI の入り口として定着しています。 しかし本番で高い性能を出すレイヤーになると、現実には多くの異なる技術が積み重なっています。
レイヤー |
言語 / 技術 |
最適化器 |
役割 |
|---|---|---|---|
アプリ |
Python |
— |
モデル定義と学習ループ |
フレームワーク |
Python + C 拡張 |
PyTorch / TensorFlow |
テンソル演算グラフ |
カーネル |
CUDA C++ / Triton |
cuBLAS / cuDNN |
GPU 行列演算 |
中間表現 |
PTX / LLVM IR |
nvcc / LLVM |
デバイス別コード生成 |
ハードウェア |
GPU / CPU |
— |
実際の演算 |
各レイヤーは異なる言語、ツール、コンパイラを持ちます。 「Python で書いた処理がどのように GPU で走るか」を人間が全部追うのは現実的ではなく、レイヤーをまたぐ最適化も難しい状況です。
Mojo が目指すのは、この断層を一つの言語と一つのコンパイラスタックで解消することです。
2.3 MLIR とは何か
MLIR(Multi-Level Intermediate Representation)は、2020 年に Google がオープンソース化した汎用コンパイラ基盤です。
コラム: 従来の LLVM との違い
LLVM(2000 年ごろ〜)は「ソース言語 → LLVM IR → 機械語」という 1 段階の変換を担います。 C、C++、Rust などはすべて LLVM IR に落としてから最適化します。 強力ですが、「テンソル演算」「GPU スレッドブロック」「ニューラルネットのグラフ」のような高水準の概念は LLVM IR では直接扱えません。
MLIR はこの問題を解決するために設計されました。 「抽象度の異なる複数の中間表現(ダイアレクト)を同時に使える」基盤です。 高水準の演算グラフのまま最適化し、徐々に低水準へ変換(lowering)していきます。
2.3.1 MLIR のダイアレクト
MLIR では、目的に応じた「ダイアレクト」(方言)を定義します。 Mojo のコンパイラは複数のダイアレクトを通じて段階的に変換を行います。
ダイアレクト |
抽象度 |
扱うもの |
|---|---|---|
|
最高水準 |
Mojo の型、ジェネリクス、所有権 |
|
高水準 |
行列演算とループ抽象 |
|
中間 |
ループ境界とアクセスパターン |
|
低水準 |
LLVM IR に相当する命令 |
LLVM IR → x86 / PTX |
最低水準 |
CPU / GPU 向けの機械語 |
図2-1: MLIR のダイアレクト lowering 全体像
kgen → linalg → affine → llvm の各ダイアレクトを「低水準化」しながら x86-64(機械語)または PTX(GPU)へ変換する流れ。 各段階でループ抽象、アクセスパターン、LLVM IR 命令へと表現が具体化していく。
2.4 lowering:段階的な変換の流れ
Mojo のソースコードは次の経路でコンパイルされます。
hello_mojo_minimal.mojo
│ ← Mojo フロントエンド
▼
kgen ダイアレクト(型・所有権・ジェネリクス)
│ ← lowering pass
▼
linalg / affine ダイアレクト(ループ・演算)
│ ← lowering pass
▼
llvm ダイアレクト(LLVM IR 相当)
│ ← LLVM バックエンド
▼
x86-64 機械語 / PTX(GPU 向け)
ポイントは「共通の中間表現基盤の上で、ターゲットに応じて lowering(低水準化)の経路が変わる」点です。
CPU 向けなら x86-64 を、GPU 向けなら PTX を、同じ高水準コードから生成し分けられます(GPU 対応の範囲は発展途上で、すべてのコードがそのまま PTX になるわけではありません)。
bin/mojo-build.sh が --target-triple x86_64-apple-macosx15.0 を渡しているのはこのためです。
2.5 アセンブリの中に見える MLIR の痕跡
第1章で逆アセンブルした hello_mojo_minimal.asm には、MLIR の痕跡が文字列として刻まれています。
76: callq "_std::io::io::print[KGENParamList[::Writable],*::Writable,
LITImmutOrigin,::Origin[::Bool(False), $2]]
(*$0, sep:::StringSlice[...], end:::StringSlice[...],
flush:::Bool, file:::FileDescriptor$),
Ts.values`=[[typevalue<...>, struct<...>]]"
この長いシンボル名の中に KGENParamList という文字列が含まれています。
コラム: KGENParamList とは
KGEN(Kernel GENeration)は Mojo のコンパイラが内部で使う MLIR ダイアレクトの名前です。
KGENParamList は、このダイアレクトがジェネリクスの型パラメータリストを表現するために使う構造です。
Mojo の print はジェネリック関数として定義されており、どの型の引数を受け取るかをコンパイル時に特化(specialization)します。
その特化情報が KGENParamList[::Writable] という形でシンボル名に埋め込まれます。
つまり、逆アセンブルのシンボル名は「Mojo の型システム → KGEN ダイアレクト → マングル済みシンボル」という変換の最終結果です。
.asm を読むだけで、MLIR の変換経路の一端が見えます。
このように、アセンブリのシンボル名は MLIR の処理を通じて確定します。 第1章で「なぜシンボル名がこんなに長いのか」と感じたとすれば、その答えがここにあります。
2.6 Chris Lattner と LLVM/MLIR の系譜
Mojo の背景を理解するうえで、Chris Lattner の仕事の流れが参考になります。
時期 |
プロジェクト |
主な貢献 |
|---|---|---|
2000〜 |
LLVM |
汎用コンパイラ基盤。C、C++、Rust など多くの言語のバックエンドになる |
2010〜 |
Swift |
Apple のシステム言語。型安全と所有権の設計で Mojo の文法設計に影響 |
2019〜 |
MLIR |
Google で開発。多段 IR によるコンパイラ基盤。TensorFlow などに採用 |
2022〜 |
Mojo / Modular |
MLIR を基盤に AI インフラ向け言語を構築 |
注目すべきは「言語(Swift)とコンパイラ基盤(LLVM と MLIR)の両方を深く経験した人物が、その集大成として Mojo を設計した」という点です。 Mojo の ownership が Rust より柔らかく Python より明示的な形になっているのも、その経緯を反映しています。
2.7 CPU と GPU、同じコードから
MLIR が実現する最大のメリットの一つが、CPU と GPU の統一です。
MLIR 以前 |
Mojo + MLIR |
|
|---|---|---|
GPU カーネルの言語 |
CUDA C++(専用言語) |
Mojo(同じ言語) |
CPU コードとの共有 |
原則できない |
|
最適化のタイミング |
GPU と CPU で別々のツールチェーン |
lowering パスで一括 |
デバッグ |
GPU 専用ツール(cuda-gdb 等) |
統一されたコンパイラスタック |
現時点(2025 年)では GPU サポートはまだ発展途上ですが、この設計思想は 第2部 の 第12章 ポインタ・GPU・レイアウト 章で改めて取り上げます。
2.8 まとめ
AI インフラは「Python / C++ / CUDA / PTX」がレイヤーをまたいで混在する構造になっていました。
MLIR(Multi-Level IR)は、高水準から低水準まで複数の中間表現を段階的に変換(lowering)できるコンパイラ基盤です。
Mojo は MLIR を基盤とすることで、同じ高水準コードから CPU と GPU 向けの機械語を生成し分けられる設計を目指しています(GPU 対応は発展途上)。
第1章の
.asmに現れたKGENParamListは、MLIR のkgenダイアレクトがシンボル名に残した痕跡です。MLIR の詳細を暗記する必要はありません。「なぜ Mojo が一つの言語で幅広いハードウェアを扱えるか」の答えが MLIR にある、と理解すれば十分です。