(microgpt Mojo 版を MAX で高速化する)= # microgpt Mojo 版を MAX で高速化する ## この章で学ぶこと - Mojo 版 microgpt(Tape ベース)の学習はそのまま維持し、**推論だけ MAX に切り替える**方法 - Mojo の Tape ノード値を Python interop で numpy 配列に変換する方法 - MAX Graph で GPT 推論ループを再構築する方法 - `microgpt_mojo` と `microgpt_mojo_max` を比較して結果を確認する方法 --- ## 方針:学習と推論を分離する Mojo の Tape 実装はスカラーループのため推論が遅いですが、自動微分(学習)には最適です。 MAX Engine には学習(勾配計算)API がないため、学習は Tape のままにして推論だけ MAX に切り替えます。 ```{mermaid} flowchart LR A["学習
(Mojo Tape + Adam)
B1〜B6 変更なし"] -->|"t.node_data(id)
→ numpy float32"| B["重み抽出
b08_max_infer.mojo"] B -->|"Python interop
max_infer_helper.py"| C["MAX Graph
推論"] C --> D["生成結果"] ``` | フェーズ | 担当 | microgpt_mojo との違い | |---------|------|-----------------------| | 学習 | Mojo Tape + Adam(B1〜B6) | **なし** | | 重みの抽出 | `b08_max_infer.mojo`(新規) | 新規 | | 推論 | `max_infer_helper.py`(MAX Graph) | 新規(高速) | --- ## ディレクトリ構成 ``` src/part3/microgpt_mojo_max/ ├── b01_dataset.mojo ← microgpt_mojo からコピー(変更なし) ├── b02_tokenizer.mojo ← 同上 ├── b03_value.mojo ← 同上 ├── b04_state_dict.mojo ← 同上 ├── b05_ops.mojo ← 同上 ├── b05_gpt.mojo ← 同上 ├── b06_train.mojo ← 同上 ├── b08_max_infer.mojo ← 新規:重み抽出 + MAX 呼び出し ├── max_infer_helper.py ← 新規:MAX Graph 定義・推論ループ ├── main.mojo ← 変更:推論部分を b08 に差し替え └── input.txt ← microgpt_mojo からシンボリックリンク ``` --- ## B8: 重みの抽出と MAX 呼び出し(b08_max_infer.mojo) `b08_max_infer.mojo` は Mojo と MAX(Python)の橋渡しをする唯一の新規 Mojo ファイルです。 ```{literalinclude} ../../../src/part3/microgpt_mojo_max/b08_max_infer.mojo :language: mojo ``` **ポイント:** - `_matrix_to_numpy()` では `t.node_data(mat[i][j])` で Tape ノードの値(Float64)を取り出し、 `Python.list()` に積んで `np.array(...).reshape(rows, cols)` で ndarray に変換する - キー名は `"attn_wq_0"`, `"mlp_fc1_0"` のように `名前_層番号` で統一する - `sys.path.insert(0, ".")` で実行ディレクトリを Python パスに追加してから `max_infer_helper` をインポートする --- ## MAX Graph の定義(max_infer_helper.py) MAX Graph のコード(`EmbedLayer`・`AttentionLayer`・`MLPLayer`・`build_gpt_graph`・`run_inference`)は 純粋な Python ファイルにまとめます。 ```{literalinclude} ../../../src/part3/microgpt_mojo_max/max_infer_helper.py :language: python ``` **b08_max_infer.mojo との対応:** | Mojo 側(b08)の変数 | Python 側(helper)での使われ方 | |---------------------|-------------------------------| | `weights["wte"]` など | `build_gpt_graph(weights, hp)` に渡す | | `hp_dict` | `hp["n_embd"]` などで参照 | | `uchars_py` | `uchars[token_id]` で文字を逆引き | | `bos` | 生成終端の判定に使用 | --- ## main.mojo の変更点 学習ループは `microgpt_mojo/main.mojo` と**まったく同じ**です。 違いは末尾の推論部分だけです。 ```{literalinclude} ../../../src/part3/microgpt_mojo_max/main.mojo :language: mojo ``` **差分まとめ:** | 変更前(microgpt_mojo) | 変更後(microgpt_mojo_max) | |------------------------|---------------------------| | `from b07_infer import ...` | `from b08_max_infer import run_max_inference` | | Tape ベースの推論ループ | `run_max_inference(t, sd, hp, ...)` 1行 | --- ## 比較実行 どちらも同じ `input.txt` と `seed(42)` を使うため、 学習の loss 推移は**一致**します。 ```bash # Tape ベース推論(従来版) cd src/part3/microgpt_mojo && mojo run main.mojo # MAX Graph 推論(新版) cd src/part3/microgpt_mojo_max && mojo run main.mojo ``` | 比較項目 | 結果 | |---------|------| | 学習の loss 推移 | **同一**(同じ Tape 実装・同じ seed) | | 推論結果 | 異なりうる(確率的サンプリングのため) | | 推論速度 | MAX 版の方が速い(特に `n_embd` が大きい場合) | --- ## Tape 方式との速度比較 | 方式 | 推論 1 ステップの仕組み | 特徴 | |------|----------------------|------| | Mojo Tape | スカラー演算をループで追記・即時実行 | 遅い、自動微分可能 | | MAX Graph | グラフをコンパイル後に行列演算として一括実行 | 速い、SIMD/GPU 対応 | MAX Graph では行列演算をハードウェアの SIMD 命令や GPU カーネルにマッピングできるため、 `n_embd=16` 程度の小さなモデルでも推論のオーバーヘッドが大幅に減ります。 モデルが大きくなるほど(`n_embd=256`, `n_layer=6` など)差が顕著になります。 --- ## まとめ - 学習(Tape + Adam、B1〜B6)はそのまま活かし、**推論だけ MAX に切り替える**のが段階的な高速化の第一歩 - 重みの受け渡しは Mojo Python interop で `t.node_data()` → numpy に変換するだけ - MAX Graph の定義(Python)は `max_infer_helper.py` に分離し、Mojo からは1行で呼ぶ - `b08_max_infer.mojo` が Mojo と MAX の橋渡し役になる 本書で実装した microgpt の知識は、より大きなモデルへのスケールアップや MAX/Mojo による最適化の基礎になります。