第15章 相互運用の実例・導入判断・まとめ
15.1 この章で学ぶこと
この章で学ぶ内容は以下のとおりです。
学ぶこと |
ポイント |
|---|---|
Mojo → NumPy の実例 |
Python 資産をそのまま活かしながら Mojo を使う |
性能境界の設計 |
どこを Mojo にして、どこで Python を呼ぶかが鍵 |
導入判断 |
向く場面・向かない場面の整理 |
第2部 の要点まとめ |
アセンブリ証拠とともに学んだ言語仕様の全体像 |
15.2 Mojo から NumPy を呼ぶ
Mojo と NumPy の連携は、機械学習や科学計算のワークフローで最も現実的な相互運用パターンです。 「既存の NumPy コードをそのまま使いながら、ボトルネック部分だけ Mojo で書く」という段階的な移行ができます。
基本パターン:NumPy 配列の作成と参照
# Mojo から NumPy 配列を作成して表示する基本パターン。
# Python.import_module で numpy を取り込み、np.array で配列を作る。
from std.python import Python
def main() raises:
var np = Python.import_module("numpy")
var array = np.array(Python.list(1, 2, 3))
print(array)
リスト15-1: numpy_basic.mojo
出力:
[1 2 3]
ポイント |
説明 |
|---|---|
|
CPython を起動し |
|
NumPy の C 拡張が動く。Mojo のコンパイル済みコードは介在しない |
「自動高速化」の誤解 |
Mojo から呼んだ Python コードは CPython の世界で実行されるため、速度は変わらない |
配列の中身へのアクセス |
|
ゼロコピーで NumPy バッファを読む
NumPy 配列の実データは連続メモリバッファに格納されています。
ctypes.data で得た生アドレスを unsafe_get_as_pointer で Pointer に変換すれば、Mojo 側でコピーなしに読み書きできます。
# NumPy 配列のバッファを Mojo からゼロコピーで読み書きする例。
# ctypes.data の生アドレスを Pointer に変換し、書き込みは NumPy 側に反映される。
from std.python import Python
def main() raises:
var np = Python.import_module("numpy")
var arr = np.zeros(4, dtype=np.float64)
# ctypes.data は配列先頭の生アドレス(NumPy が所有するメモリ)。
var ptr = arr.ctypes.data.unsafe_get_as_pointer[DType.float64]()
# Mojo から直接書き込む。コピーせず NumPy 配列にそのまま反映される。
ptr[unsafe_offset=0] = 1.0
ptr[unsafe_offset=1] = 2.0
ptr[unsafe_offset=2] = 3.0
ptr[unsafe_offset=3] = 4.0
print(arr) # [1. 2. 3. 4.]
リスト15-2: numpy_zero_copy.mojo
データのコピーは発生しません。 NumPy と Mojo が同じメモリ領域を共有します。
段階導入のパターン
「Python を捨てずに、ボトルネックの部分だけ Mojo で書き直す」が現実的な導入パターンです。
図15-2: 段階導入のパターン
ポイントは 境界の数を最小化する ことです。 「データを NumPy で用意 → ポインタを Mojo に渡す → 計算結果を NumPy に戻す」という 1 往復に絞るほど、GIL 取得と型変換のコストが減ります。
15.3 導入判断
Mojo を実際のプロジェクトに導入するかどうかを判断するための基準を整理します。 「Mojo は万能ではない」という前提で、向く場面と向かない場面を見極めます。
向く場面・向かない場面
向く場面 |
向かない場面 |
最初に試す場所 |
|---|---|---|
Python で性能限界が見えている数値処理 |
一般 Web アプリ・スクリプト全体の全面移植 |
|
SIMD・GPU・カーネル寄りのコード |
Python の動的な感覚をそのまま期待する用途 |
NumPy interop でデータ受け渡しを試す |
CPU / GPU / AI accelerator を見据える処理 |
すぐに強い安定性が必要なプロダクション |
ホットパス 1 本だけ Mojo 化して速度比較 |
Python 資産を残したまま部分高速化 |
多人数チームでの全面移行(エコシステム成熟待ち) |
本書 第3部 の MicroGPT 実装を写経する |
導入を決める前のチェックリスト
Mojo 化を検討する前に、以下を確認します。
プロファイリングで本当にそこがボトルネックか?:
cProfileやpy-spy等でホットパスを特定してから検討する。「なんとなく遅い」でいきなり Mojo に移行しない。NumPy / PyTorch で解決できないか?:SIMD 最適化や GPU 転送は NumPy や PyTorch でも部分的に解決できる。Mojo が必要になるのは「既存ライブラリでは対応できない自前カーネル」が必要な場合。
チームが所有権、comptime、trait を学習するコストを払えるか?:Python に慣れたチームには学習コストがある。1 人の PoC から始めるのが現実的。
エコシステムの成熟を待てるか?:1.0.0 で言語仕様と標準ライブラリの主要部分は安定版になったが、サードパーティ製ライブラリやツールは Python ほど揃っていない。周辺エコシステムの充実を待つか、特定用途に限定する。
判断フロー
図15-3: Mojo 導入の判断フロー
コラム: 1.0 時点での現実的な評価
Mojo 1.0.0(本書執筆時点の最新安定版)は言語仕様と標準ライブラリの主要部分が安定版になりましたが、サードパーティ製ライブラリやツールを含むエコシステムは Python と比べてまだ小さいです。 「今すぐプロダクション全体を Mojo に移行する」よりも、性能が課題になっている部分を限定的に試す PoC から始めるのが現実的です。
一方で、uv run mojo で環境が整う手軽さと、Python 相互運用の充実度は今でも強みです。
15.4 第2部 の要点:アセンブリで学んだこと
第2部 では「言語仕様の説明」だけでなく、アセンブリを直接確認することで動作を証拠として示しました。 「コンパイラが何をしているか」を目で見て確かめることは、「なぜこの言語機能が速いのか」を理解する手がかりになります。
確認したアセンブリ証拠の一覧
言語機能 |
アセンブリの証拠 |
何を示すか |
|---|---|---|
|
|
スタック上に 8 バイト。コンパイル時にオフセット確定 |
SIMD 加算(第8章 8.2.3) |
|
1 命令で 2 つの Float64 を同時加算 |
|
|
|
|
|
参照なし(コピーまたは read) |
|
|
スコープ終了時にコンパイラが自動挿入 |
|
|
選ばれなかった枝はバイナリに存在しない |
|
3 つの |
ループ命令なし。完全に展開される |
|
シンボル名に |
コンパイル時 parameter の specialization |
アセンブリが教えてくれること
各証拠が示す「設計上の意図」を整理します。
「ゼロコスト」の意味:comptime if で選ばれなかった枝はバイナリに存在しません。
「実行時に分岐する」ではなく「使わないコードは生成されない」のが Mojo の comptime です。
「自動管理」の意味:__deinit__ の呼び出しはコンパイラが自動挿入します。
プログラマが free() を書かなくても、スコープを抜けた時点で確実に解放されます。
これは「実行時の GC が回収する」のではなく「コンパイル時にデストラクタ呼び出し位置が決まる」仕組みです。
SIMD の意味:addpd %xmm0, %xmm2 は 2 つの Float64 を同時に加算する 1 命令です。
Python のループで 1 要素ずつ処理するのとは異なる実行モデルです。
15.5 第2部 → 第3部 への橋渡し
第3部 では、第2部 で学んだ言語機能を使って MicroGPT(最小限の GPT 実装) を Mojo で書きます。 MicroGPT は GPT-2 相当のアーキテクチャを簡略化したもので、「トークン埋め込み → Transformer 層 → 出力確率」という推論パスを Mojo で実装します。
第2部 の知識がどこに使われるか
第2部 で学んだこと |
第3部 での使われ方 |
|---|---|
|
行列積・Softmax・ReLU・LayerNorm の SIMD ベクトル演算 |
|
|
|
シーケンス長・埋め込み次元・ヘッド数を定数化してループ展開 |
|
重みテンソルのメモリ配置とカーネルへの引き渡し |
Python interop |
tiktoken(トークナイザー)と |
|
層ごとの重みバッファのライフタイム管理 |
MicroGPT の全体像
図15-5: MicroGPT の全体像
第2部 をもう一度振り返る視点
第3部 を読むときに、次の問いを持つと理解が深まります。
structの__init__は何を確保しているか? → 重みバッファのallocmut selfを持つメソッドは何を変更しているか? → 中間活性化テンソルの書き換えcomptimeparameter はどこにあるか? →embed_dim・n_heads・seq_len等Pointerはどこに現れるか? → テンソルの生メモリアクセスPython interop の境界はどこか? → 起動時の重み読み込みとトークン変換のみ
15.6 まとめ
本章で扱った内容を以下にまとめます。
要素 |
押さえるべきこと |
|---|---|
Mojo の本質 |
Python に近い入口を持ちながら、ownership / comptime / SIMD が中心 |
Python interop の設計 |
ホットパスは Mojo に閉じ込め、境界をまたぐ回数を最小化 |
導入の現実 |
全面移行より部分 PoC が現実的(エコシステム発展中) |
アセンブリが示すこと |
|
次の一歩 |
第3部 MicroGPT で言語機能を実装レベルで体験する |