(ポインタ・GPU・レイアウト)= # ポインタ・GPU・レイアウト ## この章で学ぶこと - ポインタの基本と `unsafe` の考え方 - GPU 実行モデルの入口 - `Layout` と `LayoutTensor` の役割 この章のテーマは、**「安全な高級 API を優先し、必要なときだけ低水準へ降りる」** という設計の軸を理解することです。 - ポインタは `Pointer`(借用)や `OwnedPointer`(所有)から始め、`UnsafePointer` は最後の手段 - GPU も `DeviceContext` / `LayoutTensor` の高級抽象から入る - Layout は shape と stride を型で表した抽象で、直接バイト列を触る手前の層 今すぐ GPU や数値計算が必要でなければ、最初は見出しだけ追っても大丈夫です。 ## Pointers この節でいちばん大事なのは、**Mojo ではポインタも用途ごとに型を分けて扱う** ことです。 ### どんなポインタがあるか Mojo には、たとえば次のような種類があります。 - `Pointer`:借用 - `OwnedPointer`:所有 - `ArcPointer`:共有 - `UnsafePointer`:低水準操作用 ここで大事なのは、**ただの生アドレスとして扱うのではなく、意味を型で分ける** ことです。アライメント・ストライドの話とも、後述の GPU や layout ともつながります。 ### 基本の考え方 - まずは安全な API を優先する - 生ポインタに近い操作は `unsafe` に閉じ込める - 借用・所有・共有を意識して使い分ける つまり、**便利だからすぐ `UnsafePointer` を使う、という流れではない** ということです。 ### 最小の例 次の例は、`UnsafePointer` に近い操作を最小限で示したものです。**実アプリでは安全なラッパーを優先**し、こうした操作は **`unsafe` ブロックやレビュー体制のもと**に置く、という前提で読んでください。 ```{literalinclude} ../../../src/part2/ch10/pointers_unsafe_minimal.mojo :language: mojo ```

リスト-1: pointers_unsafe_minimal.mojo

この例では、 - `alloc[Int](1)` で 1 要素分の領域を確保する - `init_pointee_copy` で値を書き込む - `[]` で読み出す - 最後に `free` で解放する という流れになっています。 つまり、**確保して、使って、最後に返す** という順番です。**`Pointer` / `OwnedPointer` など別の型**は、マニュアルの API に沿って **より制約のはっきりした借用・所有**を表せます。 このプログラムをコンパイルすると以下のようになります。 ```{literalinclude} ../../../src/part2/ch10/pointers_unsafe_minimal.asm ```

リスト-2: pointers_unsafe_minimal.asm

詳細: [Pointers](https://docs.modular.com/mojo/manual/pointers/) 出典: [Mojo Manual — pointers](https://docs.modular.com/mojo/manual/pointers/) > **補足:** C に近い感覚がありますが、Mojo では意味ごとにポインタ型がわかれています。 ## Unsafe pointers この節は、**本当に低水準なメモリ操作をする場面** の話です。 ### 何をするのか `UnsafePointer` では、 - `alloc` / `free` で領域を管理する - `init_pointee` などで中身を初期化する - 必要なら自分で後始末する という流れを自分で扱います。 ### なぜ注意が必要か この領域では、コンパイラの安全保証が弱くなります。 そのため、`unsafe` ブロックで **ここは自分で責任を持つ操作だ** と明示します。 つまり、`unsafe` は「速そうだから使う」ものではなく、 **安全な方法では足りないときの最後の手段** です。FFI・低水準バッファで必要になる **最後の手段**として位置づけます。 ### 例 複数要素ぶんを連続確保し、**要素ごとに初期化**してから読み、**`destroy_pointee`** と **`free`** で後始末する流れです。`+ i` でポインタを進めている部分が、**配列のように見えるメモリ**を C 風に扱うイメージです。 次の例では、複数要素ぶんの領域を連続で確保して、要素ごとに初期化してから読み出しています。 ```{literalinclude} ../../../src/part2/ch10/unsafe_buffer_three_ints.mojo :language: mojo ```

リスト-3: unsafe_buffer_three_ints.mojo

ここで大事なのは、 - 初期化する - 使う - `destroy_pointee` と `free` で片づける という対応関係です。**`init_pointee_copy` と `destroy_pointee` を対にする**ことで、型のデストラクタが必要なオブジェクトを置く場合にも拡張しやすいパターンです。 実際の `unsafe` ブロックで囲むかどうかは、呼び出しコンテキストとマニュアルの推薦に沿ってください。 詳細: [Unsafe pointers](https://docs.modular.com/mojo/manual/pointers/unsafe-pointers/) 出典: [Mojo Manual — unsafe-pointers](https://docs.modular.com/mojo/manual/pointers/unsafe-pointers/) > **補足:** この領域は、実務ではレビュー前提で扱うと考えるのが安全です。 ## GPU architecture ここからは GPU の基本です。 この節でまず押さえたいのは、**GPU はたくさんのスレッドを同時に動かす前提で設計されている** ことです。 ### よく出てくる単語 - SM - warp - SIMT - grid - block - thread 最初は全部を厳密に覚えなくて大丈夫です。 まずは、**thread が集まって block になり、block が集まって grid になる** と捉えるとよいでしょう。 ### 何が大事か GPU では、多くの処理を小さな単位に分けて、一気に並列実行します。 そのとき、**自分が何番目のスレッドなのか** がよく重要になります。 1 次元では、たとえば次のように考えます。 - `block_id * block_dim + thread_in_block` これは、グローバルなスレッド番号の典型的な考え方です。 ### 例 次の例は、GPU 上で実行するコードではなく、 その番号計算の考え方だけを CPU 上で確認するためのものです。GPU では **スレッドが大量に並列**に動き、**ブロック**や **グリッド**といった単位でまとめられます(**実カーネルやデバイス API は含みません**。環境依存が大きいため、実行モデルの数式のイメージ用です)。 ```{literalinclude} ../../../src/part2/ch10/gpu_linear_thread_index.mojo :language: mojo ```

リスト-4: gpu_linear_thread_index.mojo

多次元グリッドやワープ単位の動きは、**マニュアルの図**とセットで見るのが確実です。 詳細: [GPU architecture](https://docs.modular.com/mojo/manual/gpu/architecture/) 出典: [Mojo Manual — gpu/architecture](https://docs.modular.com/mojo/manual/gpu/architecture/) > **補足:** 初学者は、ここはマニュアルの図と併せて確認すると理解が深まります。CUDA 経験者はスキップしてかまいません。 ## GPU fundamentals この節では、**GPU を使うときの大まかな流れ** を見ます。 ### 基本の流れ - CPU 側でデータを用意する - GPU 用のバッファへ渡す - カーネルを実行する - 結果を読み戻す ここでいう **カーネル** は、GPU 上で並列に動く関数です(戻り値はなく **バッファへ書き込みます**)。 ### よく出てくる名前 - `DeviceContext` - `compile_function` - `enqueue_function` - `DeviceBuffer` - `HostBuffer` 名前は少し多いですが、 要するに **GPU を使う準備・実行・データ受け渡し** のための部品です。 ### 例 次の例は、本物の `HostBuffer` ではなく、 CPU 側で値をまとめて持つイメージを `List` で簡単に示したものです。ホスト側で **入力データをまとめておき、カーネルに渡すバッファへコピーする**流れのうち、**ホスト上で値を保持する部分**のイメージに使えます(**`HostBuffer` そのものではありません**)。 ```{literalinclude} ../../../src/part2/ch10/gpu_host_buffer_list_stand_in.mojo :language: mojo ```

リスト-5: gpu_host_buffer_list_stand_in.mojo

実際の **`DeviceContext`・`DeviceBuffer`** による転送や同期は、**インストール環境とマニュアルの API** に沿ってください(名前や手順はバージョンで変わり得ます)。 詳細: [GPU fundamentals](https://docs.modular.com/mojo/manual/gpu/fundamentals/) 出典: [Mojo Manual — gpu/fundamentals](https://docs.modular.com/mojo/manual/gpu/fundamentals/) > **補足:** GPU 関連の API 名や細かな手順は、バージョンで変わることがあります。実際に試すときは公式を確認するのが安全です。 ## GPU block and warp この節は、**GPU の中でスレッド同士がどう協力するか** の話です。 ### まず押さえること - block 内では同期が必要になることがある(**`barrier` 等**) - warp 単位で効率よく通信できる場合がある(**shuffle** など) - 大きな処理を小さなタイルに分ける考え方が重要(**タイル化(tiling)**、共有メモリ・協調ロードはマニュアル本編) ### タイル化とは タイル化は、大きな配列や行列をそのまま処理せず、 **小さなかたまりに分けて順番に処理する** 考え方です。 これは GPU でとてもよく出てきます。 なぜなら、共有メモリや協調処理と相性がよいからです。 ### 例 次の例は、GPU カーネルそのものではなく、 CPU 上の二重ループでタイルの進み方だけを確認する簡単な形です。**タイル化**は、大きな配列を **小さなブロック(タイル)に分けて**順に処理する考え方のイメージです。GPU の共有メモリや協調ロードの説明はマニュアルに譲ります。 ```{literalinclude} ../../../src/part2/ch10/gpu_tile_loop_nest.mojo :language: mojo ```

リスト-6: gpu_tile_loop_nest.mojo

`16×16` を `4` ずつ進めると、タイル数は `4×4 = 16` になります。**実カーネルでのタイルとバリア**は [GPU block and warp](https://docs.modular.com/mojo/manual/gpu/block-and-warp/) のコード例を参照してください。 詳細: [GPU block and warp](https://docs.modular.com/mojo/manual/gpu/block-and-warp/) 出典: [Mojo Manual — gpu/block-and-warp](https://docs.modular.com/mojo/manual/gpu/block-and-warp/) > **補足:** ここは性能チューニングの入口です。実際の同期や共有メモリのコードは、まず公式例を見るのがおすすめです。 ## Layouts この節で大事なのは、**同じ配列でも、メモリ上の並び方を明示できる** ことです。 ### Layout とは `Layout` は、 **データの形と並び方を表すための仕組み** です。 ここでよく出てくるのが次の2つです。 - shape:形 - stride:並び方の間隔 つまり、**論理的にどう見えるか** と **実際にどう並んでいるか** を結びつける仕組みです。**コンパイル時**に **`IntTuple`** 等で表現し、**メモリ上の並び**と **論理インデックス**の対応を **型レベル**で固定します。**`tile`** などで **部分レイアウト**を切り出し、**カーネル**に渡す使い方にもつながります。 ### row-major の考え方 `Layout.row_major` は、行優先の並びです。 これは、最も右のインデックス側が連続したメモリになる並び方です。 ### 例 次の例では、`IntTuple` で形状を与え、ランクを確認しています。 ```{literalinclude} ../../../src/part2/ch10/layout_row_major_shape.mojo :language: mojo ```

リスト-7: layout_row_major_shape.mojo

**ストライド**や **`tile`** による部分ビューは、マニュアルで数値例と合わせて確認すると理解しやすくなります。 詳細: [Layouts](https://docs.modular.com/mojo/manual/layout/layouts/) 出典: [Mojo Manual — layout/layouts](https://docs.modular.com/mojo/manual/layout/layouts/) > **補足:** レイアウトは、次の `LayoutTensor` を理解するための前提です。 ## Layout tensors 最後に、**レイアウト情報つきの多次元データ** を見ます。 ### `LayoutTensor` とは `LayoutTensor` は、 **Layout で決めた並び方の上に、実際のデータを載せた多次元ビュー** です。 つまり、ただの配列ではなく、 **どう並んでいるかまでわかった状態で扱うテンソル** です。 ### 何ができるか - `tile` で分割する - `vectorize` でベクトル化を考える - `distribute` で並列処理の単位へ分ける このあたりは、CPU SIMD と GPU の両方に関わってきます。**GPU カーネル**と **CPU SIMD** の両方で **同じ抽象**を使う方向性です(詳細はマニュアルを参照します)。 ### 例 次の例は、CPU 上の `InlineArray` を使った小さな `2×3` テンソルです。 二重インデックスで要素にアクセスしています。 ```{literalinclude} ../../../src/part2/ch10/layout_tensor_small.mojo :language: mojo ```

リスト-8: layout_tensor_small.mojo

**`tile` や GPU バッファとの組み合わせ**は、ワークロードに応じてマニュアル本編のパターンを参照してください。 詳細: [Layout tensors](https://docs.modular.com/mojo/manual/layout/tensors/) 出典: [Mojo Manual — layout/tensors](https://docs.modular.com/mojo/manual/layout/tensors/) > **補足:** AI や数値計算では重要ですが、最初は「並び方つきのテンソル」と理解すれば十分です。 ## この章を一文で言うと **この章は、低水準なメモリ操作、GPU の並列実行、データの並び方を、Mojo でどう扱うかを見る章です。** ## まとめ - Mojo ではポインタも用途ごとに型がわかれている - `unsafe` は最後の手段として使う - GPU では thread・block・grid の階層を意識する - カーネル実行では、データ転送と同期の流れが重要になる - Layout は shape と stride を使って並び方を表す - `LayoutTensor` はレイアウトつきの多次元データを扱う仕組み この章は、今すぐ全部を使わなくても大丈夫です。 必要になったときに、**「メモリ」「並列実行」「並び方」** の3つに分けて読み返すと整理しやすいです。